diff --git a/BUILD.gn b/BUILD.gn
index 8a6af59..cf98951 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -1254,9 +1254,19 @@
 
   _common_web_test_script = "//testing/scripts/run_isolated_script_test.py"
 
-  _common_web_test_args = [ "@WrappedPath(" + rebase_path(
-                                "//third_party/blink/tools/run_web_tests.py",
-                                root_build_dir) + ")" ]
+  if (is_fuchsia) {
+    _common_web_test_args = [
+      "@WrappedPath(" +
+          rebase_path("//build/fuchsia/test/run_test.py", root_build_dir) + ")",
+      "blink",
+      "--out-dir",
+      "@WrappedPath(.)",
+    ]
+  } else {
+    _common_web_test_args = [ "@WrappedPath(" + rebase_path(
+                                  "//third_party/blink/tools/run_web_tests.py",
+                                  root_build_dir) + ")" ]
+  }
   if (is_debug) {
     _common_web_test_args += [ "--debug" ]
   } else {
@@ -1273,6 +1283,15 @@
     ]
   }
 
+  if (is_fuchsia) {
+    _common_web_test_args += [
+      "--platform",
+      "fuchsia",
+      "--jobs",
+      "1",
+    ]
+  }
+
   _common_web_test_args += [
     "--seed",
     "4",
@@ -1402,7 +1421,10 @@
         data += [ "//third_party/blink/web_tests/platform/linux/" ]
       }
       if (is_fuchsia) {
-        data += [ "//third_party/blink/web_tests/platform/fuchsia/" ]
+        data += [
+          "//third_party/blink/web_tests/platform/fuchsia/",
+          "$root_gen_dir/package_metadata/content_shell.meta",
+        ]
       }
     } else if (is_mac) {
       data += [
@@ -1470,7 +1492,10 @@
         data += [ "//third_party/blink/web_tests/platform/linux/" ]
       }
       if (is_fuchsia) {
-        data += [ "//third_party/blink/web_tests/platform/fuchsia/" ]
+        data += [
+          "//third_party/blink/web_tests/platform/fuchsia/",
+          "$root_gen_dir/package_metadata/content_shell.meta",
+        ]
       }
     } else if (is_mac) {
       data += [
diff --git a/DEPS b/DEPS
index 18de664d..64ed47c 100644
--- a/DEPS
+++ b/DEPS
@@ -295,7 +295,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': 'df5a545d503a2f79b4b1b91591a3e0836feb948a',
+  'skia_revision': '2691cd7b41104b8434fc2c06445d5cb968444314',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
@@ -303,7 +303,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': '81a3c71eec0c0df19e5cbfbab7c88e6db31b9477',
+  'angle_revision': 'f7cf32260066c04be1981d1c3b0c9a447b03c5c8',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -311,7 +311,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': '4e9c5b7500a79b5c3eb065a5bd63ed67b380ae5b',
+  'pdfium_revision': '9a02c9040e16d95c44e7dfec10d55c3ad0b39380',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling BoringSSL
   # and whatever else without interference from each other.
@@ -374,7 +374,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': '3754dd4154a2bb31a1efefcb2e37dc0669744bbd',
+  'devtools_frontend_revision': 'a6df1571d7accd1d57ab5f6be53638fbd52e4dd7',
   # 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.
@@ -410,7 +410,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': 'c9949ac59e50cec45f8bfceaef4358fa2e929971',
+  'dawn_revision': '837b8042d382e1b0618892bab2aa87fef2500e44',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -430,7 +430,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': '570c42c2c10a878c8cc896f1c5daf1a955274142',
+  'libavif_revision': 'bfb84ad6f9791be5b1d2d68e43bf1c79fc17b06c',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling nearby
   # and whatever else without interference from each other.
@@ -478,13 +478,14 @@
 
   # If you change this, also update the libc++ revision in
   # //buildtools/deps_revisions.gni.
-  'libcxx_revision':       'e4e39cee1f5e89a9f9949084ba26a6efc3cd4141',
+  'libcxx_revision':       '52399655fdafdd14ade17ab12ddc9e955423aa5a',
 
   # GN CIPD package version.
   'gn_version': 'git_revision:70d6c60823c0233a0f35eccc25b2b640d2980bdc',
 
   # ninja CIPD package version.
   # https://chrome-infra-packages.appspot.com/p/infra/3pp/tools/ninja
+  # This has to stay in sync with the version in src/third_party/ninja/README.chromium.
   'ninja_version': 'version:2@1.8.2.chromium.3',
 }
 
@@ -561,15 +562,6 @@
     ],
     'dep_type': 'cipd',
   },
-  'src/third_party/ninja': {
-    'packages': [
-      {
-        'package': 'infra/3pp/tools/ninja/${{platform}}',
-        'version': Var('ninja_version'),
-      }
-    ],
-    'dep_type': 'cipd',
-  },
   'src/third_party/android_rust_toolchain/toolchain': {
     'packages': [
       {
@@ -776,12 +768,12 @@
 
   'src/clank': {
     'url': 'https://chrome-internal.googlesource.com/clank/internal/apps.git' + '@' +
-    'c37c81a1b327a1fbe28a583d142e44a75654d494',
+    'e44cb668c0f02c605ef86b216a01f85e5aab8e5d',
     'condition': 'checkout_android and checkout_src_internal',
   },
 
   'src/docs/website': {
-    'url': Var('chromium_git') + '/website.git' + '@' + '5d599f528a177175564f7b79eb3906c94ff28e68',
+    'url': Var('chromium_git') + '/website.git' + '@' + 'f4045351063427a57a8d0c019983483b450f50f7',
   },
 
   'src/ios/third_party/earl_grey2/src': {
@@ -790,7 +782,7 @@
   },
 
   'src/ios/third_party/edo/src': {
-      'url': Var('chromium_git') + '/external/github.com/google/eDistantObject.git' + '@' + '904c99f0237920066a507129b0266080db3fda11',
+      'url': Var('chromium_git') + '/external/github.com/google/eDistantObject.git' + '@' + '1d58a069cbc6c6838656d95b2746bb21c0fe7e1e',
       'condition': 'checkout_ios',
   },
 
@@ -875,7 +867,7 @@
       'packages': [
         {
           'package': 'chromium/rts/model/linux-amd64',
-          'version': 'jiHtHdUMilIuwzbcsQlGdAjat-y-eDGcK6-FAs5ANUMC',
+          'version': 'GyNxvmRcqsmWeXBYdqpRvDZNUoAZ81Rgr24IaKfoUcUC',
         },
       ],
       'dep_type': 'cipd',
@@ -886,7 +878,7 @@
       'packages': [
         {
           'package': 'chromium/rts/model/mac-amd64',
-          'version': 'qxYR6mF7oeg0RUwannR8cPfPbCpvnXmEVA4-jg-Z6GIC',
+          'version': 'reNR6RZngQjw5yiGM0r0mztUvLY2b0KzH8--1Kpmh4QC',
         },
       ],
       'dep_type': 'cipd',
@@ -897,7 +889,7 @@
       'packages': [
         {
           'package': 'chromium/rts/model/windows-amd64',
-          'version': 'dk1NkAN_VOc5Mg8FvzntxNgpyQHLUtOhbyMjIyMkg-wC',
+          'version': '3gQqhc79s84TWnf86PyMteWjqdMvCjC7MYGEC2ukHv0C',
         },
       ],
       'dep_type': 'cipd',
@@ -965,7 +957,7 @@
     'packages': [
       {
           'package': 'chromium/third_party/androidx',
-          'version': 'HscLs2ReHeJnHLbPuX949OD2wsLnxxiTjgFCTS4_seoC',
+          'version': 'dDsZortX-1TkjllBOI1gUUjDsrr7Oh32bM1szHmRU7IC',
       },
     ],
     'condition': 'checkout_android',
@@ -1131,7 +1123,7 @@
     Var('boringssl_git') + '/boringssl.git' + '@' +  Var('boringssl_revision'),
 
   'src/third_party/breakpad/breakpad':
-    Var('chromium_git') + '/breakpad/breakpad.git' + '@' + '87b60390f05bc91a13d0c636f7ce75fa2f7533ca',
+    Var('chromium_git') + '/breakpad/breakpad.git' + '@' + 'cc7abac08b0c52e6581b9c9c4226816b17a4c26d',
 
   'src/third_party/byte_buddy': {
       'packages': [
@@ -1213,13 +1205,13 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'd259b3164494eb84d00f57a172c26633361415c8',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '8e77bba1aebf5a675cae6ef45079a080134ebfc6',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
 
   'src/third_party/devtools-frontend-internal': {
-      'url': 'https://chrome-internal.googlesource.com/devtools/devtools-internal.git' + '@' + '6044ea5fb29d5bd6689e6f8f20b196137cf4c381',
+      'url': 'https://chrome-internal.googlesource.com/devtools/devtools-internal.git' + '@' + '7a7443fee22fbb08bc497fac72f2b705cf7657d1',
     'condition': 'checkout_src_internal',
   },
 
@@ -1434,7 +1426,7 @@
     Var('chromium_git') + '/external/libaddressinput.git' + '@' + 'df35d6c42da4fa2759e4cfb592afe33817993b89',
 
   'src/third_party/libaom/source/libaom':
-    Var('aomedia_git') + '/aom.git' + '@' +  'bee1caded272127a6d6b70ac79479083d183d5d0',
+    Var('aomedia_git') + '/aom.git' + '@' +  'c0239a23c24796ddc003f2a3199a9014a1930a80',
 
   'src/third_party/libavif/src':
     Var('chromium_git') + '/external/github.com/AOMediaCodec/libavif.git' + '@' + Var('libavif_revision'),
@@ -1606,6 +1598,16 @@
       'condition': 'checkout_android',
   },
 
+  'src/third_party/ninja': {
+    'packages': [
+      {
+        'package': 'infra/3pp/tools/ninja/${{platform}}',
+        'version': Var('ninja_version'),
+      }
+    ],
+    'dep_type': 'cipd',
+  },
+
   'src/third_party/objenesis': {
       'packages': [
           {
@@ -1638,7 +1640,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + '591266c4ffd77425d8da9f8b982941e23faf115d',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'b5161f639122c92cba8384ed62f2de376f6d1e73',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1783,7 +1785,7 @@
       'dep_type': 'cipd',
   },
 
-  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@7935ee7ccbc0074344db22c88702fcb0595272e5',
+  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@89b9b4762b94dcd7416846c0ef956b7c1a03291d',
 
   'src/third_party/vulkan_memory_allocator':
     Var('chromium_git') + '/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git' + '@' + 'ebe84bec02c041d28f902da0214bf442743fc907',
@@ -1820,7 +1822,7 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + 'd1b65aa5a88f6efd900604dfcda840154e9f16e2',
 
   'src/third_party/webgpu-cts/src':
-    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '677ece7d76116e07601e0d90f7084f17b3c57d8b',
+    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + 'b6f75840c6bbfc7d58e1366bea12ee9ad675eed8',
 
   'src/third_party/webrtc':
     Var('webrtc_git') + '/src.git' + '@' + '01cac31d581b515c4f513196a7fdaebd04547809',
@@ -1893,7 +1895,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@fb7a6591ecf61919e4eafa5b7b219ec732409f1f',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@685d5c37f8f7c1baa808bab220eac3c4f2f4838e',
     'condition': 'checkout_src_internal',
   },
 
@@ -1934,7 +1936,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/media_app/app',
-        'version': 'h1ZgxYFoEqsBUdk5OZVRIJDs-r_YRE_lCrxXh3M4PVQC',
+        'version': 'MBoigIg8MEjU0OIA-ccAjqE3NJA6C1M6nvwxXsHsRCcC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -1945,7 +1947,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/projector_app/app',
-        'version': '8uuVwVIJ0BQSNmRz6dH2W-rAnZTt-BGF4iRXESgWF5cC',
+        'version': 'YqOpDJhXb5RCCJjKmdBTMsxhnkhtMBfKMiqBtQsCCC8C',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index e112f4f..cc5574a 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -228,6 +228,7 @@
           r'.*Test[^a-z]',
           r'third_party/',
           'base/android/java/src/org/chromium/base/ContextUtils.java',
+          'chromecast/browser/android/apk/src/org/chromium/chromecast/shell/BroadcastReceiverScope.java',
       ),
     ),
     BanRule(
diff --git a/android_webview/browser/BUILD.gn b/android_webview/browser/BUILD.gn
index 84ccab8..2a1ed4ab 100644
--- a/android_webview/browser/BUILD.gn
+++ b/android_webview/browser/BUILD.gn
@@ -132,8 +132,6 @@
     "icon_helper.h",
     "js_java_interaction/aw_web_message_host_factory.cc",
     "js_java_interaction/aw_web_message_host_factory.h",
-    "js_java_interaction/converter.cc",
-    "js_java_interaction/converter.h",
     "js_java_interaction/js_reply_proxy.cc",
     "js_java_interaction/js_reply_proxy.h",
     "network_service/aw_network_change_notifier.cc",
diff --git a/android_webview/browser/aw_client_hints_controller_delegate.cc b/android_webview/browser/aw_client_hints_controller_delegate.cc
index 693e136..93ecc29 100644
--- a/android_webview/browser/aw_client_hints_controller_delegate.cc
+++ b/android_webview/browser/aw_client_hints_controller_delegate.cc
@@ -74,19 +74,20 @@
 bool AwClientHintsControllerDelegate::IsJavaScriptAllowed(
     const GURL& url,
     content::RenderFrameHost* parent_rfh) {
+  // Javascript can only be disabled per-frame, so if we're pre-loading
+  // and/or there is no frame Javascript is considered enabled.
   if (!parent_rfh) {
-    return false;
+    return true;
   }
   content::WebContents* web_contents =
       content::WebContents::FromRenderFrameHost(
           parent_rfh->GetOutermostMainFrame());
   if (!web_contents) {
-    // TODO(crbug.com/921655): Detect and support service workers here.
-    return false;
+    return true;
   }
   AwContents* aw_contents = AwContents::FromWebContents(web_contents);
   if (!aw_contents) {
-    return false;
+    return true;
   }
   return aw_contents->IsJavaScriptAllowed();
 }
@@ -94,23 +95,10 @@
 bool AwClientHintsControllerDelegate::AreThirdPartyCookiesBlocked(
     const GURL& url,
     content::RenderFrameHost* rfh) {
-  if (!rfh) {
-    return true;
-  }
-  content::WebContents* web_contents =
-      content::WebContents::FromRenderFrameHost(rfh);
-  if (!web_contents) {
-    // TODO(crbug.com/921655): Detect and support service workers here.
-    return true;
-  }
-  AwContents* aw_contents = AwContents::FromWebContents(web_contents);
-  if (!aw_contents) {
-    return true;
-  }
-  // Despite the name of the function we want to mirror the function of
-  // ClientHints::AreThirdPartyCookiesBlocked and check the global block too.
-  return !AwCookieAccessPolicy::GetInstance()->GetShouldAcceptCookies() ||
-         !aw_contents->AllowThirdPartyCookies();
+  // This function is related to an OT for the Sec-CH-UA-Reduced client hint
+  // and as this doesn't affect WebView at the moment, we have no reason to
+  // implement it.
+  return false;
 }
 
 blink::UserAgentMetadata
@@ -128,8 +116,9 @@
       !network::IsUrlPotentiallyTrustworthy(primary_url)) {
     return;
   }
-  if (!IsJavaScriptAllowed(primary_url, parent_rfh))
+  if (!IsJavaScriptAllowed(primary_url, parent_rfh)) {
     return;
+  }
   if (client_hints.size() >
       (static_cast<size_t>(network::mojom::WebClientHintsType::kMaxValue) +
        1)) {
diff --git a/android_webview/browser/aw_client_hints_controller_delegate_unittest.cc b/android_webview/browser/aw_client_hints_controller_delegate_unittest.cc
index 94aa8e3..235eb42 100644
--- a/android_webview/browser/aw_client_hints_controller_delegate_unittest.cc
+++ b/android_webview/browser/aw_client_hints_controller_delegate_unittest.cc
@@ -96,19 +96,17 @@
 }
 
 TEST_F(AwClientHintsControllerDelegateTest, IsJavaScriptAllowed) {
-  EXPECT_FALSE(client_hints_controller_delegate_->IsJavaScriptAllowed(GURL(""),
-                                                                      nullptr));
-  EXPECT_FALSE(client_hints_controller_delegate_->IsJavaScriptAllowed(
+  EXPECT_TRUE(client_hints_controller_delegate_->IsJavaScriptAllowed(GURL(""),
+                                                                     nullptr));
+  EXPECT_TRUE(client_hints_controller_delegate_->IsJavaScriptAllowed(
       GURL("https://example.com/"), nullptr));
-  // TODO(crbug.com/921655): Add integration test when the rest is implemented.
 }
 
 TEST_F(AwClientHintsControllerDelegateTest, AreThirdPartyCookiesBlocked) {
-  EXPECT_TRUE(client_hints_controller_delegate_->AreThirdPartyCookiesBlocked(
+  EXPECT_FALSE(client_hints_controller_delegate_->AreThirdPartyCookiesBlocked(
       GURL(""), nullptr));
-  EXPECT_TRUE(client_hints_controller_delegate_->AreThirdPartyCookiesBlocked(
+  EXPECT_FALSE(client_hints_controller_delegate_->AreThirdPartyCookiesBlocked(
       GURL("https://example.com"), nullptr));
-  // TODO(crbug.com/921655): Add integration test when the rest is implemented.
 }
 
 TEST_F(AwClientHintsControllerDelegateTest, GetUserAgentMetadata) {
@@ -135,15 +133,6 @@
       url::Origin::Create(GURL("http://example.com")), &enabled_hints);
   EXPECT_TRUE(enabled_hints.GetEnabledHints().empty());
 
-  // Delegate that bans JavaScript can't persist hints.
-  enabled_hints = blink::EnabledClientHints();
-  client_hints_controller_delegate_->PersistClientHints(
-      url::Origin::Create(GURL("https://example.com")), nullptr,
-      {network::mojom::WebClientHintsType::kDeviceMemory});
-  client_hints_controller_delegate_->GetAllowedClientHintsFromSource(
-      url::Origin::Create(GURL("https://example.com")), &enabled_hints);
-  EXPECT_TRUE(enabled_hints.GetEnabledHints().empty());
-
   // Persisting hints for one origin doesnt affect others.
   enabled_hints = blink::EnabledClientHints();
   permissive_client_hints_controller_delegate_->PersistClientHints(
diff --git a/android_webview/browser/aw_metrics_service_client_delegate.cc b/android_webview/browser/aw_metrics_service_client_delegate.cc
index 8ff5584..b08cd94 100644
--- a/android_webview/browser/aw_metrics_service_client_delegate.cc
+++ b/android_webview/browser/aw_metrics_service_client_delegate.cc
@@ -8,6 +8,7 @@
 #include "android_webview/browser/lifecycle/aw_contents_lifecycle_notifier.h"
 #include "android_webview/browser/metrics/aw_component_metrics_provider_delegate.h"
 #include "android_webview/browser/metrics/aw_metrics_service_client.h"
+#include "android_webview/browser/metrics/aw_server_side_allowlist_metrics_provider.h"
 #include "android_webview/browser/metrics/renderer_process_metrics_provider.h"
 #include "android_webview/browser/metrics/visibility_metrics_provider.h"
 #include "android_webview/browser/page_load_metrics/aw_page_load_metrics_provider.h"
@@ -34,6 +35,9 @@
               AwMetricsServiceClient::GetInstance())));
   service->RegisterMetricsProvider(
       std::make_unique<tracing::AwBackgroundTracingMetricsProvider>());
+  service->RegisterMetricsProvider(
+      std::make_unique<AwServerSideAllowlistMetricsProvider>(
+          AwMetricsServiceClient::GetInstance()));
 }
 
 void AwMetricsServiceClientDelegate::AddWebViewAppStateObserver(
diff --git a/android_webview/browser/component_updater/loader_policies/aw_apps_package_names_allowlist_component_loader_policy.cc b/android_webview/browser/component_updater/loader_policies/aw_apps_package_names_allowlist_component_loader_policy.cc
index 44b112f..2313d29 100644
--- a/android_webview/browser/component_updater/loader_policies/aw_apps_package_names_allowlist_component_loader_policy.cc
+++ b/android_webview/browser/component_updater/loader_policies/aw_apps_package_names_allowlist_component_loader_policy.cc
@@ -14,6 +14,7 @@
 #include <vector>
 
 #include "android_webview/browser/metrics/aw_metrics_service_client.h"
+#include "android_webview/common/aw_features.h"
 #include "android_webview/common/aw_switches.h"
 #include "android_webview/common/components/aw_apps_package_names_allowlist_component_utils.h"
 #include "android_webview/common/metrics/app_package_name_logging_rule.h"
@@ -270,6 +271,12 @@
     AwMetricsServiceClient* metrics_service_client) {
   DCHECK(metrics_service_client);
 
+  // Prevent loading of client-side allowlist if using server-side allowlist
+  if (base::FeatureList::IsEnabled(
+          android_webview::features::
+              kWebViewAppsPackageNamesServerSideAllowlist)) {
+    return;
+  }
   absl::optional<AppPackageNameLoggingRule> cached_record =
       metrics_service_client->GetCachedAppPackageNameLoggingRule();
   base::Time last_update =
diff --git a/android_webview/browser/js_java_interaction/aw_web_message_host_factory.cc b/android_webview/browser/js_java_interaction/aw_web_message_host_factory.cc
index c6a668dc..a7a0b24 100644
--- a/android_webview/browser/js_java_interaction/aw_web_message_host_factory.cc
+++ b/android_webview/browser/js_java_interaction/aw_web_message_host_factory.cc
@@ -44,7 +44,7 @@
     Java_WebMessageListenerHolder_onPostMessage(
         env, listener_,
         base::android::ConvertUTF16ToJavaString(
-            env, message->message->get_string_value()),
+            env, absl::get<std::u16string>(message->message)),
         base::android::ConvertUTF8ToJavaString(env, origin_string_),
         is_main_frame_, jports, reply_proxy_.GetJavaPeer());
   }
diff --git a/android_webview/browser/js_java_interaction/converter.cc b/android_webview/browser/js_java_interaction/converter.cc
deleted file mode 100644
index 4f9a79d2..0000000
--- a/android_webview/browser/js_java_interaction/converter.cc
+++ /dev/null
@@ -1,32 +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.
-
-#include "android_webview/browser/js_java_interaction/converter.h"
-
-#include "base/functional/overloaded.h"
-#include "components/js_injection/common/interfaces.mojom.h"
-#include "third_party/abseil-cpp/absl/types/variant.h"
-#include "third_party/blink/public/common/messaging/string_message_codec.h"
-
-namespace android_webview {
-
-js_injection::mojom::JsWebMessagePtr ConvertToJsWebMessagePtr(
-    blink::WebMessagePayload payload) {
-  return absl::visit(
-      base::Overloaded{
-          [](std::u16string& str) {
-            return js_injection::mojom::JsWebMessage::NewStringValue(
-                std::move(str));
-          },
-          [](std::unique_ptr<blink::WebMessageArrayBufferPayload>&
-                 array_buffer) {
-            mojo_base::BigBuffer big_buffer(array_buffer->GetLength());
-            array_buffer->CopyInto(big_buffer);
-            return js_injection::mojom::JsWebMessage::NewArrayBufferValue(
-                std::move(big_buffer));
-          }},
-      payload);
-}
-
-}  // namespace android_webview
diff --git a/android_webview/browser/js_java_interaction/converter.h b/android_webview/browser/js_java_interaction/converter.h
deleted file mode 100644
index f417318f..0000000
--- a/android_webview/browser/js_java_interaction/converter.h
+++ /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.
-
-#ifndef ANDROID_WEBVIEW_BROWSER_JS_JAVA_INTERACTION_CONVERTER_H_
-#define ANDROID_WEBVIEW_BROWSER_JS_JAVA_INTERACTION_CONVERTER_H_
-
-#include "components/js_injection/common/interfaces.mojom-forward.h"
-#include "third_party/blink/public/common/messaging/string_message_codec.h"
-
-namespace android_webview {
-
-// Converts |blink::WebMessagePayload| to |mojom::JsWebMessagePtr|.
-js_injection::mojom::JsWebMessagePtr ConvertToJsWebMessagePtr(
-    blink::WebMessagePayload payload);
-
-}  // namespace android_webview
-
-#endif  // ANDROID_WEBVIEW_BROWSER_JS_JAVA_INTERACTION_CONVERTER_H_
diff --git a/android_webview/browser/js_java_interaction/js_reply_proxy.cc b/android_webview/browser/js_java_interaction/js_reply_proxy.cc
index f9b6a82..75a09fc 100644
--- a/android_webview/browser/js_java_interaction/js_reply_proxy.cc
+++ b/android_webview/browser/js_java_interaction/js_reply_proxy.cc
@@ -6,11 +6,9 @@
 
 #include <utility>
 
-#include "android_webview/browser/js_java_interaction/converter.h"
 #include "android_webview/browser_jni_headers/JsReplyProxy_jni.h"
 #include "base/android/jni_string.h"
 #include "components/js_injection/browser/web_message_reply_proxy.h"
-#include "components/js_injection/common/interfaces.mojom.h"
 #include "content/public/browser/android/message_payload.h"
 
 namespace android_webview {
@@ -37,9 +35,9 @@
 void JsReplyProxy::PostMessage(
     JNIEnv* env,
     const base::android::JavaParamRef<jobject>& payload) {
-  reply_proxy_->PostWebMessage(ConvertToJsWebMessagePtr(
+  reply_proxy_->PostWebMessage(
       content::android::ConvertToWebMessagePayloadFromJava(
-          base::android::ScopedJavaLocalRef<jobject>(payload))));
+          base::android::ScopedJavaLocalRef<jobject>(payload)));
 }
 
 }  // namespace android_webview
diff --git a/android_webview/browser/metrics/BUILD.gn b/android_webview/browser/metrics/BUILD.gn
index 3eddf723a..58520b1b 100644
--- a/android_webview/browser/metrics/BUILD.gn
+++ b/android_webview/browser/metrics/BUILD.gn
@@ -9,6 +9,8 @@
     "aw_metrics_service_accessor.h",
     "aw_metrics_service_client.cc",
     "aw_metrics_service_client.h",
+    "aw_server_side_allowlist_metrics_provider.cc",
+    "aw_server_side_allowlist_metrics_provider.h",
     "renderer_process_metrics_provider.cc",
     "renderer_process_metrics_provider.h",
     "visibility_metrics_logger.cc",
diff --git a/android_webview/browser/metrics/aw_metrics_service_client.cc b/android_webview/browser/metrics/aw_metrics_service_client.cc
index 412076b..9ccde949 100644
--- a/android_webview/browser/metrics/aw_metrics_service_client.cc
+++ b/android_webview/browser/metrics/aw_metrics_service_client.cc
@@ -64,6 +64,11 @@
 
 AwMetricsServiceClient* g_aw_metrics_service_client = nullptr;
 
+bool IsAppPackageNameServerSideAllowlistEnabled() {
+  return base::FeatureList::IsEnabled(
+      android_webview::features::kWebViewAppsPackageNamesServerSideAllowlist);
+}
+
 }  // namespace
 
 const base::TimeDelta kRecordAppDataDirectorySizeDelay = base::Seconds(10);
@@ -122,9 +127,15 @@
 }
 
 bool AwMetricsServiceClient::ShouldRecordPackageName() {
+  // Record app package name when app consent, user consent,
+  // and server-side allowlist is used
+  if (IsAppPackageNameServerSideAllowlistEnabled()) {
+    return true;
+  }
   base::UmaHistogramEnumeration(
       "Android.WebView.Metrics.PackagesAllowList.RecordStatus",
       package_name_record_status_);
+
   if (!cached_package_name_record_.has_value() ||
       !cached_package_name_record_.value().IsAppPackageNameAllowed()) {
     return false;
diff --git a/android_webview/browser/metrics/aw_metrics_service_client_unittest.cc b/android_webview/browser/metrics/aw_metrics_service_client_unittest.cc
index 14e1c937..f5bb711 100644
--- a/android_webview/browser/metrics/aw_metrics_service_client_unittest.cc
+++ b/android_webview/browser/metrics/aw_metrics_service_client_unittest.cc
@@ -69,7 +69,6 @@
         prefs_(std::make_unique<TestingPrefServiceSimple>()),
         client_(std::make_unique<AwMetricsServiceTestClient>(
             std::make_unique<AwMetricsServiceClientTestDelegate>())) {
-    // Required by MetricsService.
     base::SetRecordActionTaskRunner(task_runner_);
     AwMetricsServiceTestClient::RegisterMetricsPrefs(prefs_->registry());
     client_->Initialize(prefs_.get());
@@ -205,6 +204,18 @@
       expiry_time.InHours(), 1);
 }
 
+TEST_F(
+    AwMetricsServiceClientTest,
+    TestServerSideAllowlist_TestShouldRecordPackageNameWithServerSideAllowlistEnabled) {
+  base::test::ScopedFeatureList scoped_list;
+  scoped_list.InitAndEnableFeature(
+      android_webview::features::kWebViewAppsPackageNamesServerSideAllowlist);
+
+  AwMetricsServiceClient* client = GetClient();
+  EXPECT_TRUE(client->ShouldRecordPackageName());
+  EXPECT_FALSE(client->GetCachedAppPackageNameLoggingRule().has_value());
+}
+
 TEST_F(AwMetricsServiceClientTest,
        TestShouldRecordPackageName_TestFailureAfterValidResult) {
   base::HistogramTester histogram_tester;
diff --git a/android_webview/browser/metrics/aw_server_side_allowlist_metrics_provider.cc b/android_webview/browser/metrics/aw_server_side_allowlist_metrics_provider.cc
new file mode 100644
index 0000000..6d2d2e6
--- /dev/null
+++ b/android_webview/browser/metrics/aw_server_side_allowlist_metrics_provider.cc
@@ -0,0 +1,57 @@
+// 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 "android_webview/browser/metrics/aw_server_side_allowlist_metrics_provider.h"
+
+#include "android_webview/browser/metrics/aw_metrics_service_client.h"
+#include "android_webview/common/aw_features.h"
+#include "components/embedder_support/android/metrics/android_metrics_service_client.h"
+#include "third_party/metrics_proto/system_profile.pb.h"
+
+namespace {
+bool IsServerSideAllowlistEnabled() {
+  return base::FeatureList::IsEnabled(
+      android_webview::features::kWebViewAppsPackageNamesServerSideAllowlist);
+}
+}  // namespace
+
+namespace android_webview {
+AwServerSideAllowlistMetricsProvider::AwServerSideAllowlistMetricsProvider()
+    : client_(nullptr) {}
+
+AwServerSideAllowlistMetricsProvider::AwServerSideAllowlistMetricsProvider(
+    AwMetricsServiceClient* client)
+    : client_(client) {}
+
+void AwServerSideAllowlistMetricsProvider::ProvideSystemProfileMetrics(
+    metrics::SystemProfileProto* system_profile) {
+  if (!IsServerSideAllowlistEnabled()) {
+    system_profile->set_app_package_name_allowlist_filter(
+        metrics::SystemProfileProto::
+            NO_SERVER_SIDE_FILTER_REQUIRED_DUE_TO_CLIENT_FILTERING);
+    return;
+  }
+
+  if (IsAppPackageNameSystemApp()) {
+    system_profile->set_app_package_name_allowlist_filter(
+        metrics::SystemProfileProto::
+            NO_SERVER_SIDE_FILTER_REQUIRED_FOR_SYSTEM_APPS);
+  } else {
+    system_profile->set_app_package_name_allowlist_filter(
+        metrics::SystemProfileProto::SERVER_SIDE_FILTER_REQUIRED);
+  }
+}
+
+bool AwServerSideAllowlistMetricsProvider::IsAppPackageNameSystemApp() {
+  return GetInstallerPackageType() ==
+         metrics::AndroidMetricsServiceClient::InstallerPackageType::SYSTEM_APP;
+}
+
+metrics::AndroidMetricsServiceClient::InstallerPackageType
+AwServerSideAllowlistMetricsProvider::GetInstallerPackageType() {
+  DCHECK(client_);
+  return client_->GetInstallerPackageType();
+}
+
+}  // namespace android_webview
diff --git a/android_webview/browser/metrics/aw_server_side_allowlist_metrics_provider.h b/android_webview/browser/metrics/aw_server_side_allowlist_metrics_provider.h
new file mode 100644
index 0000000..b4a97f6
--- /dev/null
+++ b/android_webview/browser/metrics/aw_server_side_allowlist_metrics_provider.h
@@ -0,0 +1,40 @@
+// 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 ANDROID_WEBVIEW_BROWSER_METRICS_AW_SERVER_SIDE_ALLOWLIST_METRICS_PROVIDER_H_
+#define ANDROID_WEBVIEW_BROWSER_METRICS_AW_SERVER_SIDE_ALLOWLIST_METRICS_PROVIDER_H_
+
+#include "android_webview/browser/metrics/aw_metrics_service_client.h"
+#include "components/metrics/metrics_provider.h"
+#include "third_party/metrics_proto/system_profile.pb.h"
+
+namespace android_webview {
+
+class AwServerSideAllowlistMetricsProvider : public ::metrics::MetricsProvider {
+ public:
+  AwServerSideAllowlistMetricsProvider();
+  explicit AwServerSideAllowlistMetricsProvider(AwMetricsServiceClient* client);
+
+  AwServerSideAllowlistMetricsProvider(
+      const AwServerSideAllowlistMetricsProvider&) = delete;
+  AwServerSideAllowlistMetricsProvider& operator=(
+      const AwServerSideAllowlistMetricsProvider&) = delete;
+
+  ~AwServerSideAllowlistMetricsProvider() override = default;
+
+  void ProvideSystemProfileMetrics(
+      metrics::SystemProfileProto* system_profile) override;
+
+ private:
+  metrics::AndroidMetricsServiceClient::InstallerPackageType
+  GetInstallerPackageType();
+
+  bool IsAppPackageNameSystemApp();
+
+  AwMetricsServiceClient* client_;
+};
+
+}  // namespace android_webview
+
+#endif  // ANDROID_WEBVIEW_BROWSER_METRICS_AW_SERVER_SIDE_ALLOWLIST_METRICS_PROVIDER_H_
\ No newline at end of file
diff --git a/android_webview/browser/metrics/aw_server_side_allowlist_metrics_provider_unittests.cc b/android_webview/browser/metrics/aw_server_side_allowlist_metrics_provider_unittests.cc
new file mode 100644
index 0000000..4786537
--- /dev/null
+++ b/android_webview/browser/metrics/aw_server_side_allowlist_metrics_provider_unittests.cc
@@ -0,0 +1,112 @@
+// 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 "android_webview/browser/metrics/aw_server_side_allowlist_metrics_provider.h"
+
+#include "android_webview/browser/metrics/aw_metrics_service_client.h"
+#include "android_webview/common/aw_features.h"
+#include "base/test/scoped_feature_list.h"
+#include "components/embedder_support/android/metrics/android_metrics_service_client.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/metrics_proto/chrome_user_metrics_extension.pb.h"
+
+namespace android_webview {
+
+using InstallerPackageType =
+    metrics::AndroidMetricsServiceClient::InstallerPackageType;
+
+namespace {
+
+class AwMetricsServiceClientTestDelegate
+    : public AwMetricsServiceClient::Delegate {
+  void RegisterAdditionalMetricsProviders(
+      metrics::MetricsService* service) override {}
+  void AddWebViewAppStateObserver(WebViewAppStateObserver* observer) override {}
+  bool HasAwContentsEverCreated() const override { return false; }
+};
+
+class TestClient : public AwMetricsServiceClient {
+ public:
+  TestClient()
+      : AwMetricsServiceClient(
+            std::make_unique<AwMetricsServiceClientTestDelegate>()) {}
+  ~TestClient() override = default;
+
+  InstallerPackageType GetInstallerPackageType() override {
+    return installer_type_;
+  }
+
+  void SetInstallerPackageType(InstallerPackageType installer_type) {
+    installer_type_ = installer_type;
+  }
+
+ private:
+  InstallerPackageType installer_type_;
+};
+
+class AwServerSideAllowlistMetricsProviderTest : public testing::Test {};
+
+}  // namespace
+
+}  // namespace android_webview
+
+namespace android_webview {
+
+TEST_F(AwServerSideAllowlistMetricsProviderTest,
+       TestServerSideAllowlist_TestServerSideFilteringRequired) {
+  base::test::ScopedFeatureList scoped_list;
+  TestClient client;
+  AwServerSideAllowlistMetricsProvider test_provider(&client);
+  metrics::ChromeUserMetricsExtension uma_proto;
+
+  client.SetInstallerPackageType(InstallerPackageType::GOOGLE_PLAY_STORE);
+  scoped_list.InitAndEnableFeature(
+      android_webview::features::kWebViewAppsPackageNamesServerSideAllowlist);
+  test_provider.ProvideSystemProfileMetrics(uma_proto.mutable_system_profile());
+  EXPECT_TRUE(uma_proto.mutable_system_profile()
+                  ->has_app_package_name_allowlist_filter());
+  EXPECT_TRUE(
+      uma_proto.mutable_system_profile()->app_package_name_allowlist_filter() ==
+      metrics::SystemProfileProto::SERVER_SIDE_FILTER_REQUIRED);
+}
+
+TEST_F(
+    AwServerSideAllowlistMetricsProviderTest,
+    TestServerSideAllowlist_TestNoServerSideFilteringDueToClientSideFiltering) {
+  base::test::ScopedFeatureList scoped_list;
+  TestClient client;
+  AwServerSideAllowlistMetricsProvider test_provider(&client);
+  metrics::ChromeUserMetricsExtension uma_proto;
+
+  client.SetInstallerPackageType(InstallerPackageType::GOOGLE_PLAY_STORE);
+  scoped_list.Init();
+  test_provider.ProvideSystemProfileMetrics(uma_proto.mutable_system_profile());
+  EXPECT_TRUE(uma_proto.mutable_system_profile()
+                  ->has_app_package_name_allowlist_filter());
+  EXPECT_TRUE(
+      uma_proto.mutable_system_profile()->app_package_name_allowlist_filter() ==
+      metrics::SystemProfileProto::
+          NO_SERVER_SIDE_FILTER_REQUIRED_DUE_TO_CLIENT_FILTERING);
+}
+
+TEST_F(AwServerSideAllowlistMetricsProviderTest,
+       TestServerSideAllowlist_TestNoServerSideFilteringForSystemApp) {
+  base::test::ScopedFeatureList scoped_list;
+  TestClient client;
+  AwServerSideAllowlistMetricsProvider test_provider(&client);
+  metrics::ChromeUserMetricsExtension uma_proto;
+
+  client.SetInstallerPackageType(InstallerPackageType::SYSTEM_APP);
+  scoped_list.InitAndEnableFeature(
+      android_webview::features::kWebViewAppsPackageNamesServerSideAllowlist);
+  test_provider.ProvideSystemProfileMetrics(uma_proto.mutable_system_profile());
+  EXPECT_TRUE(uma_proto.mutable_system_profile()
+                  ->has_app_package_name_allowlist_filter());
+  EXPECT_TRUE(
+      uma_proto.mutable_system_profile()->app_package_name_allowlist_filter() ==
+      metrics::SystemProfileProto::
+          NO_SERVER_SIDE_FILTER_REQUIRED_FOR_SYSTEM_APPS);
+}
+
+}  // namespace android_webview
diff --git a/android_webview/common/aw_features.cc b/android_webview/common/aw_features.cc
index 963aaec2..03f38a1 100644
--- a/android_webview/common/aw_features.cc
+++ b/android_webview/common/aw_features.cc
@@ -11,6 +11,14 @@
 
 // Alphabetical:
 
+// Enables package name logging for the most popular WebView embedders that are
+// on a dynamically generated allowlist.
+// The filtering for package names will be done on the server side using this
+// flag
+BASE_FEATURE(kWebViewAppsPackageNamesServerSideAllowlist,
+             "WebViewAppsPackageNamesServerSideAllowlist",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 // Enable brotli compression support in WebView.
 BASE_FEATURE(kWebViewBrotliSupport,
              "WebViewBrotliSupport",
@@ -139,7 +147,7 @@
 // This persists client hints between top-level navigations.
 BASE_FEATURE(kWebViewClientHintsControllerDelegate,
              "WebViewClientHintsControllerDelegate",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 // This enables image drage out for Webview.
 BASE_FEATURE(kWebViewImageDrag,
diff --git a/android_webview/common/aw_features.h b/android_webview/common/aw_features.h
index 78b2bc43..bd4efd4 100644
--- a/android_webview/common/aw_features.h
+++ b/android_webview/common/aw_features.h
@@ -16,6 +16,7 @@
 // alongside the definition of their values in the .cc file.
 
 // Alphabetical:
+BASE_DECLARE_FEATURE(kWebViewAppsPackageNamesServerSideAllowlist);
 BASE_DECLARE_FEATURE(kWebViewBrotliSupport);
 BASE_DECLARE_FEATURE(kWebViewCheckReturnResources);
 BASE_DECLARE_FEATURE(kWebViewClientHintsControllerDelegate);
diff --git a/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java b/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java
index fbf7df16..c40c129 100644
--- a/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java
+++ b/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java
@@ -129,6 +129,9 @@
             Flag.baseFeature(AwFeatures.WEBVIEW_CONNECTIONLESS_SAFE_BROWSING,
                     "Uses GooglePlayService's 'connectionless' APIs for Safe Browsing "
                             + "security checks."),
+            Flag.baseFeature(AwFeatures.WEBVIEW_APPS_PACKAGE_NAMES_SERVER_SIDE_ALLOWLIST,
+                    "Enables usage of server-side allowlist filtering of"
+                            + " app package names."),
             Flag.baseFeature(AwFeatures.WEBVIEW_BROTLI_SUPPORT,
                     "Enables brotli compression support in WebView."),
             Flag.baseFeature(AwFeatures.WEBVIEW_EXTRA_HEADERS_SAME_ORIGIN_ONLY,
@@ -355,6 +358,8 @@
                             + "on this device."),
             Flag.baseFeature(AwFeatures.WEBVIEW_IMAGE_DRAG,
                     "If enabled, images can be dragged out from Webview"),
+            Flag.baseFeature(BlinkFeatures.WEB_RTC_COMBINED_NETWORK_AND_WORKER_THREAD,
+                    "Combines WebRTC's worker thread and network thread onto a single thread."),
             // Add new commandline switches and features above. The final entry should have a
             // trailing comma for cleaner diffs.
     };
diff --git a/android_webview/java/src/org/chromium/android_webview/common/services/ServiceHelper.java b/android_webview/java/src/org/chromium/android_webview/common/services/ServiceHelper.java
index 2260ef6b..c573eb9 100644
--- a/android_webview/java/src/org/chromium/android_webview/common/services/ServiceHelper.java
+++ b/android_webview/java/src/org/chromium/android_webview/common/services/ServiceHelper.java
@@ -30,14 +30,9 @@
     public static boolean bindService(
             Context context, Intent intent, ServiceConnection serviceConnection, int flags) {
         try {
-            boolean serviceBindingSuccess = context.bindService(intent, serviceConnection, flags);
-            if (!serviceBindingSuccess) {
-                context.unbindService(serviceConnection);
-            }
-            return serviceBindingSuccess;
+            return context.bindService(intent, serviceConnection, flags);
         } catch (ReceiverCallNotAllowedException e) {
             // If we're running in a BroadcastReceiver Context then we cannot bind to Services.
-            context.unbindService(serviceConnection);
             return false;
         } catch (SecurityException e) {
             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
@@ -48,7 +43,6 @@
                 // exception and carry on.
                 Log.e(TAG, "Unable to bind to services from a secondary user account on Android N",
                         e);
-                context.unbindService(serviceConnection);
                 return false;
             } else {
                 throw e;
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/ClientHintsTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/ClientHintsTest.java
index 7d414024..63cfb36 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/ClientHintsTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/ClientHintsTest.java
@@ -10,9 +10,8 @@
 
 import androidx.test.filters.SmallTest;
 
-import org.junit.After;
+import org.json.JSONObject;
 import org.junit.Assert;
-import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -26,13 +25,12 @@
 import org.chromium.base.test.util.DoNotBatch;
 import org.chromium.base.test.util.Feature;
 import org.chromium.content_public.common.ContentSwitches;
+import org.chromium.net.test.ServerCertificate;
 
-import java.util.Collections;
-import java.util.Map;
 import java.util.concurrent.TimeUnit;
 
 /**
- * Test suite for loadUrl().
+ * Test suite for AwClientHintsControllerDelegate.
  */
 @DoNotBatch(reason = "These tests conflict with each other.")
 @RunWith(AwJUnit4ClassRunner.class)
@@ -40,166 +38,259 @@
     @Rule
     public AwActivityTestRule mActivityTestRule = new AwActivityTestRule();
 
-    private AwContents mAwContents;
-    private AwCookieManager mCookieManager;
-    private AwEmbeddedTestServer mTestServer;
-    private TestAwContentsClient mContentsClient;
-    private String mLocalhostUrl;
-    private String mFooUrl;
+    @Test
+    @SmallTest
+    @Feature({"AndroidWebView"})
+    @CommandLineFlags.Add({ContentSwitches.HOST_RESOLVER_RULES + "=MAP * 127.0.0.1"})
+    public void testClientHintsDefault() throws Throwable {
+        setupAndVerifyClientHintBehavior(true);
+    }
 
-    private static final String LIGHT = "light";
-    private static final String NONE = "None";
+    @Test
+    @SmallTest
+    @Feature({"AndroidWebView"})
+    @CommandLineFlags.
+    Add({"disable-features=" + AwFeatures.WEBVIEW_CLIENT_HINTS_CONTROLLER_DELEGATE,
+            ContentSwitches.HOST_RESOLVER_RULES + "=MAP * 127.0.0.1"})
+    public void
+    testClientHintsDisabled() throws Throwable {
+        setupAndVerifyClientHintBehavior(false);
+    }
 
-    @Before
-    public void setUp() {
-        mCookieManager = new AwCookieManager();
-        mContentsClient = new TestAwContentsClient();
-        final AwTestContainerView testContainerView =
-                mActivityTestRule.createAwTestContainerViewOnMainSync(mContentsClient);
-        mAwContents = testContainerView.getAwContents();
-        mAwContents.getSettings().setJavaScriptEnabled(true);
-        mTestServer = AwEmbeddedTestServer.createAndStartServer(
+    @Test
+    @SmallTest
+    @Feature({"AndroidWebView"})
+    @CommandLineFlags.Add({"enable-features=" + AwFeatures.WEBVIEW_CLIENT_HINTS_CONTROLLER_DELEGATE,
+            ContentSwitches.HOST_RESOLVER_RULES + "=MAP * 127.0.0.1"})
+    public void
+    testClientHintsEnabled() throws Throwable {
+        setupAndVerifyClientHintBehavior(true);
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"AndroidWebView"})
+    @CommandLineFlags.Add({ContentSwitches.HOST_RESOLVER_RULES + "=MAP * 127.0.0.1"})
+    public void testAllClientHints() throws Throwable {
+        // Initial test setup.
+        final TestAwContentsClient contentsClient = new TestAwContentsClient();
+        final AwContents contents =
+                mActivityTestRule.createAwTestContainerViewOnMainSync(contentsClient)
+                        .getAwContents();
+        AwActivityTestRule.enableJavaScriptOnUiThread(contents);
+        contents.getSettings().setJavaScriptEnabled(true);
+        final AwEmbeddedTestServer server = AwEmbeddedTestServer.createAndStartServer(
                 InstrumentationRegistry.getInstrumentation().getTargetContext());
-        mLocalhostUrl = mTestServer.getURL("/echoheader?sec-ch-prefers-color-scheme");
-        mFooUrl = mTestServer.getURLWithHostName(
-                "foo.test", "/echoheader?sec-ch-prefers-color-scheme");
-    }
 
-    @After
-    public void tearDown() {
-        try {
-            clearCookies();
-        } catch (Throwable e) {
-            throw new RuntimeException("Could not clear cookies.");
-        }
-        mTestServer.stopAndDestroyServer();
+        // Please keep these here (and below) in the same order as web_client_hints_types.mojom.
+        final String url = server.getURL(
+                "/client-hints-header?accept-ch=device-memory,dpr,width,viewport-width,"
+                + "rtt,downlink,ect,sec-ch-lang,sec-ch-ua,sec-ch-ua-arch,sec-ch-ua-platform,"
+                + "sec-ch-ua-model,sec-ch-ua-mobile,sec-ch-ua-full-version,"
+                + "sec-ch-ua-platform-version,sec-ch-prefers-color-scheme,"
+                + "sec-ch-ua-bitness,sec-ch-ua-reduced,sec-ch-viewport-height,"
+                + "sec-ch-device-memory,sec-ch-dpr,sec-ch-width,sec-ch-viewport-width,"
+                + "sec-ch-ua-full-version-list,sec-ch-ua-full,sec-ch-ua-wow64,save-data,"
+                + "sec-ch-prefers-reduced-motion");
+
+        // Load twice to be sure hints are returned, then parse the results.
+        loadUrlSync(contents, contentsClient.getOnPageFinishedHelper(), url);
+        loadUrlSync(contents, contentsClient.getOnPageFinishedHelper(), url);
+        String textContent =
+                mActivityTestRule.getJavaScriptResultBodyTextContent(contents, contentsClient)
+                        .replaceAll("\\\\\"", "\"");
+        JSONObject jsonObject = new JSONObject(textContent);
+        // If you're here because this line broke, please update this test to verify whichever
+        // client hints were added or removed and don't just modify the number below.
+        Assert.assertEquals(27, jsonObject.length());
+
+        // All client hints must be verified for default behavior.
+        Assert.assertTrue(jsonObject.getInt("device-memory") > 0);
+        Assert.assertTrue(jsonObject.getDouble("dpr") > 0);
+        // This is only set for subresources.
+        Assert.assertEquals("HEADER_NOT_FOUND", jsonObject.getString("width"));
+        Assert.assertTrue(jsonObject.getInt("viewport-width") > 0);
+        Assert.assertEquals(0, jsonObject.getInt("rtt"));
+        Assert.assertEquals(0, jsonObject.getInt("downlink"));
+        // This is the holdback value (the default in some cases).
+        Assert.assertEquals("4g", jsonObject.getString("ect"));
+        // This client hint was removed.
+        Assert.assertFalse(jsonObject.has("sec-ch-lang"));
+        // User agent client hints are inactive on android webview.
+        Assert.assertEquals("HEADER_NOT_FOUND", jsonObject.getString("sec-ch-ua"));
+        // User agent client hints are inactive on android webview.
+        Assert.assertEquals("HEADER_NOT_FOUND", jsonObject.getString("sec-ch-ua-arch"));
+        // User agent client hints are inactive on android webview.
+        Assert.assertEquals("HEADER_NOT_FOUND", jsonObject.getString("sec-ch-ua-platform"));
+        // User agent client hints are inactive on android webview.
+        Assert.assertEquals("HEADER_NOT_FOUND", jsonObject.getString("sec-ch-ua-model"));
+        // User agent client hints are inactive on android webview.
+        Assert.assertEquals("HEADER_NOT_FOUND", jsonObject.getString("sec-ch-ua-mobile"));
+        // User agent client hints are inactive on android webview.
+        Assert.assertEquals("HEADER_NOT_FOUND", jsonObject.getString("sec-ch-ua-full-version"));
+        // User agent client hints are inactive on android webview.
+        Assert.assertEquals("HEADER_NOT_FOUND", jsonObject.getString("sec-ch-ua-platform-version"));
+        Assert.assertEquals("light", jsonObject.getString("sec-ch-prefers-color-scheme"));
+        // User agent client hints are inactive on android webview.
+        Assert.assertEquals("HEADER_NOT_FOUND", jsonObject.getString("sec-ch-ua-bitness"));
+        // User agent client hints are inactive on android webview.
+        Assert.assertEquals("HEADER_NOT_FOUND", jsonObject.getString("sec-ch-ua-reduced"));
+        Assert.assertTrue(jsonObject.getInt("sec-ch-viewport-height") > 0);
+        Assert.assertTrue(jsonObject.getInt("sec-ch-device-memory") > 0);
+        Assert.assertTrue(jsonObject.getDouble("sec-ch-dpr") > 0);
+        // This is only set for subresources.
+        Assert.assertEquals("HEADER_NOT_FOUND", jsonObject.getString("sec-ch-width"));
+        Assert.assertTrue(jsonObject.getInt("sec-ch-viewport-width") > 0);
+        // User agent client hints are inactive on android webview.
+        Assert.assertEquals(
+                "HEADER_NOT_FOUND", jsonObject.getString("sec-ch-ua-full-version-list"));
+        // User agent client hints are inactive on android webview.
+        Assert.assertEquals("HEADER_NOT_FOUND", jsonObject.getString("sec-ch-ua-full"));
+        // User agent client hints are inactive on android webview.
+        Assert.assertEquals("HEADER_NOT_FOUND", jsonObject.getString("sec-ch-ua-wow64"));
+        // This client hint isn't sent when data-saver is off.
+        Assert.assertEquals("HEADER_NOT_FOUND", jsonObject.getString("save-data"));
+        Assert.assertEquals("no-preference", jsonObject.getString("sec-ch-prefers-reduced-motion"));
+
+        // Cleanup after test.
+        clearCookies();
+        server.stopAndDestroyServer();
     }
 
     @Test
     @SmallTest
     @Feature({"AndroidWebView"})
-    @CommandLineFlags.
-    Add({"disable-features=" + AwFeatures.WEBVIEW_CLIENT_HINTS_CONTROLLER_DELEGATE,
-            ContentSwitches.HOST_RESOLVER_RULES + "=MAP * 127.0.0.1"})
-    public void
-    testClientHintsWithoutPersistance() throws Throwable {
-        // First load of the localhost shouldn't have the hint as it wasn't requested before.
-        loadUrlWithExtraHeadersSync(mAwContents, mContentsClient.getOnPageFinishedHelper(),
-                mLocalhostUrl,
-                Collections.singletonMap("Accept-CH", "Sec-CH-Prefers-Color-Scheme"));
-        String header =
-                mActivityTestRule.getJavaScriptResultBodyTextContent(mAwContents, mContentsClient);
-        Assert.assertEquals(NONE, header);
+    @CommandLineFlags.Add({ContentSwitches.HOST_RESOLVER_RULES + "=MAP * 127.0.0.1"})
+    public void testCriticalClientHints() throws Throwable {
+        // Initial test setup.
+        final TestAwContentsClient contentsClient = new TestAwContentsClient();
+        final AwContents contents =
+                mActivityTestRule.createAwTestContainerViewOnMainSync(contentsClient)
+                        .getAwContents();
+        AwActivityTestRule.enableJavaScriptOnUiThread(contents);
+        contents.getSettings().setJavaScriptEnabled(true);
+        final AwEmbeddedTestServer server = AwEmbeddedTestServer.createAndStartServer(
+                InstrumentationRegistry.getInstrumentation().getTargetContext());
 
-        // Second load of the localhost shouldn't have the hint as it wasn't persisted before.
-        loadUrlWithExtraHeadersSync(mAwContents, mContentsClient.getOnPageFinishedHelper(),
-                mLocalhostUrl,
-                Collections.singletonMap("Accept-CH", "Sec-CH-Prefers-Color-Scheme"));
-        header = mActivityTestRule.getJavaScriptResultBodyTextContent(mAwContents, mContentsClient);
-        Assert.assertEquals(NONE, header);
+        // First we verify that sec-ch-device-memory (critical) is returned on the first load.
+        String url = server.getURL("/critical-client-hints-header?accept-ch=sec-ch-device-memory&"
+                + "critical-ch=sec-ch-device-memory");
+        loadUrlSync(contents, contentsClient.getOnPageFinishedHelper(), url);
+        validateHeadersFromJSON(contents, contentsClient, "sec-ch-device-memory", true);
+        validateHeadersFromJSON(contents, contentsClient, "device-memory", false);
+
+        // Second we verify that device-memory (not critical) won't cause a reload.
+        url = server.getURL(
+                "/critical-client-hints-header?accept-ch=sec-ch-device-memory,device-memory&"
+                + "critical-ch=sec-ch-device-memory");
+        loadUrlSync(contents, contentsClient.getOnPageFinishedHelper(), url);
+        validateHeadersFromJSON(contents, contentsClient, "sec-ch-device-memory", true);
+        validateHeadersFromJSON(contents, contentsClient, "device-memory", false);
+
+        // Third we verify that device-memory is returned on the final load even with no request.
+        url = server.getURL("/critical-client-hints-header?accept-ch=sec-ch-device-memory&"
+                + "critical-ch=sec-ch-device-memory");
+        loadUrlSync(contents, contentsClient.getOnPageFinishedHelper(), url);
+        validateHeadersFromJSON(contents, contentsClient, "sec-ch-device-memory", true);
+        validateHeadersFromJSON(contents, contentsClient, "device-memory", true);
+
+        // Cleanup after test.
+        clearCookies();
+        server.stopAndDestroyServer();
+    }
+
+    private void setupAndVerifyClientHintBehavior(boolean isPersisted) throws Throwable {
+        final TestAwContentsClient contentsClient = new TestAwContentsClient();
+        final AwContents contents =
+                mActivityTestRule.createAwTestContainerViewOnMainSync(contentsClient)
+                        .getAwContents();
+        AwActivityTestRule.enableJavaScriptOnUiThread(contents);
+        contents.getSettings().setJavaScriptEnabled(true);
+
+        // First round uses insecure server.
+        AwEmbeddedTestServer server = AwEmbeddedTestServer.createAndStartServer(
+                InstrumentationRegistry.getInstrumentation().getTargetContext());
+        verifyClientHintBehavior(server, contents, contentsClient, isPersisted, false);
+        clearCookies();
+        server.stopAndDestroyServer();
+
+        // Second round uses secure server.
+        server = AwEmbeddedTestServer.createAndStartHTTPSServer(
+                InstrumentationRegistry.getInstrumentation().getTargetContext(),
+                ServerCertificate.CERT_OK);
+        verifyClientHintBehavior(server, contents, contentsClient, isPersisted, true);
+        clearCookies();
+        server.stopAndDestroyServer();
+    }
+
+    private void verifyClientHintBehavior(final AwEmbeddedTestServer server,
+            final AwContents contents, final TestAwContentsClient contentsClient,
+            boolean isPersisted, boolean isSecure) throws Throwable {
+        final String localhostURL =
+                server.getURL("/client-hints-header?accept-ch=sec-ch-device-memory");
+        final String fooURL = server.getURLWithHostName(
+                "foo.test", "/client-hints-header?accept-ch=sec-ch-device-memory");
+
+        // First load of the localhost shouldn't have the hint as it wasn't requested before.
+        loadUrlSync(contents, contentsClient.getOnPageFinishedHelper(), localhostURL);
+        validateHeadersFromJSON(contents, contentsClient, "sec-ch-device-memory", false);
+
+        // Second load of the localhost might have the hint if it was persisted.
+        loadUrlSync(contents, contentsClient.getOnPageFinishedHelper(), localhostURL);
+        validateHeadersFromJSON(contents, contentsClient, "sec-ch-device-memory", isPersisted);
 
         // Clearing cookies to clear out per-origin client hint preferences.
         clearCookies();
 
         // Third load of the localhost shouldn't have the hint as hint prefs were cleared.
-        loadUrlWithExtraHeadersSync(mAwContents, mContentsClient.getOnPageFinishedHelper(),
-                mLocalhostUrl,
-                Collections.singletonMap("Accept-CH", "Sec-CH-Prefers-Color-Scheme"));
-        header = mActivityTestRule.getJavaScriptResultBodyTextContent(mAwContents, mContentsClient);
-        Assert.assertEquals(NONE, header);
+        loadUrlSync(contents, contentsClient.getOnPageFinishedHelper(), localhostURL);
+        validateHeadersFromJSON(contents, contentsClient, "sec-ch-device-memory", false);
 
-        // Fourth load of the localhost shouldn't have the hint as it wasn't persisted before.
-        loadUrlWithExtraHeadersSync(mAwContents, mContentsClient.getOnPageFinishedHelper(),
-                mLocalhostUrl,
-                Collections.singletonMap("Accept-CH", "Sec-CH-Prefers-Color-Scheme"));
-        header = mActivityTestRule.getJavaScriptResultBodyTextContent(mAwContents, mContentsClient);
-        Assert.assertEquals(NONE, header);
+        // Fourth load of the localhost might have the hint if it was persisted.
+        loadUrlSync(contents, contentsClient.getOnPageFinishedHelper(), localhostURL);
+        validateHeadersFromJSON(contents, contentsClient, "sec-ch-device-memory", isPersisted);
+
+        // Fifth load of the localhost won't have the hint as JavaScript will be off.
+        contents.getSettings().setJavaScriptEnabled(false);
+        loadUrlSync(contents, contentsClient.getOnPageFinishedHelper(), localhostURL);
+        contents.getSettings().setJavaScriptEnabled(true);
+        validateHeadersFromJSON(contents, contentsClient, "sec-ch-device-memory", false);
 
         // First load of foo.test shouldn't have the hint as it wasn't requested before.
-        loadUrlWithExtraHeadersSync(mAwContents, mContentsClient.getOnPageFinishedHelper(), mFooUrl,
-                Collections.singletonMap("Accept-CH", "Sec-CH-Prefers-Color-Scheme"));
-        header = mActivityTestRule.getJavaScriptResultBodyTextContent(mAwContents, mContentsClient);
-        Assert.assertEquals(NONE, header);
+        loadUrlSync(contents, contentsClient.getOnPageFinishedHelper(), fooURL);
+        validateHeadersFromJSON(contents, contentsClient, "sec-ch-device-memory", false);
 
-        // Second load of foo.test shouldn't have the hint as it wasn't persisted before.
-        loadUrlWithExtraHeadersSync(mAwContents, mContentsClient.getOnPageFinishedHelper(), mFooUrl,
-                Collections.singletonMap("Accept-CH", "Sec-CH-Prefers-Color-Scheme"));
-        header = mActivityTestRule.getJavaScriptResultBodyTextContent(mAwContents, mContentsClient);
-        Assert.assertEquals(NONE, header);
+        // Second load of foo.test might have the hint if it was persisted and the site is secure.
+        loadUrlSync(contents, contentsClient.getOnPageFinishedHelper(), fooURL);
+        validateHeadersFromJSON(
+                contents, contentsClient, "sec-ch-device-memory", isPersisted && isSecure);
     }
 
-    @Test
-    @SmallTest
-    @Feature({"AndroidWebView"})
-    @CommandLineFlags.
-    Add({"disable-features=" + AwFeatures.WEBVIEW_CLIENT_HINTS_CONTROLLER_DELEGATE,
-            ContentSwitches.HOST_RESOLVER_RULES + "=MAP * 127.0.0.1"})
-    public void
-    testClientHintsWithPersistance() throws Throwable {
-        // First load of the localhost shouldn't have the hint as it wasn't requested before.
-        loadUrlWithExtraHeadersSync(mAwContents, mContentsClient.getOnPageFinishedHelper(),
-                mLocalhostUrl,
-                Collections.singletonMap("Accept-CH", "Sec-CH-Prefers-Color-Scheme"));
-        String header =
-                mActivityTestRule.getJavaScriptResultBodyTextContent(mAwContents, mContentsClient);
-        Assert.assertEquals(NONE, header);
-
-        // Second load of the localhost should have the hint as it was persisted before.
-        loadUrlWithExtraHeadersSync(mAwContents, mContentsClient.getOnPageFinishedHelper(),
-                mLocalhostUrl,
-                Collections.singletonMap("Accept-CH", "Sec-CH-Prefers-Color-Scheme"));
-        header = mActivityTestRule.getJavaScriptResultBodyTextContent(mAwContents, mContentsClient);
-        // TODO(crbug.com/921655): Replace the expectations when this is working.
-        // Assert.assertEquals(LIGHT, header);
-        Assert.assertEquals(NONE, header);
-
-        // Clearing cookies to clear out per-origin client hint preferences.
-        clearCookies();
-
-        // Third load of the localhost shouldn't have the hint as hint prefs were cleared.
-        loadUrlWithExtraHeadersSync(mAwContents, mContentsClient.getOnPageFinishedHelper(),
-                mLocalhostUrl,
-                Collections.singletonMap("Accept-CH", "Sec-CH-Prefers-Color-Scheme"));
-        header = mActivityTestRule.getJavaScriptResultBodyTextContent(mAwContents, mContentsClient);
-        Assert.assertEquals(NONE, header);
-
-        // Fourth load of the localhost should have the hint as it was persisted before.
-        loadUrlWithExtraHeadersSync(mAwContents, mContentsClient.getOnPageFinishedHelper(),
-                mLocalhostUrl,
-                Collections.singletonMap("Accept-CH", "Sec-CH-Prefers-Color-Scheme"));
-        header = mActivityTestRule.getJavaScriptResultBodyTextContent(mAwContents, mContentsClient);
-        // TODO(crbug.com/921655): Replace the expectations when this is working.
-        // Assert.assertEquals(LIGHT, header);
-        Assert.assertEquals(NONE, header);
-
-        // First load of foo.test shouldn't have the hint as it wasn't requested before.
-        loadUrlWithExtraHeadersSync(mAwContents, mContentsClient.getOnPageFinishedHelper(),
-                mLocalhostUrl,
-                Collections.singletonMap("Accept-CH", "Sec-CH-Prefers-Color-Scheme"));
-        header = mActivityTestRule.getJavaScriptResultBodyTextContent(mAwContents, mContentsClient);
-        Assert.assertEquals(NONE, header);
-
-        // Second load of foo.test should have the hint as it was persisted before.
-        loadUrlWithExtraHeadersSync(mAwContents, mContentsClient.getOnPageFinishedHelper(),
-                mLocalhostUrl,
-                Collections.singletonMap("Accept-CH", "Sec-CH-Prefers-Color-Scheme"));
-        header = mActivityTestRule.getJavaScriptResultBodyTextContent(mAwContents, mContentsClient);
-        // TODO(crbug.com/921655): Replace the expectations when this is working.
-        // Assert.assertEquals(LIGHT, header);
-        Assert.assertEquals(NONE, header);
-    }
-
-    private void loadUrlWithExtraHeadersSync(final AwContents awContents,
-            CallbackHelper onPageFinishedHelper, final String url,
-            final Map<String, String> extraHeaders) throws Throwable {
+    private void loadUrlSync(final AwContents contents, CallbackHelper onPageFinishedHelper,
+            final String url) throws Throwable {
         int currentCallCount = onPageFinishedHelper.getCallCount();
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(
-                () -> awContents.loadUrl(url, extraHeaders));
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> contents.loadUrl(url));
         onPageFinishedHelper.waitForCallback(
                 currentCallCount, 1, WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
     }
 
+    private void validateHeadersFromJSON(final AwContents contents,
+            final TestAwContentsClient contentsClient, String name, boolean isPresent)
+            throws Exception {
+        String textContent =
+                mActivityTestRule.getJavaScriptResultBodyTextContent(contents, contentsClient)
+                        .replaceAll("\\\\\"", "\"");
+        JSONObject jsonObject = new JSONObject(textContent);
+        String actualVaue = jsonObject.getString(name);
+        if (isPresent) {
+            Assert.assertNotEquals("HEADER_NOT_FOUND", actualVaue);
+        } else {
+            Assert.assertEquals("HEADER_NOT_FOUND", actualVaue);
+        }
+    }
+
     private void clearCookies() throws Throwable {
-        CookieUtils.clearCookies(InstrumentationRegistry.getInstrumentation(), mCookieManager);
+        CookieUtils.clearCookies(
+                InstrumentationRegistry.getInstrumentation(), new AwCookieManager());
     }
 }
diff --git a/android_webview/test/BUILD.gn b/android_webview/test/BUILD.gn
index c1ea4b2..2a4ba532 100644
--- a/android_webview/test/BUILD.gn
+++ b/android_webview/test/BUILD.gn
@@ -622,6 +622,7 @@
     "../browser/lifecycle/aw_contents_lifecycle_notifier_unittest.cc",
     "../browser/metrics/aw_component_metrics_provider_delegate_unittests.cc",
     "../browser/metrics/aw_metrics_service_client_unittest.cc",
+    "../browser/metrics/aw_server_side_allowlist_metrics_provider_unittests.cc",
     "../browser/metrics/visibility_metrics_logger_unittest.cc",
     "../browser/permission/media_access_permission_request_unittest.cc",
     "../browser/permission/permission_request_handler_unittest.cc",
diff --git a/android_webview/test/embedded_test_server/BUILD.gn b/android_webview/test/embedded_test_server/BUILD.gn
index 37884dc..f3f36d4 100644
--- a/android_webview/test/embedded_test_server/BUILD.gn
+++ b/android_webview/test/embedded_test_server/BUILD.gn
@@ -37,7 +37,10 @@
     "aw_embedded_test_server.cc",
     "aw_test_entry_point.cc",
   ]
-  deps = [ "//net:test_support" ]
+  deps = [
+    "//net:test_support",
+    "//services/network/public/cpp",
+  ]
   public_deps = [
     ":aw_net_jni_headers",
     "//net:net_test_jni_headers",
diff --git a/android_webview/test/embedded_test_server/aw_embedded_test_server.cc b/android_webview/test/embedded_test_server/aw_embedded_test_server.cc
index 86fa32f..aeb8ea6 100644
--- a/android_webview/test/embedded_test_server/aw_embedded_test_server.cc
+++ b/android_webview/test/embedded_test_server/aw_embedded_test_server.cc
@@ -11,6 +11,7 @@
 #include "net/test/embedded_test_server/http_request.h"
 #include "net/test/embedded_test_server/http_response.h"
 #include "net/test/embedded_test_server/request_handler_util.h"
+#include "services/network/public/cpp/client_hints.h"
 
 using base::android::JavaParamRef;
 using base::android::JavaRef;
@@ -216,6 +217,84 @@
   return std::move(http_response);
 }
 
+std::unique_ptr<HttpResponse> HandleClientHintsHeaderResponseHelper(
+    const HttpRequest& request,
+    const base::StringPiece& accept_ch,
+    const base::StringPiece& critical_ch) {
+  // Setup basic response.
+  std::unique_ptr<BasicHttpResponse> http_response(new BasicHttpResponse);
+  http_response->set_content_type("text/html");
+  http_response->AddCustomHeader("Cache-Control", "no-cache, no-store");
+
+  // Add relevant headers.
+  if (accept_ch.size()) {
+    http_response->AddCustomHeader("Accept-CH", accept_ch);
+  }
+  if (critical_ch.size()) {
+    http_response->AddCustomHeader("Critical-CH", critical_ch);
+  }
+
+  // Reflect any client hint headers in the request.
+  std::string client_hint_dict;
+  for (const auto& client_hint_name : network::GetClientHintToNameMap()) {
+    const auto& client_hint_value =
+        request.headers.find(client_hint_name.second);
+    if (!client_hint_dict.empty()) {
+      client_hint_dict += ",";
+    }
+    if (client_hint_value == request.headers.end()) {
+      client_hint_dict +=
+          "\"" + client_hint_name.second + "\":\"HEADER_NOT_FOUND\"";
+    } else {
+      client_hint_dict += "\"" + client_hint_name.second + "\":\"" +
+                          client_hint_value->second + "\"";
+    }
+  }
+  http_response->set_content("{" + client_hint_dict + "}");
+
+  return std::move(http_response);
+}
+
+// /client-hints-header?accept-ch=ACCEPT-CH
+// Returns the response with the requested ACCEPT-CH headers set
+// and all client hint request headers reflected into the response.
+std::unique_ptr<HttpResponse> HandleClientHintsHeaderResponse(
+    const HttpRequest& request) {
+  if (!ShouldHandle(request, "/client-hints-header")) {
+    return nullptr;
+  }
+  GURL request_url = request.GetURL();
+  RequestQuery query = ParseQuery(request_url);
+  const auto& accept_ch = query.find("accept-ch");
+  if (accept_ch == query.end()) {
+    return nullptr;
+  }
+  return HandleClientHintsHeaderResponseHelper(request,
+                                               accept_ch->second.front(), "");
+}
+
+// /critical-client-hints-header?accept-ch=ACCEPT-CH&critical-ch=CRITICAL-CH
+// Returns the response with the requested ACCEPT-CH/CRITICAL-CH headers set
+// and all client hint request headers reflected into the response.
+std::unique_ptr<HttpResponse> HandleCriticalClientHintsHeaderResponse(
+    const HttpRequest& request) {
+  if (!ShouldHandle(request, "/critical-client-hints-header")) {
+    return nullptr;
+  }
+  GURL request_url = request.GetURL();
+  RequestQuery query = ParseQuery(request_url);
+  const auto& accept_ch = query.find("accept-ch");
+  if (accept_ch == query.end()) {
+    return nullptr;
+  }
+  const auto& critical_ch = query.find("critical-ch");
+  if (critical_ch == query.end()) {
+    return nullptr;
+  }
+  return HandleClientHintsHeaderResponseHelper(
+      request, accept_ch->second.front(), critical_ch->second.front());
+}
+
 }  // namespace
 
 // static
@@ -226,7 +305,9 @@
       reinterpret_cast<int64_t>(&HandleEchoHeaderAndSetData),
       reinterpret_cast<int64_t>(&HandleServerRedirectEchoHeader),
       reinterpret_cast<int64_t>(&HandleSetImageResponse),
-      reinterpret_cast<int64_t>(&HandleImageOnloadHtml)};
+      reinterpret_cast<int64_t>(&HandleImageOnloadHtml),
+      reinterpret_cast<int64_t>(&HandleClientHintsHeaderResponse),
+      reinterpret_cast<int64_t>(&HandleCriticalClientHintsHeaderResponse)};
   return base::android::ToJavaLongArray(env, handlers);
 }
 
diff --git a/android_webview/test/embedded_test_server/java/src/org/chromium/android_webview/test/AwEmbeddedTestServer.java b/android_webview/test/embedded_test_server/java/src/org/chromium/android_webview/test/AwEmbeddedTestServer.java
index 52769d8..fd0d9fa 100644
--- a/android_webview/test/embedded_test_server/java/src/org/chromium/android_webview/test/AwEmbeddedTestServer.java
+++ b/android_webview/test/embedded_test_server/java/src/org/chromium/android_webview/test/AwEmbeddedTestServer.java
@@ -8,6 +8,7 @@
 import android.content.Intent;
 
 import org.chromium.net.test.EmbeddedTestServer;
+import org.chromium.net.test.ServerCertificate;
 
 /** A simple file server for java android webview tests which extends
  *  from class EmbeddedTestServer. It is able to add custom handlers
@@ -45,4 +46,20 @@
     public static AwEmbeddedTestServer createAndStartServer(Context context) {
         return initializeAndStartServer(new AwEmbeddedTestServer(), context, 0 /* port */);
     }
+
+    /**
+     * Create and initialize an HTTPS server with the default and custom handlers.
+     *
+     *  This handles native object initialization, server configuration, and server initialization.
+     *  On returning, the server is ready for use.
+     *
+     *  @param context The context in which the server will run.
+     *  @param serverCertificate The certificate option that the server will use.
+     *  @return The created server.
+     */
+    public static AwEmbeddedTestServer createAndStartHTTPSServer(
+            Context context, @ServerCertificate int serverCertificate) {
+        return initializeAndStartHTTPSServer(
+                new AwEmbeddedTestServer(), context, serverCertificate, 0 /* port */);
+    }
 }
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index e61c99d..b0c0c56 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -1041,6 +1041,8 @@
     "style/icon_button.h",
     "style/icon_switch.cc",
     "style/icon_switch.h",
+    "style/knob_switch.cc",
+    "style/knob_switch.h",
     "style/option_button_base.cc",
     "style/option_button_base.h",
     "style/option_button_group.cc",
@@ -2981,6 +2983,7 @@
     "system/camera/camera_effects_controller_unittest.cc",
     "system/caps_lock_notification_controller_unittest.cc",
     "system/cast/cast_feature_pod_controller_unittest.cc",
+    "system/cast/tray_cast_unittest.cc",
     "system/channel_indicator/channel_indicator_quick_settings_view_unittest.cc",
     "system/channel_indicator/channel_indicator_unittest.cc",
     "system/channel_indicator/channel_indicator_utils_unittest.cc",
diff --git a/ash/accelerators/accelerator_history_impl.cc b/ash/accelerators/accelerator_history_impl.cc
index 9ed10815..7d3f09e4 100644
--- a/ash/accelerators/accelerator_history_impl.cc
+++ b/ash/accelerators/accelerator_history_impl.cc
@@ -72,10 +72,8 @@
       for (auto key_code : currently_pressed_keys_)
         pressed_keys.append(base::NumberToString(key_code).append(" "));
 
-      LOG(WARNING) << "Key Release (" << accelerator.key_code()
-                   << ") delivered with no corresponding Press. "
-                      "Clearing all pressed keys: "
-                   << pressed_keys;
+      // Key release was delivered with no corresponding press. This usually
+      // happens when the key press is lost somehow.
       currently_pressed_keys_.clear();
     }
   }
diff --git a/ash/accessibility/ui/accessibility_focus_ring_controller_impl.cc b/ash/accessibility/ui/accessibility_focus_ring_controller_impl.cc
index c49a37b3..18be270 100644
--- a/ash/accessibility/ui/accessibility_focus_ring_controller_impl.cc
+++ b/ash/accessibility/ui/accessibility_focus_ring_controller_impl.cc
@@ -92,6 +92,11 @@
   UpdateHighlightFromHighlightRects();
 }
 
+void AccessibilityFocusRingControllerImpl::SetFocusRingObserverForTesting(
+    base::RepeatingCallback<void()> observer) {
+  focus_ring_observer_for_test_ = std::move(observer);
+}
+
 void AccessibilityFocusRingControllerImpl::UpdateHighlightFromHighlightRects() {
   if (!highlight_layer_)
     highlight_layer_ = std::make_unique<AccessibilityHighlightLayer>(this);
@@ -104,6 +109,9 @@
   animation_info->change_time = base::TimeTicks::Now();
   if (animation_info->opacity == 0)
     animation_info->start_time = animation_info->change_time;
+
+  if (focus_ring_observer_for_test_)
+    focus_ring_observer_for_test_.Run();
 }
 
 void AccessibilityFocusRingControllerImpl::SetCursorRing(
diff --git a/ash/accessibility/ui/accessibility_focus_ring_controller_impl.h b/ash/accessibility/ui/accessibility_focus_ring_controller_impl.h
index b980fe4..c08dbb5 100644
--- a/ash/accessibility/ui/accessibility_focus_ring_controller_impl.h
+++ b/ash/accessibility/ui/accessibility_focus_ring_controller_impl.h
@@ -49,6 +49,8 @@
   void SetHighlights(const std::vector<gfx::Rect>& rects,
                      SkColor color) override;
   void HideHighlights() override;
+  void SetFocusRingObserverForTesting(
+      base::RepeatingCallback<void()> observer) override;
 
   // Draw a ring around the mouse cursor. It fades out automatically.
   void SetCursorRing(const gfx::Point& location);
@@ -115,6 +117,8 @@
   SkColor highlight_color_ = SK_ColorBLACK;
   float highlight_opacity_ = 0.f;
 
+  base::RepeatingCallback<void()> focus_ring_observer_for_test_;
+
   bool no_fade_for_testing_ = false;
 };
 
diff --git a/ash/app_list/app_list_presenter_unittest.cc b/ash/app_list/app_list_presenter_unittest.cc
index f1600bd..7a1bebb 100644
--- a/ash/app_list/app_list_presenter_unittest.cc
+++ b/ash/app_list/app_list_presenter_unittest.cc
@@ -2107,7 +2107,8 @@
   views::test::WidgetDestroyedWaiter widget_close_waiter(confirmation_dialog);
   GetSearchBoxView()->ClearSearchAndDeactivateSearchBox();
   EXPECT_FALSE(AppListSearchResultPageVisible());
-  EXPECT_FALSE(keyboard_controller->IsKeyboardVisible());
+  if (tablet_mode_param())
+    EXPECT_FALSE(keyboard_controller->IsKeyboardVisible());
 
   // Exiting the search results page should close the dialog.
   widget_close_waiter.Wait();
diff --git a/ash/app_list/app_list_test_api.cc b/ash/app_list/app_list_test_api.cc
index c5a3e7c..e792e86 100644
--- a/ash/app_list/app_list_test_api.cc
+++ b/ash/app_list/app_list_test_api.cc
@@ -748,12 +748,13 @@
 
 void AppListTestApi::RegisterReorderAnimationDoneCallback(
     ReorderAnimationEndState* actual_state) {
-  AddReorderAnimationCallback(
-      base::BindRepeating(&AppListTestApi::OnReorderAnimationDone,
-                          weak_factory_.GetWeakPtr(), actual_state));
+  AddReorderAnimationCallback(base::BindRepeating(
+      &AppListTestApi::OnReorderAnimationDone, weak_factory_.GetWeakPtr(),
+      !ash::Shell::Get()->IsInTabletMode(), actual_state));
 }
 
-void AppListTestApi::OnReorderAnimationDone(ReorderAnimationEndState* result,
+void AppListTestApi::OnReorderAnimationDone(bool for_bubble_app_list,
+                                            ReorderAnimationEndState* result,
                                             bool abort,
                                             AppListGridAnimationStatus status) {
   DCHECK(status == AppListGridAnimationStatus::kReorderFadeOut ||
@@ -771,9 +772,8 @@
 
     // Verify that the toast container under the clamshell mode does not have
     // a layer after reorder animation completes.
-    views::View* toast_container = GetToastContainerView();
-    if (toast_container && !ash::Shell::Get()->IsInTabletMode())
-      EXPECT_FALSE(toast_container->layer());
+    if (for_bubble_app_list)
+      EXPECT_FALSE(GetToastContainerView()->layer());
   }
 
   // Callback can be registered without a running loop.
diff --git a/ash/app_list/views/apps_container_view.cc b/ash/app_list/views/apps_container_view.cc
index 4c20a2c4..65b438d 100644
--- a/ash/app_list/views/apps_container_view.cc
+++ b/ash/app_list/views/apps_container_view.cc
@@ -471,6 +471,7 @@
   DVLOG(1) << __FUNCTION__;
   UpdateRecentApps(/*needs_layout=*/false);
   SetShowState(SHOW_APPS, false);
+  apps_grid_view_->MaybeAbortWholeGridAnimation();
   DisableFocusForShowingActiveFolder(false);
 }
 
diff --git a/ash/app_list/views/contents_view.cc b/ash/app_list/views/contents_view.cc
index 8be282e92..cd9f7939 100644
--- a/ash/app_list/views/contents_view.cc
+++ b/ash/app_list/views/contents_view.cc
@@ -289,8 +289,10 @@
   int search_page = GetPageIndexForState(AppListState::kStateSearchResults);
   DCHECK_GE(search_page, 0);
 
-  // Hide or Show results
-  search_result_page_view()->SetVisible(show);
+  // SetVisible() only when showing search results, the search results page will
+  // be hidden at the end of its own bounds animation.
+  if (show)
+    search_result_page_view()->SetVisible(true);
   SetActiveStateInternal(show ? search_page : page_before_search_,
                          true /*animate*/);
   if (show)
@@ -347,7 +349,6 @@
 
   search_box->UpdateLayout(target_state,
                            GetSearchBoxSize(target_state).height());
-  search_box->UpdateBackground(target_state);
 
   gfx::Rect target_bounds = GetSearchBoxBounds(target_state);
   target_bounds = search_box->GetViewBoundsForSearchBoxContentsBounds(
diff --git a/ash/app_list/views/paged_apps_grid_view.cc b/ash/app_list/views/paged_apps_grid_view.cc
index 5b2d2c0..cea2ec56 100644
--- a/ash/app_list/views/paged_apps_grid_view.cc
+++ b/ash/app_list/views/paged_apps_grid_view.cc
@@ -227,9 +227,6 @@
 
   // Prevent context menus from remaining open after a transition
   CancelContextMenusOnCurrentPage();
-
-  // Abort the running reorder animation when the tablet mode updates.
-  MaybeAbortWholeGridAnimation();
 }
 
 void PagedAppsGridView::HandleScrollFromParentView(const gfx::Vector2d& offset,
diff --git a/ash/app_list/views/productivity_launcher_search_view_unittest.cc b/ash/app_list/views/productivity_launcher_search_view_unittest.cc
index 1e8206c..58a9367 100644
--- a/ash/app_list/views/productivity_launcher_search_view_unittest.cc
+++ b/ash/app_list/views/productivity_launcher_search_view_unittest.cc
@@ -29,7 +29,6 @@
 #include "ui/compositor/layer.h"
 #include "ui/compositor/scoped_animation_duration_scale_mode.h"
 #include "ui/compositor/test/layer_animation_stopped_waiter.h"
-#include "ui/compositor/test/test_utils.h"
 #include "ui/events/keycodes/keyboard_codes_posix.h"
 #include "ui/views/controls/button/label_button.h"
 #include "ui/views/controls/textfield/textfield.h"
@@ -51,15 +50,13 @@
 
 namespace ash {
 
-// Parameterized based on whether the search view is shown within the clamshell
-// or tablet mode launcher UI.
-class ProductivityLauncherSearchViewTest
-    : public AshTestBase,
-      public testing::WithParamInterface<bool> {
+// Subclasses set `test_under_tablet_` in the constructor to indicate
+// which mode to test.
+class ProductivityLauncherSearchViewTest : public AshTestBase {
  public:
-  ProductivityLauncherSearchViewTest()
+  explicit ProductivityLauncherSearchViewTest(bool test_under_tablet)
       : AshTestBase((base::test::TaskEnvironment::TimeSource::MOCK_TIME)),
-        test_under_tablet_(GetParam()) {}
+        test_under_tablet_(test_under_tablet) {}
   ProductivityLauncherSearchViewTest(
       const ProductivityLauncherSearchViewTest&) = delete;
   ProductivityLauncherSearchViewTest& operator=(
@@ -190,12 +187,33 @@
   }
 
  private:
-  const bool test_under_tablet_;
+  const bool test_under_tablet_ = false;
 };
 
-// An extension of ProductivityLauncherSearchViewTest to test launcher image
+// Parameterized based on whether the search view is shown within the clamshell
+// or tablet mode launcher UI.
+class SearchViewClamshellAndTabletTest
+    : public ProductivityLauncherSearchViewTest,
+      public testing::WithParamInterface<bool> {
+ public:
+  SearchViewClamshellAndTabletTest()
+      : ProductivityLauncherSearchViewTest(/*test_under_tablet=*/GetParam()) {}
+};
+
+INSTANTIATE_TEST_SUITE_P(Tablet,
+                         SearchViewClamshellAndTabletTest,
+                         testing::Bool());
+
+// ProductivityLauncherSearchViewTest which only tests tablet mode.
+class SearchViewTabletTest : public ProductivityLauncherSearchViewTest {
+ public:
+  SearchViewTabletTest()
+      : ProductivityLauncherSearchViewTest(/*test_under_tablet=*/true) {}
+};
+
+// An extension of SearchViewClamshellAndTabletTest to test launcher image
 // search.
-class SearchResultImageViewTest : public ProductivityLauncherSearchViewTest {
+class SearchResultImageViewTest : public SearchViewClamshellAndTabletTest {
  public:
   SearchResultImageViewTest() {
     scoped_feature_list_.InitAndEnableFeature(
@@ -206,10 +224,6 @@
   base::test::ScopedFeatureList scoped_feature_list_;
 };
 
-INSTANTIATE_TEST_SUITE_P(Tablet,
-                         ProductivityLauncherSearchViewTest,
-                         testing::Bool());
-
 INSTANTIATE_TEST_SUITE_P(Tablet, SearchResultImageViewTest, testing::Bool());
 
 TEST_P(SearchResultImageViewTest, ImageListViewVisible) {
@@ -300,7 +314,7 @@
   EXPECT_TRUE(SearchResultImageViewDelegate::Get()->HasActiveContextMenu());
 }
 
-TEST_P(ProductivityLauncherSearchViewTest, AnimateSearchResultView) {
+TEST_P(SearchViewClamshellAndTabletTest, AnimateSearchResultView) {
   // Enable animations.
   ui::ScopedAnimationDurationScaleMode duration(
       ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
@@ -373,7 +387,7 @@
   client->set_search_callback(TestAppListClient::SearchCallback());
 }
 
-TEST_P(ProductivityLauncherSearchViewTest, ResultContainerIsVisible) {
+TEST_P(SearchViewClamshellAndTabletTest, ResultContainerIsVisible) {
   GetAppListTestHelper()->ShowAppList();
 
   TestAppListClient* const client = GetAppListTestHelper()->app_list_client();
@@ -416,7 +430,7 @@
   client->set_search_callback(TestAppListClient::SearchCallback());
 }
 
-TEST_P(ProductivityLauncherSearchViewTest,
+TEST_P(SearchViewClamshellAndTabletTest,
        SearchResultsAreVisibleDuringHidePageAnimation) {
   auto* helper = GetAppListTestHelper();
   helper->ShowAppList();
@@ -498,9 +512,67 @@
   client->set_search_callback(TestAppListClient::SearchCallback());
 }
 
+// Test that the search result page view is visible while animating the search
+// page from expanded to closed, specifically in tablet mode.
+TEST_F(SearchViewTabletTest, SearchResultPageShownWhileClosing) {
+  auto* helper = GetAppListTestHelper();
+  helper->ShowAppList();
+
+  TestAppListClient* const client = GetAppListTestHelper()->app_list_client();
+  client->set_search_callback(
+      base::BindLambdaForTesting([&](const std::u16string& query) {
+        if (query.empty()) {
+          AppListModelProvider::Get()->search_model()->DeleteAllResults();
+          return;
+        }
+        EXPECT_EQ(u"a", query);
+
+        auto* test_helper = GetAppListTestHelper();
+        SearchModel::SearchResults* results = test_helper->GetSearchResults();
+        // Create categorized results and order categories as {kApps, kWeb}.
+        std::vector<AppListSearchResultCategory>* ordered_categories =
+            test_helper->GetOrderedResultCategories();
+        ordered_categories->push_back(AppListSearchResultCategory::kApps);
+        ordered_categories->push_back(AppListSearchResultCategory::kWeb);
+        SetUpSearchResults(results, 1, kDefaultSearchItems, 100, false,
+                           SearchResult::Category::kApps);
+        SetUpSearchResults(results, 1 + kDefaultSearchItems,
+                           kDefaultSearchItems, 1, false,
+                           SearchResult::Category::kWeb);
+      }));
+
+  // Press a key to start a search.
+  PressAndReleaseKey(ui::VKEY_A);
+
+  std::vector<SearchResultContainerView*> result_containers =
+      GetProductivityLauncherSearchView()->result_container_views_for_test();
+  for (auto* container : result_containers)
+    EXPECT_TRUE(container->RunScheduledUpdateForTest());
+
+  SearchResultView* app_result = GetSearchResultView(2, 0);
+  ASSERT_TRUE(app_result);
+
+  // Enable animations.
+  ui::ScopedAnimationDurationScaleMode duration(
+      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
+
+  // Press backspace to delete the query and switch back to the apps page.
+  PressAndReleaseKey(ui::VKEY_ESCAPE);
+
+  // The search page should be visible and animating while closing.
+  EXPECT_TRUE(GetSearchPage()->GetVisible());
+  EXPECT_TRUE(GetSearchPageAnimationLayer()->GetAnimator()->is_animating());
+
+  ui::LayerAnimationStoppedWaiter().Wait(GetSearchPageAnimationLayer());
+
+  // After waiting for animation, the search page should now be hidden.
+  EXPECT_FALSE(GetSearchPage()->GetVisible());
+  EXPECT_FALSE(GetSearchPageAnimationLayer()->GetAnimator()->is_animating());
+}
+
 // Tests that attempts to change selection during results hide animation are
 // handed gracefully.
-TEST_P(ProductivityLauncherSearchViewTest, SelectionChangeDuringHide) {
+TEST_P(SearchViewClamshellAndTabletTest, SelectionChangeDuringHide) {
   auto* helper = GetAppListTestHelper();
   helper->ShowAppList();
 
@@ -565,7 +637,7 @@
 
 // Tests that key traversal correctly cycles between the list of results and
 // search box close button.
-TEST_P(ProductivityLauncherSearchViewTest, ResultSelectionCycle) {
+TEST_P(SearchViewClamshellAndTabletTest, ResultSelectionCycle) {
   auto* test_helper = GetAppListTestHelper();
   test_helper->ShowAppList();
   EXPECT_FALSE(GetProductivityLauncherSearchView()->CanSelectSearchResults());
@@ -642,7 +714,7 @@
             kDefaultSearchItems - 1);
 }
 
-TEST_P(ProductivityLauncherSearchViewTest, AnswerCardSelection) {
+TEST_P(SearchViewClamshellAndTabletTest, AnswerCardSelection) {
   auto* test_helper = GetAppListTestHelper();
   test_helper->ShowAppList();
 
@@ -690,7 +762,7 @@
 
 // Tests that result selection controller can change between  within and between
 // result containers.
-TEST_P(ProductivityLauncherSearchViewTest, ResultSelection) {
+TEST_P(SearchViewClamshellAndTabletTest, ResultSelection) {
   auto* test_helper = GetAppListTestHelper();
   test_helper->ShowAppList();
   EXPECT_FALSE(GetProductivityLauncherSearchView()->CanSelectSearchResults());
@@ -751,7 +823,7 @@
   EXPECT_EQ(controller->selected_location_details()->result_index, 2);
 }
 
-TEST_P(ProductivityLauncherSearchViewTest, ResultPageHiddenInZeroSearchState) {
+TEST_P(SearchViewClamshellAndTabletTest, ResultPageHiddenInZeroSearchState) {
   auto* test_helper = GetAppListTestHelper();
   test_helper->ShowAppList();
 
@@ -812,7 +884,7 @@
 }
 
 // Verifies that search result categories are sorted properly.
-TEST_P(ProductivityLauncherSearchViewTest, SearchResultCategoricalSort) {
+TEST_P(SearchViewClamshellAndTabletTest, SearchResultCategoricalSort) {
   auto* test_helper = GetAppListTestHelper();
   test_helper->ShowAppList();
 
@@ -901,7 +973,7 @@
   EXPECT_EQ(GetVisibleResultContainers(), (std::vector<size_t>{}));
 }
 
-TEST_P(ProductivityLauncherSearchViewTest, SearchResultA11y) {
+TEST_P(SearchViewClamshellAndTabletTest, SearchResultA11y) {
   auto* test_helper = GetAppListTestHelper();
   test_helper->ShowAppList();
 
@@ -945,7 +1017,7 @@
   EXPECT_EQ(5, ax_counter.GetCount(ax::mojom::Event::kActiveDescendantChanged));
 }
 
-TEST_P(ProductivityLauncherSearchViewTest, SearchPageA11y) {
+TEST_P(SearchViewClamshellAndTabletTest, SearchPageA11y) {
   auto* test_helper = GetAppListTestHelper();
   test_helper->ShowAppList();
 
@@ -992,7 +1064,7 @@
             data.GetStringAttribute(ax::mojom::StringAttribute::kValue));
 }
 
-TEST_P(ProductivityLauncherSearchViewTest, SearchClearedOnModelUpdate) {
+TEST_P(SearchViewClamshellAndTabletTest, SearchClearedOnModelUpdate) {
   auto* test_helper = GetAppListTestHelper();
   test_helper->ShowAppList();
 
diff --git a/ash/app_list/views/search_box_view.cc b/ash/app_list/views/search_box_view.cc
index cea5be90..7681e38 100644
--- a/ash/app_list/views/search_box_view.cc
+++ b/ash/app_list/views/search_box_view.cc
@@ -613,7 +613,10 @@
       gfx::Insets::TLBR(0, horizontal_spacing, 0, horizontal_right_padding));
   box_layout_view()->SetBetweenChildSpacing(horizontal_spacing);
   InvalidateLayout();
-  UpdateBackground(target_state);
+  // Avoid setting background when animating to kStateApps, background will be
+  // set when the animation ends.
+  if (target_state != AppListState::kStateApps)
+    UpdateBackground(target_state);
 }
 
 int SearchBoxView::GetSearchBoxBorderCornerRadiusForState(
@@ -944,7 +947,7 @@
   // |node_data| for "Value".
   NotifyAccessibilityEvent(ax::mojom::Event::kValueChanged, true);
 
-    MaybeSetAutocompleteGhostText(std::u16string(), std::u16string());
+  MaybeSetAutocompleteGhostText(std::u16string(), std::u16string());
 }
 
 SearchBoxView::PlaceholderTextType SearchBoxView::SelectPlaceholderText()
@@ -968,8 +971,11 @@
     return;
 
   SetA11yActiveDescendant(absl::nullopt);
-  ClearSearch();
+  // Set search box as inactive first, because ClearSearch() eventually calls
+  // into AppListMainView::QueryChanged() which will hide search results based
+  // on `is_search_box_active_`.
   SetSearchBoxActive(false, ui::ET_UNKNOWN);
+  ClearSearch();
   MaybeSetAutocompleteGhostText(std::u16string(), std::u16string());
 }
 
diff --git a/ash/app_list/views/search_result_page_view.cc b/ash/app_list/views/search_result_page_view.cc
index 22bab2b..4d550ee08 100644
--- a/ash/app_list/views/search_result_page_view.cc
+++ b/ash/app_list/views/search_result_page_view.cc
@@ -54,10 +54,14 @@
 constexpr base::TimeDelta kExpandingSearchResultDuration =
     base::Milliseconds(200);
 
-// The duration of the search result page view closing animation.
-constexpr base::TimeDelta kClosingSearchResultDuration =
+// The duration of the search result page view going from expanded to active.
+constexpr base::TimeDelta kExpandedToActiveSearchResultDuration =
     base::Milliseconds(100);
 
+// The duration of the search result page view going from expanded to closed.
+constexpr base::TimeDelta kExpandedToClosedSearchResultDuration =
+    base::Milliseconds(250);
+
 // The duration of the search result page view decreasing height animation
 // within the kExpanded state.
 constexpr base::TimeDelta kDecreasingHeightSearchResultsDuration =
@@ -256,18 +260,38 @@
     return;
   }
 
-  gfx::Rect clip_rect = from_rect;
-  clip_rect -= to_rect.OffsetFromOrigin();
+  const bool is_expanding = from_rect.height() < to_rect.height();
+  gfx::Rect clip_rect;
+  gfx::Rect to_clip_rect;
+
+  // The clip rects will always be located relative to the view bounds current
+  // OffsetFromOrigin(). To ensure the animation is not cutoff by the view
+  // bounds, the view bounds will equal the larger of `from_rect` and
+  // `to_rect`. Because of this, calculate the clip rects so that their 0,0
+  // origin is located at the offset of the wider input bounds (widest between
+  // `from_rect` and `to_rect`).
+  if (is_expanding) {
+    clip_rect = from_rect - to_rect.OffsetFromOrigin();
+    to_clip_rect = gfx::Rect(to_rect.size());
+  } else {
+    clip_rect = gfx::Rect(from_rect.size());
+    to_clip_rect = to_rect - from_rect.OffsetFromOrigin();
+  }
   layer()->SetClipRect(clip_rect);
   shadow_.reset();
 
   base::TimeDelta duration;
-  if (from_rect.height() < to_rect.height()) {
-    duration = kExpandingSearchResultDuration;
-  } else {
-    duration = (current_search_results_state_ == SearchResultsState::kExpanded)
-                   ? kDecreasingHeightSearchResultsDuration
-                   : kClosingSearchResultDuration;
+  switch (current_search_results_state_) {
+    case SearchResultsState::kExpanded:
+      duration = is_expanding ? kExpandingSearchResultDuration
+                              : kDecreasingHeightSearchResultsDuration;
+      break;
+    case SearchResultsState::kActive:
+      duration = kExpandedToActiveSearchResultDuration;
+      break;
+    case SearchResultsState::kClosed:
+      duration = kExpandedToClosedSearchResultDuration;
+      break;
   }
 
   views::AnimationBuilder()
@@ -278,8 +302,7 @@
                          base::Unretained(this)))
       .Once()
       .SetDuration(duration)
-      .SetClipRect(layer(), gfx::Rect(to_rect.size()),
-                   gfx::Tween::FAST_OUT_SLOW_IN)
+      .SetClipRect(layer(), to_clip_rect, gfx::Tween::FAST_OUT_SLOW_IN)
       .SetRoundedCorners(
           layer(),
           gfx::RoundedCornersF(GetCornerRadiusForSearchResultsState(
diff --git a/ash/ash_prefs.cc b/ash/ash_prefs.cc
index 64dd921..0c9c7765 100644
--- a/ash/ash_prefs.cc
+++ b/ash/ash_prefs.cc
@@ -37,7 +37,6 @@
 #include "ash/system/gesture_education/gesture_education_notification_controller.h"
 #include "ash/system/human_presence/snooping_protection_controller.h"
 #include "ash/system/keyboard_brightness/keyboard_backlight_color_controller.h"
-#include "ash/system/keyboard_brightness/keyboard_brightness_controller.h"
 #include "ash/system/media/media_tray.h"
 #include "ash/system/message_center/message_center_controller.h"
 #include "ash/system/network/cellular_setup_notifier.h"
@@ -101,7 +100,6 @@
   LogoutButtonTray::RegisterProfilePrefs(registry);
   LogoutConfirmationController::RegisterProfilePrefs(registry);
   KeyboardBacklightColorController::RegisterPrefs(registry);
-  KeyboardBrightnessController::RegisterPrefs(registry);
   KeyboardControllerImpl::RegisterProfilePrefs(registry);
   MediaControllerImpl::RegisterProfilePrefs(registry);
   MessageCenterController::RegisterProfilePrefs(registry);
@@ -161,7 +159,6 @@
     TopShortcutsView::RegisterLocalStatePrefs(registry);
   glanceables_util::RegisterLocalStatePrefs(registry);
   KeyboardBacklightColorController::RegisterPrefs(registry);
-  KeyboardBrightnessController::RegisterPrefs(registry);
 }
 
 void RegisterSigninProfilePrefs(PrefRegistrySimple* registry, bool for_test) {
diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd
index 88214c1..c5781b2d 100644
--- a/ash/ash_strings.grd
+++ b/ash/ash_strings.grd
@@ -1435,6 +1435,11 @@
         Scanning
       </message>
 
+       <!-- Video Conference tray-->
+      <message name="IDS_ASH_VIDEO_CONFERENCE_TOGGLE_BUBBLE_BUTTON_TOOLTIP" desc="The tooltip for the toggle bubble button in the video conference tray.">
+        Camera and audio controls
+      </message>
+
       <!-- Phone Hub tray-->
       <message name="IDS_ASH_PHONE_HUB_TRAY_ACCESSIBLE_NAME" desc="The accessible name of the Phone Hub tray bubble for screen readers.">
         Phone Hub
@@ -5716,6 +5721,9 @@
       <message name="IDS_ASH_ECHE_TOAST_TABLET_MODE_NOT_SUPPORTED" desc="A toast message that we show when user tries to switch to tablet mode.">
         Can't stream apps in tablet mode. Try again in laptop mode.
       </message>
+      <message name="ID_ASH_ECHE_APP_STREAMING_BUBBLE_TITLE" desc="The title appear on the top of app streaming bubble">
+        From <ph name="PHONE_NAME">$1<ex>Pixel 7</ex></ph>
+      </message>
 
       <!-- Deferred update dialog -->
       <message name="IDS_DEFERRED_UPDATE_DIALOG_TITLE" desc="Title of the dialog for notifying deferred update available to be applied.">
diff --git a/ash/ash_strings_grd/IDS_ASH_VIDEO_CONFERENCE_TOGGLE_BUBBLE_BUTTON_TOOLTIP.png.sha1 b/ash/ash_strings_grd/IDS_ASH_VIDEO_CONFERENCE_TOGGLE_BUBBLE_BUTTON_TOOLTIP.png.sha1
new file mode 100644
index 0000000..05b260d
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_VIDEO_CONFERENCE_TOGGLE_BUBBLE_BUTTON_TOOLTIP.png.sha1
@@ -0,0 +1 @@
+55c6b4568703b2c2a5650401254bb72016345aac
\ No newline at end of file
diff --git a/ash/ash_strings_grd/ID_ASH_ECHE_APP_STREAMING_BUBBLE_TITLE.png.sha1 b/ash/ash_strings_grd/ID_ASH_ECHE_APP_STREAMING_BUBBLE_TITLE.png.sha1
new file mode 100644
index 0000000..346107c
--- /dev/null
+++ b/ash/ash_strings_grd/ID_ASH_ECHE_APP_STREAMING_BUBBLE_TITLE.png.sha1
@@ -0,0 +1 @@
+04e3969ffe01e445482699f9d7b97e1f6d823a1a
\ No newline at end of file
diff --git a/ash/clipboard/DIR_METADATA b/ash/clipboard/DIR_METADATA
index 30a4cb9..3825bfb7 100644
--- a/ash/clipboard/DIR_METADATA
+++ b/ash/clipboard/DIR_METADATA
@@ -1,3 +1,13 @@
-monorail {
-  component: "UI>Shell>EnhancedClipboard"
+# Metadata information for this directory.
+#
+# For more information on DIR_METADATA files, see:
+#   https://chromium.googlesource.com/chromiumos/docs/+/HEAD/dir_metadata.md
+#
+# For the schema of this file, see Metadata message:
+#   https://source.chromium.org/chromium/infra/infra/+/HEAD:go/src/infra/tools/dirmd/proto/dir_metadata.proto
+
+buganizer: {
+  component_id: 1268414 # ChromeOS > Software > System UI Surfaces > EnhancedClipboard
 }
+
+team_email: "multipaste-eng@google.com"
diff --git a/ash/constants/ash_features.cc b/ash/constants/ash_features.cc
index 55d89cc..65ee7b67 100644
--- a/ash/constants/ash_features.cc
+++ b/ash/constants/ash_features.cc
@@ -361,11 +361,6 @@
              "CalendarJelly",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
-// Enables to allow using document scanning feature via DLC in the camera app.
-BASE_FEATURE(kCameraAppDocScanDlc,
-             "CameraAppDocScanDlc",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 // Enables to allow low storage warning feature in the camera app.
 BASE_FEATURE(kCameraAppLowStorageWarning,
              "CameraAppLowStorageWarning",
@@ -1553,6 +1548,12 @@
              "OverviewButton",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+// Enables staying in overview when navigating between desks using a swipe
+// gesture or keyboard shortcut.
+BASE_FEATURE(kOverviewDeskNavigation,
+             "OverviewDeskNavigation",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 // Enables a notification warning users that their Thunderbolt device is not
 // supported on their CrOS device.
 BASE_FEATURE(kPcieBillboardNotification,
@@ -2882,6 +2883,10 @@
   return base::FeatureList::IsEnabled(kOsSettingsSearchFeedback);
 }
 
+bool IsOverviewDeskNavigationEnabled() {
+  return base::FeatureList::IsEnabled(kOverviewDeskNavigation);
+}
+
 bool IsPcieBillboardNotificationEnabled() {
   return base::FeatureList::IsEnabled(kPcieBillboardNotification);
 }
diff --git a/ash/constants/ash_features.h b/ash/constants/ash_features.h
index a4638950..94198d8e 100644
--- a/ash/constants/ash_features.h
+++ b/ash/constants/ash_features.h
@@ -113,7 +113,6 @@
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kCalendarView);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kCalendarModelDebugMode);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kCalendarJelly);
-COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kCameraAppDocScanDlc);
 COMPONENT_EXPORT(ASH_CONSTANTS)
 BASE_DECLARE_FEATURE(kCameraAppLowStorageWarning);
 COMPONENT_EXPORT(ASH_CONSTANTS)
@@ -443,6 +442,7 @@
 BASE_DECLARE_FEATURE(kOsSettingsAppBadgingToggle);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kOsSettingsSearchFeedback);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kOverviewButton);
+COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kOverviewDeskNavigation);
 COMPONENT_EXPORT(ASH_CONSTANTS)
 BASE_DECLARE_FEATURE(kPcieBillboardNotification);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kPerDeskShelf);
@@ -784,6 +784,7 @@
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsOobeThemeSelectionEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsOsSettingsAppBadgingToggleEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsOsSettingsSearchFeedbackEnabled();
+COMPONENT_EXPORT(ASH_CONSTANTS) bool IsOverviewDeskNavigationEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsPcieBillboardNotificationEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsPciguardUiEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsPerDeskShelfEnabled();
diff --git a/ash/constants/ash_pref_names.cc b/ash/constants/ash_pref_names.cc
index b2fea54..7a702fe 100644
--- a/ash/constants/ash_pref_names.cc
+++ b/ash/constants/ash_pref_names.cc
@@ -1138,21 +1138,6 @@
 const char kPersonalizationKeyboardBacklightColor[] =
     "ash.personalization.keyboard_backlight_color";
 
-// This integer pref indicates keyboard brightness which is currently set,
-// represented as a percentage. A pref with this name is registered in two
-// different contexts:
-// - User profile:
-//   Indicates the brightness set by the user for their profile.
-//   Can be "recommended" through device policy DeviceKeyboardBrightness.
-// - Local state:
-//   Indicates the brightness used on the sign-in screen.
-//   Can be "recommended" through device policy DeviceKeyboardBrightness
-//   (the brightness can be updated by pressing Alt+F6, Alt+F7, so the local
-//   state will only be used to set the brightness when entering the sign-in
-//   screen).
-const char kPersonalizationKeyboardBrightness[] =
-    "ash.personalization.keyboard_brightness";
-
 // Integer pref corresponding to the autozoom state, the value should be one of
 // cros::mojom::CameraAutoFramingState.
 const char kAutozoomState[] = "ash.camera.autozoom_state";
diff --git a/ash/constants/ash_pref_names.h b/ash/constants/ash_pref_names.h
index 4b46175..56bbb8df 100644
--- a/ash/constants/ash_pref_names.h
+++ b/ash/constants/ash_pref_names.h
@@ -521,9 +521,6 @@
 COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const char kPersonalizationKeyboardBacklightColor[];
 
-COMPONENT_EXPORT(ASH_CONSTANTS)
-extern const char kPersonalizationKeyboardBrightness[];
-
 COMPONENT_EXPORT(ASH_CONSTANTS) extern const char kAutozoomState[];
 
 COMPONENT_EXPORT(ASH_CONSTANTS) extern const char kAutozoomNudges[];
diff --git a/ash/frame/caption_buttons/frame_size_button_unittest.cc b/ash/frame/caption_buttons/frame_size_button_unittest.cc
index ffbd0720..d27a2ce 100644
--- a/ash/frame/caption_buttons/frame_size_button_unittest.cc
+++ b/ash/frame/caption_buttons/frame_size_button_unittest.cc
@@ -16,12 +16,14 @@
 #include "base/i18n/rtl.h"
 #include "base/run_loop.h"
 #include "base/test/metrics/histogram_tester.h"
+#include "base/test/metrics/user_action_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "chromeos/ui/base/window_properties.h"
 #include "chromeos/ui/frame/caption_buttons/frame_caption_button_container_view.h"
 #include "chromeos/ui/frame/default_frame_header.h"
 #include "chromeos/ui/frame/multitask_menu/multitask_button.h"
 #include "chromeos/ui/frame/multitask_menu/multitask_menu.h"
+#include "chromeos/ui/frame/multitask_menu/multitask_menu_metrics.h"
 #include "chromeos/ui/frame/multitask_menu/split_button_view.h"
 #include "chromeos/ui/vector_icons/vector_icons.h"
 #include "chromeos/ui/wm/features.h"
@@ -49,7 +51,6 @@
 using ::chromeos::MultitaskButton;
 using ::chromeos::MultitaskMenu;
 using ::chromeos::MultitaskMenuEntryType;
-using ::chromeos::MultitaskMenuView;
 using ::chromeos::SplitButtonView;
 using ::chromeos::WindowStateType;
 
@@ -781,6 +782,15 @@
   const gfx::Rect work_area_bounds_in_screen =
       display::Screen::GetScreen()->GetPrimaryDisplay().work_area();
 
+  // Verify the metrics initial states.
+  base::UserActionTester user_action_tester;
+  EXPECT_EQ(user_action_tester.GetActionCount(
+                chromeos::kPartialSplitOneThirdUserAction),
+            0);
+  EXPECT_EQ(user_action_tester.GetActionCount(
+                chromeos::kPartialSplitTwoThirdsUserAction),
+            0);
+
   // Snap to primary with 0.67f screen ratio.
   ShowMultitaskMenu();
   generator->MoveMouseTo(multitask_menu()
@@ -792,6 +802,9 @@
   EXPECT_EQ(WindowStateType::kPrimarySnapped, window_state()->GetStateType());
   EXPECT_EQ(window_state()->window()->bounds().width(),
             work_area_bounds_in_screen.width() * 0.67);
+  EXPECT_EQ(user_action_tester.GetActionCount(
+                chromeos::kPartialSplitTwoThirdsUserAction),
+            1);
 
   // Snap to secondary with 0.33f screen ratio.
   ShowMultitaskMenu();
@@ -807,6 +820,9 @@
   EXPECT_EQ(WindowStateType::kSecondarySnapped, window_state()->GetStateType());
   EXPECT_EQ(window_state()->window()->bounds().width(),
             work_area_bounds_in_screen.width() * 0.33);
+  EXPECT_EQ(user_action_tester.GetActionCount(
+                chromeos::kPartialSplitOneThirdUserAction),
+            1);
 }
 
 // Test Full Button Functionality.
@@ -848,7 +864,7 @@
   EXPECT_TRUE(bubble_widget);
 
   histogram_tester.ExpectBucketCount(
-      MultitaskMenuView::GetEntryTypeHistogramName(),
+      chromeos::GetEntryTypeHistogramName(),
       MultitaskMenuEntryType::kFrameSizeButtonLongTouch, 1);
 }
 
@@ -859,19 +875,18 @@
   // Check that mouse hover increments the correct bucket.
   GetEventGenerator()->MoveMouseTo(CenterPointInScreen(size_button()));
   histogram_tester.ExpectBucketCount(
-      MultitaskMenuView::GetEntryTypeHistogramName(),
+      chromeos::GetEntryTypeHistogramName(),
       MultitaskMenuEntryType::kFrameSizeButtonHover, 1);
 
   // Check that long press increments the correct bucket.
   GetEventGenerator()->MoveMouseTo(CenterPointInScreen(size_button()));
   GetEventGenerator()->PressLeftButton();
   histogram_tester.ExpectBucketCount(
-      MultitaskMenuView::GetEntryTypeHistogramName(),
+      chromeos::GetEntryTypeHistogramName(),
       MultitaskMenuEntryType::kFrameSizeButtonLongPress, 1);
 
   // Check total counts for each histogram to ensure calls aren't counted in
   // multiple buckets.
-  histogram_tester.ExpectTotalCount(
-      MultitaskMenuView::GetEntryTypeHistogramName(), 2);
+  histogram_tester.ExpectTotalCount(chromeos::GetEntryTypeHistogramName(), 2);
 }
 }  // namespace ash
diff --git a/ash/public/cpp/accessibility_focus_ring_controller.h b/ash/public/cpp/accessibility_focus_ring_controller.h
index 8411692..b9094b2 100644
--- a/ash/public/cpp/accessibility_focus_ring_controller.h
+++ b/ash/public/cpp/accessibility_focus_ring_controller.h
@@ -9,6 +9,7 @@
 #include <vector>
 
 #include "ash/public/cpp/ash_public_export.h"
+#include "base/functional/callback_forward.h"
 #include "third_party/skia/include/core/SkColor.h"
 
 namespace gfx {
@@ -50,6 +51,10 @@
   // TODO(katie): Add |caller_id| to highlights as well.
   virtual void HideHighlights() = 0;
 
+  // Callback used when SetFocusRing is called, for testing.
+  virtual void SetFocusRingObserverForTesting(
+      base::RepeatingCallback<void()> observer) = 0;
+
  protected:
   AccessibilityFocusRingController();
   virtual ~AccessibilityFocusRingController();
diff --git a/ash/public/cpp/holding_space/COMMON_METADATA b/ash/public/cpp/holding_space/COMMON_METADATA
index 982c76d..3d9b9a5 100644
--- a/ash/public/cpp/holding_space/COMMON_METADATA
+++ b/ash/public/cpp/holding_space/COMMON_METADATA
@@ -1,3 +1,13 @@
-monorail {
-  component: "UI>Shell>HoldingSpace"
+# Metadata information for this directory.
+#
+# For more information on DIR_METADATA files, see:
+#   https://chromium.googlesource.com/chromiumos/docs/+/HEAD/dir_metadata.md
+#
+# For the schema of this file, see Metadata message:
+#   https://source.chromium.org/chromium/infra/infra/+/HEAD:go/src/infra/tools/dirmd/proto/dir_metadata.proto
+
+buganizer: {
+  component_id: 1268276 # ChromeOS > Software > System UI Surfaces > HoldingSpace
 }
+
+team_email: "tote-eng@google.com"
diff --git a/ash/public/cpp/test/app_list_test_api.h b/ash/public/cpp/test/app_list_test_api.h
index 33615864..d9d7e06 100644
--- a/ash/public/cpp/test/app_list_test_api.h
+++ b/ash/public/cpp/test/app_list_test_api.h
@@ -247,7 +247,8 @@
       ReorderAnimationEndState* actual_state);
 
   // Called at the end of the reorder animation.
-  void OnReorderAnimationDone(ReorderAnimationEndState* result,
+  void OnReorderAnimationDone(bool for_bubble_app_list,
+                              ReorderAnimationEndState* result,
                               bool abort,
                               AppListGridAnimationStatus status);
 
diff --git a/ash/quick_pair/common/fast_pair/fast_pair_metrics.cc b/ash/quick_pair/common/fast_pair/fast_pair_metrics.cc
index b79b822f..5e4c7c3 100644
--- a/ash/quick_pair/common/fast_pair/fast_pair_metrics.cc
+++ b/ash/quick_pair/common/fast_pair/fast_pair_metrics.cc
@@ -537,6 +537,7 @@
 const char kSavedDevicesCount[] =
     "Bluetooth.ChromeOS.FastPair.SavedDevices.DeviceCount";
 constexpr char kFastPairGattConnectionStep[] = "FastPair.GattConnection";
+constexpr char kInitialSuccessFunnelMetric[] = "FastPair.InitialPairing";
 constexpr char kSubsequentSuccessFunnelMetric[] = "FastPair.SubsequentPairing";
 
 const std::string GetEngagementFlowInitialModelIdMetric(
@@ -593,6 +594,10 @@
   }
 }
 
+void RecordInitialSuccessFunnelFlow(FastPairInitialSuccessFunnelEvent event) {
+  base::UmaHistogramEnumeration(kInitialSuccessFunnelMetric, event);
+}
+
 void RecordSubsequentSuccessFunnelFlow(
     FastPairSubsequentSuccessFunnelEvent event) {
   base::UmaHistogramEnumeration(kSubsequentSuccessFunnelMetric, event);
diff --git a/ash/quick_pair/common/fast_pair/fast_pair_metrics.h b/ash/quick_pair/common/fast_pair/fast_pair_metrics.h
index 03b19d4..dff9d39 100644
--- a/ash/quick_pair/common/fast_pair/fast_pair_metrics.h
+++ b/ash/quick_pair/common/fast_pair/fast_pair_metrics.h
@@ -64,6 +64,26 @@
 // These values are persisted to logs. Entries should not be renumbered and
 // numeric values should never be reused. The numbers here correspond to the
 // ordering of the flow. This enum should be kept in sync with the
+// FastPairInitialSuccessFunnelEvent enum in
+// src/tools/metrics/histograms/enums.xml.
+enum class COMPONENT_EXPORT(QUICK_PAIR_COMMON)
+    FastPairInitialSuccessFunnelEvent {
+      kNotificationsClicked = 0,
+      kV1DeviceDetected = 1,
+      kInitializationStarted = 2,
+      kPairingStarted = 3,
+      kPairingComplete = 4,
+      kGuestModeDetected = 5,
+      kDeviceAlreadyAssociatedToAccount = 6,
+      kPreparingToWriteAccountKey = 7,
+      kAccountKeyWritten = 8,
+      kProcessComplete = 9,
+      kMaxValue = kProcessComplete,
+    };
+
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused. The numbers here correspond to the
+// ordering of the flow. This enum should be kept in sync with the
 // FastPairSubsequentSuccessFunnelEvent enum in
 // src/tools/metrics/histograms/enums.xml.
 enum class COMPONENT_EXPORT(QUICK_PAIR_COMMON)
@@ -135,6 +155,8 @@
 COMPONENT_EXPORT(QUICK_PAIR_COMMON)
 void AttemptRecordingFastPairEngagementFlow(const Device& device,
                                             FastPairEngagementFlowEvent event);
+COMPONENT_EXPORT(QUICK_PAIR_COMMON)
+void RecordInitialSuccessFunnelFlow(FastPairInitialSuccessFunnelEvent event);
 
 COMPONENT_EXPORT(QUICK_PAIR_COMMON)
 void RecordSubsequentSuccessFunnelFlow(
diff --git a/ash/quick_pair/keyed_service/quick_pair_metrics_logger.cc b/ash/quick_pair/keyed_service/quick_pair_metrics_logger.cc
index a4b7efd..05dd4fb0 100644
--- a/ash/quick_pair/keyed_service/quick_pair_metrics_logger.cc
+++ b/ash/quick_pair/keyed_service/quick_pair_metrics_logger.cc
@@ -105,6 +105,9 @@
               FastPairSubsequentSuccessFunnelEvent::kNotificationsClicked);
           break;
         case Protocol::kFastPairInitial:
+          RecordInitialSuccessFunnelFlow(
+              FastPairInitialSuccessFunnelEvent::kNotificationsClicked);
+          break;
         case Protocol::kFastPairRetroactive:
           break;
       }
@@ -204,6 +207,9 @@
           FastPairSubsequentSuccessFunnelEvent::kInitializationStarted);
       break;
     case Protocol::kFastPairInitial:
+      RecordInitialSuccessFunnelFlow(
+          FastPairInitialSuccessFunnelEvent::kInitializationStarted);
+      break;
     case Protocol::kFastPairRetroactive:
       break;
   }
@@ -216,6 +222,9 @@
           FastPairSubsequentSuccessFunnelEvent::kPairingStarted);
       break;
     case Protocol::kFastPairInitial:
+      RecordInitialSuccessFunnelFlow(
+          FastPairInitialSuccessFunnelEvent::kPairingStarted);
+      break;
     case Protocol::kFastPairRetroactive:
       break;
   }
@@ -228,6 +237,9 @@
           FastPairSubsequentSuccessFunnelEvent::kProcessComplete);
       break;
     case Protocol::kFastPairInitial:
+      RecordInitialSuccessFunnelFlow(
+          FastPairInitialSuccessFunnelEvent::kPairingComplete);
+      break;
     case Protocol::kFastPairRetroactive:
       break;
   }
@@ -317,8 +329,20 @@
 void QuickPairMetricsLogger::OnAccountKeyWrite(
     scoped_refptr<Device> device,
     absl::optional<AccountKeyFailure> error) {
-  if (device->protocol == Protocol::kFastPairRetroactive)
-    RecordRetroactivePairingResult(/*success=*/!error.has_value());
+  switch (device->protocol) {
+    case Protocol::kFastPairSubsequent:
+      // TODO(b/259443372): Record this case once we implement account key
+      // writing in all scenarios,
+      NOTREACHED();
+      break;
+    case Protocol::kFastPairInitial:
+      RecordInitialSuccessFunnelFlow(
+          FastPairInitialSuccessFunnelEvent::kProcessComplete);
+      break;
+    case Protocol::kFastPairRetroactive:
+      RecordRetroactivePairingResult(/*success=*/!error.has_value());
+      break;
+  }
 
   if (error.has_value()) {
     RecordAccountKeyResult(*device, /*success=*/false);
diff --git a/ash/quick_pair/keyed_service/quick_pair_metrics_logger_unittest.cc b/ash/quick_pair/keyed_service/quick_pair_metrics_logger_unittest.cc
index d3d838d..2747e37 100644
--- a/ash/quick_pair/keyed_service/quick_pair_metrics_logger_unittest.cc
+++ b/ash/quick_pair/keyed_service/quick_pair_metrics_logger_unittest.cc
@@ -46,6 +46,7 @@
 constexpr char kFastPairEngagementFlowMetricSubsequent[] =
     "Bluetooth.ChromeOS.FastPair.EngagementFunnel.Steps."
     "SubsequentPairingProtocol";
+constexpr char kInitialSuccessFunnelMetric[] = "FastPair.InitialPairing";
 constexpr char kSubsequentSuccessFunnelMetric[] = "FastPair.SubsequentPairing";
 const char kFastPairRetroactiveEngagementFlowMetric[] =
     "Bluetooth.ChromeOS.FastPair.RetroactiveEngagementFunnel.Steps";
@@ -300,6 +301,14 @@
         mock_pairer_broker_->NotifyPairComplete(subsequent_device_);
         break;
       case Protocol::kFastPairInitial:
+        mock_ui_broker_->NotifyDiscoveryAction(initial_device_,
+                                               DiscoveryAction::kPairToDevice);
+        mock_pairer_broker_->NotifyPairingStart(initial_device_);
+        mock_pairer_broker_->NotifyHandshakeComplete(initial_device_);
+        mock_pairer_broker_->NotifyPairComplete(initial_device_);
+        mock_pairer_broker_->NotifyAccountKeyWrite(initial_device_,
+                                                   /*error=*/absl::nullopt);
+        break;
       case Protocol::kFastPairRetroactive:
         break;
     }
@@ -1083,6 +1092,32 @@
             0);
 }
 
+TEST_F(QuickPairMetricsLoggerTest, LogSuccessFunnel_Initial) {
+  SimulatePairingFlow(Protocol::kFastPairInitial);
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kInitialSuccessFunnelMetric,
+                FastPairInitialSuccessFunnelEvent::kNotificationsClicked),
+            1);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kInitialSuccessFunnelMetric,
+                FastPairInitialSuccessFunnelEvent::kInitializationStarted),
+            1);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kInitialSuccessFunnelMetric,
+                FastPairInitialSuccessFunnelEvent::kPairingStarted),
+            1);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kInitialSuccessFunnelMetric,
+                FastPairInitialSuccessFunnelEvent::kPairingComplete),
+            1);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kInitialSuccessFunnelMetric,
+                FastPairInitialSuccessFunnelEvent::kProcessComplete),
+            1);
+}
+
 TEST_F(QuickPairMetricsLoggerTest, LogSuccessFunnel_Subseqent) {
   SimulatePairingFlow(Protocol::kFastPairSubsequent);
   base::RunLoop().RunUntilIdle();
diff --git a/ash/quick_pair/pairing/fast_pair/fast_pair_pairer_impl.cc b/ash/quick_pair/pairing/fast_pair/fast_pair_pairer_impl.cc
index ab2b338..463f40e 100644
--- a/ash/quick_pair/pairing/fast_pair/fast_pair_pairer_impl.cc
+++ b/ash/quick_pair/pairing/fast_pair/fast_pair_pairer_impl.cc
@@ -133,6 +133,8 @@
   // BluetoothAdapter::Observer::DevicePairedChanged event before firing the
   // |paired_callback|.
   if (device_->version().value() == DeviceFastPairVersion::kV1) {
+    RecordInitialSuccessFunnelFlow(
+        FastPairInitialSuccessFunnelEvent::kV1DeviceDetected);
     Shell::Get()->system_tray_model()->client()->ShowBluetoothPairingDialog(
         device_->ble_address);
     return;
@@ -480,6 +482,8 @@
   // be.
   if (!ShouldBeEnabledForLoginStatus(
           Shell::Get()->session_controller()->login_status())) {
+    RecordInitialSuccessFunnelFlow(
+        FastPairInitialSuccessFunnelEvent::kGuestModeDetected);
     QP_LOG(VERBOSE) << __func__ << ": No logged in user to save account key to";
     std::move(pairing_procedure_complete_).Run(device_);
     return;
@@ -537,13 +541,15 @@
     QP_LOG(INFO) << __func__
                  << ": Device is already saved, skipping write account key. "
                     "Pairing procedure complete.";
+    RecordInitialSuccessFunnelFlow(
+        FastPairInitialSuccessFunnelEvent::kDeviceAlreadyAssociatedToAccount);
     std::move(pairing_procedure_complete_).Run(device_);
     return;
   }
 
   // If we can't load the user's saved devices for some reason (e.g. offline)
   // |is_device_saved_to_account| will return false even though we didn't
-  // properly check Footrpints. This will cause us to write a new account key to
+  // properly check Footprints. This will cause us to write a new account key to
   // the device. This may cause problems since the device will have a different
   // account key than what is stored in Footprints, causing the not discoverable
   // advertisement to not be recognized.
@@ -555,6 +561,8 @@
   RAND_bytes(account_key.data(), account_key.size());
   account_key[0] = 0x04;
 
+  RecordInitialSuccessFunnelFlow(
+      FastPairInitialSuccessFunnelEvent::kPreparingToWriteAccountKey);
   fast_pair_gatt_service_client_->WriteAccountKey(
       account_key, fast_pair_handshake_->fast_pair_data_encryptor(),
       base::BindOnce(&FastPairPairerImpl::OnWriteAccountKey,
@@ -595,6 +603,8 @@
   QP_LOG(INFO)
       << __func__
       << ": Account key written to device. Pairing procedure complete.";
+  RecordInitialSuccessFunnelFlow(
+      FastPairInitialSuccessFunnelEvent::kAccountKeyWritten);
   std::move(pairing_procedure_complete_).Run(device_);
 }
 
diff --git a/ash/quick_pair/pairing/fast_pair/fast_pair_pairer_impl_unittest.cc b/ash/quick_pair/pairing/fast_pair/fast_pair_pairer_impl_unittest.cc
index b3601c2..37ee17cc 100644
--- a/ash/quick_pair/pairing/fast_pair/fast_pair_pairer_impl_unittest.cc
+++ b/ash/quick_pair/pairing/fast_pair/fast_pair_pairer_impl_unittest.cc
@@ -99,6 +99,7 @@
 const char kSavedDeviceUpdateOptInStatusSubsequentResult[] =
     "Bluetooth.ChromeOS.FastPair.SavedDevices.UpdateOptInStatus.Result."
     "SubsequentPairingProtocol";
+constexpr char kInitialSuccessFunnelMetric[] = "FastPair.InitialPairing";
 
 class FakeBluetoothAdapter
     : public testing::NiceMock<device::MockBluetoothAdapter> {
@@ -611,6 +612,11 @@
   fake_fast_pair_handshake_->InvokeCallback();
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(GetPairFailure(), absl::nullopt);
+  EXPECT_EQ(
+      histogram_tester().GetBucketCount(
+          kInitialSuccessFunnelMetric,
+          FastPairInitialSuccessFunnelEvent::kDeviceAlreadyAssociatedToAccount),
+      1);
 }
 
 TEST_F(FastPairPairerImplTest,
@@ -1401,6 +1407,10 @@
   adapter_->DevicePairedChanged(fake_bluetooth_device_ptr_, true);
   histogram_tester().ExpectTotalCount(
       kWriteAccountKeyCharacteristicResultMetric, 0);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kInitialSuccessFunnelMetric,
+                FastPairInitialSuccessFunnelEvent::kGuestModeDetected),
+            1);
 }
 
 TEST_F(FastPairPairerImplTest, WriteAccountKey_Initial_KioskAppLoggedIn) {
@@ -1943,6 +1953,10 @@
   EXPECT_CALL(paired_callback_, Run).Times(0);
   EXPECT_CALL(pairing_procedure_complete_, Run).Times(0);
   EXPECT_EQ(DeviceFastPairVersion::kV1, device_->version().value());
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kInitialSuccessFunnelMetric,
+                FastPairInitialSuccessFunnelEvent::kV1DeviceDetected),
+            1);
   DeviceUnpaired();
 }
 
@@ -1991,6 +2005,14 @@
   RunWriteAccountKeyCallback();
   histogram_tester().ExpectTotalCount(
       kWriteAccountKeyCharacteristicResultMetric, 1);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kInitialSuccessFunnelMetric,
+                FastPairInitialSuccessFunnelEvent::kPreparingToWriteAccountKey),
+            1);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kInitialSuccessFunnelMetric,
+                FastPairInitialSuccessFunnelEvent::kAccountKeyWritten),
+            1);
 }
 
 TEST_F(FastPairPairerImplTest, WriteAccount_OptedIn_StrictFlagDisabled) {
@@ -2009,6 +2031,14 @@
   PerformAndCheckSuccessfulPairingCallbacks();
   histogram_tester().ExpectTotalCount(
       kWriteAccountKeyCharacteristicResultMetric, 1);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kInitialSuccessFunnelMetric,
+                FastPairInitialSuccessFunnelEvent::kPreparingToWriteAccountKey),
+            1);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kInitialSuccessFunnelMetric,
+                FastPairInitialSuccessFunnelEvent::kAccountKeyWritten),
+            1);
 }
 
 TEST_F(FastPairPairerImplTest, WriteAccount_OptedOut_FlagDisabled) {
@@ -2028,6 +2058,14 @@
   PerformAndCheckSuccessfulPairingCallbacks();
   histogram_tester().ExpectTotalCount(
       kWriteAccountKeyCharacteristicResultMetric, 1);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kInitialSuccessFunnelMetric,
+                FastPairInitialSuccessFunnelEvent::kPreparingToWriteAccountKey),
+            1);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kInitialSuccessFunnelMetric,
+                FastPairInitialSuccessFunnelEvent::kAccountKeyWritten),
+            1);
 }
 
 TEST_F(FastPairPairerImplTest, WriteAccount_OptedOut_StrictFlagDisabled) {
@@ -2046,6 +2084,14 @@
   PerformAndCheckSuccessfulPairingCallbacks();
   histogram_tester().ExpectTotalCount(
       kWriteAccountKeyCharacteristicResultMetric, 1);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kInitialSuccessFunnelMetric,
+                FastPairInitialSuccessFunnelEvent::kPreparingToWriteAccountKey),
+            1);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kInitialSuccessFunnelMetric,
+                FastPairInitialSuccessFunnelEvent::kAccountKeyWritten),
+            1);
 }
 
 TEST_F(FastPairPairerImplTest, WriteAccount_StatusUnknown_FlagEnabled) {
@@ -2089,6 +2135,14 @@
   PerformAndCheckSuccessfulPairingCallbacks();
   histogram_tester().ExpectTotalCount(
       kWriteAccountKeyCharacteristicResultMetric, 1);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kInitialSuccessFunnelMetric,
+                FastPairInitialSuccessFunnelEvent::kPreparingToWriteAccountKey),
+            1);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kInitialSuccessFunnelMetric,
+                FastPairInitialSuccessFunnelEvent::kAccountKeyWritten),
+            1);
 }
 
 TEST_F(FastPairPairerImplTest, WriteAccount_StatusUnknown_StrictFlagDisabled) {
@@ -2125,6 +2179,14 @@
   RunWriteAccountKeyCallback();
   histogram_tester().ExpectTotalCount(
       kWriteAccountKeyCharacteristicResultMetric, 1);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kInitialSuccessFunnelMetric,
+                FastPairInitialSuccessFunnelEvent::kPreparingToWriteAccountKey),
+            1);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kInitialSuccessFunnelMetric,
+                FastPairInitialSuccessFunnelEvent::kAccountKeyWritten),
+            1);
 }
 
 TEST_F(FastPairPairerImplTest, UpdateOptInStatus_InitialPairing) {
diff --git a/ash/resources/vector_icons/BUILD.gn b/ash/resources/vector_icons/BUILD.gn
index 731cd8b..afa797f 100644
--- a/ash/resources/vector_icons/BUILD.gn
+++ b/ash/resources/vector_icons/BUILD.gn
@@ -501,6 +501,7 @@
     "unified_network_badge_vpn.icon",
     "unpinned.icon",
     "unrendered_html_placeholder.icon",
+    "video_conference_camera_muted.icon",
     "visibility.icon",
     "visibility_off.icon",
     "wallpaper.icon",
diff --git a/ash/resources/vector_icons/video_conference_camera_muted.icon b/ash/resources/vector_icons/video_conference_camera_muted.icon
new file mode 100644
index 0000000..3cc4f545
--- /dev/null
+++ b/ash/resources/vector_icons/video_conference_camera_muted.icon
@@ -0,0 +1,35 @@
+// 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.
+
+CANVAS_DIMENSIONS, 20,
+MOVE_TO, 2.23f, 2.31f,
+LINE_TO, 1, 3.55f,
+R_LINE_TO, 1.56f, 1.56f,
+ARC_TO, 1, 1, 0, 0, 0, 2, 6,
+R_V_LINE_TO, 8.01f,
+R_CUBIC_TO, 0, 0.55f, 0.45f, 1, 1, 1,
+R_H_LINE_TO, 9.45f,
+R_LINE_TO, 3.14f, 3.14f,
+R_LINE_TO, 1.24f, -1.24f,
+LINE_TO, 2.23f, 2.31f,
+CLOSE,
+MOVE_TO, 10.45f, 13,
+R_LINE_TO, -6, -6,
+H_LINE_TO, 4,
+R_V_LINE_TO, 6,
+R_H_LINE_TO, 6.45f,
+CLOSE,
+NEW_PATH,
+R_MOVE_TO, 12, 10,
+R_LINE_TO, 2, 2,
+LINE_TO, 18, 14.5f,
+R_V_LINE_TO, -9,
+R_LINE_TO, -4, 3,
+V_LINE_TO, 6,
+CUBIC_TO, 14, 5.45f, 13.55f, 5, 13, 5,
+H_LINE_TO, 7,
+R_LINE_TO, 2, 2,
+H_LINE_TO, 12,
+R_V_LINE_TO, 3,
+CLOSE
diff --git a/ash/shelf/shelf_layout_manager_unittest.cc b/ash/shelf/shelf_layout_manager_unittest.cc
index 30aa3190..b312d42 100644
--- a/ash/shelf/shelf_layout_manager_unittest.cc
+++ b/ash/shelf/shelf_layout_manager_unittest.cc
@@ -5067,7 +5067,8 @@
   gfx::ImageSkia image_skia = gfx::ImageSkia::CreateFrom1xBitmap(bitmap);
   image_skia.MakeThreadSafe();
   status_area->eche_tray()->LoadBubble(GURL("http://google.com"),
-                                       gfx::Image(image_skia), u"app 1");
+                                       gfx::Image(image_skia), u"app 1",
+                                       u"your phone");
   status_area->eche_tray()->ShowBubble();
   UpdateAutoHideStateNow();
 
diff --git a/ash/style/icon_button.cc b/ash/style/icon_button.cc
index 1cab975..c206bd9 100644
--- a/ash/style/icon_button.cc
+++ b/ash/style/icon_button.cc
@@ -150,6 +150,11 @@
   UpdateVectorIcon();
 }
 
+void IconButton::SetToggledVectorIcon(const gfx::VectorIcon& icon) {
+  toggled_icon_ = &icon;
+  UpdateVectorIcon();
+}
+
 void IconButton::SetBackgroundColor(const SkColor background_color) {
   if (background_color_ == background_color)
     return;
@@ -333,6 +338,8 @@
             : static_cast<ui::ColorId>(kColorAshButtonIconColorPrimary)));
   }
 
+  const gfx::VectorIcon* icon =
+      toggled_ && toggled_icon_ ? toggled_icon_ : icon_;
   const SkColor icon_color = toggled_ ? toggled_icon_color : normal_icon_color;
   const int icon_size = icon_size_.value_or(GetIconSizeOnType(type_));
 
@@ -342,7 +349,7 @@
   // assumes that toggled/disabled images changes at the same time as the normal
   // image, which it currently does.
   const gfx::ImageSkia new_normal_image =
-      gfx::CreateVectorIcon(*icon_, icon_size, icon_color);
+      gfx::CreateVectorIcon(*icon, icon_size, icon_color);
   const gfx::ImageSkia& old_normal_image =
       GetImage(views::Button::STATE_NORMAL);
   if (!new_normal_image.isNull() && !old_normal_image.isNull() &&
@@ -353,7 +360,7 @@
   SetImage(views::Button::STATE_NORMAL, new_normal_image);
   SetImage(
       views::Button::STATE_DISABLED,
-      gfx::CreateVectorIcon(*icon_, icon_size,
+      gfx::CreateVectorIcon(*icon, icon_size,
                             ColorUtil::GetDisabledColor(normal_icon_color)));
 }
 
diff --git a/ash/style/icon_button.h b/ash/style/icon_button.h
index 955b75b3..0121bbeb 100644
--- a/ash/style/icon_button.h
+++ b/ash/style/icon_button.h
@@ -101,6 +101,11 @@
   // states.
   void SetVectorIcon(const gfx::VectorIcon& icon);
 
+  // Sets the vector icon used when the button is toggled. If the button does
+  // not specify a toggled vector icon, it will use the same vector icon for
+  // all states.
+  void SetToggledVectorIcon(const gfx::VectorIcon& icon);
+
   // Sets the button's background color or toggled color with color value and
   // color ID when the button wants to have a different background color from
   // the default one. When both color value and color ID are set, color ID takes
@@ -151,6 +156,7 @@
 
   const Type type_;
   const gfx::VectorIcon* icon_ = nullptr;
+  const gfx::VectorIcon* toggled_icon_ = nullptr;
 
   Delegate* delegate_ = nullptr;
 
diff --git a/ash/style/knob_switch.cc b/ash/style/knob_switch.cc
new file mode 100644
index 0000000..82a05053
--- /dev/null
+++ b/ash/style/knob_switch.cc
@@ -0,0 +1,186 @@
+// 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 "ash/style/knob_switch.h"
+
+#include "ash/style/color_util.h"
+#include "ui/base/metadata/metadata_impl_macros.h"
+#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
+#include "ui/color/color_id.h"
+#include "ui/compositor/layer.h"
+#include "ui/gfx/canvas.h"
+#include "ui/views/background.h"
+#include "ui/views/controls/highlight_path_generator.h"
+
+namespace ash {
+
+namespace {
+
+// Switch, track, and knob size.
+constexpr int kSwitchWidth = 48;
+constexpr int kSwitchHeight = 32;
+constexpr int kSwitchInnerPadding = 8;
+constexpr int kTrackInnerPadding = 2;
+constexpr int kKnobRadius = 6;
+constexpr int kFocusPadding = 2;
+
+// Track and knob color ids.
+constexpr ui::ColorId kSelectedTrackColorId = cros_tokens::kCrosSysPrimary;
+constexpr ui::ColorId kSelectedKnobColorId = cros_tokens::kCrosSysOnPrimary;
+constexpr ui::ColorId kUnSelectedTrackColorId = cros_tokens::kCrosSysSecondary;
+constexpr ui::ColorId kUnSelectedKnobColorId = cros_tokens::kCrosSysOnSecondary;
+
+//------------------------------------------------------------------------------
+// ThemedFullyRoundedRectBackground
+// A themed fully rounded rect background whose corner radius equals to the half
+// of the minimum dimension of its view's local bounds.
+
+class ThemedFullyRoundedRectBackground : public views::Background {
+ public:
+  explicit ThemedFullyRoundedRectBackground(ui::ColorId color_id)
+      : color_id_(color_id) {}
+
+  ThemedFullyRoundedRectBackground(const ThemedFullyRoundedRectBackground&) =
+      delete;
+  ThemedFullyRoundedRectBackground& operator=(
+      const ThemedFullyRoundedRectBackground&) = delete;
+  ~ThemedFullyRoundedRectBackground() override = default;
+
+  static std::unique_ptr<Background> Create(ui::ColorId color_id) {
+    return std::make_unique<ThemedFullyRoundedRectBackground>(color_id);
+  }
+
+  void OnViewThemeChanged(views::View* view) override {
+    SetNativeControlColor(view->GetColorProvider()->GetColor(color_id_));
+    view->SchedulePaint();
+  }
+
+  void Paint(gfx::Canvas* canvas, views::View* view) const override {
+    // Draw a fully rounded rect filling in the view's local bounds.
+    cc::PaintFlags paint;
+    paint.setAntiAlias(true);
+
+    SkColor color = get_color();
+    if (!view->GetEnabled()) {
+      color = ColorUtil::GetDisabledColor(color);
+    }
+    paint.setColor(color);
+
+    const gfx::Rect bounds = view->GetLocalBounds();
+    const int radius = std::min(bounds.width(), bounds.height()) / 2;
+    canvas->DrawRoundRect(bounds, radius, paint);
+  }
+
+ private:
+  // Color Id of the background.
+  ui::ColorId color_id_;
+};
+
+}  // namespace
+
+//------------------------------------------------------------------------------
+// KnobSwitch:
+
+KnobSwitch::KnobSwitch(KnobSwitch::Callback switch_callback)
+    : switch_callback_(std::move(switch_callback)) {
+  // Build view hierarchy. The track view and knob view cannot be focused and
+  // process event.
+  views::Builder<KnobSwitch>(this)
+      .SetBorder(views::CreateEmptyBorder(gfx::Insets(kSwitchInnerPadding)))
+      .SetPreferredSize(gfx::Size(kSwitchWidth, kSwitchHeight))
+      .SetUseDefaultFillLayout(true)
+      .AddChildren(
+          views::Builder<views::View>()
+              .CopyAddressTo(&track_)
+              .SetFocusBehavior(views::View::FocusBehavior::NEVER)
+              .SetPaintToLayer()
+              .SetCanProcessEventsWithinSubtree(false)
+              .SetBorder(
+                  views::CreateEmptyBorder(gfx::Insets(kTrackInnerPadding)))
+              .SetBackground(ThemedFullyRoundedRectBackground::Create(
+                  kUnSelectedTrackColorId))
+              .AddChildren(
+                  views::Builder<views::View>()
+                      .CopyAddressTo(&knob_)
+                      .SetFocusBehavior(views::View::FocusBehavior::NEVER)
+                      .SetPaintToLayer()
+                      .SetCanProcessEventsWithinSubtree(false)
+                      .SetPreferredSize(
+                          gfx::Size(2 * kKnobRadius, 2 * kKnobRadius))
+                      .SetBackground(ThemedFullyRoundedRectBackground::Create(
+                          kUnSelectedKnobColorId))))
+      .BuildChildren();
+
+  track_->layer()->SetFillsBoundsOpaquely(false);
+  knob_->layer()->SetFillsBoundsOpaquely(false);
+
+  // Install a pill shaped focus ring on the track.
+  auto* focus_ring = views::FocusRing::Get(this);
+  focus_ring->SetColorId(cros_tokens::kCrosSysFocusRing);
+  const float halo_inset = focus_ring->GetHaloThickness() / 2.f + kFocusPadding;
+  focus_ring->SetHaloInset(-halo_inset);
+  auto pill_shape_path = std::make_unique<views::PillHighlightPathGenerator>();
+  pill_shape_path->set_use_contents_bounds(true);
+  views::HighlightPathGenerator::Install(this, std::move(pill_shape_path));
+
+  // The switch is unselected initially.
+  if (switch_callback_)
+    switch_callback_.Run(false);
+}
+
+KnobSwitch::~KnobSwitch() = default;
+
+void KnobSwitch::SetSelected(bool selected) {
+  if (selected_ == selected)
+    return;
+
+  selected_ = selected;
+
+  // Update the track and knob colors.
+  const ui::ColorId knob_color_id =
+      selected_ ? kSelectedKnobColorId : kUnSelectedKnobColorId;
+  const ui::ColorId track_color_id =
+      selected_ ? kSelectedTrackColorId : kUnSelectedTrackColorId;
+  knob_->SetBackground(ThemedFullyRoundedRectBackground::Create(knob_color_id));
+  track_->SetBackground(
+      ThemedFullyRoundedRectBackground::Create(track_color_id));
+
+  Layout();
+  SchedulePaint();
+
+  if (switch_callback_)
+    switch_callback_.Run(selected_);
+}
+
+void KnobSwitch::Layout() {
+  views::Button::Layout();
+
+  // If selected, move the knob to the right. Otherwise, move knob to the left.
+  const gfx::Rect track_contents_bounds = track_->GetContentsBounds();
+  const int knob_x = selected_ ? track_contents_bounds.right() - 2 * kKnobRadius
+                               : track_contents_bounds.x();
+  const int knob_y = track_contents_bounds.y();
+  knob_->SizeToPreferredSize();
+  knob_->SetPosition(gfx::Point(knob_x, knob_y));
+}
+
+void KnobSwitch::StateChanged(ButtonState old_state) {
+  if (GetState() == ButtonState::STATE_DISABLED) {
+    track_->SetEnabled(false);
+    knob_->SetEnabled(false);
+  } else if (old_state == ButtonState::STATE_DISABLED) {
+    track_->SetEnabled(true);
+    knob_->SetEnabled(true);
+  }
+}
+
+void KnobSwitch::NotifyClick(const ui::Event& event) {
+  // Switch the current selected state on click.
+  SetSelected(!selected_);
+}
+
+BEGIN_METADATA(KnobSwitch, views::View)
+END_METADATA
+
+}  // namespace ash
diff --git a/ash/style/knob_switch.h b/ash/style/knob_switch.h
new file mode 100644
index 0000000..1d77f01b
--- /dev/null
+++ b/ash/style/knob_switch.h
@@ -0,0 +1,53 @@
+// 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 ASH_STYLE_KNOB_SWITCH_H_
+#define ASH_STYLE_KNOB_SWITCH_H_
+
+#include "ash/ash_export.h"
+#include "ui/base/metadata/metadata_header_macros.h"
+#include "ui/views/controls/button/button.h"
+
+namespace ash {
+
+class ASH_EXPORT KnobSwitch : public views::Button {
+ public:
+  METADATA_HEADER(KnobSwitch);
+
+  using Callback = base::RepeatingCallback<void(bool selected)>;
+
+  explicit KnobSwitch(Callback switch_callback = Callback());
+  KnobSwitch(const KnobSwitch&) = delete;
+  KnobSwitch& operator=(const KnobSwitch&) = delete;
+  ~KnobSwitch() override;
+
+  void SetSelected(bool selected);
+  bool selected() const { return selected_; }
+
+  // views::View:
+  void Layout() override;
+
+ private:
+  // views::Button:
+  void StateChanged(ButtonState old_state) override;
+  void NotifyClick(const ui::Event& event) override;
+
+  Callback switch_callback_;
+
+  // Owned by switch view.
+  views::View* track_ = nullptr;
+  // Owned by tracker view.
+  views::View* knob_ = nullptr;
+
+  bool selected_ = false;
+};
+
+BEGIN_VIEW_BUILDER(ASH_EXPORT, KnobSwitch, views::Button)
+END_VIEW_BUILDER
+
+}  // namespace ash
+
+DEFINE_VIEW_BUILDER(ASH_EXPORT, ash::KnobSwitch)
+
+#endif  // ASH_STYLE_KNOB_SWITCH_H_
diff --git a/ash/system/cast/tray_cast.cc b/ash/system/cast/tray_cast.cc
index 47be705..b4aa211 100644
--- a/ash/system/cast/tray_cast.cc
+++ b/ash/system/cast/tray_cast.cc
@@ -9,11 +9,13 @@
 #include <utility>
 #include <vector>
 
+#include "ash/constants/ash_features.h"
 #include "ash/public/cpp/ash_view_ids.h"
 #include "ash/public/cpp/system_tray_client.h"
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
+#include "ash/style/rounded_container.h"
 #include "ash/system/model/system_tray_model.h"
 #include "ash/system/tray/hover_highlight_view.h"
 #include "ash/system/tray/tray_constants.h"
@@ -24,6 +26,7 @@
 #include "components/access_code_cast/common/access_code_cast_metrics.h"
 #include "components/vector_icons/vector_icons.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/base/resource/resource_bundle.h"
 #include "ui/gfx/image/image.h"
 #include "ui/gfx/paint_vector_icon.h"
@@ -105,20 +108,22 @@
   Layout();
 }
 
-const char* CastDetailedView::GetClassName() const {
-  return "CastDetailedView";
-}
-
 void CastDetailedView::UpdateReceiverListFromCachedData() {
   // Remove all of the existing views.
   view_to_sink_map_.clear();
   scroll_content()->RemoveAllChildViews();
 
+  // QsRevamp places items in a rounded container.
+  views::View* item_container =
+      features::IsQsRevampEnabled()
+          ? scroll_content()->AddChildView(std::make_unique<RoundedContainer>())
+          : scroll_content();
+
   // Per product requirement, access code receiver should be shown before other
   // receivers.
   if (CastConfigController::Get()->AccessCodeCastingEnabled()) {
     add_access_code_device_ = AddScrollListItem(
-        scroll_content(), vector_icons::kKeyboardIcon,
+        item_container, vector_icons::kKeyboardIcon,
         l10n_util::GetStringUTF16(
             IDS_ASH_STATUS_TRAY_CAST_ACCESS_CODE_CAST_CONNECT));
   }
@@ -127,7 +132,7 @@
   for (auto& it : sinks_and_routes_) {
     const CastSink& sink = it.second.sink;
     views::View* container = AddScrollListItem(
-        scroll_content(), SinkIconTypeToIcon(sink.sink_icon_type),
+        item_container, SinkIconTypeToIcon(sink.sink_icon_type),
         base::UTF8ToUTF16(sink.name));
     view_to_sink_map_[container] = sink.id;
   }
@@ -149,6 +154,12 @@
     Shell::Get()->system_tray_model()->client()->ShowAccessCodeCastingDialog(
         AccessCodeCastDialogOpenLocation::kSystemTrayCastMenu);
   }
+  // Close the system tray to emphasize the pinned Cast notification.
+  if (features::IsQsRevampEnabled())
+    CloseBubble();  // Deletes `this`.
 }
 
+BEGIN_METADATA(CastDetailedView, TrayDetailedView)
+END_METADATA
+
 }  // namespace ash
diff --git a/ash/system/cast/tray_cast.h b/ash/system/cast/tray_cast.h
index 732034c..4bf593b1 100644
--- a/ash/system/cast/tray_cast.h
+++ b/ash/system/cast/tray_cast.h
@@ -8,17 +8,21 @@
 #include <string>
 #include <vector>
 
+#include "ash/ash_export.h"
 #include "ash/public/cpp/cast_config_controller.h"
 #include "ash/system/tray/tray_detailed_view.h"
+#include "ui/base/metadata/metadata_header_macros.h"
 
 namespace ash {
 
 // This view displays a list of cast receivers that can be clicked on and casted
 // to. It is activated by clicking on the chevron inside of
 // |CastSelectDefaultView|.
-class CastDetailedView : public TrayDetailedView,
-                         public CastConfigController::Observer {
+class ASH_EXPORT CastDetailedView : public TrayDetailedView,
+                                    public CastConfigController::Observer {
  public:
+  METADATA_HEADER(CastDetailedView);
+
   explicit CastDetailedView(DetailedViewDelegate* delegate);
 
   CastDetailedView(const CastDetailedView&) = delete;
@@ -29,14 +33,13 @@
   // CastConfigController::Observer:
   void OnDevicesUpdated(const std::vector<SinkAndRoute>& devices) override;
 
-  // views::View:
-  const char* GetClassName() const override;
-
   views::View* get_add_access_code_device_for_testing() {
     return add_access_code_device_;
   }
 
  private:
+  friend class CastDetailedViewTest;
+
   void CreateItems();
 
   void UpdateReceiverListFromCachedData();
@@ -46,6 +49,7 @@
 
   // A mapping from the sink id to the receiver/activity data.
   std::map<std::string, SinkAndRoute> sinks_and_routes_;
+
   // A mapping from the view pointer to the associated activity sink id.
   std::map<views::View*, std::string> view_to_sink_map_;
 
diff --git a/ash/system/cast/tray_cast_unittest.cc b/ash/system/cast/tray_cast_unittest.cc
new file mode 100644
index 0000000..6f5715b
--- /dev/null
+++ b/ash/system/cast/tray_cast_unittest.cc
@@ -0,0 +1,131 @@
+// 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 "ash/system/cast/tray_cast.h"
+
+#include <memory>
+#include <vector>
+
+#include "ash/constants/ash_features.h"
+#include "ash/public/cpp/cast_config_controller.h"
+#include "ash/system/tray/fake_detailed_view_delegate.h"
+#include "ash/test/ash_test_base.h"
+#include "base/test/scoped_feature_list.h"
+#include "ui/views/view.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+namespace {
+
+class TestCastConfigController : public CastConfigController {
+ public:
+  TestCastConfigController() = default;
+  TestCastConfigController(const TestCastConfigController&) = delete;
+  TestCastConfigController& operator=(const TestCastConfigController&) = delete;
+  ~TestCastConfigController() override = default;
+
+  // CastConfigController:
+  void AddObserver(Observer* observer) override {}
+  void RemoveObserver(Observer* observer) override {}
+  bool HasMediaRouterForPrimaryProfile() const override { return true; }
+  bool HasSinksAndRoutes() const override { return false; }
+  bool HasActiveRoute() const override { return false; }
+  bool AccessCodeCastingEnabled() const override { return false; }
+  void RequestDeviceRefresh() override {}
+  const std::vector<SinkAndRoute>& GetSinksAndRoutes() override {
+    return sinks_and_routes_;
+  }
+  void CastToSink(const std::string& sink_id) override {
+    ++cast_to_sink_count_;
+  }
+  void StopCasting(const std::string& route_id) override {}
+
+  std::vector<SinkAndRoute> sinks_and_routes_;
+  size_t cast_to_sink_count_ = 0;
+};
+
+}  // namespace
+
+class CastDetailedViewTest : public AshTestBase {
+ public:
+  CastDetailedViewTest() {
+    feature_list_.InitWithFeatures(
+        /*enabled_features=*/{features::kQsRevamp, features::kQsRevampWip},
+        /*disabled_features=*/{});
+  }
+
+  // AshTestBase:
+  void SetUp() override {
+    AshTestBase::SetUp();
+    // Create a widget so tests can click on views.
+    widget_ = CreateFramelessTestWidget();
+    widget_->SetFullscreen(true);
+    delegate_ = std::make_unique<FakeDetailedViewDelegate>();
+    detailed_view_ = widget_->SetContentsView(
+        std::make_unique<CastDetailedView>(delegate_.get()));
+  }
+
+  void TearDown() override {
+    widget_.reset();
+    detailed_view_ = nullptr;
+    delegate_.reset();
+    AshTestBase::TearDown();
+  }
+
+  std::vector<views::View*> GetDeviceViews() {
+    std::vector<views::View*> views;
+    for (const auto& it : detailed_view_->view_to_sink_map_)
+      views.push_back(it.first);
+    return views;
+  }
+
+  // Adds two simulated cast devices.
+  void AddCastDevices() {
+    std::vector<SinkAndRoute> devices;
+    SinkAndRoute device1;
+    device1.sink.id = "fake_sink_id_1";
+    device1.sink.name = "Sink Name 1";
+    device1.sink.domain = "example.com";
+    device1.sink.sink_icon_type = SinkIconType::kCast;
+    devices.push_back(device1);
+    SinkAndRoute device2;
+    device2.sink.id = "fake_sink_id_2";
+    device2.sink.name = "Sink Name 2";
+    device2.sink.domain = "example.com";
+    device2.sink.sink_icon_type = SinkIconType::kCast;
+    devices.push_back(device2);
+    detailed_view_->OnDevicesUpdated(devices);
+  }
+
+  base::test::ScopedFeatureList feature_list_;
+  std::unique_ptr<views::Widget> widget_;
+  TestCastConfigController cast_config_;
+  std::unique_ptr<FakeDetailedViewDelegate> delegate_;
+  CastDetailedView* detailed_view_ = nullptr;
+};
+
+TEST_F(CastDetailedViewTest, ViewsCreatedForCastDevices) {
+  // Adding cast devices creates views.
+  AddCastDevices();
+  EXPECT_EQ(GetDeviceViews().size(), 2u);
+
+  // Device views are children of the rounded container.
+  for (views::View* view : GetDeviceViews()) {
+    EXPECT_STREQ(view->parent()->GetClassName(), "RoundedContainer");
+  }
+}
+
+TEST_F(CastDetailedViewTest, ClickOnViewClosesBubble) {
+  AddCastDevices();
+  std::vector<views::View*> views = GetDeviceViews();
+  ASSERT_FALSE(views.empty());
+  views::View* first_view = views[0];
+
+  // Clicking on a view triggers a cast session and closes the bubble.
+  LeftClickOn(first_view);
+  EXPECT_EQ(cast_config_.cast_to_sink_count_, 1u);
+  EXPECT_EQ(delegate_->close_bubble_call_count(), 1u);
+}
+
+}  // namespace ash
diff --git a/ash/system/eche/eche_tray.cc b/ash/system/eche/eche_tray.cc
index f3698e8..ffb374a1 100644
--- a/ash/system/eche/eche_tray.cc
+++ b/ash/system/eche/eche_tray.cc
@@ -395,7 +395,8 @@
 
 bool EcheTray::LoadBubble(const GURL& url,
                           const gfx::Image& icon,
-                          const std::u16string& visible_name) {
+                          const std::u16string& visible_name,
+                          const std::u16string& phone_name) {
   if (Shell::Get()->IsInTabletMode()) {
     ash::ToastManager::Get()->Show(ash::ToastData(
         kEcheTrayTabletModeNotSupportedId,
@@ -417,7 +418,7 @@
     ShowBubble();
     return true;
   }
-  InitBubble();
+  InitBubble(phone_name);
   StartLoadingAnimation();
   auto* phone_hub_tray = GetPhoneHubTray();
   if (phone_hub_tray) {
@@ -477,7 +478,7 @@
   shelf()->UpdateAutoHideState();
 }
 
-void EcheTray::InitBubble() {
+void EcheTray::InitBubble(const std::u16string& phone_name) {
   base::UmaHistogramEnumeration(
       "Eche.StreamEvent",
       eche_app::mojom::StreamStatus::kStreamStatusInitializing);
@@ -504,7 +505,8 @@
   bubble_view->SetCanActivate(true);
   bubble_view->SetBorder(views::CreateEmptyBorder(kBubblePadding));
 
-  auto* header_view = bubble_view->AddChildView(CreateBubbleHeaderView());
+  auto* header_view =
+      bubble_view->AddChildView(CreateBubbleHeaderView(phone_name));
 
   // We need the header be always visible with the same size.
   static_cast<views::BoxLayout*>(bubble_view->GetLayoutManager())
@@ -585,7 +587,8 @@
   }
 }
 
-std::unique_ptr<views::View> EcheTray::CreateBubbleHeaderView() {
+std::unique_ptr<views::View> EcheTray::CreateBubbleHeaderView(
+    const std::u16string& phone_name) {
   auto header = std::make_unique<views::View>();
   header->SetLayoutManager(std::make_unique<views::FlexLayout>())
       ->SetInteriorMargin(gfx::Insets::VH(0, kHeaderHorizontalInteriorMargins))
@@ -601,10 +604,11 @@
                    kEcheArrowBackIcon, IDS_APP_ACCNAME_BACK));
 
   views::Label* title = header->AddChildView(std::make_unique<views::Label>(
-      std::u16string(), views::style::CONTEXT_DIALOG_TITLE,
-      views::style::STYLE_PRIMARY,
+      l10n_util::GetStringFUTF16(ID_ASH_ECHE_APP_STREAMING_BUBBLE_TITLE,
+                                 phone_name),
+      views::style::CONTEXT_DIALOG_TITLE, views::style::STYLE_PRIMARY,
       gfx::DirectionalityMode::DIRECTIONALITY_AS_URL));
-  title->SetMultiLine(true);
+  title->SetMultiLine(false);
   title->SetAllowCharacterBreak(true);
   title->SetProperty(
       views::kFlexBehaviorKey,
@@ -612,7 +616,7 @@
                                views::MaximumFlexSizeRule::kUnbounded,
                                /*adjust_height_for_width =*/true)
           .WithWeight(1));
-  title->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+  title->SetHorizontalAlignment(gfx::ALIGN_CENTER);
 
   // Add minimize button
   minimize_button_ = header->AddChildView(CreateButton(
diff --git a/ash/system/eche/eche_tray.h b/ash/system/eche/eche_tray.h
index 81d54d97..9a8a4d87 100644
--- a/ash/system/eche/eche_tray.h
+++ b/ash/system/eche/eche_tray.h
@@ -182,7 +182,8 @@
   // Returns true if the bubble is loaded or initialized successfully.
   bool LoadBubble(const GURL& url,
                   const gfx::Image& icon,
-                  const std::u16string& visible_name);
+                  const std::u16string& visible_name,
+                  const std::u16string& phone_name);
 
   // Destroys the view inclusing the web view.
   // Note: `CloseBubble` only hides the view.
@@ -199,7 +200,7 @@
   // Set up the params and init the bubble.
   // Note: This function makes the bubble active and makes the
   // TrayBackgroundView's background inkdrop activate.
-  void InitBubble();
+  void InitBubble(const std::u16string& phone_name);
 
   // Starts graceful close to ensure the connection resource is released before
   // the window is closed.
@@ -243,7 +244,8 @@
 
   // Creates the header of the bubble that includes a back arrow,
   // close, and minimize buttons.
-  std::unique_ptr<views::View> CreateBubbleHeaderView();
+  std::unique_ptr<views::View> CreateBubbleHeaderView(
+      const std::u16string& phone_name);
 
   void StopLoadingAnimation();
   void StartLoadingAnimation();
diff --git a/ash/system/eche/eche_tray_unittest.cc b/ash/system/eche/eche_tray_unittest.cc
index 5d2a8cb..f116325 100644
--- a/ash/system/eche/eche_tray_unittest.cc
+++ b/ash/system/eche/eche_tray_unittest.cc
@@ -139,7 +139,7 @@
 
   eche_tray()->SetVisiblePreferred(true);
   eche_tray()->LoadBubble(GURL("http://google.com"), CreateTestImage(),
-                          u"app 1");
+                          u"app 1", u"your phone");
   eche_tray()->ShowBubble();
 
   EXPECT_TRUE(eche_tray()->is_active());
@@ -176,7 +176,7 @@
 TEST_F(EcheTrayTest, EcheTrayIconResize) {
   eche_tray()->SetVisiblePreferred(true);
   eche_tray()->LoadBubble(GURL("http://google.com"), CreateTestImage(),
-                          u"app 1");
+                          u"app 1", u"your phone");
   eche_tray()->ShowBubble();
 
   int image_width = phone_hub_tray()
@@ -196,7 +196,7 @@
 
 TEST_F(EcheTrayTest, OnAnyBubbleVisibilityChanged) {
   eche_tray()->LoadBubble(GURL("http://google.com"), CreateTestImage(),
-                          u"app 1");
+                          u"app 1", u"your phone");
   eche_tray()->ShowBubble();
 
   EXPECT_TRUE(
@@ -215,7 +215,7 @@
 // should be ignored.
 TEST_F(EcheTrayTest, OnAnyBubbleVisibilityChanged_SameWidget) {
   eche_tray()->LoadBubble(GURL("http://google.com"), CreateTestImage(),
-                          u"app 1");
+                          u"app 1", u"your phone");
   eche_tray()->ShowBubble();
 
   EXPECT_TRUE(
@@ -232,7 +232,7 @@
 // visible parameter is false, hence we should not do anything.
 TEST_F(EcheTrayTest, OnAnyBubbleVisibilityChanged_NonVisible) {
   eche_tray()->LoadBubble(GURL("http://google.com"), CreateTestImage(),
-                          u"app 1");
+                          u"app 1", u"your phone");
   eche_tray()->ShowBubble();
 
   EXPECT_TRUE(
@@ -254,7 +254,7 @@
   // Allow us to create the bubble but it is not visible until we need this
   // bubble to show up.
   eche_tray()->LoadBubble(GURL("http://google.com"), CreateTestImage(),
-                          u"app 1");
+                          u"app 1", u"your phone");
 
   EXPECT_FALSE(eche_tray()->is_active());
   EXPECT_TRUE(eche_tray()->get_bubble_wrapper_for_test());
@@ -282,7 +282,7 @@
   // Allow us to create the bubble but it is not visible until we need this
   // bubble to show up.
   eche_tray()->LoadBubble(GURL("http://google.com"), CreateTestImage(),
-                          u"app 1");
+                          u"app 1", u"your phone");
 
   EXPECT_FALSE(eche_tray()->is_active());
   EXPECT_TRUE(eche_tray()->get_bubble_wrapper_for_test());
@@ -310,7 +310,7 @@
 
 TEST_F(EcheTrayTest, EcheTrayMinimizeButtonClicked) {
   eche_tray()->LoadBubble(GURL("http://google.com"), CreateTestImage(),
-                          u"app 1");
+                          u"app 1", u"your phone");
   eche_tray()->ShowBubble();
 
   EXPECT_TRUE(
@@ -327,7 +327,7 @@
   ResetUnloadWebContent();
   eche_tray()->SetGracefulCloseCallback(base::BindOnce(&UnloadWebContent));
   eche_tray()->LoadBubble(GURL("http://google.com"), CreateTestImage(),
-                          u"app 1");
+                          u"app 1", u"your phone");
   eche_tray()->ShowBubble();
 
   ClickButton(eche_tray()->GetCloseButtonForTesting());
@@ -340,7 +340,7 @@
   eche_tray()->SetGracefulGoBackCallback(
       base::BindRepeating(&WebContentGoBack));
   eche_tray()->LoadBubble(GURL("http://google.com"), CreateTestImage(),
-                          u"app 1");
+                          u"app 1", u"your phone");
   eche_tray()->ShowBubble();
 
   ClickButton(eche_tray()->GetArrowBackButtonForTesting());
@@ -354,7 +354,7 @@
 
 TEST_F(EcheTrayTest, AcceleratorKeyHandled_Minimize) {
   eche_tray()->LoadBubble(GURL("http://google.com"), CreateTestImage(),
-                          u"app 1");
+                          u"app 1", u"your phone");
   eche_tray()->ShowBubble();
 
   EXPECT_TRUE(
@@ -381,7 +381,7 @@
   ResetUnloadWebContent();
   eche_tray()->SetGracefulCloseCallback(base::BindOnce(&UnloadWebContent));
   eche_tray()->LoadBubble(GURL("http://google.com"), CreateTestImage(),
-                          u"app 1");
+                          u"app 1", u"your phone");
   eche_tray()->ShowBubble();
 
   EXPECT_TRUE(
@@ -396,7 +396,7 @@
 
 TEST_F(EcheTrayTest, AcceleratorKeyHandled_Ctrl_C) {
   eche_tray()->LoadBubble(GURL("http://google.com"), CreateTestImage(),
-                          u"app 1");
+                          u"app 1", u"your phone");
   eche_tray()->ShowBubble();
 
   EXPECT_TRUE(
@@ -414,7 +414,7 @@
 
 TEST_F(EcheTrayTest, AcceleratorKeyHandled_Ctrl_V) {
   eche_tray()->LoadBubble(GURL("http://google.com"), CreateTestImage(),
-                          u"app 1");
+                          u"app 1", u"your phone");
   eche_tray()->ShowBubble();
 
   EXPECT_TRUE(
@@ -432,7 +432,7 @@
 
 TEST_F(EcheTrayTest, AcceleratorKeyHandled_Ctrl_X) {
   eche_tray()->LoadBubble(GURL("http://google.com"), CreateTestImage(),
-                          u"app 1");
+                          u"app 1", u"your phone");
   eche_tray()->ShowBubble();
 
   EXPECT_TRUE(
@@ -453,7 +453,7 @@
   eche_tray()->SetGracefulGoBackCallback(
       base::BindRepeating(&WebContentGoBack));
   eche_tray()->LoadBubble(GURL("http://google.com"), CreateTestImage(),
-                          u"app 1");
+                          u"app 1", u"your phone");
   eche_tray()->ShowBubble();
 
   GetEventGenerator()->PressKey(ui::KeyboardCode::VKEY_BROWSER_BACK, 0);
@@ -465,7 +465,7 @@
   ResetUnloadWebContent();
   eche_tray()->SetGracefulCloseCallback(base::BindOnce(&UnloadWebContent));
   eche_tray()->LoadBubble(GURL("http://google.com"), CreateTestImage(),
-                          u"app 1");
+                          u"app 1", u"your phone");
   eche_tray()->ShowBubble();
 
   EXPECT_TRUE(
@@ -482,7 +482,7 @@
   UpdateDisplay("800x600");
   gfx::Size expected_eche_size = eche_tray()->CalculateSizeForEche();
   eche_tray()->LoadBubble(GURL("http://google.com"), CreateTestImage(),
-                          u"app 1");
+                          u"app 1", u"your phone");
   eche_tray()->ShowBubble();
 
   EXPECT_EQ(expected_eche_size.width(),
@@ -506,7 +506,7 @@
 TEST_F(EcheTrayTest, EcheTrayKeyboardShowHideUpdateBubbleBounds) {
   gfx::Size expected_eche_size = eche_tray()->CalculateSizeForEche();
   eche_tray()->LoadBubble(GURL("http://google.com"), CreateTestImage(),
-                          u"app 1");
+                          u"app 1", u"your phone");
   eche_tray()->ShowBubble();
 
   EXPECT_EQ(expected_eche_size.width(),
diff --git a/ash/system/keyboard_brightness/keyboard_brightness_controller.cc b/ash/system/keyboard_brightness/keyboard_brightness_controller.cc
index ca9fd2c3..342635f 100644
--- a/ash/system/keyboard_brightness/keyboard_brightness_controller.cc
+++ b/ash/system/keyboard_brightness/keyboard_brightness_controller.cc
@@ -4,21 +4,11 @@
 
 #include "ash/system/keyboard_brightness/keyboard_brightness_controller.h"
 
-#include "ash/constants/ash_pref_names.h"
 #include "chromeos/dbus/power/power_manager_client.h"
 #include "chromeos/dbus/power_manager/backlight.pb.h"
-#include "components/prefs/pref_registry_simple.h"
 
 namespace ash {
 
-static constexpr int kDefaultKeyboardBrightness = 60;
-
-// static
-void KeyboardBrightnessController::RegisterPrefs(PrefRegistrySimple* registry) {
-  registry->RegisterIntegerPref(prefs::kPersonalizationKeyboardBrightness,
-                                kDefaultKeyboardBrightness);
-}
-
 void KeyboardBrightnessController::HandleKeyboardBrightnessDown() {
   chromeos::PowerManagerClient::Get()->DecreaseKeyboardBrightness();
 }
diff --git a/ash/system/keyboard_brightness/keyboard_brightness_controller.h b/ash/system/keyboard_brightness/keyboard_brightness_controller.h
index 9fef6c2..a60fc6cf 100644
--- a/ash/system/keyboard_brightness/keyboard_brightness_controller.h
+++ b/ash/system/keyboard_brightness/keyboard_brightness_controller.h
@@ -8,8 +8,6 @@
 #include "ash/ash_export.h"
 #include "ash/system/keyboard_brightness_control_delegate.h"
 
-class PrefRegistrySimple;
-
 namespace ash {
 
 // A class which controls keyboard brightness when Alt+F6, Alt+F7 or a
@@ -19,9 +17,6 @@
  public:
   KeyboardBrightnessController() = default;
 
-  // Register the pref to store keyboard brightness in the given registry.
-  static void RegisterPrefs(PrefRegistrySimple* registry);
-
   // Disallow copy and move.
   KeyboardBrightnessController(const KeyboardBrightnessController&) = delete;
   KeyboardBrightnessController& operator=(const KeyboardBrightnessController&) =
diff --git a/ash/system/phonehub/camera_roll_thumbnail.cc b/ash/system/phonehub/camera_roll_thumbnail.cc
index e360b51..42f8998 100644
--- a/ash/system/phonehub/camera_roll_thumbnail.cc
+++ b/ash/system/phonehub/camera_roll_thumbnail.cc
@@ -10,6 +10,7 @@
 #include "chromeos/ash/components/multidevice/logging/logging.h"
 #include "chromeos/ash/components/phonehub/camera_roll_manager.h"
 #include "chromeos/ash/components/phonehub/user_action_recorder.h"
+#include "third_party/skia/include/core/SkRRect.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/color/color_id.h"
 #include "ui/gfx/paint_vector_icon.h"
diff --git a/ash/system/phonehub/phone_hub_ui_controller_unittest.cc b/ash/system/phonehub/phone_hub_ui_controller_unittest.cc
index 39a89e7..b8eb5e5 100644
--- a/ash/system/phonehub/phone_hub_ui_controller_unittest.cc
+++ b/ash/system/phonehub/phone_hub_ui_controller_unittest.cc
@@ -391,7 +391,8 @@
 TEST_F(PhoneHubUiControllerTest, HandleBubbleOpenedShouldCloseEcheBubble) {
   EcheTray* eche_tray =
       StatusAreaWidgetTestHelper::GetStatusAreaWidget()->eche_tray();
-  eche_tray->LoadBubble(GURL("http://google.com"), gfx::Image(), u"app 1");
+  eche_tray->LoadBubble(GURL("http://google.com"), gfx::Image(), u"app 1",
+                        u"your phone");
   eche_tray->ShowBubble();
   EXPECT_TRUE(
       eche_tray->get_bubble_wrapper_for_test()->bubble_view()->GetVisible());
diff --git a/ash/system/status_area_widget_unittest.cc b/ash/system/status_area_widget_unittest.cc
index f25764d..64a12dc 100644
--- a/ash/system/status_area_widget_unittest.cc
+++ b/ash/system/status_area_widget_unittest.cc
@@ -705,7 +705,8 @@
   gfx::ImageSkia image_skia = gfx::ImageSkia::CreateFrom1xBitmap(bitmap);
   image_skia.MakeThreadSafe();
   status_area->eche_tray()->LoadBubble(GURL("http://google.com"),
-                                       gfx::Image(image_skia), u"app 1");
+                                       gfx::Image(image_skia), u"app 1",
+                                       u"your phone");
   status_area->eche_tray()->ShowBubble();
 
   // Auto-hidden shelf would be forced to be visible.
diff --git a/ash/system/video_conference/video_conference_tray.cc b/ash/system/video_conference/video_conference_tray.cc
index c401b1a4..6b6e6722 100644
--- a/ash/system/video_conference/video_conference_tray.cc
+++ b/ash/system/video_conference/video_conference_tray.cc
@@ -31,6 +31,7 @@
 
 namespace {
 
+constexpr float kTrayButtonsSpacing = 4;
 constexpr float kPrivacyIndicatorRadius = 4;
 constexpr float kIndicatorBorderWidth = 1;
 
@@ -39,9 +40,9 @@
  public:
   ToggleBubbleButton(VideoConferenceTray* tray, PressedCallback callback)
       : IconButton(std::move(callback),
-                   IconButton::Type::kMedium,
+                   IconButton::Type::kMediumFloating,
                    &kUnifiedMenuExpandIcon,
-                   IDS_ASH_STATUS_TRAY_SCREEN_SHARE_TITLE,
+                   IDS_ASH_VIDEO_CONFERENCE_TOGGLE_BUBBLE_BUTTON_TOOLTIP,
                    /*is_togglable=*/true,
                    /*has_border=*/true),
         tray_(tray) {}
@@ -51,13 +52,16 @@
 
   // IconButton:
   void PaintButtonContents(gfx::Canvas* canvas) override {
-    const gfx::Rect rect(GetContentsBounds());
-    cc::PaintFlags flags;
-    flags.setAntiAlias(true);
-    flags.setColor(GetBackgroundColor());
-    flags.setStyle(cc::PaintFlags::kFill_Style);
-    canvas->DrawCircle(gfx::PointF(rect.CenterPoint()), rect.width() / 2,
-                       flags);
+    // Only draw the background when the button is toggled.
+    if (toggled()) {
+      const gfx::Rect rect(GetContentsBounds());
+      cc::PaintFlags flags;
+      flags.setAntiAlias(true);
+      flags.setColor(GetBackgroundColor());
+      flags.setStyle(cc::PaintFlags::kFill_Style);
+      canvas->DrawCircle(gfx::PointF(rect.CenterPoint()), rect.width() / 2,
+                         flags);
+    }
 
     // Rotate the canvas to rotate the expand indicator according to toggle
     // state and shelf alignment. Note that when shelf alignment changes,
@@ -80,13 +84,19 @@
 VideoConferenceTrayButton::VideoConferenceTrayButton(
     PressedCallback callback,
     const gfx::VectorIcon* icon,
+    const gfx::VectorIcon* toggled_icon,
     const int accessible_name_id)
     : IconButton(std::move(callback),
-                 IconButton::Type::kLarge,
+                 IconButton::Type::kMedium,
                  icon,
                  accessible_name_id,
                  /*is_togglable=*/true,
-                 /*has_border=*/true) {}
+                 /*has_border=*/true) {
+  // TODO(b/261620616): Update this color according to spec.
+  SetBackgroundToggledColorId(kColorAshControlBackgroundColorAlert);
+
+  SetToggledVectorIcon(*toggled_icon);
+}
 
 VideoConferenceTrayButton::~VideoConferenceTrayButton() = default;
 
@@ -132,23 +142,26 @@
 VideoConferenceTray::VideoConferenceTray(Shelf* shelf)
     : TrayBackgroundView(shelf,
                          TrayBackgroundViewCatalogName::kVideoConferenceTray) {
+  tray_container()->SetSpacingBetweenChildren(kTrayButtonsSpacing);
+
   audio_icon_ = tray_container()->AddChildView(
       std::make_unique<VideoConferenceTrayButton>(
           base::BindRepeating(&VideoConferenceTray::OnAudioButtonClicked,
                               weak_ptr_factory_.GetWeakPtr()),
-          &kPrivacyIndicatorsMicrophoneIcon,
+          &kPrivacyIndicatorsMicrophoneIcon, &kPrivacyIndicatorsMicrophoneIcon,
           IDS_PRIVACY_NOTIFICATION_TITLE_MIC));
   camera_icon_ = tray_container()->AddChildView(
       std::make_unique<VideoConferenceTrayButton>(
           base::BindRepeating(&VideoConferenceTray::OnCameraButtonClicked,
                               weak_ptr_factory_.GetWeakPtr()),
-          &kPrivacyIndicatorsCameraIcon,
+          &kPrivacyIndicatorsCameraIcon, &kVideoConferenceCameraMutedIcon,
           IDS_PRIVACY_NOTIFICATION_TITLE_CAMERA));
   screen_share_icon_ = tray_container()->AddChildView(
       std::make_unique<VideoConferenceTrayButton>(
           base::BindRepeating(&VideoConferenceTray::OnScreenShareButtonClicked,
                               weak_ptr_factory_.GetWeakPtr()),
           &kPrivacyIndicatorsScreenShareIcon,
+          &kPrivacyIndicatorsScreenShareIcon,
           IDS_ASH_STATUS_TRAY_SCREEN_SHARE_TITLE));
   toggle_bubble_button_ =
       tray_container()->AddChildView(std::make_unique<ToggleBubbleButton>(
diff --git a/ash/system/video_conference/video_conference_tray.h b/ash/system/video_conference/video_conference_tray.h
index bae4bc9..405111c 100644
--- a/ash/system/video_conference/video_conference_tray.h
+++ b/ash/system/video_conference/video_conference_tray.h
@@ -39,6 +39,7 @@
  public:
   VideoConferenceTrayButton(PressedCallback callback,
                             const gfx::VectorIcon* icon,
+                            const gfx::VectorIcon* toggled_icon,
                             const int accessible_name_id);
 
   VideoConferenceTrayButton(const VideoConferenceTrayButton&) = delete;
diff --git a/ash/wallpaper/wallpaper_controller_impl.cc b/ash/wallpaper/wallpaper_controller_impl.cc
index 98ad99ca..f5536ff 100644
--- a/ash/wallpaper/wallpaper_controller_impl.cc
+++ b/ash/wallpaper/wallpaper_controller_impl.cc
@@ -2951,12 +2951,14 @@
                        /*save_file=*/true, std::move(callback)));
   } else {
     // Start fetching the wallpaper variants.
-    url_to_image_map_.clear();
+    num_variants_downloaded_ = 0;
+    online_wallpaper_variant_to_use_ = gfx::ImageSkia();
     auto on_done = base::BarrierClosure(
         variants.size(),
         base::BindOnce(
             &WallpaperControllerImpl::OnAllOnlineWallpaperVariantsDownloaded,
-            weak_factory_.GetWeakPtr(), params, std::move(callback)));
+            set_wallpaper_weak_factory_.GetWeakPtr(), params,
+            std::move(callback)));
 
     for (size_t i = 0; i < variants.size(); i++) {
       ImageDownloader::Get()->Download(
@@ -2983,8 +2985,10 @@
 
   const std::vector<OnlineWallpaperVariant>& variants = params.variants;
   const OnlineWallpaperVariant& current_variant = variants.at(current_index);
-  // Keep track of each downloaded image.
-  url_to_image_map_.insert({current_variant.raw_url.spec(), image});
+
+  ++num_variants_downloaded_;
+  if (params.url == current_variant.raw_url)
+    online_wallpaper_variant_to_use_ = image;
 
   // Save the image to disk.
   image.EnsureRepsForSupportedScales();
@@ -2999,15 +3003,21 @@
 void WallpaperControllerImpl::OnAllOnlineWallpaperVariantsDownloaded(
     const OnlineWallpaperParams& params,
     SetWallpaperCallback callback) {
-  bool success = url_to_image_map_.size() == params.variants.size() &&
-                 !url_to_image_map_.at(params.url.spec()).isNull();
+  bool success = num_variants_downloaded_ == params.variants.size() &&
+                 !online_wallpaper_variant_to_use_.isNull();
+  // Now that all variants are downloaded, there's no point in maintaining
+  // |online_wallpaper_variant_to_use_|. Keeping it around just leaves an unused
+  // reference count to the underlying image memory until the next wallpaper is
+  // "set", preventing that memory from being freed until then.
+  gfx::ImageSkia variant_to_use = online_wallpaper_variant_to_use_;
+  online_wallpaper_variant_to_use_ = gfx::ImageSkia();
   if (!success) {
     std::move(callback).Run(success);
     return;
   }
 
   OnOnlineWallpaperDecoded(params, /*save_file=*/false, std::move(callback),
-                           url_to_image_map_.at(params.url.spec()));
+                           variant_to_use);
 }
 
 constexpr bool WallpaperControllerImpl::IsWallpaperTypeSyncable(
diff --git a/ash/wallpaper/wallpaper_controller_impl.h b/ash/wallpaper/wallpaper_controller_impl.h
index 2664c7bd..ce6ec82 100644
--- a/ash/wallpaper/wallpaper_controller_impl.h
+++ b/ash/wallpaper/wallpaper_controller_impl.h
@@ -871,9 +871,14 @@
 
   base::FilePathWatcher drive_fs_wallpaper_watcher_;
 
-  // Used to capture wallpaper variants' url to their corresponding image.
-  // Cleared every time downloading wallpapers is initiated.
-  base::flat_map<std::string, gfx::ImageSkia> url_to_image_map_;
+  // Transient storage for the wallpaper variant (out of the N total variants
+  // that may exist for a given "unit") that was requested by the client. The
+  // other N - 1 variants are saved to disc for potential future usage. After
+  // all variants have been downloaded and saved, the
+  // |online_wallpaper_variant_to_use_| is released and passed on for further
+  // processing in the pipeline.
+  gfx::ImageSkia online_wallpaper_variant_to_use_;
+  size_t num_variants_downloaded_ = 0;
 
   // If true, use a solid color wallpaper as if it is the decoded image.
   bool bypass_decode_for_testing_ = false;
diff --git a/ash/webui/camera_app_ui/BUILD.gn b/ash/webui/camera_app_ui/BUILD.gn
index 22e369f..0f013c2 100644
--- a/ash/webui/camera_app_ui/BUILD.gn
+++ b/ash/webui/camera_app_ui/BUILD.gn
@@ -83,7 +83,6 @@
   public_deps = [ "//dbus" ]
 
   deps = [
-    "//ash/constants",
     "//base",
     "//chromeos/ash/components/dbus/dlcservice",
     "//chromeos/ash/components/dbus/dlcservice:dlcservice_proto",
diff --git a/ash/webui/camera_app_ui/document_scanner_service_client.cc b/ash/webui/camera_app_ui/document_scanner_service_client.cc
index 42b93cc2..f275896 100644
--- a/ash/webui/camera_app_ui/document_scanner_service_client.cc
+++ b/ash/webui/camera_app_ui/document_scanner_service_client.cc
@@ -4,7 +4,6 @@
 
 #include "ash/webui/camera_app_ui/document_scanner_service_client.h"
 
-#include "ash/constants/ash_features.h"
 #include "ash/webui/camera_app_ui/document_scanner_installer.h"
 #include "base/command_line.h"
 #include "base/memory/ptr_util.h"
@@ -44,9 +43,6 @@
 
 // Returns true if switch kOndeviceDocumentScanner is set to use_dlc.
 bool IsEnabledOnDlc() {
-  if (!base::FeatureList::IsEnabled(features::kCameraAppDocScanDlc)) {
-    return false;
-  }
   return HasCommandLineSwitch(kOndeviceDocumentScanner, "use_dlc");
 }
 
diff --git a/ash/webui/camera_app_ui/resources/js/device/capture_candidate_preferrer.ts b/ash/webui/camera_app_ui/resources/js/device/capture_candidate_preferrer.ts
index a8e64508..b788c45 100644
--- a/ash/webui/camera_app_ui/resources/js/device/capture_candidate_preferrer.ts
+++ b/ash/webui/camera_app_ui/resources/js/device/capture_candidate_preferrer.ts
@@ -9,7 +9,6 @@
   AspectRatioSet,
   LocalStorageKey,
   Mode,
-  NON_CROP_ASPECT_RATIO_SETS,
   PhotoResolutionLevel,
   Resolution,
   VideoResolutionLevel,
@@ -133,6 +132,8 @@
   private readonly videoResolutionOptionListeners:
       VideoResolutionOptionListener[] = [];
 
+  private preferPhotoAspectRatioOrder: AspectRatioSet[] = [];
+
   /**
    * Adds listener for photo resolution options.
    */
@@ -528,14 +529,33 @@
   }
 
   private buildPhotoOptions(deviceId: string, resolutions: Resolution[]): void {
+    const defaultPreferOrder = [
+      AspectRatioSet.RATIO_4_3,
+      AspectRatioSet.RATIO_16_9,
+      AspectRatioSet.RATIO_OTHER,
+    ];
+    // Making sure that the prefer aspect ratio has resolution which is equal to
+    // or larger than 720p.
+    const prioritizedAspectRatioSet =
+        defaultPreferOrder.find(
+            (ratio) => resolutions.some(
+                (r) => toAspectRatioSet(r) === ratio && r.height >= 720)) ??
+        defaultPreferOrder[0];
+    this.preferPhotoAspectRatioOrder = [
+      prioritizedAspectRatioSet,
+      ...defaultPreferOrder.filter(
+          (ratio) => ratio !== prioritizedAspectRatioSet),
+    ];
+
     /**
      * Categorizes the photo resolutions according to their aspect ratio and
      * sorts them.
      */
-    function groupResolutions(resolutions: Resolution[]):
+    function groupResolutions(
+        resolutions: Resolution[], preferAspectRatioSetOrder: AspectRatioSet[]):
         Map<AspectRatioSet, Resolution[]> {
       const resolutionGroups = new Map<AspectRatioSet, Resolution[]>();
-      for (const aspectRatioSet of NON_CROP_ASPECT_RATIO_SETS) {
+      for (const aspectRatioSet of preferAspectRatioSetOrder) {
         resolutionGroups.set(aspectRatioSet, []);
       }
 
@@ -546,9 +566,10 @@
       return resolutionGroups;
     }
 
-    const resolutionGroups = groupResolutions(resolutions);
+    const resolutionGroups =
+        groupResolutions(resolutions, this.preferPhotoAspectRatioOrder);
     const options = new Map<AspectRatioSet, PhotoResolutionOption[]>();
-    for (const aspectRatioSet of NON_CROP_ASPECT_RATIO_SETS) {
+    for (const aspectRatioSet of this.preferPhotoAspectRatioOrder) {
       const resolutionGroup = resolutionGroups.get(aspectRatioSet);
       assert(resolutionGroup !== undefined);
       if (resolutionGroup.length > 0) {
@@ -705,7 +726,8 @@
       return toAspectRatioSet(this.cameraConfig.captureCandidate.resolution);
     } else {
       return prefAspectRatioSet ??
-          getFallbackAspectRatioSet(aspectRatioOptionsMap);
+          getFallbackAspectRatioSet(
+                 aspectRatioOptionsMap, this.preferPhotoAspectRatioOrder);
     }
   }
 
@@ -895,14 +917,9 @@
 }
 
 function getFallbackAspectRatioSet(
-    aspectRatioOptionsMap: Map<AspectRatioSet, PhotoResolutionOption[]>):
-    AspectRatioSet {
-  const preferenceOrder = [
-    AspectRatioSet.RATIO_4_3,
-    AspectRatioSet.RATIO_16_9,
-    AspectRatioSet.RATIO_OTHER,
-  ];
-  for (const aspectRatioSet of preferenceOrder) {
+    aspectRatioOptionsMap: Map<AspectRatioSet, PhotoResolutionOption[]>,
+    preferAspectRatioSetOrder: AspectRatioSet[]): AspectRatioSet {
+  for (const aspectRatioSet of preferAspectRatioSetOrder) {
     if (aspectRatioOptionsMap.has(aspectRatioSet)) {
       return aspectRatioSet;
     }
diff --git a/ash/webui/camera_app_ui/resources/js/type.ts b/ash/webui/camera_app_ui/resources/js/type.ts
index 5540ef7..a6de866 100644
--- a/ash/webui/camera_app_ui/resources/js/type.ts
+++ b/ash/webui/camera_app_ui/resources/js/type.ts
@@ -179,12 +179,6 @@
   RATIO_SQUARE = 1.0000,
 }
 
-export const NON_CROP_ASPECT_RATIO_SETS = [
-  AspectRatioSet.RATIO_4_3,
-  AspectRatioSet.RATIO_16_9,
-  AspectRatioSet.RATIO_OTHER,
-];
-
 export enum Rotation {
   ANGLE_0 = 0,
   ANGLE_90 = 90,
diff --git a/ash/webui/common/resources/keyboard_layouts.js b/ash/webui/common/resources/keyboard_layouts.js
index 1a3cad36..8d1b6bc 100644
--- a/ash/webui/common/resources/keyboard_layouts.js
+++ b/ash/webui/common/resources/keyboard_layouts.js
@@ -646,6 +646,12 @@
   'ca.ansi': kUsEnglish,
   /* Canada (French keyboard) */
   'ca.fr': kCaFrench,
+  /* Canada (Hybrid ISO Keyboard) */
+  'ca.hybrid': kCaFrench,
+  /* Canada (Hybrid Ansi keyboard) */
+  'ca.hybridansi': kCaFrench,
+  /* Canada (Multilingual ISO, Probably not in use) */
+  'ca.multix': kCaFrench,
   /* Switzerland */
   'ch': [
     ...kQwertzLetters,
@@ -1379,22 +1385,92 @@
   ],
   /* Turkey */
   'tr': [
-    // TODO(b/221920512): We apparently ship two very different layouts in
-    // Turkey: F-type and Q-type. It's unclear how to distinguish between them
-    // in code, so for now only the glyphs they have in common are shown.
+    ...kQwertyLetters,
+    [41, {bottomLeft: '"', topLeft: 'é'}],
     [2, {bottomLeft: '1', topLeft: '!'}],
-    [3, {bottomLeft: '2', topLeft: ' '}],
+    [3, {bottomLeft: '2', topLeft: '\''}],
     [4, {bottomLeft: '3', topLeft: '◌̂'}],
-    [5, {bottomLeft: '4', topLeft: ' '}],
+    [5, {bottomLeft: '4', topLeft: '+'}],
     [6, {bottomLeft: '5', topLeft: '%'}],
     [7, {bottomLeft: '6', topLeft: '&'}],
-    [8, {bottomLeft: '7', topLeft: ' '}],
+    [8, {bottomLeft: '7', topLeft: '/'}],
     [9, {bottomLeft: '8', topLeft: '('}],
     [10, {bottomLeft: '9', topLeft: ')'}],
     [11, {bottomLeft: '0', topLeft: '='}],
-    [12, {bottomLeft: ' ', topLeft: '?'}],
+    [12, {bottomLeft: '*', topLeft: '?'}],
     [13, {bottomLeft: '-', topLeft: '_'}],
 
+    [16, {main: 'q', bottomRight: '@'}],
+    [18, {main: 'e', bottomRight: '€'}],
+    [20, {main: 't', bottomRight: '₺'}],
+    [23, 'ı'],
+    [26, 'ğ'],
+    [27, 'ü'],
+
+    [39, 'ş'],
+    [40, 'i'],
+    [43, {bottomLeft: ',', topLeft: ';'}],
+
+    [51, 'ö'],
+    [52, 'ç'],
+    [53, {bottomLeft: '.', topLeft: ':'}],
+
+    [86, {bottomLeft: '<', topLeft: '>'}],
+
+    [100, 'alt gr'],
+  ],
+  'tr.f': [
+    [41, {bottomLeft: '+', topLeft: '*'}],
+    [2, {bottomLeft: '1', topLeft: '!'}],
+    [3, {bottomLeft: '2', topLeft: '"'}],
+    [4, {bottomLeft: '3', topLeft: '◌̂'}],
+    [5, {bottomLeft: '4', topLeft: '$'}],
+    [6, {bottomLeft: '5', topLeft: '%'}],
+    [7, {bottomLeft: '6', topLeft: '&'}],
+    [8, {bottomLeft: '7', topLeft: '\''}],
+    [9, {bottomLeft: '8', topLeft: '('}],
+    [10, {bottomLeft: '9', topLeft: ')'}],
+    [11, {bottomLeft: '0', topLeft: '='}],
+    [12, {bottomLeft: '/', topLeft: '?'}],
+    [13, {bottomLeft: '-', topLeft: '_'}],
+
+    [16, 'f'],
+    [17, 'g'],
+    [18, 'ğ'],
+    [19, 'ı'],
+    [20, 'o'],
+    [21, 'd'],
+    [22, 'r'],
+    [23, 'n'],
+    [24, 'h'],
+    [25, 'p'],
+    [26, 'q'],
+    [27, 'w'],
+
+    [30, 'u'],
+    [31, 'i'],
+    [32, 'e'],
+    [33, 'a'],
+    [34, 'ü'],
+    [35, 't'],
+    [36, 'k'],
+    [37, 'm'],
+    [38, 'l'],
+    [39, 'y'],
+    [40, 'ş'],
+    [43, 'x'],
+
+    [44, 'j'],
+    [45, 'ö'],
+    [46, 'v'],
+    [47, 'c'],
+    [48, 'ç'],
+    [49, 'z'],
+    [50, 's'],
+    [51, 'b'],
+    [52, {bottomLeft: '.', topLeft: ':'}],
+    [53, {bottomLeft: ',', topLeft: ';'}],
+
     [86, {bottomLeft: '<', topLeft: '>'}],
 
     [100, 'alt gr'],
diff --git a/ash/webui/common/resources/network/apn_detail_dialog.html b/ash/webui/common/resources/network/apn_detail_dialog.html
index 68225f5..c63ffc0 100644
--- a/ash/webui/common/resources/network/apn_detail_dialog.html
+++ b/ash/webui/common/resources/network/apn_detail_dialog.html
@@ -121,16 +121,16 @@
   </div>
   <div slot="button-container">
     <template is="dom-if"
-        if="[[isUiElementVisible_(UiElement.ADD_BUTTON, mode)]]" restamp>
+        if="[[isUiElementVisible_(UiElement.ACTION_BUTTON, mode)]]" restamp>
       <cr-button id="apnDetailCancelBtn" class="cancel-button"
           on-click="onCancelClicked_">
         [[i18n('cancel')]]
       </cr-button>
-      <cr-button id="apnDetailAddBtn" class="action-button"
-          on-click="onAddClicked_"
-          disabled="[[isUiElementDisabled_(UiElement.ADD_BUTTON, mode, apn_,
-              isApnInputInvalid_)]]">
-        [[i18n('add')]]
+      <cr-button id="apnDetailActionBtn" class="action-button"
+          on-click="onActionButtonClicked_"
+          disabled="[[isUiElementDisabled_(UiElement.ACTION_BUTTON, mode,
+              apn_, isApnInputInvalid_)]]">
+        [[getActionButtonTitle_(mode)]]
       </cr-button>
     </template>
     <template is="dom-if"
diff --git a/ash/webui/common/resources/network/apn_detail_dialog.js b/ash/webui/common/resources/network/apn_detail_dialog.js
index 72f0d060..7fff321e 100644
--- a/ash/webui/common/resources/network/apn_detail_dialog.js
+++ b/ash/webui/common/resources/network/apn_detail_dialog.js
@@ -43,7 +43,7 @@
 /** @enum {number} */
 const UiElement = {
   INPUT: 0,
-  ADD_BUTTON: 1,
+  ACTION_BUTTON: 1,
   DONE_BUTTON: 2,
 };
 
@@ -270,9 +270,19 @@
    * @param {!Event} event
    * @private
    */
-  onAddClicked_(event) {
+  onActionButtonClicked_(event) {
     assert(this.guid);
+    assert(this.mode !== ApnDetailDialogMode.VIEW);
+    if (this.mode === ApnDetailDialogMode.CREATE) {
+      this.createCustomApn_();
+    }
+    // TODO(b/162365553): When the mode is edit call modifyCustomApn()
 
+    this.$.apnDetailDialog.close();
+  }
+
+  /** @private */
+  createCustomApn_() {
     const apnProperties = /** @type {!ApnProperties} */ ({
       accessPointName: this.apn_,
       username: this.username_,
@@ -283,11 +293,19 @@
       apnTypes: this.getSelectedApnTypes_(),
     });
     this.networkConfig_.createCustomApn(this.guid, apnProperties);
-
-    this.$.apnDetailDialog.close();
   }
 
   /**
+   * @return {string}
+   * @private
+   */
+  getActionButtonTitle_() {
+    if (this.mode === ApnDetailDialogMode.EDIT) {
+      return this.i18n('save');
+    }
+    return this.i18n('add');
+  }
+  /**
    * @private
    */
   getDialogTitle_() {
@@ -297,8 +315,7 @@
       case ApnDetailDialogMode.VIEW:
         return this.i18n('apnDetailViewApnDialogTitle');
       case ApnDetailDialogMode.EDIT:
-        // TODO(b/162365553): Add edit mode for the apn detail dialog.
-        return '';
+        return this.i18n('apnDetailEditApnDialogTitle');
     }
   }
   /**
@@ -375,7 +392,7 @@
     switch (uiElement) {
       case UiElement.INPUT:
         return this.mode === ApnDetailDialogMode.VIEW;
-      case UiElement.ADD_BUTTON:
+      case UiElement.ACTION_BUTTON:
         return this.apn_.length === 0 || this.isApnInputInvalid_;
     }
     return false;
@@ -390,8 +407,9 @@
     switch (uiElement) {
       case UiElement.DONE_BUTTON:
         return this.mode === ApnDetailDialogMode.VIEW;
-      case UiElement.ADD_BUTTON:
-        return this.mode === ApnDetailDialogMode.CREATE;
+      case UiElement.ACTION_BUTTON:
+        return this.mode === ApnDetailDialogMode.CREATE ||
+            this.mode === ApnDetailDialogMode.EDIT;
     }
     return true;
   }
diff --git a/ash/webui/common/resources/network/apn_list_item.js b/ash/webui/common/resources/network/apn_list_item.js
index fa969a5..3d45381 100644
--- a/ash/webui/common/resources/network/apn_list_item.js
+++ b/ash/webui/common/resources/network/apn_list_item.js
@@ -85,13 +85,14 @@
   onDetailsClicked_() {
     assert(!!this.guid);
     assert(!!this.apn);
-
     this.dispatchEvent(new CustomEvent('show-apn-detail-dialog', {
       composed: true,
       bubbles: true,
       detail: /** @type {!ApnEventData} */ ({
         apn: this.apn,
-        mode: ApnDetailDialogMode.VIEW,
+        // Only allow editing if the APN is a custom APN.
+        mode: this.isAutoDetected ? ApnDetailDialogMode.VIEW :
+                                    ApnDetailDialogMode.EDIT,
         guid: this.guid,
       }),
     }));
diff --git a/ash/webui/diagnostics_ui/backend/input/input_data_provider_keyboard.cc b/ash/webui/diagnostics_ui/backend/input/input_data_provider_keyboard.cc
index f82d46e..8340517 100644
--- a/ash/webui/diagnostics_ui/backend/input/input_data_provider_keyboard.cc
+++ b/ash/webui/diagnostics_ui/backend/input/input_data_provider_keyboard.cc
@@ -10,12 +10,16 @@
 
 #include "ash/constants/ash_switches.h"
 #include "ash/display/privacy_screen_controller.h"
+#include "ash/ime/ime_controller_impl.h"
 #include "ash/shell.h"
 #include "ash/webui/diagnostics_ui/backend/input/input_data_provider.h"
+#include "ash/webui/diagnostics_ui/mojom/input_data_provider.mojom-shared.h"
 #include "base/check_op.h"
 #include "base/command_line.h"
 #include "base/containers/fixed_flat_map.h"
 #include "base/logging.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_util.h"
 #include "chromeos/system/statistics_provider.h"
 #include "ui/events/devices/input_device.h"
 #include "ui/events/keycodes/keyboard_codes_posix.h"
@@ -204,6 +208,16 @@
     0xAE, 0xB0, 0x44, 0x57, 0xd7, 0x8B, 0xD3,
 };
 
+// Turkish F-Type xkb keyboard layout id which is used to differentiate between
+// a device from 'tr' region with Q-Type vs F-Type.
+constexpr base::StringPiece kTurkishFLayoutId = "xkb:tr:f:tur";
+
+// |kTurkeyRegionCode| is the real turkey region code.
+// |kTurkeyFLayoutRegionCode| is used purely in the diagnostics app to
+// accurately display F-Type keyboard layouts.
+constexpr base::StringPiece kTurkeyRegionCode = "tr";
+constexpr base::StringPiece kTurkeyFLayoutRegionCode = "tr.f";
+
 mojom::MechanicalLayout GetSystemMechanicalLayout() {
   chromeos::system::StatisticsProvider* stats_provider =
       chromeos::system::StatisticsProvider::GetInstance();
@@ -236,6 +250,18 @@
     return absl::nullopt;
   }
 
+  // In Turkey, two different layouts are shipped (Q-Type and F-Type) under the
+  // same region code |kTurkeyRegionCode|. To do a best effort differentiation
+  // between the two, query the current IME. If it is |kTurkishFLayoutId|,
+  // return our made up |kTurnishFLayoutRegionCode|.
+  if (layout_string.value() == kTurkeyRegionCode) {
+    ImeControllerImpl* controller = Shell::Get()->ime_controller();
+    DCHECK(controller);
+    if (base::EndsWith(controller->current_ime().id, kTurkishFLayoutId)) {
+      return std::string(kTurkeyFLayoutRegionCode);
+    }
+  }
+
   return std::string(layout_string.value());
 }
 
diff --git a/ash/webui/diagnostics_ui/backend/input/input_data_provider_keyboard_unittest.cc b/ash/webui/diagnostics_ui/backend/input/input_data_provider_keyboard_unittest.cc
index 04ac8a1d..4b4ab02 100644
--- a/ash/webui/diagnostics_ui/backend/input/input_data_provider_keyboard_unittest.cc
+++ b/ash/webui/diagnostics_ui/backend/input/input_data_provider_keyboard_unittest.cc
@@ -7,6 +7,8 @@
 #include <vector>
 
 #include "ash/constants/ash_switches.h"
+#include "ash/ime/ime_controller_impl.h"
+#include "ash/shell.h"
 #include "ash/test/ash_test_base.h"
 #include "ash/webui/diagnostics_ui/backend/input/input_data_provider.h"
 #include "ash/webui/diagnostics_ui/backend/input/input_data_provider_keyboard.h"
@@ -14,6 +16,7 @@
 #include "ash/webui/diagnostics_ui/mojom/input_data_provider.mojom-shared.h"
 #include "base/command_line.h"
 #include "base/files/file_path.h"
+#include "base/strings/string_piece_forward.h"
 #include "chromeos/ash/components/test/ash_test_suite.h"
 #include "chromeos/system/fake_statistics_provider.h"
 #include "content/public/test/browser_task_environment.h"
@@ -60,6 +63,12 @@
   kRefresh = 0xE7,
 };
 
+// xkb layout ids for both turkish Q-Type and F-Type layouts.
+constexpr base::StringPiece kTurkishKeyboardLayoutId = "xkb:tr::tur";
+constexpr base::StringPiece kTurkishKeyboardFLayoutId = "xkb:tr:f:tur";
+constexpr base::StringPiece kTurkeyRegionCode = "tr";
+constexpr base::StringPiece kTurkeyFLayoutRegionCode = "tr.f";
+
 ui::InputDevice InputDeviceFromCapabilities(
     int device_id,
     const ui::DeviceCapabilities& capabilities) {
@@ -88,6 +97,17 @@
       ui::DomKey(ui::DomKey::F1 + diff), key_code);
 }
 
+void SetCurrentImeId(const std::string& current_ime_id) {
+  // Only data relavent is the ime id.
+  ImeInfo ime_info;
+  ime_info.id = current_ime_id;
+  ime_info.name = u"English";
+  ime_info.short_name = u"US";
+  ime_info.third_party = false;
+
+  Shell::Get()->ime_controller()->RefreshIme(
+      current_ime_id, /*available_imes=*/{ime_info}, /*menu_items=*/{});
+}
 }  // namespace
 
 class InputDataProviderKeyboardTest : public ash::AshTestBase {
@@ -290,6 +310,28 @@
   EXPECT_EQ(top_row_keys_, keyboard_info_->top_row_keys);
 }
 
+TEST_F(VivaldiKeyboardTestBase, TurkishNoFLayout) {
+  SetCurrentImeId(std::string(kTurkishKeyboardLayoutId));
+  statistics_provider_.SetMachineStatistic(chromeos::system::kRegionKey,
+                                           std::string(kTurkeyRegionCode));
+
+  keyboard_info_ = input_data_provider_keyboard_->ConstructKeyboard(
+      &device_information, &aux_data_);
+
+  EXPECT_EQ(kTurkeyRegionCode, keyboard_info_->region_code);
+}
+
+TEST_F(VivaldiKeyboardTestBase, TurkishFLayout) {
+  SetCurrentImeId(std::string(kTurkishKeyboardFLayoutId));
+  statistics_provider_.SetMachineStatistic(chromeos::system::kRegionKey,
+                                           std::string(kTurkeyRegionCode));
+
+  keyboard_info_ = input_data_provider_keyboard_->ConstructKeyboard(
+      &device_information, &aux_data_);
+
+  EXPECT_EQ(kTurkeyFLayoutRegionCode, keyboard_info_->region_code);
+}
+
 class MechanicalLayoutTest
     : public VivaldiKeyboardTestBase,
       public testing::WithParamInterface<
diff --git a/ash/webui/eche_app_ui/eche_alert_generator_unittest.cc b/ash/webui/eche_app_ui/eche_alert_generator_unittest.cc
index 97dc353..3818b61 100644
--- a/ash/webui/eche_app_ui/eche_alert_generator_unittest.cc
+++ b/ash/webui/eche_app_ui/eche_alert_generator_unittest.cc
@@ -81,7 +81,8 @@
                                  const std::string& package_name,
                                  const std::u16string& visible_name,
                                  const absl::optional<int64_t>& user_id,
-                                 const gfx::Image& icon) {
+                                 const gfx::Image& icon,
+                                 const std::u16string& phone_name) {
     // Do nothing.
   }
 
diff --git a/ash/webui/eche_app_ui/eche_app_manager_unittest.cc b/ash/webui/eche_app_ui/eche_app_manager_unittest.cc
index 4146231..c570d7c 100644
--- a/ash/webui/eche_app_ui/eche_app_manager_unittest.cc
+++ b/ash/webui/eche_app_ui/eche_app_manager_unittest.cc
@@ -37,7 +37,8 @@
                            const std::string& package_name,
                            const std::u16string& visible_name,
                            const absl::optional<int64_t>& user_id,
-                           const gfx::Image& icon) {}
+                           const gfx::Image& icon,
+                           const std::u16string& phone_name) {}
 
 void LaunchNotificationFunction(
     const absl::optional<std::u16string>& title,
diff --git a/ash/webui/eche_app_ui/eche_notification_click_handler.cc b/ash/webui/eche_app_ui/eche_notification_click_handler.cc
index e01358b0..6987a623 100644
--- a/ash/webui/eche_app_ui/eche_notification_click_handler.cc
+++ b/ash/webui/eche_app_ui/eche_notification_click_handler.cc
@@ -21,6 +21,7 @@
     : feature_status_provider_(feature_status_provider),
       launch_app_helper_(launch_app_helper) {
   handler_ = phone_hub_manager->GetNotificationInteractionHandler();
+  phone_model_ = phone_hub_manager->GetPhoneModel();
   feature_status_provider_->AddObserver(this);
   if (handler_ && IsClickable(feature_status_provider_->GetStatus())) {
     handler_->AddNotificationClickHandler(this);
@@ -51,7 +52,8 @@
       launch_app_helper_->LaunchEcheApp(
           notification_id, app_metadata.package_name,
           app_metadata.visible_app_name, app_metadata.user_id,
-          app_metadata.icon);
+          app_metadata.icon,
+          phone_model_->phone_name().value_or(std::u16string()));
       break;
     case LaunchAppHelper::AppLaunchProhibitedReason::kDisabledByScreenLock:
       launch_app_helper_->ShowNotification(
diff --git a/ash/webui/eche_app_ui/eche_notification_click_handler.h b/ash/webui/eche_app_ui/eche_notification_click_handler.h
index 3ee90ada..53fe95e 100644
--- a/ash/webui/eche_app_ui/eche_notification_click_handler.h
+++ b/ash/webui/eche_app_ui/eche_notification_click_handler.h
@@ -11,6 +11,7 @@
 #include "chromeos/ash/components/phonehub/notification.h"
 #include "chromeos/ash/components/phonehub/notification_click_handler.h"
 #include "chromeos/ash/components/phonehub/notification_interaction_handler.h"
+#include "chromeos/ash/components/phonehub/phone_model.h"
 
 namespace ash {
 
@@ -47,6 +48,7 @@
   bool IsClickable(FeatureStatus status);
 
   phonehub::NotificationInteractionHandler* handler_;
+  phonehub::PhoneModel* phone_model_;
   FeatureStatusProvider* feature_status_provider_;
   LaunchAppHelper* launch_app_helper_;
   bool is_click_handler_set_ = false;
diff --git a/ash/webui/eche_app_ui/eche_notification_click_handler_unittest.cc b/ash/webui/eche_app_ui/eche_notification_click_handler_unittest.cc
index ecd4b67..292e1c4 100644
--- a/ash/webui/eche_app_ui/eche_notification_click_handler_unittest.cc
+++ b/ash/webui/eche_app_ui/eche_notification_click_handler_unittest.cc
@@ -64,7 +64,8 @@
                                  const std::string& package_name,
                                  const std::u16string& visible_name,
                                  const absl::optional<int64_t>& user_id,
-                                 const gfx::Image& icon) {
+                                 const gfx::Image& icon,
+                                 const std::u16string& phone_name) {
     num_app_launch_++;
   }
 
diff --git a/ash/webui/eche_app_ui/eche_recent_app_click_handler.cc b/ash/webui/eche_app_ui/eche_recent_app_click_handler.cc
index f67cab3c6..11bcd4a2 100644
--- a/ash/webui/eche_app_ui/eche_recent_app_click_handler.cc
+++ b/ash/webui/eche_app_ui/eche_recent_app_click_handler.cc
@@ -87,7 +87,9 @@
       launch_app_helper_->LaunchEcheApp(
           /*notification_id=*/absl::nullopt, app_metadata.package_name,
           app_metadata.visible_app_name, app_metadata.user_id,
-          app_metadata.icon);
+          app_metadata.icon,
+          phone_hub_manager_->GetPhoneModel()->phone_name().value_or(
+              std::u16string()));
       break;
     case LaunchAppHelper::AppLaunchProhibitedReason::kDisabledByScreenLock:
       launch_app_helper_->ShowNotification(
diff --git a/ash/webui/eche_app_ui/eche_recent_app_click_handler_unittest.cc b/ash/webui/eche_app_ui/eche_recent_app_click_handler_unittest.cc
index 1d4f9962..2acf931 100644
--- a/ash/webui/eche_app_ui/eche_recent_app_click_handler_unittest.cc
+++ b/ash/webui/eche_app_ui/eche_recent_app_click_handler_unittest.cc
@@ -67,7 +67,8 @@
                                  const std::string& package_name,
                                  const std::u16string& visible_name,
                                  const absl::optional<int64_t>& user_id,
-                                 const gfx::Image& icon) {
+                                 const gfx::Image& icon,
+                                 const std::u16string& phone_name) {
     package_name_ = package_name;
     visible_name_ = visible_name;
     user_id_ = user_id.value();
diff --git a/ash/webui/eche_app_ui/eche_tray_stream_status_observer.cc b/ash/webui/eche_app_ui/eche_tray_stream_status_observer.cc
index 559b783..fcf699b 100644
--- a/ash/webui/eche_app_ui/eche_tray_stream_status_observer.cc
+++ b/ash/webui/eche_app_ui/eche_tray_stream_status_observer.cc
@@ -39,11 +39,12 @@
 void LaunchBubble(const GURL& url,
                   const gfx::Image& icon,
                   const std::u16string& visible_name,
+                  const std::u16string& phone_name,
                   EcheTray::GracefulCloseCallback graceful_close_callback,
                   EcheTray::GracefulGoBackCallback graceful_go_back_callback) {
   auto* eche_tray = ash::GetEcheTray();
   DCHECK(eche_tray);
-  eche_tray->LoadBubble(url, icon, visible_name);
+  eche_tray->LoadBubble(url, icon, visible_name, phone_name);
   eche_tray->SetGracefulCloseCallback(std::move(graceful_close_callback));
   eche_tray->SetGracefulGoBackCallback(std::move(graceful_go_back_callback));
 }
diff --git a/ash/webui/eche_app_ui/eche_tray_stream_status_observer.h b/ash/webui/eche_app_ui/eche_tray_stream_status_observer.h
index e1c18d2..e0321d8 100644
--- a/ash/webui/eche_app_ui/eche_tray_stream_status_observer.h
+++ b/ash/webui/eche_app_ui/eche_tray_stream_status_observer.h
@@ -25,6 +25,7 @@
 void LaunchBubble(const GURL& url,
                   const gfx::Image& icon,
                   const std::u16string& visible_name,
+                  const std::u16string& phone_name,
                   EcheTray::GracefulCloseCallback graceful_close_callback,
                   EcheTray::GracefulGoBackCallback graceful_go_back_callback);
 
diff --git a/ash/webui/eche_app_ui/eche_tray_stream_status_observer_unittest.cc b/ash/webui/eche_app_ui/eche_tray_stream_status_observer_unittest.cc
index 2bc5e03..89c767e7 100644
--- a/ash/webui/eche_app_ui/eche_tray_stream_status_observer_unittest.cc
+++ b/ash/webui/eche_app_ui/eche_tray_stream_status_observer_unittest.cc
@@ -96,7 +96,7 @@
 };
 
 TEST_F(EcheTrayStreamStatusObserverTest, LaunchBubble) {
-  LaunchBubble(GURL("http://google.com"), gfx::Image(), u"app 1",
+  LaunchBubble(GURL("http://google.com"), gfx::Image(), u"app 1", u"your phone",
                base::BindOnce(&GracefulCloseFunction),
                base::BindRepeating(&GracefulGoBackFunction));
 
@@ -114,7 +114,7 @@
   // The bubble should not be created if LaunchBubble be called before.
   EXPECT_FALSE(eche_tray()->get_bubble_wrapper_for_test());
 
-  LaunchBubble(GURL("http://google.com"), gfx::Image(), u"app 1",
+  LaunchBubble(GURL("http://google.com"), gfx::Image(), u"app 1", u"your phone",
                base::BindOnce(&GracefulCloseFunction),
                base::BindRepeating(&GracefulGoBackFunction));
 
@@ -134,7 +134,7 @@
 }
 
 TEST_F(EcheTrayStreamStatusObserverTest, OnStreamStatusChanged) {
-  LaunchBubble(GURL("http://google.com"), gfx::Image(), u"app 1",
+  LaunchBubble(GURL("http://google.com"), gfx::Image(), u"app 1", u"your phone",
                base::BindOnce(&GracefulCloseFunction),
                base::BindRepeating(&GracefulGoBackFunction));
   OnStreamStatusChanged(mojom::StreamStatus::kStreamStatusStarted);
@@ -156,7 +156,7 @@
        StartGracefulCloseWhenFeatureStatusToIneligible) {
   ResetUnloadWebContent();
   SetStatus(FeatureStatus::kConnecting);
-  LaunchBubble(GURL("http://google.com"), gfx::Image(), u"app 1",
+  LaunchBubble(GURL("http://google.com"), gfx::Image(), u"app 1", u"your phone",
                base::BindOnce(&GracefulCloseFunction),
                base::BindRepeating(&GracefulGoBackFunction));
   OnStreamStatusChanged(mojom::StreamStatus::kStreamStatusStarted);
@@ -179,7 +179,7 @@
        StartGracefulCloseWhenFeatureDependent) {
   ResetUnloadWebContent();
   SetStatus(FeatureStatus::kConnecting);
-  LaunchBubble(GURL("http://google.com"), gfx::Image(), u"app 1",
+  LaunchBubble(GURL("http://google.com"), gfx::Image(), u"app 1", u"your phone",
                base::BindOnce(&GracefulCloseFunction),
                base::BindRepeating(&GracefulGoBackFunction));
   OnStreamStatusChanged(mojom::StreamStatus::kStreamStatusStarted);
@@ -202,7 +202,7 @@
        StartGracefulCloseWhenFeatureDisabled) {
   ResetUnloadWebContent();
   SetStatus(FeatureStatus::kConnecting);
-  LaunchBubble(GURL("http://google.com"), gfx::Image(), u"app 1",
+  LaunchBubble(GURL("http://google.com"), gfx::Image(), u"app 1", u"your phone",
                base::BindOnce(&GracefulCloseFunction),
                base::BindRepeating(&GracefulGoBackFunction));
   OnStreamStatusChanged(mojom::StreamStatus::kStreamStatusStarted);
diff --git a/ash/webui/eche_app_ui/launch_app_helper.cc b/ash/webui/eche_app_ui/launch_app_helper.cc
index abded60..0733ae0 100644
--- a/ash/webui/eche_app_ui/launch_app_helper.cc
+++ b/ash/webui/eche_app_ui/launch_app_helper.cc
@@ -87,9 +87,10 @@
                                     const std::string& package_name,
                                     const std::u16string& visible_name,
                                     const absl::optional<int64_t>& user_id,
-                                    const gfx::Image& icon) const {
+                                    const gfx::Image& icon,
+                                    const std::u16string& phone_name) const {
   launch_eche_app_function_.Run(notification_id, package_name, visible_name,
-                                user_id, icon);
+                                user_id, icon, phone_name);
 }
 
 }  // namespace eche_app
diff --git a/ash/webui/eche_app_ui/launch_app_helper.h b/ash/webui/eche_app_ui/launch_app_helper.h
index c0e0268f..8e1ee9d 100644
--- a/ash/webui/eche_app_ui/launch_app_helper.h
+++ b/ash/webui/eche_app_ui/launch_app_helper.h
@@ -68,7 +68,8 @@
       const std::string& package_name,
       const std::u16string& visible_name,
       const absl::optional<int64_t>& user_id,
-      const gfx::Image& icon)>;
+      const gfx::Image& icon,
+      const std::u16string& phone_name)>;
 
   // Enum representing potential reasons why an app is forbidden to launch.
   enum class AppLaunchProhibitedReason {
@@ -112,7 +113,8 @@
                      const std::string& package_name,
                      const std::u16string& visible_name,
                      const absl::optional<int64_t>& user_id,
-                     const gfx::Image& icon) const;
+                     const gfx::Image& icon,
+                     const std::u16string& phone_name) const;
 
  private:
   bool IsScreenLockRequired() const;
diff --git a/ash/webui/eche_app_ui/launch_app_helper_unittest.cc b/ash/webui/eche_app_ui/launch_app_helper_unittest.cc
index 1cd0d76..934e134 100644
--- a/ash/webui/eche_app_ui/launch_app_helper_unittest.cc
+++ b/ash/webui/eche_app_ui/launch_app_helper_unittest.cc
@@ -31,7 +31,8 @@
       const std::string& package_name,
       const std::u16string& visible_name,
       const absl::optional<int64_t>& user_id,
-      const gfx::Image& icon) {
+      const gfx::Image& icon,
+      const std::u16string& phone_name) {
     launchEcheApp_ = true;
   }
 
@@ -111,9 +112,10 @@
                      const std::string& package_name,
                      const std::u16string& visible_name,
                      const absl::optional<int64_t>& user_id,
-                     const gfx::Image& icon) {
+                     const gfx::Image& icon,
+                     const std::u16string& phone_name) {
     launch_app_helper_->LaunchEcheApp(notification_id, package_name,
-                                      visible_name, user_id, icon);
+                                      visible_name, user_id, icon, phone_name);
   }
 
   void ShowNotification(
@@ -173,9 +175,10 @@
   const std::string package_name = "package_name";
   const std::u16string visible_name = u"visible_name";
   const absl::optional<int64_t> user_id = 0;
+  const std::u16string phone_name = u"your phone";
 
   LaunchEcheApp(notification_id, package_name, visible_name, user_id,
-                gfx::Image());
+                gfx::Image(), phone_name);
 
   EXPECT_TRUE(Callback::getLaunchEcheApp());
 }
diff --git a/ash/webui/eche_app_ui/mojom/eche_app.mojom b/ash/webui/eche_app_ui/mojom/eche_app.mojom
index cdbe2528..c174272e 100644
--- a/ash/webui/eche_app_ui/mojom/eche_app.mojom
+++ b/ash/webui/eche_app_ui/mojom/eche_app.mojom
@@ -59,6 +59,12 @@
   // process. TODO(b/184119538): Need to handle the resize issue and virtual
   // keyboard in table mode.
   OnReceivedTabletModeChanged(bool is_tablet_mode);
+
+  // Interface for monitoring the network state of the Android device.
+  // The state is sent by the remote device during the signaling process
+  // and processed by CrOS.
+  OnAndroidDeviceNetworkInfoChanged(bool is_different_network,
+          bool android_device_on_cellular);
 };
 
 // Interface for generating uid. The uid is unique and persistent.
@@ -81,6 +87,8 @@
     LAUNCH_NOTIFICATION_FAILED,
     TABLET_MODE,
     WIFI_NOT_READY,
+    DIFFERENT_WIFI_NETWORKS,
+    REMOTE_DEVICE_ON_CELLULAR,
 };
 
 // Interface for showing a native notification which was generated from WebUI.
diff --git a/ash/webui/eche_app_ui/resources/browser_proxy.js b/ash/webui/eche_app_ui/resources/browser_proxy.js
index 503c2b3..3dc2cbb 100644
--- a/ash/webui/eche_app_ui/resources/browser_proxy.js
+++ b/ash/webui/eche_app_ui/resources/browser_proxy.js
@@ -121,6 +121,17 @@
            Message.TABLET_MODE, {/** @type {boolean} */ isTabletMode});
      });
 
+ // Add Android network info listener and send result via pipes.
+ systemInfoObserverRouter.onAndroidDeviceNetworkInfoChanged.addListener(
+     (isDifferentNetwork, androidDeviceOnCellular) => {
+       console.log(
+           'echeapi browser_proxy.js onAndroidDeviceNetworkInfoChanged');
+       guestMessagePipe.sendMessage(Message.TABLET_MODE, {
+         /** @type {boolean} */ isDifferentNetwork,
+         /** @type {boolean} */ androidDeviceOnCellular,
+       });
+     });
+
  // Add stream action listener and send result via pipes.
  streamActionObserverRouter.onStreamAction.addListener((action) => {
    console.log(`echeapi browser_proxy.js OnStreamAction ${action}`);
diff --git a/ash/webui/eche_app_ui/resources/message_types.js b/ash/webui/eche_app_ui/resources/message_types.js
index f03b266..c32101c6 100644
--- a/ash/webui/eche_app_ui/resources/message_types.js
+++ b/ash/webui/eche_app_ui/resources/message_types.js
@@ -107,4 +107,6 @@
   STREAM_ACTION: 'stream_action',
   // Message for virtual keyboard state
   IS_VIRTUAL_KEYBOARD_ENABLED: 'is_virtual_keyboard_enabled',
+  // Message for Android network info
+  ANDROID_NETWORK_INFO: 'android-network-info',
 };
diff --git a/ash/webui/eche_app_ui/resources/receiver.js b/ash/webui/eche_app_ui/resources/receiver.js
index 5ec83638..ab8482b 100644
--- a/ash/webui/eche_app_ui/resources/receiver.js
+++ b/ash/webui/eche_app_ui/resources/receiver.js
@@ -57,6 +57,18 @@
           /** @type {boolean} */ (message.isVirtualKeyboardEnabled));
     });
 
+let androidNetworkInfoCallback = null;
+parentMessagePipe.registerHandler(
+    Message.ANDROID_NETWORK_INFO, async (message) => {
+      if (!androidNetworkInfoCallback) {
+        return;
+      }
+
+      androidNetworkInfoCallback(
+          /** @type {boolean} */ (message.isDifferentNetwork),
+          /** @type {boolean} */ (message.androidDeviceOnCellular));
+    });
+
 // The implementation of echeapi.d.ts
 const EcheApiBindingImpl = new (class {
   closeWindow() {
@@ -144,6 +156,10 @@
     virtualKeyboardCallback = callback;
   }
 
+  onAndroidDeviceNetworkInfoChanged(callback) {
+    console.log('echeapi receiver.js onAndroidDeviceNetworkInfoChanged');
+    androidNetworkInfoCallback = callback;
+  }
 })();
 
 // Declare module echeapi and bind the implementation to echeapi.d.ts
@@ -184,5 +200,8 @@
 echeapi.system.registerVirtualKeyboardChangedReceiver =
     EcheApiBindingImpl.onReceivedVirtualKeyboardChanged.bind(
         EcheApiBindingImpl);
+echeapi.system.registerAndroidNetworkInfoChangedReceiver =
+    EcheApiBindingImpl.onAndroidDeviceNetworkInfoChanged.bind(
+        EcheApiBindingImpl);
 window['echeapi'] = echeapi;
 console.log('echeapi receiver.js finish bind the implementation of echeapi');
diff --git a/ash/webui/eche_app_ui/system_info_provider.cc b/ash/webui/eche_app_ui/system_info_provider.cc
index a0120b6..2af1dab 100644
--- a/ash/webui/eche_app_ui/system_info_provider.cc
+++ b/ash/webui/eche_app_ui/system_info_provider.cc
@@ -139,6 +139,24 @@
   observer_remote_->OnReceivedTabletModeChanged(enabled);
 }
 
+void SystemInfoProvider::SetAndroidDeviceNetworkInfoChanged(
+    bool is_different_network,
+    bool android_device_on_cellular) {
+  PA_LOG(INFO) << "echeapi SystemInfoProvider "
+                  "SetAndroidDeviceNetworkInfoChanged is_different_network:"
+               << is_different_network;
+  PA_LOG(INFO)
+      << "echeapi SystemInfoProvider "
+         "SetAndroidDeviceNetworkInfoChanged android_device_on_cellular:"
+      << android_device_on_cellular;
+  if (!observer_remote_.is_bound())
+    return;
+
+  PA_LOG(VERBOSE) << "OnAndroidDeviceNetworkInfoChanged";
+  observer_remote_->OnAndroidDeviceNetworkInfoChanged(
+      is_different_network, android_device_on_cellular);
+}
+
 // TabletModeObserver implementation:
 void SystemInfoProvider::OnTabletModeStarted() {
   SetTabletModeChanged(true);
diff --git a/ash/webui/eche_app_ui/system_info_provider.h b/ash/webui/eche_app_ui/system_info_provider.h
index f26aad9..46e7c5d 100644
--- a/ash/webui/eche_app_ui/system_info_provider.h
+++ b/ash/webui/eche_app_ui/system_info_provider.h
@@ -65,6 +65,8 @@
   void OnTabletModeEnded() override;
 
   void SetTabletModeChanged(bool enabled);
+  void SetAndroidDeviceNetworkInfoChanged(bool is_different_network,
+                                          bool android_device_on_cellular);
 
   // network_config::CrosNetworkConfigObserver overrides:
   void OnNetworkStateChanged(
diff --git a/ash/webui/eche_app_ui/system_info_provider_unittest.cc b/ash/webui/eche_app_ui/system_info_provider_unittest.cc
index dc6bdd99..1874a26 100644
--- a/ash/webui/eche_app_ui/system_info_provider_unittest.cc
+++ b/ash/webui/eche_app_ui/system_info_provider_unittest.cc
@@ -155,6 +155,7 @@
     return num_backlight_state_calls_;
   }
   size_t num_tablet_state_calls() const { return num_tablet_state_calls_; }
+  size_t num_android_state_calls() const { return num_android_state_calls_; }
 
   // mojom::SystemInfoObserver:
   void OnScreenBacklightStateChanged(
@@ -173,6 +174,15 @@
     }
   }
 
+  void OnAndroidDeviceNetworkInfoChanged(
+      bool is_different_network,
+      bool android_device_on_cellular) override {
+    ++num_android_state_calls_;
+    if (task_runner_) {
+      task_runner_->Finish();
+    }
+  }
+
   static void setTaskRunner(TaskRunner* task_runner) {
     task_runner_ = task_runner;
   }
@@ -182,6 +192,7 @@
  private:
   size_t num_backlight_state_calls_ = 0;
   size_t num_tablet_state_calls_ = 0;
+  size_t num_android_state_calls_ = 0;
   static TaskRunner* task_runner_;
 };
 
@@ -249,6 +260,10 @@
         ash::ScreenBacklightState::OFF);
   }
 
+  void SetAndroidDeviceNetworkInfoChanged() {
+    system_info_provider_->SetAndroidDeviceNetworkInfoChanged(false, false);
+  }
+
   void OnTabletModeStarted() { system_info_provider_->OnTabletModeStarted(); }
 
   void OnTabletModeEnded() { system_info_provider_->OnTabletModeEnded(); }
@@ -270,6 +285,9 @@
   size_t GetNumBacklightStateObserverCalls() const {
     return fake_observer_->num_backlight_state_calls();
   }
+  size_t GetNumAndroidStateObserverCalls() const {
+    return fake_observer_->num_android_state_calls();
+  }
   TaskRunner task_runner_;
 
  private:
@@ -336,5 +354,14 @@
   EXPECT_EQ(1u, GetNumTabletStateObserverCalls());
 }
 
+TEST_F(SystemInfoProviderTest,
+       ObserverCalledWhenAndroidDeviceNetworkStateChanged) {
+  FakeObserver::setTaskRunner(&task_runner_);
+  SetAndroidDeviceNetworkInfoChanged();
+  task_runner_.WaitForResult();
+
+  EXPECT_EQ(1u, GetNumAndroidStateObserverCalls());
+}
+
 }  // namespace eche_app
 }  // namespace ash
diff --git a/ash/webui/file_manager/resources/labs/labs.html b/ash/webui/file_manager/resources/labs/labs.html
index 2eabaa9..e5171c7a 100644
--- a/ash/webui/file_manager/resources/labs/labs.html
+++ b/ash/webui/file_manager/resources/labs/labs.html
@@ -1,5 +1,6 @@
 <!DOCTYPE html>
 <html>
+
 <head>
   <meta charset='utf-8'>
   <title>Files app Labs</title>
@@ -14,38 +15,59 @@
       background-color: var(--cros-bg-color);
       color: var(--cros-text-color-primary);
     }
+
     xf-tree {
       width: 300px;
     }
+
     .trailing-icon {
       box-sizing: border-box;
       padding: 5px 0 0 5px;
     }
+
     .container {
       display: flex;
       justify-content: space-around;
     }
+
     xf-tree:not(:defined) {
       display: none;
     }
+
     xf-tree:defined {
       display: block;
     }
+
+    .icons {
+      display: flex;
+      flex-wrap: wrap;
+      width: 900px;
+    }
+
+    .icon-wrapper {
+      display: flex;
+      justify-content: space-between;
+      margin-inline-end: 20px;
+      width: 200px;
+    }
+
   </style>
 
   <script type="module" src="./main.js"></script>
 </head>
+
 <body>
   <h1>Files app Labs</h1>
-  <button> Ok </button>
-  <xf-button data-category="dismiss"></xf-button>
+  <h2>Breadcrumb</h2>
 
   <xf-breadcrumb path="My Files/Downloads"></xf-breadcrumb>
   <xf-breadcrumb path="My Files/Downloads/AAA/BBB/CCC"></xf-breadcrumb>
 
+  <h2>Tree</h2>
+
   <div class="container">
     <div theme="legacy">
-      <h2>Legacy</h2>
+      <h3>Legacy</h3>
       <xf-tree>
         <xf-tree-item label="Level 1 (2 children)">
           <xf-tree-item label="Level 1.1 (2 children)">
@@ -62,7 +84,7 @@
     </div>
 
     <div theme="refresh23">
-      <h2>Refresh23</h2>
+      <h3>Refresh23</h3>
       <xf-tree>
         <xf-tree-item label="Level 1 (2 children)">
           <xf-tree-item label="Level 1.1 (2 children)">
@@ -78,5 +100,221 @@
       </xf-tree>
     </div>
   </div>
+  <h2>Icons</h2>
+  <div class="icons">
+    <div class="icon-wrapper">
+      <span>type="android_files"</span>
+      <xf-icon type="android_files"></xf-icon>
+    </div>
+    <div class="icon-wrapper">
+      <span>type="archive"</span>
+      <xf-icon type="archive"></xf-icon>
+    </div>
+    <div class="icon-wrapper">
+      <span>type="audio"</span>
+      <xf-icon type="audio"></xf-icon>
+    </div>
+    <div class="icon-wrapper">
+      <span>type="bruschetta"</span>
+      <xf-icon type="bruschetta"></xf-icon>
+    </div>
+    <div class="icon-wrapper">
+      <span>type="camera-folder"</span>
+      <xf-icon type="camera-folder"></xf-icon>
+    </div>
+    <div class="icon-wrapper">
+      <span>type="computer"</span>
+      <xf-icon type="computer"></xf-icon>
+    </div>
+    <div class="icon-wrapper">
+      <span>type="computers_grand_root"</span>
+      <xf-icon type="computers_grand_root"></xf-icon>
+    </div>
+    <div class="icon-wrapper">
+      <span>type="crostini"</span>
+      <xf-icon type="crostini"></xf-icon>
+    </div>
+    <div class="icon-wrapper">
+      <span>type="downloads"</span>
+      <xf-icon type="downloads"></xf-icon>
+    </div>
+    <div class="icon-wrapper">
+      <span>type="drive_offline"</span>
+      <xf-icon type="drive_offline"></xf-icon>
+    </div>
+    <div class="icon-wrapper">
+      <span>type="drive_recent"</span>
+      <xf-icon type="drive_recent"></xf-icon>
+    </div>
+    <div class="icon-wrapper">
+      <span>type="drive_shared_with_me"</span>
+      <xf-icon type="drive_shared_with_me"></xf-icon>
+    </div>
+    <div class="icon-wrapper">
+      <span>type="drive"</span>
+      <xf-icon type="drive"></xf-icon>
+    </div>
+    <div class="icon-wrapper">
+      <span>type="excel"</span>
+      <xf-icon type="excel"></xf-icon>
+    </div>
+    <div class="icon-wrapper">
+      <span>type="external_media"</span>
+      <xf-icon type="external_media"></xf-icon>
+    </div>
+    <div class="icon-wrapper">
+      <span>type="folder"</span>
+      <xf-icon type="folder"></xf-icon>
+    </div>
+    <div class="icon-wrapper">
+      <span>type="generic"</span>
+      <xf-icon type="generic"></xf-icon>
+    </div>
+    <div class="icon-wrapper">
+      <span>type="gdoc"</span>
+      <xf-icon type="gdoc"></xf-icon>
+    </div>
+    <div class="icon-wrapper">
+      <span>type="gdraw"</span>
+      <xf-icon type="gdraw"></xf-icon>
+    </div>
+    <div class="icon-wrapper">
+      <span>type="gform"</span>
+      <xf-icon type="gform"></xf-icon>
+    </div>
+    <div class="icon-wrapper">
+      <span>type="glink"</span>
+      <xf-icon type="glink"></xf-icon>
+    </div>
+    <div class="icon-wrapper">
+      <span>type="gmap"</span>
+      <xf-icon type="gmap"></xf-icon>
+    </div>
+    <div class="icon-wrapper">
+      <span>type="gsheet"</span>
+      <xf-icon type="gsheet"></xf-icon>
+    </div>
+    <div class="icon-wrapper">
+      <span>type="gsite"</span>
+      <xf-icon type="gsite"></xf-icon>
+    </div>
+    <div class="icon-wrapper">
+      <span>type="gslides"</span>
+      <xf-icon type="gslides"></xf-icon>
+    </div>
+    <div class="icon-wrapper">
+      <span>type="gtable"</span>
+      <xf-icon type="gtable"></xf-icon>
+    </div>
+    <div class="icon-wrapper">
+      <span>type="image"</span>
+      <xf-icon type="image"></xf-icon>
+    </div>
+    <div class="icon-wrapper">
+      <span>type="mtp"</span>
+      <xf-icon type="mtp"></xf-icon>
+    </div>
+    <div class="icon-wrapper">
+      <span>type="my_files"</span>
+      <xf-icon type="my_files"></xf-icon>
+    </div>
+    <div class="icon-wrapper">
+      <span>type="optical"</span>
+      <xf-icon type="optical"></xf-icon>
+    </div>
+    <div class="icon-wrapper">
+      <span>type="pdf"</span>
+      <xf-icon type="pdf"></xf-icon>
+    </div>
+    <div class="icon-wrapper">
+      <span>type="plugin_vm"</span>
+      <xf-icon type="plugin_vm"></xf-icon>
+    </div>
+    <div class="icon-wrapper">
+      <span>type="ppt"</span>
+      <xf-icon type="ppt"></xf-icon>
+    </div>
+    <div class="icon-wrapper">
+      <span>type="raw"</span>
+      <xf-icon type="raw"></xf-icon>
+    </div>
+    <div class="icon-wrapper">
+      <span>type="recent"</span>
+      <xf-icon type="recent"></xf-icon>
+    </div>
+    <div class="icon-wrapper">
+      <span>type="removable"</span>
+      <xf-icon type="removable"></xf-icon>
+    </div>
+    <div class="icon-wrapper">
+      <span>type="script"</span>
+      <xf-icon type="script"></xf-icon>
+    </div>
+    <div class="icon-wrapper">
+      <span>type="sd"</span>
+      <xf-icon type="sd"></xf-icon>
+    </div>
+    <div class="icon-wrapper">
+      <span>type="service_drive"</span>
+      <xf-icon type="service_drive"></xf-icon>
+    </div>
+    <div class="icon-wrapper">
+      <span>type="shared_drive"</span>
+      <xf-icon type="shared_drive"></xf-icon>
+    </div>
+    <div class="icon-wrapper">
+      <span>type="shared_drives_grand_root"</span>
+      <xf-icon type="shared_drives_grand_root"></xf-icon>
+    </div>
+    <div class="icon-wrapper">
+      <span>type="shared_folder"</span>
+      <xf-icon type="shared_folder"></xf-icon>
+    </div>
+    <div class="icon-wrapper">
+      <span>type="shortcut"</span>
+      <xf-icon type="shortcut"></xf-icon>
+    </div>
+    <div class="icon-wrapper">
+      <span>type="sites"</span>
+      <xf-icon type="sites"></xf-icon>
+    </div>
+    <div class="icon-wrapper">
+      <span>type="smb"</span>
+      <xf-icon type="smb"></xf-icon>
+    </div>
+    <div class="icon-wrapper">
+      <span>type="team_drive"</span>
+      <xf-icon type="team_drive"></xf-icon>
+    </div>
+    <div class="icon-wrapper">
+      <span>type="thumbnail_generic"</span>
+      <xf-icon type="thumbnail_generic"></xf-icon>
+    </div>
+    <div class="icon-wrapper">
+      <span>type="tini"</span>
+      <xf-icon type="tini"></xf-icon>
+    </div>
+    <div class="icon-wrapper">
+      <span>type="trash"</span>
+      <xf-icon type="trash"></xf-icon>
+    </div>
+    <div class="icon-wrapper">
+      <span>type="unknown_removable"</span>
+      <xf-icon type="unknown_removable"></xf-icon>
+    </div>
+    <div class="icon-wrapper">
+      <span>type="usb"</span>
+      <xf-icon type="usb"></xf-icon>
+    </div>
+    <div class="icon-wrapper">
+      <span>type="video"</span>
+      <xf-icon type="video"></xf-icon>
+    </div>
+    <div class="icon-wrapper">
+      <span>type="word"</span>
+      <xf-icon type="word"></xf-icon>
+    </div>
+  </div>
 </body>
+
 </html>
diff --git a/ash/webui/file_manager/resources/labs/main.ts b/ash/webui/file_manager/resources/labs/main.ts
index 56ced29..f673f6f 100644
--- a/ash/webui/file_manager/resources/labs/main.ts
+++ b/ash/webui/file_manager/resources/labs/main.ts
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'chrome://file-manager/foreground/elements/xf_button.js';
 import 'chrome://file-manager/widgets/xf_breadcrumb.js';
+import 'chrome://file-manager/widgets/xf_icon.js';
 import 'chrome://file-manager/widgets/xf_tree.js';
 import 'chrome://file-manager/widgets/xf_tree_item.js';
 
diff --git a/ash/webui/os_feedback_ui/resources/confirmation_page.html b/ash/webui/os_feedback_ui/resources/confirmation_page.html
index bdf25b0..994234d0 100644
--- a/ash/webui/os_feedback_ui/resources/confirmation_page.html
+++ b/ash/webui/os_feedback_ui/resources/confirmation_page.html
@@ -104,6 +104,7 @@
     </div>
     <div id="shadowShield"></div>
   </div>
+  <div id="separator"></div>
   <div id="navButtons">
     <cr-button id="buttonNewReport" class="cancel-button"
         on-click="handleBackButtonClicked_">
diff --git a/ash/webui/os_feedback_ui/resources/feedback_utils.js b/ash/webui/os_feedback_ui/resources/feedback_utils.js
index 563cc8d..8c2777b3 100644
--- a/ash/webui/os_feedback_ui/resources/feedback_utils.js
+++ b/ash/webui/os_feedback_ui/resources/feedback_utils.js
@@ -12,6 +12,7 @@
   const content = page.shadowRoot.querySelector('#content');
   const navButtons = page.shadowRoot.querySelector('#navButtons');
   const shadowElevation = page.shadowRoot.querySelector('#shadowElevation');
+  const separator = page.shadowRoot.querySelector('#separator');
 
   shadowElevation.classList.toggle(
       'elevation-shadow-scrolling', content.scrollTop > 0);
@@ -19,8 +20,8 @@
   shadowShield.classList.toggle(
       'scrolling-shield',
       content.scrollTop + content.clientHeight < content.scrollHeight);
-  content.classList.toggle(
-      'content-scrolling-end',
+  separator.classList.toggle(
+      'separator-scrolling-end',
       content.scrollTop + content.clientHeight == content.scrollHeight &&
           content.scrollTop > 0);
 }
diff --git a/ash/webui/os_feedback_ui/resources/os_feedback_shared_css.html b/ash/webui/os_feedback_ui/resources/os_feedback_shared_css.html
index 6aafc03..c65ac6e 100644
--- a/ash/webui/os_feedback_ui/resources/os_feedback_shared_css.html
+++ b/ash/webui/os_feedback_ui/resources/os_feedback_shared_css.html
@@ -61,8 +61,12 @@
       padding-inline-start: var(--feedback-padding-inline-start);
     }
 
-    .content-scrolling-end {
-      border-bottom: 1px solid var(--cros-separator-color);
+    #separator {
+      height: 1px;
+    }
+
+    .separator-scrolling-end {
+      background-color: var(--cros-separator-color);
     }
 
     .page-title {
@@ -244,13 +248,14 @@
       bottom: 92px;
       height: 48px;
       left: 0;
+      pointer-events: none;
       position: absolute;
       right: 0;
     }
 
     .scrolling-shield {
       background: linear-gradient(to bottom,
-          rgba(var(--cros-bg-color-rgb),0), rgba(var(--cros-bg-color-rgb),1))
+          rgba(var(--cros-bg-color-rgb),0), rgba(var(--cros-bg-color-rgb),1));
     }
   </style>
 </template>
diff --git a/ash/webui/os_feedback_ui/resources/search_page.html b/ash/webui/os_feedback_ui/resources/search_page.html
index 429a9a6e7..dbe1cf05 100644
--- a/ash/webui/os_feedback_ui/resources/search_page.html
+++ b/ash/webui/os_feedback_ui/resources/search_page.html
@@ -119,6 +119,7 @@
     </div>
     <div id="shadowShield"></div>
   </div>
+  <div id="separator"></div>
   <div id="navButtons">
     <cr-button id="buttonContinue" class="action-button"
         on-click="handleContinueButtonClicked_">
diff --git a/ash/webui/os_feedback_ui/resources/search_page.js b/ash/webui/os_feedback_ui/resources/search_page.js
index 3eb6760..cc7dc501 100644
--- a/ash/webui/os_feedback_ui/resources/search_page.js
+++ b/ash/webui/os_feedback_ui/resources/search_page.js
@@ -285,7 +285,7 @@
       isPopularContent = true;
     } else {
       response = await this.helpContentProvider_.getHelpContents(request);
-      isPopularContent = (response.response.totalResults === 0);
+      isPopularContent = (response.response.results.length === 0);
       this.helpContentSearchResultCount = response.response.results.length;
     }
 
diff --git a/ash/webui/os_feedback_ui/resources/share_data_page.html b/ash/webui/os_feedback_ui/resources/share_data_page.html
index 34285daba..e211cb8c 100644
--- a/ash/webui/os_feedback_ui/resources/share_data_page.html
+++ b/ash/webui/os_feedback_ui/resources/share_data_page.html
@@ -296,6 +296,7 @@
     <div id="privacyNote" inner-h-t-m-l="[[privacyNote_]]"></div>
     <div id="shadowShield"></div>
   </div>
+  <div id="separator"></div>
   <div id="navButtons">
     <cr-button id="buttonBack" class="cancel-button"
         on-click="handleBackButtonClicked_">
diff --git a/ash/webui/personalization_app/mojom/personalization_app.mojom b/ash/webui/personalization_app/mojom/personalization_app.mojom
index 9ad230e..b508bb0 100644
--- a/ash/webui/personalization_app/mojom/personalization_app.mojom
+++ b/ash/webui/personalization_app/mojom/personalization_app.mojom
@@ -341,6 +341,9 @@
 
   // Notifies the JS side the current state of color mode auto scheduler.
   OnColorModeAutoScheduleChanged(bool enabled);
+
+  // Notifies the JS side about the current state of the static color.
+  OnStaticColorChanged(skia.mojom.SkColor? color);
 };
 
 // Provides APIs to expose theme settings such dark/light color mode.
@@ -362,6 +365,9 @@
   // @see //ash/style/color_palette_controller.h
   SetStaticColor(skia.mojom.SkColor static_color);
 
+  // Retrieves the static seed color for dynamic color.
+  GetStaticColor() => (skia.mojom.SkColor? static_color);
+
   // Returns whether color mode auto scheduler is enabled.
   IsColorModeAutoScheduleEnabled() => (bool enabled);
 
diff --git a/ash/webui/personalization_app/resources/js/keyboard_backlight/keyboard_backlight_element.ts b/ash/webui/personalization_app/resources/js/keyboard_backlight/keyboard_backlight_element.ts
index 47c7818..dbda3e2 100644
--- a/ash/webui/personalization_app/resources/js/keyboard_backlight/keyboard_backlight_element.ts
+++ b/ash/webui/personalization_app/resources/js/keyboard_backlight/keyboard_backlight_element.ts
@@ -15,7 +15,7 @@
 
 import {BacklightColor, BLUE_COLOR, GREEN_COLOR, INDIGO_COLOR, PURPLE_COLOR, RED_COLOR, WHITE_COLOR, YELLOW_COLOR} from '../personalization_app.mojom-webui.js';
 import {WithPersonalizationStore} from '../personalization_store.js';
-import {isSelectionEvent} from '../utils.js';
+import {convertToRgbHexStr, isSelectionEvent} from '../utils.js';
 
 import {getShouldShowNudge, handleNudgeShown, setBacklightColor} from './keyboard_backlight_controller.js';
 import {getTemplate} from './keyboard_backlight_element.html.js';
@@ -47,12 +47,6 @@
   return (r * 299 + g * 587 + b * 114) / 1000;
 }
 
-/** Returns the RGB hex in #FFFFFF format. */
-function convertToRgbHexStr(hexVal: number): string {
-  const PADDING_LENGTH = 6;
-  return `#${hexVal.toString(16).padStart(PADDING_LENGTH, '0')}`;
-}
-
 interface ColorInfo {
   hexVal: string;
   enumVal: BacklightColor;
diff --git a/ash/webui/personalization_app/resources/js/theme/dynamic_color_element.html b/ash/webui/personalization_app/resources/js/theme/dynamic_color_element.html
index c42345e..a3f45578 100644
--- a/ash/webui/personalization_app/resources/js/theme/dynamic_color_element.html
+++ b/ash/webui/personalization_app/resources/js/theme/dynamic_color_element.html
@@ -37,6 +37,20 @@
     width: 76px;
   }
 
+  /* TODO(b/254478525) Replace with an icon button component. */
+  cr-button[aria-checked='true']::before {
+    background-color: var(--cros-button-background-color-primary);
+    border-radius: 50%;
+    color: var(--cros-button-icon-color-primary);
+    content: '✓';
+    font-size: 16px;
+    height: 32px;
+    line-height: 32px;
+    position: absolute;
+    text-align: center;
+    width: 32px;
+  }
+
   color-scheme-icon-svg,
   svg {
     height: 48px;
@@ -80,6 +94,7 @@
       <cr-button
           tabindex$="[[getTabIndex_(staticColor)]]"
           on-click="onClickStaticColorButton_"
+          aria-checked$="[[getStaticColorAriaChecked_(staticColor, staticColorSelected_)]]"
           data-static-color$="[[staticColor]]">
         <svg>
           <circle style$="fill: [[staticColor]]" cx="24" cy="24" r="24"></circle>
diff --git a/ash/webui/personalization_app/resources/js/theme/dynamic_color_element.ts b/ash/webui/personalization_app/resources/js/theme/dynamic_color_element.ts
index 57c26bd..5e8a5cc 100644
--- a/ash/webui/personalization_app/resources/js/theme/dynamic_color_element.ts
+++ b/ash/webui/personalization_app/resources/js/theme/dynamic_color_element.ts
@@ -20,10 +20,12 @@
 
 import {ColorScheme} from '../personalization_app.mojom-webui.js';
 import {WithPersonalizationStore} from '../personalization_store.js';
+import {convertToRgbHexStr} from '../utils.js';
 
 import {getTemplate} from './dynamic_color_element.html.js';
-import {setColorSchemePref, setStaticColorPref} from './theme_controller.js';
+import {initializeDynamicColorData, setColorSchemePref, setStaticColorPref} from './theme_controller.js';
 import {getThemeProvider} from './theme_interface_provider.js';
+import {ThemeObserver} from './theme_observer.js';
 
 export interface DynamicColorScheme {
   id: ColorScheme;
@@ -63,15 +65,9 @@
         reflectToAttribute: true,
       },
       // The static color stored in the backend.
-      staticColorSelected_: {
-        type: Object,
-        value: DEFAULT_STATIC_COLOR,
-      },
+      staticColorSelected_: Object,
       // The color scheme stored in the backend.
-      colorSchemeSelected_: {
-        type: Object,
-        value: DEFAULT_COLOR_SCHEME,
-      },
+      colorSchemeSelected_: Object,
       staticColors_: {
         type: Object,
         readOnly: true,
@@ -144,6 +140,15 @@
     this.$.colorSchemeKeys.target = this.$.colorSchemeSelector;
   }
 
+  override connectedCallback() {
+    super.connectedCallback();
+    ThemeObserver.initThemeObserverIfNeeded();
+    this.watch<DynamicColorElement['staticColorSelected_']>(
+        'staticColorSelected_', state => state.theme.staticColorSelected);
+    this.updateFromStore();
+    initializeDynamicColorData(getThemeProvider(), this.getStore());
+  }
+
   private onClickColorSchemeButton_(event: Event) {
     const eventTarget = event.currentTarget as HTMLElement;
     const colorScheme = Number(eventTarget.dataset['colorSchemeId']);
@@ -155,7 +160,6 @@
     const eventTarget = event.currentTarget as HTMLElement;
     const staticColorHexStr = String(eventTarget.dataset['staticColor']);
     const staticColor = hexColorToSkColor(staticColorHexStr);
-    this.staticColorSelected_ = staticColor;
     setStaticColorPref(staticColor, getThemeProvider(), this.getStore());
   }
 
@@ -169,6 +173,15 @@
     }
   }
 
+  private getStaticColorAriaChecked_(
+      staticColor: string, staticColorSelected: SkColor|null): string {
+    if (!staticColorSelected) {
+      return 'false';
+    }
+    return (staticColor === convertToRgbHexStr(staticColorSelected.value))
+        .toString();
+  }
+
   private onStaticColorKeysPress_(
       e: CustomEvent<{key: string, keyboardEvent: KeyboardEvent}>) {
     this.onKeysPress_(
diff --git a/ash/webui/personalization_app/resources/js/theme/theme_actions.ts b/ash/webui/personalization_app/resources/js/theme/theme_actions.ts
index 5c51a65..4d5486a 100644
--- a/ash/webui/personalization_app/resources/js/theme/theme_actions.ts
+++ b/ash/webui/personalization_app/resources/js/theme/theme_actions.ts
@@ -38,7 +38,7 @@
 
 export type SetStaticColorPrefAction = Action&{
   name: ThemeActionName.SET_STATIC_COLOR,
-  staticColor: SkColor,
+  staticColor: SkColor | null,
 };
 
 export function setDarkModeEnabledAction(enabled: boolean):
@@ -56,7 +56,7 @@
   return {name: ThemeActionName.SET_COLOR_SCHEME, colorScheme};
 }
 
-export function setStaticColorAction(staticColor: SkColor):
-    SetStaticColorPrefAction {
+export function setStaticColorAction(staticColor: SkColor|
+                                     null): SetStaticColorPrefAction {
   return {name: ThemeActionName.SET_STATIC_COLOR, staticColor};
 }
diff --git a/ash/webui/personalization_app/resources/js/theme/theme_controller.ts b/ash/webui/personalization_app/resources/js/theme/theme_controller.ts
index e9f039ef..9878edbcc 100644
--- a/ash/webui/personalization_app/resources/js/theme/theme_controller.ts
+++ b/ash/webui/personalization_app/resources/js/theme/theme_controller.ts
@@ -26,6 +26,13 @@
   store.endBatchUpdate();
 }
 
+export async function initializeDynamicColorData(
+    provider: ThemeProviderInterface,
+    store: PersonalizationStore): Promise<void> {
+  const {staticColor} = await provider.getStaticColor();
+  store.dispatch(setStaticColorAction(staticColor));
+}
+
 // Disables or enables dark color mode.
 export function setColorModePref(
     darkModeEnabled: boolean, provider: ThemeProviderInterface,
diff --git a/ash/webui/personalization_app/resources/js/theme/theme_observer.ts b/ash/webui/personalization_app/resources/js/theme/theme_observer.ts
index d93a3883..ab1eb19 100644
--- a/ash/webui/personalization_app/resources/js/theme/theme_observer.ts
+++ b/ash/webui/personalization_app/resources/js/theme/theme_observer.ts
@@ -2,10 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import {SkColor} from 'chrome://resources/mojo/skia/public/mojom/skcolor.mojom-webui.js';
+
 import {ThemeObserverInterface, ThemeObserverReceiver, ThemeProviderInterface} from '../personalization_app.mojom-webui.js';
 import {PersonalizationStore} from '../personalization_store.js';
 
-import {setColorModeAutoScheduleEnabledAction, setDarkModeEnabledAction} from './theme_actions.js';
+import {setColorModeAutoScheduleEnabledAction, setDarkModeEnabledAction, setStaticColorAction} from './theme_actions.js';
 import {getThemeProvider} from './theme_interface_provider.js';
 
 /** @fileoverview listens for updates on color mode changes. */
@@ -48,4 +50,9 @@
     const store = PersonalizationStore.getInstance();
     store.dispatch(setColorModeAutoScheduleEnabledAction(enabled));
   }
+
+  onStaticColorChanged(staticColor: SkColor): void {
+    const store = PersonalizationStore.getInstance();
+    store.dispatch(setStaticColorAction(staticColor));
+  }
 }
diff --git a/ash/webui/personalization_app/resources/js/theme/theme_reducers.ts b/ash/webui/personalization_app/resources/js/theme/theme_reducers.ts
index b37d799..53c6de3 100644
--- a/ash/webui/personalization_app/resources/js/theme/theme_reducers.ts
+++ b/ash/webui/personalization_app/resources/js/theme/theme_reducers.ts
@@ -31,8 +31,20 @@
   }
 }
 
+export function staticColorSelectedReducer(
+    state: ThemeState['staticColorSelected'], action: Actions,
+    _: PersonalizationState): ThemeState['staticColorSelected'] {
+  switch (action.name) {
+    case ThemeActionName.SET_STATIC_COLOR:
+      return action.staticColor;
+    default:
+      return state;
+  }
+}
+
 export const themeReducers:
     {[K in keyof ThemeState]: ReducerFunction<ThemeState[K]>} = {
       colorModeAutoScheduleEnabled: colorModeAutoScheduleEnabledReducer,
       darkModeEnabled: darkModeEnabledReducer,
+      staticColorSelected: staticColorSelectedReducer,
     };
diff --git a/ash/webui/personalization_app/resources/js/theme/theme_state.ts b/ash/webui/personalization_app/resources/js/theme/theme_state.ts
index 1996cb8..3b27765 100644
--- a/ash/webui/personalization_app/resources/js/theme/theme_state.ts
+++ b/ash/webui/personalization_app/resources/js/theme/theme_state.ts
@@ -2,18 +2,21 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import {SkColor} from 'chrome://resources/mojo/skia/public/mojom/skcolor.mojom-webui.js';
+
 /**
  * Stores theme related states.
  */
 export interface ThemeState {
   colorModeAutoScheduleEnabled: boolean|null;
   darkModeEnabled: boolean|null;
+  staticColorSelected: SkColor|null;
 }
 
 export function emptyState(): ThemeState {
   return {
     colorModeAutoScheduleEnabled: null,
     darkModeEnabled: null,
-
+    staticColorSelected: null,
   };
 }
diff --git a/ash/webui/personalization_app/resources/js/utils.ts b/ash/webui/personalization_app/resources/js/utils.ts
index 50e35e3..3cbba44 100644
--- a/ash/webui/personalization_app/resources/js/utils.ts
+++ b/ash/webui/personalization_app/resources/js/utils.ts
@@ -92,3 +92,13 @@
       (maybeDataUrl.url.startsWith('data:image/png;base64') ||
        maybeDataUrl.url.startsWith('data:image/jpeg;base64'));
 }
+
+/** Returns the RGB hex in #ffffff format. */
+export function convertToRgbHexStr(hexVal: number): string {
+  const PADDING_LENGTH = 6;
+  const STRING_LENGTH = 16;
+  return `#${
+      (hexVal & 0x0FFFFFF)
+          .toString(STRING_LENGTH)
+          .padStart(PADDING_LENGTH, '0')}`;
+}
diff --git a/ash/webui/personalization_app/test/fake_personalization_app_theme_provider.cc b/ash/webui/personalization_app/test/fake_personalization_app_theme_provider.cc
index c6936eb..e954d75 100644
--- a/ash/webui/personalization_app/test/fake_personalization_app_theme_provider.cc
+++ b/ash/webui/personalization_app/test/fake_personalization_app_theme_provider.cc
@@ -4,6 +4,7 @@
 
 #include "ash/webui/personalization_app/test/fake_personalization_app_theme_provider.h"
 #include "fake_personalization_app_theme_provider.h"
+#include "third_party/skia/include/core/SkColor.h"
 
 namespace ash::personalization_app {
 
@@ -53,4 +54,9 @@
     ::SkColor static_color) {
   return;
 }
+
+void FakePersonalizationAppThemeProvider::GetStaticColor(
+    GetStaticColorCallback callback) {
+  std::move(callback).Run(SK_ColorBLUE);
+}
 }  // namespace ash::personalization_app
diff --git a/ash/webui/personalization_app/test/fake_personalization_app_theme_provider.h b/ash/webui/personalization_app/test/fake_personalization_app_theme_provider.h
index 3e4c3acb..5e34a49 100644
--- a/ash/webui/personalization_app/test/fake_personalization_app_theme_provider.h
+++ b/ash/webui/personalization_app/test/fake_personalization_app_theme_provider.h
@@ -49,6 +49,8 @@
 
   void SetStaticColor(::SkColor static_color) override;
 
+  void GetStaticColor(GetStaticColorCallback callback) override;
+
   void IsDarkModeEnabled(IsDarkModeEnabledCallback callback) override;
 
   void IsColorModeAutoScheduleEnabled(
diff --git a/ash/webui/personalization_app/test/personalization_app_mojom_banned_browsertest_fixture.cc b/ash/webui/personalization_app/test/personalization_app_mojom_banned_browsertest_fixture.cc
index 5099cdc..fcc3fde 100644
--- a/ash/webui/personalization_app/test/personalization_app_mojom_banned_browsertest_fixture.cc
+++ b/ash/webui/personalization_app/test/personalization_app_mojom_banned_browsertest_fixture.cc
@@ -107,6 +107,10 @@
               (bool enabled),
               (override));
   MOCK_METHOD(void,
+              GetStaticColor,
+              (GetStaticColorCallback callback),
+              (override));
+  MOCK_METHOD(void,
               IsDarkModeEnabled,
               (IsDarkModeEnabledCallback callback),
               (override));
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 c72c3a5..7c7459b 100644
--- a/ash/webui/projector_app/test/projector_message_handler_unittest.cc
+++ b/ash/webui/projector_app/test/projector_message_handler_unittest.cc
@@ -345,7 +345,7 @@
 
   base::Value::List list_args;
   list_args.Append(kSendXhrCallback);
-  base::ListValue args;
+  base::Value::List args;
   args.Append(kTestXhrUrl);
   args.Append(kTestXhrMethod);
   args.Append(kTestXhrRequestBody);
diff --git a/ash/wm/desks/desk_animation_base.cc b/ash/wm/desks/desk_animation_base.cc
index c7b2eb8..8fe6fc71 100644
--- a/ash/wm/desks/desk_animation_base.cc
+++ b/ash/wm/desks/desk_animation_base.cc
@@ -9,6 +9,7 @@
 #include "ash/wm/desks/desk.h"
 #include "ash/wm/desks/desks_controller.h"
 #include "ash/wm/desks/desks_util.h"
+#include "ash/wm/overview/overview_controller.h"
 #include "ui/aura/window_tree_host.h"
 #include "ui/compositor/compositor.h"
 
@@ -74,6 +75,14 @@
   return false;
 }
 
+bool DeskAnimationBase::CanEnterOverview() const {
+  return is_overview_toggle_allowed_;
+}
+
+bool DeskAnimationBase::CanEndOverview() const {
+  return is_overview_toggle_allowed_;
+}
+
 void DeskAnimationBase::OnStartingDeskScreenshotTaken(int ending_desk_index) {
   DCHECK(!desk_switch_animators_.empty());
 
@@ -160,4 +169,26 @@
   return desk_switch_animators_[index].get();
 }
 
+void DeskAnimationBase::ActivateDeskDuringAnimation(
+    const Desk* desk,
+    bool update_window_activation) {
+  // Normally we do not allow toggling overview while there is an active
+  // animation. The only exception is when we are doing a desk activation and
+  // are starting the animation in overview. The desk switch animations require
+  // taking a screenshot of the starting and ending desks before animating
+  // between the two screenshots, and these screenshots need to represent what
+  // the new desk will look like for the user. If we start the animation in
+  // overview, we want to allow `ActivateDeskInternal()` to end overview on the
+  // old active desk (and enter overview on the new active desk if the overview
+  // desk navigation feature is enabled). Once `ActivateDeskInternal()` finishes
+  // updating the active desk and overview states, we immediately set
+  // `is_overview_toggle_allowed_` to false to prevent any subsequent overview
+  // toggling (i.e. user input).
+  is_overview_toggle_allowed_ =
+      features::IsOverviewDeskNavigationEnabled() &&
+      Shell::Get()->overview_controller()->InOverviewSession();
+  controller_->ActivateDeskInternal(desk, update_window_activation);
+  is_overview_toggle_allowed_ = false;
+}
+
 }  // namespace ash
diff --git a/ash/wm/desks/desk_animation_base.h b/ash/wm/desks/desk_animation_base.h
index b4c4a3af7..5b823c8 100644
--- a/ash/wm/desks/desk_animation_base.h
+++ b/ash/wm/desks/desk_animation_base.h
@@ -7,6 +7,7 @@
 
 #include "ash/ash_export.h"
 #include "ash/public/cpp/metrics_util.h"
+#include "ash/wm/desks/desk.h"
 #include "ash/wm/desks/desks_histogram_enums.h"
 #include "ash/wm/desks/root_window_desk_switch_animator.h"
 #include "base/callback.h"
@@ -54,6 +55,10 @@
   // implementation. Returns false if the animation does not support ending.
   virtual bool EndSwipeAnimation();
 
+  // Returns true if entering/exiting overview during the animation is allowed.
+  virtual bool CanEnterOverview() const;
+  virtual bool CanEndOverview() const;
+
   // RootWindowDeskSwitchAnimator::Delegate:
   void OnStartingDeskScreenshotTaken(int ending_desk_index) override;
   void OnEndingDeskScreenshotTaken() override;
@@ -72,6 +77,12 @@
       size_t index) const;
 
  protected:
+  // This will set `is_overview_toggle_allowed_` before and after calling
+  // `ActivateDeskInternal()`, allowing exiting/entering overview during the
+  // animation.
+  void ActivateDeskDuringAnimation(const Desk* desk,
+                                   bool update_window_activation);
+
   // Abstract functions that can be overridden by child classes to do different
   // things when phase (1), and phase (3) completes. Note that
   // `OnDeskSwitchAnimationFinishedInternal()` will be called before the desks
@@ -120,6 +131,14 @@
   // remove animation.
   int visible_desk_changes_ = 0;
 
+  // Used for allowing us to enter or exit overview during a desk animation. If
+  // there is an ongoing desk animation, we want to prevent unwanted exit or
+  // enter overview toggling so that we don't end up in a strange or unexpected
+  // state. Toggling overview is only allowed when we are doing an internal desk
+  // activation, where we manually set the overview states of the old active
+  // desk and the new active desk.
+  bool is_overview_toggle_allowed_ = false;
+
   // Used for the Ash.Desks.AnimationLatency.* histograms. Null if no animation
   // is being prepared. In a continuous desk animation, the latency is reported
   // only for the first desk switch, and `launch_time_` is null thereafter.
diff --git a/ash/wm/desks/desk_animation_impl.cc b/ash/wm/desks/desk_animation_impl.cc
index da1908dd..bdc0d3f 100644
--- a/ash/wm/desks/desk_animation_impl.cc
+++ b/ash/wm/desks/desk_animation_impl.cc
@@ -250,6 +250,13 @@
   return true;
 }
 
+bool DeskActivationAnimation::CanEnterOverview() const {
+  return DeskAnimationBase::CanEnterOverview() &&
+         (switch_source_ == DesksSwitchSource::kDeskSwitchShortcut ||
+          switch_source_ == DesksSwitchSource::kDeskSwitchTouchpad ||
+          switch_source_ == DesksSwitchSource::kIndexedDeskSwitchShortcut);
+}
+
 void DeskActivationAnimation::OnStartingDeskScreenshotTakenInternal(
     int ending_desk_index) {
   DCHECK_EQ(ending_desk_index_, ending_desk_index);
@@ -260,9 +267,8 @@
   // During a chained animation we may not switch desks if a replaced target
   // desk does not require a new screenshot. If that is the case, activate the
   // proper desk here.
-  controller_->ActivateDeskInternal(
-      controller_->desks()[ending_desk_index_].get(),
-      update_window_activation_);
+  ActivateDeskDuringAnimation(controller_->desks()[ending_desk_index_].get(),
+                              update_window_activation_);
 }
 
 DeskAnimationBase::LatencyReportCallback
@@ -284,36 +290,13 @@
   for (auto* root_window_controller : Shell::GetAllRootWindowControllers())
     root_window_controller->HideContextMenuNoAnimation();
 
-  // The order here matters. Overview must end before ending tablet split view
-  // before switching desks. (If clamshell split view is active on one or more
-  // displays, then it simply will end when we end overview.) That's because
-  // we don't want |TabletModeWindowManager| maximizing all windows because we
-  // cleared the snapped ones in |SplitViewController| first. See
-  // |TabletModeWindowManager::OnOverviewModeEndingAnimationComplete|.
-  // See also test coverage for this case in
-  // `TabletModeDesksTest.SnappedStateRetainedOnSwitchingDesksFromOverview`.
-  const bool in_overview =
-      Shell::Get()->overview_controller()->InOverviewSession();
-  if (in_overview) {
-    // Exit overview mode immediately without any animations before taking the
-    // ending desk screenshot. This makes sure that the ending desk
-    // screenshot will only show the windows in that desk, not overview stuff.
-    Shell::Get()->overview_controller()->EndOverview(
-        OverviewEndAction::kDeskActivation,
-        OverviewEnterExitType::kImmediateExit);
-  }
-  SplitViewController* split_view_controller =
-      SplitViewController::Get(Shell::GetPrimaryRootWindow());
-  split_view_controller->EndSplitView(
-      SplitViewController::EndReason::kDesksChange);
-
   // Check that ending_desk_index_ is in range.
   // See crbug.com/1346900.
   const auto& desks = controller_->desks();
   CHECK_LT(static_cast<size_t>(ending_desk_index_), desks.size());
 
-  controller_->ActivateDeskInternal(desks[ending_desk_index_].get(),
-                                    update_window_activation_);
+  ActivateDeskDuringAnimation(desks[ending_desk_index_].get(),
+                              update_window_activation_);
 
   MaybeRestoreSplitView(/*refresh_snapped_windows=*/true);
 }
@@ -368,9 +351,8 @@
   // will be activated after the active desk `desk_to_remove_index_` is
   // removed). This means that phase (2) will take a screenshot of that desk
   // before we move the windows of `desk_to_remove_index_` to that target desk.
-  controller_->ActivateDeskInternal(
-      controller_->desks()[ending_desk_index_].get(),
-      /*update_window_activation=*/false);
+  ActivateDeskDuringAnimation(controller_->desks()[ending_desk_index_].get(),
+                              /*update_window_activation=*/false);
 }
 
 void DeskRemovalAnimation::OnDeskSwitchAnimationFinishedInternal() {
diff --git a/ash/wm/desks/desk_animation_impl.h b/ash/wm/desks/desk_animation_impl.h
index 4bc8d866..e593d1f 100644
--- a/ash/wm/desks/desk_animation_impl.h
+++ b/ash/wm/desks/desk_animation_impl.h
@@ -34,6 +34,7 @@
   bool Replace(bool moving_left, DesksSwitchSource source) override;
   bool UpdateSwipeAnimation(float scroll_delta_x) override;
   bool EndSwipeAnimation() override;
+  bool CanEnterOverview() const override;
   void OnStartingDeskScreenshotTakenInternal(int ending_desk_index) override;
   void OnDeskSwitchAnimationFinishedInternal() override;
   LatencyReportCallback GetLatencyReportCallback() const override;
@@ -45,7 +46,7 @@
 
   // Prepares the desk associated with |index| for taking a screenshot. Exits
   // overview and splitview if necessary and then activates the desk. Restores
-  // splitview if necessary after activating the desk.
+  // splitview or overview if necessary after activating the desk.
   void PrepareDeskForScreenshot(int index);
 
   // The switch source that requested this animation.
diff --git a/ash/wm/desks/desk_animation_impl_unittest.cc b/ash/wm/desks/desk_animation_impl_unittest.cc
index 84603c4a..7662021 100644
--- a/ash/wm/desks/desk_animation_impl_unittest.cc
+++ b/ash/wm/desks/desk_animation_impl_unittest.cc
@@ -4,13 +4,18 @@
 
 #include "ash/wm/desks/desk_animation_impl.h"
 
+#include "ash/constants/ash_features.h"
+#include "ash/shell.h"
 #include "ash/test/ash_test_base.h"
+#include "ash/wm/desks/desk.h"
 #include "ash/wm/desks/desks_constants.h"
 #include "ash/wm/desks/desks_controller.h"
 #include "ash/wm/desks/desks_histogram_enums.h"
 #include "ash/wm/desks/desks_test_util.h"
 #include "ash/wm/desks/root_window_desk_switch_animator_test_api.h"
+#include "ash/wm/overview/overview_controller.h"
 #include "base/barrier_closure.h"
+#include "base/test/scoped_feature_list.h"
 #include "ui/compositor/layer.h"
 #include "ui/compositor/layer_animator.h"
 #include "ui/compositor/scoped_animation_duration_scale_mode.h"
@@ -185,7 +190,7 @@
 
   // Start and finish a animation without waiting for the screenshots to be
   // taken.
-  desks_controller->StartSwipeAnimation(/*moving_left=*/false);
+  desks_controller->StartSwipeAnimation(/*move_left=*/false);
   ASSERT_TRUE(desks_controller->animation());
 
   desks_controller->UpdateSwipeAnimation(10);
@@ -193,4 +198,44 @@
   EXPECT_FALSE(desks_controller->animation());
 }
 
+class OverviewDeskNavigationTest : public AshTestBase {
+ public:
+  OverviewDeskNavigationTest()
+      : scoped_feature_list_(features::kOverviewDeskNavigation) {}
+  OverviewDeskNavigationTest(const OverviewDeskNavigationTest&) = delete;
+  OverviewDeskNavigationTest& operator=(const OverviewDeskNavigationTest&) =
+      delete;
+  ~OverviewDeskNavigationTest() override = default;
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+// Tests when we switch between desks in overview that the desk switch animation
+// doesn't exit overview.
+TEST_F(OverviewDeskNavigationTest, SwitchDesksWithoutExitingOverview) {
+  ui::ScopedAnimationDurationScaleMode animation_scale(
+      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
+
+  NewDesk();
+  auto* desks_controller = DesksController::Get();
+  ASSERT_EQ(2u, desks_controller->desks().size());
+  EXPECT_TRUE(desks_controller->desks()[0].get()->is_active());
+
+  EnterOverview();
+  auto* overview_controller = Shell::Get()->overview_controller();
+  ASSERT_TRUE(overview_controller->InOverviewSession());
+
+  // Switch to the next desk while in overview and wait for the desk switch
+  // animation.
+  DeskSwitchAnimationWaiter waiter;
+  desks_controller->ActivateAdjacentDesk(
+      /*going_left=*/false, DesksSwitchSource::kDeskSwitchShortcut);
+  waiter.Wait();
+
+  // Verify that we have switched desks and are still in overview.
+  EXPECT_TRUE(desks_controller->desks()[1].get()->is_active());
+  ASSERT_TRUE(overview_controller->InOverviewSession());
+}
+
 }  // namespace ash
diff --git a/ash/wm/desks/desks_controller.cc b/ash/wm/desks/desks_controller.cc
index 6bad8b4..f7870d4 100644
--- a/ash/wm/desks/desks_controller.cc
+++ b/ash/wm/desks/desks_controller.cc
@@ -1389,6 +1389,44 @@
           temporary_removed_desk_->toast_id());
 }
 
+bool DesksController::CanEnterOverview() const {
+  // Prevent entering overview if a desk animation is underway and we didn't
+  // start the animation in overview. The overview animation would be completely
+  // covered anyway, and doing so could put us in a strange state (unless we are
+  // doing an immediate enter).
+  if (animation_ && !animation_->CanEnterOverview()) {
+    // The one exception to this rule is in tablet mode, having a window snapped
+    // to one side. Moving to this desk, we will want to open overview on the
+    // other side. For clamshell we don't need to enter overview as having a
+    // window snapped to one side and showing the wallpaper on the other is
+    // fine.
+    auto* split_view_controller =
+        SplitViewController::Get(Shell::GetPrimaryRootWindow());
+    if (!split_view_controller->InTabletSplitViewMode() ||
+        split_view_controller->state() ==
+            SplitViewController::State::kBothSnapped) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool DesksController::CanEndOverview() const {
+  // During an ongoing desk animation, we take screenshots of the starting
+  // active desk and the new active desk and animate between them. If overview
+  // desk navigation is enabled, we keep the user in overview for both the
+  // original and new active desks so it appears as if the user never left
+  // overview, and this is reflected in the screenshots displayed. If an
+  // overview exit is attempted during this ongoing animation (i.e. a user
+  // presses the overview button), we want to ensure that the displayed
+  // screenshot is still reflective of the user's actual ending state (which can
+  // be jarring if the screenshot is different from the appearance of the new
+  // desk), so we don't want to allow overview to be exited before the animation
+  // ends.
+  return !features::IsOverviewDeskNavigationEnabled() || !animation_ ||
+         animation_->CanEndOverview();
+}
+
 void DesksController::OnWindowActivating(ActivationReason reason,
                                          aura::Window* gaining_active,
                                          aura::Window* losing_active) {
@@ -1509,6 +1547,31 @@
   if (features::IsPerDeskZOrderEnabled())
     old_active->BuildAllDeskStackingData();
 
+  auto* shell = Shell::Get();
+  auto* overview_controller = shell->overview_controller();
+  const bool was_in_overview = overview_controller->InOverviewSession();
+  if (animation_) {
+    // The order here matters. Overview must end before ending tablet split view
+    // before switching desks. (If clamshell split view is active on one or more
+    // displays, then it simply will end when we end overview.) That's because
+    // we don't want `TabletModeWindowManager` maximizing all windows because we
+    // cleared the snapped ones in `SplitViewController` first. See
+    // `TabletModeWindowManager::OnOverviewModeEndingAnimationComplete`.
+    // See also test coverage for this case in
+    // `TabletModeDesksTest.SnappedStateRetainedOnSwitchingDesksFromOverview`.
+    if (was_in_overview) {
+      // Exit overview mode immediately without any animations before taking the
+      // ending desk screenshot. This makes sure that the ending desk screenshot
+      // will only show the windows in that desk, not overview stuff.
+      overview_controller->EndOverview(OverviewEndAction::kDeskActivation,
+                                       OverviewEnterExitType::kImmediateExit);
+    }
+    SplitViewController* split_view_controller =
+        SplitViewController::Get(Shell::GetPrimaryRootWindow());
+    split_view_controller->EndSplitView(
+        SplitViewController::EndReason::kDesksChange);
+  }
+
   MoveVisibleOnAllDesksWindowsFromActiveDeskTo(const_cast<Desk*>(desk));
   active_desk_ = const_cast<Desk*>(desk);
   RestackVisibleOnAllDesksWindowsOnActiveDesk();
@@ -1517,6 +1580,11 @@
   DCHECK(old_active);
   old_active->Deactivate(update_window_activation);
   active_desk_->Activate(update_window_activation);
+  if (features::IsOverviewDeskNavigationEnabled() && animation_ &&
+      was_in_overview) {
+    overview_controller->StartOverview(OverviewStartAction::kOverviewDeskSwitch,
+                                       OverviewEnterExitType::kImmediateEnter);
+  }
 
   // Content is normally updated in
   // `MoveVisibleOnAllDesksWindowsFromActiveDeskTo`. However, the layers for a
@@ -1531,7 +1599,6 @@
 
   // If in the middle of a window cycle gesture, reset the window cycle list
   // contents so it contains the new active desk's windows.
-  auto* shell = Shell::Get();
   auto* window_cycle_controller = shell->window_cycle_controller();
   if (window_cycle_controller->IsAltTabPerActiveDesk())
     window_cycle_controller->MaybeResetCycleList();
diff --git a/ash/wm/desks/desks_controller.h b/ash/wm/desks/desks_controller.h
index bd094eeb..9e6c287e 100644
--- a/ash/wm/desks/desks_controller.h
+++ b/ash/wm/desks/desks_controller.h
@@ -347,6 +347,12 @@
   // one is active. Returns true if the activation was successful.
   bool MaybeActivateDeskRemovalUndoButtonOnHighlightedToast();
 
+  // Returns true if it's possible to enter or exit overview mode in the current
+  // configuration. This can be false at certain times, such as when there is an
+  // active desk animation.
+  bool CanEnterOverview() const;
+  bool CanEndOverview() const;
+
   // ::wm::ActivationChangeObserver:
   void OnWindowActivating(ActivationReason reason,
                           aura::Window* gaining_active,
@@ -384,10 +390,10 @@
 
   bool HasDeskWithName(const std::u16string& desk_name) const;
 
-  // Activates the given |desk| and deactivates the currently active one. |desk|
-  // has to be an existing desk. If |update_window_activation| is true,
-  // the active desk on the deactivated desk will be deactivated, and the most-
-  // recently used window on the newly-activated desk will be deactivated. This
+  // Activates the given `desk` and deactivates the currently active one. `desk`
+  // has to be an existing desk. If `update_window_activation` is true, the
+  // active window on the deactivated desk will be deactivated, and the most-
+  // recently used window on the newly-activated desk will be activated. This
   // parameter is almost always true except when the active desk is being
   // removed while in overview mode. In that case, windows from the active desk
   // will move to another desk and remain in the overview grid, and no
diff --git a/ash/wm/desks/templates/saved_desk_save_desk_button_container.cc b/ash/wm/desks/templates/saved_desk_save_desk_button_container.cc
index d3548ab5..7538cad 100644
--- a/ash/wm/desks/templates/saved_desk_save_desk_button_container.cc
+++ b/ash/wm/desks/templates/saved_desk_save_desk_button_container.cc
@@ -33,6 +33,11 @@
   kNumberOfTooltipStatus,
 };
 
+struct SaveDeskButtonStatus {
+  bool enabled;
+  int tooltip_id;
+};
+
 constexpr std::array<int,
                      static_cast<int>(TooltipStatus::kNumberOfTooltipStatus)>
     kSaveAsTemplateButtonTooltipIDs = {
@@ -63,7 +68,7 @@
   }
 }
 
-std::pair<bool, int> GetEnableStateAndTooltipIDForButtonType(
+SaveDeskButtonStatus GetEnableStateAndTooltipIDForButtonType(
     SavedDeskSaveDeskButton::Type type,
     int current_entry_count,
     int max_entry_count,
@@ -72,34 +77,34 @@
     int window_count) {
   // Disable if we already have the max supported saved desks.
   if (current_entry_count >= max_entry_count) {
-    return {/*enabled=*/false,
-            /*tooltip_ID=*/GetTooltipID(type, TooltipStatus::kReachMax)};
+    return {.enabled = false,
+            .tooltip_id = GetTooltipID(type, TooltipStatus::kReachMax)};
   }
 
   // Enable if there are any supported window.
   if (incognito_window_count + unsupported_window_count != window_count) {
-    return {/*enabled=*/true,
-            /*tooltip_ID=*/GetTooltipID(type, TooltipStatus::kOk)};
+    return {.enabled = true,
+            .tooltip_id = GetTooltipID(type, TooltipStatus::kOk)};
   }
 
   // Disable if there are incognito windows and unsupported Linux Apps but no
   // supported windows.
   if (incognito_window_count && unsupported_window_count) {
-    return {/*enabled=*/false,
-            /*tooltip_ID=*/GetTooltipID(
+    return {.enabled = false,
+            .tooltip_id = GetTooltipID(
                 type, TooltipStatus::kIncognitoAndUnsupportedWindow)};
   }
 
   // Disable if there are incognito windows but no supported windows.
   if (incognito_window_count) {
-    return {/*enabled=*/false,
-            /*tooltip_ID=*/GetTooltipID(type, TooltipStatus::kIncognitoWindow)};
+    return {.enabled = false,
+            .tooltip_id = GetTooltipID(type, TooltipStatus::kIncognitoWindow)};
   }
 
   // Disable if there are unsupported Linux Apps but no supported windows.
   DCHECK(unsupported_window_count);
-  return {/*enabled=*/false,
-          /*tooltip_ID=*/GetTooltipID(type, TooltipStatus::kUnsupportedWindow)};
+  return {.enabled = false,
+          .tooltip_id = GetTooltipID(type, TooltipStatus::kUnsupportedWindow)};
 }
 
 }  // namespace
@@ -182,13 +187,11 @@
   SavedDeskSaveDeskButton* button = GetButtonFromType(type);
   if (!button)
     return;
-  std::pair<bool, int> enable_state_and_tooltip_ID =
-      GetEnableStateAndTooltipIDForButtonType(
-          type, current_entry_count, max_entry_count, incognito_window_count,
-          unsupported_window_count, window_count);
-  button->SetEnabled(enable_state_and_tooltip_ID.first);
-  button->SetTooltipText(
-      l10n_util::GetStringUTF16(enable_state_and_tooltip_ID.second));
+  SaveDeskButtonStatus button_status = GetEnableStateAndTooltipIDForButtonType(
+      type, current_entry_count, max_entry_count, incognito_window_count,
+      unsupported_window_count, window_count);
+  button->SetEnabled(button_status.enabled);
+  button->SetTooltipText(l10n_util::GetStringUTF16(button_status.tooltip_id));
 }
 
 void SavedDeskSaveDeskButtonContainer::
diff --git a/ash/wm/overview/overview_controller.cc b/ash/wm/overview/overview_controller.cc
index 7082b61c..17ea0a7 100644
--- a/ash/wm/overview/overview_controller.cc
+++ b/ash/wm/overview/overview_controller.cc
@@ -475,24 +475,8 @@
   if (IsSplitViewDividerDraggedOrAnimated())
     return false;
 
-  // Prevent entering overview if a desk animation is underway. The overview
-  // animation would be completely covered anyway, and doing so could put us in
-  // a strange state. Note that exiting overview is allowed as it is part of the
-  // animation.
-  if (DesksController::Get()->animation()) {
-    // The one exception to this rule is in tablet mode, having a window snapped
-    // to one side. Moving to this desk, we will want to open overview on the
-    // other side. For clamshell we don't need to enter overview as having a
-    // window snapped to one side and showing the wallpaper on the other is
-    // fine.
-    auto* split_view_controller =
-        SplitViewController::Get(Shell::GetPrimaryRootWindow());
-    if (!split_view_controller->InTabletSplitViewMode() ||
-        split_view_controller->state() ==
-            SplitViewController::State::kBothSnapped) {
-      return false;
-    }
-  }
+  if (!DesksController::Get()->CanEnterOverview())
+    return false;
 
   // Don't allow a window overview if the user session is not active (e.g.
   // locked or in user-adding screen) or a modal dialog is open or running in
@@ -524,7 +508,7 @@
     return false;
   }
 
-  return true;
+  return DesksController::Get()->CanEndOverview();
 }
 
 void OverviewController::OnStartingAnimationComplete(bool canceled) {
diff --git a/ash/wm/overview/overview_grid.cc b/ash/wm/overview/overview_grid.cc
index 4fe126b1..42b43c8 100644
--- a/ash/wm/overview/overview_grid.cc
+++ b/ash/wm/overview/overview_grid.cc
@@ -1934,6 +1934,10 @@
                                 weak_ptr_factory_.GetWeakPtr())));
   }
 
+  // If a desk animation is in progress, we don't want to animate
+  // `save_desk_button_container_widget_`.
+  const bool in_desk_animation = DesksController::Get()->animation();
+
   // There may be an existing animation in progress triggered by
   // `PerformFadeOutLayer()` above, which animates a widget to 0.f before
   // calling `OnSaveDeskButtonContainerFadedOut()` to hide the widget on
@@ -1946,7 +1950,7 @@
         ->StopAnimating();
     save_desk_button_container_widget_->Show();
     PerformFadeInLayer(save_desk_button_container_widget_->GetLayer(),
-                       /*animate=*/true);
+                       /*animate=*/!in_desk_animation);
   }
 
   // Enable/disable button and update tooltip.
@@ -1983,8 +1987,9 @@
   // with the first overview item, which has an invisible border of
   // `kWindowMargin` thickness.
   ScopedOverviewAnimationSettings settings(
-      visibility_changed ? OVERVIEW_ANIMATION_NONE
-                         : OVERVIEW_ANIMATION_LAYOUT_OVERVIEW_ITEMS_IN_OVERVIEW,
+      visibility_changed || in_desk_animation
+          ? OVERVIEW_ANIMATION_NONE
+          : OVERVIEW_ANIMATION_LAYOUT_OVERVIEW_ITEMS_IN_OVERVIEW,
       save_desk_button_container_widget_->GetNativeWindow());
   gfx::Point available_origin =
       gfx::ToRoundedPoint(first_overview_item_bounds.origin()) +
diff --git a/ash/wm/overview/overview_metrics.h b/ash/wm/overview/overview_metrics.h
index efd53c6d..d6244226 100644
--- a/ash/wm/overview/overview_metrics.h
+++ b/ash/wm/overview/overview_metrics.h
@@ -20,7 +20,8 @@
   k3FingerVerticalScroll,
   kDevTools,
   kTests,
-  kMaxValue = kTests,
+  kOverviewDeskSwitch,
+  kMaxValue = kOverviewDeskSwitch,
 };
 void RecordOverviewStartAction(OverviewStartAction type);
 
diff --git a/base/allocator/partition_alloc_support.cc b/base/allocator/partition_alloc_support.cc
index 016a475..a20bd2e 100644
--- a/base/allocator/partition_alloc_support.cc
+++ b/base/allocator/partition_alloc_support.cc
@@ -35,9 +35,9 @@
 #include "base/strings/string_piece.h"
 #include "base/strings/string_split.h"
 #include "base/strings/stringprintf.h"
+#include "base/task/single_thread_task_runner.h"
 #include "base/thread_annotations.h"
 #include "base/threading/platform_thread.h"
-#include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
 #include "base/timer/timer.h"
 #include "base/trace_event/base_tracing.h"
@@ -181,7 +181,7 @@
   instance.RunPeriodicPurge();
   TimeDelta delay =
       Microseconds(instance.GetPeriodicPurgeNextIntervalInMicroseconds());
-  ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
       FROM_HERE, BindOnce(RunThreadCachePeriodicPurge), delay);
 }
 
@@ -207,7 +207,7 @@
   auto& instance = ::partition_alloc::ThreadCacheRegistry::Instance();
   TimeDelta delay =
       Microseconds(instance.GetPeriodicPurgeNextIntervalInMicroseconds());
-  ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
       FROM_HERE, BindOnce(RunThreadCachePeriodicPurge), delay);
 }
 
diff --git a/base/android/java/src/org/chromium/base/JNIUtils.java b/base/android/java/src/org/chromium/base/JNIUtils.java
index 0e910d8..4b5f466 100644
--- a/base/android/java/src/org/chromium/base/JNIUtils.java
+++ b/base/android/java/src/org/chromium/base/JNIUtils.java
@@ -4,6 +4,7 @@
 
 package org.chromium.base;
 
+import android.content.pm.ApplicationInfo;
 import android.os.Build;
 
 import org.chromium.base.annotations.CalledByNative;
@@ -30,14 +31,30 @@
         if (!splitName.isEmpty()) {
             boolean isInstalled = BundleUtils.isIsolatedSplitInstalled(splitName);
             Log.i(TAG, "Init JNI Classloader for %s. isInstalled=%b", splitName, isInstalled);
-            if (isInstalled) {
-                return BundleUtils.getOrCreateSplitClassLoader(splitName);
-            } else {
-                assert (!BundleUtils.isBundle()
-                        || Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU)
+
+            if (!isInstalled && BundleUtils.isBundle()
+                    && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+                // Address race condition on global ApplicationInfo being updated.
+                // https://crbug.com/1395191
+                ApplicationInfo globalAppInfo =
+                        ContextUtils.getApplicationContext().getApplicationInfo();
+                ApplicationInfo freshAppInfo =
+                        PackageUtils.getApplicationPackageInfo(0).applicationInfo;
+                globalAppInfo.splitNames = freshAppInfo.splitNames;
+                globalAppInfo.splitSourceDirs = freshAppInfo.splitSourceDirs;
+                globalAppInfo.splitPublicSourceDirs = freshAppInfo.splitPublicSourceDirs;
+
+                isInstalled = BundleUtils.isIsolatedSplitInstalled(splitName);
+                Log.i(TAG, "Init JNI Classloader for %s. isInstalled=%b", splitName, isInstalled);
+                assert isInstalled
                     : "Should not hit splitcompat mode for Android T+. You might have a "
                       + "generate_jni() target that declares split_name, but includes "
                       + "a .java file from the base split (https://crbug.com/1394148).";
+            }
+
+            if (isInstalled) {
+                return BundleUtils.getOrCreateSplitClassLoader(splitName);
+            } else {
                 // Split was installed by PlayCore in "compat" mode, meaning that our base module's
                 // ClassLoader was patched to add the splits' dex file to it.
             }
diff --git a/base/android/java_handler_thread_unittest.cc b/base/android/java_handler_thread_unittest.cc
index 87647b2..f72a5864 100644
--- a/base/android/java_handler_thread_unittest.cc
+++ b/base/android/java_handler_thread_unittest.cc
@@ -64,7 +64,7 @@
 
 void PostNTasks(int posts_remaining) {
   if (posts_remaining > 1) {
-    ThreadTaskRunnerHandle::Get()->PostTask(
+    SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
         FROM_HERE, BindOnce(&PostNTasks, posts_remaining - 1));
   }
 }
@@ -136,7 +136,7 @@
   java_thread->task_runner()->PostTask(
       FROM_HERE, BindLambdaForTesting([&]() {
         sequence_manager->AddTaskObserver(&observer);
-        ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+        SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
             FROM_HERE, MakeExpectedNotRunClosure(FROM_HERE), Days(1));
         java_thread->StopSequenceManagerForTesting();
         PostNTasks(kNumPosts);
diff --git a/base/cancelable_callback_unittest.cc b/base/cancelable_callback_unittest.cc
index a54e2841..577a60d 100644
--- a/base/cancelable_callback_unittest.cc
+++ b/base/cancelable_callback_unittest.cc
@@ -13,7 +13,6 @@
 #include "base/run_loop.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/test/task_environment.h"
-#include "base/threading/thread_task_runner_handle.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace base {
@@ -174,12 +173,14 @@
   CancelableRepeatingClosure cancelable(
       base::BindRepeating(&Increment, base::Unretained(&count)));
 
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, cancelable.callback());
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE,
+                                                        cancelable.callback());
   RunLoop().RunUntilIdle();
 
   EXPECT_EQ(1, count);
 
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, cancelable.callback());
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE,
+                                                        cancelable.callback());
 
   // Cancel before running the tasks.
   cancelable.Cancel();
diff --git a/base/files/file_descriptor_watcher_posix.cc b/base/files/file_descriptor_watcher_posix.cc
index 717a438..4728998 100644
--- a/base/files/file_descriptor_watcher_posix.cc
+++ b/base/files/file_descriptor_watcher_posix.cc
@@ -16,7 +16,6 @@
 #include "base/task/current_thread.h"
 #include "base/task/sequenced_task_runner.h"
 #include "base/task/single_thread_task_runner.h"
-#include "base/threading/sequenced_task_runner_handle.h"
 #include "base/threading/thread_checker.h"
 #include "base/threading/thread_local.h"
 #include "base/threading/thread_restrictions.h"
@@ -63,7 +62,7 @@
   // Runs tasks on the sequence on which this was instantiated (i.e. the
   // sequence on which the callback must run).
   const scoped_refptr<SequencedTaskRunner> callback_task_runner_ =
-      SequencedTaskRunnerHandle::Get();
+      SequencedTaskRunner::GetCurrentDefault();
 
   // The Controller that created this Watcher. This WeakPtr is bound to the
   // |controller_| thread and can only be used by this Watcher to post back to
diff --git a/base/files/file_path_watcher_fsevents.cc b/base/files/file_path_watcher_fsevents.cc
index 514256e9..c4caff9 100644
--- a/base/files/file_path_watcher_fsevents.cc
+++ b/base/files/file_path_watcher_fsevents.cc
@@ -15,8 +15,8 @@
 #include "base/lazy_instance.h"
 #include "base/mac/scoped_cftyperef.h"
 #include "base/strings/stringprintf.h"
+#include "base/task/sequenced_task_runner.h"
 #include "base/threading/scoped_blocking_call.h"
-#include "base/threading/sequenced_task_runner_handle.h"
 
 namespace base {
 
@@ -90,7 +90,7 @@
   if (type != Type::kRecursive)
     return false;
 
-  set_task_runner(SequencedTaskRunnerHandle::Get());
+  set_task_runner(SequencedTaskRunner::GetCurrentDefault());
   callback_ = callback;
 
   FSEventStreamEventId start_event = FSEventsGetCurrentEventId();
diff --git a/base/files/file_path_watcher_inotify.cc b/base/files/file_path_watcher_inotify.cc
index f1a0590..89020b9 100644
--- a/base/files/file_path_watcher_inotify.cc
+++ b/base/files/file_path_watcher_inotify.cc
@@ -37,10 +37,10 @@
 #include "base/memory/weak_ptr.h"
 #include "base/posix/eintr_wrapper.h"
 #include "base/synchronization/lock.h"
+#include "base/task/sequenced_task_runner.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/threading/platform_thread.h"
 #include "base/threading/scoped_blocking_call.h"
-#include "base/threading/sequenced_task_runner_handle.h"
 #include "base/trace_event/base_tracing.h"
 #include "build/build_config.h"
 
@@ -558,7 +558,7 @@
                                 const FilePathWatcher::Callback& callback) {
   DCHECK(target_.empty());
 
-  set_task_runner(SequencedTaskRunnerHandle::Get());
+  set_task_runner(SequencedTaskRunner::GetCurrentDefault());
   callback_ = callback;
   target_ = path;
   type_ = type;
diff --git a/base/files/file_path_watcher_kqueue.cc b/base/files/file_path_watcher_kqueue.cc
index cc78499..98bc395 100644
--- a/base/files/file_path_watcher_kqueue.cc
+++ b/base/files/file_path_watcher_kqueue.cc
@@ -17,8 +17,8 @@
 #include "base/logging.h"
 #include "base/ranges/algorithm.h"
 #include "base/strings/stringprintf.h"
+#include "base/task/sequenced_task_runner.h"
 #include "base/threading/scoped_blocking_call.h"
-#include "base/threading/sequenced_task_runner_handle.h"
 
 // On some platforms these are not defined.
 #if !defined(EV_RECEIPT)
@@ -269,7 +269,7 @@
   callback_ = callback;
   target_ = path;
 
-  set_task_runner(SequencedTaskRunnerHandle::Get());
+  set_task_runner(SequencedTaskRunner::GetCurrentDefault());
 
   kqueue_ = kqueue();
   if (kqueue_ == -1) {
diff --git a/base/files/file_path_watcher_unittest.cc b/base/files/file_path_watcher_unittest.cc
index e10ef9e..84c4377a 100644
--- a/base/files/file_path_watcher_unittest.cc
+++ b/base/files/file_path_watcher_unittest.cc
@@ -25,7 +25,6 @@
 #include "base/test/test_file_util.h"
 #include "base/test/test_timeouts.h"
 #include "base/threading/thread.h"
-#include "base/threading/thread_task_runner_handle.h"
 #include "build/build_config.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -61,7 +60,8 @@
 class NotificationCollector
     : public base::RefCountedThreadSafe<NotificationCollector> {
  public:
-  NotificationCollector() : task_runner_(ThreadTaskRunnerHandle::Get()) {}
+  NotificationCollector()
+      : task_runner_(SingleThreadTaskRunner::GetCurrentDefault()) {}
 
   // Called from the file thread by the delegates.
   void OnChange(TestDelegate* delegate) {
@@ -218,7 +218,7 @@
     collector_->Reset(run_loop.QuitClosure());
 
     // Make sure we timeout if we don't get notified.
-    ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+    SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
         FROM_HERE, run_loop.QuitClosure(), timeout);
     run_loop.Run();
     return collector_->Success();
diff --git a/base/files/file_path_watcher_win.cc b/base/files/file_path_watcher_win.cc
index 703e284a..68700726 100644
--- a/base/files/file_path_watcher_win.cc
+++ b/base/files/file_path_watcher_win.cc
@@ -14,8 +14,8 @@
 #include "base/memory/ptr_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/strings/string_util.h"
+#include "base/task/sequenced_task_runner.h"
 #include "base/threading/scoped_blocking_call.h"
-#include "base/threading/sequenced_task_runner_handle.h"
 #include "base/time/time.h"
 #include "base/win/object_watcher.h"
 
@@ -93,7 +93,7 @@
                                 const FilePathWatcher::Callback& callback) {
   DCHECK(target_.value().empty());  // Can only watch one path.
 
-  set_task_runner(SequencedTaskRunnerHandle::Get());
+  set_task_runner(SequencedTaskRunner::GetCurrentDefault());
   callback_ = callback;
   target_ = path;
   type_ = type;
diff --git a/base/files/file_util.cc b/base/files/file_util.cc
index 8abd9fe..4828866 100644
--- a/base/files/file_util.cc
+++ b/base/files/file_util.cc
@@ -4,6 +4,7 @@
 
 #include "base/files/file_util.h"
 
+#include "base/task/sequenced_task_runner.h"
 #include "build/build_config.h"
 
 #if BUILDFLAG(IS_WIN)
@@ -31,7 +32,6 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/task/bind_post_task.h"
 #include "base/threading/scoped_blocking_call.h"
-#include "base/threading/sequenced_task_runner_handle.h"
 
 #if BUILDFLAG(IS_WIN)
 #include <windows.h>
@@ -141,7 +141,7 @@
   return BindOnce(&RunAndReply, BindOnce(&DeleteFile, path),
                   reply_callback.is_null()
                       ? std::move(reply_callback)
-                      : BindPostTask(SequencedTaskRunnerHandle::Get(),
+                      : BindPostTask(SequencedTaskRunner::GetCurrentDefault(),
                                      std::move(reply_callback)));
 }
 
@@ -151,7 +151,7 @@
   return BindOnce(&RunAndReply, BindOnce(&DeletePathRecursively, path),
                   reply_callback.is_null()
                       ? std::move(reply_callback)
-                      : BindPostTask(SequencedTaskRunnerHandle::Get(),
+                      : BindPostTask(SequencedTaskRunner::GetCurrentDefault(),
                                      std::move(reply_callback)));
 }
 
diff --git a/base/files/file_util_win.cc b/base/files/file_util_win.cc
index 607d474..4e56f7d 100644
--- a/base/files/file_util_win.cc
+++ b/base/files/file_util_win.cc
@@ -44,7 +44,6 @@
 #include "base/task/thread_pool.h"
 #include "base/threading/scoped_blocking_call.h"
 #include "base/threading/scoped_thread_priority.h"
-#include "base/threading/sequenced_task_runner_handle.h"
 #include "base/time/time.h"
 #include "base/win/scoped_handle.h"
 #include "base/win/windows_types.h"
@@ -352,7 +351,7 @@
     OnceCallback<void(bool)> reply_callback) {
   OnceCallback<void(bool)> bound_callback;
   if (!reply_callback.is_null()) {
-    bound_callback = BindPostTask(SequencedTaskRunnerHandle::Get(),
+    bound_callback = BindPostTask(SequencedTaskRunner::GetCurrentDefault(),
                                   std::move(reply_callback));
   }
   return BindOnce(&DeleteFileWithRetry, path, recursive, /*attempt=*/0,
diff --git a/base/files/important_file_writer.cc b/base/files/important_file_writer.cc
index 90b78e03..1d53336 100644
--- a/base/files/important_file_writer.cc
+++ b/base/files/important_file_writer.cc
@@ -31,7 +31,6 @@
 #include "base/task/task_runner.h"
 #include "base/threading/platform_thread.h"
 #include "base/threading/scoped_thread_priority.h"
-#include "base/threading/sequenced_task_runner_handle.h"
 #include "base/threading/thread.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
@@ -95,8 +94,8 @@
   constexpr TimeDelta kDeleteFileRetryDelay = Milliseconds(250);
 
   if (!DeleteFile(tmp_file_path) && ++attempt < kMaxDeleteAttempts &&
-      SequencedTaskRunnerHandle::IsSet()) {
-    SequencedTaskRunnerHandle::Get()->PostDelayedTask(
+      SequencedTaskRunner::HasCurrentDefault()) {
+    SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
         FROM_HERE,
         BindOnce(&DeleteTmpFileWithRetry, base::File(), tmp_file_path, attempt),
         kDeleteFileRetryDelay);
diff --git a/base/files/important_file_writer_cleaner.cc b/base/files/important_file_writer_cleaner.cc
index 2a2b4cc..66841cef 100644
--- a/base/files/important_file_writer_cleaner.cc
+++ b/base/files/important_file_writer_cleaner.cc
@@ -14,9 +14,9 @@
 #include "base/files/file_util.h"
 #include "base/no_destructor.h"
 #include "base/process/process.h"
+#include "base/task/sequenced_task_runner.h"
 #include "base/task/task_traits.h"
 #include "base/task/thread_pool.h"
-#include "base/threading/sequenced_task_runner_handle.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
 
@@ -68,8 +68,9 @@
 void ImportantFileWriterCleaner::Initialize() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   AutoLock scoped_lock(task_runner_lock_);
-  DCHECK(!task_runner_ || task_runner_ == SequencedTaskRunnerHandle::Get());
-  task_runner_ = SequencedTaskRunnerHandle::Get();
+  DCHECK(!task_runner_ ||
+         task_runner_ == SequencedTaskRunner::GetCurrentDefault());
+  task_runner_ = SequencedTaskRunner::GetCurrentDefault();
 }
 
 void ImportantFileWriterCleaner::Start() {
diff --git a/base/files/important_file_writer_unittest.cc b/base/files/important_file_writer_unittest.cc
index 04bd6c67..605cbd3 100644
--- a/base/files/important_file_writer_unittest.cc
+++ b/base/files/important_file_writer_unittest.cc
@@ -19,7 +19,6 @@
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/task_environment.h"
 #include "base/threading/thread.h"
-#include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
 #include "base/timer/mock_timer.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -159,7 +158,8 @@
 };
 
 TEST_F(ImportantFileWriterTest, Basic) {
-  ImportantFileWriter writer(file_, ThreadTaskRunnerHandle::Get());
+  ImportantFileWriter writer(file_,
+                             SingleThreadTaskRunner::GetCurrentDefault());
   EXPECT_FALSE(PathExists(writer.path()));
   EXPECT_EQ(NOT_CALLED, write_callback_observer_.GetAndResetObservationState());
   writer.WriteNow(std::make_unique<std::string>("foo"));
@@ -171,7 +171,8 @@
 }
 
 TEST_F(ImportantFileWriterTest, WriteWithObserver) {
-  ImportantFileWriter writer(file_, ThreadTaskRunnerHandle::Get());
+  ImportantFileWriter writer(file_,
+                             SingleThreadTaskRunner::GetCurrentDefault());
   EXPECT_FALSE(PathExists(writer.path()));
   EXPECT_EQ(NOT_CALLED, write_callback_observer_.GetAndResetObservationState());
 
@@ -211,7 +212,7 @@
   // Use an invalid file path (relative paths are invalid) to get a
   // FILE_ERROR_ACCESS_DENIED error when trying to write the file.
   ImportantFileWriter writer(FilePath().AppendASCII("bad/../path"),
-                             ThreadTaskRunnerHandle::Get());
+                             SingleThreadTaskRunner::GetCurrentDefault());
   EXPECT_FALSE(PathExists(writer.path()));
   EXPECT_EQ(NOT_CALLED, write_callback_observer_.GetAndResetObservationState());
   write_callback_observer_.ObserveNextWriteCallbacks(&writer);
@@ -260,7 +261,7 @@
 TEST_F(ImportantFileWriterTest, ScheduleWrite) {
   constexpr TimeDelta kCommitInterval = Seconds(12345);
   MockOneShotTimer timer;
-  ImportantFileWriter writer(file_, ThreadTaskRunnerHandle::Get(),
+  ImportantFileWriter writer(file_, SingleThreadTaskRunner::GetCurrentDefault(),
                              kCommitInterval);
   EXPECT_EQ(0u, writer.previous_data_size());
   writer.SetTimerForTesting(&timer);
@@ -281,7 +282,8 @@
 
 TEST_F(ImportantFileWriterTest, DoScheduledWrite) {
   MockOneShotTimer timer;
-  ImportantFileWriter writer(file_, ThreadTaskRunnerHandle::Get());
+  ImportantFileWriter writer(file_,
+                             SingleThreadTaskRunner::GetCurrentDefault());
   writer.SetTimerForTesting(&timer);
   EXPECT_FALSE(writer.HasPendingWrite());
   DataSerializer serializer("foo");
@@ -296,7 +298,8 @@
 
 TEST_F(ImportantFileWriterTest, BatchingWrites) {
   MockOneShotTimer timer;
-  ImportantFileWriter writer(file_, ThreadTaskRunnerHandle::Get());
+  ImportantFileWriter writer(file_,
+                             SingleThreadTaskRunner::GetCurrentDefault());
   writer.SetTimerForTesting(&timer);
   DataSerializer foo("foo"), bar("bar"), baz("baz");
   writer.ScheduleWrite(&foo);
@@ -311,7 +314,8 @@
 
 TEST_F(ImportantFileWriterTest, ScheduleWrite_FailToSerialize) {
   MockOneShotTimer timer;
-  ImportantFileWriter writer(file_, ThreadTaskRunnerHandle::Get());
+  ImportantFileWriter writer(file_,
+                             SingleThreadTaskRunner::GetCurrentDefault());
   writer.SetTimerForTesting(&timer);
   EXPECT_FALSE(writer.HasPendingWrite());
   FailingDataSerializer serializer;
@@ -326,7 +330,8 @@
 
 TEST_F(ImportantFileWriterTest, ScheduleWrite_WriteNow) {
   MockOneShotTimer timer;
-  ImportantFileWriter writer(file_, ThreadTaskRunnerHandle::Get());
+  ImportantFileWriter writer(file_,
+                             SingleThreadTaskRunner::GetCurrentDefault());
   writer.SetTimerForTesting(&timer);
   EXPECT_FALSE(writer.HasPendingWrite());
   DataSerializer serializer("foo");
@@ -344,7 +349,8 @@
 TEST_F(ImportantFileWriterTest, DoScheduledWrite_FailToSerialize) {
   base::HistogramTester histogram_tester;
   MockOneShotTimer timer;
-  ImportantFileWriter writer(file_, ThreadTaskRunnerHandle::Get());
+  ImportantFileWriter writer(file_,
+                             SingleThreadTaskRunner::GetCurrentDefault());
   writer.SetTimerForTesting(&timer);
   EXPECT_FALSE(writer.HasPendingWrite());
   FailingDataSerializer serializer;
@@ -447,7 +453,8 @@
 // Verify that a UMA metric for the serialization duration is recorded.
 TEST_F(ImportantFileWriterTest, SerializationDuration) {
   base::HistogramTester histogram_tester;
-  ImportantFileWriter writer(file_, ThreadTaskRunnerHandle::Get());
+  ImportantFileWriter writer(file_,
+                             SingleThreadTaskRunner::GetCurrentDefault());
   DataSerializer serializer("foo");
   writer.ScheduleWrite(&serializer);
   writer.DoScheduledWrite();
@@ -460,7 +467,8 @@
 // ImportantFileWriter has a custom histogram suffix.
 TEST_F(ImportantFileWriterTest, SerializationDurationWithCustomSuffix) {
   base::HistogramTester histogram_tester;
-  ImportantFileWriter writer(file_, ThreadTaskRunnerHandle::Get(), "Foo");
+  ImportantFileWriter writer(file_, SingleThreadTaskRunner::GetCurrentDefault(),
+                             "Foo");
   DataSerializer serializer("foo");
   writer.ScheduleWrite(&serializer);
   writer.DoScheduledWrite();
diff --git a/base/ios/weak_nsobject_unittest.mm b/base/ios/weak_nsobject_unittest.mm
index 2c53308..98dafbb 100644
--- a/base/ios/weak_nsobject_unittest.mm
+++ b/base/ios/weak_nsobject_unittest.mm
@@ -10,7 +10,6 @@
 #include "base/task/single_thread_task_runner.h"
 #include "base/test/task_environment.h"
 #include "base/threading/thread.h"
-#include "base/threading/thread_task_runner_handle.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace base {
@@ -128,7 +127,8 @@
   scoped_nsobject<NSMutableData> data([[NSMutableData alloc] init]);
   WeakNSObject<NSMutableData> weak(data);
 
-  scoped_refptr<SingleThreadTaskRunner> runner = ThreadTaskRunnerHandle::Get();
+  scoped_refptr<SingleThreadTaskRunner> runner =
+      SingleThreadTaskRunner::GetCurrentDefault();
   other_thread.task_runner()->PostTask(
       FROM_HERE, BindOnce(&CopyWeakNSObjectAndPost, weak, runner));
   other_thread.Stop();
diff --git a/base/memory/madv_free_discardable_memory_allocator_posix.cc b/base/memory/madv_free_discardable_memory_allocator_posix.cc
index f3f9346..977fbe3d 100644
--- a/base/memory/madv_free_discardable_memory_allocator_posix.cc
+++ b/base/memory/madv_free_discardable_memory_allocator_posix.cc
@@ -8,7 +8,7 @@
 #include "base/memory/madv_free_discardable_memory_allocator_posix.h"
 #include "base/process/process_metrics.h"
 #include "base/strings/string_number_conversions.h"
-#include "base/threading/thread_task_runner_handle.h"
+#include "base/task/single_thread_task_runner.h"
 #include "base/tracing_buildflags.h"
 
 #if BUILDFLAG(ENABLE_BASE_TRACING)
@@ -22,10 +22,10 @@
 #if BUILDFLAG(ENABLE_BASE_TRACING)
   // Don't register dump provider if ThreadTaskRunnerHandle is not set, such as
   // in tests and Android Webview.
-  if (base::ThreadTaskRunnerHandle::IsSet()) {
+  if (base::SingleThreadTaskRunner::HasCurrentDefault()) {
     trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider(
         this, "MadvFreeDiscardableMemoryAllocator",
-        ThreadTaskRunnerHandle::Get());
+        SingleThreadTaskRunner::GetCurrentDefault());
   }
 #endif  // BUILDFLAG(ENABLE_BASE_TRACING)
 }
diff --git a/base/memory/memory_pressure_listener.cc b/base/memory/memory_pressure_listener.cc
index 4eba50d..0df91b0 100644
--- a/base/memory/memory_pressure_listener.cc
+++ b/base/memory/memory_pressure_listener.cc
@@ -8,7 +8,7 @@
 
 #include "base/observer_list.h"
 #include "base/observer_list_threadsafe.h"
-#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/task/sequenced_task_runner.h"
 #include "base/trace_event/base_tracing.h"
 #include "base/tracing_buildflags.h"
 
@@ -30,7 +30,7 @@
     // TODO(crbug.com/1063868): DCHECK instead of silently failing when a
     // MemoryPressureListener is created in a non-sequenced context. Tests will
     // need to be adjusted for that to work.
-    if (SequencedTaskRunnerHandle::IsSet()) {
+    if (SequencedTaskRunner::HasCurrentDefault()) {
       async_observers_->AddObserver(listener);
     }
 
diff --git a/base/message_loop/message_pump_glib_unittest.cc b/base/message_loop/message_pump_glib_unittest.cc
index 4f01fa9..7dc69083 100644
--- a/base/message_loop/message_pump_glib_unittest.cc
+++ b/base/message_loop/message_pump_glib_unittest.cc
@@ -28,7 +28,6 @@
 #include "base/task/single_thread_task_runner.h"
 #include "base/test/task_environment.h"
 #include "base/threading/thread.h"
-#include "base/threading/thread_task_runner_handle.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace base {
@@ -161,7 +160,8 @@
 
 // Posts a task on the current message loop.
 void PostMessageLoopTask(const Location& from_here, OnceClosure task) {
-  ThreadTaskRunnerHandle::Get()->PostTask(from_here, std::move(task));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(from_here,
+                                                        std::move(task));
 }
 
 // Test fixture.
@@ -239,14 +239,14 @@
   // Tests that we process tasks while waiting for new events.
   // The event queue is empty at first.
   for (int i = 0; i < 10; ++i) {
-    ThreadTaskRunnerHandle::Get()->PostTask(
+    SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
         FROM_HERE, BindOnce(&IncrementInt, &task_count));
   }
   // After all the previous tasks have executed, enqueue an event that will
   // quit.
   {
     RunLoop run_loop;
-    ThreadTaskRunnerHandle::Get()->PostTask(
+    SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
         FROM_HERE, BindOnce(&EventInjector::AddEvent, Unretained(injector()), 0,
                             run_loop.QuitClosure()));
     run_loop.Run();
@@ -258,7 +258,7 @@
   injector()->Reset();
   task_count = 0;
   for (int i = 0; i < 10; ++i) {
-    ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+    SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
         FROM_HERE, BindOnce(&IncrementInt, &task_count), Milliseconds(10 * i));
   }
   // After all the previous tasks have executed, enqueue an event that will
@@ -267,7 +267,7 @@
   // That is verified in message_loop_unittest.cc.
   {
     RunLoop run_loop;
-    ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+    SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
         FROM_HERE,
         BindOnce(&EventInjector::AddEvent, Unretained(injector()), 0,
                  run_loop.QuitClosure()),
@@ -321,7 +321,7 @@
     if (task_count_ == 0 && event_count_ == 0) {
       std::move(done_closure_).Run();
     } else {
-      ThreadTaskRunnerHandle::Get()->PostTask(
+      SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
           FROM_HERE, BindOnce(&ConcurrentHelper::FromTask, this));
     }
   }
@@ -373,9 +373,9 @@
   injector()->AddEventAsTask(0, BindOnce(&ConcurrentHelper::FromEvent, helper));
 
   // Similarly post 2 tasks.
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, BindOnce(&ConcurrentHelper::FromTask, helper));
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, BindOnce(&ConcurrentHelper::FromTask, helper));
 
   run_loop.Run();
@@ -393,8 +393,8 @@
   injector->AddEvent(0, std::move(on_drained));
 
   // Post a couple of dummy tasks
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, DoNothing());
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, DoNothing());
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE, DoNothing());
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE, DoNothing());
 
   // Drain the events
   while (g_main_context_pending(nullptr)) {
@@ -407,7 +407,7 @@
 TEST_F(MessagePumpGLibTest, TestDrainingGLib) {
   // Tests that draining events using GLib works.
   RunLoop run_loop;
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, BindOnce(&AddEventsAndDrainGLib, Unretained(injector()),
                           run_loop.QuitClosure()));
   run_loop.Run();
@@ -458,17 +458,17 @@
   injector->AddDummyEvent(0);
   injector->AddDummyEvent(0);
   // Post a couple of dummy tasks
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          BindOnce(&IncrementInt, &task_count));
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          BindOnce(&IncrementInt, &task_count));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, BindOnce(&IncrementInt, &task_count));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, BindOnce(&IncrementInt, &task_count));
   // Delayed events
   injector->AddDummyEvent(10);
   injector->AddDummyEvent(10);
   // Delayed work
-  ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
       FROM_HERE, BindOnce(&IncrementInt, &task_count), Milliseconds(30));
-  ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
       FROM_HERE, BindOnce(&GLibLoopRunner::Quit, runner), Milliseconds(40));
 
   // Run a nested, straight GLib message loop.
@@ -490,17 +490,17 @@
   injector->AddDummyEvent(0);
   injector->AddDummyEvent(0);
   // Post a couple of dummy tasks
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          BindOnce(&IncrementInt, &task_count));
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          BindOnce(&IncrementInt, &task_count));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, BindOnce(&IncrementInt, &task_count));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, BindOnce(&IncrementInt, &task_count));
   // Delayed events
   injector->AddDummyEvent(10);
   injector->AddDummyEvent(10);
   // Delayed work
-  ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
       FROM_HERE, BindOnce(&IncrementInt, &task_count), Milliseconds(30));
-  ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
       FROM_HERE, BindOnce(&GLibLoopRunner::Quit, runner), Milliseconds(40));
 
   // Run a nested, straight Gtk message loop.
@@ -522,7 +522,7 @@
   // Note that in this case we don't make strong guarantees about niceness
   // between events and posted tasks.
   RunLoop run_loop;
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, BindOnce(&TestGLibLoopInternal, Unretained(injector()),
                           run_loop.QuitClosure()));
   run_loop.Run();
@@ -534,7 +534,7 @@
   // Note that in this case we don't make strong guarantees about niceness
   // between events and posted tasks.
   RunLoop run_loop;
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, BindOnce(&TestGtkLoopInternal, Unretained(injector()),
                           run_loop.QuitClosure()));
   run_loop.Run();
@@ -637,7 +637,8 @@
   std::move(quit_closure).Run();
 
   RunLoop runloop(RunLoop::Type::kNestableTasksAllowed);
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, runloop.QuitClosure());
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE,
+                                                        runloop.QuitClosure());
   runloop.Run();
 }
 
@@ -648,7 +649,7 @@
 
   void OnFileCanReadWithoutBlocking(int /* fd */) override {
     RunLoop runloop;
-    ThreadTaskRunnerHandle::Get()->PostTask(
+    SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
         FROM_HERE, BindOnce(&QuitMessageLoopAndStart, runloop.QuitClosure()));
     runloop.Run();
   }
@@ -748,7 +749,7 @@
                           std::move(write_fd_task), io_runner()));
 
   // Queue |event| to signal on |CurrentUIThread::Get()|.
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, BindOnce(&WaitableEvent::Signal, Unretained(&event)));
 
   // Now run the MessageLoop.
diff --git a/base/message_loop/message_pump_kqueue_unittest.cc b/base/message_loop/message_pump_kqueue_unittest.cc
index 31a3a7b..96a2f2f 100644
--- a/base/message_loop/message_pump_kqueue_unittest.cc
+++ b/base/message_loop/message_pump_kqueue_unittest.cc
@@ -16,7 +16,6 @@
 #include "base/run_loop.h"
 #include "base/task/single_thread_task_executor.h"
 #include "base/task/single_thread_task_runner.h"
-#include "base/threading/thread_task_runner_handle.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace base {
@@ -89,7 +88,7 @@
   PortWatcher watcher(run_loop.QuitClosure());
   MessagePumpKqueue::MachPortWatchController controller(FROM_HERE);
 
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, BindOnce(
                      [](mach_port_t port, mach_msg_id_t msgid, RunLoop* loop) {
                        mach_msg_return_t kr = SendEmptyMessage(port, msgid);
@@ -120,7 +119,7 @@
 
   pump()->WatchMachReceivePort(port.get(), &controller, &watcher);
 
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE,
       BindOnce(
           [](MessagePumpKqueue::MachPortWatchController* controller) {
@@ -128,7 +127,7 @@
           },
           Unretained(&controller)));
 
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, BindOnce(
                      [](mach_port_t port) {
                        EXPECT_EQ(KERN_SUCCESS, SendEmptyMessage(port, 100));
@@ -186,7 +185,7 @@
   pump()->WatchMachReceivePort(port2.get(), &controller2, &watcher2);
 
   // Start ping-ponging with by sending the first message to port1.
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, BindOnce(
                      [](mach_port_t port1) {
                        ASSERT_EQ(KERN_SUCCESS,
diff --git a/base/message_loop/message_pump_libevent_unittest.cc b/base/message_loop/message_pump_libevent_unittest.cc
index 0f089e4a..c8297122 100644
--- a/base/message_loop/message_pump_libevent_unittest.cc
+++ b/base/message_loop/message_pump_libevent_unittest.cc
@@ -22,13 +22,12 @@
 #include "base/run_loop.h"
 #include "base/synchronization/waitable_event.h"
 #include "base/synchronization/waitable_event_watcher.h"
+#include "base/task/sequenced_task_runner.h"
 #include "base/task/single_thread_task_executor.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/test/gtest_util.h"
 #include "base/test/task_environment.h"
-#include "base/threading/sequenced_task_runner_handle.h"
 #include "base/threading/thread.h"
-#include "base/threading/thread_task_runner_handle.h"
 #include "build/build_config.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/libevent/event.h"
@@ -191,7 +190,8 @@
   std::move(quit_closure).Run();
 
   RunLoop runloop(RunLoop::Type::kNestableTasksAllowed);
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, runloop.QuitClosure());
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE,
+                                                        runloop.QuitClosure());
   runloop.Run();
 }
 
@@ -202,7 +202,7 @@
 
   void OnFileCanReadWithoutBlocking(int /* fd */) override {
     RunLoop runloop;
-    ThreadTaskRunnerHandle::Get()->PostTask(
+    SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
         FROM_HERE, BindOnce(&QuitMessageLoopAndStart, runloop.QuitClosure()));
     runloop.Run();
   }
@@ -231,7 +231,8 @@
 
   void OnFileCanReadWithoutBlocking(int /* fd */) override {
     // Post a fatal closure to the MessageLoop before we quit it.
-    ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, BindOnce(&FatalClosure));
+    SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+        FROM_HERE, BindOnce(&FatalClosure));
 
     if (quit_closure_)
       std::move(quit_closure_).Run();
@@ -278,7 +279,7 @@
                           std::move(write_fd_task), io_runner()));
 
   // Queue |event| to signal on |sequence_manager|.
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, BindOnce(&WaitableEvent::Signal, Unretained(&event)));
 
   // Now run the MessageLoop.
diff --git a/base/message_loop/message_pump_mac_unittest.mm b/base/message_loop/message_pump_mac_unittest.mm
index 634ec36..d92a172f 100644
--- a/base/message_loop/message_pump_mac_unittest.mm
+++ b/base/message_loop/message_pump_mac_unittest.mm
@@ -9,9 +9,9 @@
 #include "base/mac/scoped_cftyperef.h"
 #import "base/mac/scoped_nsobject.h"
 #include "base/task/current_thread.h"
+#include "base/task/single_thread_task_runner.h"
 #include "base/test/bind.h"
 #include "base/test/task_environment.h"
-#include "base/threading/thread_task_runner_handle.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 @interface TestModalAlertCloser : NSObject
@@ -39,7 +39,8 @@
   // Since this task is "ours" rather than a system task, allow nesting.
   CurrentThread::ScopedAllowApplicationTasksInNativeNestedLoop allow;
   CancelableOnceClosure cancelable(std::move(task));
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, cancelable.callback());
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE,
+                                                        cancelable.callback());
   while (CFRunLoopRunInMode(mode, 0, true) == kCFRunLoopRunHandledSource)
     ;
 }
@@ -55,13 +56,13 @@
   CFRunLoopMode kPrivate = CFSTR("NSUnhighlightMenuRunLoopMode");
 
   // Work is seen when running in the default mode.
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE,
       BindOnce(&RunTaskInMode, kRegular, MakeExpectedRunClosure(FROM_HERE)));
   EXPECT_NO_FATAL_FAILURE(RunLoop().RunUntilIdle());
 
   // But not seen when running in a private mode.
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE,
       BindOnce(&RunTaskInMode, kPrivate, MakeExpectedNotRunClosure(FROM_HERE)));
   EXPECT_NO_FATAL_FAILURE(RunLoop().RunUntilIdle());
@@ -69,26 +70,26 @@
   {
     ScopedPumpMessagesInPrivateModes allow_private;
     // Now the work should be seen.
-    ThreadTaskRunnerHandle::Get()->PostTask(
+    SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
         FROM_HERE,
         BindOnce(&RunTaskInMode, kPrivate, MakeExpectedRunClosure(FROM_HERE)));
     EXPECT_NO_FATAL_FAILURE(RunLoop().RunUntilIdle());
 
     // The regular mode should also work the same.
-    ThreadTaskRunnerHandle::Get()->PostTask(
+    SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
         FROM_HERE,
         BindOnce(&RunTaskInMode, kRegular, MakeExpectedRunClosure(FROM_HERE)));
     EXPECT_NO_FATAL_FAILURE(RunLoop().RunUntilIdle());
   }
 
   // And now the scoper is out of scope, private modes should no longer see it.
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE,
       BindOnce(&RunTaskInMode, kPrivate, MakeExpectedNotRunClosure(FROM_HERE)));
   EXPECT_NO_FATAL_FAILURE(RunLoop().RunUntilIdle());
 
   // Only regular modes see it.
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE,
       BindOnce(&RunTaskInMode, kRegular, MakeExpectedRunClosure(FROM_HERE)));
   EXPECT_NO_FATAL_FAILURE(RunLoop().RunUntilIdle());
@@ -130,17 +131,17 @@
   // Check that quitting the run loop while a modal window is shown applies to
   // |run_loop| rather than the internal NSApplication modal run loop.
   RunLoop run_loop;
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, base::BindLambdaForTesting([&] {
         CurrentThread::ScopedAllowApplicationTasksInNativeNestedLoop allow;
         ScopedPumpMessagesInPrivateModes pump_private;
         [NSApp runModalForWindow:window];
       }));
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          base::BindLambdaForTesting([&] {
-                                            [NSApp stopModal];
-                                            run_loop.Quit();
-                                          }));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, base::BindLambdaForTesting([&] {
+        [NSApp stopModal];
+        run_loop.Quit();
+      }));
 
   EXPECT_NO_FATAL_FAILURE(run_loop.Run());
 }
diff --git a/base/metrics/statistics_recorder.cc b/base/metrics/statistics_recorder.cc
index a298291..2bd5bea 100644
--- a/base/metrics/statistics_recorder.cc
+++ b/base/metrics/statistics_recorder.cc
@@ -39,6 +39,10 @@
     LAZY_INSTANCE_INITIALIZER;
 
 // static
+StatisticsRecorder::SnapshotTransactionId
+    StatisticsRecorder::last_snapshot_transaction_id_ = 0;
+
+// static
 StatisticsRecorder* StatisticsRecorder::top_ = nullptr;
 
 // static
@@ -217,7 +221,7 @@
 }
 
 // static
-void StatisticsRecorder::PrepareDeltas(
+StatisticsRecorder::SnapshotTransactionId StatisticsRecorder::PrepareDeltas(
     bool include_persistent,
     HistogramBase::Flags flags_to_set,
     HistogramBase::Flags required_flags,
@@ -226,16 +230,26 @@
   base::AutoLock lock(snapshot_lock_.Get());
   snapshot_manager->PrepareDeltas(std::move(histograms), flags_to_set,
                                   required_flags);
+  return ++last_snapshot_transaction_id_;
 }
 
 // static
-void StatisticsRecorder::SnapshotUnloggedSamples(
+StatisticsRecorder::SnapshotTransactionId
+StatisticsRecorder::SnapshotUnloggedSamples(
     HistogramBase::Flags required_flags,
     HistogramSnapshotManager* snapshot_manager) {
   Histograms histograms = Sort(GetHistograms());
   base::AutoLock lock(snapshot_lock_.Get());
   snapshot_manager->SnapshotUnloggedSamples(std::move(histograms),
                                             required_flags);
+  return ++last_snapshot_transaction_id_;
+}
+
+// static
+StatisticsRecorder::SnapshotTransactionId
+StatisticsRecorder::GetLastSnapshotTransactionId() {
+  base::AutoLock lock(snapshot_lock_.Get());
+  return last_snapshot_transaction_id_;
 }
 
 // static
diff --git a/base/metrics/statistics_recorder.h b/base/metrics/statistics_recorder.h
index 436f7031..8a6b324 100644
--- a/base/metrics/statistics_recorder.h
+++ b/base/metrics/statistics_recorder.h
@@ -99,6 +99,7 @@
   };
 
   typedef std::vector<HistogramBase*> Histograms;
+  typedef size_t SnapshotTransactionId;
 
   StatisticsRecorder(const StatisticsRecorder&) = delete;
   StatisticsRecorder& operator=(const StatisticsRecorder&) = delete;
@@ -188,22 +189,31 @@
   // histograms should be recorded, use |Histogram::kNoFlags| as the required
   // flag. This is logically equivalent to calling SnapshotUnloggedSamples()
   // followed by HistogramSnapshotManager::MarkUnloggedSamplesAsLogged() on
-  // |snapshot_manager|.
-  static void PrepareDeltas(bool include_persistent,
-                            HistogramBase::Flags flags_to_set,
-                            HistogramBase::Flags required_flags,
-                            HistogramSnapshotManager* snapshot_manager)
+  // |snapshot_manager|. Returns the snapshot transaction ID associated with
+  // this operation. Thread-safe.
+  static SnapshotTransactionId PrepareDeltas(
+      bool include_persistent,
+      HistogramBase::Flags flags_to_set,
+      HistogramBase::Flags required_flags,
+      HistogramSnapshotManager* snapshot_manager)
       LOCKS_EXCLUDED(snapshot_lock_.Pointer());
 
   // Same as PrepareDeltas() above, but the samples are not marked as logged.
   // This includes persistent histograms, and no flags will be set. A call to
   // HistogramSnapshotManager::MarkUnloggedSamplesAsLogged() on the passed
-  // |snapshot_manager| should be made to mark them as logged.
-  static void SnapshotUnloggedSamples(
+  // |snapshot_manager| should be made to mark them as logged. Returns the
+  // snapshot transaction ID associated with this operation. Thread-safe.
+  static SnapshotTransactionId SnapshotUnloggedSamples(
       HistogramBase::Flags required_flags,
       HistogramSnapshotManager* snapshot_manager)
       LOCKS_EXCLUDED(snapshot_lock_.Pointer());
 
+  // Returns the transaction ID of the last snapshot performed (either through
+  // PrepareDeltas() or SnapshotUnloggedSamples()). Returns 0 if a snapshot was
+  // never taken so far. Thread-safe.
+  static SnapshotTransactionId GetLastSnapshotTransactionId()
+      LOCKS_EXCLUDED(snapshot_lock_.Pointer());
+
   // Retrieves and runs the list of callbacks for the histogram referred to by
   // |histogram_name|, if any.
   //
@@ -370,6 +380,12 @@
   // Global lock for internal synchronization of histogram snapshots.
   static LazyInstance<base::Lock>::Leaky snapshot_lock_;
 
+  // A strictly increasing number that is incremented every time a snapshot is
+  // taken (by either calling SnapshotUnloggedSamples() or PrepareDeltas()).
+  // This represents the transaction ID of the last snapshot taken.
+  static SnapshotTransactionId last_snapshot_transaction_id_
+      GUARDED_BY(snapshot_lock_.Get());
+
   // Current global recorder. This recorder is used by static methods. When a
   // new global recorder is created by CreateTemporaryForTesting(), then the
   // previous global recorder is referenced by top_->previous_.
diff --git a/base/observer_list_threadsafe.h b/base/observer_list_threadsafe.h
index 5dcdfaa..a698340b 100644
--- a/base/observer_list_threadsafe.h
+++ b/base/observer_list_threadsafe.h
@@ -21,7 +21,6 @@
 #include "base/observer_list.h"
 #include "base/synchronization/lock.h"
 #include "base/task/sequenced_task_runner.h"
-#include "base/threading/sequenced_task_runner_handle.h"
 #include "base/threading/thread_local.h"
 #include "build/build_config.h"
 
@@ -117,7 +116,7 @@
 
   // Adds |observer| to the list. |observer| must not already be in the list.
   AddObserverResult AddObserver(ObserverType* observer) {
-    DCHECK(SequencedTaskRunnerHandle::IsSet())
+    DCHECK(SequencedTaskRunner::HasCurrentDefault())
         << "An observer can only be registered when SequencedTaskRunnerHandle "
            "is set. If this is in a unit test, you're likely merely missing a "
            "base::test::(SingleThread)TaskEnvironment in your fixture. "
@@ -132,7 +131,7 @@
     // Add |observer| to the list of observers.
     DCHECK(!Contains(observers_, observer));
     const scoped_refptr<SequencedTaskRunner> task_runner =
-        SequencedTaskRunnerHandle::Get();
+        SequencedTaskRunner::GetCurrentDefault();
     // Each observer gets a unique identifier. These unique identifiers are used
     // to avoid execution of pending posted-tasks over removed or released
     // observers.
diff --git a/base/observer_list_threadsafe_unittest.cc b/base/observer_list_threadsafe_unittest.cc
index dc1a161c..e573e28 100644
--- a/base/observer_list_threadsafe_unittest.cc
+++ b/base/observer_list_threadsafe_unittest.cc
@@ -23,7 +23,6 @@
 #include "base/test/task_environment.h"
 #include "base/threading/platform_thread.h"
 #include "base/threading/thread_restrictions.h"
-#include "base/threading/thread_task_runner_handle.h"
 #include "build/build_config.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -108,7 +107,7 @@
       list_->Notify(FROM_HERE, &Foo::Observe, 10);
     }
 
-    ThreadTaskRunnerHandle::Get()->PostTask(
+    SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
         FROM_HERE,
         base::BindOnce(&AddRemoveThread::AddTask, weak_factory_.GetWeakPtr()));
   }
diff --git a/base/one_shot_event.cc b/base/one_shot_event.cc
index b1ff09c..132a079 100644
--- a/base/one_shot_event.cc
+++ b/base/one_shot_event.cc
@@ -11,7 +11,6 @@
 #include "base/location.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/task/task_runner.h"
-#include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
 
 namespace base {
@@ -56,7 +55,8 @@
 void OneShotEvent::PostDelayed(const Location& from_here,
                                OnceClosure task,
                                const TimeDelta& delay) const {
-  PostImpl(from_here, std::move(task), ThreadTaskRunnerHandle::Get(), delay);
+  PostImpl(from_here, std::move(task),
+           SingleThreadTaskRunner::GetCurrentDefault(), delay);
 }
 
 void OneShotEvent::Signal() {
diff --git a/base/one_shot_event.h b/base/one_shot_event.h
index 18f1d301..43330d4 100644
--- a/base/one_shot_event.h
+++ b/base/one_shot_event.h
@@ -11,8 +11,8 @@
 #include "base/callback_forward.h"
 #include "base/check.h"
 #include "base/memory/weak_ptr.h"
+#include "base/task/single_thread_task_runner.h"
 #include "base/threading/thread_checker.h"
-#include "base/threading/thread_task_runner_handle.h"
 
 namespace base {
 
@@ -72,7 +72,7 @@
   void Post(const Location& from_here,
             OnceClosure task,
             scoped_refptr<SingleThreadTaskRunner> runner =
-                ThreadTaskRunnerHandle::Get()) const;
+                SingleThreadTaskRunner::GetCurrentDefault()) const;
   void PostDelayed(const Location& from_here,
                    OnceClosure task,
                    const TimeDelta& delay) const;
diff --git a/base/power_monitor/battery_level_provider.cc b/base/power_monitor/battery_level_provider.cc
index d9ef861..8b9ccd01 100644
--- a/base/power_monitor/battery_level_provider.cc
+++ b/base/power_monitor/battery_level_provider.cc
@@ -41,8 +41,6 @@
 #if BUILDFLAG(IS_WIN)
     state.battery_discharge_granularity =
         battery_details.front().battery_discharge_granularity;
-    state.max_battery_discharge_granularity =
-        battery_details.front().max_battery_discharge_granularity;
 #endif  // BUILDFLAG(IS_WIN)
   }
   state.capture_time = base::TimeTicks::Now();
diff --git a/base/power_monitor/battery_level_provider.h b/base/power_monitor/battery_level_provider.h
index 5f4b4dd8..9ce92a4 100644
--- a/base/power_monitor/battery_level_provider.h
+++ b/base/power_monitor/battery_level_provider.h
@@ -60,17 +60,12 @@
     base::TimeTicks capture_time;
 
 #if BUILDFLAG(IS_WIN)
-    // The granularity of the |current_capacity| value, in hundredths of a
-    // percent. Only available on Windows, and if a battery is present. This
-    // value is populated by the manufacturer and is not guaranteed to be
-    // available or accurate.
+    // The granularity of the battery discharge. Always the most coarse
+    // granularity among all the reporting scales of the battery, regardless of
+    // the current capacity, in milliwatt-hours. Only available on
+    // Windows, and if a battery is present. This value is populated by the
+    // manufacturer and is not guaranteed to be available or accurate.
     absl::optional<uint32_t> battery_discharge_granularity;
-
-    // The most coarse granularity among all the reporting scales of the
-    // battery, in hundredths of a percent. Only available on Windows, and if a
-    // battery is present. This value is populated by the manufacturer and is
-    // not guaranteed to be available or accurate.
-    absl::optional<uint32_t> max_battery_discharge_granularity;
 #endif  // BUILDFLAG(IS_WIN)
   };
 
diff --git a/base/power_monitor/battery_level_provider_win.cc b/base/power_monitor/battery_level_provider_win.cc
index 889f829f..1e1d9a2 100644
--- a/base/power_monitor/battery_level_provider_win.cc
+++ b/base/power_monitor/battery_level_provider_win.cc
@@ -12,10 +12,12 @@
 #include <setupapi.h>
 #include <winioctl.h>
 
+#include <algorithm>
 #include <array>
 #include <vector>
 
 #include "base/memory/weak_ptr.h"
+#include "base/metrics/histogram_macros.h"
 #include "base/task/sequenced_task_runner.h"
 #include "base/task/task_traits.h"
 #include "base/task/thread_pool.h"
@@ -106,16 +108,12 @@
   return battery_information;
 }
 
-// Returns information about the granularity of the battery discharge rate. This
-// function returns 2 values: the granularity that correspond to the current
-// capacity; and the most coarse granularity, which can differ from the other
-// granularity if the battery has multiple scales and the current capacity is
-// not full.
-std::pair<absl::optional<uint32_t>, absl::optional<uint32_t>>
-GetBatteryGranularityInformation(HANDLE battery,
-                                 ULONG battery_tag,
-                                 ULONG current_capacity,
-                                 ULONG designed_capacity) {
+// Returns the granularity of the battery discharge.
+absl::optional<uint32_t> GetBatteryBatteryDischargeGranularity(
+    HANDLE battery,
+    ULONG battery_tag,
+    ULONG current_capacity,
+    ULONG designed_capacity) {
   BATTERY_QUERY_INFORMATION query_information = {};
   query_information.BatteryTag = battery_tag;
   query_information.InformationLevel = BatteryGranularityInformation;
@@ -134,30 +132,32 @@
       sizeof(query_information), &battery_reporting_scales,
       sizeof(battery_reporting_scales), &bytes_returned, nullptr);
   if (!success)
-    return {absl::nullopt, absl::nullopt};
+    return absl::nullopt;
 
   size_t nb_elements = bytes_returned / sizeof(BATTERY_REPORTING_SCALE);
   if (!nb_elements)
-    return {absl::nullopt, absl::nullopt};
+    return absl::nullopt;
 
-  DWORD max_granularity = battery_reporting_scales[0].Granularity;
-  DWORD current_granularity = battery_reporting_scales[0].Granularity;
-  for (size_t i = 1; i < nb_elements; i++) {
-    // The granularities are ordered from the highest capacity to the lowest
-    // capacity, or from the most coarse granularity to the most precise
-    // granularity, according to the documentation.
-    if (current_capacity < battery_reporting_scales[i].Capacity)
-      current_granularity = battery_reporting_scales[i].Granularity;
+  // The granularities are ordered from the highest capacity to the lowest
+  // capacity, or from the most coarse granularity to the most precise
+  // granularity, according to the documentation.
+  // Just in case, the documentation is not trusted for |max_granularity|. All
+  // the values are still compared to find the most coarse granularity.
+  DWORD max_granularity =
+      std::max_element(std::begin(battery_reporting_scales),
+                       std::end(battery_reporting_scales),
+                       [](const auto& lhs, const auto& rhs) {
+                         return lhs.Granularity < rhs.Granularity;
+                       })
+          ->Granularity;
 
-    // Just in case, the documentation is not trusted for |max_granularity|. All
-    // the values are still compared to find the most coarse granularity.
-    max_granularity =
-        std::max(max_granularity, battery_reporting_scales[i].Granularity);
-  }
+  // Check if the API can be trusted, which would simplify the implementation of
+  // this function.
+  UMA_HISTOGRAM_BOOLEAN(
+      "Power.BatteryDischargeGranularityIsOrdered",
+      max_granularity == battery_reporting_scales[0].Granularity);
 
-  // Returns the granularities in hundredths of a percent.
-  return {current_granularity * 10000.0 / designed_capacity,
-          max_granularity * 10000.0 / designed_capacity};
+  return max_granularity;
 }
 
 // Returns BATTERY_STATUS structure containing battery state, given battery
@@ -281,11 +281,10 @@
       return absl::nullopt;
     }
 
-    const auto& [battery_discharge_granularity,
-                 max_battery_discharge_granularity] =
-        GetBatteryGranularityInformation(battery.Get(), *battery_tag,
-                                         battery_status->Capacity,
-                                         battery_information->DesignedCapacity);
+    absl::optional<uint32_t> battery_discharge_granularity =
+        GetBatteryBatteryDischargeGranularity(
+            battery.Get(), *battery_tag, battery_status->Capacity,
+            battery_information->DesignedCapacity);
 
     battery_details_list.push_back(BatteryDetails(
         {.is_external_power_connected =
@@ -296,9 +295,7 @@
              ((battery_information->Capabilities & BATTERY_CAPACITY_RELATIVE)
                   ? BatteryLevelUnit::kRelative
                   : BatteryLevelUnit::kMWh),
-         .battery_discharge_granularity = battery_discharge_granularity,
-         .max_battery_discharge_granularity =
-             max_battery_discharge_granularity}));
+         .battery_discharge_granularity = battery_discharge_granularity}));
   }
 
   return MakeBatteryState(battery_details_list);
diff --git a/base/power_monitor/battery_state_sampler_unittest.cc b/base/power_monitor/battery_state_sampler_unittest.cc
index 16f39ae..17b7051 100644
--- a/base/power_monitor/battery_state_sampler_unittest.cc
+++ b/base/power_monitor/battery_state_sampler_unittest.cc
@@ -10,11 +10,11 @@
 #include "base/power_monitor/power_monitor_buildflags.h"
 #include "base/power_monitor/sampling_event_source.h"
 #include "base/run_loop.h"
+#include "base/task/sequenced_task_runner.h"
 #include "base/test/bind.h"
 #include "base/test/gtest_util.h"
 #include "base/test/power_monitor_test_utils.h"
 #include "base/test/task_environment.h"
-#include "base/threading/sequenced_task_runner_handle.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace base {
@@ -54,7 +54,7 @@
     auto next_battery_state = std::move(battery_states_.front());
     battery_states_.pop();
 
-    SequencedTaskRunnerHandle::Get()->PostTask(
+    SequencedTaskRunner::GetCurrentDefault()->PostTask(
         FROM_HERE,
         BindLambdaForTesting([callback = std::move(callback),
                               battery_state = next_battery_state]() mutable {
diff --git a/base/profiler/sampling_profiler_thread_token.cc b/base/profiler/sampling_profiler_thread_token.cc
index 712498906..8186e673 100644
--- a/base/profiler/sampling_profiler_thread_token.cc
+++ b/base/profiler/sampling_profiler_thread_token.cc
@@ -10,7 +10,6 @@
 #include <pthread.h>
 
 #include "base/profiler/stack_base_address_posix.h"
-#include "third_party/abseil-cpp/absl/types/optional.h"
 #endif
 
 namespace base {
@@ -22,10 +21,7 @@
 #elif BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
   absl::optional<uintptr_t> maybe_stack_base =
       GetThreadStackBaseAddress(id, pthread_self());
-  // GetThreadStackBaseAddress should only return nullopt on Android, so
-  // dereferencing should be safe.
-  CHECK(maybe_stack_base);
-  return {id, *maybe_stack_base};
+  return {id, maybe_stack_base};
 #else
   return {id};
 #endif
diff --git a/base/profiler/sampling_profiler_thread_token.h b/base/profiler/sampling_profiler_thread_token.h
index 02bc37a5..43ca395 100644
--- a/base/profiler/sampling_profiler_thread_token.h
+++ b/base/profiler/sampling_profiler_thread_token.h
@@ -8,6 +8,7 @@
 #include "base/base_export.h"
 #include "base/threading/platform_thread.h"
 #include "build/build_config.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 #if BUILDFLAG(IS_ANDROID)
 #include <pthread.h>
@@ -29,7 +30,7 @@
   // Due to the sandbox, we can only retrieve the stack base address for the
   // current thread. We must grab it during
   // GetSamplingProfilerCurrentThreadToken() and not try to get it later.
-  uintptr_t stack_base_address;
+  absl::optional<uintptr_t> stack_base_address;
 #endif
 };
 
diff --git a/base/profiler/stack_base_address_posix.cc b/base/profiler/stack_base_address_posix.cc
index d15e3fe..c49b1bb 100644
--- a/base/profiler/stack_base_address_posix.cc
+++ b/base/profiler/stack_base_address_posix.cc
@@ -4,6 +4,8 @@
 
 #include "base/profiler/stack_base_address_posix.h"
 
+#include "base/check_op.h"
+#include "base/logging.h"
 #include "base/process/process_handle.h"
 #include "build/build_config.h"
 
@@ -41,24 +43,43 @@
 }
 #endif
 
+#if !BUILDFLAG(IS_LINUX)
 uintptr_t GetThreadStackBaseAddressImpl(pthread_t pthread_id) {
   pthread_attr_t attr;
-  // This will crash on ChromeOS & Linux if we are in the sandbox and pthread_id
-  // refers to a different thread, due to the use of sched_getaffinity().
-  pthread_getattr_np(pthread_id, &attr);
-  // See crbug.com/617730 for limitations of this approach on Linux.
+  // pthread_getattr_np will crash on ChromeOS & Linux if we are in the sandbox
+  // and pthread_id refers to a different thread, due to the use of
+  // sched_getaffinity().
+  int result = pthread_getattr_np(pthread_id, &attr);
+  // pthread_getattr_np should never fail except on Linux, and Linux will never
+  // call this function. See
+  // https://crrev.com/c/chromium/src/+/4064700/comment/ef75d2b7_c255168c/ for
+  // discussion of crashing vs. returning nullopt in this case.
+  CHECK_EQ(result, 0) << "pthread_getattr_np returned "
+                      << logging::SystemErrorCodeToString(result);
+  // See crbug.com/617730 for limitations of this approach on Linux-like
+  // systems.
   void* address;
   size_t size;
-  pthread_attr_getstack(&attr, &address, &size);
+  result = pthread_attr_getstack(&attr, &address, &size);
+  CHECK_EQ(result, 0) << "pthread_attr_getstack returned "
+                      << logging::SystemErrorCodeToString(result);
   pthread_attr_destroy(&attr);
   const uintptr_t base_address = reinterpret_cast<uintptr_t>(address) + size;
   return base_address;
 }
+#endif  // !BUILDFLAG(IS_LINUX)
 
 }  // namespace
 
 absl::optional<uintptr_t> GetThreadStackBaseAddress(PlatformThreadId id,
                                                     pthread_t pthread_id) {
+#if BUILDFLAG(IS_LINUX)
+  // We don't currently support Linux; pthread_getattr_np() fails for the main
+  // thread after zygote forks. https://crbug.com/1394278. Since we don't
+  // support stack profiling at all on Linux, we just return nullopt instead of
+  // trying to work around the problem.
+  return absl::nullopt;
+#else
   const bool is_main_thread = id == GetCurrentProcId();
   if (is_main_thread) {
 #if BUILDFLAG(IS_ANDROID)
@@ -77,6 +98,7 @@
 #endif
   }
   return GetThreadStackBaseAddressImpl(pthread_id);
+#endif  // !BUILDFLAG(IS_LINUX)
 }
 
 }  // namespace base
diff --git a/base/profiler/stack_base_address_posix_unittest.cc b/base/profiler/stack_base_address_posix_unittest.cc
index 39a68cd3..498df04 100644
--- a/base/profiler/stack_base_address_posix_unittest.cc
+++ b/base/profiler/stack_base_address_posix_unittest.cc
@@ -20,6 +20,10 @@
 // ASAN moves local variables outside of the stack extents.
 #if defined(ADDRESS_SANITIZER)
 #define MAYBE_CurrentThread DISABLED_CurrentThread
+#elif BUILDFLAG(IS_LINUX)
+// We don't support getting the stack base address on Linux.
+// https://crbug.com/1394278
+#define MAYBE_CurrentThread DISABLED_CurrentThread
 #else
 #define MAYBE_CurrentThread CurrentThread
 #endif
diff --git a/base/profiler/stack_copier_signal_unittest.cc b/base/profiler/stack_copier_signal_unittest.cc
index 8af5b23..c232e2f 100644
--- a/base/profiler/stack_copier_signal_unittest.cc
+++ b/base/profiler/stack_copier_signal_unittest.cc
@@ -85,6 +85,10 @@
 #elif BUILDFLAG(IS_CHROMEOS_ASH)
 // https://crbug.com/1042974
 #define MAYBE_CopyStack DISABLED_CopyStack
+#elif BUILDFLAG(IS_LINUX)
+// We don't support getting the stack base address on Linux, and thus can't
+// copy the stack. // https://crbug.com/1394278
+#define MAYBE_CopyStack DISABLED_CopyStack
 #else
 #define MAYBE_CopyStack CopyStack
 #endif
@@ -124,6 +128,10 @@
 // TSAN hangs on the AsyncSafeWaitableEvent FUTEX_WAIT call.
 #if defined(THREAD_SANITIZER)
 #define MAYBE_CopyStackTimestamp DISABLED_CopyStackTimestamp
+#elif BUILDFLAG(IS_LINUX)
+// We don't support getting the stack base address on Linux, and thus can't
+// copy the stack. // https://crbug.com/1394278
+#define MAYBE_CopyStackTimestamp DISABLED_CopyStackTimestamp
 #else
 #define MAYBE_CopyStackTimestamp CopyStackTimestamp
 #endif
@@ -153,6 +161,10 @@
 // TSAN hangs on the AsyncSafeWaitableEvent FUTEX_WAIT call.
 #if defined(THREAD_SANITIZER)
 #define MAYBE_CopyStackDelegateInvoked DISABLED_CopyStackDelegateInvoked
+#elif BUILDFLAG(IS_LINUX)
+// We don't support getting the stack base address on Linux, and thus can't
+// copy the stack. // https://crbug.com/1394278
+#define MAYBE_CopyStackDelegateInvoked DISABLED_CopyStackDelegateInvoked
 #else
 #define MAYBE_CopyStackDelegateInvoked CopyStackDelegateInvoked
 #endif
@@ -181,6 +193,10 @@
 // to selectively disable.
 #if !(BUILDFLAG(IS_ANDROID) && defined(ARCH_CPU_32_BITS))
 #define MAYBE_CopyStackFromOtherThread DISABLED_CopyStackFromOtherThread
+#elif BUILDFLAG(IS_LINUX)
+// We don't support getting the stack base address on Linux, and thus can't
+// copy the stack. // https://crbug.com/1394278
+#define MAYBE_CopyStackFromOtherThread DISABLED_CopyStackFromOtherThread
 #else
 #define MAYBE_CopyStackFromOtherThread CopyStackFromOtherThread
 #endif
diff --git a/base/profiler/thread_delegate_posix.cc b/base/profiler/thread_delegate_posix.cc
index 49dd64f..d70413aa 100644
--- a/base/profiler/thread_delegate_posix.cc
+++ b/base/profiler/thread_delegate_posix.cc
@@ -27,9 +27,9 @@
 #else
   base_address =
       GetThreadStackBaseAddress(thread_token.id, thread_token.pthread_id);
+#endif
   if (!base_address)
     return nullptr;
-#endif
   return base::WrapUnique(
       new ThreadDelegatePosix(thread_token.id, *base_address));
 }
diff --git a/base/profiler/thread_delegate_posix_unittest.cc b/base/profiler/thread_delegate_posix_unittest.cc
index 8cc38516..1d748aa 100644
--- a/base/profiler/thread_delegate_posix_unittest.cc
+++ b/base/profiler/thread_delegate_posix_unittest.cc
@@ -15,6 +15,10 @@
 // ASAN moves local variables outside of the stack extents.
 #if defined(ADDRESS_SANITIZER)
 #define MAYBE_CurrentThreadBase DISABLED_CurrentThreadBase
+#elif BUILDFLAG(IS_LINUX)
+// We don't support getting the stack base address on Linux.
+// https://crbug.com/1394278
+#define MAYBE_CurrentThreadBase DISABLED_CurrentThreadBase
 #else
 #define MAYBE_CurrentThreadBase CurrentThreadBase
 #endif
diff --git a/base/run_loop.cc b/base/run_loop.cc
index cacb70c3..83afb90 100644
--- a/base/run_loop.cc
+++ b/base/run_loop.cc
@@ -12,7 +12,6 @@
 #include "base/observer_list.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/threading/thread_local.h"
-#include "base/threading/thread_task_runner_handle.h"
 #include "base/trace_event/base_tracing.h"
 #include "build/build_config.h"
 
@@ -96,7 +95,7 @@
 RunLoop::RunLoop(Type type)
     : delegate_(GetTlsDelegate().Get()),
       type_(type),
-      origin_task_runner_(ThreadTaskRunnerHandle::Get()) {
+      origin_task_runner_(SingleThreadTaskRunner::GetCurrentDefault()) {
   DCHECK(delegate_) << "A RunLoop::Delegate must be bound to this thread prior "
                        "to using RunLoop.";
   DCHECK(origin_task_runner_);
diff --git a/base/run_loop_unittest.cc b/base/run_loop_unittest.cc
index 6c90d67..3892182 100644
--- a/base/run_loop_unittest.cc
+++ b/base/run_loop_unittest.cc
@@ -16,6 +16,7 @@
 #include "base/memory/ref_counted.h"
 #include "base/synchronization/lock.h"
 #include "base/synchronization/waitable_event.h"
+#include "base/task/sequenced_task_runner.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/test/bind.h"
 #include "base/test/gtest_util.h"
@@ -23,10 +24,8 @@
 #include "base/test/task_environment.h"
 #include "base/test/test_timeouts.h"
 #include "base/threading/platform_thread.h"
-#include "base/threading/sequenced_task_runner_handle.h"
 #include "base/threading/thread.h"
 #include "base/threading/thread_checker_impl.h"
-#include "base/threading/thread_task_runner_handle.h"
 #include "build/build_config.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -44,11 +43,11 @@
   RunLoop nested_run_loop(RunLoop::Type::kNestableTasksAllowed);
 
   // This task should quit |nested_run_loop| but not the main RunLoop.
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, BindOnce(&QuitWhenIdleTask, Unretained(&nested_run_loop),
                           Unretained(counter)));
 
-  ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
       FROM_HERE, MakeExpectedNotRunClosure(FROM_HERE), Days(1));
 
   nested_run_loop.Run();
@@ -249,12 +248,12 @@
 
 TEST_P(RunLoopTest, QuitWhenIdle) {
   int counter = 0;
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, BindOnce(&QuitWhenIdleTask, Unretained(&run_loop_),
                           Unretained(&counter)));
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          MakeExpectedRunClosure(FROM_HERE));
-  ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, MakeExpectedRunClosure(FROM_HERE));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
       FROM_HERE, MakeExpectedNotRunClosure(FROM_HERE), Days(1));
 
   run_loop_.Run();
@@ -263,14 +262,14 @@
 
 TEST_P(RunLoopTest, QuitWhenIdleNestedLoop) {
   int counter = 0;
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, BindOnce(&RunNestedLoopTask, Unretained(&counter)));
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, BindOnce(&QuitWhenIdleTask, Unretained(&run_loop_),
                           Unretained(&counter)));
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          MakeExpectedRunClosure(FROM_HERE));
-  ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, MakeExpectedRunClosure(FROM_HERE));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
       FROM_HERE, MakeExpectedNotRunClosure(FROM_HERE), Days(1));
 
   run_loop_.Run();
@@ -278,11 +277,11 @@
 }
 
 TEST_P(RunLoopTest, QuitWhenIdleClosure) {
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          run_loop_.QuitWhenIdleClosure());
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          MakeExpectedRunClosure(FROM_HERE));
-  ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, run_loop_.QuitWhenIdleClosure());
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, MakeExpectedRunClosure(FROM_HERE));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
       FROM_HERE, MakeExpectedNotRunClosure(FROM_HERE), Days(1));
 
   run_loop_.Run();
@@ -308,8 +307,8 @@
       other_thread.task_runner();
 
   // Always expected to run before asynchronous Quit() kicks in.
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          MakeExpectedRunClosure(FROM_HERE));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, MakeExpectedRunClosure(FROM_HERE));
 
   WaitableEvent loop_was_quit(WaitableEvent::ResetPolicy::MANUAL,
                               WaitableEvent::InitialState::NOT_SIGNALED);
@@ -323,8 +322,8 @@
   // Anything that's posted after the Quit closure was posted back to this
   // sequence shouldn't get a chance to run.
   loop_was_quit.Wait();
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          MakeExpectedNotRunClosure(FROM_HERE));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, MakeExpectedNotRunClosure(FROM_HERE));
 
   run_loop_.Run();
 }
@@ -337,8 +336,8 @@
       other_thread.task_runner();
 
   // Always expected to run before asynchronous Quit() kicks in.
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          MakeExpectedRunClosure(FROM_HERE));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, MakeExpectedRunClosure(FROM_HERE));
 
   WaitableEvent loop_was_quit(WaitableEvent::ResetPolicy::MANUAL,
                               WaitableEvent::InitialState::NOT_SIGNALED);
@@ -350,8 +349,8 @@
   // Anything that's posted after the Quit closure was posted back to this
   // sequence shouldn't get a chance to run.
   loop_was_quit.Wait();
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          MakeExpectedNotRunClosure(FROM_HERE));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, MakeExpectedNotRunClosure(FROM_HERE));
 
   run_loop_.Run();
 }
@@ -365,8 +364,8 @@
       other_thread.task_runner();
 
   // Always expected to run before asynchronous Quit() kicks in.
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          MakeExpectedRunClosure(FROM_HERE));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, MakeExpectedRunClosure(FROM_HERE));
 
   other_sequence->PostTask(FROM_HERE, run_loop_.QuitClosure());
 
@@ -382,8 +381,8 @@
       other_thread.task_runner();
 
   // Always expected to run before asynchronous Quit() kicks in.
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          MakeExpectedRunClosure(FROM_HERE));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, MakeExpectedRunClosure(FROM_HERE));
 
   other_sequence->PostTask(FROM_HERE, run_loop_.QuitClosure());
 
@@ -397,16 +396,16 @@
   scoped_refptr<SequencedTaskRunner> other_sequence =
       other_thread.task_runner();
 
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          MakeExpectedRunClosure(FROM_HERE));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, MakeExpectedRunClosure(FROM_HERE));
 
   other_sequence->PostTask(
       FROM_HERE,
       base::BindOnce([](RunLoop* run_loop) { run_loop->QuitWhenIdle(); },
                      Unretained(&run_loop_)));
 
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          MakeExpectedRunClosure(FROM_HERE));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, MakeExpectedRunClosure(FROM_HERE));
 
   run_loop_.Run();
 
@@ -421,13 +420,13 @@
   scoped_refptr<SequencedTaskRunner> other_sequence =
       other_thread.task_runner();
 
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          MakeExpectedRunClosure(FROM_HERE));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, MakeExpectedRunClosure(FROM_HERE));
 
   other_sequence->PostTask(FROM_HERE, run_loop_.QuitWhenIdleClosure());
 
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          MakeExpectedRunClosure(FROM_HERE));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, MakeExpectedRunClosure(FROM_HERE));
 
   run_loop_.Run();
 
@@ -437,72 +436,77 @@
 
 TEST_P(RunLoopTest, IsRunningOnCurrentThread) {
   EXPECT_FALSE(RunLoop::IsRunningOnCurrentThread());
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE,
       BindOnce([]() { EXPECT_TRUE(RunLoop::IsRunningOnCurrentThread()); }));
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, run_loop_.QuitClosure());
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, run_loop_.QuitClosure());
   run_loop_.Run();
 }
 
 TEST_P(RunLoopTest, IsNestedOnCurrentThread) {
   EXPECT_FALSE(RunLoop::IsNestedOnCurrentThread());
 
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, BindOnce([]() {
         EXPECT_FALSE(RunLoop::IsNestedOnCurrentThread());
 
         RunLoop nested_run_loop(RunLoop::Type::kNestableTasksAllowed);
 
-        ThreadTaskRunnerHandle::Get()->PostTask(
+        SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
             FROM_HERE, BindOnce([]() {
               EXPECT_TRUE(RunLoop::IsNestedOnCurrentThread());
             }));
-        ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                                nested_run_loop.QuitClosure());
+        SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+            FROM_HERE, nested_run_loop.QuitClosure());
 
         EXPECT_FALSE(RunLoop::IsNestedOnCurrentThread());
         nested_run_loop.Run();
         EXPECT_FALSE(RunLoop::IsNestedOnCurrentThread());
       }));
 
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, run_loop_.QuitClosure());
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, run_loop_.QuitClosure());
   run_loop_.Run();
 }
 
 TEST_P(RunLoopTest, CannotRunMoreThanOnce) {
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, run_loop_.QuitClosure());
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, run_loop_.QuitClosure());
   run_loop_.Run();
   EXPECT_DCHECK_DEATH({ run_loop_.Run(); });
 }
 
 TEST_P(RunLoopTest, CanRunUntilIdleMoreThanOnce) {
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, DoNothing());
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE, DoNothing());
   run_loop_.RunUntilIdle();
 
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, DoNothing());
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE, DoNothing());
   run_loop_.RunUntilIdle();
   run_loop_.RunUntilIdle();
 }
 
 TEST_P(RunLoopTest, CanRunUntilIdleThenRunIfNotQuit) {
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, DoNothing());
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE, DoNothing());
   run_loop_.RunUntilIdle();
 
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, run_loop_.QuitClosure());
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, run_loop_.QuitClosure());
   run_loop_.Run();
 }
 
 TEST_P(RunLoopTest, CannotRunUntilIdleThenRunIfQuit) {
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, run_loop_.QuitClosure());
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, run_loop_.QuitClosure());
   run_loop_.RunUntilIdle();
 
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, DoNothing());
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE, DoNothing());
   EXPECT_DCHECK_DEATH({ run_loop_.Run(); });
 }
 
 TEST_P(RunLoopTest, CannotRunAgainIfQuitWhenIdle) {
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          run_loop_.QuitWhenIdleClosure());
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, run_loop_.QuitWhenIdleClosure());
   run_loop_.RunUntilIdle();
 
   EXPECT_DCHECK_DEATH({ run_loop_.RunUntilIdle(); });
@@ -541,8 +545,8 @@
 
   const RepeatingClosure run_nested_loop = BindRepeating([]() {
     RunLoop nested_run_loop(RunLoop::Type::kNestableTasksAllowed);
-    ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                            nested_run_loop.QuitClosure());
+    SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+        FROM_HERE, nested_run_loop.QuitClosure());
     nested_run_loop.Run();
   });
 
@@ -551,12 +555,14 @@
   // when exiting each nesting depth. Each one of these tasks is ahead of the
   // QuitClosures as those are only posted at the end of the queue when
   // |run_nested_loop| is executed.
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, run_nested_loop);
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE,
+                                                        run_nested_loop);
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE,
       base::BindOnce(&MockTask::Task, base::Unretained(&mock_task_a)));
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, run_nested_loop);
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE,
+                                                        run_nested_loop);
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE,
       base::BindOnce(&MockTask::Task, base::Unretained(&mock_task_b)));
 
@@ -613,13 +619,13 @@
   // The first task on the main loop will result in a nested run loop. Since
   // it's not kNestableTasksAllowed, no further task should be processed until
   // it's quit.
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE,
       BindOnce([](RunLoop* nested_run_loop) { nested_run_loop->Run(); },
                Unretained(&nested_run_loop)));
 
   // Post a task that will fail if it runs inside the nested run loop.
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE,
       BindOnce(
           [](const bool& nested_run_loop_ended,
diff --git a/base/synchronization/waitable_event_watcher_unittest.cc b/base/synchronization/waitable_event_watcher_unittest.cc
index 67cb962..17bc04a 100644
--- a/base/synchronization/waitable_event_watcher_unittest.cc
+++ b/base/synchronization/waitable_event_watcher_unittest.cc
@@ -10,11 +10,11 @@
 #include "base/memory/raw_ptr.h"
 #include "base/run_loop.h"
 #include "base/synchronization/waitable_event.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/task/single_thread_task_runner.h"
 #include "base/test/bind.h"
 #include "base/test/task_environment.h"
 #include "base/threading/platform_thread.h"
-#include "base/threading/sequenced_task_runner_handle.h"
-#include "base/threading/thread_task_runner_handle.h"
 #include "build/build_config.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -61,7 +61,7 @@
 
   WaitableEventWatcher watcher;
   watcher.StartWatching(&event, BindOnce(&QuitWhenSignaled),
-                        SequencedTaskRunnerHandle::Get());
+                        SequencedTaskRunner::GetCurrentDefault());
 
   event.Signal();
 
@@ -78,7 +78,7 @@
 
   WaitableEventWatcher watcher;
   watcher.StartWatching(&event, BindOnce(&QuitWhenSignaled),
-                        SequencedTaskRunnerHandle::Get());
+                        SequencedTaskRunner::GetCurrentDefault());
 
   event.Signal();
 
@@ -98,7 +98,7 @@
   WaitableEventWatcher watcher;
 
   watcher.StartWatching(&event, BindOnce(&QuitWhenSignaled),
-                        SequencedTaskRunnerHandle::Get());
+                        SequencedTaskRunner::GetCurrentDefault());
 
   watcher.StopWatching();
 }
@@ -117,7 +117,7 @@
   WaitableEventWatcher::EventCallback callback = BindOnce(
       &DecrementCountContainer::OnWaitableEventSignaled, Unretained(&delegate));
   watcher.StartWatching(&event, std::move(callback),
-                        SequencedTaskRunnerHandle::Get());
+                        SequencedTaskRunner::GetCurrentDefault());
 
   event.Signal();
 
@@ -145,7 +145,7 @@
       watcher = std::make_unique<WaitableEventWatcher>();
 
       watcher->StartWatching(&event, BindOnce(&QuitWhenSignaled),
-                             SequencedTaskRunnerHandle::Get());
+                             SequencedTaskRunner::GetCurrentDefault());
     }
   }
 }
@@ -158,7 +158,7 @@
 
   WaitableEventWatcher watcher;
   watcher.StartWatching(&event, BindOnce(&QuitWhenSignaled),
-                        SequencedTaskRunnerHandle::Get());
+                        SequencedTaskRunner::GetCurrentDefault());
 
   RunLoop().Run();
 
@@ -173,7 +173,7 @@
 
   WaitableEventWatcher watcher;
   watcher.StartWatching(&event, BindOnce(&QuitWhenSignaled),
-                        SequencedTaskRunnerHandle::Get());
+                        SequencedTaskRunner::GetCurrentDefault());
 
   RunLoop().Run();
 
@@ -195,10 +195,10 @@
             // |event| is manual, so the second watcher will run
             // immediately.
             watcher->StartWatching(event, BindOnce(&QuitWhenSignaled),
-                                   SequencedTaskRunnerHandle::Get());
+                                   SequencedTaskRunner::GetCurrentDefault());
           },
           &watcher),
-      SequencedTaskRunnerHandle::Get());
+      SequencedTaskRunner::GetCurrentDefault());
 
   event.Signal();
 
@@ -230,13 +230,13 @@
   watcher1.StartWatching(
       &event,
       BindOnce(BindLambdaForTesting(callback), Unretained(&watcher1_counter)),
-      SequencedTaskRunnerHandle::Get());
+      SequencedTaskRunner::GetCurrentDefault());
 
   WaitableEventWatcher watcher2;
   watcher2.StartWatching(
       &event,
       BindOnce(BindLambdaForTesting(callback), Unretained(&watcher2_counter)),
-      SequencedTaskRunnerHandle::Get());
+      SequencedTaskRunner::GetCurrentDefault());
 
   event.Signal();
   run_loop.Run();
@@ -272,13 +272,13 @@
   watcher1.StartWatching(
       &event,
       BindOnce(callback, Unretained(&current_run_loop), Unretained(&counter1)),
-      SequencedTaskRunnerHandle::Get());
+      SequencedTaskRunner::GetCurrentDefault());
 
   WaitableEventWatcher watcher2;
   watcher2.StartWatching(
       &event,
       BindOnce(callback, Unretained(&current_run_loop), Unretained(&counter2)),
-      SequencedTaskRunnerHandle::Get());
+      SequencedTaskRunner::GetCurrentDefault());
 
   event.Signal();
   {
@@ -327,7 +327,7 @@
                                     WaitableEvent::InitialState::NOT_SIGNALED);
 
     watcher.StartWatching(event, BindOnce(&QuitWhenSignaled),
-                          SequencedTaskRunnerHandle::Get());
+                          SequencedTaskRunner::GetCurrentDefault());
 
     if (delay_after_delete) {
       // On Windows that sleep() improves the chance to catch some problems.
@@ -357,7 +357,7 @@
         WaitableEvent::InitialState::NOT_SIGNALED);
 
     watcher.StartWatching(event.get(), BindOnce(&QuitWhenSignaled),
-                          SequencedTaskRunnerHandle::Get());
+                          SequencedTaskRunner::GetCurrentDefault());
     event->Signal();
     event.reset();
 
@@ -382,7 +382,7 @@
 
   test::TaskEnvironment task_environment(main_thread_type);
   scoped_refptr<SingleThreadTaskRunner> task_runner =
-      ThreadTaskRunnerHandle::Get();
+      SingleThreadTaskRunner::GetCurrentDefault();
 
   // Flag used to esnure that the |watcher_callback| never runs.
   bool did_callback = false;
diff --git a/base/system/sys_info.cc b/base/system/sys_info.cc
index 7dc7c90..84f29a8 100644
--- a/base/system/sys_info.cc
+++ b/base/system/sys_info.cc
@@ -31,6 +31,12 @@
 }  // namespace
 
 // static
+int SysInfo::NumberOfEfficientProcessors() {
+  static int number_of_efficient_processors = NumberOfEfficientProcessorsImpl();
+  return number_of_efficient_processors;
+}
+
+// static
 uint64_t SysInfo::AmountOfPhysicalMemory() {
   if (base::CommandLine::ForCurrentProcess()->HasSwitch(
           switches::kEnableLowEndDeviceMode)) {
diff --git a/base/system/sys_info.h b/base/system/sys_info.h
index 6bf50b9b..e14bf38b 100644
--- a/base/system/sys_info.h
+++ b/base/system/sys_info.h
@@ -46,6 +46,13 @@
   // security mitigations are enabled on Mac.
   static int NumberOfProcessors();
 
+  // Returns the number of the most efficient logical processors for the current
+  // application. This is typically e-cores on Intel hybrid architecture, or
+  // LITTLE cores on ARM bit.LITTLE architecture.
+  // Returns 0 on symmetric architecture or when it failed to recognize.
+  // This function will cache the result value in its implementation.
+  static int NumberOfEfficientProcessors();
+
   // Return the number of bytes of physical memory on the current machine.
   // If low-end device mode is manually enabled via command line flag, this
   // will return the lesser of the actual physical memory, or 512MB.
@@ -237,6 +244,7 @@
   FRIEND_TEST_ALL_PREFIXES(SysInfoTest, AmountOfAvailablePhysicalMemory);
   FRIEND_TEST_ALL_PREFIXES(debug::SystemMetricsTest, ParseMeminfo);
 
+  static int NumberOfEfficientProcessorsImpl();
   static uint64_t AmountOfPhysicalMemoryImpl();
   static uint64_t AmountOfAvailablePhysicalMemoryImpl();
   static bool IsLowEndDeviceImpl();
diff --git a/base/system/sys_info_fuchsia.cc b/base/system/sys_info_fuchsia.cc
index 603b96c9..cb28b6c 100644
--- a/base/system/sys_info_fuchsia.cc
+++ b/base/system/sys_info_fuchsia.cc
@@ -211,6 +211,12 @@
   return static_cast<size_t>(getpagesize());
 }
 
+// static
+int SysInfo::NumberOfEfficientProcessorsImpl() {
+  NOTIMPLEMENTED();
+  return 0;
+}
+
 SysInfo::HardwareInfo SysInfo::GetHardwareInfoSync() {
   const auto product_info = GetProductInfo();
 
diff --git a/base/system/sys_info_mac.mm b/base/system/sys_info_mac.mm
index b8561730..fc23c9f 100644
--- a/base/system/sys_info_mac.mm
+++ b/base/system/sys_info_mac.mm
@@ -30,17 +30,29 @@
 
 bool g_is_cpu_security_mitigation_enabled = false;
 
+// Queries sysctlbyname() for the given key and returns the 32 bit integer value
+// from the system or absl::nullopt on failure.
+// https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/bsd/sys/sysctl.h#L1224-L1225
+absl::optional<int> GetSysctlIntValue(const char* key_name) {
+  int value;
+  size_t len = sizeof(value);
+  if (sysctlbyname(key_name, &value, &len, nullptr, 0) != 0)
+    return absl::nullopt;
+  DCHECK_EQ(len, sizeof(value));
+  return value;
+}
+
 // Queries sysctlbyname() for the given key and returns the value from the
 // system or the empty string on failure.
-std::string GetSysctlValue(const char* key_name) {
+std::string GetSysctlStringValue(const char* key_name) {
   char value[256];
-  size_t len = std::size(value);
-  if (sysctlbyname(key_name, &value, &len, nullptr, 0) == 0) {
-    DCHECK_GE(len, 1u);
-    DCHECK_EQ('\0', value[len - 1]);
-    return std::string(value, len - 1);
-  }
-  return std::string();
+  size_t len = sizeof(value);
+  if (sysctlbyname(key_name, &value, &len, nullptr, 0) != 0)
+    return std::string();
+  DCHECK_GE(len, 1u);
+  DCHECK_LE(len, sizeof(value));
+  DCHECK_EQ('\0', value[len - 1]);
+  return std::string(value, len - 1);
 }
 
 }  // namespace
@@ -48,12 +60,7 @@
 namespace internal {
 
 absl::optional<int> NumberOfPhysicalProcessors() {
-  int value;
-  size_t length = sizeof(value);
-
-  if (sysctlbyname("hw.physicalcpu_max", &value, &length, nullptr, 0) != 0)
-    return absl::nullopt;
-  return value;
+  return GetSysctlIntValue("hw.physicalcpu_max");
 }
 
 absl::optional<int> NumberOfProcessorsWhenCpuSecurityMitigationEnabled() {
@@ -105,6 +112,25 @@
 }
 
 // static
+int SysInfo::NumberOfEfficientProcessorsImpl() {
+  int num_perf_levels = GetSysctlIntValue("hw.nperflevels").value_or(1);
+  if (num_perf_levels == 1)
+    return 0;
+  DCHECK_GE(num_perf_levels, 2);
+
+  // Lower values of perflevel indicate higher-performance core types. See
+  // https://developer.apple.com/documentation/kernel/1387446-sysctlbyname/determining_system_capabilities?changes=l__5
+  int num_of_efficient_processors =
+      GetSysctlIntValue(
+          StringPrintf("hw.perflevel%d.logicalcpu", num_perf_levels - 1)
+              .c_str())
+          .value_or(0);
+  DCHECK_GE(num_of_efficient_processors, 0);
+
+  return num_of_efficient_processors;
+}
+
+// static
 uint64_t SysInfo::AmountOfPhysicalMemoryImpl() {
   struct host_basic_info hostinfo;
   mach_msg_type_number_t count = HOST_BASIC_INFO_COUNT;
@@ -131,12 +157,12 @@
 
 // static
 std::string SysInfo::CPUModelName() {
-  return GetSysctlValue("machdep.cpu.brand_string");
+  return GetSysctlStringValue("machdep.cpu.brand_string");
 }
 
 // static
 std::string SysInfo::HardwareModelName() {
-  return GetSysctlValue("hw.model");
+  return GetSysctlStringValue("hw.model");
 }
 
 // static
diff --git a/base/system/sys_info_posix.cc b/base/system/sys_info_posix.cc
index dbf9947..92e70aa 100644
--- a/base/system/sys_info_posix.cc
+++ b/base/system/sys_info_posix.cc
@@ -14,10 +14,15 @@
 #include <sys/utsname.h>
 #include <unistd.h>
 
+#include <algorithm>
+
+#include "base/check.h"
 #include "base/files/file_util.h"
 #include "base/lazy_instance.h"
 #include "base/notreached.h"
 #include "base/numerics/safe_conversions.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/system/sys_info_internal.h"
 #include "base/threading/scoped_blocking_call.h"
@@ -261,4 +266,43 @@
   return checked_cast<size_t>(getpagesize());
 }
 
+#if !BUILDFLAG(IS_MAC)
+// static
+int SysInfo::NumberOfEfficientProcessorsImpl() {
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID)
+  // Try to guess the CPU architecture and cores of each cluster by comparing
+  // the maximum frequencies of the available (online and offline) cores.
+  int num_cpus = SysInfo::NumberOfProcessors();
+  DCHECK_GE(num_cpus, 0);
+  std::vector<uint32_t> max_core_frequencies_khz(static_cast<size_t>(num_cpus),
+                                                 0);
+  for (int core_index = 0; core_index < num_cpus; ++core_index) {
+    std::string content;
+    auto path = StringPrintf(
+        "/sys/devices/system/cpu/cpu%d/cpufreq/cpuinfo_max_freq", core_index);
+    if (!ReadFileToStringNonBlocking(FilePath(path), &content))
+      return 0;
+    if (!StringToUint(
+            content,
+            &max_core_frequencies_khz[static_cast<size_t>(core_index)]))
+      return 0;
+  }
+
+  auto [min_max_core_frequencies_khz_it, max_max_core_frequencies_khz_it] =
+      std::minmax_element(max_core_frequencies_khz.begin(),
+                          max_core_frequencies_khz.end());
+
+  if (*min_max_core_frequencies_khz_it == *max_max_core_frequencies_khz_it)
+    return 0;
+
+  return static_cast<int>(std::count(max_core_frequencies_khz.begin(),
+                                     max_core_frequencies_khz.end(),
+                                     *min_max_core_frequencies_khz_it));
+#else
+  NOTIMPLEMENTED();
+  return 0;
+#endif
+}
+#endif  // !BUILDFLAG(IS_MAC)
+
 }  // namespace base
diff --git a/base/system/sys_info_unittest.cc b/base/system/sys_info_unittest.cc
index d461e8f..df2dca03 100644
--- a/base/system/sys_info_unittest.cc
+++ b/base/system/sys_info_unittest.cc
@@ -59,6 +59,10 @@
 TEST_F(SysInfoTest, NumProcs) {
   // We aren't actually testing that it's correct, just that it's sane.
   EXPECT_GE(SysInfo::NumberOfProcessors(), 1);
+
+  EXPECT_GE(SysInfo::NumberOfEfficientProcessors(), 0);
+  EXPECT_LT(SysInfo::NumberOfEfficientProcessors(),
+            SysInfo::NumberOfProcessors());
 }
 
 #if BUILDFLAG(IS_MAC)
diff --git a/base/system/sys_info_win.cc b/base/system/sys_info_win.cc
index c6f39d3..683ea13 100644
--- a/base/system/sys_info_win.cc
+++ b/base/system/sys_info_win.cc
@@ -8,9 +8,14 @@
 #include <stdint.h>
 #include <windows.h>
 
+#include <algorithm>
+#include <bit>
 #include <limits>
+#include <type_traits>
+#include <vector>
 
 #include "base/check.h"
+#include "base/containers/stack_container.h"
 #include "base/files/file_path.h"
 #include "base/notreached.h"
 #include "base/numerics/safe_conversions.h"
@@ -25,6 +30,83 @@
 
 namespace {
 
+// Returns the power efficiency levels of physical cores or empty vector on
+// failure. The BYTE value of the element is the relative efficiency rank among
+// all physical cores, where 0 is the most efficient, 1 is the second most
+// efficient, and so on.
+std::vector<BYTE> GetCoreEfficiencyClasses() {
+  const DWORD kReservedSize =
+      sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX) * 64;
+  base::StackVector<BYTE, kReservedSize> buffer;
+  buffer->resize(kReservedSize);
+  DWORD byte_length = kReservedSize;
+  if (!GetLogicalProcessorInformationEx(
+          RelationProcessorCore,
+          reinterpret_cast<SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*>(
+              buffer->data()),
+          &byte_length)) {
+    DPCHECK(GetLastError() == ERROR_INSUFFICIENT_BUFFER);
+    buffer->resize(byte_length);
+    if (!GetLogicalProcessorInformationEx(
+            RelationProcessorCore,
+            reinterpret_cast<SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*>(
+                buffer->data()),
+            &byte_length)) {
+      return {};
+    }
+  }
+
+  std::vector<BYTE> efficiency_classes;
+  BYTE* byte_ptr = buffer->data();
+  while (byte_ptr < buffer->data() + byte_length) {
+    const auto* structure_ptr =
+        reinterpret_cast<SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*>(byte_ptr);
+    DCHECK_EQ(structure_ptr->Relationship, RelationProcessorCore);
+    DCHECK_LE(&structure_ptr->Processor.EfficiencyClass +
+                  sizeof(structure_ptr->Processor.EfficiencyClass),
+              buffer->data() + byte_length);
+    efficiency_classes.push_back(structure_ptr->Processor.EfficiencyClass);
+    DCHECK_GE(
+        structure_ptr->Size,
+        offsetof(std::remove_pointer_t<decltype(structure_ptr)>, Processor) +
+            sizeof(structure_ptr->Processor));
+    byte_ptr = byte_ptr + structure_ptr->Size;
+  }
+
+  return efficiency_classes;
+}
+
+// Returns the physical cores to logical processor mapping masks by using the
+// Windows API GetLogicalProcessorInformation(), or an empty vector on failure.
+// When succeeded, the vector would be of same size to the number of physical
+// cores, while each element is the bitmask of the logical processors that the
+// physical core has.
+std::vector<uint64_t> GetCoreProcessorMasks() {
+  const DWORD kReservedSize = 64;
+  base::StackVector<SYSTEM_LOGICAL_PROCESSOR_INFORMATION, kReservedSize> buffer;
+  buffer->resize(kReservedSize);
+  DWORD byte_length = sizeof(buffer[0]) * kReservedSize;
+  const BOOL result =
+      GetLogicalProcessorInformation(buffer->data(), &byte_length);
+  DWORD element_count = byte_length / sizeof(buffer[0]);
+  DCHECK_EQ(byte_length % sizeof(buffer[0]), 0u);
+  if (!result) {
+    DPCHECK(GetLastError() == ERROR_INSUFFICIENT_BUFFER);
+    buffer->resize(element_count);
+    if (!GetLogicalProcessorInformation(buffer->data(), &byte_length))
+      return {};
+  }
+
+  std::vector<uint64_t> processor_masks;
+  for (DWORD i = 0; i < element_count; i++) {
+    if (buffer[i].Relationship == RelationProcessorCore) {
+      processor_masks.push_back(buffer[i].ProcessorMask);
+    }
+  }
+
+  return processor_masks;
+}
+
 uint64_t AmountOfMemory(DWORDLONG MEMORYSTATUSEX::*memory_field) {
   MEMORYSTATUSEX memory_info;
   memory_info.dwLength = sizeof(memory_info);
@@ -68,6 +150,32 @@
 }
 
 // static
+int SysInfo::NumberOfEfficientProcessorsImpl() {
+  std::vector<BYTE> efficiency_classes = GetCoreEfficiencyClasses();
+  if (efficiency_classes.empty())
+    return 0;
+
+  auto [min_efficiency_class_it, max_efficiency_class_it] =
+      std::minmax_element(efficiency_classes.begin(), efficiency_classes.end());
+  if (*min_efficiency_class_it == *max_efficiency_class_it)
+    return 0;
+
+  std::vector<uint64_t> processor_masks = GetCoreProcessorMasks();
+  if (processor_masks.empty())
+    return 0;
+
+  DCHECK_EQ(efficiency_classes.size(), processor_masks.size());
+  int num_of_efficient_processors = 0;
+  for (size_t i = 0; i < efficiency_classes.size(); i++) {
+    if (efficiency_classes[i] == *min_efficiency_class_it) {
+      num_of_efficient_processors += std::popcount(processor_masks[i]);
+    }
+  }
+
+  return num_of_efficient_processors;
+}
+
+// static
 uint64_t SysInfo::AmountOfPhysicalMemoryImpl() {
   return AmountOfMemory(&MEMORYSTATUSEX::ullTotalPhys);
 }
diff --git a/base/task/bind_post_task_unittest.cc b/base/task/bind_post_task_unittest.cc
index d05cad8..f09072ef 100644
--- a/base/task/bind_post_task_unittest.cc
+++ b/base/task/bind_post_task_unittest.cc
@@ -10,7 +10,6 @@
 #include "base/sequence_checker_impl.h"
 #include "base/task/sequenced_task_runner.h"
 #include "base/test/task_environment.h"
-#include "base/threading/sequenced_task_runner_handle.h"
 #include "base/threading/thread.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -58,7 +57,7 @@
  protected:
   test::SingleThreadTaskEnvironment task_environment_;
   scoped_refptr<SequencedTaskRunner> task_runner_ =
-      SequencedTaskRunnerHandle::Get();
+      SequencedTaskRunner::GetCurrentDefault();
 };
 
 TEST_F(BindPostTaskTest, OnceClosure) {
diff --git a/base/task/bind_post_task_unittest.nc b/base/task/bind_post_task_unittest.nc
index 7d8a91a4..5aa1096 100644
--- a/base/task/bind_post_task_unittest.nc
+++ b/base/task/bind_post_task_unittest.nc
@@ -7,9 +7,9 @@
 
 #include "base/task/bind_post_task.h"
 
+#include "base/task/sequenced_task_runner.h"
 #include "base/bind.h"
 #include "base/callback.h"
-#include "base/threading/sequenced_task_runner_handle.h"
 
 namespace base {
 
@@ -21,7 +21,7 @@
 // OnceCallback with non-void return type.
 void WontCompile() {
   OnceCallback<int()> cb = BindOnce(&ReturnInt);
-  auto post_cb = BindPostTask(SequencedTaskRunnerHandle::Get(), std::move(cb));
+  auto post_cb = BindPostTask(SequencedTaskRunner::GetCurrentDefault(), std::move(cb));
   std::move(post_cb).Run();
 }
 
@@ -29,7 +29,7 @@
 // RepeatingCallback with non-void return type.
 void WontCompile() {
   RepeatingCallback<int()> cb = BindRepeating(&ReturnInt);
-  auto post_cb = BindPostTask(SequencedTaskRunnerHandle::Get(), std::move(cb));
+  auto post_cb = BindPostTask(SequencedTaskRunner::GetCurrentDefault(), std::move(cb));
   std::move(post_cb).Run();
 }
 
diff --git a/base/task/cancelable_task_tracker.cc b/base/task/cancelable_task_tracker.cc
index a04011a..d28a8f5 100644
--- a/base/task/cancelable_task_tracker.cc
+++ b/base/task/cancelable_task_tracker.cc
@@ -17,7 +17,6 @@
 #include "base/task/scoped_set_task_priority_for_current_thread.h"
 #include "base/task/sequenced_task_runner.h"
 #include "base/task/task_runner.h"
-#include "base/threading/sequenced_task_runner_handle.h"
 
 namespace base {
 
@@ -111,7 +110,7 @@
   CHECK(weak_this_);
 
   // We need a SequencedTaskRunnerHandle to run |reply|.
-  DCHECK(SequencedTaskRunnerHandle::IsSet());
+  DCHECK(SequencedTaskRunner::HasCurrentDefault());
 
   auto flag = MakeRefCounted<TaskCancellationFlag>();
 
@@ -124,10 +123,11 @@
       BindOnce(&CancelableTaskTracker::Untrack, Unretained(this), id);
   bool success = task_runner->PostTaskAndReply(
       from_here,
-      BindOnce(&RunIfNotCanceled, SequencedTaskRunnerHandle::Get(), flag,
-               std::move(task)),
-      BindOnce(&RunThenUntrackIfNotCanceled, SequencedTaskRunnerHandle::Get(),
-               flag, std::move(reply), std::move(untrack_closure)));
+      BindOnce(&RunIfNotCanceled, SequencedTaskRunner::GetCurrentDefault(),
+               flag, std::move(task)),
+      BindOnce(&RunThenUntrackIfNotCanceled,
+               SequencedTaskRunner::GetCurrentDefault(), flag, std::move(reply),
+               std::move(untrack_closure)));
 
   if (!success)
     return kBadTaskId;
@@ -139,7 +139,7 @@
 CancelableTaskTracker::TaskId CancelableTaskTracker::NewTrackedTaskId(
     IsCanceledCallback* is_canceled_cb) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK(SequencedTaskRunnerHandle::IsSet());
+  DCHECK(SequencedTaskRunner::HasCurrentDefault());
 
   TaskId id = next_id_;
   next_id_++;  // int64_t is big enough that we ignore the potential overflow.
@@ -152,13 +152,14 @@
       BindOnce(&CancelableTaskTracker::Untrack, Unretained(this), id);
 
   // Will always run |untrack_closure| on current sequence.
-  ScopedClosureRunner untrack_runner(
-      BindOnce(&RunOrPostToTaskRunner, SequencedTaskRunnerHandle::Get(),
-               BindOnce(&RunIfNotCanceled, SequencedTaskRunnerHandle::Get(),
-                        flag, std::move(untrack_closure))));
+  ScopedClosureRunner untrack_runner(BindOnce(
+      &RunOrPostToTaskRunner, SequencedTaskRunner::GetCurrentDefault(),
+      BindOnce(&RunIfNotCanceled, SequencedTaskRunner::GetCurrentDefault(),
+               flag, std::move(untrack_closure))));
 
-  *is_canceled_cb = BindRepeating(&IsCanceled, SequencedTaskRunnerHandle::Get(),
-                                  flag, std::move(untrack_runner));
+  *is_canceled_cb =
+      BindRepeating(&IsCanceled, SequencedTaskRunner::GetCurrentDefault(), flag,
+                    std::move(untrack_runner));
 
   Track(id, std::move(flag));
   return id;
diff --git a/base/task/common/task_annotator_unittest.cc b/base/task/common/task_annotator_unittest.cc
index c4c5e59..5c71c394 100644
--- a/base/task/common/task_annotator_unittest.cc
+++ b/base/task/common/task_annotator_unittest.cc
@@ -15,11 +15,11 @@
 #include "base/strings/stringprintf.h"
 #include "base/synchronization/lock.h"
 #include "base/synchronization/waitable_event.h"
+#include "base/task/single_thread_task_runner.h"
 #include "base/task/thread_pool.h"
 #include "base/test/bind.h"
 #include "base/test/task_environment.h"
 #include "base/threading/thread.h"
-#include "base/threading/thread_task_runner_handle.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace base {
@@ -174,7 +174,8 @@
   // last 4 parents are kept).
   OnceClosure task5 = BindOnce(
       &TaskAnnotatorBacktraceIntegrationTest::VerifyTraceAndPost,
-      Unretained(this), ThreadTaskRunnerHandle::Get(), location5, FROM_HERE,
+      Unretained(this), SingleThreadTaskRunner::GetCurrentDefault(), location5,
+      FROM_HERE,
       ExpectedTrace({location4.program_counter(), location3.program_counter(),
                      location2.program_counter(), location1.program_counter()}),
       0, run_loop.QuitClosure());
@@ -182,32 +183,36 @@
   // Task i=4/3/2/1/0 have tasks [0,i) as parents.
   OnceClosure task4 = BindOnce(
       &TaskAnnotatorBacktraceIntegrationTest::VerifyTraceAndPost,
-      Unretained(this), ThreadTaskRunnerHandle::Get(), location4, location5,
+      Unretained(this), SingleThreadTaskRunner::GetCurrentDefault(), location4,
+      location5,
       ExpectedTrace({location3.program_counter(), location2.program_counter(),
                      location1.program_counter(), location0.program_counter()}),
       0, std::move(task5));
   OnceClosure task3 = BindOnce(
       &TaskAnnotatorBacktraceIntegrationTest::VerifyTraceAndPost,
-      Unretained(this), ThreadTaskRunnerHandle::Get(), location3, location4,
+      Unretained(this), SingleThreadTaskRunner::GetCurrentDefault(), location3,
+      location4,
       ExpectedTrace({location2.program_counter(), location1.program_counter(),
                      location0.program_counter()}),
       0, std::move(task4));
   OnceClosure task2 = BindOnce(
       &TaskAnnotatorBacktraceIntegrationTest::VerifyTraceAndPost,
-      Unretained(this), ThreadTaskRunnerHandle::Get(), location2, location3,
+      Unretained(this), SingleThreadTaskRunner::GetCurrentDefault(), location2,
+      location3,
       ExpectedTrace({location1.program_counter(), location0.program_counter()}),
       dummy_ipc_hash, std::move(task3));
   OnceClosure task1 = BindOnce(
       &TaskAnnotatorBacktraceIntegrationTest::VerifyTraceAndPostWithIpcContext,
-      Unretained(this), ThreadTaskRunnerHandle::Get(), location1, location2,
-      ExpectedTrace({location0.program_counter()}), 0, std::move(task2),
-      dummy_ipc_hash);
+      Unretained(this), SingleThreadTaskRunner::GetCurrentDefault(), location1,
+      location2, ExpectedTrace({location0.program_counter()}), 0,
+      std::move(task2), dummy_ipc_hash);
   OnceClosure task0 =
       BindOnce(&TaskAnnotatorBacktraceIntegrationTest::VerifyTraceAndPost,
-               Unretained(this), ThreadTaskRunnerHandle::Get(), location0,
-               location1, ExpectedTrace({}), 0, std::move(task1));
+               Unretained(this), SingleThreadTaskRunner::GetCurrentDefault(),
+               location0, location1, ExpectedTrace({}), 0, std::move(task1));
 
-  ThreadTaskRunnerHandle::Get()->PostTask(location0, std::move(task0));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(location0,
+                                                        std::move(task0));
 
   run_loop.Run();
 }
@@ -220,7 +225,7 @@
   // based SequencedTaskRunner, and a ThreadPool based
   // SingleThreadTaskRunner) to verify that TaskAnnotator can capture backtraces
   // for PostTasks back-and-forth between these.
-  auto main_thread_a = ThreadTaskRunnerHandle::Get();
+  auto main_thread_a = SingleThreadTaskRunner::GetCurrentDefault();
   auto task_runner_b = ThreadPool::CreateSingleThreadTaskRunner({});
   auto task_runner_c = ThreadPool::CreateSequencedTaskRunner(
       {base::MayBlock(), base::WithBaseSyncPrimitives()});
@@ -359,19 +364,22 @@
   // 4.
   OnceClosure task5 = BindOnce(
       &TaskAnnotatorBacktraceIntegrationTest::VerifyTraceAndPost,
-      Unretained(this), ThreadTaskRunnerHandle::Get(), location5, FROM_HERE,
+      Unretained(this), SingleThreadTaskRunner::GetCurrentDefault(), location5,
+      FROM_HERE,
       ExpectedTrace({location4.program_counter(), location3.program_counter(),
                      location2.program_counter(), location1.program_counter()}),
       0, run_loop.QuitClosure());
   OnceClosure task4 = BindOnce(
       &TaskAnnotatorBacktraceIntegrationTest::VerifyTraceAndPost,
-      Unretained(this), ThreadTaskRunnerHandle::Get(), location4, location5,
+      Unretained(this), SingleThreadTaskRunner::GetCurrentDefault(), location4,
+      location5,
       ExpectedTrace({location3.program_counter(), location2.program_counter(),
                      location1.program_counter(), location0.program_counter()}),
       0, std::move(task5));
   OnceClosure task3 = BindOnce(
       &TaskAnnotatorBacktraceIntegrationTest::VerifyTraceAndPost,
-      Unretained(this), ThreadTaskRunnerHandle::Get(), location3, location4,
+      Unretained(this), SingleThreadTaskRunner::GetCurrentDefault(), location3,
+      location4,
       ExpectedTrace({location2.program_counter(), location1.program_counter(),
                      location0.program_counter()}),
       0, std::move(task4));
@@ -382,7 +390,8 @@
 
   OnceClosure task2 = BindOnce(
       &TaskAnnotatorBacktraceIntegrationTest::VerifyTraceAndPost,
-      Unretained(this), ThreadTaskRunnerHandle::Get(), location2, location3,
+      Unretained(this), SingleThreadTaskRunner::GetCurrentDefault(), location2,
+      location3,
       ExpectedTrace({location1.program_counter(), location0.program_counter()}),
       0, std::move(run_task_3_then_quit_nested_loop1));
 
@@ -399,23 +408,26 @@
           // This context should not leak out of the inner loop and color the
           // tasks in the outer loop.
           TaskAnnotator::ScopedSetIpcHash scoped_ipc_hash(dummy_ipc_hash1);
-          ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, DoNothing());
+          SingleThreadTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE,
+                                                                DoNothing());
           nested_run_loop->RunUntilIdle();
         }
-        ThreadTaskRunnerHandle::Get()->PostTask(location2, std::move(task2));
+        SingleThreadTaskRunner::GetCurrentDefault()->PostTask(location2,
+                                                              std::move(task2));
       }),
       Unretained(&nested_run_loop2), location2, std::move(task2));
 
   OnceClosure task0 = BindOnce(
       &TaskAnnotatorBacktraceIntegrationTest::VerifyTraceAndPostWithIpcContext,
-      Unretained(this), ThreadTaskRunnerHandle::Get(), location0, location1,
-      ExpectedTrace({}), 0, std::move(task1), dummy_ipc_hash);
+      Unretained(this), SingleThreadTaskRunner::GetCurrentDefault(), location0,
+      location1, ExpectedTrace({}), 0, std::move(task1), dummy_ipc_hash);
 
-  ThreadTaskRunnerHandle::Get()->PostTask(location0, std::move(task0));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(location0,
+                                                        std::move(task0));
 
   {
     TaskAnnotator::ScopedSetIpcHash scoped_ipc_hash(dummy_ipc_hash2);
-    ThreadTaskRunnerHandle::Get()->PostTask(
+    SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
         FROM_HERE,
         BindOnce(&RunLoop::Run, Unretained(&nested_run_loop1), FROM_HERE));
   }
diff --git a/base/task/sequence_manager/sequence_manager_impl_unittest.cc b/base/task/sequence_manager/sequence_manager_impl_unittest.cc
index 3a4c600..ef12ba30 100644
--- a/base/task/sequence_manager/sequence_manager_impl_unittest.cc
+++ b/base/task/sequence_manager/sequence_manager_impl_unittest.cc
@@ -54,7 +54,6 @@
 #include "base/test/test_simple_task_runner.h"
 #include "base/threading/sequence_local_storage_slot.h"
 #include "base/threading/thread.h"
-#include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
 #include "base/trace_event/base_tracing.h"
 #include "base/tracing_buildflags.h"
@@ -182,7 +181,7 @@
                                            test_task_runner_)),
         sequence_manager_(SequenceManagerForTest::Create(
             nullptr,
-            ThreadTaskRunnerHandle::Get(),
+            SingleThreadTaskRunner::GetCurrentDefault(),
             mock_tick_clock(),
             SequenceManager::Settings::Builder()
                 .SetMessagePumpType(MessagePumpType::DEFAULT)
@@ -4301,7 +4300,7 @@
   base_sequence_manager->SetDefaultTaskRunner(queue->task_runner());
 
   scoped_refptr<SingleThreadTaskRunner> original_task_runner =
-      ThreadTaskRunnerHandle::Get();
+      SingleThreadTaskRunner::GetCurrentDefault();
   scoped_refptr<SingleThreadTaskRunner> custom_task_runner =
       MakeRefCounted<TestSimpleTaskRunner>();
   {
@@ -4309,9 +4308,9 @@
         CreateSequenceManagerOnCurrentThread(SequenceManager::Settings());
 
     manager->SetDefaultTaskRunner(custom_task_runner);
-    DCHECK_EQ(custom_task_runner, ThreadTaskRunnerHandle::Get());
+    DCHECK_EQ(custom_task_runner, SingleThreadTaskRunner::GetCurrentDefault());
   }
-  DCHECK_EQ(original_task_runner, ThreadTaskRunnerHandle::Get());
+  DCHECK_EQ(original_task_runner, SingleThreadTaskRunner::GetCurrentDefault());
 }
 
 TEST_P(SequenceManagerTest, CanceledTasksInQueueCantMakeOtherTasksSkipAhead) {
@@ -4499,8 +4498,8 @@
   bool run = false;
   task_queue->task_runner()->PostTask(
       FROM_HERE, RunOnDestruction(BindLambdaForTesting([&]() {
-        ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                                base::BindOnce(&NopTask));
+        SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+            FROM_HERE, base::BindOnce(&NopTask));
         run = true;
       })));
 
@@ -5482,11 +5481,12 @@
   sequence_manager->SetDefaultTaskRunner(queue->task_runner());
 
   scoped_refptr<SingleThreadTaskRunner> expected_task_runner =
-      ThreadTaskRunnerHandle::Get();
+      SingleThreadTaskRunner::GetCurrentDefault();
 
   StrictMock<MockCallback<base::OnceCallback<void()>>> cb;
   EXPECT_CALL(cb, Run).WillOnce(testing::Invoke([expected_task_runner]() {
-    EXPECT_EQ(ThreadTaskRunnerHandle::Get(), expected_task_runner);
+    EXPECT_EQ(SingleThreadTaskRunner::GetCurrentDefault(),
+              expected_task_runner);
   }));
 
   static base::SequenceLocalStorageSlot<std::unique_ptr<DestructionCallback>>
diff --git a/base/task/sequence_manager/thread_controller_with_message_pump_impl_unittest.cc b/base/task/sequence_manager/thread_controller_with_message_pump_impl_unittest.cc
index 71ac09b..7ab3f5f 100644
--- a/base/task/sequence_manager/thread_controller_with_message_pump_impl_unittest.cc
+++ b/base/task/sequence_manager/thread_controller_with_message_pump_impl_unittest.cc
@@ -21,7 +21,6 @@
 #include "base/test/mock_callback.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/simple_test_tick_clock.h"
-#include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -446,13 +445,13 @@
   scoped_refptr<SingleThreadTaskRunner> task_runner1 =
       MakeRefCounted<FakeTaskRunner>();
   thread_controller_.SetDefaultTaskRunner(task_runner1);
-  EXPECT_EQ(task_runner1, ThreadTaskRunnerHandle::Get());
+  EXPECT_EQ(task_runner1, SingleThreadTaskRunner::GetCurrentDefault());
 
   // Check that we are correctly supporting overriding.
   scoped_refptr<SingleThreadTaskRunner> task_runner2 =
       MakeRefCounted<FakeTaskRunner>();
   thread_controller_.SetDefaultTaskRunner(task_runner2);
-  EXPECT_EQ(task_runner2, ThreadTaskRunnerHandle::Get());
+  EXPECT_EQ(task_runner2, SingleThreadTaskRunner::GetCurrentDefault());
 }
 
 TEST_F(ThreadControllerWithMessagePumpTest, EnsureWorkScheduled) {
diff --git a/base/task/single_thread_task_executor_unittest.cc b/base/task/single_thread_task_executor_unittest.cc
index e1d61f9..17218b9 100644
--- a/base/task/single_thread_task_executor_unittest.cc
+++ b/base/task/single_thread_task_executor_unittest.cc
@@ -35,7 +35,6 @@
 #include "base/threading/platform_thread.h"
 #include "base/threading/sequence_local_storage_slot.h"
 #include "base/threading/thread.h"
-#include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -287,7 +286,7 @@
 void RecursiveFunc(TaskList* order, int cookie, int depth) {
   order->RecordStart(RECURSIVE, cookie);
   if (depth > 0) {
-    ThreadTaskRunnerHandle::Get()->PostTask(
+    SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
         FROM_HERE, BindOnce(&RecursiveFunc, order, cookie, depth - 1));
   }
   order->RecordEnd(RECURSIVE, cookie);
@@ -548,20 +547,20 @@
   // Add tests to message loop
   scoped_refptr<Foo> foo(new Foo());
   std::string a("a"), b("b"), c("c"), d("d");
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          BindOnce(&Foo::Test0, foo));
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, BindOnce(&Foo::Test0, foo));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, BindOnce(&Foo::Test1ConstRef, foo, a));
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          BindOnce(&Foo::Test1Ptr, foo, &b));
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          BindOnce(&Foo::Test1Int, foo, 100));
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, BindOnce(&Foo::Test1Ptr, foo, &b));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, BindOnce(&Foo::Test1Int, foo, 100));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, BindOnce(&Foo::Test2Ptr, foo, &a, &c));
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, BindOnce(&Foo::Test2Mixed, foo, a, &d));
   // After all tests, post a message that will shut down the message loop
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, BindOnce(&RunLoop::QuitCurrentWhenIdleDeprecated));
 
   // Now kick things off
@@ -747,7 +746,7 @@
   ~RecordDeletionProbe() {
     *was_deleted_ = true;
     if (post_on_delete_.get())
-      ThreadTaskRunnerHandle::Get()->PostTask(
+      SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
           FROM_HERE, BindOnce(&RecordDeletionProbe::Run, post_on_delete_));
   }
 
@@ -808,8 +807,8 @@
 void NestingFunc(int* depth) {
   if (*depth > 0) {
     *depth -= 1;
-    ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                            BindOnce(&NestingFunc, depth));
+    SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+        FROM_HERE, BindOnce(&NestingFunc, depth));
 
     RunLoop(RunLoop::Type::kNestableTasksAllowed).Run();
   }
@@ -822,8 +821,8 @@
   SingleThreadTaskExecutor executor(GetParam());
 
   int depth = 50;
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          BindOnce(&NestingFunc, &depth));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, BindOnce(&NestingFunc, &depth));
   RunLoop().Run();
   EXPECT_EQ(depth, 0);
 }
@@ -832,12 +831,12 @@
   SingleThreadTaskExecutor executor(GetParam());
 
   TaskList order;
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, BindOnce(&RecursiveFunc, &order, 1, 2));
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, BindOnce(&RecursiveFunc, &order, 2, 2));
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          BindOnce(&QuitFunc, &order, 3));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, BindOnce(&QuitFunc, &order, 3));
 
   RunLoop().Run();
 
@@ -874,12 +873,12 @@
 
   TaskList order;
 
-  ThreadTaskRunnerHandle::Get()->PostNonNestableTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostNonNestableTask(
       FROM_HERE, BindOnce(&OrderedFunc, &order, 1));
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          BindOnce(&OrderedFunc, &order, 2));
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          BindOnce(&QuitFunc, &order, 3));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, BindOnce(&OrderedFunc, &order, 2));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, BindOnce(&QuitFunc, &order, 3));
   RunLoop().Run();
 
   // FIFO order.
@@ -914,17 +913,17 @@
 
   TaskList order;
 
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          BindOnce(&FuncThatPumps, &order, 1));
-  ThreadTaskRunnerHandle::Get()->PostNonNestableTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, BindOnce(&FuncThatPumps, &order, 1));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostNonNestableTask(
       FROM_HERE, BindOnce(&OrderedFunc, &order, 2));
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          BindOnce(&OrderedFunc, &order, 3));
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, BindOnce(&OrderedFunc, &order, 3));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, BindOnce(&SleepFunc, &order, 4, Milliseconds(50)));
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          BindOnce(&OrderedFunc, &order, 5));
-  ThreadTaskRunnerHandle::Get()->PostNonNestableTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, BindOnce(&OrderedFunc, &order, 5));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostNonNestableTask(
       FROM_HERE, BindOnce(&QuitFunc, &order, 6));
 
   RunLoop().Run();
@@ -967,18 +966,18 @@
 
   RunLoop nested_run_loop(RunLoop::Type::kNestableTasksAllowed);
 
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE,
       BindOnce(&FuncThatRuns, &order, 1, Unretained(&nested_run_loop)));
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          BindOnce(&OrderedFunc, &order, 2));
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          BindOnce(&FuncThatQuitsNow));
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          BindOnce(&OrderedFunc, &order, 3));
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          BindOnce(&FuncThatQuitsNow));
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, BindOnce(&OrderedFunc, &order, 2));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, BindOnce(&FuncThatQuitsNow));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, BindOnce(&OrderedFunc, &order, 3));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, BindOnce(&FuncThatQuitsNow));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, BindOnce(&OrderedFunc, &order, 4));  // never runs
 
   RunLoop().Run();
@@ -1003,15 +1002,15 @@
   RunLoop outer_run_loop;
   RunLoop nested_run_loop(RunLoop::Type::kNestableTasksAllowed);
 
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE,
       BindOnce(&FuncThatRuns, &order, 1, Unretained(&nested_run_loop)));
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          outer_run_loop.QuitClosure());
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          BindOnce(&OrderedFunc, &order, 2));
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          nested_run_loop.QuitClosure());
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, outer_run_loop.QuitClosure());
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, BindOnce(&OrderedFunc, &order, 2));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, nested_run_loop.QuitClosure());
 
   outer_run_loop.Run();
 
@@ -1033,15 +1032,15 @@
   RunLoop outer_run_loop;
   RunLoop nested_run_loop(RunLoop::Type::kNestableTasksAllowed);
 
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE,
       BindOnce(&FuncThatRuns, &order, 1, Unretained(&nested_run_loop)));
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          nested_run_loop.QuitClosure());
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          BindOnce(&OrderedFunc, &order, 2));
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          outer_run_loop.QuitClosure());
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, nested_run_loop.QuitClosure());
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, BindOnce(&OrderedFunc, &order, 2));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, outer_run_loop.QuitClosure());
 
   outer_run_loop.Run();
 
@@ -1074,9 +1073,9 @@
   RunLoop outer_run_loop;
   RunLoop nested_run_loop;
 
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          nested_run_loop.QuitClosure());
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, nested_run_loop.QuitClosure());
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, BindOnce(&QuitAndRunNestedLoop, &order, 1, &outer_run_loop,
                           &nested_run_loop));
 
@@ -1099,17 +1098,17 @@
   RunLoop nested_run_loop(RunLoop::Type::kNestableTasksAllowed);
   RunLoop bogus_run_loop;
 
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE,
       BindOnce(&FuncThatRuns, &order, 1, Unretained(&nested_run_loop)));
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          bogus_run_loop.QuitClosure());
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          BindOnce(&OrderedFunc, &order, 2));
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          outer_run_loop.QuitClosure());
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          nested_run_loop.QuitClosure());
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, bogus_run_loop.QuitClosure());
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, BindOnce(&OrderedFunc, &order, 2));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, outer_run_loop.QuitClosure());
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, nested_run_loop.QuitClosure());
 
   outer_run_loop.Run();
 
@@ -1134,36 +1133,36 @@
   RunLoop nested_loop3(RunLoop::Type::kNestableTasksAllowed);
   RunLoop nested_loop4(RunLoop::Type::kNestableTasksAllowed);
 
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, BindOnce(&FuncThatRuns, &order, 1, Unretained(&nested_loop1)));
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, BindOnce(&FuncThatRuns, &order, 2, Unretained(&nested_loop2)));
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, BindOnce(&FuncThatRuns, &order, 3, Unretained(&nested_loop3)));
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, BindOnce(&FuncThatRuns, &order, 4, Unretained(&nested_loop4)));
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          BindOnce(&OrderedFunc, &order, 5));
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          outer_run_loop.QuitClosure());
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          BindOnce(&OrderedFunc, &order, 6));
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          nested_loop1.QuitClosure());
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          BindOnce(&OrderedFunc, &order, 7));
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          nested_loop2.QuitClosure());
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          BindOnce(&OrderedFunc, &order, 8));
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          nested_loop3.QuitClosure());
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          BindOnce(&OrderedFunc, &order, 9));
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          nested_loop4.QuitClosure());
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          BindOnce(&OrderedFunc, &order, 10));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, BindOnce(&OrderedFunc, &order, 5));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, outer_run_loop.QuitClosure());
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, BindOnce(&OrderedFunc, &order, 6));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, nested_loop1.QuitClosure());
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, BindOnce(&OrderedFunc, &order, 7));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, nested_loop2.QuitClosure());
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, BindOnce(&OrderedFunc, &order, 8));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, nested_loop3.QuitClosure());
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, BindOnce(&OrderedFunc, &order, 9));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, nested_loop4.QuitClosure());
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, BindOnce(&OrderedFunc, &order, 10));
 
   outer_run_loop.Run();
 
@@ -1200,9 +1199,9 @@
 
   run_loop.Quit();
 
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, BindOnce(&OrderedFunc, &order, 1));  // never runs
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, BindOnce(&FuncThatQuitsNow));  // never runs
 
   run_loop.Run();
@@ -1218,12 +1217,13 @@
 
   RunLoop run_loop;
 
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          BindOnce(&OrderedFunc, &order, 1));
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, run_loop.QuitClosure());
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, BindOnce(&OrderedFunc, &order, 1));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE,
+                                                        run_loop.QuitClosure());
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, BindOnce(&OrderedFunc, &order, 2));  // never runs
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, BindOnce(&FuncThatQuitsNow));  // never runs
 
   run_loop.Run();
@@ -1243,21 +1243,21 @@
 
   RunLoop nested_run_loop(RunLoop::Type::kNestableTasksAllowed);
 
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE,
       BindOnce(&FuncThatRuns, &order, 1, Unretained(&nested_run_loop)));
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          BindOnce(&OrderedFunc, &order, 2));
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          BindOnce(&FuncThatQuitsNow));
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          BindOnce(&OrderedFunc, &order, 3));
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, BindOnce(&OrderedFunc, &order, 2));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, BindOnce(&FuncThatQuitsNow));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, BindOnce(&OrderedFunc, &order, 3));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, nested_run_loop.QuitClosure());  // has no affect
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          BindOnce(&OrderedFunc, &order, 4));
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          BindOnce(&FuncThatQuitsNow));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, BindOnce(&OrderedFunc, &order, 4));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, BindOnce(&FuncThatQuitsNow));
 
   nested_run_loop.allow_quit_current_deprecated_ = true;
 
@@ -1335,7 +1335,7 @@
             // kNestableTasksAllowed (i.e. this is testing that this is
             // processed and doesn't hang).
             RunLoop nested_run_loop(RunLoop::Type::kNestableTasksAllowed);
-            ThreadTaskRunnerHandle::Get()->PostTask(
+            SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
                 FROM_HERE,
                 BindOnce(
                     [](RunLoop* nested_run_loop) {
@@ -1510,8 +1510,8 @@
                        LPARAM lparam,
                        LRESULT* result) {
   if (message == static_cast<UINT>(WM_TIMER)) {
-    ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                            BindOnce(&::PostQuitMessage, 0));
+    SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+        FROM_HERE, BindOnce(&::PostQuitMessage, 0));
   }
   *result = 0;
   return true;
@@ -1524,7 +1524,7 @@
                               LPARAM lparam,
                               LRESULT* result) {
   if (message == static_cast<UINT>(WM_TIMER)) {
-    ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+    SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
         FROM_HERE, BindOnce(&::PostQuitMessage, 0), Milliseconds(10));
   }
   *result = 0;
@@ -1760,8 +1760,9 @@
   TaskList order;
   win::ScopedHandle event(CreateEvent(NULL, FALSE, FALSE, NULL));
   worker.task_runner()->PostTask(
-      FROM_HERE, BindOnce(&RecursiveFuncWin, ThreadTaskRunnerHandle::Get(),
-                          event.get(), true, &order, false));
+      FROM_HERE,
+      BindOnce(&RecursiveFuncWin, SingleThreadTaskRunner::GetCurrentDefault(),
+               event.get(), true, &order, false));
   // Let the other thread execute.
   WaitForSingleObject(event.get(), INFINITE);
   RunLoop().Run();
@@ -1810,8 +1811,9 @@
   TaskList order;
   win::ScopedHandle event(CreateEvent(NULL, FALSE, FALSE, NULL));
   worker.task_runner()->PostTask(
-      FROM_HERE, BindOnce(&RecursiveFuncWin, ThreadTaskRunnerHandle::Get(),
-                          event.get(), false, &order, true));
+      FROM_HERE,
+      BindOnce(&RecursiveFuncWin, SingleThreadTaskRunner::GetCurrentDefault(),
+               event.get(), false, &order, true));
   // Let the other thread execute.
   WaitForSingleObject(event.get(), INFINITE);
   RunLoop().Run();
@@ -1972,11 +1974,11 @@
 
   scoped_refptr<Foo> foo(new Foo());
   std::string a("a");
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, BindOnce(&Foo::Test1ConstRef, foo, a));
 
   // Post quit task;
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, BindOnce(&RunLoop::QuitCurrentWhenIdleDeprecated));
 
   // Now kick things off
@@ -1995,10 +1997,10 @@
 void EmptyFunction() {}
 
 void PostMultipleTasks() {
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          base::BindOnce(&EmptyFunction));
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                          base::BindOnce(&EmptyFunction));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, base::BindOnce(&EmptyFunction));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, base::BindOnce(&EmptyFunction));
 }
 
 static const int kSignalMsg = WM_USER + 2;
@@ -2028,11 +2030,11 @@
       // First, we post a task that will post multiple no-op tasks to make sure
       // that the pump's incoming task queue does not become empty during the
       // test.
-      ThreadTaskRunnerHandle::Get()->PostTask(
+      SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
           FROM_HERE, base::BindOnce(&PostMultipleTasks));
       // Next, we post a task that posts a windows message to trigger the second
       // stage of the test.
-      ThreadTaskRunnerHandle::Get()->PostTask(
+      SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
           FROM_HERE, base::BindOnce(&PostWindowsMessage, hwnd));
       break;
     case 2:
@@ -2041,7 +2043,7 @@
       CurrentThread::ScopedAllowApplicationTasksInNativeNestedLoop
           allow_nestable_tasks;
       bool did_run = false;
-      ThreadTaskRunnerHandle::Get()->PostTask(
+      SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
           FROM_HERE, base::BindOnce(&EndTest, &did_run, hwnd));
       // Run a nested windows-style message loop and verify that our task runs.
       // If it doesn't, then we'll loop here until the test times out.
@@ -2093,10 +2095,10 @@
 
   SequenceLocalStorageSlot<int> slot;
 
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, BindLambdaForTesting([&]() { slot.emplace(11); }));
 
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, BindLambdaForTesting([&]() { EXPECT_EQ(*slot, 11); }));
 
   RunLoop().RunUntilIdle();
@@ -2110,7 +2112,7 @@
 
   {
     SingleThreadTaskExecutor executor;
-    ThreadTaskRunnerHandle::Get()->PostTask(
+    SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
         FROM_HERE, BindLambdaForTesting([&]() { slot.emplace(11); }));
 
     RunLoop().RunUntilIdle();
@@ -2118,7 +2120,7 @@
   }
 
   SingleThreadTaskExecutor executor;
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, BindLambdaForTesting([&]() { EXPECT_FALSE(slot); }));
 
   RunLoop().RunUntilIdle();
@@ -2139,7 +2141,7 @@
   // Post a task that will repost itself on destruction |times| times.
   static void PostTaskWithPostingDestructor(int times) {
     if (times > 0) {
-      ThreadTaskRunnerHandle::Get()->PostTask(
+      SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
           FROM_HERE, DoNothingWithBoundArgs(
                          std::make_unique<PostTaskOnDestroy>(times - 1)));
     }
diff --git a/base/task/task_features.cc b/base/task/task_features.cc
index 14c4af0..a07c711 100644
--- a/base/task/task_features.cc
+++ b/base/task/task_features.cc
@@ -22,6 +22,10 @@
 // must be aware that all tests sharing a process will have the same state,
 // regardless of future ScopedFeatureList instances.
 
+BASE_FEATURE(kUseUtilityThreadGroup,
+             "UseUtilityThreadGroup",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 BASE_FEATURE(kNoWorkerThreadReclaim,
              "NoWorkerThreadReclaim",
              base::FEATURE_DISABLED_BY_DEFAULT);
diff --git a/base/task/task_features.h b/base/task/task_features.h
index bccf10f..c909258 100644
--- a/base/task/task_features.h
+++ b/base/task/task_features.h
@@ -12,6 +12,10 @@
 
 namespace base {
 
+// Under this feature, a utility_thread_group will be created for
+// running USER_VISIBLE tasks.
+BASE_EXPORT BASE_DECLARE_FEATURE(kUseUtilityThreadGroup);
+
 // Under this feature, worker threads are not reclaimed after a timeout. Rather,
 // only excess workers are cleaned up immediately after finishing a task.
 BASE_EXPORT BASE_DECLARE_FEATURE(kNoWorkerThreadReclaim);
diff --git a/base/task/task_runner_unittest.cc b/base/task/task_runner_unittest.cc
index f574e8db..d844046 100644
--- a/base/task/task_runner_unittest.cc
+++ b/base/task/task_runner_unittest.cc
@@ -10,8 +10,8 @@
 #include "base/bind.h"
 #include "base/location.h"
 #include "base/run_loop.h"
+#include "base/task/single_thread_task_runner.h"
 #include "base/test/task_environment.h"
-#include "base/threading/thread_task_runner_handle.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace base {
@@ -96,7 +96,7 @@
   int result = 0;
 
   test::SingleThreadTaskEnvironment task_environment;
-  ThreadTaskRunnerHandle::Get()->PostTaskAndReplyWithResult(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTaskAndReplyWithResult(
       FROM_HERE, BindOnce(&ReturnFourtyTwo), BindOnce(&StoreValue, &result));
 
   RunLoop().RunUntilIdle();
@@ -108,7 +108,7 @@
   int result = 0;
 
   test::SingleThreadTaskEnvironment task_environment;
-  ThreadTaskRunnerHandle::Get()->PostTaskAndReplyWithResult(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTaskAndReplyWithResult(
       FROM_HERE, BindRepeating(&ReturnFourtyTwo),
       BindRepeating(&StoreValue, &result));
 
@@ -121,7 +121,7 @@
   double result = 0;
 
   test::SingleThreadTaskEnvironment task_environment;
-  ThreadTaskRunnerHandle::Get()->PostTaskAndReplyWithResult(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTaskAndReplyWithResult(
       FROM_HERE, BindOnce(&ReturnFourtyTwo),
       BindOnce(&StoreDoubleValue, &result));
 
@@ -132,7 +132,7 @@
 
 TEST_F(TaskRunnerTest, PostTaskAndReplyWithResultPassed) {
   test::SingleThreadTaskEnvironment task_environment;
-  ThreadTaskRunnerHandle::Get()->PostTaskAndReplyWithResult(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTaskAndReplyWithResult(
       FROM_HERE, BindOnce(&CreateFoo), BindOnce(&ExpectFoo));
 
   RunLoop().RunUntilIdle();
@@ -143,7 +143,7 @@
 
 TEST_F(TaskRunnerTest, PostTaskAndReplyWithResultPassedFreeProc) {
   test::SingleThreadTaskEnvironment task_environment;
-  ThreadTaskRunnerHandle::Get()->PostTaskAndReplyWithResult(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTaskAndReplyWithResult(
       FROM_HERE, BindOnce(&CreateScopedFoo), BindOnce(&ExpectScopedFoo));
 
   RunLoop().RunUntilIdle();
@@ -158,7 +158,7 @@
   test::SingleThreadTaskEnvironment task_environment;
   int actual = 0;
 
-  ThreadTaskRunnerHandle::Get()->PostTaskAndReplyWithResult(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTaskAndReplyWithResult(
       FROM_HERE, BindOnce(&CreateFooWithoutDefaultConstructor, kSomeVal),
       BindOnce(&SaveFooWithoutDefaultConstructor, &actual));
 
diff --git a/base/task/thread_pool/environment_config.cc b/base/task/thread_pool/environment_config.cc
index 4de0c855..cf497f4f 100644
--- a/base/task/thread_pool/environment_config.cc
+++ b/base/task/thread_pool/environment_config.cc
@@ -46,12 +46,29 @@
   return true;
 }
 
+bool CanUseUtilityThreadTypeForWorkerThreadImpl() {
+#if !BUILDFLAG(IS_ANDROID)
+  // Same as CanUseBackgroundThreadTypeForWorkerThreadImpl()
+  if (!PlatformThread::CanChangeThreadType(ThreadType::kUtility,
+                                           ThreadType::kDefault))
+    return false;
+#endif  // BUILDFLAG(IS_ANDROID)
+
+  return true;
+}
+
 }  // namespace
 
 bool CanUseBackgroundThreadTypeForWorkerThread() {
-  static const bool can_use_background_priority_for_worker_thread =
+  static const bool can_use_background_thread_type_for_worker_thread =
       CanUseBackgroundThreadTypeForWorkerThreadImpl();
-  return can_use_background_priority_for_worker_thread;
+  return can_use_background_thread_type_for_worker_thread;
+}
+
+bool CanUseUtilityThreadTypeForWorkerThread() {
+  static const bool can_use_utility_thread_type_for_worker_thread =
+      CanUseUtilityThreadTypeForWorkerThreadImpl();
+  return can_use_utility_thread_type_for_worker_thread;
 }
 
 }  // namespace internal
diff --git a/base/task/thread_pool/environment_config.h b/base/task/thread_pool/environment_config.h
index 9288d5bc..181e117 100644
--- a/base/task/thread_pool/environment_config.h
+++ b/base/task/thread_pool/environment_config.h
@@ -19,6 +19,8 @@
 enum EnvironmentType {
   FOREGROUND = 0,
   FOREGROUND_BLOCKING,
+  UTILITY,
+  UTILITY_BLOCKING,
   BACKGROUND,
   BACKGROUND_BLOCKING,
   ENVIRONMENT_COUNT  // Always last.
@@ -38,14 +40,20 @@
 constexpr EnvironmentParams kEnvironmentParams[] = {
     {"Foreground", base::ThreadType::kDefault},
     {"ForegroundBlocking", base::ThreadType::kDefault},
+    {"Utility", base::ThreadType::kUtility},
+    {"UtilityBlocking", base::ThreadType::kUtility},
     {"Background", base::ThreadType::kBackground},
     {"BackgroundBlocking", base::ThreadType::kBackground},
 };
 
 // Returns true if this platform supports having WorkerThreads running with a
-// background priority.
+// background thread type.
 bool BASE_EXPORT CanUseBackgroundThreadTypeForWorkerThread();
 
+// Returns true if this platform supports having WorkerThreads running with a
+// utility thread type.
+bool BASE_EXPORT CanUseUtilityThreadTypeForWorkerThread();
+
 }  // namespace internal
 }  // namespace base
 
diff --git a/base/task/thread_pool/environment_config_unittest.cc b/base/task/thread_pool/environment_config_unittest.cc
index 5b438958..3a726d0 100644
--- a/base/task/thread_pool/environment_config_unittest.cc
+++ b/base/task/thread_pool/environment_config_unittest.cc
@@ -20,6 +20,15 @@
 #else
 #error Platform doesn't match any block
 #endif
+
+#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_APPLE) || BUILDFLAG(IS_ANDROID)
+  EXPECT_TRUE(CanUseUtilityThreadTypeForWorkerThread());
+#elif BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_FUCHSIA) || \
+    BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_NACL)
+  EXPECT_FALSE(CanUseUtilityThreadTypeForWorkerThread());
+#else
+#error Platform doesn't match any block
+#endif
 }
 
 }  // namespace internal
diff --git a/base/task/thread_pool/pooled_single_thread_task_runner_manager.cc b/base/task/thread_pool/pooled_single_thread_task_runner_manager.cc
index 77cf5a2..23d88f32 100644
--- a/base/task/thread_pool/pooled_single_thread_task_runner_manager.cc
+++ b/base/task/thread_pool/pooled_single_thread_task_runner_manager.cc
@@ -57,14 +57,23 @@
 // thread pool.
 bool g_manager_is_alive = false;
 
+bool g_use_utility_thread_group = false;
+
 size_t GetEnvironmentIndexForTraits(const TaskTraits& traits) {
   const bool is_background =
       traits.priority() == TaskPriority::BEST_EFFORT &&
       traits.thread_policy() == ThreadPolicy::PREFER_BACKGROUND &&
       CanUseBackgroundThreadTypeForWorkerThread();
-  if (traits.may_block() || traits.with_base_sync_primitives())
-    return is_background ? BACKGROUND_BLOCKING : FOREGROUND_BLOCKING;
-  return is_background ? BACKGROUND : FOREGROUND;
+  const bool is_utility =
+      !is_background && traits.priority() <= TaskPriority::USER_VISIBLE &&
+      traits.thread_policy() == ThreadPolicy::PREFER_BACKGROUND &&
+      g_use_utility_thread_group;
+  if (traits.may_block() || traits.with_base_sync_primitives()) {
+    return is_background ? BACKGROUND_BLOCKING
+           : is_utility  ? UTILITY_BLOCKING
+                         : FOREGROUND_BLOCKING;
+  }
+  return is_background ? BACKGROUND : is_utility ? UTILITY : FOREGROUND;
 }
 
 // Allows for checking the PlatformThread::CurrentRef() against a set
@@ -530,6 +539,7 @@
 PooledSingleThreadTaskRunnerManager::~PooledSingleThreadTaskRunnerManager() {
   DCHECK(g_manager_is_alive);
   g_manager_is_alive = false;
+  g_use_utility_thread_group = false;
 }
 
 void PooledSingleThreadTaskRunnerManager::Start(
@@ -542,6 +552,9 @@
   io_thread_task_runner_ = std::move(io_thread_task_runner);
 #endif  // (BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_NACL)) || BUILDFLAG(IS_FUCHSIA)
 
+  g_use_utility_thread_group = CanUseUtilityThreadTypeForWorkerThread() &&
+                               FeatureList::IsEnabled(kUseUtilityThreadGroup);
+
   decltype(workers_) workers_to_start;
   {
     CheckedAutoLock auto_lock(lock_);
@@ -635,10 +648,7 @@
         worker_name += "Shared";
       worker_name += environment_params.name_suffix;
       worker = CreateAndRegisterWorkerThread<DelegateType>(
-          worker_name, thread_mode,
-          CanUseBackgroundThreadTypeForWorkerThread()
-              ? environment_params.thread_type_hint
-              : ThreadType::kDefault);
+          worker_name, thread_mode, environment_params.thread_type_hint);
       new_worker = true;
     }
     started = started_;
diff --git a/base/task/thread_pool/pooled_single_thread_task_runner_manager_unittest.cc b/base/task/thread_pool/pooled_single_thread_task_runner_manager_unittest.cc
index 95b36d51..20da3ce 100644
--- a/base/task/thread_pool/pooled_single_thread_task_runner_manager_unittest.cc
+++ b/base/task/thread_pool/pooled_single_thread_task_runner_manager_unittest.cc
@@ -18,6 +18,7 @@
 #include "base/task/thread_pool/test_utils.h"
 #include "base/test/bind.h"
 #include "base/test/gtest_util.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/test/test_timeouts.h"
 #include "base/test/test_waitable_event.h"
 #include "base/threading/platform_thread.h"
@@ -224,9 +225,15 @@
 
 class PooledSingleThreadTaskRunnerManagerCommonTest
     : public PooledSingleThreadTaskRunnerManagerTest,
-      public ::testing::WithParamInterface<SingleThreadTaskRunnerThreadMode> {
+      public ::testing::WithParamInterface<
+          std::tuple<SingleThreadTaskRunnerThreadMode,
+                     bool /* enable_utility_threads */>> {
  public:
-  PooledSingleThreadTaskRunnerManagerCommonTest() = default;
+  PooledSingleThreadTaskRunnerManagerCommonTest() {
+    if (std::get<1>(GetParam())) {
+      feature_list_.InitWithFeatures({kUseUtilityThreadGroup}, {});
+    }
+  }
   PooledSingleThreadTaskRunnerManagerCommonTest(
       const PooledSingleThreadTaskRunnerManagerCommonTest&) = delete;
   PooledSingleThreadTaskRunnerManagerCommonTest& operator=(
@@ -235,8 +242,17 @@
   scoped_refptr<SingleThreadTaskRunner> CreateTaskRunner(
       TaskTraits traits = {}) {
     return single_thread_task_runner_manager_->CreateSingleThreadTaskRunner(
-        traits, GetParam());
+        traits, GetSingleThreadTaskRunnerThreadMode());
   }
+
+  SingleThreadTaskRunnerThreadMode GetSingleThreadTaskRunnerThreadMode() const {
+    return std::get<0>(GetParam());
+  }
+
+ protected:
+  const bool use_utility_thread_group_ =
+      CanUseUtilityThreadTypeForWorkerThread() && std::get<1>(GetParam());
+  base::test::ScopedFeatureList feature_list_;
 };
 
 }  // namespace
@@ -248,15 +264,18 @@
   } test_cases[] = {
       {{TaskPriority::BEST_EFFORT},
        CanUseBackgroundThreadTypeForWorkerThread() ? ThreadType::kBackground
+       : use_utility_thread_group_                 ? ThreadType::kUtility
                                                    : ThreadType::kDefault},
       {{TaskPriority::BEST_EFFORT, ThreadPolicy::PREFER_BACKGROUND},
        CanUseBackgroundThreadTypeForWorkerThread() ? ThreadType::kBackground
+       : use_utility_thread_group_                 ? ThreadType::kUtility
                                                    : ThreadType::kDefault},
       {{TaskPriority::BEST_EFFORT, ThreadPolicy::MUST_USE_FOREGROUND},
        ThreadType::kDefault},
-      {{TaskPriority::USER_VISIBLE}, ThreadType::kDefault},
+      {{TaskPriority::USER_VISIBLE},
+       use_utility_thread_group_ ? ThreadType::kUtility : ThreadType::kDefault},
       {{TaskPriority::USER_VISIBLE, ThreadPolicy::PREFER_BACKGROUND},
-       ThreadType::kDefault},
+       use_utility_thread_group_ ? ThreadType::kUtility : ThreadType::kDefault},
       {{TaskPriority::USER_VISIBLE, ThreadPolicy::MUST_USE_FOREGROUND},
        ThreadType::kDefault},
       {{TaskPriority::USER_BLOCKING}, ThreadType::kDefault},
@@ -282,14 +301,20 @@
 
 TEST_P(PooledSingleThreadTaskRunnerManagerCommonTest, ThreadNamesSet) {
   const std::string maybe_shared(
-      GetParam() == SingleThreadTaskRunnerThreadMode::DEDICATED ? ""
-                                                                : "Shared");
+      GetSingleThreadTaskRunnerThreadMode() ==
+              SingleThreadTaskRunnerThreadMode::DEDICATED
+          ? ""
+          : "Shared");
   const std::string background =
       "^ThreadPoolSingleThread" + maybe_shared + "Background\\d+$";
+  const std::string utility =
+      "^ThreadPoolSingleThread" + maybe_shared + "Utility\\d+$";
   const std::string foreground =
       "^ThreadPoolSingleThread" + maybe_shared + "Foreground\\d+$";
   const std::string background_blocking =
       "^ThreadPoolSingleThread" + maybe_shared + "BackgroundBlocking\\d+$";
+  const std::string utility_blocking =
+      "^ThreadPoolSingleThread" + maybe_shared + "UtilityBlocking\\d+$";
   const std::string foreground_blocking =
       "^ThreadPoolSingleThread" + maybe_shared + "ForegroundBlocking\\d+$";
 
@@ -299,14 +324,19 @@
   } test_cases[] = {
       // Non-MayBlock()
       {{TaskPriority::BEST_EFFORT},
-       CanUseBackgroundThreadTypeForWorkerThread() ? background : foreground},
+       CanUseBackgroundThreadTypeForWorkerThread() ? background
+       : use_utility_thread_group_                 ? utility
+                                                   : foreground},
       {{TaskPriority::BEST_EFFORT, ThreadPolicy::PREFER_BACKGROUND},
-       CanUseBackgroundThreadTypeForWorkerThread() ? background : foreground},
+       CanUseBackgroundThreadTypeForWorkerThread() ? background
+       : use_utility_thread_group_                 ? utility
+                                                   : foreground},
       {{TaskPriority::BEST_EFFORT, ThreadPolicy::MUST_USE_FOREGROUND},
        foreground},
-      {{TaskPriority::USER_VISIBLE}, foreground},
+      {{TaskPriority::USER_VISIBLE},
+       use_utility_thread_group_ ? utility : foreground},
       {{TaskPriority::USER_VISIBLE, ThreadPolicy::PREFER_BACKGROUND},
-       foreground},
+       use_utility_thread_group_ ? utility : foreground},
       {{TaskPriority::USER_VISIBLE, ThreadPolicy::MUST_USE_FOREGROUND},
        foreground},
       {{TaskPriority::USER_BLOCKING}, foreground},
@@ -318,17 +348,20 @@
       // MayBlock()
       {{TaskPriority::BEST_EFFORT, MayBlock()},
        CanUseBackgroundThreadTypeForWorkerThread() ? background_blocking
+       : use_utility_thread_group_                 ? utility_blocking
                                                    : foreground_blocking},
       {{TaskPriority::BEST_EFFORT, ThreadPolicy::PREFER_BACKGROUND, MayBlock()},
        CanUseBackgroundThreadTypeForWorkerThread() ? background_blocking
+       : use_utility_thread_group_                 ? utility_blocking
                                                    : foreground_blocking},
       {{TaskPriority::BEST_EFFORT, ThreadPolicy::MUST_USE_FOREGROUND,
         MayBlock()},
        foreground_blocking},
-      {{TaskPriority::USER_VISIBLE, MayBlock()}, foreground_blocking},
+      {{TaskPriority::USER_VISIBLE, MayBlock()},
+       use_utility_thread_group_ ? utility_blocking : foreground_blocking},
       {{TaskPriority::USER_VISIBLE, ThreadPolicy::PREFER_BACKGROUND,
         MayBlock()},
-       foreground_blocking},
+       use_utility_thread_group_ ? utility_blocking : foreground_blocking},
       {{TaskPriority::USER_VISIBLE, ThreadPolicy::MUST_USE_FOREGROUND,
         MayBlock()},
 
@@ -426,8 +459,10 @@
 INSTANTIATE_TEST_SUITE_P(
     SharedAndDedicated,
     PooledSingleThreadTaskRunnerManagerCommonTest,
-    ::testing::Values(SingleThreadTaskRunnerThreadMode::SHARED,
-                      SingleThreadTaskRunnerThreadMode::DEDICATED));
+    ::testing::Combine(
+        ::testing::Values(SingleThreadTaskRunnerThreadMode::SHARED,
+                          SingleThreadTaskRunnerThreadMode::DEDICATED),
+        ::testing::Values(false, true)));
 
 namespace {
 
@@ -537,7 +572,8 @@
 TEST_P(PooledSingleThreadTaskRunnerManagerCommonTest, COMSTAInitialized) {
   scoped_refptr<SingleThreadTaskRunner> com_task_runner =
       single_thread_task_runner_manager_->CreateCOMSTATaskRunner(
-          {TaskShutdownBehavior::BLOCK_SHUTDOWN}, GetParam());
+          {TaskShutdownBehavior::BLOCK_SHUTDOWN},
+          GetSingleThreadTaskRunnerThreadMode());
 
   com_task_runner->PostTask(FROM_HERE, BindOnce(&win::AssertComApartmentType,
                                                 win::ComApartmentType::STA));
diff --git a/base/task/thread_pool/task_tracker_unittest.cc b/base/task/thread_pool/task_tracker_unittest.cc
index 2560d3a6..55b89640 100644
--- a/base/task/thread_pool/task_tracker_unittest.cc
+++ b/base/task/thread_pool/task_tracker_unittest.cc
@@ -36,10 +36,8 @@
 #include "base/test/test_waitable_event.h"
 #include "base/threading/platform_thread.h"
 #include "base/threading/scoped_blocking_call.h"
-#include "base/threading/sequenced_task_runner_handle.h"
 #include "base/threading/simple_thread.h"
 #include "base/threading/thread_restrictions.h"
-#include "base/threading/thread_task_runner_handle.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -540,8 +538,8 @@
 
   // Confirm that the test conditions are right (no TaskRunnerHandles set
   // already).
-  EXPECT_FALSE(ThreadTaskRunnerHandle::IsSet());
-  EXPECT_FALSE(SequencedTaskRunnerHandle::IsSet());
+  EXPECT_FALSE(SingleThreadTaskRunner::HasCurrentDefault());
+  EXPECT_FALSE(SequencedTaskRunner::HasCurrentDefault());
 
   test::QueueAndRunTaskSource(
       tracker,
@@ -549,13 +547,13 @@
                                    std::move(task_runner), execution_mode));
 
   // TaskRunnerHandle state is reset outside of task's scope.
-  EXPECT_FALSE(ThreadTaskRunnerHandle::IsSet());
-  EXPECT_FALSE(SequencedTaskRunnerHandle::IsSet());
+  EXPECT_FALSE(SingleThreadTaskRunner::HasCurrentDefault());
+  EXPECT_FALSE(SequencedTaskRunner::HasCurrentDefault());
 }
 
 static void VerifyNoTaskRunnerHandle() {
-  EXPECT_FALSE(ThreadTaskRunnerHandle::IsSet());
-  EXPECT_FALSE(SequencedTaskRunnerHandle::IsSet());
+  EXPECT_FALSE(SingleThreadTaskRunner::HasCurrentDefault());
+  EXPECT_FALSE(SequencedTaskRunner::HasCurrentDefault());
 }
 
 TEST_P(ThreadPoolTaskTrackerTest, TaskRunnerHandleIsNotSetOnParallel) {
@@ -571,9 +569,9 @@
 
 static void VerifySequencedTaskRunnerHandle(
     const SequencedTaskRunner* expected_task_runner) {
-  EXPECT_FALSE(ThreadTaskRunnerHandle::IsSet());
-  EXPECT_TRUE(SequencedTaskRunnerHandle::IsSet());
-  EXPECT_EQ(expected_task_runner, SequencedTaskRunnerHandle::Get());
+  EXPECT_FALSE(SingleThreadTaskRunner::HasCurrentDefault());
+  EXPECT_TRUE(SequencedTaskRunner::HasCurrentDefault());
+  EXPECT_EQ(expected_task_runner, SequencedTaskRunner::GetCurrentDefault());
 }
 
 TEST_P(ThreadPoolTaskTrackerTest, SequencedTaskRunnerHandleIsSetOnSequenced) {
@@ -594,10 +592,10 @@
 
 static void VerifyThreadTaskRunnerHandle(
     const SingleThreadTaskRunner* expected_task_runner) {
-  EXPECT_TRUE(ThreadTaskRunnerHandle::IsSet());
+  EXPECT_TRUE(SingleThreadTaskRunner::HasCurrentDefault());
   // SequencedTaskRunnerHandle inherits ThreadTaskRunnerHandle for thread.
-  EXPECT_TRUE(SequencedTaskRunnerHandle::IsSet());
-  EXPECT_EQ(expected_task_runner, ThreadTaskRunnerHandle::Get());
+  EXPECT_TRUE(SequencedTaskRunner::HasCurrentDefault());
+  EXPECT_EQ(expected_task_runner, SingleThreadTaskRunner::GetCurrentDefault());
 }
 
 TEST_P(ThreadPoolTaskTrackerTest, ThreadTaskRunnerHandleIsSetOnSingleThreaded) {
diff --git a/base/task/thread_pool/test_task_factory.cc b/base/task/thread_pool/test_task_factory.cc
index 72186140..b1c17f9f 100644
--- a/base/task/thread_pool/test_task_factory.cc
+++ b/base/task/thread_pool/test_task_factory.cc
@@ -10,8 +10,8 @@
 #include "base/check_op.h"
 #include "base/location.h"
 #include "base/synchronization/waitable_event.h"
-#include "base/threading/sequenced_task_runner_handle.h"
-#include "base/threading/thread_task_runner_handle.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/task/single_thread_task_runner.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace base {
@@ -63,21 +63,21 @@
   switch (execution_mode_) {
     case TaskSourceExecutionMode::kJob:
     case TaskSourceExecutionMode::kParallel:
-      EXPECT_FALSE(ThreadTaskRunnerHandle::IsSet());
-      EXPECT_FALSE(SequencedTaskRunnerHandle::IsSet());
+      EXPECT_FALSE(SingleThreadTaskRunner::HasCurrentDefault());
+      EXPECT_FALSE(SequencedTaskRunner::HasCurrentDefault());
       break;
     case TaskSourceExecutionMode::kSequenced:
-      EXPECT_FALSE(ThreadTaskRunnerHandle::IsSet());
-      EXPECT_TRUE(SequencedTaskRunnerHandle::IsSet());
-      EXPECT_EQ(task_runner_, SequencedTaskRunnerHandle::Get());
+      EXPECT_FALSE(SingleThreadTaskRunner::HasCurrentDefault());
+      EXPECT_TRUE(SequencedTaskRunner::HasCurrentDefault());
+      EXPECT_EQ(task_runner_, SequencedTaskRunner::GetCurrentDefault());
       break;
     case TaskSourceExecutionMode::kSingleThread:
       // SequencedTaskRunnerHandle inherits from ThreadTaskRunnerHandle so
       // both are expected to be "set" in the kSingleThread case.
-      EXPECT_TRUE(ThreadTaskRunnerHandle::IsSet());
-      EXPECT_TRUE(SequencedTaskRunnerHandle::IsSet());
-      EXPECT_EQ(task_runner_, ThreadTaskRunnerHandle::Get());
-      EXPECT_EQ(task_runner_, SequencedTaskRunnerHandle::Get());
+      EXPECT_TRUE(SingleThreadTaskRunner::HasCurrentDefault());
+      EXPECT_TRUE(SequencedTaskRunner::HasCurrentDefault());
+      EXPECT_EQ(task_runner_, SingleThreadTaskRunner::GetCurrentDefault());
+      EXPECT_EQ(task_runner_, SequencedTaskRunner::GetCurrentDefault());
       break;
   }
 
diff --git a/base/task/thread_pool/thread_group.cc b/base/task/thread_pool/thread_group.cc
index 1b72cd8..56d87475 100644
--- a/base/task/thread_pool/thread_group.cc
+++ b/base/task/thread_pool/thread_group.cc
@@ -266,6 +266,27 @@
   replacement_thread_group_ = destination_thread_group;
 }
 
+void ThreadGroup::HandoffNonUserBlockingTaskSourcesToOtherThreadGroup(
+    ThreadGroup* destination_thread_group) {
+  CheckedAutoLock current_thread_group_lock(lock_);
+  CheckedAutoLock destination_thread_group_lock(
+      destination_thread_group->lock_);
+  PriorityQueue new_priority_queue;
+  TaskSourceSortKey top_sort_key;
+  // This works because all USER_BLOCKING tasks are at the front of the queue.
+  while (!priority_queue_.IsEmpty() &&
+         (top_sort_key = priority_queue_.PeekSortKey()).priority() ==
+             TaskPriority::USER_BLOCKING) {
+    new_priority_queue.Push(priority_queue_.PopTaskSource(), top_sort_key);
+  }
+  while (!priority_queue_.IsEmpty()) {
+    top_sort_key = priority_queue_.PeekSortKey();
+    destination_thread_group->priority_queue_.Push(
+        priority_queue_.PopTaskSource(), top_sort_key);
+  }
+  priority_queue_ = std::move(new_priority_queue);
+}
+
 bool ThreadGroup::ShouldYield(TaskSourceSortKey sort_key) {
   DCHECK(TS_UNCHECKED_READ(max_allowed_sort_key_).is_lock_free());
 
diff --git a/base/task/thread_pool/thread_group.h b/base/task/thread_pool/thread_group.h
index 674250f..f2c8da5e 100644
--- a/base/task/thread_pool/thread_group.h
+++ b/base/task/thread_pool/thread_group.h
@@ -96,6 +96,11 @@
   void InvalidateAndHandoffAllTaskSourcesToOtherThreadGroup(
       ThreadGroup* destination_thread_group);
 
+  // Move all task sources except the ones with TaskPriority::USER_BLOCKING,
+  // from this ThreadGroup's PriorityQueue to the |destination_thread_group|'s.
+  void HandoffNonUserBlockingTaskSourcesToOtherThreadGroup(
+      ThreadGroup* destination_thread_group);
+
   // Returns true if a task with |sort_key| running in this thread group should
   // return ASAP, either because its priority is not allowed to run or because
   // work of higher priority is pending. Thread-safe but may return an outdated
diff --git a/base/task/thread_pool/thread_group_impl.cc b/base/task/thread_pool/thread_group_impl.cc
index fc79c6f..32036d4 100644
--- a/base/task/thread_pool/thread_group_impl.cc
+++ b/base/task/thread_pool/thread_group_impl.cc
@@ -341,8 +341,11 @@
                                  StringPiece thread_group_label,
                                  ThreadType thread_type_hint,
                                  TrackedRef<TaskTracker> task_tracker,
-                                 TrackedRef<Delegate> delegate)
-    : ThreadGroup(std::move(task_tracker), std::move(delegate)),
+                                 TrackedRef<Delegate> delegate,
+                                 ThreadGroup* predecessor_thread_group)
+    : ThreadGroup(std::move(task_tracker),
+                  std::move(delegate),
+                  predecessor_thread_group),
       histogram_label_(histogram_label),
       thread_group_label_(thread_group_label),
       thread_type_hint_(thread_type_hint),
@@ -367,12 +370,13 @@
   in_start().no_worker_reclaim = FeatureList::IsEnabled(kNoWorkerThreadReclaim);
   in_start().may_block_threshold =
       may_block_threshold ? may_block_threshold.value()
-                          : (thread_type_hint_ == ThreadType::kDefault
+                          : (thread_type_hint_ != ThreadType::kBackground
                                  ? kForegroundMayBlockThreshold
                                  : kBackgroundMayBlockThreshold);
   in_start().blocked_workers_poll_period =
-      thread_type_hint_ == ThreadType::kDefault ? kForegroundBlockedWorkersPoll
-                                                : kBackgroundBlockedWorkersPoll;
+      thread_type_hint_ != ThreadType::kBackground
+          ? kForegroundBlockedWorkersPoll
+          : kBackgroundBlockedWorkersPoll;
 
   ScopedCommandsExecutor executor(this);
   CheckedAutoLock auto_lock(lock_);
diff --git a/base/task/thread_pool/thread_group_impl.h b/base/task/thread_pool/thread_group_impl.h
index 9f6d5e3..2c5d52d 100644
--- a/base/task/thread_pool/thread_group_impl.h
+++ b/base/task/thread_pool/thread_group_impl.h
@@ -59,7 +59,8 @@
                   StringPiece thread_group_label,
                   ThreadType thread_type_hint,
                   TrackedRef<TaskTracker> task_tracker,
-                  TrackedRef<Delegate> delegate);
+                  TrackedRef<Delegate> delegate,
+                  ThreadGroup* predecessor_thread_group = nullptr);
 
   // Creates threads, allowing existing and future tasks to run. The thread
   // group runs at most |max_tasks| / `max_best_effort_tasks` unblocked task
diff --git a/base/task/thread_pool/thread_pool_impl.cc b/base/task/thread_pool/thread_pool_impl.cc
index c0745feb..eac909f1b 100644
--- a/base/task/thread_pool/thread_pool_impl.cc
+++ b/base/task/thread_pool/thread_pool_impl.cc
@@ -41,6 +41,9 @@
 constexpr EnvironmentParams kForegroundPoolEnvironmentParams{
     "Foreground", base::ThreadType::kDefault};
 
+constexpr EnvironmentParams kUtilityPoolEnvironmentParams{
+    "Utility", base::ThreadType::kUtility};
+
 constexpr EnvironmentParams kBackgroundPoolEnvironmentParams{
     "Background", base::ThreadType::kBackground};
 
@@ -74,7 +77,8 @@
 
 ThreadPoolImpl::ThreadPoolImpl(StringPiece histogram_label,
                                std::unique_ptr<TaskTrackerImpl> task_tracker)
-    : task_tracker_(std::move(task_tracker)),
+    : histogram_label_(histogram_label),
+      task_tracker_(std::move(task_tracker)),
       single_thread_task_runner_manager_(task_tracker_->GetTrackedRef(),
                                          &delayed_task_manager_),
       has_disable_best_effort_switch_(HasDisableBestEffortTasksSwitch()),
@@ -109,6 +113,7 @@
 
   // Reset thread groups to release held TrackedRefs, which block teardown.
   foreground_thread_group_.reset();
+  utility_thread_group_.reset();
   background_thread_group_.reset();
 }
 
@@ -137,6 +142,23 @@
   if (g_synchronous_thread_start_for_testing)
     service_thread_.WaitUntilThreadStarted();
 
+  if (FeatureList::IsEnabled(kUseUtilityThreadGroup) &&
+      CanUseUtilityThreadTypeForWorkerThread()) {
+    utility_thread_group_ = std::make_unique<ThreadGroupImpl>(
+        histogram_label_.empty()
+            ? std::string()
+            : JoinString(
+                  {histogram_label_, kUtilityPoolEnvironmentParams.name_suffix},
+                  "."),
+        kUtilityPoolEnvironmentParams.name_suffix,
+        kUtilityPoolEnvironmentParams.thread_type_hint,
+        task_tracker_->GetTrackedRef(), tracked_ref_factory_.GetTrackedRef(),
+        foreground_thread_group_.get());
+    foreground_thread_group_
+        ->HandoffNonUserBlockingTaskSourcesToOtherThreadGroup(
+            utility_thread_group_.get());
+  }
+
   // Update the CanRunPolicy based on |has_disable_best_effort_switch_|.
   UpdateCanRunPolicy();
 
@@ -170,6 +192,14 @@
               worker_thread_observer, worker_environment,
               g_synchronous_thread_start_for_testing);
 
+  if (utility_thread_group_) {
+    static_cast<ThreadGroupImpl*>(utility_thread_group_.get())
+        ->Start(init_params.max_num_utility_threads, max_best_effort_tasks,
+                init_params.suggested_reclaim_time, service_thread_task_runner,
+                worker_thread_observer, worker_environment,
+                g_synchronous_thread_start_for_testing);
+  }
+
   if (background_thread_group_) {
     static_cast<ThreadGroupImpl*>(background_thread_group_.get())
         ->Start(max_best_effort_tasks, max_best_effort_tasks,
@@ -287,6 +317,8 @@
   // Ensures that there are enough background worker to run BLOCK_SHUTDOWN
   // tasks.
   foreground_thread_group_->OnShutdownStarted();
+  if (utility_thread_group_)
+    utility_thread_group_->OnShutdownStarted();
   if (background_thread_group_)
     background_thread_group_->OnShutdownStarted();
 
@@ -312,6 +344,8 @@
   service_thread_.Stop();
   single_thread_task_runner_manager_.JoinForTesting();
   foreground_thread_group_->JoinForTesting();
+  if (utility_thread_group_)
+    utility_thread_group_->JoinForTesting();  // IN-TEST
   if (background_thread_group_)
     background_thread_group_->JoinForTesting();
 #if DCHECK_IS_ON()
@@ -494,6 +528,12 @@
     return background_thread_group_.get();
   }
 
+  if (traits.priority() <= TaskPriority::USER_VISIBLE &&
+      traits.thread_policy() == ThreadPolicy::PREFER_BACKGROUND &&
+      utility_thread_group_) {
+    return utility_thread_group_.get();
+  }
+
   return foreground_thread_group_.get();
 }
 
@@ -514,6 +554,8 @@
 
   task_tracker_->SetCanRunPolicy(can_run_policy);
   foreground_thread_group_->DidUpdateCanRunPolicy();
+  if (utility_thread_group_)
+    utility_thread_group_->DidUpdateCanRunPolicy();
   if (background_thread_group_)
     background_thread_group_->DidUpdateCanRunPolicy();
   single_thread_task_runner_manager_.DidUpdateCanRunPolicy();
diff --git a/base/task/thread_pool/thread_pool_impl.h b/base/task/thread_pool/thread_pool_impl.h
index 97730e1..b1a21ed 100644
--- a/base/task/thread_pool/thread_pool_impl.h
+++ b/base/task/thread_pool/thread_pool_impl.h
@@ -141,12 +141,14 @@
                             scoped_refptr<Sequence> sequence) override;
   bool ShouldYield(const TaskSource* task_source) override;
 
+  const std::string histogram_label_;
   const std::unique_ptr<TaskTrackerImpl> task_tracker_;
   ServiceThread service_thread_;
   DelayedTaskManager delayed_task_manager_;
   PooledSingleThreadTaskRunnerManager single_thread_task_runner_manager_;
 
   std::unique_ptr<ThreadGroup> foreground_thread_group_;
+  std::unique_ptr<ThreadGroup> utility_thread_group_;
   std::unique_ptr<ThreadGroup> background_thread_group_;
 
   // Whether this TaskScheduler was started.
diff --git a/base/task/thread_pool/thread_pool_impl_unittest.cc b/base/task/thread_pool/thread_pool_impl_unittest.cc
index 712c79c..0f882b3 100644
--- a/base/task/thread_pool/thread_pool_impl_unittest.cc
+++ b/base/task/thread_pool/thread_pool_impl_unittest.cc
@@ -33,6 +33,7 @@
 #include "base/task/updateable_sequenced_task_runner.h"
 #include "base/test/bind.h"
 #include "base/test/gtest_util.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/test/test_timeouts.h"
 #include "base/test/test_waitable_event.h"
 #include "base/threading/platform_thread.h"
@@ -63,6 +64,7 @@
 namespace {
 
 constexpr size_t kMaxNumForegroundThreads = 4;
+constexpr size_t kMaxNumUtilityThreads = 2;
 
 struct TraitsExecutionModePair {
   TraitsExecutionModePair(const TaskTraits& traits,
@@ -82,10 +84,21 @@
          CanUseBackgroundThreadTypeForWorkerThread();
 }
 
+// Returns true if a task with |traits| could run at utility thread
+// type on this platform. Even if this returns true, it is possible that the
+// task won't run at efficient thread priority if a native thread group is used
+// or the utility thread group is disabled.
+bool TraitsSupportUtilityThreadType(const TaskTraits& traits) {
+  return traits.priority() <= TaskPriority::USER_VISIBLE &&
+         traits.thread_policy() == ThreadPolicy::PREFER_BACKGROUND &&
+         CanUseUtilityThreadTypeForWorkerThread();
+}
+
 // Verify that the current thread type and I/O restrictions are appropriate to
 // run a Task with |traits|.
 // Note: ExecutionMode is verified inside TestTaskFactory.
-void VerifyTaskEnvironment(const TaskTraits& traits) {
+void VerifyTaskEnvironment(const TaskTraits& traits,
+                           bool use_resource_efficient_group) {
   const std::string thread_name(PlatformThread::GetName());
   const bool is_single_threaded =
       (thread_name.find("SingleThread") != std::string::npos);
@@ -93,7 +106,12 @@
   const bool expect_background_thread_type =
       TraitsSupportBackgroundThreadType(traits);
 
+  const bool expect_utility_thread_type =
+      !TraitsSupportBackgroundThreadType(traits) &&
+      TraitsSupportUtilityThreadType(traits) && use_resource_efficient_group;
+
   EXPECT_EQ(expect_background_thread_type ? ThreadType::kBackground
+            : expect_utility_thread_type  ? ThreadType::kUtility
                                           : ThreadType::kDefault,
             PlatformThread::GetCurrentThreadType());
 
@@ -105,9 +123,10 @@
   // Verify that the thread the task is running on is named as expected.
   EXPECT_THAT(thread_name, ::testing::HasSubstr("ThreadPool"));
 
-  EXPECT_THAT(thread_name, ::testing::HasSubstr(expect_background_thread_type
-                                                    ? "Background"
-                                                    : "Foreground"));
+  EXPECT_THAT(thread_name, ::testing::HasSubstr(
+                               expect_background_thread_type ? "Background"
+                               : expect_utility_thread_type  ? "Utility"
+                                                             : "Foreground"));
 
   if (is_single_threaded) {
     // SingleThread workers discriminate blocking/non-blocking tasks.
@@ -123,29 +142,33 @@
 }
 
 void VerifyTaskEnvironmentAndSignalEvent(const TaskTraits& traits,
+                                         bool use_resource_efficient_group,
                                          TestWaitableEvent* event) {
   DCHECK(event);
-  VerifyTaskEnvironment(traits);
+  VerifyTaskEnvironment(traits, use_resource_efficient_group);
   event->Signal();
 }
 
-void VerifyTimeAndTaskEnvironmentAndSignalEvent(const TaskTraits& traits,
-                                                TimeTicks expected_time,
-                                                TestWaitableEvent* event) {
+void VerifyTimeAndTaskEnvironmentAndSignalEvent(
+    const TaskTraits& traits,
+    bool use_resource_efficient_group,
+    TimeTicks expected_time,
+    TestWaitableEvent* event) {
   DCHECK(event);
   EXPECT_LE(expected_time, TimeTicks::Now());
-  VerifyTaskEnvironment(traits);
+  VerifyTaskEnvironment(traits, use_resource_efficient_group);
   event->Signal();
 }
 
 void VerifyOrderAndTaskEnvironmentAndSignalEvent(
     const TaskTraits& traits,
+    bool use_resource_efficient_group,
     TestWaitableEvent* expected_previous_event,
     TestWaitableEvent* event) {
   DCHECK(event);
   if (expected_previous_event)
     EXPECT_TRUE(expected_previous_event->IsSignaled());
-  VerifyTaskEnvironment(traits);
+  VerifyTaskEnvironment(traits, use_resource_efficient_group);
   event->Signal();
 }
 
@@ -177,9 +200,11 @@
   // |execution_mode|.
   ThreadPostingTasks(ThreadPoolImpl* thread_pool,
                      const TaskTraits& traits,
+                     bool use_resource_efficient_group,
                      TaskSourceExecutionMode execution_mode)
       : SimpleThread("ThreadPostingTasks"),
         traits_(traits),
+        use_resource_efficient_group_(use_resource_efficient_group),
         factory_(CreateTaskRunnerAndExecutionMode(thread_pool,
                                                   traits,
                                                   execution_mode),
@@ -195,11 +220,13 @@
     const size_t kNumTasksPerThread = 150;
     for (size_t i = 0; i < kNumTasksPerThread; ++i) {
       factory_.PostTask(test::TestTaskFactory::PostNestedTask::NO,
-                        BindOnce(&VerifyTaskEnvironment, traits_));
+                        BindOnce(&VerifyTaskEnvironment, traits_,
+                                 use_resource_efficient_group_));
     }
   }
 
   const TaskTraits traits_;
+  bool use_resource_efficient_group_;
   test::TestTaskFactory factory_;
 };
 
@@ -238,10 +265,14 @@
 GetTraitsExecutionModePairsToCoverAllSchedulingOptions() {
   return {TraitsExecutionModePair({TaskPriority::BEST_EFFORT},
                                   TaskSourceExecutionMode::kSequenced),
+          TraitsExecutionModePair({TaskPriority::USER_VISIBLE},
+                                  TaskSourceExecutionMode::kSequenced),
           TraitsExecutionModePair({TaskPriority::USER_BLOCKING},
                                   TaskSourceExecutionMode::kSequenced),
           TraitsExecutionModePair({TaskPriority::BEST_EFFORT},
                                   TaskSourceExecutionMode::kSingleThread),
+          TraitsExecutionModePair({TaskPriority::USER_VISIBLE},
+                                  TaskSourceExecutionMode::kSingleThread),
           TraitsExecutionModePair({TaskPriority::USER_BLOCKING},
                                   TaskSourceExecutionMode::kSingleThread)};
 }
@@ -259,6 +290,8 @@
   ThreadPoolImplTestBase(const ThreadPoolImplTestBase&) = delete;
   ThreadPoolImplTestBase& operator=(const ThreadPoolImplTestBase&) = delete;
 
+  virtual bool GetUseResourceEfficientThreadGroup() const = 0;
+
   void set_worker_thread_observer(
       WorkerThreadObserver* worker_thread_observer) {
     worker_thread_observer_ = worker_thread_observer;
@@ -266,8 +299,12 @@
 
   void StartThreadPool(
       size_t max_num_foreground_threads = kMaxNumForegroundThreads,
+      size_t max_num_utility_threads = kMaxNumUtilityThreads,
       TimeDelta reclaim_time = Seconds(30)) {
-    ThreadPoolInstance::InitParams init_params(max_num_foreground_threads);
+    SetupFeatures();
+
+    ThreadPoolInstance::InitParams init_params(max_num_foreground_threads,
+                                               max_num_utility_threads);
     init_params.suggested_reclaim_time = reclaim_time;
 
     thread_pool_->Start(init_params, worker_thread_observer_);
@@ -288,18 +325,38 @@
   Thread service_thread_;
 
  private:
+  void SetupFeatures() {
+    std::vector<base::test::FeatureRef> features;
+
+    if (GetUseResourceEfficientThreadGroup())
+      features.push_back(kUseUtilityThreadGroup);
+
+    if (!features.empty())
+      feature_list_.InitWithFeatures(features, {});
+  }
+
+  base::test::ScopedFeatureList feature_list_;
   raw_ptr<WorkerThreadObserver> worker_thread_observer_ = nullptr;
   bool did_tear_down_ = false;
 };
 
-using ThreadPoolImplTest = ThreadPoolImplTestBase;
+class ThreadPoolImplTest : public ThreadPoolImplTestBase,
+                           public testing::WithParamInterface<
+                               bool /* use_resource_efficient_thread_group */> {
+ public:
+  bool GetUseResourceEfficientThreadGroup() const override {
+    return GetParam();
+  }
+};
 
 // Tests run for enough traits and execution mode combinations to cover all
 // valid combinations of task destination (background/foreground ThreadGroup,
 // single-thread) and whether the task is affected by a BEST_EFFORT fence.
 class ThreadPoolImplTest_CoverAllSchedulingOptions
     : public ThreadPoolImplTestBase,
-      public testing::WithParamInterface<TraitsExecutionModePair> {
+      public testing::WithParamInterface<
+          std::tuple<bool /* use_resource_efficient_thread_group */,
+                     TraitsExecutionModePair>> {
  public:
   ThreadPoolImplTest_CoverAllSchedulingOptions() = default;
   ThreadPoolImplTest_CoverAllSchedulingOptions(
@@ -307,9 +364,12 @@
   ThreadPoolImplTest_CoverAllSchedulingOptions& operator=(
       const ThreadPoolImplTest_CoverAllSchedulingOptions&) = delete;
 
-  TaskTraits GetTraits() const { return GetParam().traits; }
+  bool GetUseResourceEfficientThreadGroup() const override {
+    return std::get<0>(GetParam());
+  }
+  TaskTraits GetTraits() const { return std::get<1>(GetParam()).traits; }
   TaskSourceExecutionMode GetExecutionMode() const {
-    return GetParam().execution_mode;
+    return std::get<1>(GetParam()).execution_mode;
   }
 };
 
@@ -321,10 +381,11 @@
 TEST_P(ThreadPoolImplTest_CoverAllSchedulingOptions, PostDelayedTaskNoDelay) {
   StartThreadPool();
   TestWaitableEvent task_ran;
-  thread_pool_->PostDelayedTask(FROM_HERE, GetTraits(),
-                                BindOnce(&VerifyTaskEnvironmentAndSignalEvent,
-                                         GetTraits(), Unretained(&task_ran)),
-                                TimeDelta());
+  thread_pool_->PostDelayedTask(
+      FROM_HERE, GetTraits(),
+      BindOnce(&VerifyTaskEnvironmentAndSignalEvent, GetTraits(),
+               GetUseResourceEfficientThreadGroup(), Unretained(&task_ran)),
+      TimeDelta());
   task_ran.Wait();
 }
 
@@ -338,6 +399,7 @@
   thread_pool_->PostDelayedTask(
       FROM_HERE, GetTraits(),
       BindOnce(&VerifyTimeAndTaskEnvironmentAndSignalEvent, GetTraits(),
+               GetUseResourceEfficientThreadGroup(),
                TimeTicks::Now() + TestTimeouts::tiny_timeout(),
                Unretained(&task_ran)),
       TestTimeouts::tiny_timeout());
@@ -381,6 +443,7 @@
           ->PostCancelableDelayedTaskAt(
               subtle::PostDelayedTaskPassKeyForTesting(), FROM_HERE,
               BindOnce(&VerifyTimeAndTaskEnvironmentAndSignalEvent, GetTraits(),
+                       GetUseResourceEfficientThreadGroup(),
                        TimeTicks::Now() + TestTimeouts::tiny_timeout(),
                        Unretained(&task_ran)),
               TimeTicks::Now() + TestTimeouts::tiny_timeout(),
@@ -401,7 +464,8 @@
   const size_t kNumTasksPerTest = 150;
   for (size_t i = 0; i < kNumTasksPerTest; ++i) {
     factory.PostTask(test::TestTaskFactory::PostNestedTask::NO,
-                     BindOnce(&VerifyTaskEnvironment, GetTraits()));
+                     BindOnce(&VerifyTaskEnvironment, GetTraits(),
+                              GetUseResourceEfficientThreadGroup()));
   }
 
   factory.WaitForAllTasksToRun();
@@ -415,7 +479,7 @@
   thread_pool_->PostDelayedTask(
       FROM_HERE, GetTraits(),
       BindOnce(&VerifyTaskEnvironmentAndSignalEvent, GetTraits(),
-               Unretained(&task_running)),
+               GetUseResourceEfficientThreadGroup(), Unretained(&task_running)),
       TimeDelta());
 
   // Wait a little bit to make sure that the task doesn't run before Start().
@@ -437,6 +501,7 @@
   thread_pool_->PostDelayedTask(
       FROM_HERE, GetTraits(),
       BindOnce(&VerifyTimeAndTaskEnvironmentAndSignalEvent, GetTraits(),
+               GetUseResourceEfficientThreadGroup(),
                TimeTicks::Now() + TestTimeouts::tiny_timeout(),
                Unretained(&task_running)),
       TestTimeouts::tiny_timeout());
@@ -456,11 +521,20 @@
 // called.
 TEST_P(ThreadPoolImplTest_CoverAllSchedulingOptions,
        PostTaskViaTaskRunnerBeforeStart) {
+  bool use_resource_efficient_thread_group =
+      GetUseResourceEfficientThreadGroup();
+  // The worker_thread of SingleThreadTaskRunner is selected before
+  // kUseUtilityThreadGroup feature is set up at StartThreadPool().
+  if (GetExecutionMode() == TaskSourceExecutionMode::kSingleThread) {
+    use_resource_efficient_thread_group = false;
+  }
   TestWaitableEvent task_running;
   CreateTaskRunnerAndExecutionMode(thread_pool_.get(), GetTraits(),
                                    GetExecutionMode())
-      ->PostTask(FROM_HERE, BindOnce(&VerifyTaskEnvironmentAndSignalEvent,
-                                     GetTraits(), Unretained(&task_running)));
+      ->PostTask(FROM_HERE,
+                 BindOnce(&VerifyTaskEnvironmentAndSignalEvent, GetTraits(),
+                          use_resource_efficient_thread_group,
+                          Unretained(&task_running)));
 
   // Wait a little bit to make sure that the task doesn't run before Start().
   // Note: This test won't catch a case where the task runs just after the check
@@ -524,7 +598,8 @@
       switches::kDisableBestEffortTasks);
 
   ThreadPoolImpl thread_pool("Test");
-  ThreadPoolInstance::InitParams init_params(kMaxNumForegroundThreads);
+  ThreadPoolInstance::InitParams init_params(kMaxNumForegroundThreads,
+                                             kMaxNumUtilityThreads);
   thread_pool.Start(init_params, nullptr);
 
   AtomicFlag best_effort_can_run;
@@ -714,12 +789,13 @@
 // TaskTraits and ExecutionModes. Verifies that each Task runs on a thread with
 // the expected priority and I/O restrictions and respects the characteristics
 // of its ExecutionMode.
-TEST_F(ThreadPoolImplTest, MultipleTraitsExecutionModePair) {
+TEST_P(ThreadPoolImplTest, MultipleTraitsExecutionModePair) {
   StartThreadPool();
   std::vector<std::unique_ptr<ThreadPostingTasks>> threads_posting_tasks;
   for (const auto& test_params : GetTraitsExecutionModePairs()) {
     threads_posting_tasks.push_back(std::make_unique<ThreadPostingTasks>(
-        thread_pool_.get(), test_params.traits, test_params.execution_mode));
+        thread_pool_.get(), test_params.traits,
+        GetUseResourceEfficientThreadGroup(), test_params.execution_mode));
     threads_posting_tasks.back()->Start();
   }
 
@@ -729,7 +805,7 @@
   }
 }
 
-TEST_F(ThreadPoolImplTest,
+TEST_P(ThreadPoolImplTest,
        GetMaxConcurrentNonBlockedTasksWithTraitsDeprecated) {
   StartThreadPool();
 
@@ -745,10 +821,16 @@
         {MayBlock(), TaskPriority::BEST_EFFORT});
   });
 
-  EXPECT_EQ(kMaxNumForegroundThreads,
+  EXPECT_EQ(GetUseResourceEfficientThreadGroup() &&
+                    CanUseUtilityThreadTypeForWorkerThread()
+                ? kMaxNumUtilityThreads
+                : kMaxNumForegroundThreads,
             thread_pool_->GetMaxConcurrentNonBlockedTasksWithTraitsDeprecated(
                 {TaskPriority::USER_VISIBLE}));
-  EXPECT_EQ(kMaxNumForegroundThreads,
+  EXPECT_EQ(GetUseResourceEfficientThreadGroup() &&
+                    CanUseUtilityThreadTypeForWorkerThread()
+                ? kMaxNumUtilityThreads
+                : kMaxNumForegroundThreads,
             thread_pool_->GetMaxConcurrentNonBlockedTasksWithTraitsDeprecated(
                 {MayBlock(), TaskPriority::USER_VISIBLE}));
   EXPECT_EQ(kMaxNumForegroundThreads,
@@ -761,7 +843,7 @@
 
 // Verify that the RunsTasksInCurrentSequence() method of a SequencedTaskRunner
 // returns false when called from a task that isn't part of the sequence.
-TEST_F(ThreadPoolImplTest, SequencedRunsTasksInCurrentSequence) {
+TEST_P(ThreadPoolImplTest, SequencedRunsTasksInCurrentSequence) {
   StartThreadPool();
   auto single_thread_task_runner = thread_pool_->CreateSingleThreadTaskRunner(
       {}, SingleThreadTaskRunnerThreadMode::SHARED);
@@ -783,7 +865,7 @@
 // Verify that the RunsTasksInCurrentSequence() method of a
 // SingleThreadTaskRunner returns false when called from a task that isn't part
 // of the sequence.
-TEST_F(ThreadPoolImplTest, SingleThreadRunsTasksInCurrentSequence) {
+TEST_P(ThreadPoolImplTest, SingleThreadRunsTasksInCurrentSequence) {
   StartThreadPool();
   auto sequenced_task_runner = thread_pool_->CreateSequencedTaskRunner({});
   auto single_thread_task_runner = thread_pool_->CreateSingleThreadTaskRunner(
@@ -804,7 +886,7 @@
 }
 
 #if BUILDFLAG(IS_WIN)
-TEST_F(ThreadPoolImplTest, COMSTATaskRunnersRunWithCOMSTA) {
+TEST_P(ThreadPoolImplTest, COMSTATaskRunnersRunWithCOMSTA) {
   StartThreadPool();
   auto com_sta_task_runner = thread_pool_->CreateCOMSTATaskRunner(
       {}, SingleThreadTaskRunnerThreadMode::SHARED);
@@ -821,7 +903,7 @@
 }
 #endif  // BUILDFLAG(IS_WIN)
 
-TEST_F(ThreadPoolImplTest, DelayedTasksNotRunAfterShutdown) {
+TEST_P(ThreadPoolImplTest, DelayedTasksNotRunAfterShutdown) {
   StartThreadPool();
   // As with delayed tasks in general, this is racy. If the task does happen to
   // run after Shutdown within the timeout, it will fail this test.
@@ -844,7 +926,7 @@
 
 #if BUILDFLAG(IS_POSIX)
 
-TEST_F(ThreadPoolImplTest, FileDescriptorWatcherNoOpsAfterShutdown) {
+TEST_P(ThreadPoolImplTest, FileDescriptorWatcherNoOpsAfterShutdown) {
   StartThreadPool();
 
   int pipes[2];
@@ -916,7 +998,7 @@
 
 // Verify that tasks posted on the same sequence access the same values on
 // SequenceLocalStorage, and tasks on different sequences see different values.
-TEST_F(ThreadPoolImplTest, SequenceLocalStorage) {
+TEST_P(ThreadPoolImplTest, SequenceLocalStorage) {
   StartThreadPool();
 
   SequenceLocalStorageSlot<int> slot;
@@ -945,7 +1027,7 @@
   thread_pool_->FlushForTesting();
 }
 
-TEST_F(ThreadPoolImplTest, FlushAsyncNoTasks) {
+TEST_P(ThreadPoolImplTest, FlushAsyncNoTasks) {
   StartThreadPool();
   bool called_back = false;
   thread_pool_->FlushAsyncForTesting(
@@ -989,7 +1071,7 @@
 // Integration test that verifies that workers have a frame on their stacks
 // which easily identifies the type of worker and shutdown behavior (useful to
 // diagnose issues from logs without memory dumps).
-TEST_F(ThreadPoolImplTest, MAYBE_IdentifiableStacks) {
+TEST_P(ThreadPoolImplTest, MAYBE_IdentifiableStacks) {
   StartThreadPool();
 
   // Shutdown behaviors and expected stack frames.
@@ -1069,20 +1151,36 @@
   thread_pool_->FlushForTesting();
 }
 
-TEST_F(ThreadPoolImplTest, WorkerThreadObserver) {
+TEST_P(ThreadPoolImplTest, WorkerThreadObserver) {
   testing::StrictMock<test::MockWorkerThreadObserver> observer;
   set_worker_thread_observer(&observer);
 
   // A worker should be created for each thread group. After that, 4 threads
   // should be created for each SingleThreadTaskRunnerThreadMode (8 on Windows).
   const int kExpectedNumForegroundPoolWorkers = 1;
+  const int kExpectedNumUtilityPoolWorkers =
+      GetUseResourceEfficientThreadGroup() &&
+              CanUseUtilityThreadTypeForWorkerThread()
+          ? 1
+          : 0;
   const int kExpectedNumBackgroundPoolWorkers =
       CanUseBackgroundThreadTypeForWorkerThread() ? 1 : 0;
-  const int kExpectedNumPoolWorkers =
-      kExpectedNumForegroundPoolWorkers + kExpectedNumBackgroundPoolWorkers;
+  const int kExpectedNumPoolWorkers = kExpectedNumForegroundPoolWorkers +
+                                      kExpectedNumUtilityPoolWorkers +
+                                      kExpectedNumBackgroundPoolWorkers;
+  const int kExpectedNumSharedSingleThreadedForegroundWorkers = 2;
+  const int kExpectedNumSharedSingleThreadedUtilityWorkers =
+      GetUseResourceEfficientThreadGroup() &&
+              CanUseUtilityThreadTypeForWorkerThread()
+          ? 2
+          : 0;
+  const int kExpectedNumSharedSingleThreadedBackgroundWorkers =
+      CanUseBackgroundThreadTypeForWorkerThread() ? 2 : 0;
   const int kExpectedNumSharedSingleThreadedWorkers =
-      CanUseBackgroundThreadTypeForWorkerThread() ? 4 : 2;
-  const int kExpectedNumDedicatedSingleThreadedWorkers = 4;
+      kExpectedNumSharedSingleThreadedForegroundWorkers +
+      kExpectedNumSharedSingleThreadedUtilityWorkers +
+      kExpectedNumSharedSingleThreadedBackgroundWorkers;
+  const int kExpectedNumDedicatedSingleThreadedWorkers = 6;
 
   const int kExpectedNumCOMSharedSingleThreadedWorkers =
 #if BUILDFLAG(IS_WIN)
@@ -1105,7 +1203,8 @@
 
   // Infinite detach time to prevent workers from invoking
   // OnWorkerThreadMainExit() earlier than expected.
-  StartThreadPool(kMaxNumForegroundThreads, TimeDelta::Max());
+  StartThreadPool(kMaxNumForegroundThreads, kMaxNumUtilityThreads,
+                  TimeDelta::Max());
 
   std::vector<scoped_refptr<SingleThreadTaskRunner>> task_runners;
 
@@ -1115,6 +1214,11 @@
       {TaskPriority::BEST_EFFORT, MayBlock()},
       SingleThreadTaskRunnerThreadMode::SHARED));
   task_runners.push_back(thread_pool_->CreateSingleThreadTaskRunner(
+      {TaskPriority::USER_VISIBLE}, SingleThreadTaskRunnerThreadMode::SHARED));
+  task_runners.push_back(thread_pool_->CreateSingleThreadTaskRunner(
+      {TaskPriority::USER_VISIBLE, MayBlock()},
+      SingleThreadTaskRunnerThreadMode::SHARED));
+  task_runners.push_back(thread_pool_->CreateSingleThreadTaskRunner(
       {TaskPriority::USER_BLOCKING}, SingleThreadTaskRunnerThreadMode::SHARED));
   task_runners.push_back(thread_pool_->CreateSingleThreadTaskRunner(
       {TaskPriority::USER_BLOCKING, MayBlock()},
@@ -1127,6 +1231,12 @@
       {TaskPriority::BEST_EFFORT, MayBlock()},
       SingleThreadTaskRunnerThreadMode::DEDICATED));
   task_runners.push_back(thread_pool_->CreateSingleThreadTaskRunner(
+      {TaskPriority::USER_VISIBLE},
+      SingleThreadTaskRunnerThreadMode::DEDICATED));
+  task_runners.push_back(thread_pool_->CreateSingleThreadTaskRunner(
+      {TaskPriority::USER_VISIBLE, MayBlock()},
+      SingleThreadTaskRunnerThreadMode::DEDICATED));
+  task_runners.push_back(thread_pool_->CreateSingleThreadTaskRunner(
       {TaskPriority::USER_BLOCKING},
       SingleThreadTaskRunnerThreadMode::DEDICATED));
   task_runners.push_back(thread_pool_->CreateSingleThreadTaskRunner(
@@ -1140,6 +1250,11 @@
       {TaskPriority::BEST_EFFORT, MayBlock()},
       SingleThreadTaskRunnerThreadMode::SHARED));
   task_runners.push_back(thread_pool_->CreateCOMSTATaskRunner(
+      {TaskPriority::USER_VISIBLE}, SingleThreadTaskRunnerThreadMode::SHARED));
+  task_runners.push_back(thread_pool_->CreateCOMSTATaskRunner(
+      {TaskPriority::USER_VISIBLE, MayBlock()},
+      SingleThreadTaskRunnerThreadMode::SHARED));
+  task_runners.push_back(thread_pool_->CreateCOMSTATaskRunner(
       {TaskPriority::USER_BLOCKING}, SingleThreadTaskRunnerThreadMode::SHARED));
   task_runners.push_back(thread_pool_->CreateCOMSTATaskRunner(
       {TaskPriority::USER_BLOCKING, MayBlock()},
@@ -1152,6 +1267,12 @@
       {TaskPriority::BEST_EFFORT, MayBlock()},
       SingleThreadTaskRunnerThreadMode::DEDICATED));
   task_runners.push_back(thread_pool_->CreateCOMSTATaskRunner(
+      {TaskPriority::USER_VISIBLE},
+      SingleThreadTaskRunnerThreadMode::DEDICATED));
+  task_runners.push_back(thread_pool_->CreateCOMSTATaskRunner(
+      {TaskPriority::USER_VISIBLE, MayBlock()},
+      SingleThreadTaskRunnerThreadMode::DEDICATED));
+  task_runners.push_back(thread_pool_->CreateCOMSTATaskRunner(
       {TaskPriority::USER_BLOCKING},
       SingleThreadTaskRunnerThreadMode::DEDICATED));
   task_runners.push_back(thread_pool_->CreateCOMSTATaskRunner(
@@ -1179,7 +1300,7 @@
 }
 
 // Verify a basic EnqueueJobTaskSource() runs the worker task.
-TEST_F(ThreadPoolImplTest, ScheduleJobTaskSource) {
+TEST_P(ThreadPoolImplTest, ScheduleJobTaskSource) {
   StartThreadPool();
 
   TestWaitableEvent threads_running;
@@ -1197,24 +1318,24 @@
 
 // Verify that calling ShouldYield() returns true for a job task source that
 // needs to change thread group because of a priority update.
-TEST_F(ThreadPoolImplTest, ThreadGroupChangeShouldYield) {
+TEST_P(ThreadPoolImplTest, ThreadGroupChangeShouldYield) {
   StartThreadPool();
 
   TestWaitableEvent threads_running;
   TestWaitableEvent threads_continue;
 
   auto job_task = base::MakeRefCounted<test::MockJobTask>(
-      BindLambdaForTesting([&threads_running,
-                            &threads_continue](JobDelegate* delegate) {
-        EXPECT_FALSE(delegate->ShouldYield());
+      BindLambdaForTesting(
+          [&threads_running, &threads_continue](JobDelegate* delegate) {
+            EXPECT_FALSE(delegate->ShouldYield());
 
-        threads_running.Signal();
-        threads_continue.Wait();
+            threads_running.Signal();
+            threads_continue.Wait();
 
-        // The task source needs to yield if background thread groups exist.
-        EXPECT_EQ(delegate->ShouldYield(),
-                  CanUseBackgroundThreadTypeForWorkerThread());
-      }),
+            // The task source needs to yield if background thread groups exist.
+            EXPECT_EQ(delegate->ShouldYield(),
+                      CanUseBackgroundThreadTypeForWorkerThread());
+          }),
       /* num_tasks_to_run */ 1);
   scoped_refptr<JobTaskSource> task_source = job_task->GetJobTaskSource(
       FROM_HERE, {TaskPriority::USER_VISIBLE}, thread_pool_.get());
@@ -1305,8 +1426,9 @@
 // Create a series of sample task runners that will post tasks at various
 // initial priorities, then update priority.
 std::vector<std::unique_ptr<TaskRunnerAndEvents>> CreateTaskRunnersAndEvents(
-    ThreadPoolImpl* thread_pool,
+    ThreadPoolImplTest* test,
     ThreadPolicy thread_policy) {
+  ThreadPoolImpl* thread_pool = test->thread_pool_.get();
   std::vector<std::unique_ptr<TaskRunnerAndEvents>> task_runners_and_events;
 
   // -----
@@ -1319,11 +1441,17 @@
 
   // -----
   // Task runner that will start as BEST_EFFORT and update to USER_VISIBLE.
-  // Its task is expected to run after the USER_BLOCKING task runner's task.
+  // Its task is expected to run after the USER_BLOCKING task runner's task,
+  // unless resource-efficient thread group exists, in which case they will run
+  // asynchronously.
+  TestWaitableEvent* expected_previous_event =
+      test->GetUseResourceEfficientThreadGroup()
+          ? nullptr
+          : &task_runners_and_events.back()->task_ran;
   task_runners_and_events.push_back(std::make_unique<TaskRunnerAndEvents>(
       thread_pool->CreateUpdateableSequencedTaskRunner(
           {TaskPriority::BEST_EFFORT, thread_policy}),
-      TaskPriority::USER_VISIBLE, &task_runners_and_events.back()->task_ran));
+      TaskPriority::USER_VISIBLE, expected_previous_event));
 
   // -----
   // Task runner that will start as USER_BLOCKING and update to BEST_EFFORT. Its
@@ -1334,10 +1462,9 @@
   // If the task following the priority update is expected to run in the
   // foreground group, it should be after the task posted to the TaskRunner
   // whose priority is updated to USER_VISIBLE.
-  TestWaitableEvent* expected_previous_event =
-      CanUseBackgroundThreadTypeForWorkerThread()
-          ? nullptr
-          : &task_runners_and_events.back()->task_ran;
+  expected_previous_event = CanUseBackgroundThreadTypeForWorkerThread()
+                                ? nullptr
+                                : &task_runners_and_events.back()->task_ran;
 
   task_runners_and_events.push_back(std::make_unique<TaskRunnerAndEvents>(
       thread_pool->CreateUpdateableSequencedTaskRunner(
@@ -1358,7 +1485,7 @@
 
   test->StartThreadPool(kLocalMaxNumForegroundThreads);
   auto task_runners_and_events =
-      CreateTaskRunnersAndEvents(test->thread_pool_.get(), thread_policy);
+      CreateTaskRunnersAndEvents(test, thread_policy);
 
   // Prevent tasks from running.
   test->thread_pool_->BeginFence();
@@ -1371,6 +1498,7 @@
         BindOnce(
             &VerifyOrderAndTaskEnvironmentAndSignalEvent,
             TaskTraits{task_runner_and_events->updated_priority, thread_policy},
+            test->GetUseResourceEfficientThreadGroup(),
             Unretained(task_runner_and_events->expected_previous_event.get()),
             Unretained(&task_runner_and_events->task_ran)));
   }
@@ -1394,7 +1522,7 @@
                                          ThreadPolicy thread_policy) {
   test->StartThreadPool();
   auto task_runners_and_events =
-      CreateTaskRunnersAndEvents(test->thread_pool_.get(), thread_policy);
+      CreateTaskRunnersAndEvents(test, thread_policy);
 
   // Post blocking tasks to all task runners to prevent tasks from being
   // scheduled later in the test.
@@ -1422,6 +1550,7 @@
         BindOnce(
             &VerifyOrderAndTaskEnvironmentAndSignalEvent,
             TaskTraits{task_runner_and_events->updated_priority, thread_policy},
+            test->GetUseResourceEfficientThreadGroup(),
             Unretained(task_runner_and_events->expected_previous_event),
             Unretained(&task_runner_and_events->task_ran)));
   }
@@ -1437,28 +1566,28 @@
 
 }  // namespace
 
-TEST_F(ThreadPoolImplTest,
+TEST_P(ThreadPoolImplTest,
        UpdatePrioritySequenceNotScheduled_PreferBackground) {
   TestUpdatePrioritySequenceNotScheduled(this, ThreadPolicy::PREFER_BACKGROUND);
 }
 
-TEST_F(ThreadPoolImplTest,
+TEST_P(ThreadPoolImplTest,
        UpdatePrioritySequenceNotScheduled_MustUseForeground) {
   TestUpdatePrioritySequenceNotScheduled(this,
                                          ThreadPolicy::MUST_USE_FOREGROUND);
 }
 
-TEST_F(ThreadPoolImplTest, UpdatePrioritySequenceScheduled_PreferBackground) {
+TEST_P(ThreadPoolImplTest, UpdatePrioritySequenceScheduled_PreferBackground) {
   TestUpdatePrioritySequenceScheduled(this, ThreadPolicy::PREFER_BACKGROUND);
 }
 
-TEST_F(ThreadPoolImplTest, UpdatePrioritySequenceScheduled_MustUseForeground) {
+TEST_P(ThreadPoolImplTest, UpdatePrioritySequenceScheduled_MustUseForeground) {
   TestUpdatePrioritySequenceScheduled(this, ThreadPolicy::MUST_USE_FOREGROUND);
 }
 
 // Verify that a ThreadPolicy has to be specified in TaskTraits to increase
 // TaskPriority from BEST_EFFORT.
-TEST_F(ThreadPoolImplTest, UpdatePriorityFromBestEffortNoThreadPolicy) {
+TEST_P(ThreadPoolImplTest, UpdatePriorityFromBestEffortNoThreadPolicy) {
   testing::GTEST_FLAG(death_test_style) = "threadsafe";
   StartThreadPool();
   {
@@ -1475,11 +1604,15 @@
   }
 }
 
+INSTANTIATE_TEST_SUITE_P(All, ThreadPoolImplTest, ::testing::Bool());
+
 INSTANTIATE_TEST_SUITE_P(
     All,
     ThreadPoolImplTest_CoverAllSchedulingOptions,
-    ::testing::ValuesIn(
-        GetTraitsExecutionModePairsToCoverAllSchedulingOptions()));
+    ::testing::Combine(
+        ::testing::Bool(),
+        ::testing::ValuesIn(
+            GetTraitsExecutionModePairsToCoverAllSchedulingOptions())));
 
 }  // namespace internal
 }  // namespace base
diff --git a/base/task/thread_pool/thread_pool_instance.cc b/base/task/thread_pool/thread_pool_instance.cc
index be19c0c8..d5173c7a 100644
--- a/base/task/thread_pool/thread_pool_instance.cc
+++ b/base/task/thread_pool/thread_pool_instance.cc
@@ -21,10 +21,26 @@
 // |g_thread_pool| is intentionally leaked on shutdown.
 ThreadPoolInstance* g_thread_pool = nullptr;
 
+size_t GetDefaultMaxNumUtilityThreads(size_t max_num_foreground_threads_in) {
+  int num_of_efficient_processors = SysInfo::NumberOfEfficientProcessors();
+  if (num_of_efficient_processors != 0) {
+    DCHECK_GT(num_of_efficient_processors, 0);
+    return static_cast<size_t>(num_of_efficient_processors);
+  }
+  return std::max<size_t>(1, max_num_foreground_threads_in / 2);
+}
+
 }  // namespace
 
 ThreadPoolInstance::InitParams::InitParams(size_t max_num_foreground_threads_in)
-    : max_num_foreground_threads(max_num_foreground_threads_in) {}
+    : max_num_foreground_threads(max_num_foreground_threads_in),
+      max_num_utility_threads(
+          GetDefaultMaxNumUtilityThreads(max_num_foreground_threads_in)) {}
+
+ThreadPoolInstance::InitParams::InitParams(size_t max_num_foreground_threads_in,
+                                           size_t max_num_utility_threads_in)
+    : max_num_foreground_threads(max_num_foreground_threads_in),
+      max_num_utility_threads(max_num_utility_threads_in) {}
 
 ThreadPoolInstance::InitParams::~InitParams() = default;
 
diff --git a/base/task/thread_pool/thread_pool_instance.h b/base/task/thread_pool/thread_pool_instance.h
index a8508cf..4f021466 100644
--- a/base/task/thread_pool/thread_pool_instance.h
+++ b/base/task/thread_pool/thread_pool_instance.h
@@ -58,12 +58,18 @@
     };
 
     InitParams(size_t max_num_foreground_threads_in);
+    InitParams(size_t max_num_foreground_threads_in,
+               size_t max_num_utility_threads_in);
     ~InitParams();
 
     // Maximum number of unblocked tasks that can run concurrently in the
     // foreground thread group.
     size_t max_num_foreground_threads;
 
+    // Maximum number of unblocked tasks that can run concurrently in the
+    // utility thread group.
+    size_t max_num_utility_threads;
+
     // Whether COM is initialized when running sequenced and parallel tasks.
     CommonThreadPoolEnvironment common_thread_pool_environment =
         CommonThreadPoolEnvironment::DEFAULT;
diff --git a/base/task/thread_pool/worker_thread.cc b/base/task/thread_pool/worker_thread.cc
index 92ad516..f5807b0 100644
--- a/base/task/thread_pool/worker_thread.cc
+++ b/base/task/thread_pool/worker_thread.cc
@@ -148,6 +148,8 @@
   DCHECK(task_tracker_);
   DCHECK(CanUseBackgroundThreadTypeForWorkerThread() ||
          thread_type_hint_ != ThreadType::kBackground);
+  DCHECK(CanUseUtilityThreadTypeForWorkerThread() ||
+         thread_type_hint != ThreadType::kUtility);
   wake_up_event_.declare_only_used_while_idle();
   wake_up_event_.opt_out_of_wakeup_flow_events();
 }
diff --git a/base/test/launcher/test_launcher.cc b/base/test/launcher/test_launcher.cc
index ff962c1c7..6675bed 100644
--- a/base/test/launcher/test_launcher.cc
+++ b/base/test/launcher/test_launcher.cc
@@ -59,7 +59,6 @@
 #include "base/test/test_switches.h"
 #include "base/test/test_timeouts.h"
 #include "base/threading/thread_restrictions.h"
-#include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
@@ -845,7 +844,7 @@
   for (size_t i = 0; i < runner_count_; i++) {
     task_runners_.push_back(ThreadPool::CreateSequencedTaskRunner(
         {MayBlock(), TaskShutdownBehavior::BLOCK_SHUTDOWN}));
-    ThreadTaskRunnerHandle::Get()->PostTask(
+    SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
         FROM_HERE,
         BindOnce(&TestRunner::LaunchNextTask, weak_ptr_factory_.GetWeakPtr(),
                  task_runners_.back(), FilePath()));
@@ -891,14 +890,15 @@
     task_runner->PostTask(
         FROM_HERE,
         BindOnce(&TestLauncher::LaunchChildGTestProcess, Unretained(launcher_),
-                 ThreadTaskRunnerHandle::Get(), batch, task_temp_dir,
+                 SingleThreadTaskRunner::GetCurrentDefault(), batch,
+                 task_temp_dir,
                  CreateChildTempDirIfSupported(task_temp_dir, child_index++)));
     post_to_current_runner = ShouldReuseStateFromLastBatch(batch);
   }
   task_runner->PostTask(
-      FROM_HERE,
-      BindOnce(&TestRunner::ClearAndLaunchNext, Unretained(this),
-               ThreadTaskRunnerHandle::Get(), task_runner, task_temp_dir));
+      FROM_HERE, BindOnce(&TestRunner::ClearAndLaunchNext, Unretained(this),
+                          SingleThreadTaskRunner::GetCurrentDefault(),
+                          task_runner, task_temp_dir));
 }
 
 // Returns the number of files and directories in |dir|, or 0 if |dir| is empty.
diff --git a/base/test/repeating_test_future_unittest.cc b/base/test/repeating_test_future_unittest.cc
index 96342aa..7ecb777 100644
--- a/base/test/repeating_test_future_unittest.cc
+++ b/base/test/repeating_test_future_unittest.cc
@@ -4,10 +4,10 @@
 
 #include "base/test/repeating_test_future.h"
 
+#include "base/task/single_thread_task_runner.h"
 #include "base/test/bind.h"
 #include "base/test/gtest_util.h"
 #include "base/test/task_environment.h"
-#include "base/threading/thread_task_runner_handle.h"
 #include "testing/gtest/include/gtest/gtest-spi.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -38,7 +38,8 @@
   ~RepeatingTestFutureTest() override = default;
 
   void RunLater(OnceClosure callable) {
-    ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, std::move(callable));
+    SingleThreadTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE,
+                                                          std::move(callable));
   }
 
  private:
diff --git a/base/test/scoped_mock_time_message_loop_task_runner.cc b/base/test/scoped_mock_time_message_loop_task_runner.cc
index 8a17ebb..e4486e4 100644
--- a/base/test/scoped_mock_time_message_loop_task_runner.cc
+++ b/base/test/scoped_mock_time_message_loop_task_runner.cc
@@ -8,15 +8,15 @@
 #include "base/check_op.h"
 #include "base/run_loop.h"
 #include "base/task/current_thread.h"
+#include "base/task/single_thread_task_runner.h"
 #include "base/test/test_pending_task.h"
-#include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
 
 namespace base {
 
 ScopedMockTimeMessageLoopTaskRunner::ScopedMockTimeMessageLoopTaskRunner()
     : task_runner_(new TestMockTimeTaskRunner),
-      previous_task_runner_(ThreadTaskRunnerHandle::Get()) {
+      previous_task_runner_(SingleThreadTaskRunner::GetCurrentDefault()) {
   DCHECK(CurrentThread::Get());
   // To ensure that we process any initialization tasks posted to the
   // MessageLoop by a test fixture before replacing its TaskRunner.
@@ -26,7 +26,7 @@
 
 ScopedMockTimeMessageLoopTaskRunner::~ScopedMockTimeMessageLoopTaskRunner() {
   DCHECK(previous_task_runner_->RunsTasksInCurrentSequence());
-  DCHECK_EQ(task_runner_, ThreadTaskRunnerHandle::Get());
+  DCHECK_EQ(task_runner_, SingleThreadTaskRunner::GetCurrentDefault());
   for (auto& pending_task : task_runner_->TakePendingTasks()) {
     previous_task_runner_->PostDelayedTask(
         pending_task.location, std::move(pending_task.task),
diff --git a/base/test/scoped_mock_time_message_loop_task_runner_unittest.cc b/base/test/scoped_mock_time_message_loop_task_runner_unittest.cc
index dddb6e5..93610d94 100644
--- a/base/test/scoped_mock_time_message_loop_task_runner_unittest.cc
+++ b/base/test/scoped_mock_time_message_loop_task_runner_unittest.cc
@@ -12,10 +12,10 @@
 #include "base/memory/ptr_util.h"
 #include "base/memory/ref_counted.h"
 #include "base/task/current_thread.h"
+#include "base/task/single_thread_task_runner.h"
 #include "base/test/task_environment.h"
 #include "base/test/test_mock_time_task_runner.h"
 #include "base/test/test_pending_task.h"
-#include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -23,7 +23,7 @@
 namespace {
 
 TaskRunner* GetCurrentTaskRunner() {
-  return ThreadTaskRunnerHandle::Get().get();
+  return SingleThreadTaskRunner::GetCurrentDefault().get();
 }
 
 void AssignTrue(bool* out) {
diff --git a/base/test/scoped_run_loop_timeout_unittest.cc b/base/test/scoped_run_loop_timeout_unittest.cc
index 11af82b..20f495f6 100644
--- a/base/test/scoped_run_loop_timeout_unittest.cc
+++ b/base/test/scoped_run_loop_timeout_unittest.cc
@@ -7,10 +7,10 @@
 #include "base/bind.h"
 #include "base/callback_helpers.h"
 #include "base/location.h"
+#include "base/task/sequenced_task_runner.h"
 #include "base/test/bind.h"
 #include "base/test/gtest_util.h"
 #include "base/test/task_environment.h"
-#include "base/threading/sequenced_task_runner_handle.h"
 #include "base/time/time.h"
 #include "testing/gtest/include/gtest/gtest-spi.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -28,14 +28,14 @@
   // Since the delayed task will be posted only after the message pump starts
   // running, the ScopedRunLoopTimeout will already have started to elapse,
   // so if Run() exits at the correct time then our delayed task will not run.
-  SequencedTaskRunnerHandle::Get()->PostTask(
+  SequencedTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE,
       BindOnce(IgnoreResult(&SequencedTaskRunner::PostDelayedTask),
-               SequencedTaskRunnerHandle::Get(), FROM_HERE,
+               SequencedTaskRunner::GetCurrentDefault(), FROM_HERE,
                MakeExpectedNotRunClosure(FROM_HERE), kArbitraryTimeout));
 
   // This task should get to run before Run() times-out.
-  SequencedTaskRunnerHandle::Get()->PostDelayedTask(
+  SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
       FROM_HERE, MakeExpectedRunClosure(FROM_HERE), kArbitraryTimeout);
 
   // EXPECT_FATAL_FAILURE() can only reference globals and statics.
@@ -53,7 +53,7 @@
   // Posting a task with the same delay as our timeout, immediately before
   // calling Run(), means it should get to run. Since this uses QuitWhenIdle(),
   // the Run() timeout callback should also get to run.
-  SequencedTaskRunnerHandle::Get()->PostDelayedTask(
+  SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
       FROM_HERE, MakeExpectedRunClosure(FROM_HERE), kArbitraryTimeout);
 
   // EXPECT_FATAL_FAILURE() can only reference globals and statics.
diff --git a/base/test/task_environment.cc b/base/test/task_environment.cc
index 95b08c11f..0848818 100644
--- a/base/test/task_environment.cc
+++ b/base/test/task_environment.cc
@@ -28,6 +28,7 @@
 #include "base/task/sequence_manager/sequence_manager_impl.h"
 #include "base/task/sequence_manager/time_domain.h"
 #include "base/task/simple_task_executor.h"
+#include "base/task/single_thread_task_runner.h"
 #include "base/task/thread_pool/thread_pool_impl.h"
 #include "base/task/thread_pool/thread_pool_instance.h"
 #include "base/test/bind.h"
@@ -39,7 +40,6 @@
 #include "base/threading/thread_checker_impl.h"
 #include "base/threading/thread_local.h"
 #include "base/threading/thread_restrictions.h"
-#include "base/threading/thread_task_runner_handle.h"
 #include "base/time/clock.h"
 #include "base/time/tick_clock.h"
 #include "base/time/time.h"
@@ -407,7 +407,7 @@
                     BindRepeating(&sequence_manager::SequenceManager::
                                       DescribeAllPendingTasks,
                                   Unretained(sequence_manager_.get())))) {
-  CHECK(!base::ThreadTaskRunnerHandle::IsSet());
+  CHECK(!base::SingleThreadTaskRunner::HasCurrentDefault());
   // If |subclass_creates_default_taskrunner| is true then initialization is
   // deferred until DeferredInitFromSubclass().
   if (!subclass_creates_default_taskrunner) {
@@ -419,7 +419,7 @@
     if (mock_time_domain_)
       sequence_manager_->SetTimeDomain(mock_time_domain_.get());
     simple_task_executor_ = std::make_unique<SimpleTaskExecutor>(task_runner_);
-    CHECK(base::ThreadTaskRunnerHandle::IsSet())
+    CHECK(base::SingleThreadTaskRunner::HasCurrentDefault())
         << "ThreadTaskRunnerHandle should've been set now.";
     CompleteInitialization();
   }
diff --git a/base/test/task_environment_unittest.cc b/base/test/task_environment_unittest.cc
index fd2463f..8d63331 100644
--- a/base/test/task_environment_unittest.cc
+++ b/base/test/task_environment_unittest.cc
@@ -19,6 +19,8 @@
 #include "base/synchronization/waitable_event.h"
 #include "base/task/current_thread.h"
 #include "base/task/sequence_manager/time_domain.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/task/single_thread_task_runner.h"
 #include "base/task/thread_pool.h"
 #include "base/task/thread_pool/thread_pool_instance.h"
 #include "base/test/bind.h"
@@ -30,9 +32,7 @@
 #include "base/test/test_waitable_event.h"
 #include "base/threading/platform_thread.h"
 #include "base/threading/sequence_local_storage_slot.h"
-#include "base/threading/sequenced_task_runner_handle.h"
 #include "base/threading/thread.h"
-#include "base/threading/thread_task_runner_handle.h"
 #include "base/time/clock.h"
 #include "base/time/default_clock.h"
 #include "base/time/tick_clock.h"
@@ -78,7 +78,7 @@
   TaskEnvironment task_environment(thread_pool_execution_mode);
 
   AtomicFlag first_main_thread_task_ran;
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, BindOnce(&VerifyRunUntilIdleDidNotReturnAndSetFlag,
                           Unretained(&run_until_idle_returned),
                           Unretained(&first_main_thread_task_ran)));
@@ -185,7 +185,7 @@
 
   constexpr base::TimeDelta kShortTaskDelay = Days(1);
   // Should run only in MOCK_TIME environment when time is fast-forwarded.
-  ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
       FROM_HERE,
       BindOnce(
           [](subtle::Atomic32* counter) {
@@ -205,7 +205,7 @@
   constexpr base::TimeDelta kLongTaskDelay = Days(7);
   // Same as first task, longer delays to exercise
   // FastForwardUntilNoTasksRemain().
-  ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
       FROM_HERE,
       BindOnce(
           [](subtle::Atomic32* counter) {
@@ -213,7 +213,7 @@
           },
           Unretained(&counter)),
       Days(5));
-  ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
       FROM_HERE,
       BindOnce(
           [](subtle::Atomic32* counter) {
@@ -229,7 +229,7 @@
                                   },
                                   Unretained(&counter)),
                               kLongTaskDelay * 2);
-  ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
       FROM_HERE,
       BindOnce(
           [](subtle::Atomic32* counter) {
@@ -246,7 +246,7 @@
                                   Unretained(&counter)),
                               kLongTaskDelay * 4);
 
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, BindOnce(
                      [](subtle::Atomic32* counter) {
                        subtle::NoBarrier_AtomicIncrement(counter, 1);
@@ -390,7 +390,7 @@
 
   RunLoop run_loop;
 
-  ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
       FROM_HERE, BindLambdaForTesting([&]() {
         int64_t x = 1;
         auto ret = write(pipe_fds_[1], &x, sizeof(x));
@@ -431,12 +431,12 @@
       TaskEnvironment::ThreadPoolExecutionMode::QUEUED);
 
   constexpr base::TimeDelta kShortTaskDelay = Days(1);
-  ThreadTaskRunnerHandle::Get()->PostDelayedTask(FROM_HERE, base::DoNothing(),
-                                                 kShortTaskDelay);
+  SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
+      FROM_HERE, base::DoNothing(), kShortTaskDelay);
 
   constexpr base::TimeDelta kLongTaskDelay = Days(7);
-  ThreadTaskRunnerHandle::Get()->PostDelayedTask(FROM_HERE, base::DoNothing(),
-                                                 kLongTaskDelay);
+  SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
+      FROM_HERE, base::DoNothing(), kLongTaskDelay);
 
   const base::TickClock* tick_clock = task_environment.GetMockTickClock();
   base::TimeTicks tick_clock_ref = tick_clock->NowTicks();
@@ -528,8 +528,8 @@
   TaskEnvironment task_environment(TaskEnvironment::TimeSource::MOCK_TIME);
 
   constexpr base::TimeDelta kTaskDelay = Days(1);
-  ThreadTaskRunnerHandle::Get()->PostDelayedTask(FROM_HERE, base::DoNothing(),
-                                                 kTaskDelay);
+  SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
+      FROM_HERE, base::DoNothing(), kTaskDelay);
 
   EXPECT_EQ(1U, task_environment.GetPendingMainThreadTaskCount());
   EXPECT_TRUE(task_environment.NextTaskIsDelayed());
@@ -566,10 +566,10 @@
 
   constexpr base::TimeDelta kDelay = Seconds(42);
   constexpr base::TimeDelta kFastForwardUntil = Seconds(100);
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, BindLambdaForTesting(
                      [&]() { EXPECT_EQ(start_time, base::TimeTicks::Now()); }));
-  ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
       FROM_HERE, BindLambdaForTesting([&]() {
         EXPECT_EQ(start_time + kDelay, base::TimeTicks::Now());
       }),
@@ -585,7 +585,7 @@
   std::atomic_int run_count{0};
 
   for (int i = 0; i < 1000; ++i) {
-    ThreadTaskRunnerHandle::Get()->PostTask(
+    SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
         FROM_HERE, BindLambdaForTesting([&]() {
           run_count.fetch_add(1, std::memory_order_relaxed);
         }));
@@ -611,7 +611,7 @@
   post_fast_forwarding_task = BindLambdaForTesting([&]() {
     if (max_nesting_level < 5) {
       ++max_nesting_level;
-      ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+      SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
           FROM_HERE, post_fast_forwarding_task, kDelayPerTask);
       task_environment.FastForwardBy(kDelayPerTask);
     }
@@ -634,7 +634,7 @@
   post_and_runloop_task = BindLambdaForTesting([&]() {
     // Run 4 nested run loops on top of the initial FastForwardBy().
     if (run_loops.size() < 4U) {
-      ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+      SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
           FROM_HERE, post_and_runloop_task, kDelayPerTask);
 
       RunLoop run_loop(RunLoop::Type::kNestableTasksAllowed);
@@ -648,7 +648,7 @@
   });
 
   // Initial task is FastForwardBy().
-  ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
       FROM_HERE, post_and_runloop_task, kDelayPerTask);
   task_environment.FastForwardBy(kDelayPerTask);
 
@@ -665,7 +665,7 @@
   // Post tasks delayd between 0 and 999 seconds.
   for (int i = 0; i < 1000; ++i) {
     const TimeDelta delay = Seconds(i);
-    ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+    SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
         FROM_HERE,
         BindOnce(
             [](TimeTicks expected_run_time, int* count) {
@@ -719,13 +719,13 @@
     // the end!).
     if (last_main_thread_ticks < task_environment.NowTicks() &&
         task_environment.NowTicks() < end_time) {
-      SequencedTaskRunnerHandle::Get()->PostDelayedTask(
+      SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
           FROM_HERE, post_main_thread_delayed_task, kOneMs);
-      SequencedTaskRunnerHandle::Get()->PostDelayedTask(
+      SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
           FROM_HERE, post_main_thread_delayed_task, kOneMs);
-      SequencedTaskRunnerHandle::Get()->PostDelayedTask(
+      SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
           FROM_HERE, post_main_thread_delayed_task, kOneMs);
-      SequencedTaskRunnerHandle::Get()->PostDelayedTask(
+      SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
           FROM_HERE, post_main_thread_delayed_task, kOneMs);
     }
 
@@ -742,13 +742,13 @@
     // the end!).
     if (last_thread_pool_ticks < task_environment.NowTicks() &&
         task_environment.NowTicks() < end_time) {
-      SequencedTaskRunnerHandle::Get()->PostDelayedTask(
+      SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
           FROM_HERE, post_thread_pool_delayed_task, kOneMs);
-      SequencedTaskRunnerHandle::Get()->PostDelayedTask(
+      SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
           FROM_HERE, post_thread_pool_delayed_task, kOneMs);
-      SequencedTaskRunnerHandle::Get()->PostDelayedTask(
+      SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
           FROM_HERE, post_thread_pool_delayed_task, kOneMs);
-      SequencedTaskRunnerHandle::Get()->PostDelayedTask(
+      SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
           FROM_HERE, post_thread_pool_delayed_task, kOneMs);
 
       EXPECT_LT(task_environment.NowTicks(), end_time);
@@ -757,7 +757,7 @@
     last_thread_pool_ticks = task_environment.NowTicks();
   });
 
-  ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
       FROM_HERE, post_main_thread_delayed_task, kOneMs);
   ThreadPool::CreateSequencedTaskRunner({})->PostDelayedTask(
       FROM_HERE, post_thread_pool_delayed_task, kOneMs);
@@ -779,7 +779,7 @@
 
   // The 1s delayed task in the pool should run but not the 5s delayed task on
   // the main thread and fast-forward by should be capped at +2s.
-  ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
       FROM_HERE, MakeExpectedNotRunClosure(FROM_HERE), Seconds(5));
   ThreadPool::PostDelayedTask(FROM_HERE, {}, MakeExpectedRunClosure(FROM_HERE),
                               Seconds(1));
@@ -812,14 +812,14 @@
 
   // Time should auto-advance to +500s in RunLoop::Run() without having to run
   // the above forcefully QUEUED tasks.
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, BindLambdaForTesting([&]() { count += 1; }));
-  ThreadTaskRunnerHandle::Get()->PostDelayedTask(FROM_HERE,
-                                                 BindLambdaForTesting([&]() {
-                                                   count += 2;
-                                                   run_loop.Quit();
-                                                 }),
-                                                 Seconds(500));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
+      FROM_HERE, BindLambdaForTesting([&]() {
+        count += 2;
+        run_loop.Quit();
+      }),
+      Seconds(500));
 
   int expected_value = 0;
   EXPECT_EQ(expected_value, count);
@@ -849,7 +849,7 @@
   // (only the main thread task should run from RunLoop).
   ThreadPool::PostTask(FROM_HERE,
                        BindLambdaForTesting([&]() { count += 1024; }));
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, BindLambdaForTesting([&]() { count += 2048; }));
   PlatformThread::Sleep(Milliseconds(1));
   RunLoop().RunUntilIdle();
@@ -915,7 +915,7 @@
 
 TEST_F(TaskEnvironmentTest, DescribeCurrentTasksHasPendingMainThreadTasks) {
   TaskEnvironment task_environment;
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, DoNothing());
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE, DoNothing());
 
   test::MockLog mock_log;
   mock_log.StartCapturingLogs();
@@ -1000,25 +1000,25 @@
 
   int counter = 0;
 
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE,
       BindOnce([](int* counter) { *counter += 1; }, Unretained(&counter)));
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE,
       BindOnce([](int* counter) { *counter += 32; }, Unretained(&counter)));
-  ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
       FROM_HERE,
       BindOnce([](int* counter) { *counter += 256; }, Unretained(&counter)),
       Seconds(3));
-  ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
       FROM_HERE,
       BindOnce([](int* counter) { *counter += 64; }, Unretained(&counter)),
       Seconds(1));
-  ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
       FROM_HERE,
       BindOnce([](int* counter) { *counter += 1024; }, Unretained(&counter)),
       Minutes(20));
-  ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
       FROM_HERE,
       BindOnce([](int* counter) { *counter += 4096; }, Unretained(&counter)),
       Days(20));
@@ -1053,28 +1053,28 @@
       TaskEnvironment::ThreadPoolExecutionMode::QUEUED);
 
   int counter = 0;
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, base::BindOnce([](int* counter) { *counter += 1; },
                                 Unretained(&counter)));
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, base::BindOnce([](int* counter) { *counter += 32; },
                                 Unretained(&counter)));
-  ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
       FROM_HERE,
       base::BindOnce([](int* counter) { *counter += 256; },
                      Unretained(&counter)),
       Seconds(3));
-  ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
       FROM_HERE,
       base::BindOnce([](int* counter) { *counter += 64; },
                      Unretained(&counter)),
       Seconds(1));
-  ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
       FROM_HERE,
       base::BindOnce([](int* counter) { *counter += 1024; },
                      Unretained(&counter)),
       Minutes(20));
-  ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
       FROM_HERE,
       base::BindOnce([](int* counter) { *counter += 4096; },
                      Unretained(&counter)),
@@ -1092,9 +1092,9 @@
 
   {
     RunLoop run_loop;
-    ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+    SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
         FROM_HERE, run_loop.QuitClosure(), Seconds(1));
-    ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+    SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
         FROM_HERE,
         base::BindOnce([](int* counter) { *counter += 8192; },
                        Unretained(&counter)),
@@ -1115,9 +1115,9 @@
 
   {
     RunLoop run_loop;
-    ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+    SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
         FROM_HERE, run_loop.QuitWhenIdleClosure(), Seconds(5));
-    ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+    SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
         FROM_HERE,
         base::BindOnce([](int* counter) { *counter += 16384; },
                        Unretained(&counter)),
@@ -1141,7 +1141,7 @@
   ScopedDisableRunLoopTimeout disable_timeout;
 
   RunLoop run_loop;
-  ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
       FROM_HERE, run_loop.QuitWhenIdleClosure(), Days(50));
 
   run_loop.Run();
@@ -1155,7 +1155,7 @@
   TaskEnvironment task_environment(TaskEnvironment::TimeSource::MOCK_TIME);
 
   base::RunLoop run_loop;
-  ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
       FROM_HERE, run_loop.QuitClosure(), base::Seconds(1));
   ThreadPool::PostTask(FROM_HERE, base::DoNothing());
 
@@ -1183,7 +1183,7 @@
   TaskEnvironment task_environment(TaskEnvironment::TimeSource::MOCK_TIME);
 
   base::RunLoop run_loop;
-  ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
       FROM_HERE, run_loop.QuitClosure(), base::Seconds(1));
   TestWaitableEvent wait_for_collaboration;
   ThreadPool::PostTask(FROM_HERE, BindLambdaForTesting([&]() {
@@ -1203,8 +1203,8 @@
       TaskEnvironment::ThreadPoolExecutionMode::QUEUED);
 
   CancelableOnceClosure task1(BindOnce([]() {}));
-  ThreadTaskRunnerHandle::Get()->PostDelayedTask(FROM_HERE, task1.callback(),
-                                                 Seconds(1));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
+      FROM_HERE, task1.callback(), Seconds(1));
   EXPECT_TRUE(task_environment.MainThreadIsIdle());
   EXPECT_EQ(1u, task_environment.GetPendingMainThreadTaskCount());
   EXPECT_EQ(Seconds(1), task_environment.NextMainThreadPendingTaskDelay());
@@ -1215,21 +1215,21 @@
             task_environment.NextMainThreadPendingTaskDelay());
 
   CancelableRepeatingClosure task2(BindRepeating([]() {}));
-  ThreadTaskRunnerHandle::Get()->PostDelayedTask(FROM_HERE, task2.callback(),
-                                                 Seconds(1));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
+      FROM_HERE, task2.callback(), Seconds(1));
   task2.Cancel();
   EXPECT_EQ(0u, task_environment.GetPendingMainThreadTaskCount());
 
   CancelableRepeatingClosure task3(BindRepeating([]() {}));
-  ThreadTaskRunnerHandle::Get()->PostDelayedTask(FROM_HERE, task3.callback(),
-                                                 Seconds(1));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
+      FROM_HERE, task3.callback(), Seconds(1));
   task3.Cancel();
   EXPECT_EQ(TimeDelta::Max(),
             task_environment.NextMainThreadPendingTaskDelay());
 
   CancelableRepeatingClosure task4(BindRepeating([]() {}));
-  ThreadTaskRunnerHandle::Get()->PostDelayedTask(FROM_HERE, task4.callback(),
-                                                 Seconds(1));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
+      FROM_HERE, task4.callback(), Seconds(1));
   task4.Cancel();
   EXPECT_TRUE(task_environment.MainThreadIsIdle());
 }
@@ -1239,7 +1239,8 @@
   EXPECT_TRUE(task_environment.MainThreadIsIdle());
 
   CancelableOnceClosure task1(BindOnce([]() {}));
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, task1.callback());
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE,
+                                                        task1.callback());
   EXPECT_FALSE(task_environment.MainThreadIsIdle());
 
   task1.Cancel();
@@ -1253,8 +1254,8 @@
 
   TimeTicks start_time = task_environment.NowTicks();
   CancelableRepeatingClosure task(BindRepeating([]() {}));
-  ThreadTaskRunnerHandle::Get()->PostDelayedTask(FROM_HERE, task.callback(),
-                                                 Seconds(1));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
+      FROM_HERE, task.callback(), Seconds(1));
   EXPECT_EQ(Seconds(1), task_environment.NextMainThreadPendingTaskDelay());
   task.Cancel();
   task_environment.FastForwardUntilNoTasksRemain();
@@ -1266,19 +1267,20 @@
 
   EXPECT_FALSE(task_environment.NextTaskIsDelayed());
   CancelableRepeatingClosure task(BindRepeating([]() {}));
-  ThreadTaskRunnerHandle::Get()->PostDelayedTask(FROM_HERE, task.callback(),
-                                                 Seconds(1));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
+      FROM_HERE, task.callback(), Seconds(1));
   EXPECT_TRUE(task_environment.NextTaskIsDelayed());
   task.Cancel();
   EXPECT_FALSE(task_environment.NextTaskIsDelayed());
 
-  ThreadTaskRunnerHandle::Get()->PostDelayedTask(FROM_HERE, BindOnce([]() {}),
-                                                 Seconds(2));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
+      FROM_HERE, BindOnce([]() {}), Seconds(2));
   EXPECT_TRUE(task_environment.NextTaskIsDelayed());
   task_environment.FastForwardUntilNoTasksRemain();
   EXPECT_FALSE(task_environment.NextTaskIsDelayed());
 
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, BindOnce([]() {}));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE,
+                                                        BindOnce([]() {}));
   EXPECT_FALSE(task_environment.NextTaskIsDelayed());
 }
 
@@ -1287,7 +1289,8 @@
 
   EXPECT_EQ(TimeDelta::Max(),
             task_environment.NextMainThreadPendingTaskDelay());
-  ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, BindOnce([]() {}));
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE,
+                                                        BindOnce([]() {}));
   EXPECT_EQ(TimeDelta(), task_environment.NextMainThreadPendingTaskDelay());
 }
 
@@ -1310,7 +1313,7 @@
   EXPECT_THAT(ThreadPoolInstance::Get(), IsNull());
 
   bool ran = false;
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, base::BindLambdaForTesting([&]() { ran = true; }));
   RunLoop().RunUntilIdle();
   EXPECT_TRUE(ran);
@@ -1329,9 +1332,9 @@
   constexpr TimeDelta kDelay = Seconds(100);
 
   int counter = 0;
-  ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
       FROM_HERE, base::BindLambdaForTesting([&]() { counter += 1; }), kDelay);
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, base::BindLambdaForTesting([&]() { counter += 2; }));
 
   int expected_value = 0;
diff --git a/base/test/test_future_unittest.cc b/base/test/test_future_unittest.cc
index 51a4a86..1d3a8c4d 100644
--- a/base/test/test_future_unittest.cc
+++ b/base/test/test_future_unittest.cc
@@ -6,11 +6,11 @@
 
 #include <tuple>
 
+#include "base/task/single_thread_task_runner.h"
 #include "base/test/bind.h"
 #include "base/test/gtest_util.h"
 #include "base/test/scoped_run_loop_timeout.h"
 #include "base/test/task_environment.h"
-#include "base/threading/thread_task_runner_handle.h"
 #include "testing/gtest/include/gtest/gtest-spi.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -44,17 +44,17 @@
 
   template <typename Lambda>
   void RunLater(Lambda lambda) {
-    base::ThreadTaskRunnerHandle::Get()->PostTask(
+    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
         FROM_HERE, base::BindLambdaForTesting(lambda));
   }
 
   void RunLater(base::OnceClosure callable) {
-    base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                                  std::move(callable));
+    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+        FROM_HERE, std::move(callable));
   }
 
   void PostDelayedTask(base::OnceClosure callable, base::TimeDelta delay) {
-    base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+    base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
         FROM_HERE, std::move(callable), delay);
   }
 
diff --git a/base/test/test_mock_time_task_runner.cc b/base/test/test_mock_time_task_runner.cc
index f09e723..7cc0d21 100644
--- a/base/test/test_mock_time_task_runner.cc
+++ b/base/test/test_mock_time_task_runner.cc
@@ -151,7 +151,7 @@
 // Ref. TestMockTimeTaskRunner::RunsTasksInCurrentSequence().
 TestMockTimeTaskRunner::ScopedContext::ScopedContext(
     scoped_refptr<TestMockTimeTaskRunner> scope)
-    : thread_task_runner_handle_override_(scope) {
+    : single_thread_task_runner_current_default_handle_override_(scope) {
   scope->RunUntilIdle();
 }
 
diff --git a/base/test/test_mock_time_task_runner.h b/base/test/test_mock_time_task_runner.h
index ab0bab4..542dd3e 100644
--- a/base/test/test_mock_time_task_runner.h
+++ b/base/test/test_mock_time_task_runner.h
@@ -21,15 +21,12 @@
 #include "base/task/single_thread_task_runner.h"
 #include "base/test/test_pending_task.h"
 #include "base/threading/thread_checker_impl.h"
-#include "base/threading/thread_task_runner_handle.h"
 #include "base/time/clock.h"
 #include "base/time/tick_clock.h"
 #include "base/time/time.h"
 
 namespace base {
 
-class ThreadTaskRunnerHandle;
-
 // ATTENTION: Prefer using base::test::SingleThreadTaskEnvironment with a
 // base::test::SingleThreadTaskEnvironment::TimeSource::MOCK_TIME trait instead.
 // The only case where TestMockTimeTaskRunner is necessary is when instantiating
@@ -124,8 +121,8 @@
     ~ScopedContext();
 
    private:
-    ThreadTaskRunnerHandleOverrideForTesting
-        thread_task_runner_handle_override_;
+    SingleThreadTaskRunner::CurrentHandleOverrideForTesting
+        single_thread_task_runner_current_default_handle_override_;
   };
 
   enum class Type {
diff --git a/base/test/test_mock_time_task_runner_unittest.cc b/base/test/test_mock_time_task_runner_unittest.cc
index 9596007..4482495 100644
--- a/base/test/test_mock_time_task_runner_unittest.cc
+++ b/base/test/test_mock_time_task_runner_unittest.cc
@@ -9,12 +9,12 @@
 #include "base/cancelable_callback.h"
 #include "base/memory/ref_counted.h"
 #include "base/run_loop.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/task/single_thread_task_runner.h"
 #include "base/test/bind.h"
 #include "base/test/gtest_util.h"
 #include "base/test/test_timeouts.h"
-#include "base/threading/sequenced_task_runner_handle.h"
 #include "base/threading/thread.h"
-#include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -88,8 +88,8 @@
 // A default TestMockTimeTaskRunner shouldn't result in a thread association.
 TEST(TestMockTimeTaskRunnerTest, DefaultUnbound) {
   auto unbound_mock_time_task_runner = MakeRefCounted<TestMockTimeTaskRunner>();
-  EXPECT_FALSE(ThreadTaskRunnerHandle::IsSet());
-  EXPECT_FALSE(SequencedTaskRunnerHandle::IsSet());
+  EXPECT_FALSE(SingleThreadTaskRunner::HasCurrentDefault());
+  EXPECT_FALSE(SequencedTaskRunner::HasCurrentDefault());
   EXPECT_DEATH_IF_SUPPORTED({ RunLoop().RunUntilIdle(); }, "");
 }
 
@@ -98,28 +98,28 @@
       TestMockTimeTaskRunner::Type::kBoundToThread);
 
   int counter = 0;
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, base::BindOnce([](int* counter) { *counter += 1; },
                                 Unretained(&counter)));
-  ThreadTaskRunnerHandle::Get()->PostTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, base::BindOnce([](int* counter) { *counter += 32; },
                                 Unretained(&counter)));
-  ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
       FROM_HERE,
       base::BindOnce([](int* counter) { *counter += 256; },
                      Unretained(&counter)),
       Seconds(3));
-  ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
       FROM_HERE,
       base::BindOnce([](int* counter) { *counter += 64; },
                      Unretained(&counter)),
       Seconds(1));
-  ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
       FROM_HERE,
       base::BindOnce([](int* counter) { *counter += 1024; },
                      Unretained(&counter)),
       Minutes(20));
-  ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
       FROM_HERE,
       base::BindOnce([](int* counter) { *counter += 4096; },
                      Unretained(&counter)),
@@ -137,9 +137,9 @@
 
   {
     RunLoop run_loop;
-    ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+    SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
         FROM_HERE, run_loop.QuitClosure(), Seconds(1));
-    ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+    SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
         FROM_HERE,
         base::BindOnce([](int* counter) { *counter += 8192; },
                        Unretained(&counter)),
@@ -160,9 +160,9 @@
 
   {
     RunLoop run_loop;
-    ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+    SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
         FROM_HERE, run_loop.QuitWhenIdleClosure(), Seconds(5));
-    ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+    SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
         FROM_HERE,
         base::BindOnce([](int* counter) { *counter += 16384; },
                        Unretained(&counter)),
@@ -181,7 +181,7 @@
   // do this, this is just done here for the purpose of extensively testing the
   // RunLoop approach).
   RunLoop run_loop;
-  ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+  SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
       FROM_HERE, run_loop.QuitWhenIdleClosure(), Days(50));
 
   run_loop.Run();
@@ -205,7 +205,8 @@
         TestMockTimeTaskRunner::Type::kBoundToThread);
 
     task_runner->PostTask(FROM_HERE, base::BindLambdaForTesting([&]() {
-                            captured = ThreadTaskRunnerHandle::Get();
+                            captured =
+                                SingleThreadTaskRunner::GetCurrentDefault();
                           }));
     task_runner->RunUntilIdle();
   }
diff --git a/base/test/trace_to_file.cc b/base/test/trace_to_file.cc
index a3d0b46..69db354 100644
--- a/base/test/trace_to_file.cc
+++ b/base/test/trace_to_file.cc
@@ -10,8 +10,8 @@
 #include "base/files/file_util.h"
 #include "base/memory/ref_counted_memory.h"
 #include "base/run_loop.h"
+#include "base/task/single_thread_task_runner.h"
 #include "base/test/task_environment.h"
-#include "base/threading/thread_task_runner_handle.h"
 #include "base/trace_event/trace_buffer.h"
 #include "base/trace_event/trace_log.h"
 
@@ -97,7 +97,7 @@
 
   // In tests we might not have a TaskEnvironment, create one if needed.
   std::unique_ptr<SingleThreadTaskEnvironment> task_environment;
-  if (!ThreadTaskRunnerHandle::IsSet())
+  if (!SingleThreadTaskRunner::HasCurrentDefault())
     task_environment = std::make_unique<SingleThreadTaskEnvironment>();
 
   RunLoop run_loop;
diff --git a/base/threading/platform_thread.h b/base/threading/platform_thread.h
index 7607140..91db298 100644
--- a/base/threading/platform_thread.h
+++ b/base/threading/platform_thread.h
@@ -96,6 +96,9 @@
   // Suitable for threads that have the least urgency and lowest priority, and
   // can be interrupted or delayed by other types.
   kBackground,
+  // Suitable for threads that are less important than normal type, and can be
+  // interrupted or delayed by threads with kDefault type.
+  kUtility,
   // Suitable for threads that produce user-visible artifacts but aren't
   // latency sensitive. The underlying platform will try to be economic
   // in its usage of resources for this thread, if possible.
@@ -118,6 +121,7 @@
 // the underlying effects of SetCurrentThreadType.
 enum class ThreadPriorityForTest : int {
   kBackground,
+  kUtility,
   kNormal,
   // The priority obtained via ThreadType::kDisplayCritical (and potentially
   // other ThreadTypes).
diff --git a/base/threading/platform_thread_android.cc b/base/threading/platform_thread_android.cc
index 0cc58ef..13453e1 100644
--- a/base/threading/platform_thread_android.cc
+++ b/base/threading/platform_thread_android.cc
@@ -28,23 +28,26 @@
 // result in heavy throttling and force the thread onto a little core on
 // big.LITTLE devices.
 const ThreadPriorityToNiceValuePairForTest
-    kThreadPriorityToNiceValueMapForTest[4] = {
+    kThreadPriorityToNiceValueMapForTest[5] = {
         {ThreadPriorityForTest::kRealtimeAudio, -16},
         {ThreadPriorityForTest::kDisplay, -4},
         {ThreadPriorityForTest::kNormal, 0},
+        {ThreadPriorityForTest::kUtility, 1},
         {ThreadPriorityForTest::kBackground, 10},
 };
 
 // - kBackground corresponds to Android's PRIORITY_BACKGROUND = 10 value and can
 // result in heavy throttling and force the thread onto a little core on
 // big.LITTLE devices.
+// - kUtility corresponds to Android's THREAD_PRIORITY_LESS_FAVORABLE = 1 value.
 // - kCompositing and kDisplayCritical corresponds to Android's PRIORITY_DISPLAY
 // = -4 value.
 // - kRealtimeAudio corresponds to Android's PRIORITY_AUDIO = -16 value.
-const ThreadTypeToNiceValuePair kThreadTypeToNiceValueMap[6] = {
-    {ThreadType::kBackground, 10},      {ThreadType::kResourceEfficient, 0},
-    {ThreadType::kDefault, 0},          {ThreadType::kCompositing, -4},
-    {ThreadType::kDisplayCritical, -4}, {ThreadType::kRealtimeAudio, -16},
+const ThreadTypeToNiceValuePair kThreadTypeToNiceValueMap[7] = {
+    {ThreadType::kBackground, 10},       {ThreadType::kUtility, 1},
+    {ThreadType::kResourceEfficient, 0}, {ThreadType::kDefault, 0},
+    {ThreadType::kCompositing, -4},      {ThreadType::kDisplayCritical, -4},
+    {ThreadType::kRealtimeAudio, -16},
 };
 
 bool CanSetThreadTypeToRealtimeAudio() {
diff --git a/base/threading/platform_thread_fuchsia.cc b/base/threading/platform_thread_fuchsia.cc
index ccc0754..9d78aee 100644
--- a/base/threading/platform_thread_fuchsia.cc
+++ b/base/threading/platform_thread_fuchsia.cc
@@ -104,6 +104,10 @@
       SetThreadRole("chromium.base.threading.background");
       break;
 
+    case ThreadType::kUtility:
+      SetThreadRole("chromium.base.threading.utility");
+      break;
+
     case ThreadType::kResourceEfficient:
       SetThreadRole("chromium.base.threading.resource-efficient");
       break;
@@ -134,6 +138,7 @@
   const ThreadType thread_type = PlatformThread::GetCurrentThreadType();
   switch (thread_type) {
     case ThreadType::kBackground:
+    case ThreadType::kUtility:
     case ThreadType::kResourceEfficient:
     case ThreadType::kDefault:
     case ThreadType::kCompositing:
diff --git a/base/threading/platform_thread_internal_posix.h b/base/threading/platform_thread_internal_posix.h
index cfcb1fe..120abae 100644
--- a/base/threading/platform_thread_internal_posix.h
+++ b/base/threading/platform_thread_internal_posix.h
@@ -27,13 +27,13 @@
 // The elements must be listed in the order of increasing priority (lowest
 // priority first), that is, in the order of decreasing nice values (highest
 // nice value first).
-extern const ThreadTypeToNiceValuePair kThreadTypeToNiceValueMap[6];
+extern const ThreadTypeToNiceValuePair kThreadTypeToNiceValueMap[7];
 
 // The elements must be listed in the order of decreasing priority (highest
 // priority first), that is, in the order of increasing nice values (lowest nice
 // value first).
 extern const ThreadPriorityToNiceValuePairForTest
-    kThreadPriorityToNiceValueMapForTest[4];
+    kThreadPriorityToNiceValueMapForTest[5];
 
 // Returns the nice value matching |priority| based on the platform-specific
 // implementation of kThreadTypeToNiceValueMap.
diff --git a/base/threading/platform_thread_linux.cc b/base/threading/platform_thread_linux.cc
index 078f66f..209b99e 100644
--- a/base/threading/platform_thread_linux.cc
+++ b/base/threading/platform_thread_linux.cc
@@ -147,6 +147,7 @@
                                      ThreadType thread_type) {
   switch (thread_type) {
     case ThreadType::kBackground:
+    case ThreadType::kUtility:
     case ThreadType::kResourceEfficient:
       return cgroup_filepath.Append(FILE_PATH_LITERAL("non-urgent"));
     case ThreadType::kDefault:
@@ -246,6 +247,7 @@
 
   switch (thread_type) {
     case ThreadType::kBackground:
+    case ThreadType::kUtility:
     case ThreadType::kResourceEfficient:
     case ThreadType::kDefault:
       break;
@@ -317,23 +319,24 @@
 }  // namespace
 
 const ThreadPriorityToNiceValuePairForTest
-    kThreadPriorityToNiceValueMapForTest[4] = {
+    kThreadPriorityToNiceValueMapForTest[5] = {
         {ThreadPriorityForTest::kRealtimeAudio, -10},
         {ThreadPriorityForTest::kDisplay, -8},
         {ThreadPriorityForTest::kNormal, 0},
+        {ThreadPriorityForTest::kUtility, 1},
         {ThreadPriorityForTest::kBackground, 10},
 };
 
-const ThreadTypeToNiceValuePair kThreadTypeToNiceValueMap[6] = {
-    {ThreadType::kBackground, 10},      {ThreadType::kResourceEfficient, 0},
-    {ThreadType::kDefault, 0},
+const ThreadTypeToNiceValuePair kThreadTypeToNiceValueMap[7] = {
+    {ThreadType::kBackground, 10},       {ThreadType::kUtility, 1},
+    {ThreadType::kResourceEfficient, 0}, {ThreadType::kDefault, 0},
 #if BUILDFLAG(IS_CHROMEOS)
     {ThreadType::kCompositing, -8},
 #else
     // TODO(1329208): Experiment with bringing IS_LINUX inline with IS_CHROMEOS.
     {ThreadType::kCompositing, 0},
 #endif
-    {ThreadType::kDisplayCritical, -8}, {ThreadType::kRealtimeAudio, -10},
+    {ThreadType::kDisplayCritical, -8},  {ThreadType::kRealtimeAudio, -10},
 };
 
 bool CanSetThreadTypeToRealtimeAudio() {
diff --git a/base/threading/platform_thread_mac.mm b/base/threading/platform_thread_mac.mm
index 26614fa1..55837033 100644
--- a/base/threading/platform_thread_mac.mm
+++ b/base/threading/platform_thread_mac.mm
@@ -316,6 +316,13 @@
       else
         [[NSThread currentThread] setThreadPriority:0];
       break;
+    case ThreadType::kUtility:
+      priority = ThreadPriorityForTest::kUtility;
+      if (use_thread_qos)
+        pthread_set_qos_class_self_np(QOS_CLASS_UTILITY, 0);
+      else
+        [[NSThread currentThread] setThreadPriority:0.5];
+      break;
     case ThreadType::kResourceEfficient:
       if (use_thread_qos) {
         pthread_set_qos_class_self_np(QOS_CLASS_UTILITY, 0);
diff --git a/base/threading/platform_thread_unittest.cc b/base/threading/platform_thread_unittest.cc
index 3f1a371..48b558f 100644
--- a/base/threading/platform_thread_unittest.cc
+++ b/base/threading/platform_thread_unittest.cc
@@ -246,7 +246,8 @@
 constexpr ThreadType kAllThreadTypes[] = {
     ThreadType::kRealtimeAudio,     ThreadType::kDisplayCritical,
     ThreadType::kCompositing,       ThreadType::kDefault,
-    ThreadType::kResourceEfficient, ThreadType::kBackground};
+    ThreadType::kResourceEfficient, ThreadType::kUtility,
+    ThreadType::kBackground};
 
 class ThreadTypeTestThread : public FunctionTestThread {
  public:
@@ -279,6 +280,10 @@
 
  private:
   void RunTest() override {
+    testing::Message message;
+    message << "thread_type: " << static_cast<int>(thread_type_);
+    SCOPED_TRACE(message);
+
     EXPECT_EQ(PlatformThread::GetCurrentThreadType(), ThreadType::kDefault);
     PlatformThread::SetCurrentThreadType(thread_type_);
     EXPECT_EQ(PlatformThread::GetCurrentThreadType(), thread_type_);
@@ -429,6 +434,8 @@
     EXPECT_TRUE(PlatformThread::CanChangeThreadType(type, type));
   }
 #if BUILDFLAG(IS_FUCHSIA)
+  EXPECT_FALSE(PlatformThread::CanChangeThreadType(ThreadType::kBackground,
+                                                   ThreadType::kUtility));
   EXPECT_FALSE(PlatformThread::CanChangeThreadType(
       ThreadType::kBackground, ThreadType::kResourceEfficient));
   EXPECT_FALSE(PlatformThread::CanChangeThreadType(ThreadType::kBackground,
@@ -441,6 +448,9 @@
                                                    ThreadType::kBackground));
 #else
   EXPECT_EQ(PlatformThread::CanChangeThreadType(ThreadType::kBackground,
+                                                ThreadType::kUtility),
+            kCanIncreasePriority);
+  EXPECT_EQ(PlatformThread::CanChangeThreadType(ThreadType::kBackground,
                                                 ThreadType::kResourceEfficient),
             kCanIncreasePriority);
   EXPECT_EQ(PlatformThread::CanChangeThreadType(ThreadType::kBackground,
@@ -476,6 +486,8 @@
 TEST(PlatformThreadTest, SetCurrentThreadTypeTest) {
   TestPriorityResultingFromThreadType(ThreadType::kBackground,
                                       ThreadPriorityForTest::kBackground);
+  TestPriorityResultingFromThreadType(ThreadType::kUtility,
+                                      ThreadPriorityForTest::kUtility);
   TestPriorityResultingFromThreadType(ThreadType::kResourceEfficient,
                                       ThreadPriorityForTest::kNormal);
   TestPriorityResultingFromThreadType(ThreadType::kDefault,
diff --git a/base/threading/platform_thread_win.cc b/base/threading/platform_thread_win.cc
index c4df0797..316ba67 100644
--- a/base/threading/platform_thread_win.cc
+++ b/base/threading/platform_thread_win.cc
@@ -414,6 +414,9 @@
               ? THREAD_PRIORITY_LOWEST
               : THREAD_MODE_BACKGROUND_BEGIN;
       break;
+    case ThreadType::kUtility:
+      desired_priority = THREAD_PRIORITY_BELOW_NORMAL;
+      break;
     case ThreadType::kResourceEfficient:
     case ThreadType::kDefault:
       desired_priority = THREAD_PRIORITY_NORMAL;
@@ -462,6 +465,7 @@
   bool desire_ecoqos = false;
   switch (thread_type) {
     case ThreadType::kBackground:
+    case ThreadType::kUtility:
     case ThreadType::kResourceEfficient:
       desire_ecoqos = true;
       break;
@@ -534,13 +538,15 @@
   // -6 when THREAD_MODE_BACKGROUND_* is used. THREAD_PRIORITY_IDLE,
   // THREAD_PRIORITY_LOWEST and THREAD_PRIORITY_BELOW_NORMAL are other possible
   // negative values.
-  if (priority < THREAD_PRIORITY_NORMAL)
+  if (priority < THREAD_PRIORITY_BELOW_NORMAL)
     return ThreadPriorityForTest::kBackground;
 
   switch (priority) {
     case kWin7BackgroundThreadModePriority:
       DCHECK_EQ(win::GetVersion(), win::Version::WIN7);
       return ThreadPriorityForTest::kBackground;
+    case THREAD_PRIORITY_BELOW_NORMAL:
+      return ThreadPriorityForTest::kUtility;
     case kWin7NormalPriority:
       DCHECK_EQ(win::GetVersion(), win::Version::WIN7);
       [[fallthrough]];
diff --git a/base/threading/post_task_and_reply_impl.cc b/base/threading/post_task_and_reply_impl.cc
index 8a38386..a346c59 100644
--- a/base/threading/post_task_and_reply_impl.cc
+++ b/base/threading/post_task_and_reply_impl.cc
@@ -11,7 +11,6 @@
 #include "base/debug/leak_annotations.h"
 #include "base/memory/ref_counted.h"
 #include "base/task/sequenced_task_runner.h"
-#include "base/threading/sequenced_task_runner_handle.h"
 
 namespace base {
 
@@ -135,15 +134,15 @@
   DCHECK(task) << from_here.ToString();
   DCHECK(reply) << from_here.ToString();
 
-  const bool has_sequenced_context = SequencedTaskRunnerHandle::IsSet();
+  const bool has_sequenced_context = SequencedTaskRunner::HasCurrentDefault();
 
   const bool post_task_success = PostTask(
-      from_here,
-      BindOnce(&PostTaskAndReplyRelay::RunTaskAndPostReply,
-               PostTaskAndReplyRelay(
-                   from_here, std::move(task), std::move(reply),
-                   has_sequenced_context ? SequencedTaskRunnerHandle::Get()
-                                         : nullptr)));
+      from_here, BindOnce(&PostTaskAndReplyRelay::RunTaskAndPostReply,
+                          PostTaskAndReplyRelay(
+                              from_here, std::move(task), std::move(reply),
+                              has_sequenced_context
+                                  ? SequencedTaskRunner::GetCurrentDefault()
+                                  : nullptr)));
 
   // PostTaskAndReply() requires a SequencedTaskRunnerHandle to post the reply.
   // Having no SequencedTaskRunnerHandle is allowed when posting the task fails,
diff --git a/base/threading/post_task_and_reply_impl_unittest.cc b/base/threading/post_task_and_reply_impl_unittest.cc
index df0e7d4..f0f55467 100644
--- a/base/threading/post_task_and_reply_impl_unittest.cc
+++ b/base/threading/post_task_and_reply_impl_unittest.cc
@@ -11,8 +11,8 @@
 #include "base/callback_helpers.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/ref_counted.h"
+#include "base/task/sequenced_task_runner.h"
 #include "base/test/test_mock_time_task_runner.h"
-#include "base/threading/sequenced_task_runner_handle.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -220,7 +220,7 @@
 TEST_F(PostTaskAndReplyImplTest,
        PostTaskToStoppedTaskRunnerWithoutSequencedContext) {
   reply_runner_.reset();
-  EXPECT_FALSE(SequencedTaskRunnerHandle::IsSet());
+  EXPECT_FALSE(SequencedTaskRunner::HasCurrentDefault());
   post_runner_->StopAcceptingTasks();
 
   // Expect the post to return false, but not to crash.
diff --git a/base/threading/sequence_bound.h b/base/threading/sequence_bound.h
index 639e331..e848fffa 100644
--- a/base/threading/sequence_bound.h
+++ b/base/threading/sequence_bound.h
@@ -18,7 +18,6 @@
 #include "base/sequence_checker.h"
 #include "base/task/sequenced_task_runner.h"
 #include "base/threading/sequence_bound_internal.h"
-#include "base/threading/sequenced_task_runner_handle.h"
 
 namespace base {
 
diff --git a/base/threading/sequence_bound_unittest.cc b/base/threading/sequence_bound_unittest.cc
index dcb6cf2..f71e42fe 100644
--- a/base/threading/sequence_bound_unittest.cc
+++ b/base/threading/sequence_bound_unittest.cc
@@ -14,10 +14,10 @@
 #include "base/sequence_checker.h"
 #include "base/strings/stringprintf.h"
 #include "base/synchronization/lock.h"
+#include "base/task/sequenced_task_runner.h"
 #include "base/task/thread_pool.h"
 #include "base/test/bind.h"
 #include "base/test/task_environment.h"
-#include "base/threading/sequenced_task_runner_handle.h"
 #include "build/build_config.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -1105,7 +1105,7 @@
   // Death tests use fork(), which can interact (very) poorly with threads.
   test::SingleThreadTaskEnvironment task_environment_;
   scoped_refptr<SequencedTaskRunner> task_runner_ =
-      SequencedTaskRunnerHandle::Get();
+      SequencedTaskRunner::GetCurrentDefault();
 };
 
 TEST_F(SequenceBoundDeathTest, AsyncCallIntArgNoWithArgsShouldCheck) {
diff --git a/base/threading/thread.cc b/base/threading/thread.cc
index c973e6b..c07e124e 100644
--- a/base/threading/thread.cc
+++ b/base/threading/thread.cc
@@ -22,11 +22,11 @@
 #include "base/task/sequence_manager/sequence_manager_impl.h"
 #include "base/task/sequence_manager/task_queue.h"
 #include "base/task/simple_task_executor.h"
+#include "base/task/single_thread_task_runner.h"
 #include "base/third_party/dynamic_annotations/dynamic_annotations.h"
 #include "base/threading/thread_id_name_manager.h"
 #include "base/threading/thread_local.h"
 #include "base/threading/thread_restrictions.h"
-#include "base/threading/thread_task_runner_handle.h"
 #include "base/types/pass_key.h"
 #include "build/build_config.h"
 
@@ -379,7 +379,7 @@
   // This binds CurrentThread and ThreadTaskRunnerHandle.
   delegate_->BindToCurrentThread(timer_slack_);
   DCHECK(CurrentThread::Get());
-  DCHECK(ThreadTaskRunnerHandle::IsSet());
+  DCHECK(SingleThreadTaskRunner::HasCurrentDefault());
 #if (BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_NACL)) || BUILDFLAG(IS_FUCHSIA)
   // Allow threads running a MessageLoopForIO to use FileDescriptorWatcher API.
   std::unique_ptr<FileDescriptorWatcher> file_descriptor_watcher;
diff --git a/base/threading/thread_checker_impl.cc b/base/threading/thread_checker_impl.cc
index d5af3e3..48adb59 100644
--- a/base/threading/thread_checker_impl.cc
+++ b/base/threading/thread_checker_impl.cc
@@ -6,10 +6,10 @@
 
 #include "base/check.h"
 #include "base/debug/stack_trace.h"
+#include "base/task/single_thread_task_runner.h"
 #include "base/threading/platform_thread.h"
 #include "base/threading/thread_checker.h"
 #include "base/threading/thread_local.h"
-#include "base/threading/thread_task_runner_handle.h"
 
 namespace {
 bool g_log_thread_and_sequence_checker_binding = false;
@@ -90,7 +90,7 @@
     // the thread to which this ThreadCheckerImpl is bound is fortuitous.
     if (sequence_token_.IsValid() &&
         (sequence_token_ != SequenceToken::GetForCurrentThread() ||
-         !ThreadTaskRunnerHandle::IsSet())) {
+         !SingleThreadTaskRunner::HasCurrentDefault())) {
       if (out_bound_at && bound_at_) {
         *out_bound_at = std::make_unique<debug::StackTrace>(*bound_at_);
       }
diff --git a/base/timer/timer.cc b/base/timer/timer.cc
index 02d38fc..f755b249 100644
--- a/base/timer/timer.cc
+++ b/base/timer/timer.cc
@@ -13,9 +13,9 @@
 #include "base/memory/ptr_util.h"
 #include "base/memory/raw_ptr_exclusion.h"
 #include "base/memory/ref_counted.h"
+#include "base/task/sequenced_task_runner.h"
 #include "base/task/task_features.h"
 #include "base/threading/platform_thread.h"
-#include "base/threading/sequenced_task_runner_handle.h"
 #include "base/time/tick_clock.h"
 
 namespace base {
@@ -83,7 +83,7 @@
 }
 
 scoped_refptr<SequencedTaskRunner> TimerBase::GetTaskRunner() {
-  return task_runner_ ? task_runner_ : SequencedTaskRunnerHandle::Get();
+  return task_runner_ ? task_runner_ : SequencedTaskRunner::GetCurrentDefault();
 }
 
 void TimerBase::Stop() {
@@ -365,14 +365,11 @@
 void DeadlineTimer::Start(const Location& posted_from,
                           TimeTicks deadline,
                           OnceClosure user_task,
-                          ExactDeadline exact) {
+                          subtle::DelayPolicy delay_policy) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   AbandonScheduledTask();
   user_task_ = std::move(user_task);
   posted_from_ = posted_from;
-  subtle::DelayPolicy delay_policy =
-      exact ? subtle::DelayPolicy::kPrecise
-            : subtle::DelayPolicy::kFlexiblePreferEarly;
   ScheduleNewTask(deadline, delay_policy);
 }
 
diff --git a/base/timer/timer.h b/base/timer/timer.h
index 8449f84..d78485a 100644
--- a/base/timer/timer.h
+++ b/base/timer/timer.h
@@ -83,8 +83,6 @@
 
 class TickClock;
 
-using ExactDeadline = base::StrongAlias<class ExactDeadlineTag, bool>;
-
 namespace internal {
 
 // This class wraps logic shared by all timers.
@@ -431,14 +429,14 @@
   DeadlineTimer(const DeadlineTimer&) = delete;
   DeadlineTimer& operator=(const DeadlineTimer&) = delete;
 
-  // Start the timer to run |user_task| near the specified |deadline|;
-  // preferably as close as possible to the specified time if |exact|, or
-  // preferably a little bit before than after otherwise. If the timer is
-  // already running, it will be replaced to call the given |user_task|.
+  // Start the timer to run |user_task| near the specified |deadline| following
+  // |delay_policy| If the timer is already running, it will be replaced to call
+  // the given |user_task|.
   void Start(const Location& posted_from,
              TimeTicks deadline,
              OnceClosure user_task,
-             ExactDeadline exact = ExactDeadline(false));
+             subtle::DelayPolicy delay_policy =
+                 subtle::DelayPolicy::kFlexiblePreferEarly);
 
   // Start the timer to run |user_task| near the specified |deadline|. If the
   // timer is already running, it will be replaced to call a task formed from
@@ -448,8 +446,10 @@
              TimeTicks deadline,
              Receiver* receiver,
              void (Receiver::*method)(),
-             ExactDeadline exact = ExactDeadline(false)) {
-    Start(posted_from, deadline, BindOnce(method, Unretained(receiver)), exact);
+             subtle::DelayPolicy delay_policy =
+                 subtle::DelayPolicy::kFlexiblePreferEarly) {
+    Start(posted_from, deadline, BindOnce(method, Unretained(receiver)),
+          delay_policy);
   }
 
  protected:
diff --git a/base/timer/timer_unittest.cc b/base/timer/timer_unittest.cc
index 7da210c..d401321 100644
--- a/base/timer/timer_unittest.cc
+++ b/base/timer/timer_unittest.cc
@@ -18,7 +18,6 @@
 #include "base/test/mock_callback.h"
 #include "base/test/task_environment.h"
 #include "base/test/test_simple_task_runner.h"
-#include "base/threading/sequenced_task_runner_handle.h"
 #include "base/time/tick_clock.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
@@ -78,7 +77,8 @@
   auto* timer_ptr = timer.get();
 
   // This should run before the timer expires.
-  SequencedTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE, std::move(timer));
+  SequencedTaskRunner::GetCurrentDefault()->DeleteSoon(FROM_HERE,
+                                                       std::move(timer));
 
   timer_ptr->Start(FROM_HERE, kTestDelay,
                    BindOnce(&Receiver::OnCalled, Unretained(&receiver)));
@@ -135,7 +135,8 @@
   auto* timer_ptr = timer.get();
 
   // This should run before the timer expires.
-  SequencedTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE, std::move(timer));
+  SequencedTaskRunner::GetCurrentDefault()->DeleteSoon(FROM_HERE,
+                                                       std::move(timer));
 
   timer_ptr->Start(FROM_HERE, delay,
                    BindRepeating(&Receiver::OnCalled, Unretained(&receiver)));
diff --git a/base/trace_event/memory_dump_manager.cc b/base/trace_event/memory_dump_manager.cc
index afdb86c..b0d3ead 100644
--- a/base/trace_event/memory_dump_manager.cc
+++ b/base/trace_event/memory_dump_manager.cc
@@ -21,9 +21,9 @@
 #include "base/memory/ptr_util.h"
 #include "base/strings/string_util.h"
 #include "base/task/sequenced_task_runner.h"
+#include "base/task/single_thread_task_runner.h"
 #include "base/third_party/dynamic_annotations/dynamic_annotations.h"
 #include "base/threading/thread.h"
-#include "base/threading/thread_task_runner_handle.h"
 #include "base/trace_event/heap_profiler.h"
 #include "base/trace_event/heap_profiler_allocation_context_tracker.h"
 #include "base/trace_event/malloc_dump_provider.h"
@@ -535,7 +535,7 @@
     scoped_refptr<SequencedTaskRunner> dump_thread_task_runner)
     : req_args(req_args),
       callback(std::move(callback)),
-      callback_task_runner(ThreadTaskRunnerHandle::Get()),
+      callback_task_runner(SingleThreadTaskRunner::GetCurrentDefault()),
       dump_thread_task_runner(std::move(dump_thread_task_runner)) {
   pending_dump_providers.reserve(dump_providers.size());
   pending_dump_providers.assign(dump_providers.rbegin(), dump_providers.rend());
diff --git a/base/trace_event/memory_dump_manager_unittest.cc b/base/trace_event/memory_dump_manager_unittest.cc
index 1d9ff58..6d989f8c 100644
--- a/base/trace_event/memory_dump_manager_unittest.cc
+++ b/base/trace_event/memory_dump_manager_unittest.cc
@@ -18,14 +18,13 @@
 #include "base/command_line.h"
 #include "base/run_loop.h"
 #include "base/synchronization/waitable_event.h"
+#include "base/task/sequenced_task_runner.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/task/thread_pool.h"
 #include "base/test/task_environment.h"
 #include "base/test/test_io_thread.h"
 #include "base/threading/platform_thread.h"
-#include "base/threading/sequenced_task_runner_handle.h"
 #include "base/threading/thread.h"
-#include "base/threading/thread_task_runner_handle.h"
 #include "base/trace_event/memory_dump_manager_test_utils.h"
 #include "base/trace_event/memory_dump_provider.h"
 #include "base/trace_event/memory_dump_request_args.h"
@@ -228,7 +227,7 @@
            std::unique_ptr<ProcessMemoryDump> pmd) {
           *curried_success = success;
           EXPECT_EQ(curried_expected_guid, dump_guid);
-          ThreadTaskRunnerHandle::Get()->PostTask(
+          SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
               FROM_HERE, std::move(curried_quit_closure));
         },
         Unretained(&success), run_loop.QuitClosure(), test_guid);
@@ -281,7 +280,7 @@
 // called.
 TEST_F(MemoryDumpManagerTest, SingleDumper) {
   MockMemoryDumpProvider mdp;
-  RegisterDumpProvider(&mdp, ThreadTaskRunnerHandle::Get());
+  RegisterDumpProvider(&mdp, SingleThreadTaskRunner::GetCurrentDefault());
 
   // Now repeat enabling the memory category and check that the dumper is
   // invoked this time.
@@ -313,7 +312,7 @@
 TEST_F(MemoryDumpManagerTest, CheckMemoryDumpArgs) {
   MockMemoryDumpProvider mdp;
 
-  RegisterDumpProvider(&mdp, ThreadTaskRunnerHandle::Get());
+  RegisterDumpProvider(&mdp, SingleThreadTaskRunner::GetCurrentDefault());
   EnableForTracing();
   EXPECT_CALL(mdp, OnMemoryDump(IsDetailedDump(), _));
   EXPECT_TRUE(RequestProcessDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED,
@@ -324,7 +323,7 @@
 
   // Check that requesting dumps with low level of detail actually propagates to
   // OnMemoryDump() call on dump providers.
-  RegisterDumpProvider(&mdp, ThreadTaskRunnerHandle::Get());
+  RegisterDumpProvider(&mdp, SingleThreadTaskRunner::GetCurrentDefault());
   EnableForTracing();
   EXPECT_CALL(mdp, OnMemoryDump(IsLightDump(), _));
   EXPECT_TRUE(RequestProcessDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED,
@@ -339,7 +338,7 @@
 TEST_F(MemoryDumpManagerTest, CheckMemoryDumpArgsDeterministic) {
   MockMemoryDumpProvider mdp;
 
-  RegisterDumpProvider(&mdp, ThreadTaskRunnerHandle::Get());
+  RegisterDumpProvider(&mdp, SingleThreadTaskRunner::GetCurrentDefault());
   EnableForTracing();
   EXPECT_CALL(mdp, OnMemoryDump(IsDeterministicDump(), _));
   EXPECT_TRUE(RequestProcessDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED,
@@ -350,7 +349,7 @@
 
   // Check that requesting dumps with deterministic option set to false
   // actually propagates to OnMemoryDump() call on dump providers.
-  RegisterDumpProvider(&mdp, ThreadTaskRunnerHandle::Get());
+  RegisterDumpProvider(&mdp, SingleThreadTaskRunner::GetCurrentDefault());
   EnableForTracing();
   EXPECT_CALL(mdp, OnMemoryDump(IsNotDeterministicDump(), _));
   EXPECT_TRUE(RequestProcessDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED,
@@ -366,7 +365,7 @@
   MockMemoryDumpProvider mdp2;
 
   // Enable only mdp1.
-  RegisterDumpProvider(&mdp1, ThreadTaskRunnerHandle::Get());
+  RegisterDumpProvider(&mdp1, SingleThreadTaskRunner::GetCurrentDefault());
   EnableForTracing();
   EXPECT_CALL(mdp1, OnMemoryDump(_, _));
   EXPECT_CALL(mdp2, OnMemoryDump(_, _)).Times(0);
@@ -408,7 +407,7 @@
 TEST_F(MemoryDumpManagerTest, MAYBE_RegistrationConsistency) {
   MockMemoryDumpProvider mdp;
 
-  RegisterDumpProvider(&mdp, ThreadTaskRunnerHandle::Get());
+  RegisterDumpProvider(&mdp, SingleThreadTaskRunner::GetCurrentDefault());
 
   {
     EXPECT_CALL(mdp, OnMemoryDump(_, _));
@@ -430,7 +429,7 @@
     DisableTracing();
   }
 
-  RegisterDumpProvider(&mdp, ThreadTaskRunnerHandle::Get());
+  RegisterDumpProvider(&mdp, SingleThreadTaskRunner::GetCurrentDefault());
   mdm_->UnregisterDumpProvider(&mdp);
 
   {
@@ -442,9 +441,9 @@
     DisableTracing();
   }
 
-  RegisterDumpProvider(&mdp, ThreadTaskRunnerHandle::Get());
+  RegisterDumpProvider(&mdp, SingleThreadTaskRunner::GetCurrentDefault());
   mdm_->UnregisterDumpProvider(&mdp);
-  RegisterDumpProvider(&mdp, ThreadTaskRunnerHandle::Get());
+  RegisterDumpProvider(&mdp, SingleThreadTaskRunner::GetCurrentDefault());
 
   {
     EXPECT_CALL(mdp, OnMemoryDump(_, _));
@@ -619,8 +618,10 @@
   MockMemoryDumpProvider mdp1;
   MockMemoryDumpProvider mdp2;
 
-  RegisterDumpProvider(&mdp1, ThreadTaskRunnerHandle::Get(), kDefaultOptions);
-  RegisterDumpProvider(&mdp2, ThreadTaskRunnerHandle::Get(), kDefaultOptions);
+  RegisterDumpProvider(&mdp1, SingleThreadTaskRunner::GetCurrentDefault(),
+                       kDefaultOptions);
+  RegisterDumpProvider(&mdp2, SingleThreadTaskRunner::GetCurrentDefault(),
+                       kDefaultOptions);
   EnableForTracing();
 
   EXPECT_CALL(mdp1, OnMemoryDump(_, _))
@@ -717,7 +718,7 @@
     TestIOThread* other_thread = threads[other_idx].get();
     // TestIOThread isn't thread-safe and must be stopped on the |main_runner|.
     scoped_refptr<SequencedTaskRunner> main_runner =
-        SequencedTaskRunnerHandle::Get();
+        SequencedTaskRunner::GetCurrentDefault();
     auto on_dump = [other_thread, main_runner, &on_memory_dump_call_count](
                        const MemoryDumpArgs& args, ProcessMemoryDump* pmd) {
       PostTaskAndWait(
diff --git a/base/trace_event/memory_dump_scheduler.cc b/base/trace_event/memory_dump_scheduler.cc
index c729e87..77356d5 100644
--- a/base/trace_event/memory_dump_scheduler.cc
+++ b/base/trace_event/memory_dump_scheduler.cc
@@ -9,7 +9,7 @@
 
 #include "base/bind.h"
 #include "base/check_op.h"
-#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/task/sequenced_task_runner.h"
 #include "base/time/time.h"
 
 namespace base {
@@ -78,7 +78,7 @@
   // TODO(lalitm): this is a tempoarary hack to delay the first scheduled dump
   // so that the child processes get tracing enabled notification via IPC.
   // See crbug.com/770151.
-  SequencedTaskRunnerHandle::Get()->PostDelayedTask(
+  SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
       FROM_HERE,
       BindOnce(&MemoryDumpScheduler::Tick, Unretained(this), ++generation_),
       Milliseconds(200));
@@ -103,7 +103,7 @@
 
   callback_.Run(level_of_detail);
 
-  SequencedTaskRunnerHandle::Get()->PostDelayedTask(
+  SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
       FROM_HERE,
       BindOnce(&MemoryDumpScheduler::Tick, Unretained(this),
                expected_generation),
diff --git a/base/trace_event/trace_log.cc b/base/trace_event/trace_log.cc
index bbcd8e7..2530677 100644
--- a/base/trace_event/trace_log.cc
+++ b/base/trace_event/trace_log.cc
@@ -33,11 +33,11 @@
 #include "base/strings/stringprintf.h"
 #include "base/system/sys_info.h"
 #include "base/task/current_thread.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/task/single_thread_task_runner.h"
 #include "base/task/thread_pool.h"
 #include "base/threading/platform_thread.h"
-#include "base/threading/sequenced_task_runner_handle.h"
 #include "base/threading/thread_id_name_manager.h"
-#include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
 #include "base/trace_event/heap_profiler.h"
 #include "base/trace_event/heap_profiler_allocation_context_tracker.h"
@@ -516,12 +516,14 @@
 
   // This is to report the local memory usage when memory-infra is enabled.
   MemoryDumpManager::GetInstance()->RegisterDumpProvider(
-      this, "ThreadLocalEventBuffer", ThreadTaskRunnerHandle::Get());
+      this, "ThreadLocalEventBuffer",
+      SingleThreadTaskRunner::GetCurrentDefault());
 
   auto thread_id = PlatformThread::CurrentId();
 
   AutoLock lock(trace_log->lock_);
-  trace_log->thread_task_runners_[thread_id] = ThreadTaskRunnerHandle::Get();
+  trace_log->thread_task_runners_[thread_id] =
+      SingleThreadTaskRunner::GetCurrentDefault();
 }
 
 TraceLog::ThreadLocalEventBuffer::~ThreadLocalEventBuffer() {
@@ -604,7 +606,8 @@
 
 struct TraceLog::RegisteredAsyncObserver {
   explicit RegisteredAsyncObserver(WeakPtr<AsyncEnabledStateObserver> observer)
-      : observer(observer), task_runner(ThreadTaskRunnerHandle::Get()) {}
+      : observer(observer),
+        task_runner(SingleThreadTaskRunner::GetCurrentDefault()) {}
   ~RegisteredAsyncObserver() = default;
 
   WeakPtr<AsyncEnabledStateObserver> observer;
@@ -693,7 +696,7 @@
   // For a thread without a message loop or if the message loop may be blocked,
   // the trace events will be added into the main buffer directly.
   if (thread_blocks_message_loop_.Get() || !CurrentThread::IsSet() ||
-      !ThreadTaskRunnerHandle::IsSet()) {
+      !SingleThreadTaskRunner::HasCurrentDefault()) {
     return;
   }
   HEAP_PROFILER_SCOPED_IGNORE;
@@ -1090,7 +1093,7 @@
   // If the current thread has an active task runner, allow nested tasks to run
   // while stopping the session. This is needed by some tests, e.g., to allow
   // data sources to properly flush themselves.
-  if (ThreadTaskRunnerHandle::IsSet()) {
+  if (SingleThreadTaskRunner::HasCurrentDefault()) {
     RunLoop stop_loop(RunLoop::Type::kNestableTasksAllowed);
     auto quit_closure = stop_loop.QuitClosure();
     tracing_session_->SetOnStopCallback(
@@ -1303,7 +1306,9 @@
         perfetto::trace_processor::TraceProcessorStorage::CreateInstance(
             processor_config);
     json_output_writer_.reset(new JsonStringOutputWriter(
-        use_worker_thread ? ThreadTaskRunnerHandle::Get() : nullptr, cb));
+        use_worker_thread ? SingleThreadTaskRunner::GetCurrentDefault()
+                          : nullptr,
+        cb));
   } else {
     proto_output_callback_ = std::move(cb);
   }
@@ -1340,8 +1345,8 @@
   {
     AutoLock lock(lock_);
     DCHECK(!flush_task_runner_);
-    flush_task_runner_ = SequencedTaskRunnerHandle::IsSet()
-                             ? SequencedTaskRunnerHandle::Get()
+    flush_task_runner_ = SequencedTaskRunner::HasCurrentDefault()
+                             ? SequencedTaskRunner::GetCurrentDefault()
                              : nullptr;
     DCHECK(thread_task_runners_.empty() || flush_task_runner_);
     flush_output_callback_ = cb;
diff --git a/base/win/object_watcher.cc b/base/win/object_watcher.cc
index eb9914c3..151469c 100644
--- a/base/win/object_watcher.cc
+++ b/base/win/object_watcher.cc
@@ -8,7 +8,7 @@
 
 #include "base/bind.h"
 #include "base/logging.h"
-#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/task/sequenced_task_runner.h"
 
 namespace base {
 namespace win {
@@ -77,10 +77,10 @@
                                           const Location& from_here) {
   DCHECK(delegate);
   DCHECK(!wait_object_) << "Already watching an object";
-  DCHECK(SequencedTaskRunnerHandle::IsSet());
+  DCHECK(SequencedTaskRunner::HasCurrentDefault());
 
   location_ = from_here;
-  task_runner_ = SequencedTaskRunnerHandle::Get();
+  task_runner_ = SequencedTaskRunner::GetCurrentDefault();
 
   run_once_ = execute_only_once;
 
diff --git a/base/win/post_async_results.h b/base/win/post_async_results.h
index bb0e0de8..de6934a 100644
--- a/base/win/post_async_results.h
+++ b/base/win/post_async_results.h
@@ -18,7 +18,7 @@
 #include "base/location.h"
 #include "base/logging.h"
 #include "base/task/bind_post_task.h"
-#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/task/sequenced_task_runner.h"
 
 namespace base {
 namespace win {
@@ -125,7 +125,7 @@
         return std::make_pair(async_operation, async_status);
       })
           .Then(base::BindPostTask(
-              base::SequencedTaskRunnerHandle::Get(),
+              base::SequencedTaskRunner::GetCurrentDefault(),
               base::BindOnce(
                   [](IAsyncOperationCompletedHandlerT<T> completed_handler,
                      AsyncResult async_result) {
diff --git a/base/win/security_util.cc b/base/win/security_util.cc
index c94fe83..3bf54efe 100644
--- a/base/win/security_util.cc
+++ b/base/win/security_util.cc
@@ -4,18 +4,15 @@
 
 #include "base/win/security_util.h"
 
-#include <aclapi.h>
 #include <windows.h>
 
-#include <string>
-
 #include "base/check.h"
 #include "base/files/file_path.h"
 #include "base/logging.h"
-#include "base/numerics/checked_math.h"
+#include "base/win/access_control_list.h"
 #include "base/win/scoped_handle.h"
-#include "base/win/scoped_localalloc.h"
-#include "base/win/win_util.h"
+#include "base/win/security_descriptor.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace base {
 namespace win {
@@ -27,70 +24,41 @@
                   DWORD access_mask,
                   DWORD inheritance,
                   bool recursive,
-                  ACCESS_MODE access_mode) {
+                  SecurityAccessMode access_mode) {
   DCHECK(!path.empty());
-  if (sids.empty())
+  if (sids.empty()) {
     return true;
+  }
 
-  std::wstring object_name = path.value();
-  PSECURITY_DESCRIPTOR sd = nullptr;
-  PACL dacl = nullptr;
-
-  // Get the existing DACL.
-  DWORD error = ::GetNamedSecurityInfo(object_name.c_str(), SE_FILE_OBJECT,
-                                       DACL_SECURITY_INFORMATION, nullptr,
-                                       nullptr, &dacl, nullptr, &sd);
-  if (error != ERROR_SUCCESS) {
-    ::SetLastError(error);
-    DPLOG(ERROR) << "Failed getting DACL for path \"" << path.value() << "\"";
+  absl::optional<SecurityDescriptor> sd =
+      SecurityDescriptor::FromFile(path, DACL_SECURITY_INFORMATION);
+  if (!sd) {
     return false;
   }
-  auto sd_ptr = TakeLocalAlloc(sd);
-  std::vector<EXPLICIT_ACCESS> access_entries(sids.size());
-  auto entries_interator = access_entries.begin();
+
+  std::vector<ExplicitAccessEntry> entries;
   for (const Sid& sid : sids) {
-    EXPLICIT_ACCESS& new_access = *entries_interator++;
-    new_access.grfAccessMode = access_mode;
-    new_access.grfAccessPermissions = access_mask;
-    new_access.grfInheritance = inheritance;
-    ::BuildTrusteeWithSid(&new_access.Trustee, sid.GetPSID());
+    entries.emplace_back(sid, access_mode, access_mask, inheritance);
   }
 
-  PACL new_dacl = nullptr;
-  error = ::SetEntriesInAcl(checked_cast<ULONG>(access_entries.size()),
-                            access_entries.data(), dacl, &new_dacl);
-  if (error != ERROR_SUCCESS) {
-    ::SetLastError(error);
-    DPLOG(ERROR) << "Failed adding ACEs to DACL for path \"" << path.value()
-                 << "\"";
+  if (!sd->SetDaclEntries(entries)) {
     return false;
   }
-  auto new_dacl_ptr = TakeLocalAlloc(new_dacl);
+
   if (recursive) {
-    error = ::SetNamedSecurityInfo(&object_name[0], SE_FILE_OBJECT,
-                                   DACL_SECURITY_INFORMATION, nullptr, nullptr,
-                                   new_dacl_ptr.get(), nullptr);
-  } else {
-    ScopedHandle handle(::CreateFile(path.value().c_str(), WRITE_DAC, 0,
-                                     nullptr, OPEN_EXISTING,
-                                     FILE_FLAG_BACKUP_SEMANTICS, nullptr));
-    if (!handle.is_valid()) {
-      DPLOG(ERROR) << "Failed opening path \"" << path.value()
-                   << "\" to write DACL";
-      return false;
-    }
-    error = ::SetSecurityInfo(handle.get(), SE_KERNEL_OBJECT,
-                              DACL_SECURITY_INFORMATION, nullptr, nullptr,
-                              new_dacl_ptr.get(), nullptr);
+    return sd->WriteToFile(path, DACL_SECURITY_INFORMATION);
   }
 
-  if (error != ERROR_SUCCESS) {
-    ::SetLastError(error);
-    DPLOG(ERROR) << "Failed setting DACL for path \"" << path.value() << "\"";
+  ScopedHandle handle(::CreateFile(path.value().c_str(), WRITE_DAC, 0, nullptr,
+                                   OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS,
+                                   nullptr));
+  if (!handle.is_valid()) {
+    DPLOG(ERROR) << "Failed opening path \"" << path.value()
+                 << "\" to write DACL";
     return false;
   }
-
-  return true;
+  return sd->WriteToHandle(handle.get(), SecurityObjectType::kKernel,
+                           DACL_SECURITY_INFORMATION);
 }
 
 }  // namespace
@@ -101,7 +69,7 @@
                        DWORD inheritance,
                        bool recursive) {
   return AddACEToPath(path, sids, access_mask, inheritance, recursive,
-                      GRANT_ACCESS);
+                      SecurityAccessMode::kGrant);
 }
 
 bool DenyAccessToPath(const FilePath& path,
@@ -110,7 +78,7 @@
                       DWORD inheritance,
                       bool recursive) {
   return AddACEToPath(path, sids, access_mask, inheritance, recursive,
-                      DENY_ACCESS);
+                      SecurityAccessMode::kDeny);
 }
 
 std::vector<Sid> CloneSidVector(const std::vector<Sid>& sids) {
diff --git a/base/win/security_util_unittest.cc b/base/win/security_util_unittest.cc
index a09edb4..1e79f5c 100644
--- a/base/win/security_util_unittest.cc
+++ b/base/win/security_util_unittest.cc
@@ -8,8 +8,11 @@
 #include <sddl.h>
 #include <windows.h>
 
+#include <utility>
+
 #include "base/files/file_util.h"
 #include "base/files/scoped_temp_dir.h"
+#include "base/strings/stringprintf.h"
 #include "base/win/scoped_handle.h"
 #include "base/win/scoped_localalloc.h"
 #include "base/win/sid.h"
@@ -94,6 +97,15 @@
       GrantAccessToPath(path, *sids, FILE_GENERIC_READ, NO_INHERITANCE, true));
   EXPECT_TRUE(
       GrantAccessToPath(path, *sids, FILE_GENERIC_READ, NO_INHERITANCE, false));
+  std::vector<Sid> large_sid_list;
+  while (large_sid_list.size() < 0x10000) {
+    auto sid = Sid::FromSddlString(
+        base::StringPrintf(L"S-1-5-1234-%zu", large_sid_list.size()).c_str());
+    ASSERT_TRUE(sid);
+    large_sid_list.emplace_back(std::move(*sid));
+  }
+  EXPECT_FALSE(GrantAccessToPath(path, large_sid_list, FILE_GENERIC_READ,
+                                 NO_INHERITANCE, false));
   path = temp_dir.GetPath().Append(L"test_nowritedac");
   ASSERT_TRUE(CreateWithDacl(path, kNoWriteDacDacl, false));
   EXPECT_FALSE(
@@ -126,6 +138,9 @@
   FilePath path = temp_dir.GetPath().Append(L"test");
   ASSERT_TRUE(CreateWithDacl(path, kBaseDacl, false));
   EXPECT_EQ(kBaseDacl, GetFileDacl(path));
+  EXPECT_TRUE(
+      GrantAccessToPath(path, {}, FILE_GENERIC_READ, NO_INHERITANCE, false));
+  EXPECT_EQ(kBaseDacl, GetFileDacl(path));
   auto sids = Sid::FromSddlStringVector({kAuthenticatedUsersSid});
   ASSERT_TRUE(sids);
   EXPECT_TRUE(
@@ -144,6 +159,9 @@
   FilePath path = temp_dir.GetPath().Append(L"test");
   ASSERT_TRUE(CreateWithDacl(path, kBaseDacl, false));
   EXPECT_EQ(kBaseDacl, GetFileDacl(path));
+  EXPECT_TRUE(
+      DenyAccessToPath(path, {}, FILE_GENERIC_READ, NO_INHERITANCE, true));
+  EXPECT_EQ(kBaseDacl, GetFileDacl(path));
   auto sids = Sid::FromSddlStringVector({kLocalGuestSid});
   ASSERT_TRUE(sids);
   EXPECT_TRUE(
diff --git a/build/android/pylib/junit/junit_test_instance.py b/build/android/pylib/junit/junit_test_instance.py
index 5471b7a..f7bd49a 100644
--- a/build/android/pylib/junit/junit_test_instance.py
+++ b/build/android/pylib/junit/junit_test_instance.py
@@ -22,6 +22,8 @@
     self._runner_filter = args.runner_filter
     self._shards = args.shards
     self._test_filters = test_filter.InitializeFiltersFromArgs(args)
+    self._has_literal_filters = (args.isolated_script_test_filters
+                                 or args.test_filters)
     self._test_suite = args.test_suite
 
   #override
@@ -73,6 +75,10 @@
     return self._test_filters
 
   @property
+  def has_literal_filters(self):
+    return self._has_literal_filters
+
+  @property
   def shards(self):
     return self._shards
 
diff --git a/build/android/pylib/local/machine/local_machine_junit_test_run.py b/build/android/pylib/local/machine/local_machine_junit_test_run.py
index 45e899048..b33ae9b 100644
--- a/build/android/pylib/local/machine/local_machine_junit_test_run.py
+++ b/build/android/pylib/local/machine/local_machine_junit_test_run.py
@@ -61,13 +61,13 @@
   def SetUp(self):
     pass
 
-  def _GetFilterArgs(self, test_filter_override=None):
+  def _GetFilterArgs(self, shard_test_filter=None):
     ret = []
-    if test_filter_override:
-      ret += ['-gtest-filter', ':'.join(test_filter_override)]
-    elif self._test_instance.test_filters:
-      for test_filter in self._test_instance.test_filters:
-        ret += ['-gtest-filter', test_filter]
+    if shard_test_filter:
+      ret += ['-gtest-filter', ':'.join(shard_test_filter)]
+
+    for test_filter in self._test_instance.test_filters:
+      ret += ['-gtest-filter', test_filter]
 
     if self._test_instance.package_filter:
       ret += ['-package-filter', self._test_instance.package_filter]
@@ -84,8 +84,8 @@
     jar_args_list = [['-json-results-file', result_file]
                      for result_file in json_result_file_paths]
     for index, jar_arg in enumerate(jar_args_list):
-      test_filter_override = group_test_list[index] if shards > 1 else None
-      jar_arg += self._GetFilterArgs(test_filter_override)
+      shard_test_filter = group_test_list[index] if shards > 1 else None
+      jar_arg += self._GetFilterArgs(shard_test_filter)
 
     return jar_args_list
 
@@ -155,10 +155,10 @@
   def RunTests(self, results, raw_logs_fh=None):
     # This avoids searching through the classparth jars for tests classes,
     # which takes about 1-2 seconds.
-    # Do not shard when a test filter is present since we do not know at this
-    # point which tests will be filtered out.
-    if (self._test_instance.shards == 1 or self._test_instance.test_filters
-        or self._test_instance.suite in _EXCLUDED_SUITES):
+    if (self._test_instance.shards == 1
+        # TODO(crbug.com/1383650): remove this
+        or self._test_instance.has_literal_filters or
+        self._test_instance.suite in _EXCLUDED_SUITES):
       test_classes = []
       shards = 1
     else:
diff --git a/build/fuchsia/linux_internal.sdk.sha1 b/build/fuchsia/linux_internal.sdk.sha1
index e133ca02..c59da28 100644
--- a/build/fuchsia/linux_internal.sdk.sha1
+++ b/build/fuchsia/linux_internal.sdk.sha1
@@ -1 +1 @@
-11.20221208.0.1
+11.20221208.2.1
diff --git a/build/fuchsia/test/common.py b/build/fuchsia/test/common.py
index de0d9dd..448b1df 100644
--- a/build/fuchsia/test/common.py
+++ b/build/fuchsia/test/common.py
@@ -62,6 +62,14 @@
     return 'Running' in _get_daemon_status()
 
 
+def check_ssh_config_file() -> None:
+    """Checks for ssh keys and generates them if they are missing."""
+
+    script_path = os.path.join(SDK_ROOT, 'bin', 'fuchsia-common.sh')
+    check_cmd = ['bash', '-c', f'. {script_path}; check-fuchsia-ssh-config']
+    subprocess.run(check_cmd, check=True)
+
+
 def _wait_for_daemon(start=True, timeout_seconds=100):
     """Waits for daemon to reach desired state in a polling loop.
 
@@ -177,13 +185,14 @@
 
 def run_continuous_ffx_command(cmd: Iterable[str],
                                target_id: Optional[str] = None,
+                               encoding: Optional[str] = 'utf-8',
                                **kwargs) -> subprocess.Popen:
     """Runs an ffx command asynchronously."""
     ffx_cmd = [_FFX_TOOL]
     if target_id:
         ffx_cmd.extend(('--target', target_id))
     ffx_cmd.extend(cmd)
-    return subprocess.Popen(ffx_cmd, encoding='utf-8', **kwargs)
+    return subprocess.Popen(ffx_cmd, encoding=encoding, **kwargs)
 
 
 def read_package_paths(out_dir: str, pkg_name: str) -> List[str]:
diff --git a/build/fuchsia/test/ffx_integration.py b/build/fuchsia/test/ffx_integration.py
index 424873f..0c09c084 100644
--- a/build/fuchsia/test/ffx_integration.py
+++ b/build/fuchsia/test/ffx_integration.py
@@ -15,8 +15,8 @@
 from contextlib import AbstractContextManager
 from typing import Iterable, Optional
 
-from common import run_ffx_command, run_continuous_ffx_command, \
-                   SDK_ROOT
+from common import check_ssh_config_file, run_ffx_command, \
+                   run_continuous_ffx_command, SDK_ROOT
 from compatible_utils import get_host_arch
 
 _EMU_COMMAND_RETRIES = 3
@@ -119,16 +119,6 @@
                 self._scoped_pb_metadata = ScopedFfxConfig(
                     'pbms.metadata', json.dumps((pb_metadata)))
 
-    @staticmethod
-    def _check_ssh_config_file() -> None:
-        """Checks for ssh keys and generates them if they are missing."""
-
-        script_path = os.path.join(SDK_ROOT, 'bin', 'fuchsia-common.sh')
-        check_cmd = [
-            'bash', '-c', f'. {script_path}; check-fuchsia-ssh-config'
-        ]
-        subprocess.run(check_cmd, check=True)
-
     def __enter__(self) -> str:
         """Start the emulator.
 
@@ -138,7 +128,7 @@
 
         if self._scoped_pb_metadata:
             self._scoped_pb_metadata.__enter__()
-        self._check_ssh_config_file()
+        check_ssh_config_file()
         emu_command = [
             'emu', 'start', self._product_bundle, '--name', self._node_name
         ]
diff --git a/build/fuchsia/test/flash_device.py b/build/fuchsia/test/flash_device.py
index 159ed7c4..8e1efe29 100755
--- a/build/fuchsia/test/flash_device.py
+++ b/build/fuchsia/test/flash_device.py
@@ -14,7 +14,8 @@
 
 from typing import Optional, Tuple
 
-from common import register_device_args, run_ffx_command, SDK_ROOT
+from common import check_ssh_config_file, register_device_args, \
+                   run_ffx_command, SDK_ROOT
 from compatible_utils import get_sdk_hash, get_ssh_keys, pave, \
     running_unattended, add_exec_to_file, get_host_arch, find_image_in_sdk
 from ffx_integration import ScopedFfxConfig
@@ -155,6 +156,7 @@
 
     system_image_dir = actual_image_dir
     if needs_update:
+        check_ssh_config_file()
         if should_pave:
             if running_unattended():
                 assert target, ('Target ID must be specified on swarming when'
diff --git a/build/fuchsia/test/flash_device_unittests.py b/build/fuchsia/test/flash_device_unittests.py
index 4b69b01..b0eaf07b 100755
--- a/build/fuchsia/test/flash_device_unittests.py
+++ b/build/fuchsia/test/flash_device_unittests.py
@@ -31,11 +31,15 @@
         self._sdk_hash_patcher = mock.patch('flash_device.get_sdk_hash',
                                             return_value=(_TEST_PRODUCT,
                                                           _TEST_VERSION))
+        self._check_patcher = mock.patch('flash_device.check_ssh_config_file')
         self._config_mock = self._config_patcher.start()
         self._ffx_mock = self._ffx_patcher.start()
         self._sdk_hash_mock = self._sdk_hash_patcher.start()
+        self._check_patcher_mock = self._check_patcher.start()
+        self.addCleanup(self._config_mock.stop)
         self.addCleanup(self._ffx_mock.stop)
         self.addCleanup(self._sdk_hash_mock.stop)
+        self.addCleanup(self._check_patcher_mock.stop)
 
     def test_update_required_on_ignore_returns_immediately(self) -> None:
         """Test |os_check|='ignore' skips all checks."""
diff --git a/build/fuchsia/test/run_blink_test.py b/build/fuchsia/test/run_blink_test.py
index 44ffc5f..ba71aa6 100644
--- a/build/fuchsia/test/run_blink_test.py
+++ b/build/fuchsia/test/run_blink_test.py
@@ -29,8 +29,8 @@
         return False
 
     def run_test(self):
-        test_cmd = [_BLINK_TEST_SCRIPT]
-        test_cmd.append('--platform=fuchsia')
+        test_cmd = [_BLINK_TEST_SCRIPT, '-t', os.path.basename(self._out_dir)]
+
         if self._test_args:
             test_cmd.extend(self._test_args)
         return subprocess.run(test_cmd, check=True)
diff --git a/buildtools/deps_revisions.gni b/buildtools/deps_revisions.gni
index 1048aa3..04fa0ff6 100644
--- a/buildtools/deps_revisions.gni
+++ b/buildtools/deps_revisions.gni
@@ -5,5 +5,5 @@
 declare_args() {
   # Used to cause full rebuilds on libc++ rolls. This should be kept in sync
   # with the libcxx_revision vars in //DEPS.
-  libcxx_revision = "e4e39cee1f5e89a9f9949084ba26a6efc3cd4141"
+  libcxx_revision = "52399655fdafdd14ade17ab12ddc9e955423aa5a"
 }
diff --git a/cc/layers/layer.h b/cc/layers/layer.h
index ee15ee0..503ee5e 100644
--- a/cc/layers/layer.h
+++ b/cc/layers/layer.h
@@ -26,6 +26,7 @@
 #include "cc/layers/touch_action_region.h"
 #include "cc/paint/element_id.h"
 #include "cc/paint/filter_operations.h"
+#include "cc/paint/node_id.h"
 #include "cc/paint/paint_record.h"
 #include "cc/trees/effect_node.h"
 #include "cc/trees/property_tree.h"
diff --git a/cc/paint/BUILD.gn b/cc/paint/BUILD.gn
index 152ec659..ca082143 100644
--- a/cc/paint/BUILD.gn
+++ b/cc/paint/BUILD.gn
@@ -49,8 +49,12 @@
     "paint_image_builder.h",
     "paint_image_generator.cc",
     "paint_image_generator.h",
+    "paint_op.cc",
+    "paint_op.h",
     "paint_op_buffer.cc",
     "paint_op_buffer.h",
+    "paint_op_buffer_iterator.cc",
+    "paint_op_buffer_iterator.h",
     "paint_op_buffer_serializer.cc",
     "paint_op_buffer_serializer.h",
     "paint_op_reader.cc",
diff --git a/cc/paint/discardable_image_map.cc b/cc/paint/discardable_image_map.cc
index bf5cf44..1899598 100644
--- a/cc/paint/discardable_image_map.cc
+++ b/cc/paint/discardable_image_map.cc
@@ -18,6 +18,7 @@
 #include "cc/paint/image_provider.h"
 #include "cc/paint/paint_filter.h"
 #include "cc/paint/paint_op_buffer.h"
+#include "cc/paint/paint_op_buffer_iterator.h"
 #include "cc/paint/skottie_wrapper.h"
 #include "third_party/skia/include/utils/SkNoDrawCanvas.h"
 #include "ui/gfx/display_color_spaces.h"
diff --git a/cc/paint/display_item_list.cc b/cc/paint/display_item_list.cc
index c88838c2c..0a3365c4 100644
--- a/cc/paint/display_item_list.cc
+++ b/cc/paint/display_item_list.cc
@@ -13,6 +13,7 @@
 #include "base/trace_event/traced_value.h"
 #include "cc/base/math_util.h"
 #include "cc/debug/picture_debug_util.h"
+#include "cc/paint/paint_op_buffer_iterator.h"
 #include "cc/paint/solid_color_analyzer.h"
 #include "third_party/skia/include/core/SkCanvas.h"
 #include "third_party/skia/include/core/SkPictureRecorder.h"
diff --git a/cc/paint/display_item_list.h b/cc/paint/display_item_list.h
index b68b325..4a2739d 100644
--- a/cc/paint/display_item_list.h
+++ b/cc/paint/display_item_list.h
@@ -17,6 +17,7 @@
 #include "cc/paint/discardable_image_map.h"
 #include "cc/paint/image_id.h"
 #include "cc/paint/paint_export.h"
+#include "cc/paint/paint_op.h"
 #include "cc/paint/paint_op_buffer.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/skia/include/core/SkPicture.h"
diff --git a/cc/paint/paint_filter_unittest.cc b/cc/paint/paint_filter_unittest.cc
index 781a20d..a34c7be 100644
--- a/cc/paint/paint_filter_unittest.cc
+++ b/cc/paint/paint_filter_unittest.cc
@@ -5,7 +5,7 @@
 #include "cc/paint/paint_filter.h"
 
 #include "cc/paint/image_provider.h"
-#include "cc/paint/paint_op_buffer.h"
+#include "cc/paint/paint_op.h"
 #include "cc/test/skia_common.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/skia/include/effects/SkLumaColorFilter.h"
diff --git a/cc/paint/paint_op.cc b/cc/paint/paint_op.cc
new file mode 100644
index 0000000..80bb723
--- /dev/null
+++ b/cc/paint/paint_op.cc
@@ -0,0 +1,2864 @@
+// 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.
+
+#include "cc/paint/paint_op.h"
+
+#include <algorithm>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/memory/raw_ptr.h"
+#include "base/notreached.h"
+#include "base/types/optional_util.h"
+#include "cc/paint/decoded_draw_image.h"
+#include "cc/paint/display_item_list.h"
+#include "cc/paint/image_provider.h"
+#include "cc/paint/paint_flags.h"
+#include "cc/paint/paint_image_builder.h"
+#include "cc/paint/paint_op_reader.h"
+#include "cc/paint/paint_op_writer.h"
+#include "cc/paint/paint_record.h"
+#include "cc/paint/skottie_serialization_history.h"
+#include "third_party/skia/include/core/SkAnnotation.h"
+#include "third_party/skia/include/core/SkCanvas.h"
+#include "third_party/skia/include/core/SkColorSpace.h"
+#include "third_party/skia/include/core/SkImage.h"
+#include "third_party/skia/include/core/SkRegion.h"
+#include "third_party/skia/include/core/SkSerialProcs.h"
+#include "third_party/skia/include/core/SkTextBlob.h"
+#include "third_party/skia/include/docs/SkPDFDocument.h"
+#include "third_party/skia/include/private/chromium/GrSlug.h"
+#include "ui/gfx/geometry/skia_conversions.h"
+
+namespace cc {
+namespace {
+// In a future CL, convert DrawImage to explicitly take sampling instead of
+// quality
+PaintFlags::FilterQuality sampling_to_quality(
+    const SkSamplingOptions& sampling) {
+  if (sampling.useCubic) {
+    return PaintFlags::FilterQuality::kHigh;
+  }
+  if (sampling.mipmap != SkMipmapMode::kNone) {
+    return PaintFlags::FilterQuality::kMedium;
+  }
+  return sampling.filter == SkFilterMode::kLinear
+             ? PaintFlags::FilterQuality::kLow
+             : PaintFlags::FilterQuality::kNone;
+}
+
+DrawImage CreateDrawImage(const PaintImage& image,
+                          const PaintFlags* flags,
+                          const SkSamplingOptions& sampling,
+                          const SkM44& matrix) {
+  if (!image)
+    return DrawImage();
+  return DrawImage(image, flags->useDarkModeForImage(),
+                   SkIRect::MakeWH(image.width(), image.height()),
+                   sampling_to_quality(sampling), matrix);
+}
+
+bool IsScaleAdjustmentIdentity(const SkSize& scale_adjustment) {
+  return std::abs(scale_adjustment.width() - 1.f) < FLT_EPSILON &&
+         std::abs(scale_adjustment.height() - 1.f) < FLT_EPSILON;
+}
+
+SkRect AdjustSrcRectForScale(SkRect original, SkSize scale_adjustment) {
+  if (IsScaleAdjustmentIdentity(scale_adjustment))
+    return original;
+
+  float x_scale = scale_adjustment.width();
+  float y_scale = scale_adjustment.height();
+  return SkRect::MakeXYWH(original.x() * x_scale, original.y() * y_scale,
+                          original.width() * x_scale,
+                          original.height() * y_scale);
+}
+
+SkRect MapRect(const SkMatrix& matrix, const SkRect& src) {
+  SkRect dst;
+  matrix.mapRect(&dst, src);
+  return dst;
+}
+
+void DrawImageRect(SkCanvas* canvas,
+                   const SkImage* image,
+                   const SkRect& src,
+                   const SkRect& dst,
+                   const SkSamplingOptions& options,
+                   const SkPaint* paint,
+                   SkCanvas::SrcRectConstraint constraint) {
+  if (!image)
+    return;
+  if (constraint == SkCanvas::kStrict_SrcRectConstraint &&
+      options.mipmap != SkMipmapMode::kNone &&
+      src.contains(SkRect::Make(image->dimensions()))) {
+    SkMatrix m;
+    m.setRectToRect(src, dst, SkMatrix::ScaleToFit::kFill_ScaleToFit);
+    canvas->save();
+    canvas->concat(m);
+    canvas->drawImage(image, 0, 0, options, paint);
+    canvas->restore();
+    return;
+  }
+  canvas->drawImageRect(image, src, dst, options, paint, constraint);
+}
+
+bool GrSlugAreEqual(sk_sp<GrSlug> left, sk_sp<GrSlug> right) {
+  if (!left && !right) {
+    return true;
+  }
+  if (left && right) {
+    auto left_data = left->serialize();
+    auto right_data = right->serialize();
+    return left_data->equals(right_data.get());
+  }
+  return false;
+}
+
+}  // namespace
+
+#define TYPES(M)      \
+  M(AnnotateOp)       \
+  M(ClipPathOp)       \
+  M(ClipRectOp)       \
+  M(ClipRRectOp)      \
+  M(ConcatOp)         \
+  M(CustomDataOp)     \
+  M(DrawColorOp)      \
+  M(DrawDRRectOp)     \
+  M(DrawImageOp)      \
+  M(DrawImageRectOp)  \
+  M(DrawIRectOp)      \
+  M(DrawLineOp)       \
+  M(DrawOvalOp)       \
+  M(DrawPathOp)       \
+  M(DrawRecordOp)     \
+  M(DrawRectOp)       \
+  M(DrawRRectOp)      \
+  M(DrawSkottieOp)    \
+  M(DrawTextBlobOp)   \
+  M(NoopOp)           \
+  M(RestoreOp)        \
+  M(RotateOp)         \
+  M(SaveOp)           \
+  M(SaveLayerOp)      \
+  M(SaveLayerAlphaOp) \
+  M(ScaleOp)          \
+  M(SetMatrixOp)      \
+  M(SetNodeIdOp)      \
+  M(TranslateOp)
+
+static constexpr size_t kNumOpTypes =
+    static_cast<size_t>(PaintOpType::LastPaintOpType) + 1;
+
+// Verify that every op is in the TYPES macro.
+#define M(T) +1
+static_assert(kNumOpTypes == TYPES(M), "Missing op in list");
+#undef M
+
+#define M(T) sizeof(T),
+static const size_t g_type_to_size[kNumOpTypes] = {TYPES(M)};
+#undef M
+
+template <typename T, bool HasFlags>
+struct Rasterizer {
+  static void RasterWithFlags(const T* op,
+                              const PaintFlags* flags,
+                              SkCanvas* canvas,
+                              const PlaybackParams& params) {
+    static_assert(
+        !T::kHasPaintFlags,
+        "This function should not be used for a PaintOp that has PaintFlags");
+    DCHECK(op->IsValid());
+    NOTREACHED();
+  }
+  static void Raster(const T* op,
+                     SkCanvas* canvas,
+                     const PlaybackParams& params) {
+    static_assert(
+        !T::kHasPaintFlags,
+        "This function should not be used for a PaintOp that has PaintFlags");
+    DCHECK(op->IsValid());
+    T::Raster(op, canvas, params);
+  }
+};
+
+template <typename T>
+struct Rasterizer<T, true> {
+  static void RasterWithFlags(const T* op,
+                              const PaintFlags* flags,
+                              SkCanvas* canvas,
+                              const PlaybackParams& params) {
+    static_assert(T::kHasPaintFlags,
+                  "This function expects the PaintOp to have PaintFlags");
+    DCHECK(op->IsValid());
+    T::RasterWithFlags(op, flags, canvas, params);
+  }
+
+  static void Raster(const T* op,
+                     SkCanvas* canvas,
+                     const PlaybackParams& params) {
+    static_assert(T::kHasPaintFlags,
+                  "This function expects the PaintOp to have PaintFlags");
+    DCHECK(op->IsValid());
+    T::RasterWithFlags(op, &op->flags, canvas, params);
+  }
+};
+
+using RasterFunction = void (*)(const PaintOp* op,
+                                SkCanvas* canvas,
+                                const PlaybackParams& params);
+#define M(T)                                                              \
+  [](const PaintOp* op, SkCanvas* canvas, const PlaybackParams& params) { \
+    Rasterizer<T, T::kHasPaintFlags>::Raster(static_cast<const T*>(op),   \
+                                             canvas, params);             \
+  },
+static const RasterFunction g_raster_functions[kNumOpTypes] = {TYPES(M)};
+#undef M
+
+using RasterWithFlagsFunction = void (*)(const PaintOp* op,
+                                         const PaintFlags* flags,
+                                         SkCanvas* canvas,
+                                         const PlaybackParams& params);
+#define M(T)                                                       \
+  [](const PaintOp* op, const PaintFlags* flags, SkCanvas* canvas, \
+     const PlaybackParams& params) {                               \
+    Rasterizer<T, T::kHasPaintFlags>::RasterWithFlags(             \
+        static_cast<const T*>(op), flags, canvas, params);         \
+  },
+static const RasterWithFlagsFunction
+    g_raster_with_flags_functions[kNumOpTypes] = {TYPES(M)};
+#undef M
+
+using SerializeFunction = size_t (*)(const PaintOp* op,
+                                     void* memory,
+                                     size_t size,
+                                     const PaintOp::SerializeOptions& options,
+                                     const PaintFlags* flags_to_serialize,
+                                     const SkM44& current_ctm,
+                                     const SkM44& original_ctm);
+
+#define M(T) &T::Serialize,
+static const SerializeFunction g_serialize_functions[kNumOpTypes] = {TYPES(M)};
+#undef M
+
+using DeserializeFunction =
+    PaintOp* (*)(const volatile void* input,
+                 size_t input_size,
+                 void* output,
+                 size_t output_size,
+                 const PaintOp::DeserializeOptions& options);
+
+#define M(T) &T::Deserialize,
+static const DeserializeFunction g_deserialize_functions[kNumOpTypes] = {
+    TYPES(M)};
+#undef M
+
+using EqualsFunction = bool (*)(const PaintOp* left, const PaintOp* right);
+#define M(T) &T::AreEqual,
+static const EqualsFunction g_equals_operator[kNumOpTypes] = {TYPES(M)};
+#undef M
+
+// Most state ops (matrix, clip, save, restore) have a trivial destructor.
+// TODO(enne): evaluate if we need the nullptr optimization or if
+// we even need to differentiate trivial destructors here.
+using VoidFunction = void (*)(PaintOp* op);
+#define M(T)                                           \
+  !std::is_trivially_destructible<T>::value            \
+      ? [](PaintOp* op) { static_cast<T*>(op)->~T(); } \
+      : static_cast<VoidFunction>(nullptr),
+static const VoidFunction g_destructor_functions[kNumOpTypes] = {TYPES(M)};
+#undef M
+
+#define M(T) T::kIsDrawOp,
+static bool g_is_draw_op[kNumOpTypes] = {TYPES(M)};
+#undef M
+
+#define M(T) T::kHasPaintFlags,
+static bool g_has_paint_flags[kNumOpTypes] = {TYPES(M)};
+#undef M
+
+#define M(T)                                         \
+  static_assert(sizeof(T) <= sizeof(LargestPaintOp), \
+                #T " must be no bigger than LargestPaintOp");
+TYPES(M)
+#undef M
+
+#define M(T)                                                \
+  static_assert(alignof(T) <= PaintOpBuffer::kPaintOpAlign, \
+                #T " must have alignment no bigger than PaintOpAlign");
+TYPES(M)
+#undef M
+
+using AnalyzeOpFunc = void (*)(PaintOpBuffer*, const PaintOp*);
+#define M(T)                                           \
+  [](PaintOpBuffer* buffer, const PaintOp* op) {       \
+    buffer->AnalyzeAddedOp(static_cast<const T*>(op)); \
+  },
+static const AnalyzeOpFunc g_analyze_op_functions[kNumOpTypes] = {TYPES(M)};
+#undef M
+
+#undef TYPES
+
+const SkRect PaintOp::kUnsetRect = {SK_ScalarInfinity, 0, 0, 0};
+const size_t PaintOp::kMaxSkip;
+
+std::string PaintOpTypeToString(PaintOpType type) {
+  switch (type) {
+    case PaintOpType::Annotate:
+      return "Annotate";
+    case PaintOpType::ClipPath:
+      return "ClipPath";
+    case PaintOpType::ClipRect:
+      return "ClipRect";
+    case PaintOpType::ClipRRect:
+      return "ClipRRect";
+    case PaintOpType::Concat:
+      return "Concat";
+    case PaintOpType::CustomData:
+      return "CustomData";
+    case PaintOpType::DrawColor:
+      return "DrawColor";
+    case PaintOpType::DrawDRRect:
+      return "DrawDRRect";
+    case PaintOpType::DrawImage:
+      return "DrawImage";
+    case PaintOpType::DrawImageRect:
+      return "DrawImageRect";
+    case PaintOpType::DrawIRect:
+      return "DrawIRect";
+    case PaintOpType::DrawLine:
+      return "DrawLine";
+    case PaintOpType::DrawOval:
+      return "DrawOval";
+    case PaintOpType::DrawPath:
+      return "DrawPath";
+    case PaintOpType::DrawRecord:
+      return "DrawRecord";
+    case PaintOpType::DrawRect:
+      return "DrawRect";
+    case PaintOpType::DrawRRect:
+      return "DrawRRect";
+    case PaintOpType::DrawSkottie:
+      return "DrawSkottie";
+    case PaintOpType::DrawTextBlob:
+      return "DrawTextBlob";
+    case PaintOpType::Noop:
+      return "Noop";
+    case PaintOpType::Restore:
+      return "Restore";
+    case PaintOpType::Rotate:
+      return "Rotate";
+    case PaintOpType::Save:
+      return "Save";
+    case PaintOpType::SaveLayer:
+      return "SaveLayer";
+    case PaintOpType::SaveLayerAlpha:
+      return "SaveLayerAlpha";
+    case PaintOpType::Scale:
+      return "Scale";
+    case PaintOpType::SetMatrix:
+      return "SetMatrix";
+    case PaintOpType::SetNodeId:
+      return "SetNodeId";
+    case PaintOpType::Translate:
+      return "Translate";
+  }
+  return "UNKNOWN";
+}
+
+std::ostream& operator<<(std::ostream& os, PaintOpType type) {
+  return os << PaintOpTypeToString(type);
+}
+
+size_t AnnotateOp::Serialize(const PaintOp* base_op,
+                             void* memory,
+                             size_t size,
+                             const SerializeOptions& options,
+                             const PaintFlags* flags_to_serialize,
+                             const SkM44& current_ctm,
+                             const SkM44& original_ctm) {
+  auto* op = static_cast<const AnnotateOp*>(base_op);
+  PaintOpWriter helper(memory, size, options);
+  helper.Write(op->annotation_type);
+  helper.Write(op->rect);
+  helper.Write(op->data);
+  return helper.size();
+}
+
+size_t ClipPathOp::Serialize(const PaintOp* base_op,
+                             void* memory,
+                             size_t size,
+                             const SerializeOptions& options,
+                             const PaintFlags* flags_to_serialize,
+                             const SkM44& current_ctm,
+                             const SkM44& original_ctm) {
+  auto* op = static_cast<const ClipPathOp*>(base_op);
+  PaintOpWriter helper(memory, size, options);
+  helper.Write(op->path, op->use_cache);
+  helper.Write(op->op);
+  helper.Write(op->antialias);
+  return helper.size();
+}
+
+size_t ClipRectOp::Serialize(const PaintOp* base_op,
+                             void* memory,
+                             size_t size,
+                             const SerializeOptions& options,
+                             const PaintFlags* flags_to_serialize,
+                             const SkM44& current_ctm,
+                             const SkM44& original_ctm) {
+  auto* op = static_cast<const ClipRectOp*>(base_op);
+  PaintOpWriter helper(memory, size, options);
+  helper.Write(op->rect);
+  helper.Write(op->op);
+  helper.Write(op->antialias);
+  return helper.size();
+}
+
+size_t ClipRRectOp::Serialize(const PaintOp* base_op,
+                              void* memory,
+                              size_t size,
+                              const SerializeOptions& options,
+                              const PaintFlags* flags_to_serialize,
+                              const SkM44& current_ctm,
+                              const SkM44& original_ctm) {
+  auto* op = static_cast<const ClipRRectOp*>(base_op);
+  PaintOpWriter helper(memory, size, options);
+  helper.Write(op->rrect);
+  helper.Write(op->op);
+  helper.Write(op->antialias);
+  return helper.size();
+}
+
+size_t ConcatOp::Serialize(const PaintOp* base_op,
+                           void* memory,
+                           size_t size,
+                           const SerializeOptions& options,
+                           const PaintFlags* flags_to_serialize,
+                           const SkM44& current_ctm,
+                           const SkM44& original_ctm) {
+  auto* op = static_cast<const ConcatOp*>(base_op);
+  PaintOpWriter helper(memory, size, options);
+  helper.Write(op->matrix);
+  return helper.size();
+}
+
+size_t CustomDataOp::Serialize(const PaintOp* base_op,
+                               void* memory,
+                               size_t size,
+                               const SerializeOptions& options,
+                               const PaintFlags* flags_to_serialize,
+                               const SkM44& current_ctm,
+                               const SkM44& original_ctm) {
+  auto* op = static_cast<const CustomDataOp*>(base_op);
+  PaintOpWriter helper(memory, size, options);
+  helper.Write(op->id);
+  return helper.size();
+}
+
+size_t DrawColorOp::Serialize(const PaintOp* base_op,
+                              void* memory,
+                              size_t size,
+                              const SerializeOptions& options,
+                              const PaintFlags* flags_to_serialize,
+                              const SkM44& current_ctm,
+                              const SkM44& original_ctm) {
+  auto* op = static_cast<const DrawColorOp*>(base_op);
+  PaintOpWriter helper(memory, size, options);
+  helper.Write(op->color);
+  helper.Write(op->mode);
+  return helper.size();
+}
+
+size_t DrawDRRectOp::Serialize(const PaintOp* base_op,
+                               void* memory,
+                               size_t size,
+                               const SerializeOptions& options,
+                               const PaintFlags* flags_to_serialize,
+                               const SkM44& current_ctm,
+                               const SkM44& original_ctm) {
+  auto* op = static_cast<const DrawDRRectOp*>(base_op);
+  PaintOpWriter helper(memory, size, options);
+  if (!flags_to_serialize)
+    flags_to_serialize = &op->flags;
+  helper.Write(*flags_to_serialize, current_ctm);
+  helper.Write(op->outer);
+  helper.Write(op->inner);
+  return helper.size();
+}
+
+size_t DrawImageOp::Serialize(const PaintOp* base_op,
+                              void* memory,
+                              size_t size,
+                              const SerializeOptions& options,
+                              const PaintFlags* flags_to_serialize,
+                              const SkM44& current_ctm,
+                              const SkM44& original_ctm) {
+  auto* op = static_cast<const DrawImageOp*>(base_op);
+  PaintOpWriter helper(memory, size, options);
+  if (!flags_to_serialize)
+    flags_to_serialize = &op->flags;
+  helper.Write(*flags_to_serialize, current_ctm);
+
+  SkSize scale_adjustment = SkSize::Make(1.f, 1.f);
+  helper.Write(
+      CreateDrawImage(op->image, flags_to_serialize, op->sampling, current_ctm),
+      &scale_adjustment);
+  helper.AssertAlignment(alignof(SkScalar));
+  helper.Write(scale_adjustment.width());
+  helper.Write(scale_adjustment.height());
+
+  helper.Write(op->left);
+  helper.Write(op->top);
+  helper.Write(op->sampling);
+  return helper.size();
+}
+
+size_t DrawImageRectOp::Serialize(const PaintOp* base_op,
+                                  void* memory,
+                                  size_t size,
+                                  const SerializeOptions& options,
+                                  const PaintFlags* flags_to_serialize,
+                                  const SkM44& current_ctm,
+                                  const SkM44& original_ctm) {
+  auto* op = static_cast<const DrawImageRectOp*>(base_op);
+  PaintOpWriter helper(memory, size, options);
+  if (!flags_to_serialize)
+    flags_to_serialize = &op->flags;
+  helper.Write(*flags_to_serialize, current_ctm);
+
+  // This adjustment mirrors DiscardableImageMap::GatherDiscardableImage logic.
+  SkM44 matrix = current_ctm * SkM44(SkMatrix::RectToRect(op->src, op->dst));
+  // Note that we don't request subsets here since the GpuImageCache has no
+  // optimizations for using subsets.
+  SkSize scale_adjustment = SkSize::Make(1.f, 1.f);
+  helper.Write(
+      CreateDrawImage(op->image, flags_to_serialize, op->sampling, matrix),
+      &scale_adjustment);
+  helper.AssertAlignment(alignof(SkScalar));
+  helper.Write(scale_adjustment.width());
+  helper.Write(scale_adjustment.height());
+
+  helper.Write(op->src);
+  helper.Write(op->dst);
+  helper.Write(op->sampling);
+  helper.Write(op->constraint);
+  return helper.size();
+}
+
+size_t DrawIRectOp::Serialize(const PaintOp* base_op,
+                              void* memory,
+                              size_t size,
+                              const SerializeOptions& options,
+                              const PaintFlags* flags_to_serialize,
+                              const SkM44& current_ctm,
+                              const SkM44& original_ctm) {
+  auto* op = static_cast<const DrawIRectOp*>(base_op);
+  PaintOpWriter helper(memory, size, options);
+  if (!flags_to_serialize)
+    flags_to_serialize = &op->flags;
+  helper.Write(*flags_to_serialize, current_ctm);
+  helper.Write(op->rect);
+  return helper.size();
+}
+
+size_t DrawLineOp::Serialize(const PaintOp* base_op,
+                             void* memory,
+                             size_t size,
+                             const SerializeOptions& options,
+                             const PaintFlags* flags_to_serialize,
+                             const SkM44& current_ctm,
+                             const SkM44& original_ctm) {
+  auto* op = static_cast<const DrawLineOp*>(base_op);
+  PaintOpWriter helper(memory, size, options);
+  if (!flags_to_serialize)
+    flags_to_serialize = &op->flags;
+  helper.Write(*flags_to_serialize, current_ctm);
+  helper.AssertAlignment(alignof(SkScalar));
+  helper.Write(op->x0);
+  helper.Write(op->y0);
+  helper.Write(op->x1);
+  helper.Write(op->y1);
+  return helper.size();
+}
+
+size_t DrawOvalOp::Serialize(const PaintOp* base_op,
+                             void* memory,
+                             size_t size,
+                             const SerializeOptions& options,
+                             const PaintFlags* flags_to_serialize,
+                             const SkM44& current_ctm,
+                             const SkM44& original_ctm) {
+  auto* op = static_cast<const DrawOvalOp*>(base_op);
+  PaintOpWriter helper(memory, size, options);
+  if (!flags_to_serialize)
+    flags_to_serialize = &op->flags;
+  helper.Write(*flags_to_serialize, current_ctm);
+  helper.Write(op->oval);
+  return helper.size();
+}
+
+size_t DrawPathOp::Serialize(const PaintOp* base_op,
+                             void* memory,
+                             size_t size,
+                             const SerializeOptions& options,
+                             const PaintFlags* flags_to_serialize,
+                             const SkM44& current_ctm,
+                             const SkM44& original_ctm) {
+  auto* op = static_cast<const DrawPathOp*>(base_op);
+  PaintOpWriter helper(memory, size, options);
+  if (!flags_to_serialize)
+    flags_to_serialize = &op->flags;
+  helper.Write(*flags_to_serialize, current_ctm);
+  helper.Write(op->path, op->use_cache);
+  helper.Write(op->sk_path_fill_type);
+  return helper.size();
+}
+
+size_t DrawRecordOp::Serialize(const PaintOp* op,
+                               void* memory,
+                               size_t size,
+                               const SerializeOptions& options,
+                               const PaintFlags* flags_to_serialize,
+                               const SkM44& current_ctm,
+                               const SkM44& original_ctm) {
+  // TODO(enne): these must be flattened.  Serializing this will not do
+  // anything.
+  NOTREACHED();
+  return 0u;
+}
+
+size_t DrawRectOp::Serialize(const PaintOp* base_op,
+                             void* memory,
+                             size_t size,
+                             const SerializeOptions& options,
+                             const PaintFlags* flags_to_serialize,
+                             const SkM44& current_ctm,
+                             const SkM44& original_ctm) {
+  auto* op = static_cast<const DrawRectOp*>(base_op);
+  PaintOpWriter helper(memory, size, options);
+  if (!flags_to_serialize)
+    flags_to_serialize = &op->flags;
+  helper.Write(*flags_to_serialize, current_ctm);
+  helper.Write(op->rect);
+  return helper.size();
+}
+
+size_t DrawRRectOp::Serialize(const PaintOp* base_op,
+                              void* memory,
+                              size_t size,
+                              const SerializeOptions& options,
+                              const PaintFlags* flags_to_serialize,
+                              const SkM44& current_ctm,
+                              const SkM44& original_ctm) {
+  auto* op = static_cast<const DrawRRectOp*>(base_op);
+  PaintOpWriter helper(memory, size, options);
+  if (!flags_to_serialize)
+    flags_to_serialize = &op->flags;
+  helper.Write(*flags_to_serialize, current_ctm);
+  helper.Write(op->rrect);
+  return helper.size();
+}
+
+namespace {
+
+template <typename T>
+void SerializeSkottieMap(
+    const base::flat_map<SkottieResourceIdHash, T>& map,
+    PaintOpWriter& helper,
+    const base::RepeatingCallback<void(const T&, PaintOpWriter&)>&
+        value_serializer) {
+  // Write the size of the map first so that we know how many entries to read
+  // from the buffer during deserialization.
+  helper.WriteSize(map.size());
+  for (const auto& [resource_id, val] : map) {
+    helper.WriteSize(resource_id.GetUnsafeValue());
+    value_serializer.Run(val, helper);
+  }
+}
+
+void SerializeSkottieFrameData(const SkM44& current_ctm,
+                               const SkottieFrameData& frame_data,
+                               PaintOpWriter& helper) {
+  // |scale_adjustment| is not ultimately used; Skottie handles image
+  // scale adjustment internally when rastering.
+  SkSize scale_adjustment = SkSize::MakeEmpty();
+  DrawImage draw_image;
+  if (frame_data.image) {
+    draw_image = DrawImage(
+        frame_data.image, /*use_dark_mode=*/false,
+        SkIRect::MakeWH(frame_data.image.width(), frame_data.image.height()),
+        frame_data.quality, current_ctm);
+  }
+  helper.Write(draw_image, &scale_adjustment);
+  helper.Write(frame_data.quality);
+}
+
+}  // namespace
+
+size_t DrawSkottieOp::Serialize(const PaintOp* base_op,
+                                void* memory,
+                                size_t size,
+                                const SerializeOptions& options,
+                                const PaintFlags* flags_to_serialize,
+                                const SkM44& current_ctm,
+                                const SkM44& original_ctm) {
+  auto* op = static_cast<const DrawSkottieOp*>(base_op);
+  PaintOpWriter helper(memory, size, options);
+  helper.Write(op->dst);
+  helper.Write(SkFloatToScalar(op->t));
+  helper.Write(op->skottie);
+
+  SkottieFrameDataMap images_to_serialize = op->images;
+  SkottieTextPropertyValueMap text_map_to_serialize = op->text_map;
+  if (options.skottie_serialization_history) {
+    options.skottie_serialization_history->FilterNewSkottieFrameState(
+        *op->skottie, images_to_serialize, text_map_to_serialize);
+  }
+
+  SerializeSkottieMap(
+      images_to_serialize, helper,
+      base::BindRepeating(&SerializeSkottieFrameData, std::cref(current_ctm)));
+  SerializeSkottieMap(
+      op->color_map, helper,
+      base::BindRepeating([](const SkColor& color, PaintOpWriter& helper) {
+        helper.Write(color);
+      }));
+  SerializeSkottieMap(
+      text_map_to_serialize, helper,
+      base::BindRepeating([](const SkottieTextPropertyValue& text_property_val,
+                             PaintOpWriter& helper) {
+        helper.WriteSize(text_property_val.text().size());
+        // If there is not enough space in the underlying buffer, WriteData()
+        // will mark the |helper| as invalid and the buffer will keep growing
+        // until a max size is reached (currently 64MB which should be ample for
+        // text).
+        helper.WriteData(text_property_val.text().size(),
+                         text_property_val.text().c_str());
+        helper.Write(gfx::RectFToSkRect(text_property_val.box()));
+      }));
+  return helper.size();
+}
+
+size_t DrawTextBlobOp::Serialize(const PaintOp* base_op,
+                                 void* memory,
+                                 size_t size,
+                                 const SerializeOptions& options,
+                                 const PaintFlags* flags_to_serialize,
+                                 const SkM44& current_ctm,
+                                 const SkM44& original_ctm) {
+  auto* op = static_cast<const DrawTextBlobOp*>(base_op);
+  PaintOpWriter helper(memory, size, options);
+  if (!flags_to_serialize)
+    flags_to_serialize = &op->flags;
+  helper.Write(*flags_to_serialize, current_ctm);
+  unsigned int count = op->extra_slugs.size() + 1;
+  helper.Write(count);
+  helper.Write(op->slug);
+  for (const auto& slug : op->extra_slugs) {
+    helper.Write(slug);
+  }
+  return helper.size();
+}
+
+size_t NoopOp::Serialize(const PaintOp* base_op,
+                         void* memory,
+                         size_t size,
+                         const SerializeOptions& options,
+                         const PaintFlags* flags_to_serialize,
+                         const SkM44& current_ctm,
+                         const SkM44& original_ctm) {
+  PaintOpWriter helper(memory, size, options);
+  return helper.size();
+}
+
+size_t RestoreOp::Serialize(const PaintOp* base_op,
+                            void* memory,
+                            size_t size,
+                            const SerializeOptions& options,
+                            const PaintFlags* flags_to_serialize,
+                            const SkM44& current_ctm,
+                            const SkM44& original_ctm) {
+  PaintOpWriter helper(memory, size, options);
+  return helper.size();
+}
+
+size_t RotateOp::Serialize(const PaintOp* base_op,
+                           void* memory,
+                           size_t size,
+                           const SerializeOptions& options,
+                           const PaintFlags* flags_to_serialize,
+                           const SkM44& current_ctm,
+                           const SkM44& original_ctm) {
+  auto* op = static_cast<const RotateOp*>(base_op);
+  PaintOpWriter helper(memory, size, options);
+  helper.Write(op->degrees);
+  return helper.size();
+}
+
+size_t SaveOp::Serialize(const PaintOp* base_op,
+                         void* memory,
+                         size_t size,
+                         const SerializeOptions& options,
+                         const PaintFlags* flags_to_serialize,
+                         const SkM44& current_ctm,
+                         const SkM44& original_ctm) {
+  PaintOpWriter helper(memory, size, options);
+  return helper.size();
+}
+
+size_t SaveLayerOp::Serialize(const PaintOp* base_op,
+                              void* memory,
+                              size_t size,
+                              const SerializeOptions& options,
+                              const PaintFlags* flags_to_serialize,
+                              const SkM44& current_ctm,
+                              const SkM44& original_ctm) {
+  auto* op = static_cast<const SaveLayerOp*>(base_op);
+  PaintOpWriter helper(memory, size, options);
+  if (!flags_to_serialize)
+    flags_to_serialize = &op->flags;
+  helper.Write(*flags_to_serialize, current_ctm);
+  helper.Write(op->bounds);
+  return helper.size();
+}
+
+size_t SaveLayerAlphaOp::Serialize(const PaintOp* base_op,
+                                   void* memory,
+                                   size_t size,
+                                   const SerializeOptions& options,
+                                   const PaintFlags* flags_to_serialize,
+                                   const SkM44& current_ctm,
+                                   const SkM44& original_ctm) {
+  auto* op = static_cast<const SaveLayerAlphaOp*>(base_op);
+  PaintOpWriter helper(memory, size, options);
+  helper.Write(op->bounds);
+  helper.Write(op->alpha);
+  return helper.size();
+}
+
+size_t ScaleOp::Serialize(const PaintOp* base_op,
+                          void* memory,
+                          size_t size,
+                          const SerializeOptions& options,
+                          const PaintFlags* flags_to_serialize,
+                          const SkM44& current_ctm,
+                          const SkM44& original_ctm) {
+  auto* op = static_cast<const ScaleOp*>(base_op);
+  PaintOpWriter helper(memory, size, options);
+  helper.Write(op->sx);
+  helper.Write(op->sy);
+  return helper.size();
+}
+
+size_t SetMatrixOp::Serialize(const PaintOp* base_op,
+                              void* memory,
+                              size_t size,
+                              const SerializeOptions& options,
+                              const PaintFlags* flags_to_serialize,
+                              const SkM44& current_ctm,
+                              const SkM44& original_ctm) {
+  auto* op = static_cast<const SetMatrixOp*>(base_op);
+  PaintOpWriter helper(memory, size, options);
+  // Use original_ctm here because SetMatrixOp replaces current_ctm
+  helper.Write(original_ctm * op->matrix);
+  return helper.size();
+}
+
+size_t SetNodeIdOp::Serialize(const PaintOp* base_op,
+                              void* memory,
+                              size_t size,
+                              const SerializeOptions& options,
+                              const PaintFlags* flags_to_serialize,
+                              const SkM44& current_ctm,
+                              const SkM44& original_ctm) {
+  auto* op = static_cast<const SetNodeIdOp*>(base_op);
+  PaintOpWriter helper(memory, size, options);
+  helper.Write(op->node_id);
+  return helper.size();
+}
+
+size_t TranslateOp::Serialize(const PaintOp* base_op,
+                              void* memory,
+                              size_t size,
+                              const SerializeOptions& options,
+                              const PaintFlags* flags_to_serialize,
+                              const SkM44& current_ctm,
+                              const SkM44& original_ctm) {
+  auto* op = static_cast<const TranslateOp*>(base_op);
+  PaintOpWriter helper(memory, size, options);
+  helper.Write(op->dx);
+  helper.Write(op->dy);
+  return helper.size();
+}
+
+template <typename T>
+void UpdateTypeAndSkip(T* op) {
+  op->type = static_cast<uint8_t>(T::kType);
+  op->skip = PaintOpBuffer::ComputeOpSkip(sizeof(T));
+}
+
+template <typename T>
+class PaintOpDeserializer {
+ public:
+  static_assert(std::is_base_of<PaintOp, T>::value, "T not a PaintOp.");
+
+  explicit PaintOpDeserializer(const volatile void* input,
+                               size_t input_size,
+                               const PaintOp::DeserializeOptions& options,
+                               T* op)
+      : reader_(input, input_size, options), op_(op) {
+    DCHECK(op_);
+  }
+  PaintOpDeserializer(const PaintOpDeserializer&) = delete;
+  PaintOpDeserializer& operator=(const PaintOpDeserializer&) = delete;
+
+  ~PaintOpDeserializer() {
+    DCHECK(!op_)
+        << "FinalizeOp must be called before PaintOpDeserializer is destroyed. "
+           "type="
+        << T::kType;
+  }
+
+  PaintOp* FinalizeOp(bool force_invalid = false) {
+    DCHECK(op_) << "PaintOp has already been finalized. type=" << T::kType;
+
+    if (force_invalid || !reader_.valid() || !op_->IsValid()) {
+      op_->~T();
+      op_ = nullptr;
+      return nullptr;
+    }
+
+    UpdateTypeAndSkip(op_.get());
+    T* op_snapshot = op_;
+    op_ = nullptr;
+    return op_snapshot;
+  }
+
+  PaintOp* InvalidateAndFinalizeOp() {
+    return FinalizeOp(/*force_invalid=*/true);
+  }
+
+  T* operator->() { return op_; }
+
+  template <typename... Args>
+  void Read(Args&&... args) {
+    reader_.Read(std::forward<Args>(args)...);
+  }
+
+  void ReadData(size_t bytes, void* data) { reader_.ReadData(bytes, data); }
+
+  void ReadSize(size_t* size) { reader_.ReadSize(size); }
+
+  void AssertAlignment(size_t alignment) { reader_.AssertAlignment(alignment); }
+
+ private:
+  PaintOpReader reader_;
+  raw_ptr<T> op_;
+};
+
+PaintOp* AnnotateOp::Deserialize(const volatile void* input,
+                                 size_t input_size,
+                                 void* output,
+                                 size_t output_size,
+                                 const DeserializeOptions& options) {
+  DCHECK_GE(output_size, sizeof(AnnotateOp));
+  PaintOpDeserializer<AnnotateOp> deserializer(input, input_size, options,
+                                               new (output) AnnotateOp);
+
+  deserializer.Read(&deserializer->annotation_type);
+  deserializer.Read(&deserializer->rect);
+  deserializer.Read(&deserializer->data);
+  return deserializer.FinalizeOp();
+}
+
+PaintOp* ClipPathOp::Deserialize(const volatile void* input,
+                                 size_t input_size,
+                                 void* output,
+                                 size_t output_size,
+                                 const DeserializeOptions& options) {
+  DCHECK_GE(output_size, sizeof(ClipPathOp));
+  PaintOpDeserializer<ClipPathOp> deserializer(input, input_size, options,
+                                               new (output) ClipPathOp);
+
+  deserializer.Read(&deserializer->path);
+  deserializer.Read(&deserializer->op);
+  deserializer.Read(&deserializer->antialias);
+  return deserializer.FinalizeOp();
+}
+
+PaintOp* ClipRectOp::Deserialize(const volatile void* input,
+                                 size_t input_size,
+                                 void* output,
+                                 size_t output_size,
+                                 const DeserializeOptions& options) {
+  DCHECK_GE(output_size, sizeof(ClipRectOp));
+  PaintOpDeserializer<ClipRectOp> deserializer(input, input_size, options,
+                                               new (output) ClipRectOp);
+  deserializer.Read(&deserializer->rect);
+  deserializer.Read(&deserializer->op);
+  deserializer.Read(&deserializer->antialias);
+  return deserializer.FinalizeOp();
+}
+
+PaintOp* ClipRRectOp::Deserialize(const volatile void* input,
+                                  size_t input_size,
+                                  void* output,
+                                  size_t output_size,
+                                  const DeserializeOptions& options) {
+  DCHECK_GE(output_size, sizeof(ClipRRectOp));
+  PaintOpDeserializer<ClipRRectOp> deserializer(input, input_size, options,
+                                                new (output) ClipRRectOp);
+  deserializer.Read(&deserializer->rrect);
+  deserializer.Read(&deserializer->op);
+  deserializer.Read(&deserializer->antialias);
+  return deserializer.FinalizeOp();
+}
+
+PaintOp* ConcatOp::Deserialize(const volatile void* input,
+                               size_t input_size,
+                               void* output,
+                               size_t output_size,
+                               const DeserializeOptions& options) {
+  DCHECK_GE(output_size, sizeof(ConcatOp));
+  PaintOpDeserializer<ConcatOp> deserializer(input, input_size, options,
+                                             new (output) ConcatOp);
+  deserializer.Read(&deserializer->matrix);
+  return deserializer.FinalizeOp();
+}
+
+PaintOp* CustomDataOp::Deserialize(const volatile void* input,
+                                   size_t input_size,
+                                   void* output,
+                                   size_t output_size,
+                                   const DeserializeOptions& options) {
+  DCHECK_GE(output_size, sizeof(CustomDataOp));
+  PaintOpDeserializer<CustomDataOp> deserializer(input, input_size, options,
+                                                 new (output) CustomDataOp);
+  deserializer.Read(&deserializer->id);
+  return deserializer.FinalizeOp();
+}
+
+PaintOp* DrawColorOp::Deserialize(const volatile void* input,
+                                  size_t input_size,
+                                  void* output,
+                                  size_t output_size,
+                                  const DeserializeOptions& options) {
+  DCHECK_GE(output_size, sizeof(DrawColorOp));
+  PaintOpDeserializer<DrawColorOp> deserializer(input, input_size, options,
+                                                new (output) DrawColorOp);
+  deserializer.Read(&deserializer->color);
+  deserializer.Read(&deserializer->mode);
+  return deserializer.FinalizeOp();
+}
+
+PaintOp* DrawDRRectOp::Deserialize(const volatile void* input,
+                                   size_t input_size,
+                                   void* output,
+                                   size_t output_size,
+                                   const DeserializeOptions& options) {
+  DCHECK_GE(output_size, sizeof(DrawDRRectOp));
+  PaintOpDeserializer<DrawDRRectOp> deserializer(input, input_size, options,
+                                                 new (output) DrawDRRectOp);
+  deserializer.Read(&deserializer->flags);
+  deserializer.Read(&deserializer->outer);
+  deserializer.Read(&deserializer->inner);
+  return deserializer.FinalizeOp();
+}
+
+PaintOp* DrawImageOp::Deserialize(const volatile void* input,
+                                  size_t input_size,
+                                  void* output,
+                                  size_t output_size,
+                                  const DeserializeOptions& options) {
+  DCHECK_GE(output_size, sizeof(DrawImageOp));
+  PaintOpDeserializer<DrawImageOp> deserializer(input, input_size, options,
+                                                new (output) DrawImageOp);
+  deserializer.Read(&deserializer->flags);
+
+  deserializer.Read(&deserializer->image);
+  deserializer.AssertAlignment(alignof(SkScalar));
+  deserializer.Read(&deserializer->scale_adjustment.fWidth);
+  deserializer.Read(&deserializer->scale_adjustment.fHeight);
+
+  deserializer.Read(&deserializer->left);
+  deserializer.Read(&deserializer->top);
+  deserializer.Read(&deserializer->sampling);
+  return deserializer.FinalizeOp();
+}
+
+PaintOp* DrawImageRectOp::Deserialize(const volatile void* input,
+                                      size_t input_size,
+                                      void* output,
+                                      size_t output_size,
+                                      const DeserializeOptions& options) {
+  DCHECK_GE(output_size, sizeof(DrawImageRectOp));
+  PaintOpDeserializer<DrawImageRectOp> deserializer(
+      input, input_size, options, new (output) DrawImageRectOp);
+  deserializer.Read(&deserializer->flags);
+
+  deserializer.Read(&deserializer->image);
+  deserializer.AssertAlignment(alignof(SkScalar));
+  deserializer.Read(&deserializer->scale_adjustment.fWidth);
+  deserializer.Read(&deserializer->scale_adjustment.fHeight);
+
+  deserializer.Read(&deserializer->src);
+  deserializer.Read(&deserializer->dst);
+  deserializer.Read(&deserializer->sampling);
+  deserializer.Read(&deserializer->constraint);
+  return deserializer.FinalizeOp();
+}
+
+PaintOp* DrawIRectOp::Deserialize(const volatile void* input,
+                                  size_t input_size,
+                                  void* output,
+                                  size_t output_size,
+                                  const DeserializeOptions& options) {
+  DCHECK_GE(output_size, sizeof(DrawIRectOp));
+  PaintOpDeserializer<DrawIRectOp> deserializer(input, input_size, options,
+                                                new (output) DrawIRectOp);
+  deserializer.Read(&deserializer->flags);
+  deserializer.Read(&deserializer->rect);
+  return deserializer.FinalizeOp();
+}
+
+PaintOp* DrawLineOp::Deserialize(const volatile void* input,
+                                 size_t input_size,
+                                 void* output,
+                                 size_t output_size,
+                                 const DeserializeOptions& options) {
+  DCHECK_GE(output_size, sizeof(DrawLineOp));
+  PaintOpDeserializer<DrawLineOp> deserializer(input, input_size, options,
+                                               new (output) DrawLineOp);
+  deserializer.Read(&deserializer->flags);
+  deserializer.AssertAlignment(alignof(SkScalar));
+  deserializer.Read(&deserializer->x0);
+  deserializer.Read(&deserializer->y0);
+  deserializer.Read(&deserializer->x1);
+  deserializer.Read(&deserializer->y1);
+  return deserializer.FinalizeOp();
+}
+
+PaintOp* DrawOvalOp::Deserialize(const volatile void* input,
+                                 size_t input_size,
+                                 void* output,
+                                 size_t output_size,
+                                 const DeserializeOptions& options) {
+  DCHECK_GE(output_size, sizeof(DrawOvalOp));
+  PaintOpDeserializer<DrawOvalOp> deserializer(input, input_size, options,
+                                               new (output) DrawOvalOp);
+  deserializer.Read(&deserializer->flags);
+  deserializer.Read(&deserializer->oval);
+  return deserializer.FinalizeOp();
+}
+
+PaintOp* DrawPathOp::Deserialize(const volatile void* input,
+                                 size_t input_size,
+                                 void* output,
+                                 size_t output_size,
+                                 const DeserializeOptions& options) {
+  DCHECK_GE(output_size, sizeof(DrawPathOp));
+  PaintOpDeserializer<DrawPathOp> deserializer(input, input_size, options,
+                                               new (output) DrawPathOp);
+  deserializer.Read(&deserializer->flags);
+  deserializer.Read(&deserializer->path);
+  deserializer.Read(&deserializer->sk_path_fill_type);
+  deserializer->path.setFillType(
+      static_cast<SkPathFillType>(deserializer->sk_path_fill_type));
+  return deserializer.FinalizeOp();
+}
+
+PaintOp* DrawRecordOp::Deserialize(const volatile void* input,
+                                   size_t input_size,
+                                   void* output,
+                                   size_t output_size,
+                                   const DeserializeOptions& options) {
+  // TODO(enne): these must be flattened and not sent directly.
+  // TODO(enne): could also consider caching these service side.
+  return nullptr;
+}
+
+PaintOp* DrawRectOp::Deserialize(const volatile void* input,
+                                 size_t input_size,
+                                 void* output,
+                                 size_t output_size,
+                                 const DeserializeOptions& options) {
+  DCHECK_GE(output_size, sizeof(DrawRectOp));
+  PaintOpDeserializer<DrawRectOp> deserializer(input, input_size, options,
+                                               new (output) DrawRectOp);
+  deserializer.Read(&deserializer->flags);
+  deserializer.Read(&deserializer->rect);
+  return deserializer.FinalizeOp();
+}
+
+PaintOp* DrawRRectOp::Deserialize(const volatile void* input,
+                                  size_t input_size,
+                                  void* output,
+                                  size_t output_size,
+                                  const DeserializeOptions& options) {
+  DCHECK_GE(output_size, sizeof(DrawRRectOp));
+  PaintOpDeserializer<DrawRRectOp> deserializer(input, input_size, options,
+                                                new (output) DrawRRectOp);
+  deserializer.Read(&deserializer->flags);
+  deserializer.Read(&deserializer->rrect);
+  return deserializer.FinalizeOp();
+}
+
+namespace {
+
+// |max_map_size| is purely a safety mechanism to prevent disastrous behavior
+// (trying to allocate an enormous map, looping for long periods of time, etc)
+// in case the serialization buffer is corrupted somehow.
+template <typename T>
+bool DeserializeSkottieMap(
+    base::flat_map<SkottieResourceIdHash, T>& map,
+    absl::optional<size_t> max_map_size,
+    PaintOpDeserializer<DrawSkottieOp>& deserializer,
+    const base::RepeatingCallback<absl::optional<T>(
+        PaintOpDeserializer<DrawSkottieOp>&)>& value_deserializer) {
+  size_t map_size = 0;
+  deserializer.ReadSize(&map_size);
+  if (max_map_size && map_size > *max_map_size)
+    return false;
+
+  for (size_t i = 0; i < map_size; ++i) {
+    size_t resource_id_hash_raw = 0;
+    deserializer.ReadSize(&resource_id_hash_raw);
+    SkottieResourceIdHash resource_id_hash =
+        SkottieResourceIdHash::FromUnsafeValue(resource_id_hash_raw);
+    if (!resource_id_hash)
+      return false;
+
+    absl::optional<T> value = value_deserializer.Run(deserializer);
+    if (!value)
+      return false;
+
+    // Duplicate keys should not happen by design, but defend against it
+    // gracefully in case the underlying buffer is corrupted.
+    bool is_new_entry = map.emplace(resource_id_hash, std::move(*value)).second;
+    if (!is_new_entry)
+      return false;
+  }
+  return true;
+}
+
+absl::optional<SkottieFrameData> DeserializeSkottieFrameData(
+    PaintOpDeserializer<DrawSkottieOp>& deserializer) {
+  SkottieFrameData frame_data;
+  deserializer.Read(&frame_data.image);
+  deserializer.Read(&frame_data.quality);
+  return frame_data;
+}
+
+absl::optional<SkColor> DeserializeSkottieColor(
+    PaintOpDeserializer<DrawSkottieOp>& deserializer) {
+  SkColor color = SK_ColorTRANSPARENT;
+  deserializer.Read(&color);
+  return color;
+}
+
+absl::optional<SkottieTextPropertyValue> DeserializeSkottieTextPropertyValue(
+    PaintOpDeserializer<DrawSkottieOp>& deserializer) {
+  size_t text_size = 0u;
+  deserializer.ReadSize(&text_size);
+  std::string text(text_size, char());
+  deserializer.ReadData(text_size, const_cast<char*>(text.c_str()));
+  SkRect box;
+  deserializer.Read(&box);
+  return SkottieTextPropertyValue(std::move(text), gfx::SkRectToRectF(box));
+}
+
+}  // namespace
+
+PaintOp* DrawSkottieOp::Deserialize(const volatile void* input,
+                                    size_t input_size,
+                                    void* output,
+                                    size_t output_size,
+                                    const DeserializeOptions& options) {
+  DCHECK_GE(output_size, sizeof(DrawSkottieOp));
+  PaintOpDeserializer<DrawSkottieOp> deserializer(input, input_size, options,
+                                                  new (output) DrawSkottieOp);
+  deserializer.Read(&deserializer->dst);
+
+  SkScalar t;
+  deserializer.Read(&t);
+  deserializer->t = SkScalarToFloat(t);
+
+  deserializer.Read(&deserializer->skottie);
+  // The |skottie| object gets used below, so no point in continuing if it's
+  // invalid. That can lead to crashing or unexpected behavior.
+  if (!deserializer->skottie || !deserializer->skottie->is_valid())
+    return deserializer.InvalidateAndFinalizeOp();
+
+  size_t num_assets_in_animation =
+      deserializer->skottie->GetImageAssetMetadata().asset_storage().size();
+  size_t num_text_nodes_in_animation =
+      deserializer->skottie->GetTextNodeNames().size();
+  bool deserialized_all_maps =
+      DeserializeSkottieMap(
+          deserializer->images, /*max_map_size=*/num_assets_in_animation,
+          deserializer, base::BindRepeating(&DeserializeSkottieFrameData)) &&
+      DeserializeSkottieMap(deserializer->color_map,
+                            /*max_map_size=*/absl::nullopt, deserializer,
+                            base::BindRepeating(&DeserializeSkottieColor)) &&
+      DeserializeSkottieMap(
+          deserializer->text_map, /*max_map_size=*/num_text_nodes_in_animation,
+          deserializer,
+          base::BindRepeating(&DeserializeSkottieTextPropertyValue));
+  return deserialized_all_maps ? deserializer.FinalizeOp()
+                               : deserializer.InvalidateAndFinalizeOp();
+}
+
+PaintOp* DrawTextBlobOp::Deserialize(const volatile void* input,
+                                     size_t input_size,
+                                     void* output,
+                                     size_t output_size,
+                                     const DeserializeOptions& options) {
+  DCHECK_GE(output_size, sizeof(DrawTextBlobOp));
+  PaintOpDeserializer<DrawTextBlobOp> deserializer(input, input_size, options,
+                                                   new (output) DrawTextBlobOp);
+  deserializer.Read(&deserializer->flags);
+  unsigned int count = 0;
+  deserializer.Read(&count);
+  deserializer.Read(&deserializer->slug);
+  deserializer->extra_slugs.resize(count - 1);
+  for (auto& slug : deserializer->extra_slugs) {
+    deserializer.Read(&slug);
+  }
+  return deserializer.FinalizeOp();
+}
+
+PaintOp* NoopOp::Deserialize(const volatile void* input,
+                             size_t input_size,
+                             void* output,
+                             size_t output_size,
+                             const DeserializeOptions& options) {
+  DCHECK_GE(output_size, sizeof(NoopOp));
+  PaintOpDeserializer<NoopOp> deserializer(input, input_size, options,
+                                           new (output) NoopOp);
+  return deserializer.FinalizeOp();
+}
+
+PaintOp* RestoreOp::Deserialize(const volatile void* input,
+                                size_t input_size,
+                                void* output,
+                                size_t output_size,
+                                const DeserializeOptions& options) {
+  DCHECK_GE(output_size, sizeof(RestoreOp));
+  PaintOpDeserializer<RestoreOp> deserializer(input, input_size, options,
+                                              new (output) RestoreOp);
+  return deserializer.FinalizeOp();
+}
+
+PaintOp* RotateOp::Deserialize(const volatile void* input,
+                               size_t input_size,
+                               void* output,
+                               size_t output_size,
+                               const DeserializeOptions& options) {
+  DCHECK_GE(output_size, sizeof(RotateOp));
+  PaintOpDeserializer<RotateOp> deserializer(input, input_size, options,
+                                             new (output) RotateOp);
+  deserializer.Read(&deserializer->degrees);
+  return deserializer.FinalizeOp();
+}
+
+PaintOp* SaveOp::Deserialize(const volatile void* input,
+                             size_t input_size,
+                             void* output,
+                             size_t output_size,
+                             const DeserializeOptions& options) {
+  DCHECK_GE(output_size, sizeof(SaveOp));
+  PaintOpDeserializer<SaveOp> deserializer(input, input_size, options,
+                                           new (output) SaveOp);
+  return deserializer.FinalizeOp();
+}
+
+PaintOp* SaveLayerOp::Deserialize(const volatile void* input,
+                                  size_t input_size,
+                                  void* output,
+                                  size_t output_size,
+                                  const DeserializeOptions& options) {
+  DCHECK_GE(output_size, sizeof(SaveLayerOp));
+  PaintOpDeserializer<SaveLayerOp> deserializer(input, input_size, options,
+                                                new (output) SaveLayerOp);
+  deserializer.Read(&deserializer->flags);
+  deserializer.Read(&deserializer->bounds);
+  return deserializer.FinalizeOp();
+}
+
+PaintOp* SaveLayerAlphaOp::Deserialize(const volatile void* input,
+                                       size_t input_size,
+                                       void* output,
+                                       size_t output_size,
+                                       const DeserializeOptions& options) {
+  DCHECK_GE(output_size, sizeof(SaveLayerAlphaOp));
+  PaintOpDeserializer<SaveLayerAlphaOp> deserializer(
+      input, input_size, options, new (output) SaveLayerAlphaOp);
+  deserializer.Read(&deserializer->bounds);
+  deserializer.Read(&deserializer->alpha);
+  return deserializer.FinalizeOp();
+}
+
+PaintOp* ScaleOp::Deserialize(const volatile void* input,
+                              size_t input_size,
+                              void* output,
+                              size_t output_size,
+                              const DeserializeOptions& options) {
+  DCHECK_GE(output_size, sizeof(ScaleOp));
+  PaintOpDeserializer<ScaleOp> deserializer(input, input_size, options,
+                                            new (output) ScaleOp);
+  deserializer.Read(&deserializer->sx);
+  deserializer.Read(&deserializer->sy);
+  return deserializer.FinalizeOp();
+}
+
+PaintOp* SetMatrixOp::Deserialize(const volatile void* input,
+                                  size_t input_size,
+                                  void* output,
+                                  size_t output_size,
+                                  const DeserializeOptions& options) {
+  DCHECK_GE(output_size, sizeof(SetMatrixOp));
+  PaintOpDeserializer<SetMatrixOp> deserializer(input, input_size, options,
+                                                new (output) SetMatrixOp);
+  deserializer.Read(&deserializer->matrix);
+  return deserializer.FinalizeOp();
+}
+
+PaintOp* SetNodeIdOp::Deserialize(const volatile void* input,
+                                  size_t input_size,
+                                  void* output,
+                                  size_t output_size,
+                                  const DeserializeOptions& options) {
+  DCHECK_GE(output_size, sizeof(SetNodeIdOp));
+  PaintOpDeserializer<SetNodeIdOp> deserializer(input, input_size, options,
+                                                new (output) SetNodeIdOp);
+  deserializer.Read(&deserializer->node_id);
+  return deserializer.FinalizeOp();
+}
+
+PaintOp* TranslateOp::Deserialize(const volatile void* input,
+                                  size_t input_size,
+                                  void* output,
+                                  size_t output_size,
+                                  const DeserializeOptions& options) {
+  DCHECK_GE(output_size, sizeof(TranslateOp));
+  PaintOpDeserializer<TranslateOp> deserializer(input, input_size, options,
+                                                new (output) TranslateOp);
+  deserializer.Read(&deserializer->dx);
+  deserializer.Read(&deserializer->dy);
+  return deserializer.FinalizeOp();
+}
+
+void AnnotateOp::Raster(const AnnotateOp* op,
+                        SkCanvas* canvas,
+                        const PlaybackParams& params) {
+  switch (op->annotation_type) {
+    case PaintCanvas::AnnotationType::URL:
+      SkAnnotateRectWithURL(canvas, op->rect, op->data.get());
+      break;
+    case PaintCanvas::AnnotationType::LINK_TO_DESTINATION:
+      SkAnnotateLinkToDestination(canvas, op->rect, op->data.get());
+      break;
+    case PaintCanvas::AnnotationType::NAMED_DESTINATION: {
+      SkPoint point = SkPoint::Make(op->rect.x(), op->rect.y());
+      SkAnnotateNamedDestination(canvas, point, op->data.get());
+      break;
+    }
+  }
+}
+
+void ClipPathOp::Raster(const ClipPathOp* op,
+                        SkCanvas* canvas,
+                        const PlaybackParams& params) {
+  canvas->clipPath(op->path, op->op, op->antialias);
+}
+
+void ClipRectOp::Raster(const ClipRectOp* op,
+                        SkCanvas* canvas,
+                        const PlaybackParams& params) {
+  canvas->clipRect(op->rect, op->op, op->antialias);
+}
+
+void ClipRRectOp::Raster(const ClipRRectOp* op,
+                         SkCanvas* canvas,
+                         const PlaybackParams& params) {
+  canvas->clipRRect(op->rrect, op->op, op->antialias);
+}
+
+void ConcatOp::Raster(const ConcatOp* op,
+                      SkCanvas* canvas,
+                      const PlaybackParams& params) {
+  canvas->concat(op->matrix);
+}
+
+void CustomDataOp::Raster(const CustomDataOp* op,
+                          SkCanvas* canvas,
+                          const PlaybackParams& params) {
+  if (params.custom_callback)
+    params.custom_callback.Run(canvas, op->id);
+}
+
+void DrawColorOp::Raster(const DrawColorOp* op,
+                         SkCanvas* canvas,
+                         const PlaybackParams& params) {
+  canvas->drawColor(op->color, op->mode);
+}
+
+void DrawDRRectOp::RasterWithFlags(const DrawDRRectOp* op,
+                                   const PaintFlags* flags,
+                                   SkCanvas* canvas,
+                                   const PlaybackParams& params) {
+  flags->DrawToSk(canvas, [op](SkCanvas* c, const SkPaint& p) {
+    c->drawDRRect(op->outer, op->inner, p);
+  });
+}
+
+void DrawImageOp::RasterWithFlags(const DrawImageOp* op,
+                                  const PaintFlags* flags,
+                                  SkCanvas* canvas,
+                                  const PlaybackParams& params) {
+  DCHECK(!op->image.IsPaintWorklet());
+  SkPaint paint = flags ? flags->ToSkPaint() : SkPaint();
+
+  if (!params.image_provider) {
+    const bool needs_scale = !IsScaleAdjustmentIdentity(op->scale_adjustment);
+    SkAutoCanvasRestore save_restore(canvas, needs_scale);
+    if (needs_scale) {
+      canvas->scale(1.f / op->scale_adjustment.width(),
+                    1.f / op->scale_adjustment.height());
+    }
+    sk_sp<SkImage> sk_image;
+    if (op->image.IsTextureBacked()) {
+      sk_image = op->image.GetAcceleratedSkImage();
+      DCHECK(sk_image || !canvas->recordingContext());
+    }
+    if (!sk_image)
+      sk_image = op->image.GetSwSkImage();
+
+    canvas->drawImage(sk_image.get(), op->left, op->top, op->sampling, &paint);
+    return;
+  }
+
+  // Dark mode is applied only for OOP raster during serialization.
+  DrawImage draw_image(
+      op->image, false, SkIRect::MakeWH(op->image.width(), op->image.height()),
+      sampling_to_quality(op->sampling), canvas->getLocalToDevice());
+  auto scoped_result = params.image_provider->GetRasterContent(draw_image);
+  if (!scoped_result)
+    return;
+
+  const auto& decoded_image = scoped_result.decoded_image();
+  DCHECK(decoded_image.image());
+
+  DCHECK_EQ(0, static_cast<int>(decoded_image.src_rect_offset().width()));
+  DCHECK_EQ(0, static_cast<int>(decoded_image.src_rect_offset().height()));
+  SkSize scale_adjustment = SkSize::Make(
+      op->scale_adjustment.width() * decoded_image.scale_adjustment().width(),
+      op->scale_adjustment.height() *
+          decoded_image.scale_adjustment().height());
+  const bool needs_scale = !IsScaleAdjustmentIdentity(scale_adjustment);
+  SkAutoCanvasRestore save_restore(canvas, needs_scale);
+  if (needs_scale) {
+    canvas->scale(1.f / scale_adjustment.width(),
+                  1.f / scale_adjustment.height());
+  }
+  canvas->drawImage(decoded_image.image().get(), op->left, op->top,
+                    PaintFlags::FilterQualityToSkSamplingOptions(
+                        decoded_image.filter_quality()),
+                    &paint);
+}
+
+void DrawImageRectOp::RasterWithFlags(const DrawImageRectOp* op,
+                                      const PaintFlags* flags,
+                                      SkCanvas* canvas,
+                                      const PlaybackParams& params) {
+  // TODO(crbug.com/931704): make sure to support the case where paint worklet
+  // generated images are used in other raster work such as canvas2d.
+  if (op->image.IsPaintWorklet()) {
+    // When rasterizing on the main thread (e.g. paint invalidation checking,
+    // see https://crbug.com/990382), an image provider may not be available, so
+    // we should draw nothing.
+    if (!params.image_provider)
+      return;
+    ImageProvider::ScopedResult result =
+        params.image_provider->GetRasterContent(DrawImage(op->image));
+
+    // Check that we are not using loopers with paint worklets, since converting
+    // PaintFlags to SkPaint drops loopers.
+    DCHECK(!flags->getLooper());
+    SkPaint paint = flags ? flags->ToSkPaint() : SkPaint();
+
+    DCHECK(IsScaleAdjustmentIdentity(op->scale_adjustment));
+    SkAutoCanvasRestore save_restore(canvas, true);
+    canvas->concat(SkMatrix::RectToRect(op->src, op->dst));
+    canvas->clipRect(op->src);
+    canvas->saveLayer(&op->src, &paint);
+    // Compositor thread animations can cause PaintWorklet jobs to be dispatched
+    // to the worklet thread even after main has torn down the worklet (e.g.
+    // because a navigation is happening). In that case the PaintWorklet jobs
+    // will fail and there will be no result to raster here. This state is
+    // transient as the next main frame commit will remove the PaintWorklets.
+    if (result && result.paint_record())
+      result.paint_record()->Playback(canvas, params);
+    return;
+  }
+
+  if (!params.image_provider) {
+    SkRect adjusted_src = AdjustSrcRectForScale(op->src, op->scale_adjustment);
+    flags->DrawToSk(canvas, [op, adjusted_src](SkCanvas* c, const SkPaint& p) {
+      sk_sp<SkImage> sk_image;
+      if (op->image.IsTextureBacked()) {
+        sk_image = op->image.GetAcceleratedSkImage();
+        DCHECK(sk_image || !c->recordingContext());
+      }
+      if (!sk_image)
+        sk_image = op->image.GetSwSkImage();
+      DrawImageRect(c, sk_image.get(), adjusted_src, op->dst, op->sampling, &p,
+                    op->constraint);
+    });
+    return;
+  }
+
+  SkM44 matrix = canvas->getLocalToDevice() *
+                 SkM44(SkMatrix::RectToRect(op->src, op->dst));
+
+  SkIRect int_src_rect;
+  op->src.roundOut(&int_src_rect);
+
+  // Dark mode is applied only for OOP raster during serialization.
+  DrawImage draw_image(op->image, false, int_src_rect,
+                       sampling_to_quality(op->sampling), matrix);
+  auto scoped_result = params.image_provider->GetRasterContent(draw_image);
+  if (!scoped_result)
+    return;
+
+  const auto& decoded_image = scoped_result.decoded_image();
+  DCHECK(decoded_image.image());
+
+  SkSize scale_adjustment = SkSize::Make(
+      op->scale_adjustment.width() * decoded_image.scale_adjustment().width(),
+      op->scale_adjustment.height() *
+          decoded_image.scale_adjustment().height());
+  SkRect adjusted_src =
+      op->src.makeOffset(decoded_image.src_rect_offset().width(),
+                         decoded_image.src_rect_offset().height());
+  adjusted_src = AdjustSrcRectForScale(adjusted_src, scale_adjustment);
+  flags->DrawToSk(canvas, [op, &decoded_image, adjusted_src](SkCanvas* c,
+                                                             const SkPaint& p) {
+    SkSamplingOptions options = PaintFlags::FilterQualityToSkSamplingOptions(
+        decoded_image.filter_quality());
+    DrawImageRect(c, decoded_image.image().get(), adjusted_src, op->dst,
+                  options, &p, op->constraint);
+  });
+}
+
+void DrawIRectOp::RasterWithFlags(const DrawIRectOp* op,
+                                  const PaintFlags* flags,
+                                  SkCanvas* canvas,
+                                  const PlaybackParams& params) {
+  flags->DrawToSk(canvas, [op](SkCanvas* c, const SkPaint& p) {
+    c->drawIRect(op->rect, p);
+  });
+}
+
+void DrawLineOp::RasterWithFlags(const DrawLineOp* op,
+                                 const PaintFlags* flags,
+                                 SkCanvas* canvas,
+                                 const PlaybackParams& params) {
+  SkPaint paint = flags->ToSkPaint();
+  flags->DrawToSk(canvas, [op](SkCanvas* c, const SkPaint& p) {
+    c->drawLine(op->x0, op->y0, op->x1, op->y1, p);
+  });
+}
+
+void DrawOvalOp::RasterWithFlags(const DrawOvalOp* op,
+                                 const PaintFlags* flags,
+                                 SkCanvas* canvas,
+                                 const PlaybackParams& params) {
+  flags->DrawToSk(canvas, [op](SkCanvas* c, const SkPaint& p) {
+    c->drawOval(op->oval, p);
+  });
+}
+
+void DrawPathOp::RasterWithFlags(const DrawPathOp* op,
+                                 const PaintFlags* flags,
+                                 SkCanvas* canvas,
+                                 const PlaybackParams& params) {
+  flags->DrawToSk(canvas, [op](SkCanvas* c, const SkPaint& p) {
+    c->drawPath(op->path, p);
+  });
+}
+
+void DrawRecordOp::Raster(const DrawRecordOp* op,
+                          SkCanvas* canvas,
+                          const PlaybackParams& params) {
+  // Don't use drawPicture here, as it adds an implicit clip.
+  // TODO(enne): Temporary CHECK debugging for http://crbug.com/823835
+  CHECK(op->record);
+  op->record->Playback(canvas, params);
+}
+
+void DrawRectOp::RasterWithFlags(const DrawRectOp* op,
+                                 const PaintFlags* flags,
+                                 SkCanvas* canvas,
+                                 const PlaybackParams& params) {
+  flags->DrawToSk(canvas, [op](SkCanvas* c, const SkPaint& p) {
+    c->drawRect(op->rect, p);
+  });
+}
+
+void DrawRRectOp::RasterWithFlags(const DrawRRectOp* op,
+                                  const PaintFlags* flags,
+                                  SkCanvas* canvas,
+                                  const PlaybackParams& params) {
+  flags->DrawToSk(canvas, [op](SkCanvas* c, const SkPaint& p) {
+    c->drawRRect(op->rrect, p);
+  });
+}
+
+void DrawSkottieOp::Raster(const DrawSkottieOp* op,
+                           SkCanvas* canvas,
+                           const PlaybackParams& params) {
+  // Binding unretained references in the callback is safe because Draw()'s API
+  // guarantees that the callback is invoked synchronously.
+  op->skottie->Draw(
+      canvas, op->t, op->dst,
+      base::BindRepeating(&DrawSkottieOp::GetImageAssetForRaster,
+                          base::Unretained(op), canvas, std::cref(params)),
+      op->color_map, op->text_map);
+}
+
+SkottieWrapper::FrameDataFetchResult DrawSkottieOp::GetImageAssetForRaster(
+    SkCanvas* canvas,
+    const PlaybackParams& params,
+    SkottieResourceIdHash asset_id,
+    float t_frame,
+    sk_sp<SkImage>& sk_image,
+    SkSamplingOptions& sampling_out) const {
+  auto images_iter = images.find(asset_id);
+  if (images_iter == images.end())
+    return SkottieWrapper::FrameDataFetchResult::NO_UPDATE;
+
+  const SkottieFrameData& frame_data = images_iter->second;
+  if (!frame_data.image) {
+    sk_image = nullptr;
+  } else if (params.image_provider) {
+    // There is no use case for applying dark mode filters to skottie images
+    // currently.
+    DrawImage draw_image(
+        frame_data.image, /*use_dark_mode=*/false,
+        SkIRect::MakeWH(frame_data.image.width(), frame_data.image.height()),
+        frame_data.quality, canvas->getLocalToDevice());
+    auto scoped_result = params.image_provider->GetRasterContent(draw_image);
+    if (scoped_result) {
+      sk_image = scoped_result.decoded_image().image();
+      DCHECK(sk_image);
+    }
+  } else {
+    if (frame_data.image.IsTextureBacked()) {
+      sk_image = frame_data.image.GetAcceleratedSkImage();
+      DCHECK(sk_image || !canvas->recordingContext());
+    }
+    if (!sk_image)
+      sk_image = frame_data.image.GetSwSkImage();
+  }
+  sampling_out =
+      PaintFlags::FilterQualityToSkSamplingOptions(frame_data.quality);
+  return SkottieWrapper::FrameDataFetchResult::NEW_DATA_AVAILABLE;
+}
+
+void DrawTextBlobOp::RasterWithFlags(const DrawTextBlobOp* op,
+                                     const PaintFlags* flags,
+                                     SkCanvas* canvas,
+                                     const PlaybackParams& params) {
+  if (op->node_id)
+    SkPDF::SetNodeId(canvas, op->node_id);
+
+  // The PaintOpBuffer could be rasterized with different global matrix. It is
+  // used for over scall on Android. So we cannot reuse slugs, they have to be
+  // recreated.
+  if (params.is_analyzing) {
+    const_cast<DrawTextBlobOp*>(op)->slug.reset();
+    const_cast<DrawTextBlobOp*>(op)->extra_slugs.clear();
+  }
+
+  // flags may contain SkDrawLooper for shadow effect, so we need to convert
+  // SkTextBlob to slug for each run.
+  size_t i = 0;
+  flags->DrawToSk(canvas, [op, &params, &i](SkCanvas* c, const SkPaint& p) {
+    if (op->blob) {
+      c->drawTextBlob(op->blob.get(), op->x, op->y, p);
+      if (params.is_analyzing) {
+        auto s = GrSlug::ConvertBlob(c, *op->blob, {op->x, op->y}, p);
+        if (i == 0) {
+          const_cast<DrawTextBlobOp*>(op)->slug = std::move(s);
+        } else {
+          const_cast<DrawTextBlobOp*>(op)->extra_slugs.push_back(std::move(s));
+        }
+      }
+    } else if (i < 1 + op->extra_slugs.size()) {
+      DCHECK(!params.is_analyzing);
+      const auto& draw_slug = i == 0 ? op->slug : op->extra_slugs[i - 1];
+      if (draw_slug)
+        draw_slug->draw(c);
+    }
+    ++i;
+  });
+
+  if (op->node_id)
+    SkPDF::SetNodeId(canvas, 0);
+}
+
+void RestoreOp::Raster(const RestoreOp* op,
+                       SkCanvas* canvas,
+                       const PlaybackParams& params) {
+  canvas->restore();
+}
+
+void RotateOp::Raster(const RotateOp* op,
+                      SkCanvas* canvas,
+                      const PlaybackParams& params) {
+  canvas->rotate(op->degrees);
+}
+
+void SaveOp::Raster(const SaveOp* op,
+                    SkCanvas* canvas,
+                    const PlaybackParams& params) {
+  canvas->save();
+}
+
+void SaveLayerOp::RasterWithFlags(const SaveLayerOp* op,
+                                  const PaintFlags* flags,
+                                  SkCanvas* canvas,
+                                  const PlaybackParams& params) {
+  // See PaintOp::kUnsetRect
+  SkPaint paint = flags->ToSkPaint();
+  bool unset = op->bounds.left() == SK_ScalarInfinity;
+  canvas->saveLayer(unset ? nullptr : &op->bounds, &paint);
+}
+
+void SaveLayerAlphaOp::Raster(const SaveLayerAlphaOp* op,
+                              SkCanvas* canvas,
+                              const PlaybackParams& params) {
+  // See PaintOp::kUnsetRect
+  bool unset = op->bounds.left() == SK_ScalarInfinity;
+  absl::optional<SkPaint> paint;
+  if (op->alpha != 1.0f) {
+    paint.emplace();
+    paint->setAlpha(op->alpha * 255.0f);
+  }
+  SkCanvas::SaveLayerRec rec(unset ? nullptr : &op->bounds,
+                             base::OptionalToPtr(paint));
+  if (params.save_layer_alpha_should_preserve_lcd_text.has_value() &&
+      *params.save_layer_alpha_should_preserve_lcd_text) {
+    rec.fSaveLayerFlags = SkCanvas::kPreserveLCDText_SaveLayerFlag |
+                          SkCanvas::kInitWithPrevious_SaveLayerFlag;
+  }
+  canvas->saveLayer(rec);
+}
+
+void ScaleOp::Raster(const ScaleOp* op,
+                     SkCanvas* canvas,
+                     const PlaybackParams& params) {
+  canvas->scale(op->sx, op->sy);
+}
+
+void SetMatrixOp::Raster(const SetMatrixOp* op,
+                         SkCanvas* canvas,
+                         const PlaybackParams& params) {
+  canvas->setMatrix(params.original_ctm * op->matrix);
+}
+
+void SetNodeIdOp::Raster(const SetNodeIdOp* op,
+                         SkCanvas* canvas,
+                         const PlaybackParams& params) {
+  SkPDF::SetNodeId(canvas, op->node_id);
+}
+
+void TranslateOp::Raster(const TranslateOp* op,
+                         SkCanvas* canvas,
+                         const PlaybackParams& params) {
+  canvas->translate(op->dx, op->dy);
+}
+
+// static
+bool PaintOp::AreSkPointsEqual(const SkPoint& left, const SkPoint& right) {
+  if (!AreEqualEvenIfNaN(left.fX, right.fX))
+    return false;
+  if (!AreEqualEvenIfNaN(left.fY, right.fY))
+    return false;
+  return true;
+}
+
+// static
+bool PaintOp::AreSkPoint3sEqual(const SkPoint3& left, const SkPoint3& right) {
+  if (!AreEqualEvenIfNaN(left.fX, right.fX))
+    return false;
+  if (!AreEqualEvenIfNaN(left.fY, right.fY))
+    return false;
+  if (!AreEqualEvenIfNaN(left.fZ, right.fZ))
+    return false;
+  return true;
+}
+
+// static
+bool PaintOp::AreSkRectsEqual(const SkRect& left, const SkRect& right) {
+  if (!AreEqualEvenIfNaN(left.fLeft, right.fLeft))
+    return false;
+  if (!AreEqualEvenIfNaN(left.fTop, right.fTop))
+    return false;
+  if (!AreEqualEvenIfNaN(left.fRight, right.fRight))
+    return false;
+  if (!AreEqualEvenIfNaN(left.fBottom, right.fBottom))
+    return false;
+  return true;
+}
+
+// static
+bool PaintOp::AreSkRRectsEqual(const SkRRect& left, const SkRRect& right) {
+  char left_buffer[SkRRect::kSizeInMemory];
+  left.writeToMemory(left_buffer);
+  char right_buffer[SkRRect::kSizeInMemory];
+  right.writeToMemory(right_buffer);
+  return !memcmp(left_buffer, right_buffer, SkRRect::kSizeInMemory);
+}
+
+// static
+bool PaintOp::AreSkMatricesEqual(const SkMatrix& left, const SkMatrix& right) {
+  for (int i = 0; i < 9; ++i) {
+    if (!AreEqualEvenIfNaN(left.get(i), right.get(i)))
+      return false;
+  }
+
+  // If a serialized matrix says it is identity, then the original must have
+  // those values, as the serialization process clobbers the matrix values.
+  if (left.isIdentity()) {
+    if (SkMatrix::I() != left)
+      return false;
+    if (SkMatrix::I() != right)
+      return false;
+  }
+
+  if (left.getType() != right.getType())
+    return false;
+
+  return true;
+}
+
+// static
+bool PaintOp::AreSkM44sEqual(const SkM44& left, const SkM44& right) {
+  for (int r = 0; r < 4; ++r) {
+    for (int c = 0; c < 4; ++c) {
+      if (!AreEqualEvenIfNaN(left.rc(r, c), right.rc(r, c)))
+        return false;
+    }
+  }
+
+  return true;
+}
+
+// static
+bool PaintOp::AreSkFlattenablesEqual(SkFlattenable* left,
+                                     SkFlattenable* right) {
+  if (!right || !left)
+    return !right && !left;
+
+  sk_sp<SkData> left_data = left->serialize();
+  sk_sp<SkData> right_data = right->serialize();
+  if (left_data->size() != right_data->size())
+    return false;
+  if (!left_data->equals(right_data.get()))
+    return false;
+  return true;
+}
+
+bool AnnotateOp::AreEqual(const PaintOp* base_left, const PaintOp* base_right) {
+  auto* left = static_cast<const AnnotateOp*>(base_left);
+  auto* right = static_cast<const AnnotateOp*>(base_right);
+  DCHECK(left->IsValid());
+  DCHECK(right->IsValid());
+  if (left->annotation_type != right->annotation_type)
+    return false;
+  if (!AreSkRectsEqual(left->rect, right->rect))
+    return false;
+  if (!left->data != !right->data)
+    return false;
+  if (left->data) {
+    if (left->data->size() != right->data->size())
+      return false;
+    if (0 !=
+        memcmp(left->data->data(), right->data->data(), right->data->size()))
+      return false;
+  }
+  return true;
+}
+
+bool ClipPathOp::AreEqual(const PaintOp* base_left, const PaintOp* base_right) {
+  auto* left = static_cast<const ClipPathOp*>(base_left);
+  auto* right = static_cast<const ClipPathOp*>(base_right);
+  DCHECK(left->IsValid());
+  DCHECK(right->IsValid());
+  if (left->path != right->path)
+    return false;
+  if (left->op != right->op)
+    return false;
+  if (left->antialias != right->antialias)
+    return false;
+  return true;
+}
+
+bool ClipRectOp::AreEqual(const PaintOp* base_left, const PaintOp* base_right) {
+  auto* left = static_cast<const ClipRectOp*>(base_left);
+  auto* right = static_cast<const ClipRectOp*>(base_right);
+  DCHECK(left->IsValid());
+  DCHECK(right->IsValid());
+  if (!AreSkRectsEqual(left->rect, right->rect))
+    return false;
+  if (left->op != right->op)
+    return false;
+  if (left->antialias != right->antialias)
+    return false;
+  return true;
+}
+
+bool ClipRRectOp::AreEqual(const PaintOp* base_left,
+                           const PaintOp* base_right) {
+  auto* left = static_cast<const ClipRRectOp*>(base_left);
+  auto* right = static_cast<const ClipRRectOp*>(base_right);
+  DCHECK(left->IsValid());
+  DCHECK(right->IsValid());
+  if (!AreSkRRectsEqual(left->rrect, right->rrect))
+    return false;
+  if (left->op != right->op)
+    return false;
+  if (left->antialias != right->antialias)
+    return false;
+  return true;
+}
+
+bool ConcatOp::AreEqual(const PaintOp* base_left, const PaintOp* base_right) {
+  auto* left = static_cast<const ConcatOp*>(base_left);
+  auto* right = static_cast<const ConcatOp*>(base_right);
+  DCHECK(left->IsValid());
+  DCHECK(right->IsValid());
+  return AreSkM44sEqual(left->matrix, right->matrix);
+}
+
+bool CustomDataOp::AreEqual(const PaintOp* base_left,
+                            const PaintOp* base_right) {
+  auto* left = static_cast<const CustomDataOp*>(base_left);
+  auto* right = static_cast<const CustomDataOp*>(base_right);
+  DCHECK(left->IsValid());
+  DCHECK(right->IsValid());
+  return left->id == right->id;
+}
+
+bool DrawColorOp::AreEqual(const PaintOp* base_left,
+                           const PaintOp* base_right) {
+  auto* left = static_cast<const DrawColorOp*>(base_left);
+  auto* right = static_cast<const DrawColorOp*>(base_right);
+  DCHECK(left->IsValid());
+  DCHECK(right->IsValid());
+  return left->color == right->color;
+}
+
+bool DrawDRRectOp::AreEqual(const PaintOp* base_left,
+                            const PaintOp* base_right) {
+  auto* left = static_cast<const DrawDRRectOp*>(base_left);
+  auto* right = static_cast<const DrawDRRectOp*>(base_right);
+  DCHECK(left->IsValid());
+  DCHECK(right->IsValid());
+  if (left->flags != right->flags)
+    return false;
+  if (!AreSkRRectsEqual(left->outer, right->outer))
+    return false;
+  if (!AreSkRRectsEqual(left->inner, right->inner))
+    return false;
+  return true;
+}
+
+bool DrawImageOp::AreEqual(const PaintOp* base_left,
+                           const PaintOp* base_right) {
+  auto* left = static_cast<const DrawImageOp*>(base_left);
+  auto* right = static_cast<const DrawImageOp*>(base_right);
+  DCHECK(left->IsValid());
+  DCHECK(right->IsValid());
+  if (left->flags != right->flags)
+    return false;
+  // TODO(enne): Test PaintImage equality once implemented
+  if (!AreEqualEvenIfNaN(left->left, right->left))
+    return false;
+  if (!AreEqualEvenIfNaN(left->top, right->top))
+    return false;
+
+  // scale_adjustment intentionally omitted because it is added during
+  // serialization based on raster scale.
+  return true;
+}
+
+bool DrawImageRectOp::AreEqual(const PaintOp* base_left,
+                               const PaintOp* base_right) {
+  auto* left = static_cast<const DrawImageRectOp*>(base_left);
+  auto* right = static_cast<const DrawImageRectOp*>(base_right);
+  DCHECK(left->IsValid());
+  DCHECK(right->IsValid());
+  if (left->flags != right->flags)
+    return false;
+  // TODO(enne): Test PaintImage equality once implemented
+  if (!AreSkRectsEqual(left->src, right->src))
+    return false;
+  if (!AreSkRectsEqual(left->dst, right->dst))
+    return false;
+
+  // scale_adjustment intentionally omitted because it is added during
+  // serialization based on raster scale.
+  return true;
+}
+
+bool DrawIRectOp::AreEqual(const PaintOp* base_left,
+                           const PaintOp* base_right) {
+  auto* left = static_cast<const DrawIRectOp*>(base_left);
+  auto* right = static_cast<const DrawIRectOp*>(base_right);
+  DCHECK(left->IsValid());
+  DCHECK(right->IsValid());
+  if (left->flags != right->flags)
+    return false;
+  if (left->rect != right->rect)
+    return false;
+  return true;
+}
+
+bool DrawLineOp::AreEqual(const PaintOp* base_left, const PaintOp* base_right) {
+  auto* left = static_cast<const DrawLineOp*>(base_left);
+  auto* right = static_cast<const DrawLineOp*>(base_right);
+  DCHECK(left->IsValid());
+  DCHECK(right->IsValid());
+  if (left->flags != right->flags)
+    return false;
+  if (!AreEqualEvenIfNaN(left->x0, right->x0))
+    return false;
+  if (!AreEqualEvenIfNaN(left->y0, right->y0))
+    return false;
+  if (!AreEqualEvenIfNaN(left->x1, right->x1))
+    return false;
+  if (!AreEqualEvenIfNaN(left->y1, right->y1))
+    return false;
+  return true;
+}
+
+bool DrawOvalOp::AreEqual(const PaintOp* base_left, const PaintOp* base_right) {
+  auto* left = static_cast<const DrawOvalOp*>(base_left);
+  auto* right = static_cast<const DrawOvalOp*>(base_right);
+  DCHECK(left->IsValid());
+  DCHECK(right->IsValid());
+  if (left->flags != right->flags)
+    return false;
+  if (!AreSkRectsEqual(left->oval, right->oval))
+    return false;
+  return true;
+}
+
+bool DrawPathOp::AreEqual(const PaintOp* base_left, const PaintOp* base_right) {
+  auto* left = static_cast<const DrawPathOp*>(base_left);
+  auto* right = static_cast<const DrawPathOp*>(base_right);
+  DCHECK(left->IsValid());
+  DCHECK(right->IsValid());
+  if (left->flags != right->flags)
+    return false;
+  if (left->path != right->path)
+    return false;
+  return true;
+}
+
+bool DrawRecordOp::AreEqual(const PaintOp* base_left,
+                            const PaintOp* base_right) {
+  auto* left = static_cast<const DrawRecordOp*>(base_left);
+  auto* right = static_cast<const DrawRecordOp*>(base_right);
+  DCHECK(left->IsValid());
+  DCHECK(right->IsValid());
+  if (!left->record != !right->record)
+    return false;
+  if (*left->record != *right->record)
+    return false;
+  return true;
+}
+
+bool DrawRectOp::AreEqual(const PaintOp* base_left, const PaintOp* base_right) {
+  auto* left = static_cast<const DrawRectOp*>(base_left);
+  auto* right = static_cast<const DrawRectOp*>(base_right);
+  DCHECK(left->IsValid());
+  DCHECK(right->IsValid());
+  if (left->flags != right->flags)
+    return false;
+  if (!AreSkRectsEqual(left->rect, right->rect))
+    return false;
+  return true;
+}
+
+bool DrawRRectOp::AreEqual(const PaintOp* base_left,
+                           const PaintOp* base_right) {
+  auto* left = static_cast<const DrawRRectOp*>(base_left);
+  auto* right = static_cast<const DrawRRectOp*>(base_right);
+  DCHECK(left->IsValid());
+  DCHECK(right->IsValid());
+  if (left->flags != right->flags)
+    return false;
+  if (!AreSkRRectsEqual(left->rrect, right->rrect))
+    return false;
+  return true;
+}
+
+bool DrawSkottieOp::AreEqual(const PaintOp* base_left,
+                             const PaintOp* base_right) {
+  auto* left = static_cast<const DrawSkottieOp*>(base_left);
+  auto* right = static_cast<const DrawSkottieOp*>(base_right);
+  DCHECK(left->IsValid());
+  DCHECK(right->IsValid());
+  // TODO(malaykeshav): Verify the skottie objects of each PaintOb are equal
+  // based on the serialized bytes.
+  if (left->t != right->t)
+    return false;
+  if (!AreSkRectsEqual(left->dst, right->dst))
+    return false;
+  if (left->images.size() != right->images.size())
+    return false;
+
+  auto left_iter = left->images.begin();
+  auto right_iter = right->images.begin();
+  for (; left_iter != left->images.end(); ++left_iter, ++right_iter) {
+    if (left_iter->first != right_iter->first ||
+        // PaintImage's comparison operator compares the underlying SkImage's
+        // pointer address. This does not necessarily hold in cases where the
+        // image's content may be the same, but it got realloacted to a
+        // different spot somewhere in memory via the transfer cache. The next
+        // best thing is to just compare the dimensions of the PaintImage.
+        left_iter->second.image.width() != right_iter->second.image.width() ||
+        left_iter->second.image.height() != right_iter->second.image.height() ||
+        left_iter->second.quality != right_iter->second.quality) {
+      return false;
+    }
+  }
+
+  if (left->color_map != right->color_map)
+    return false;
+
+  if (left->text_map != right->text_map)
+    return false;
+
+  return true;
+}
+
+bool DrawTextBlobOp::AreEqual(const PaintOp* base_left,
+                              const PaintOp* base_right) {
+  auto* left = static_cast<const DrawTextBlobOp*>(base_left);
+  auto* right = static_cast<const DrawTextBlobOp*>(base_right);
+  DCHECK(left->IsValid());
+  DCHECK(right->IsValid());
+  if (left->flags != right->flags)
+    return false;
+  if (!AreEqualEvenIfNaN(left->x, right->x))
+    return false;
+  if (!AreEqualEvenIfNaN(left->y, right->y))
+    return false;
+  if (left->node_id != right->node_id)
+    return false;
+  return GrSlugAreEqual(left->slug, right->slug);
+}
+
+bool NoopOp::AreEqual(const PaintOp* base_left, const PaintOp* base_right) {
+  return true;
+}
+
+bool RestoreOp::AreEqual(const PaintOp* base_left, const PaintOp* base_right) {
+  return true;
+}
+
+bool RotateOp::AreEqual(const PaintOp* base_left, const PaintOp* base_right) {
+  auto* left = static_cast<const RotateOp*>(base_left);
+  auto* right = static_cast<const RotateOp*>(base_right);
+  DCHECK(left->IsValid());
+  DCHECK(right->IsValid());
+  if (!AreEqualEvenIfNaN(left->degrees, right->degrees))
+    return false;
+  return true;
+}
+
+bool SaveOp::AreEqual(const PaintOp* base_left, const PaintOp* base_right) {
+  return true;
+}
+
+bool SaveLayerOp::AreEqual(const PaintOp* base_left,
+                           const PaintOp* base_right) {
+  auto* left = static_cast<const SaveLayerOp*>(base_left);
+  auto* right = static_cast<const SaveLayerOp*>(base_right);
+  DCHECK(left->IsValid());
+  DCHECK(right->IsValid());
+  if (left->flags != right->flags)
+    return false;
+  if (!AreSkRectsEqual(left->bounds, right->bounds))
+    return false;
+  return true;
+}
+
+bool SaveLayerAlphaOp::AreEqual(const PaintOp* base_left,
+                                const PaintOp* base_right) {
+  auto* left = static_cast<const SaveLayerAlphaOp*>(base_left);
+  auto* right = static_cast<const SaveLayerAlphaOp*>(base_right);
+  DCHECK(left->IsValid());
+  DCHECK(right->IsValid());
+  if (!AreSkRectsEqual(left->bounds, right->bounds))
+    return false;
+  if (left->alpha != right->alpha)
+    return false;
+  return true;
+}
+
+bool ScaleOp::AreEqual(const PaintOp* base_left, const PaintOp* base_right) {
+  auto* left = static_cast<const ScaleOp*>(base_left);
+  auto* right = static_cast<const ScaleOp*>(base_right);
+  DCHECK(left->IsValid());
+  DCHECK(right->IsValid());
+  if (!AreEqualEvenIfNaN(left->sx, right->sx))
+    return false;
+  if (!AreEqualEvenIfNaN(left->sy, right->sy))
+    return false;
+  return true;
+}
+
+bool SetMatrixOp::AreEqual(const PaintOp* base_left,
+                           const PaintOp* base_right) {
+  auto* left = static_cast<const SetMatrixOp*>(base_left);
+  auto* right = static_cast<const SetMatrixOp*>(base_right);
+  DCHECK(left->IsValid());
+  DCHECK(right->IsValid());
+  if (!AreSkM44sEqual(left->matrix, right->matrix))
+    return false;
+  return true;
+}
+
+bool SetNodeIdOp::AreEqual(const PaintOp* base_left,
+                           const PaintOp* base_right) {
+  auto* left = static_cast<const SetNodeIdOp*>(base_left);
+  auto* right = static_cast<const SetNodeIdOp*>(base_right);
+
+  DCHECK(left->IsValid());
+  DCHECK(right->IsValid());
+  return left->node_id == right->node_id;
+}
+
+bool TranslateOp::AreEqual(const PaintOp* base_left,
+                           const PaintOp* base_right) {
+  auto* left = static_cast<const TranslateOp*>(base_left);
+  auto* right = static_cast<const TranslateOp*>(base_right);
+  DCHECK(left->IsValid());
+  DCHECK(right->IsValid());
+  if (!AreEqualEvenIfNaN(left->dx, right->dx))
+    return false;
+  if (!AreEqualEvenIfNaN(left->dy, right->dy))
+    return false;
+  return true;
+}
+
+bool PaintOp::IsDrawOp() const {
+  return g_is_draw_op[type];
+}
+
+bool PaintOp::IsPaintOpWithFlags() const {
+  return g_has_paint_flags[type];
+}
+
+bool PaintOp::operator==(const PaintOp& other) const {
+  if (GetType() != other.GetType())
+    return false;
+  return g_equals_operator[type](this, &other);
+}
+
+// static
+bool PaintOp::TypeHasFlags(PaintOpType type) {
+  return g_has_paint_flags[static_cast<uint8_t>(type)];
+}
+
+void PaintOp::Raster(SkCanvas* canvas, const PlaybackParams& params) const {
+  g_raster_functions[type](this, canvas, params);
+}
+
+size_t PaintOp::Serialize(void* memory,
+                          size_t size,
+                          const SerializeOptions& options,
+                          const PaintFlags* flags_to_serialize,
+                          const SkM44& current_ctm,
+                          const SkM44& original_ctm) const {
+  // Need at least enough room for a skip/type header.
+  if (size < 4)
+    return 0u;
+
+  DCHECK_EQ(0u,
+            reinterpret_cast<uintptr_t>(memory) % PaintOpBuffer::kPaintOpAlign);
+
+  size_t written = g_serialize_functions[type](this, memory, size, options,
+                                               flags_to_serialize, current_ctm,
+                                               original_ctm);
+  DCHECK_LE(written, size);
+  if (written < 4)
+    return 0u;
+
+  size_t aligned_written = ((written + PaintOpBuffer::kPaintOpAlign - 1) &
+                            ~(PaintOpBuffer::kPaintOpAlign - 1));
+  if (aligned_written >= kMaxSkip)
+    return 0u;
+  if (aligned_written > size)
+    return 0u;
+
+  // Update skip and type now that the size is known.
+  uint32_t bytes_to_skip = static_cast<uint32_t>(aligned_written);
+  static_cast<uint32_t*>(memory)[0] = type | bytes_to_skip << 8;
+  return bytes_to_skip;
+}
+
+PaintOp* PaintOp::Deserialize(const volatile void* input,
+                              size_t input_size,
+                              void* output,
+                              size_t output_size,
+                              size_t* read_bytes,
+                              const DeserializeOptions& options) {
+  DCHECK_GE(output_size, sizeof(LargestPaintOp));
+
+  uint8_t type;
+  uint32_t skip;
+  if (!PaintOpReader::ReadAndValidateOpHeader(input, input_size, &type, &skip))
+    return nullptr;
+
+  *read_bytes = skip;
+  return g_deserialize_functions[type](input, skip, output, output_size,
+                                       options);
+}
+
+PaintOp* PaintOp::DeserializeIntoPaintOpBuffer(
+    const volatile void* input,
+    size_t input_size,
+    PaintOpBuffer* buffer,
+    size_t* read_bytes,
+    const DeserializeOptions& options) {
+  uint8_t type;
+  uint32_t skip;
+  if (!PaintOpReader::ReadAndValidateOpHeader(input, input_size, &type,
+                                              &skip)) {
+    return nullptr;
+  }
+
+  size_t op_skip = PaintOpBuffer::ComputeOpSkip(g_type_to_size[type]);
+  if (auto* op = g_deserialize_functions[type](
+          input, skip, buffer->AllocatePaintOp(op_skip), op_skip, options)) {
+    g_analyze_op_functions[type](buffer, op);
+    *read_bytes = skip;
+    return op;
+  }
+
+  // The last allocated op has already been destroyed if it failed to
+  // deserialize. Update the buffer's op tracking to exclude it to avoid
+  // access during cleanup at destruction.
+  buffer->used_ -= op_skip;
+  buffer->op_count_--;
+  return nullptr;
+}
+
+// static
+bool PaintOp::GetBounds(const PaintOp& op, SkRect* rect) {
+  DCHECK(op.IsDrawOp());
+
+  switch (op.GetType()) {
+    case PaintOpType::DrawColor:
+      return false;
+    case PaintOpType::DrawDRRect: {
+      const auto& rect_op = static_cast<const DrawDRRectOp&>(op);
+      *rect = rect_op.outer.getBounds();
+      rect->sort();
+      return true;
+    }
+    case PaintOpType::DrawImage: {
+      const auto& image_op = static_cast<const DrawImageOp&>(op);
+      *rect = SkRect::MakeXYWH(image_op.left, image_op.top,
+                               image_op.image.width(), image_op.image.height());
+      rect->sort();
+      return true;
+    }
+    case PaintOpType::DrawImageRect: {
+      const auto& image_rect_op = static_cast<const DrawImageRectOp&>(op);
+      *rect = image_rect_op.dst;
+      rect->sort();
+      return true;
+    }
+    case PaintOpType::DrawIRect: {
+      const auto& rect_op = static_cast<const DrawIRectOp&>(op);
+      *rect = SkRect::Make(rect_op.rect);
+      rect->sort();
+      return true;
+    }
+    case PaintOpType::DrawLine: {
+      const auto& line_op = static_cast<const DrawLineOp&>(op);
+      rect->setLTRB(line_op.x0, line_op.y0, line_op.x1, line_op.y1);
+      rect->sort();
+      return true;
+    }
+    case PaintOpType::DrawOval: {
+      const auto& oval_op = static_cast<const DrawOvalOp&>(op);
+      *rect = oval_op.oval;
+      rect->sort();
+      return true;
+    }
+    case PaintOpType::DrawPath: {
+      const auto& path_op = static_cast<const DrawPathOp&>(op);
+      *rect = path_op.path.getBounds();
+      rect->sort();
+      return true;
+    }
+    case PaintOpType::DrawRect: {
+      const auto& rect_op = static_cast<const DrawRectOp&>(op);
+      *rect = rect_op.rect;
+      rect->sort();
+      return true;
+    }
+    case PaintOpType::DrawRRect: {
+      const auto& rect_op = static_cast<const DrawRRectOp&>(op);
+      *rect = rect_op.rrect.rect();
+      rect->sort();
+      return true;
+    }
+    case PaintOpType::DrawRecord:
+      return false;
+    case PaintOpType::DrawSkottie: {
+      const auto& skottie_op = static_cast<const DrawSkottieOp&>(op);
+      *rect = skottie_op.dst;
+      rect->sort();
+      return true;
+    }
+    case PaintOpType::DrawTextBlob: {
+      const auto& text_op = static_cast<const DrawTextBlobOp&>(op);
+      *rect = text_op.blob->bounds().makeOffset(text_op.x, text_op.y);
+      rect->sort();
+      return true;
+    }
+    default:
+      NOTREACHED();
+  }
+  return false;
+}
+
+// static
+gfx::Rect PaintOp::ComputePaintRect(const PaintOp& op,
+                                    const SkRect& clip_rect,
+                                    const SkMatrix& ctm) {
+  gfx::Rect transformed_rect;
+  SkRect op_rect;
+  if (!op.IsDrawOp() || !PaintOp::GetBounds(op, &op_rect)) {
+    // If we can't provide a conservative bounding rect for the op, assume it
+    // covers the complete current clip.
+    // TODO(khushalsagar): See if we can do something better for non-draw ops.
+    transformed_rect = gfx::ToEnclosingRect(gfx::SkRectToRectF(clip_rect));
+  } else {
+    const PaintFlags* flags =
+        op.IsPaintOpWithFlags()
+            ? &(static_cast<const PaintOpWithFlags&>(op).flags)
+            : nullptr;
+    SkRect paint_rect = MapRect(ctm, op_rect);
+    if (flags) {
+      SkPaint paint = flags->ToSkPaint();
+      paint_rect = paint.canComputeFastBounds() && paint_rect.isFinite()
+                       ? paint.computeFastBounds(paint_rect, &paint_rect)
+                       : clip_rect;
+    }
+    // Clamp the image rect by the current clip rect.
+    if (!paint_rect.intersect(clip_rect))
+      return gfx::Rect();
+
+    transformed_rect = gfx::ToEnclosingRect(gfx::SkRectToRectF(paint_rect));
+  }
+
+  // During raster, we use the device clip bounds on the canvas, which outsets
+  // the actual clip by 1 due to the possibility of antialiasing. Account for
+  // this here by outsetting the image rect by 1. Note that this only affects
+  // queries into the rtree, which will now return images that only touch the
+  // bounds of the query rect.
+  //
+  // Note that it's not sufficient for us to inset the device clip bounds at
+  // raster time, since we might be sending a larger-than-one-item display
+  // item to skia, which means that skia will internally determine whether to
+  // raster the picture (using device clip bounds that are outset).
+  transformed_rect.Inset(-1);
+  return transformed_rect;
+}
+
+// static
+bool PaintOp::QuickRejectDraw(const PaintOp& op, const SkCanvas* canvas) {
+  if (!op.IsDrawOp())
+    return false;
+
+  SkRect rect;
+  if (!PaintOp::GetBounds(op, &rect))
+    return false;
+  if (!rect.isFinite())
+    return true;
+
+  if (op.IsPaintOpWithFlags()) {
+    SkPaint paint = static_cast<const PaintOpWithFlags&>(op).flags.ToSkPaint();
+    if (!paint.canComputeFastBounds())
+      return false;
+    // canvas->quickReject tried to be very fast, and sometimes give a false
+    // but conservative result. That's why we need the additional check for
+    // |local_op_rect| because it could quickReject could return false even if
+    // |local_op_rect| is empty.
+    const SkRect& clip_rect = SkRect::Make(canvas->getDeviceClipBounds());
+    const SkMatrix& ctm = canvas->getTotalMatrix();
+    gfx::Rect local_op_rect = PaintOp::ComputePaintRect(op, clip_rect, ctm);
+    if (local_op_rect.IsEmpty())
+      return true;
+    paint.computeFastBounds(rect, &rect);
+  }
+
+  return canvas->quickReject(rect);
+}
+
+// static
+bool PaintOp::OpHasDiscardableImages(const PaintOp& op) {
+  if (op.IsPaintOpWithFlags() && static_cast<const PaintOpWithFlags&>(op)
+                                     .HasDiscardableImagesFromFlags()) {
+    return true;
+  }
+
+  if (op.GetType() == PaintOpType::DrawImage &&
+      static_cast<const DrawImageOp&>(op).HasDiscardableImages()) {
+    return true;
+  } else if (op.GetType() == PaintOpType::DrawImageRect &&
+             static_cast<const DrawImageRectOp&>(op).HasDiscardableImages()) {
+    return true;
+  } else if (op.GetType() == PaintOpType::DrawRecord &&
+             static_cast<const DrawRecordOp&>(op).HasDiscardableImages()) {
+    return true;
+  } else if (op.GetType() == PaintOpType::DrawSkottie &&
+             static_cast<const DrawSkottieOp&>(op).HasDiscardableImages()) {
+    return true;
+  }
+
+  return false;
+}
+
+void PaintOp::DestroyThis() {
+  auto func = g_destructor_functions[type];
+  if (func)
+    func(this);
+}
+
+bool PaintOpWithFlags::HasDiscardableImagesFromFlags() const {
+  return flags.HasDiscardableImages();
+}
+
+void PaintOpWithFlags::RasterWithFlags(SkCanvas* canvas,
+                                       const PaintFlags* raster_flags,
+                                       const PlaybackParams& params) const {
+  g_raster_with_flags_functions[type](this, raster_flags, canvas, params);
+}
+
+int ClipPathOp::CountSlowPaths() const {
+  return antialias && !path.isConvex() ? 1 : 0;
+}
+
+int DrawLineOp::CountSlowPaths() const {
+  if (const SkPathEffect* effect = flags.getPathEffect().get()) {
+    SkPathEffect::DashInfo info;
+    SkPathEffect::DashType dashType = effect->asADash(&info);
+    if (flags.getStrokeCap() != PaintFlags::kRound_Cap &&
+        dashType == SkPathEffect::kDash_DashType && info.fCount == 2) {
+      // The PaintFlags will count this as 1, so uncount that here as
+      // this kind of line is special cased and not slow.
+      return -1;
+    }
+  }
+  return 0;
+}
+
+int DrawPathOp::CountSlowPaths() const {
+  // This logic is copied from SkPathCounter instead of attempting to expose
+  // that from Skia.
+  if (!flags.isAntiAlias() || path.isConvex())
+    return 0;
+
+  PaintFlags::Style paintStyle = flags.getStyle();
+  const SkRect& pathBounds = path.getBounds();
+  if (paintStyle == PaintFlags::kStroke_Style && flags.getStrokeWidth() == 0) {
+    // AA hairline concave path is not slow.
+    return 0;
+  } else if (paintStyle == PaintFlags::kFill_Style &&
+             pathBounds.width() < 64.f && pathBounds.height() < 64.f &&
+             !path.isVolatile()) {
+    // AADF eligible concave path is not slow.
+    return 0;
+  } else {
+    return 1;
+  }
+}
+
+int DrawRecordOp::CountSlowPaths() const {
+  return record->num_slow_paths_up_to_min_for_MSAA();
+}
+
+bool DrawRecordOp::HasNonAAPaint() const {
+  return record->HasNonAAPaint();
+}
+
+bool DrawRecordOp::HasDrawTextOps() const {
+  return record->has_draw_text_ops();
+}
+
+bool DrawRecordOp::HasSaveLayerOps() const {
+  return record->has_save_layer_ops();
+}
+
+bool DrawRecordOp::HasSaveLayerAlphaOps() const {
+  return record->has_save_layer_alpha_ops();
+}
+
+bool DrawRecordOp::HasEffectsPreventingLCDTextForSaveLayerAlpha() const {
+  return record->has_effects_preventing_lcd_text_for_save_layer_alpha();
+}
+
+AnnotateOp::AnnotateOp() : PaintOp(kType) {}
+
+AnnotateOp::AnnotateOp(PaintCanvas::AnnotationType annotation_type,
+                       const SkRect& rect,
+                       sk_sp<SkData> data)
+    : PaintOp(kType),
+      annotation_type(annotation_type),
+      rect(rect),
+      data(std::move(data)) {}
+
+AnnotateOp::~AnnotateOp() = default;
+AnnotateOp::AnnotateOp(const AnnotateOp&) = default;
+AnnotateOp& AnnotateOp::operator=(const AnnotateOp&) = default;
+
+DrawImageOp::DrawImageOp() : PaintOpWithFlags(kType) {}
+
+DrawImageOp::DrawImageOp(const PaintImage& image, SkScalar left, SkScalar top)
+    : PaintOpWithFlags(kType, PaintFlags()),
+      image(image),
+      left(left),
+      top(top) {}
+
+DrawImageOp::DrawImageOp(const PaintImage& image,
+                         SkScalar left,
+                         SkScalar top,
+                         const SkSamplingOptions& sampling,
+                         const PaintFlags* flags)
+    : PaintOpWithFlags(kType, flags ? *flags : PaintFlags()),
+      image(image),
+      left(left),
+      top(top),
+      sampling(sampling) {}
+
+bool DrawImageOp::HasDiscardableImages() const {
+  return image && !image.IsTextureBacked();
+}
+
+DrawImageOp::~DrawImageOp() = default;
+
+DrawImageRectOp::DrawImageRectOp() : PaintOpWithFlags(kType) {}
+
+DrawImageRectOp::DrawImageRectOp(const PaintImage& image,
+                                 const SkRect& src,
+                                 const SkRect& dst,
+                                 SkCanvas::SrcRectConstraint constraint)
+    : PaintOpWithFlags(kType, PaintFlags()),
+      image(image),
+      src(src),
+      dst(dst),
+      constraint(constraint) {}
+
+DrawImageRectOp::DrawImageRectOp(const PaintImage& image,
+                                 const SkRect& src,
+                                 const SkRect& dst,
+                                 const SkSamplingOptions& sampling,
+                                 const PaintFlags* flags,
+                                 SkCanvas::SrcRectConstraint constraint)
+    : PaintOpWithFlags(kType, flags ? *flags : PaintFlags()),
+      image(image),
+      src(src),
+      dst(dst),
+      sampling(sampling),
+      constraint(constraint) {}
+
+bool DrawImageRectOp::HasDiscardableImages() const {
+  return image && !image.IsTextureBacked();
+}
+
+DrawImageRectOp::~DrawImageRectOp() = default;
+
+DrawRecordOp::DrawRecordOp(sk_sp<const PaintRecord> record)
+    : PaintOp(kType), record(std::move(record)) {}
+
+DrawRecordOp::~DrawRecordOp() = default;
+DrawRecordOp::DrawRecordOp(const DrawRecordOp&) = default;
+DrawRecordOp& DrawRecordOp::operator=(const DrawRecordOp&) = default;
+
+size_t DrawRecordOp::AdditionalBytesUsed() const {
+  return record->bytes_used();
+}
+
+size_t DrawRecordOp::AdditionalOpCount() const {
+  return record->total_op_count();
+}
+
+DrawSkottieOp::DrawSkottieOp(scoped_refptr<SkottieWrapper> skottie,
+                             SkRect dst,
+                             float t,
+                             SkottieFrameDataMap images,
+                             const SkottieColorMap& color_map,
+                             SkottieTextPropertyValueMap text_map)
+    : PaintOp(kType),
+      skottie(std::move(skottie)),
+      dst(dst),
+      t(t),
+      images(std::move(images)),
+      color_map(color_map),
+      text_map(std::move(text_map)) {}
+
+DrawSkottieOp::DrawSkottieOp() : PaintOp(kType) {}
+
+DrawSkottieOp::~DrawSkottieOp() = default;
+DrawSkottieOp::DrawSkottieOp(const DrawSkottieOp&) = default;
+DrawSkottieOp& DrawSkottieOp::operator=(const DrawSkottieOp&) = default;
+
+bool DrawSkottieOp::HasDiscardableImages() const {
+  return !images.empty();
+}
+
+bool DrawRecordOp::HasDiscardableImages() const {
+  return record->HasDiscardableImages();
+}
+
+DrawTextBlobOp::DrawTextBlobOp() : PaintOpWithFlags(kType) {}
+
+DrawTextBlobOp::DrawTextBlobOp(sk_sp<SkTextBlob> blob,
+                               SkScalar x,
+                               SkScalar y,
+                               const PaintFlags& flags)
+    : PaintOpWithFlags(kType, flags), blob(std::move(blob)), x(x), y(y) {}
+
+DrawTextBlobOp::DrawTextBlobOp(sk_sp<SkTextBlob> blob,
+                               SkScalar x,
+                               SkScalar y,
+                               NodeId node_id,
+                               const PaintFlags& flags)
+    : PaintOpWithFlags(kType, flags),
+      blob(std::move(blob)),
+      x(x),
+      y(y),
+      node_id(node_id) {}
+
+DrawTextBlobOp::~DrawTextBlobOp() = default;
+DrawTextBlobOp::DrawTextBlobOp(const DrawTextBlobOp&) = default;
+DrawTextBlobOp& DrawTextBlobOp::operator=(const DrawTextBlobOp&) = default;
+
+}  // namespace cc
diff --git a/cc/paint/paint_op.h b/cc/paint/paint_op.h
new file mode 100644
index 0000000..f83828a
--- /dev/null
+++ b/cc/paint/paint_op.h
@@ -0,0 +1,1053 @@
+// 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.
+
+#ifndef CC_PAINT_PAINT_OP_H_
+#define CC_PAINT_PAINT_OP_H_
+
+#include <stdint.h>
+
+#include <limits>
+#include <memory>
+#include <string>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/check_op.h"
+#include "base/containers/stack_container.h"
+#include "base/debug/alias.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/notreached.h"
+#include "cc/base/math_util.h"
+#include "cc/paint/node_id.h"
+#include "cc/paint/paint_canvas.h"
+#include "cc/paint/paint_export.h"
+#include "cc/paint/paint_flags.h"
+#include "cc/paint/paint_record.h"
+#include "cc/paint/skottie_color_map.h"
+#include "cc/paint/skottie_frame_data.h"
+#include "cc/paint/skottie_resource_metadata.h"
+#include "cc/paint/skottie_text_property_value.h"
+#include "cc/paint/skottie_wrapper.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "third_party/abseil-cpp/absl/types/variant.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "third_party/skia/include/core/SkPath.h"
+#include "third_party/skia/include/core/SkRRect.h"
+#include "third_party/skia/include/core/SkRect.h"
+#include "third_party/skia/include/core/SkRefCnt.h"
+#include "third_party/skia/include/core/SkScalar.h"
+#include "ui/gfx/geometry/rect.h"
+
+class SkImage;
+class SkTextBlob;
+
+namespace cc {
+
+class CC_PAINT_EXPORT ThreadsafePath : public SkPath {
+ public:
+  explicit ThreadsafePath(const SkPath& path) : SkPath(path) {
+    updateBoundsCache();
+  }
+  ThreadsafePath() { updateBoundsCache(); }
+};
+
+// See PaintOp::Serialize/Deserialize for comments.  Derived Serialize types
+// don't write the 4 byte type/skip header because they don't know how much
+// data they will need to write.  PaintOp::Serialize itself must update it.
+#define HAS_SERIALIZATION_FUNCTIONS()                                        \
+  static size_t Serialize(                                                   \
+      const PaintOp* op, void* memory, size_t size,                          \
+      const SerializeOptions& options, const PaintFlags* flags_to_serialize, \
+      const SkM44& current_ctm, const SkM44& original_ctm);                  \
+  static PaintOp* Deserialize(const volatile void* input, size_t input_size, \
+                              void* output, size_t output_size,              \
+                              const DeserializeOptions& options)
+
+enum class PaintOpType : uint8_t {
+  Annotate,
+  ClipPath,
+  ClipRect,
+  ClipRRect,
+  Concat,
+  CustomData,
+  DrawColor,
+  DrawDRRect,
+  DrawImage,
+  DrawImageRect,
+  DrawIRect,
+  DrawLine,
+  DrawOval,
+  DrawPath,
+  DrawRecord,
+  DrawRect,
+  DrawRRect,
+  DrawSkottie,
+  DrawTextBlob,
+  Noop,
+  Restore,
+  Rotate,
+  Save,
+  SaveLayer,
+  SaveLayerAlpha,
+  Scale,
+  SetMatrix,
+  SetNodeId,
+  Translate,
+  LastPaintOpType = Translate,
+};
+
+CC_PAINT_EXPORT std::string PaintOpTypeToString(PaintOpType type);
+CC_PAINT_EXPORT std::ostream& operator<<(std::ostream&, PaintOpType);
+
+class CC_PAINT_EXPORT PaintOp {
+ public:
+  uint32_t type : 8;
+  uint32_t skip : 24;
+
+  using SerializeOptions = PaintOpBuffer::SerializeOptions;
+  using DeserializeOptions = PaintOpBuffer::DeserializeOptions;
+
+  explicit PaintOp(PaintOpType type) : type(static_cast<uint8_t>(type)) {}
+
+  PaintOpType GetType() const { return static_cast<PaintOpType>(type); }
+
+  // Subclasses should provide a static Raster() method which is called from
+  // here. The Raster method should take a const PaintOp* parameter. It is
+  // static with a pointer to the base type so that we can use it as a function
+  // pointer.
+  void Raster(SkCanvas* canvas, const PlaybackParams& params) const;
+  bool IsDrawOp() const;
+  bool IsPaintOpWithFlags() const;
+
+  bool operator==(const PaintOp& other) const;
+  bool operator!=(const PaintOp& other) const { return !(*this == other); }
+
+  // Indicates how PaintImages are serialized.
+  enum class SerializedImageType : uint8_t {
+    kNoImage,
+    kImageData,
+    kTransferCacheEntry,
+    kMailbox,
+    kLastType = kMailbox
+  };
+
+  // Subclasses should provide a static Serialize() method called from here.
+  // If the op can be serialized to |memory| in no more than |size| bytes,
+  // then return the number of bytes written.  If it won't fit, return 0.
+  // If |flags_to_serialize| is non-null, it overrides any flags within the op.
+  // |current_ctm| is the transform that will affect the op when rasterized.
+  // |original_ctm| is the transform that SetMatrixOps must be made relative to.
+  size_t Serialize(void* memory,
+                   size_t size,
+                   const SerializeOptions& options,
+                   const PaintFlags* flags_to_serialize,
+                   const SkM44& current_ctm,
+                   const SkM44& original_ctm) const;
+
+  // Deserializes a PaintOp of this type from a given buffer |input| of
+  // at most |input_size| bytes.  Returns null on any errors.
+  // The PaintOp is deserialized into the |output| buffer and returned
+  // if valid.  nullptr is returned if the deserialization fails.
+  // |output_size| must be at least LargestPaintOp + serialized->skip,
+  // to fit all ops.  The caller is responsible for destroying these ops.
+  // After reading, it returns the number of bytes read in |read_bytes|.
+  static PaintOp* Deserialize(const volatile void* input,
+                              size_t input_size,
+                              void* output,
+                              size_t output_size,
+                              size_t* read_bytes,
+                              const DeserializeOptions& options);
+  // Similar to the above, but deserializes into |buffer|.
+  static PaintOp* DeserializeIntoPaintOpBuffer(
+      const volatile void* input,
+      size_t input_size,
+      PaintOpBuffer* buffer,
+      size_t* read_bytes,
+      const DeserializeOptions& options);
+
+  // For draw ops, returns true if a conservative bounding rect can be provided
+  // for the op.
+  static bool GetBounds(const PaintOp& op, SkRect* rect);
+
+  // Returns the minimum conservative bounding rect that |op| draws to on a
+  // canvas. |clip_rect| and |ctm| are the current clip rect and transform on
+  // this canvas.
+  static gfx::Rect ComputePaintRect(const PaintOp& op,
+                                    const SkRect& clip_rect,
+                                    const SkMatrix& ctm);
+
+  // Returns true if the op lies outside the current clip and should be skipped.
+  // Should only be used with draw ops.
+  static bool QuickRejectDraw(const PaintOp& op, const SkCanvas* canvas);
+
+  // Returns true if executing this op will require decoding of any lazy
+  // generated images.
+  static bool OpHasDiscardableImages(const PaintOp& op);
+
+  // Returns true if the given op type has PaintFlags.
+  static bool TypeHasFlags(PaintOpType type);
+
+  int CountSlowPaths() const { return 0; }
+  int CountSlowPathsFromFlags() const { return 0; }
+
+  bool HasNonAAPaint() const { return false; }
+  bool HasDrawTextOps() const { return false; }
+  bool HasSaveLayerOps() const { return false; }
+  bool HasSaveLayerAlphaOps() const { return false; }
+  // Returns true if effects are present that would break LCD text or be broken
+  // by the flags for SaveLayerAlpha to preserving LCD text.
+  bool HasEffectsPreventingLCDTextForSaveLayerAlpha() const { return false; }
+
+  bool HasDiscardableImages() const { return false; }
+  bool HasDiscardableImagesFromFlags() const { return false; }
+
+  // Returns the number of bytes used by this op in referenced sub records
+  // and display lists.  This doesn't count other objects like paths or blobs.
+  size_t AdditionalBytesUsed() const { return 0; }
+
+  // Returns the number of ops in referenced sub records and display lists.
+  size_t AdditionalOpCount() const { return 0; }
+
+  // Run the destructor for the derived op type.  Ops are usually contained in
+  // memory buffers and so don't have their destructors run automatically.
+  void DestroyThis();
+
+  // DrawColor is more restrictive on the blend modes that can be used.
+  static bool IsValidDrawColorSkBlendMode(SkBlendMode mode) {
+    return static_cast<uint32_t>(mode) <=
+           static_cast<uint32_t>(SkBlendMode::kLastCoeffMode);
+  }
+
+  // PaintFlags can have more complex blend modes than DrawColor.
+  static bool IsValidPaintFlagsSkBlendMode(SkBlendMode mode) {
+    return static_cast<uint32_t>(mode) <=
+           static_cast<uint32_t>(SkBlendMode::kLastMode);
+  }
+
+  static bool IsValidSkClipOp(SkClipOp op) {
+    return static_cast<uint32_t>(op) <=
+           static_cast<uint32_t>(SkClipOp::kMax_EnumValue);
+  }
+
+  static bool IsValidPath(const SkPath& path) { return path.isValid(); }
+
+  static bool IsUnsetRect(const SkRect& rect) {
+    return rect.fLeft == SK_ScalarInfinity;
+  }
+
+  static bool IsValidOrUnsetRect(const SkRect& rect) {
+    return IsUnsetRect(rect) || rect.isFinite();
+  }
+
+  // PaintOp supports having nans, but some tests want to make sure
+  // that operator== is true on two objects.  These helpers compare
+  // various types in a way where nan == nan is true.
+  static bool AreEqualEvenIfNaN(float left, float right) {
+    if (std::isnan(left) && std::isnan(right))
+      return true;
+    return left == right;
+  }
+  static bool AreSkPointsEqual(const SkPoint& left, const SkPoint& right);
+  static bool AreSkPoint3sEqual(const SkPoint3& left, const SkPoint3& right);
+  static bool AreSkRectsEqual(const SkRect& left, const SkRect& right);
+  static bool AreSkRRectsEqual(const SkRRect& left, const SkRRect& right);
+  static bool AreSkMatricesEqual(const SkMatrix& left, const SkMatrix& right);
+  static bool AreSkM44sEqual(const SkM44& left, const SkM44& right);
+  static bool AreSkFlattenablesEqual(SkFlattenable* left, SkFlattenable* right);
+
+  static constexpr bool kIsDrawOp = false;
+  static constexpr bool kHasPaintFlags = false;
+  // Since skip and type fit in a uint32_t, this is the max size of skip.
+  static constexpr size_t kMaxSkip = static_cast<size_t>(1 << 24);
+  static const SkRect kUnsetRect;
+
+ protected:
+  PaintOp(const PaintOp&) = default;
+  PaintOp& operator=(const PaintOp&) = default;
+};
+
+class CC_PAINT_EXPORT PaintOpWithFlags : public PaintOp {
+ public:
+  static constexpr bool kHasPaintFlags = true;
+  PaintOpWithFlags(PaintOpType type, const PaintFlags& flags)
+      : PaintOp(type), flags(flags) {}
+
+  int CountSlowPathsFromFlags() const { return flags.getPathEffect() ? 1 : 0; }
+  bool HasNonAAPaint() const { return !flags.isAntiAlias(); }
+  bool HasDiscardableImagesFromFlags() const;
+
+  void RasterWithFlags(SkCanvas* canvas,
+                       const PaintFlags* flags,
+                       const PlaybackParams& params) const;
+
+  // Subclasses should provide a static RasterWithFlags() method which is called
+  // from the Raster() method. The RasterWithFlags() should use the SkPaint
+  // passed to it, instead of the |flags| member directly, as some callers may
+  // provide a modified PaintFlags. The RasterWithFlags() method is static with
+  // a const PaintOpWithFlags* parameter so that it can be used as a function
+  // pointer.
+  PaintFlags flags;
+
+ protected:
+  PaintOpWithFlags(const PaintOpWithFlags&) = default;
+  PaintOpWithFlags& operator=(const PaintOpWithFlags&) = default;
+
+  explicit PaintOpWithFlags(PaintOpType type) : PaintOp(type) {}
+};
+
+class CC_PAINT_EXPORT AnnotateOp final : public PaintOp {
+ public:
+  static constexpr PaintOpType kType = PaintOpType::Annotate;
+  AnnotateOp(PaintCanvas::AnnotationType annotation_type,
+             const SkRect& rect,
+             sk_sp<SkData> data);
+  ~AnnotateOp();
+  AnnotateOp(const AnnotateOp&);
+  AnnotateOp& operator=(const AnnotateOp&);
+  static void Raster(const AnnotateOp* op,
+                     SkCanvas* canvas,
+                     const PlaybackParams& params);
+  bool IsValid() const { return rect.isFinite(); }
+  static bool AreEqual(const PaintOp* left, const PaintOp* right);
+  HAS_SERIALIZATION_FUNCTIONS();
+
+  PaintCanvas::AnnotationType annotation_type;
+  SkRect rect;
+  sk_sp<SkData> data;
+
+ private:
+  AnnotateOp();
+};
+
+class CC_PAINT_EXPORT ClipPathOp final : public PaintOp {
+ public:
+  static constexpr PaintOpType kType = PaintOpType::ClipPath;
+  ClipPathOp(SkPath path,
+             SkClipOp op,
+             bool antialias,
+             UsePaintCache use_paint_cache = UsePaintCache::kEnabled)
+      : PaintOp(kType),
+        path(path),
+        op(op),
+        antialias(antialias),
+        use_cache(use_paint_cache) {}
+  ClipPathOp(const ClipPathOp&) = default;
+  ClipPathOp& operator=(const ClipPathOp&) = default;
+  static void Raster(const ClipPathOp* op,
+                     SkCanvas* canvas,
+                     const PlaybackParams& params);
+  bool IsValid() const { return IsValidSkClipOp(op) && IsValidPath(path); }
+  static bool AreEqual(const PaintOp* left, const PaintOp* right);
+  int CountSlowPaths() const;
+  bool HasNonAAPaint() const { return !antialias; }
+  HAS_SERIALIZATION_FUNCTIONS();
+
+  ThreadsafePath path;
+  SkClipOp op;
+  bool antialias;
+  UsePaintCache use_cache;
+
+ private:
+  ClipPathOp() : PaintOp(kType) {}
+};
+
+class CC_PAINT_EXPORT ClipRectOp final : public PaintOp {
+ public:
+  static constexpr PaintOpType kType = PaintOpType::ClipRect;
+  ClipRectOp(const SkRect& rect, SkClipOp op, bool antialias)
+      : PaintOp(kType), rect(rect), op(op), antialias(antialias) {}
+  ClipRectOp(const ClipRectOp&) = default;
+  ClipRectOp& operator=(const ClipRectOp&) = default;
+  static void Raster(const ClipRectOp* op,
+                     SkCanvas* canvas,
+                     const PlaybackParams& params);
+  bool IsValid() const { return IsValidSkClipOp(op) && rect.isFinite(); }
+  static bool AreEqual(const PaintOp* left, const PaintOp* right);
+  HAS_SERIALIZATION_FUNCTIONS();
+
+  SkRect rect;
+  SkClipOp op;
+  bool antialias;
+
+ private:
+  ClipRectOp() : PaintOp(kType) {}
+};
+
+class CC_PAINT_EXPORT ClipRRectOp final : public PaintOp {
+ public:
+  static constexpr PaintOpType kType = PaintOpType::ClipRRect;
+  ClipRRectOp(const SkRRect& rrect, SkClipOp op, bool antialias)
+      : PaintOp(kType), rrect(rrect), op(op), antialias(antialias) {}
+  ClipRRectOp(const ClipRRectOp&) = default;
+  ClipRRectOp& operator=(const ClipRRectOp&) = default;
+  static void Raster(const ClipRRectOp* op,
+                     SkCanvas* canvas,
+                     const PlaybackParams& params);
+  bool IsValid() const { return IsValidSkClipOp(op) && rrect.isValid(); }
+  static bool AreEqual(const PaintOp* left, const PaintOp* right);
+  bool HasNonAAPaint() const { return !antialias; }
+  HAS_SERIALIZATION_FUNCTIONS();
+
+  SkRRect rrect;
+  SkClipOp op;
+  bool antialias;
+
+ private:
+  ClipRRectOp() : PaintOp(kType) {}
+};
+
+class CC_PAINT_EXPORT ConcatOp final : public PaintOp {
+ public:
+  static constexpr PaintOpType kType = PaintOpType::Concat;
+  explicit ConcatOp(const SkM44& matrix) : PaintOp(kType), matrix(matrix) {}
+  ConcatOp(const ConcatOp&) = default;
+  ConcatOp& operator=(const ConcatOp&) = default;
+  static void Raster(const ConcatOp* op,
+                     SkCanvas* canvas,
+                     const PlaybackParams& params);
+  bool IsValid() const { return true; }
+  static bool AreEqual(const PaintOp* left, const PaintOp* right);
+  HAS_SERIALIZATION_FUNCTIONS();
+
+  SkM44 matrix;
+
+ private:
+  ConcatOp() : PaintOp(kType) {}
+};
+
+class CC_PAINT_EXPORT CustomDataOp final : public PaintOp {
+ public:
+  static constexpr PaintOpType kType = PaintOpType::CustomData;
+  explicit CustomDataOp(uint32_t id) : PaintOp(kType), id(id) {}
+  CustomDataOp(const CustomDataOp&) = default;
+  CustomDataOp& operator=(const CustomDataOp&) = default;
+  static void Raster(const CustomDataOp* op,
+                     SkCanvas* canvas,
+                     const PlaybackParams& params);
+  bool IsValid() const { return true; }
+  static bool AreEqual(const PaintOp* left, const PaintOp* right);
+  HAS_SERIALIZATION_FUNCTIONS();
+
+  // Stores user defined id as a placeholder op.
+  uint32_t id;
+
+ private:
+  CustomDataOp() : PaintOp(kType) {}
+};
+
+class CC_PAINT_EXPORT DrawColorOp final : public PaintOp {
+ public:
+  static constexpr PaintOpType kType = PaintOpType::DrawColor;
+  static constexpr bool kIsDrawOp = true;
+  DrawColorOp(SkColor4f color, SkBlendMode mode)
+      : PaintOp(kType), color(color), mode(mode) {}
+  DrawColorOp(const DrawColorOp&) = default;
+  DrawColorOp& operator=(const DrawColorOp&) = default;
+  static void Raster(const DrawColorOp* op,
+                     SkCanvas* canvas,
+                     const PlaybackParams& params);
+  bool IsValid() const { return IsValidDrawColorSkBlendMode(mode); }
+  static bool AreEqual(const PaintOp* left, const PaintOp* right);
+  HAS_SERIALIZATION_FUNCTIONS();
+
+  SkColor4f color;
+  SkBlendMode mode;
+
+ private:
+  DrawColorOp() : PaintOp(kType) {}
+};
+
+class CC_PAINT_EXPORT DrawDRRectOp final : public PaintOpWithFlags {
+ public:
+  static constexpr PaintOpType kType = PaintOpType::DrawDRRect;
+  static constexpr bool kIsDrawOp = true;
+  DrawDRRectOp(const SkRRect& outer,
+               const SkRRect& inner,
+               const PaintFlags& flags)
+      : PaintOpWithFlags(kType, flags), outer(outer), inner(inner) {}
+  DrawDRRectOp(const DrawDRRectOp&) = default;
+  DrawDRRectOp& operator=(const DrawDRRectOp&) = default;
+  static void RasterWithFlags(const DrawDRRectOp* op,
+                              const PaintFlags* flags,
+                              SkCanvas* canvas,
+                              const PlaybackParams& params);
+  bool IsValid() const {
+    return flags.IsValid() && outer.isValid() && inner.isValid();
+  }
+  static bool AreEqual(const PaintOp* left, const PaintOp* right);
+  HAS_SERIALIZATION_FUNCTIONS();
+
+  SkRRect outer;
+  SkRRect inner;
+
+ private:
+  DrawDRRectOp() : PaintOpWithFlags(kType) {}
+};
+
+class CC_PAINT_EXPORT DrawImageOp final : public PaintOpWithFlags {
+ public:
+  static constexpr PaintOpType kType = PaintOpType::DrawImage;
+  static constexpr bool kIsDrawOp = true;
+  DrawImageOp(const PaintImage& image, SkScalar left, SkScalar top);
+  DrawImageOp(const PaintImage& image,
+              SkScalar left,
+              SkScalar top,
+              const SkSamplingOptions&,
+              const PaintFlags* flags);
+  ~DrawImageOp();
+  DrawImageOp(const DrawImageOp&) = default;
+  DrawImageOp& operator=(const DrawImageOp&) = default;
+  static void RasterWithFlags(const DrawImageOp* op,
+                              const PaintFlags* flags,
+                              SkCanvas* canvas,
+                              const PlaybackParams& params);
+  bool IsValid() const {
+    return flags.IsValid() && SkScalarIsFinite(scale_adjustment.width()) &&
+           SkScalarIsFinite(scale_adjustment.height());
+  }
+  static bool AreEqual(const PaintOp* left, const PaintOp* right);
+  bool HasDiscardableImages() const;
+  bool HasNonAAPaint() const { return false; }
+  HAS_SERIALIZATION_FUNCTIONS();
+
+  PaintImage image;
+  SkScalar left;
+  SkScalar top;
+  SkSamplingOptions sampling;
+
+ private:
+  DrawImageOp();
+
+  // Scale that has already been applied to the decoded image during
+  // serialization. Used with OOP raster.
+  SkSize scale_adjustment = SkSize::Make(1.f, 1.f);
+};
+
+class CC_PAINT_EXPORT DrawImageRectOp final : public PaintOpWithFlags {
+ public:
+  static constexpr PaintOpType kType = PaintOpType::DrawImageRect;
+  static constexpr bool kIsDrawOp = true;
+  DrawImageRectOp(const PaintImage& image,
+                  const SkRect& src,
+                  const SkRect& dst,
+                  SkCanvas::SrcRectConstraint constraint);
+  DrawImageRectOp(const PaintImage& image,
+                  const SkRect& src,
+                  const SkRect& dst,
+                  const SkSamplingOptions&,
+                  const PaintFlags* flags,
+                  SkCanvas::SrcRectConstraint constraint);
+  ~DrawImageRectOp();
+  DrawImageRectOp(const DrawImageRectOp&) = default;
+  DrawImageRectOp& operator=(const DrawImageRectOp&) = default;
+  static void RasterWithFlags(const DrawImageRectOp* op,
+                              const PaintFlags* flags,
+                              SkCanvas* canvas,
+                              const PlaybackParams& params);
+  bool IsValid() const {
+    return flags.IsValid() && src.isFinite() && dst.isFinite() &&
+           SkScalarIsFinite(scale_adjustment.width()) &&
+           SkScalarIsFinite(scale_adjustment.height());
+  }
+  static bool AreEqual(const PaintOp* left, const PaintOp* right);
+  bool HasDiscardableImages() const;
+  HAS_SERIALIZATION_FUNCTIONS();
+
+  PaintImage image;
+  SkRect src;
+  SkRect dst;
+  SkSamplingOptions sampling;
+  SkCanvas::SrcRectConstraint constraint;
+
+ private:
+  DrawImageRectOp();
+
+  // Scale that has already been applied to the decoded image during
+  // serialization. Used with OOP raster.
+  SkSize scale_adjustment = SkSize::Make(1.f, 1.f);
+};
+
+class CC_PAINT_EXPORT DrawIRectOp final : public PaintOpWithFlags {
+ public:
+  static constexpr PaintOpType kType = PaintOpType::DrawIRect;
+  static constexpr bool kIsDrawOp = true;
+  DrawIRectOp(const SkIRect& rect, const PaintFlags& flags)
+      : PaintOpWithFlags(kType, flags), rect(rect) {}
+  DrawIRectOp(const DrawIRectOp&) = default;
+  DrawIRectOp& operator=(const DrawIRectOp&) = default;
+  static void RasterWithFlags(const DrawIRectOp* op,
+                              const PaintFlags* flags,
+                              SkCanvas* canvas,
+                              const PlaybackParams& params);
+  bool IsValid() const { return flags.IsValid(); }
+  static bool AreEqual(const PaintOp* left, const PaintOp* right);
+  bool HasNonAAPaint() const { return false; }
+  HAS_SERIALIZATION_FUNCTIONS();
+
+  SkIRect rect;
+
+ private:
+  DrawIRectOp() : PaintOpWithFlags(kType) {}
+};
+
+class CC_PAINT_EXPORT DrawLineOp final : public PaintOpWithFlags {
+ public:
+  static constexpr PaintOpType kType = PaintOpType::DrawLine;
+  static constexpr bool kIsDrawOp = true;
+  DrawLineOp(SkScalar x0,
+             SkScalar y0,
+             SkScalar x1,
+             SkScalar y1,
+             const PaintFlags& flags)
+      : PaintOpWithFlags(kType, flags), x0(x0), y0(y0), x1(x1), y1(y1) {}
+  DrawLineOp(const DrawLineOp&) = default;
+  DrawLineOp& operator=(const DrawLineOp&) = default;
+  static void RasterWithFlags(const DrawLineOp* op,
+                              const PaintFlags* flags,
+                              SkCanvas* canvas,
+                              const PlaybackParams& params);
+  bool IsValid() const { return flags.IsValid(); }
+  static bool AreEqual(const PaintOp* left, const PaintOp* right);
+  HAS_SERIALIZATION_FUNCTIONS();
+
+  int CountSlowPaths() const;
+
+  SkScalar x0;
+  SkScalar y0;
+  SkScalar x1;
+  SkScalar y1;
+
+ private:
+  DrawLineOp() : PaintOpWithFlags(kType) {}
+};
+
+class CC_PAINT_EXPORT DrawOvalOp final : public PaintOpWithFlags {
+ public:
+  static constexpr PaintOpType kType = PaintOpType::DrawOval;
+  static constexpr bool kIsDrawOp = true;
+  DrawOvalOp(const SkRect& oval, const PaintFlags& flags)
+      : PaintOpWithFlags(kType, flags), oval(oval) {}
+  DrawOvalOp(const DrawOvalOp&) = default;
+  DrawOvalOp& operator=(const DrawOvalOp&) = default;
+  static void RasterWithFlags(const DrawOvalOp* op,
+                              const PaintFlags* flags,
+                              SkCanvas* canvas,
+                              const PlaybackParams& params);
+  bool IsValid() const {
+    // Reproduce SkRRect::isValid without converting.
+    return flags.IsValid() && oval.isFinite() && oval.isSorted();
+  }
+  static bool AreEqual(const PaintOp* left, const PaintOp* right);
+  HAS_SERIALIZATION_FUNCTIONS();
+
+  SkRect oval;
+
+ private:
+  DrawOvalOp() : PaintOpWithFlags(kType) {}
+};
+
+class CC_PAINT_EXPORT DrawPathOp final : public PaintOpWithFlags {
+ public:
+  static constexpr PaintOpType kType = PaintOpType::DrawPath;
+  static constexpr bool kIsDrawOp = true;
+  DrawPathOp(const SkPath& path,
+             const PaintFlags& flags,
+             UsePaintCache use_paint_cache = UsePaintCache::kEnabled)
+      : PaintOpWithFlags(kType, flags),
+        path(path),
+        sk_path_fill_type(static_cast<uint8_t>(path.getFillType())),
+        use_cache(use_paint_cache) {}
+  DrawPathOp(const DrawPathOp&) = default;
+  DrawPathOp& operator=(const DrawPathOp&) = default;
+  static void RasterWithFlags(const DrawPathOp* op,
+                              const PaintFlags* flags,
+                              SkCanvas* canvas,
+                              const PlaybackParams& params);
+  bool IsValid() const { return flags.IsValid() && IsValidPath(path); }
+  static bool AreEqual(const PaintOp* left, const PaintOp* right);
+  int CountSlowPaths() const;
+  HAS_SERIALIZATION_FUNCTIONS();
+
+  ThreadsafePath path;
+
+  // Changing the fill type on an SkPath does not change the
+  // generation id. This can lead to caching issues so we explicitly
+  // serialize/deserialize this value and set it on the SkPath before handing it
+  // to Skia.
+  uint8_t sk_path_fill_type;
+  UsePaintCache use_cache;
+
+ private:
+  DrawPathOp() : PaintOpWithFlags(kType) {}
+};
+
+class CC_PAINT_EXPORT DrawRecordOp final : public PaintOp {
+ public:
+  static constexpr PaintOpType kType = PaintOpType::DrawRecord;
+  static constexpr bool kIsDrawOp = true;
+  explicit DrawRecordOp(sk_sp<const PaintRecord> record);
+  ~DrawRecordOp();
+  DrawRecordOp(const DrawRecordOp&);
+  DrawRecordOp& operator=(const DrawRecordOp&);
+  static void Raster(const DrawRecordOp* op,
+                     SkCanvas* canvas,
+                     const PlaybackParams& params);
+  bool IsValid() const { return true; }
+  static bool AreEqual(const PaintOp* left, const PaintOp* right);
+  size_t AdditionalBytesUsed() const;
+  size_t AdditionalOpCount() const;
+  bool HasDiscardableImages() const;
+  int CountSlowPaths() const;
+  bool HasNonAAPaint() const;
+  bool HasDrawTextOps() const;
+  bool HasSaveLayerOps() const;
+  bool HasSaveLayerAlphaOps() const;
+  bool HasEffectsPreventingLCDTextForSaveLayerAlpha() const;
+  HAS_SERIALIZATION_FUNCTIONS();
+
+  sk_sp<const PaintRecord> record;
+};
+
+class CC_PAINT_EXPORT DrawRectOp final : public PaintOpWithFlags {
+ public:
+  static constexpr PaintOpType kType = PaintOpType::DrawRect;
+  static constexpr bool kIsDrawOp = true;
+  DrawRectOp(const SkRect& rect, const PaintFlags& flags)
+      : PaintOpWithFlags(kType, flags), rect(rect) {}
+  DrawRectOp(const DrawRectOp&) = default;
+  DrawRectOp& operator=(const DrawRectOp&) = default;
+  static void RasterWithFlags(const DrawRectOp* op,
+                              const PaintFlags* flags,
+                              SkCanvas* canvas,
+                              const PlaybackParams& params);
+  bool IsValid() const { return flags.IsValid() && rect.isFinite(); }
+  static bool AreEqual(const PaintOp* left, const PaintOp* right);
+  HAS_SERIALIZATION_FUNCTIONS();
+
+  SkRect rect;
+
+ private:
+  DrawRectOp() : PaintOpWithFlags(kType) {}
+};
+
+class CC_PAINT_EXPORT DrawRRectOp final : public PaintOpWithFlags {
+ public:
+  static constexpr PaintOpType kType = PaintOpType::DrawRRect;
+  static constexpr bool kIsDrawOp = true;
+  DrawRRectOp(const SkRRect& rrect, const PaintFlags& flags)
+      : PaintOpWithFlags(kType, flags), rrect(rrect) {}
+  DrawRRectOp(const DrawRRectOp&) = default;
+  DrawRRectOp& operator=(const DrawRRectOp&) = default;
+  static void RasterWithFlags(const DrawRRectOp* op,
+                              const PaintFlags* flags,
+                              SkCanvas* canvas,
+                              const PlaybackParams& params);
+  bool IsValid() const { return flags.IsValid() && rrect.isValid(); }
+  static bool AreEqual(const PaintOp* left, const PaintOp* right);
+  HAS_SERIALIZATION_FUNCTIONS();
+
+  SkRRect rrect;
+
+ private:
+  DrawRRectOp() : PaintOpWithFlags(kType) {}
+};
+
+class CC_PAINT_EXPORT DrawSkottieOp final : public PaintOp {
+ public:
+  static constexpr PaintOpType kType = PaintOpType::DrawSkottie;
+  static constexpr bool kIsDrawOp = true;
+  DrawSkottieOp(scoped_refptr<SkottieWrapper> skottie,
+                SkRect dst,
+                float t,
+                SkottieFrameDataMap images,
+                const SkottieColorMap& color_map,
+                SkottieTextPropertyValueMap text_map);
+  ~DrawSkottieOp();
+  DrawSkottieOp(const DrawSkottieOp&);
+  DrawSkottieOp& operator=(const DrawSkottieOp&);
+  static void Raster(const DrawSkottieOp* op,
+                     SkCanvas* canvas,
+                     const PlaybackParams& params);
+  bool IsValid() const {
+    return !!skottie && !dst.isEmpty() && t >= 0 && t <= 1.f;
+  }
+  static bool AreEqual(const PaintOp* left, const PaintOp* right);
+  bool HasDiscardableImages() const;
+  HAS_SERIALIZATION_FUNCTIONS();
+
+  scoped_refptr<SkottieWrapper> skottie;
+  SkRect dst;
+  float t;
+  // Image to use for each asset in this frame of the animation. If an asset is
+  // missing, the most recently used image for that asset (from a previous
+  // DrawSkottieOp) gets reused when rendering this frame. Given that image
+  // assets generally do not change from frame to frame in most animations, that
+  // means in practice, this map is often empty.
+  SkottieFrameDataMap images;
+  // Node name hashes and corresponding colors to use for dynamic coloration.
+  SkottieColorMap color_map;
+  SkottieTextPropertyValueMap text_map;
+
+ private:
+  SkottieWrapper::FrameDataFetchResult GetImageAssetForRaster(
+      SkCanvas* canvas,
+      const PlaybackParams& params,
+      SkottieResourceIdHash asset_id,
+      float t_frame,
+      sk_sp<SkImage>& image_out,
+      SkSamplingOptions& sampling_out) const;
+
+  DrawSkottieOp();
+};
+
+// TODO(penghuang): Replace DrawTextBlobOp with DrawSlugOp, when GrSlug can be
+// serialized.
+class CC_PAINT_EXPORT DrawTextBlobOp final : public PaintOpWithFlags {
+ public:
+  static constexpr PaintOpType kType = PaintOpType::DrawTextBlob;
+  static constexpr bool kIsDrawOp = true;
+  DrawTextBlobOp(sk_sp<SkTextBlob> blob,
+                 SkScalar x,
+                 SkScalar y,
+                 const PaintFlags& flags);
+  DrawTextBlobOp(sk_sp<SkTextBlob> blob,
+                 SkScalar x,
+                 SkScalar y,
+                 NodeId node_id,
+                 const PaintFlags& flags);
+  ~DrawTextBlobOp();
+  DrawTextBlobOp(const DrawTextBlobOp&);
+  DrawTextBlobOp& operator=(const DrawTextBlobOp&);
+  static void RasterWithFlags(const DrawTextBlobOp* op,
+                              const PaintFlags* flags,
+                              SkCanvas* canvas,
+                              const PlaybackParams& params);
+  bool IsValid() const { return flags.IsValid(); }
+  bool HasDrawTextOps() const { return true; }
+  static bool AreEqual(const PaintOp* left, const PaintOp* right);
+  HAS_SERIALIZATION_FUNCTIONS();
+
+  sk_sp<SkTextBlob> blob;
+  sk_sp<GrSlug> slug;
+  std::vector<sk_sp<GrSlug>> extra_slugs;
+  SkScalar x;
+  SkScalar y;
+  // This field isn't serialized.
+  NodeId node_id = kInvalidNodeId;
+
+ private:
+  DrawTextBlobOp();
+};
+
+class CC_PAINT_EXPORT NoopOp final : public PaintOp {
+ public:
+  static constexpr PaintOpType kType = PaintOpType::Noop;
+  NoopOp() : PaintOp(kType) {}
+  NoopOp(const NoopOp&) = default;
+  NoopOp& operator=(const NoopOp&) = default;
+  static void Raster(const NoopOp* op,
+                     SkCanvas* canvas,
+                     const PlaybackParams& params) {}
+  bool IsValid() const { return true; }
+  static bool AreEqual(const PaintOp* left, const PaintOp* right);
+  HAS_SERIALIZATION_FUNCTIONS();
+};
+
+class CC_PAINT_EXPORT RestoreOp final : public PaintOp {
+ public:
+  static constexpr PaintOpType kType = PaintOpType::Restore;
+  RestoreOp() : PaintOp(kType) {}
+  RestoreOp(const RestoreOp&) = default;
+  RestoreOp& operator=(const RestoreOp&) = default;
+  static void Raster(const RestoreOp* op,
+                     SkCanvas* canvas,
+                     const PlaybackParams& params);
+  bool IsValid() const { return true; }
+  static bool AreEqual(const PaintOp* left, const PaintOp* right);
+  HAS_SERIALIZATION_FUNCTIONS();
+};
+
+class CC_PAINT_EXPORT RotateOp final : public PaintOp {
+ public:
+  static constexpr PaintOpType kType = PaintOpType::Rotate;
+  explicit RotateOp(SkScalar degrees) : PaintOp(kType), degrees(degrees) {}
+  RotateOp(const RotateOp&) = default;
+  RotateOp& operator=(const RotateOp&) = default;
+  static void Raster(const RotateOp* op,
+                     SkCanvas* canvas,
+                     const PlaybackParams& params);
+  bool IsValid() const { return true; }
+  static bool AreEqual(const PaintOp* left, const PaintOp* right);
+  HAS_SERIALIZATION_FUNCTIONS();
+
+  SkScalar degrees;
+
+ private:
+  RotateOp() : PaintOp(kType) {}
+};
+
+class CC_PAINT_EXPORT SaveOp final : public PaintOp {
+ public:
+  static constexpr PaintOpType kType = PaintOpType::Save;
+  SaveOp() : PaintOp(kType) {}
+  SaveOp(const SaveOp&) = default;
+  SaveOp& operator=(const SaveOp&) = default;
+  static void Raster(const SaveOp* op,
+                     SkCanvas* canvas,
+                     const PlaybackParams& params);
+  bool IsValid() const { return true; }
+  static bool AreEqual(const PaintOp* left, const PaintOp* right);
+  HAS_SERIALIZATION_FUNCTIONS();
+};
+
+class CC_PAINT_EXPORT SaveLayerOp final : public PaintOpWithFlags {
+ public:
+  static constexpr PaintOpType kType = PaintOpType::SaveLayer;
+  SaveLayerOp(const SkRect* bounds, const PaintFlags* flags)
+      : PaintOpWithFlags(kType, flags ? *flags : PaintFlags()),
+        bounds(bounds ? *bounds : kUnsetRect) {}
+  SaveLayerOp(const SaveLayerOp&) = default;
+  SaveLayerOp& operator=(const SaveLayerOp&) = default;
+  static void RasterWithFlags(const SaveLayerOp* op,
+                              const PaintFlags* flags,
+                              SkCanvas* canvas,
+                              const PlaybackParams& params);
+  bool IsValid() const { return flags.IsValid() && IsValidOrUnsetRect(bounds); }
+  static bool AreEqual(const PaintOp* left, const PaintOp* right);
+  bool HasNonAAPaint() const { return false; }
+  // We simply assume any effects (or even no effects -- just starting an empty
+  // transparent layer) would break LCD text or be broken by the flags for
+  // SaveLayerAlpha to preserve LCD text.
+  bool HasEffectsPreventingLCDTextForSaveLayerAlpha() const { return true; }
+  bool HasSaveLayerOps() const { return true; }
+  HAS_SERIALIZATION_FUNCTIONS();
+
+  SkRect bounds;
+
+ private:
+  SaveLayerOp() : PaintOpWithFlags(kType) {}
+};
+
+class CC_PAINT_EXPORT SaveLayerAlphaOp final : public PaintOp {
+ public:
+  static constexpr PaintOpType kType = PaintOpType::SaveLayerAlpha;
+  template <class F, class = std::enable_if_t<std::is_same_v<F, float>>>
+  SaveLayerAlphaOp(const SkRect* bounds, F alpha)
+      : PaintOp(kType), bounds(bounds ? *bounds : kUnsetRect), alpha(alpha) {}
+  SaveLayerAlphaOp(const SaveLayerAlphaOp&) = default;
+  SaveLayerAlphaOp& operator=(const SaveLayerAlphaOp&) = default;
+  static void Raster(const SaveLayerAlphaOp* op,
+                     SkCanvas* canvas,
+                     const PlaybackParams& params);
+  bool IsValid() const { return IsValidOrUnsetRect(bounds); }
+  static bool AreEqual(const PaintOp* left, const PaintOp* right);
+  bool HasSaveLayerOps() const { return true; }
+  bool HasSaveLayerAlphaOps() const { return true; }
+  HAS_SERIALIZATION_FUNCTIONS();
+
+  SkRect bounds;
+  float alpha;
+
+ private:
+  SaveLayerAlphaOp() : PaintOp(kType) {}
+};
+
+class CC_PAINT_EXPORT ScaleOp final : public PaintOp {
+ public:
+  static constexpr PaintOpType kType = PaintOpType::Scale;
+  ScaleOp(SkScalar sx, SkScalar sy) : PaintOp(kType), sx(sx), sy(sy) {}
+  ScaleOp(const ScaleOp&) = default;
+  ScaleOp& operator=(const ScaleOp&) = default;
+  static void Raster(const ScaleOp* op,
+                     SkCanvas* canvas,
+                     const PlaybackParams& params);
+  bool IsValid() const { return true; }
+  static bool AreEqual(const PaintOp* left, const PaintOp* right);
+  HAS_SERIALIZATION_FUNCTIONS();
+
+  SkScalar sx;
+  SkScalar sy;
+
+ private:
+  ScaleOp() : PaintOp(kType) {}
+};
+
+class CC_PAINT_EXPORT SetMatrixOp final : public PaintOp {
+ public:
+  static constexpr PaintOpType kType = PaintOpType::SetMatrix;
+  explicit SetMatrixOp(const SkM44& matrix) : PaintOp(kType), matrix(matrix) {}
+  SetMatrixOp(const SetMatrixOp&) = default;
+  SetMatrixOp& operator=(const SetMatrixOp&) = default;
+  // This is the only op that needs the original ctm of the SkCanvas
+  // used for raster (since SetMatrix is relative to the recording origin and
+  // shouldn't clobber the SkCanvas raster origin).
+  //
+  // TODO(enne): Find some cleaner way to do this, possibly by making
+  // all SetMatrix calls Concat??
+  static void Raster(const SetMatrixOp* op,
+                     SkCanvas* canvas,
+                     const PlaybackParams& params);
+  bool IsValid() const { return true; }
+  static bool AreEqual(const PaintOp* left, const PaintOp* right);
+  HAS_SERIALIZATION_FUNCTIONS();
+
+  SkM44 matrix;
+
+ private:
+  SetMatrixOp() : PaintOp(kType) {}
+};
+
+class CC_PAINT_EXPORT SetNodeIdOp final : public PaintOp {
+ public:
+  static constexpr PaintOpType kType = PaintOpType::SetNodeId;
+  explicit SetNodeIdOp(int node_id) : PaintOp(kType), node_id(node_id) {}
+  SetNodeIdOp(const SetNodeIdOp&) = default;
+  SetNodeIdOp& operator=(const SetNodeIdOp&) = default;
+  static void Raster(const SetNodeIdOp* op,
+                     SkCanvas* canvas,
+                     const PlaybackParams& params);
+  bool IsValid() const { return true; }
+  static bool AreEqual(const PaintOp* left, const PaintOp* right);
+  HAS_SERIALIZATION_FUNCTIONS();
+
+  int node_id;
+
+ private:
+  SetNodeIdOp() : PaintOp(kType) {}
+};
+
+class CC_PAINT_EXPORT TranslateOp final : public PaintOp {
+ public:
+  static constexpr PaintOpType kType = PaintOpType::Translate;
+  TranslateOp(SkScalar dx, SkScalar dy) : PaintOp(kType), dx(dx), dy(dy) {}
+  TranslateOp(const TranslateOp&) = default;
+  TranslateOp& operator=(const TranslateOp&) = default;
+  static void Raster(const TranslateOp* op,
+                     SkCanvas* canvas,
+                     const PlaybackParams& params);
+  bool IsValid() const { return true; }
+  static bool AreEqual(const PaintOp* left, const PaintOp* right);
+  HAS_SERIALIZATION_FUNCTIONS();
+
+  SkScalar dx;
+  SkScalar dy;
+
+ private:
+  TranslateOp() : PaintOp(kType) {}
+};
+
+#undef HAS_SERIALIZATION_FUNCTIONS
+
+// TODO(vmpstr): Revisit this when sizes of DrawImageRectOp change.
+using LargestPaintOp =
+    typename std::conditional<(sizeof(DrawImageRectOp) > sizeof(DrawDRRectOp)),
+                              DrawImageRectOp,
+                              DrawDRRectOp>::type;
+
+}  // namespace cc
+
+#endif  // CC_PAINT_PAINT_OP_H_
diff --git a/cc/paint/paint_op_buffer.cc b/cc/paint/paint_op_buffer.cc
index 65e333f7..4958fb5 100644
--- a/cc/paint/paint_op_buffer.cc
+++ b/cc/paint/paint_op_buffer.cc
@@ -5,377 +5,22 @@
 #include "cc/paint/paint_op_buffer.h"
 
 #include <algorithm>
-#include <memory>
 #include <utility>
-#include <vector>
 
-#include "base/bind.h"
-#include "base/memory/raw_ptr.h"
 #include "base/notreached.h"
 #include "base/types/optional_util.h"
-#include "cc/paint/decoded_draw_image.h"
-#include "cc/paint/display_item_list.h"
-#include "cc/paint/image_provider.h"
 #include "cc/paint/paint_flags.h"
 #include "cc/paint/paint_image_builder.h"
+#include "cc/paint/paint_op.h"
+#include "cc/paint/paint_op_buffer_iterator.h"
 #include "cc/paint/paint_op_reader.h"
 #include "cc/paint/paint_op_writer.h"
 #include "cc/paint/paint_record.h"
 #include "cc/paint/scoped_raster_flags.h"
 #include "cc/paint/skottie_serialization_history.h"
-#include "third_party/abseil-cpp/absl/types/variant.h"
-#include "third_party/skia/include/core/SkAnnotation.h"
-#include "third_party/skia/include/core/SkCanvas.h"
-#include "third_party/skia/include/core/SkColorSpace.h"
-#include "third_party/skia/include/core/SkImage.h"
-#include "third_party/skia/include/core/SkRegion.h"
-#include "third_party/skia/include/core/SkSerialProcs.h"
-#include "third_party/skia/include/core/SkTextBlob.h"
-#include "third_party/skia/include/docs/SkPDFDocument.h"
 #include "third_party/skia/include/gpu/GrRecordingContext.h"
-#include "third_party/skia/include/private/chromium/GrSlug.h"
-#include "ui/gfx/geometry/skia_conversions.h"
 
 namespace cc {
-namespace {
-// In a future CL, convert DrawImage to explicitly take sampling instead of
-// quality
-PaintFlags::FilterQuality sampling_to_quality(
-    const SkSamplingOptions& sampling) {
-  if (sampling.useCubic) {
-    return PaintFlags::FilterQuality::kHigh;
-  }
-  if (sampling.mipmap != SkMipmapMode::kNone) {
-    return PaintFlags::FilterQuality::kMedium;
-  }
-  return sampling.filter == SkFilterMode::kLinear
-             ? PaintFlags::FilterQuality::kLow
-             : PaintFlags::FilterQuality::kNone;
-}
-
-DrawImage CreateDrawImage(const PaintImage& image,
-                          const PaintFlags* flags,
-                          const SkSamplingOptions& sampling,
-                          const SkM44& matrix) {
-  if (!image)
-    return DrawImage();
-  return DrawImage(image, flags->useDarkModeForImage(),
-                   SkIRect::MakeWH(image.width(), image.height()),
-                   sampling_to_quality(sampling), matrix);
-}
-
-bool IsScaleAdjustmentIdentity(const SkSize& scale_adjustment) {
-  return std::abs(scale_adjustment.width() - 1.f) < FLT_EPSILON &&
-         std::abs(scale_adjustment.height() - 1.f) < FLT_EPSILON;
-}
-
-SkRect AdjustSrcRectForScale(SkRect original, SkSize scale_adjustment) {
-  if (IsScaleAdjustmentIdentity(scale_adjustment))
-    return original;
-
-  float x_scale = scale_adjustment.width();
-  float y_scale = scale_adjustment.height();
-  return SkRect::MakeXYWH(original.x() * x_scale, original.y() * y_scale,
-                          original.width() * x_scale,
-                          original.height() * y_scale);
-}
-
-SkRect MapRect(const SkMatrix& matrix, const SkRect& src) {
-  SkRect dst;
-  matrix.mapRect(&dst, src);
-  return dst;
-}
-
-void DrawImageRect(SkCanvas* canvas,
-                   const SkImage* image,
-                   const SkRect& src,
-                   const SkRect& dst,
-                   const SkSamplingOptions& options,
-                   const SkPaint* paint,
-                   SkCanvas::SrcRectConstraint constraint) {
-  if (!image)
-    return;
-  if (constraint == SkCanvas::kStrict_SrcRectConstraint &&
-      options.mipmap != SkMipmapMode::kNone &&
-      src.contains(SkRect::Make(image->dimensions()))) {
-    SkMatrix m;
-    m.setRectToRect(src, dst, SkMatrix::ScaleToFit::kFill_ScaleToFit);
-    canvas->save();
-    canvas->concat(m);
-    canvas->drawImage(image, 0, 0, options, paint);
-    canvas->restore();
-    return;
-  }
-  canvas->drawImageRect(image, src, dst, options, paint, constraint);
-}
-
-bool GrSlugAreEqual(sk_sp<GrSlug> left, sk_sp<GrSlug> right) {
-  if (!left && !right) {
-    return true;
-  }
-  if (left && right) {
-    auto left_data = left->serialize();
-    auto right_data = right->serialize();
-    return left_data->equals(right_data.get());
-  }
-  return false;
-}
-
-}  // namespace
-
-#define TYPES(M)      \
-  M(AnnotateOp)       \
-  M(ClipPathOp)       \
-  M(ClipRectOp)       \
-  M(ClipRRectOp)      \
-  M(ConcatOp)         \
-  M(CustomDataOp)     \
-  M(DrawColorOp)      \
-  M(DrawDRRectOp)     \
-  M(DrawImageOp)      \
-  M(DrawImageRectOp)  \
-  M(DrawIRectOp)      \
-  M(DrawLineOp)       \
-  M(DrawOvalOp)       \
-  M(DrawPathOp)       \
-  M(DrawRecordOp)     \
-  M(DrawRectOp)       \
-  M(DrawRRectOp)      \
-  M(DrawSkottieOp)    \
-  M(DrawTextBlobOp)   \
-  M(NoopOp)           \
-  M(RestoreOp)        \
-  M(RotateOp)         \
-  M(SaveOp)           \
-  M(SaveLayerOp)      \
-  M(SaveLayerAlphaOp) \
-  M(ScaleOp)          \
-  M(SetMatrixOp)      \
-  M(SetNodeIdOp)      \
-  M(TranslateOp)
-
-static constexpr size_t kNumOpTypes =
-    static_cast<size_t>(PaintOpType::LastPaintOpType) + 1;
-
-// Verify that every op is in the TYPES macro.
-#define M(T) +1
-static_assert(kNumOpTypes == TYPES(M), "Missing op in list");
-#undef M
-
-#define M(T) sizeof(T),
-static const size_t g_type_to_size[kNumOpTypes] = {TYPES(M)};
-#undef M
-
-template <typename T, bool HasFlags>
-struct Rasterizer {
-  static void RasterWithFlags(const T* op,
-                              const PaintFlags* flags,
-                              SkCanvas* canvas,
-                              const PlaybackParams& params) {
-    static_assert(
-        !T::kHasPaintFlags,
-        "This function should not be used for a PaintOp that has PaintFlags");
-    DCHECK(op->IsValid());
-    NOTREACHED();
-  }
-  static void Raster(const T* op,
-                     SkCanvas* canvas,
-                     const PlaybackParams& params) {
-    static_assert(
-        !T::kHasPaintFlags,
-        "This function should not be used for a PaintOp that has PaintFlags");
-    DCHECK(op->IsValid());
-    T::Raster(op, canvas, params);
-  }
-};
-
-template <typename T>
-struct Rasterizer<T, true> {
-  static void RasterWithFlags(const T* op,
-                              const PaintFlags* flags,
-                              SkCanvas* canvas,
-                              const PlaybackParams& params) {
-    static_assert(T::kHasPaintFlags,
-                  "This function expects the PaintOp to have PaintFlags");
-    DCHECK(op->IsValid());
-    T::RasterWithFlags(op, flags, canvas, params);
-  }
-
-  static void Raster(const T* op,
-                     SkCanvas* canvas,
-                     const PlaybackParams& params) {
-    static_assert(T::kHasPaintFlags,
-                  "This function expects the PaintOp to have PaintFlags");
-    DCHECK(op->IsValid());
-    T::RasterWithFlags(op, &op->flags, canvas, params);
-  }
-};
-
-using RasterFunction = void (*)(const PaintOp* op,
-                                SkCanvas* canvas,
-                                const PlaybackParams& params);
-#define M(T)                                                              \
-  [](const PaintOp* op, SkCanvas* canvas, const PlaybackParams& params) { \
-    Rasterizer<T, T::kHasPaintFlags>::Raster(static_cast<const T*>(op),   \
-                                             canvas, params);             \
-  },
-static const RasterFunction g_raster_functions[kNumOpTypes] = {TYPES(M)};
-#undef M
-
-using RasterWithFlagsFunction = void (*)(const PaintOp* op,
-                                         const PaintFlags* flags,
-                                         SkCanvas* canvas,
-                                         const PlaybackParams& params);
-#define M(T)                                                       \
-  [](const PaintOp* op, const PaintFlags* flags, SkCanvas* canvas, \
-     const PlaybackParams& params) {                               \
-    Rasterizer<T, T::kHasPaintFlags>::RasterWithFlags(             \
-        static_cast<const T*>(op), flags, canvas, params);         \
-  },
-static const RasterWithFlagsFunction
-    g_raster_with_flags_functions[kNumOpTypes] = {TYPES(M)};
-#undef M
-
-using SerializeFunction = size_t (*)(const PaintOp* op,
-                                     void* memory,
-                                     size_t size,
-                                     const PaintOp::SerializeOptions& options,
-                                     const PaintFlags* flags_to_serialize,
-                                     const SkM44& current_ctm,
-                                     const SkM44& original_ctm);
-
-#define M(T) &T::Serialize,
-static const SerializeFunction g_serialize_functions[kNumOpTypes] = {TYPES(M)};
-#undef M
-
-using DeserializeFunction =
-    PaintOp* (*)(const volatile void* input,
-                 size_t input_size,
-                 void* output,
-                 size_t output_size,
-                 const PaintOp::DeserializeOptions& options);
-
-#define M(T) &T::Deserialize,
-static const DeserializeFunction g_deserialize_functions[kNumOpTypes] = {
-    TYPES(M)};
-#undef M
-
-using EqualsFunction = bool (*)(const PaintOp* left, const PaintOp* right);
-#define M(T) &T::AreEqual,
-static const EqualsFunction g_equals_operator[kNumOpTypes] = {TYPES(M)};
-#undef M
-
-// Most state ops (matrix, clip, save, restore) have a trivial destructor.
-// TODO(enne): evaluate if we need the nullptr optimization or if
-// we even need to differentiate trivial destructors here.
-using VoidFunction = void (*)(PaintOp* op);
-#define M(T)                                           \
-  !std::is_trivially_destructible<T>::value            \
-      ? [](PaintOp* op) { static_cast<T*>(op)->~T(); } \
-      : static_cast<VoidFunction>(nullptr),
-static const VoidFunction g_destructor_functions[kNumOpTypes] = {TYPES(M)};
-#undef M
-
-#define M(T) T::kIsDrawOp,
-static bool g_is_draw_op[kNumOpTypes] = {TYPES(M)};
-#undef M
-
-#define M(T) T::kHasPaintFlags,
-static bool g_has_paint_flags[kNumOpTypes] = {TYPES(M)};
-#undef M
-
-#define M(T)                                         \
-  static_assert(sizeof(T) <= sizeof(LargestPaintOp), \
-                #T " must be no bigger than LargestPaintOp");
-TYPES(M)
-#undef M
-
-#define M(T)                                               \
-  static_assert(alignof(T) <= PaintOpBuffer::PaintOpAlign, \
-                #T " must have alignment no bigger than PaintOpAlign");
-TYPES(M)
-#undef M
-
-using AnalyzeOpFunc = void (*)(PaintOpBuffer*, const PaintOp*);
-#define M(T)                                           \
-  [](PaintOpBuffer* buffer, const PaintOp* op) {       \
-    buffer->AnalyzeAddedOp(static_cast<const T*>(op)); \
-  },
-static const AnalyzeOpFunc g_analyze_op_functions[kNumOpTypes] = {TYPES(M)};
-#undef M
-
-#undef TYPES
-
-const SkRect PaintOp::kUnsetRect = {SK_ScalarInfinity, 0, 0, 0};
-const size_t PaintOp::kMaxSkip;
-
-std::string PaintOpTypeToString(PaintOpType type) {
-  switch (type) {
-    case PaintOpType::Annotate:
-      return "Annotate";
-    case PaintOpType::ClipPath:
-      return "ClipPath";
-    case PaintOpType::ClipRect:
-      return "ClipRect";
-    case PaintOpType::ClipRRect:
-      return "ClipRRect";
-    case PaintOpType::Concat:
-      return "Concat";
-    case PaintOpType::CustomData:
-      return "CustomData";
-    case PaintOpType::DrawColor:
-      return "DrawColor";
-    case PaintOpType::DrawDRRect:
-      return "DrawDRRect";
-    case PaintOpType::DrawImage:
-      return "DrawImage";
-    case PaintOpType::DrawImageRect:
-      return "DrawImageRect";
-    case PaintOpType::DrawIRect:
-      return "DrawIRect";
-    case PaintOpType::DrawLine:
-      return "DrawLine";
-    case PaintOpType::DrawOval:
-      return "DrawOval";
-    case PaintOpType::DrawPath:
-      return "DrawPath";
-    case PaintOpType::DrawRecord:
-      return "DrawRecord";
-    case PaintOpType::DrawRect:
-      return "DrawRect";
-    case PaintOpType::DrawRRect:
-      return "DrawRRect";
-    case PaintOpType::DrawSkottie:
-      return "DrawSkottie";
-    case PaintOpType::DrawTextBlob:
-      return "DrawTextBlob";
-    case PaintOpType::Noop:
-      return "Noop";
-    case PaintOpType::Restore:
-      return "Restore";
-    case PaintOpType::Rotate:
-      return "Rotate";
-    case PaintOpType::Save:
-      return "Save";
-    case PaintOpType::SaveLayer:
-      return "SaveLayer";
-    case PaintOpType::SaveLayerAlpha:
-      return "SaveLayerAlpha";
-    case PaintOpType::Scale:
-      return "Scale";
-    case PaintOpType::SetMatrix:
-      return "SetMatrix";
-    case PaintOpType::SetNodeId:
-      return "SetNodeId";
-    case PaintOpType::Translate:
-      return "Translate";
-  }
-  return "UNKNOWN";
-}
-
-std::ostream& operator<<(std::ostream& os, PaintOpType type) {
-  return os << PaintOpTypeToString(type);
-}
 
 PlaybackParams::PlaybackParams(ImageProvider* image_provider)
     : PlaybackParams(image_provider, SkM44()) {}
@@ -395,7 +40,7 @@
 PlaybackParams& PlaybackParams::operator=(const PlaybackParams& other) =
     default;
 
-PaintOp::SerializeOptions::SerializeOptions(
+PaintOpBuffer::SerializeOptions::SerializeOptions(
     ImageProvider* image_provider,
     TransferCacheSerializeHelper* transfer_cache,
     ClientPaintCache* paint_cache,
@@ -416,13 +61,14 @@
           context_supports_distance_field_text),
       max_texture_size(max_texture_size) {}
 
-PaintOp::SerializeOptions::SerializeOptions() = default;
-PaintOp::SerializeOptions::SerializeOptions(const SerializeOptions&) = default;
-PaintOp::SerializeOptions& PaintOp::SerializeOptions::operator=(
+PaintOpBuffer::SerializeOptions::SerializeOptions() = default;
+PaintOpBuffer::SerializeOptions::SerializeOptions(const SerializeOptions&) =
+    default;
+PaintOpBuffer::SerializeOptions& PaintOpBuffer::SerializeOptions::operator=(
     const SerializeOptions&) = default;
-PaintOp::SerializeOptions::~SerializeOptions() = default;
+PaintOpBuffer::SerializeOptions::~SerializeOptions() = default;
 
-PaintOp::DeserializeOptions::DeserializeOptions(
+PaintOpBuffer::DeserializeOptions::DeserializeOptions(
     TransferCacheDeserializeHelper* transfer_cache,
     ServicePaintCache* paint_cache,
     SkStrikeClient* strike_client,
@@ -438,2482 +84,6 @@
   DCHECK(scratch_buffer);
 }
 
-size_t AnnotateOp::Serialize(const PaintOp* base_op,
-                             void* memory,
-                             size_t size,
-                             const SerializeOptions& options,
-                             const PaintFlags* flags_to_serialize,
-                             const SkM44& current_ctm,
-                             const SkM44& original_ctm) {
-  auto* op = static_cast<const AnnotateOp*>(base_op);
-  PaintOpWriter helper(memory, size, options);
-  helper.Write(op->annotation_type);
-  helper.Write(op->rect);
-  helper.Write(op->data);
-  return helper.size();
-}
-
-size_t ClipPathOp::Serialize(const PaintOp* base_op,
-                             void* memory,
-                             size_t size,
-                             const SerializeOptions& options,
-                             const PaintFlags* flags_to_serialize,
-                             const SkM44& current_ctm,
-                             const SkM44& original_ctm) {
-  auto* op = static_cast<const ClipPathOp*>(base_op);
-  PaintOpWriter helper(memory, size, options);
-  helper.Write(op->path, op->use_cache);
-  helper.Write(op->op);
-  helper.Write(op->antialias);
-  return helper.size();
-}
-
-size_t ClipRectOp::Serialize(const PaintOp* base_op,
-                             void* memory,
-                             size_t size,
-                             const SerializeOptions& options,
-                             const PaintFlags* flags_to_serialize,
-                             const SkM44& current_ctm,
-                             const SkM44& original_ctm) {
-  auto* op = static_cast<const ClipRectOp*>(base_op);
-  PaintOpWriter helper(memory, size, options);
-  helper.Write(op->rect);
-  helper.Write(op->op);
-  helper.Write(op->antialias);
-  return helper.size();
-}
-
-size_t ClipRRectOp::Serialize(const PaintOp* base_op,
-                              void* memory,
-                              size_t size,
-                              const SerializeOptions& options,
-                              const PaintFlags* flags_to_serialize,
-                              const SkM44& current_ctm,
-                              const SkM44& original_ctm) {
-  auto* op = static_cast<const ClipRRectOp*>(base_op);
-  PaintOpWriter helper(memory, size, options);
-  helper.Write(op->rrect);
-  helper.Write(op->op);
-  helper.Write(op->antialias);
-  return helper.size();
-}
-
-size_t ConcatOp::Serialize(const PaintOp* base_op,
-                           void* memory,
-                           size_t size,
-                           const SerializeOptions& options,
-                           const PaintFlags* flags_to_serialize,
-                           const SkM44& current_ctm,
-                           const SkM44& original_ctm) {
-  auto* op = static_cast<const ConcatOp*>(base_op);
-  PaintOpWriter helper(memory, size, options);
-  helper.Write(op->matrix);
-  return helper.size();
-}
-
-size_t CustomDataOp::Serialize(const PaintOp* base_op,
-                               void* memory,
-                               size_t size,
-                               const SerializeOptions& options,
-                               const PaintFlags* flags_to_serialize,
-                               const SkM44& current_ctm,
-                               const SkM44& original_ctm) {
-  auto* op = static_cast<const CustomDataOp*>(base_op);
-  PaintOpWriter helper(memory, size, options);
-  helper.Write(op->id);
-  return helper.size();
-}
-
-size_t DrawColorOp::Serialize(const PaintOp* base_op,
-                              void* memory,
-                              size_t size,
-                              const SerializeOptions& options,
-                              const PaintFlags* flags_to_serialize,
-                              const SkM44& current_ctm,
-                              const SkM44& original_ctm) {
-  auto* op = static_cast<const DrawColorOp*>(base_op);
-  PaintOpWriter helper(memory, size, options);
-  helper.Write(op->color);
-  helper.Write(op->mode);
-  return helper.size();
-}
-
-size_t DrawDRRectOp::Serialize(const PaintOp* base_op,
-                               void* memory,
-                               size_t size,
-                               const SerializeOptions& options,
-                               const PaintFlags* flags_to_serialize,
-                               const SkM44& current_ctm,
-                               const SkM44& original_ctm) {
-  auto* op = static_cast<const DrawDRRectOp*>(base_op);
-  PaintOpWriter helper(memory, size, options);
-  if (!flags_to_serialize)
-    flags_to_serialize = &op->flags;
-  helper.Write(*flags_to_serialize, current_ctm);
-  helper.Write(op->outer);
-  helper.Write(op->inner);
-  return helper.size();
-}
-
-size_t DrawImageOp::Serialize(const PaintOp* base_op,
-                              void* memory,
-                              size_t size,
-                              const SerializeOptions& options,
-                              const PaintFlags* flags_to_serialize,
-                              const SkM44& current_ctm,
-                              const SkM44& original_ctm) {
-  auto* op = static_cast<const DrawImageOp*>(base_op);
-  PaintOpWriter helper(memory, size, options);
-  if (!flags_to_serialize)
-    flags_to_serialize = &op->flags;
-  helper.Write(*flags_to_serialize, current_ctm);
-
-  SkSize scale_adjustment = SkSize::Make(1.f, 1.f);
-  helper.Write(
-      CreateDrawImage(op->image, flags_to_serialize, op->sampling, current_ctm),
-      &scale_adjustment);
-  helper.AssertAlignment(alignof(SkScalar));
-  helper.Write(scale_adjustment.width());
-  helper.Write(scale_adjustment.height());
-
-  helper.Write(op->left);
-  helper.Write(op->top);
-  helper.Write(op->sampling);
-  return helper.size();
-}
-
-size_t DrawImageRectOp::Serialize(const PaintOp* base_op,
-                                  void* memory,
-                                  size_t size,
-                                  const SerializeOptions& options,
-                                  const PaintFlags* flags_to_serialize,
-                                  const SkM44& current_ctm,
-                                  const SkM44& original_ctm) {
-  auto* op = static_cast<const DrawImageRectOp*>(base_op);
-  PaintOpWriter helper(memory, size, options);
-  if (!flags_to_serialize)
-    flags_to_serialize = &op->flags;
-  helper.Write(*flags_to_serialize, current_ctm);
-
-  // This adjustment mirrors DiscardableImageMap::GatherDiscardableImage logic.
-  SkM44 matrix = current_ctm * SkM44(SkMatrix::RectToRect(op->src, op->dst));
-  // Note that we don't request subsets here since the GpuImageCache has no
-  // optimizations for using subsets.
-  SkSize scale_adjustment = SkSize::Make(1.f, 1.f);
-  helper.Write(
-      CreateDrawImage(op->image, flags_to_serialize, op->sampling, matrix),
-      &scale_adjustment);
-  helper.AssertAlignment(alignof(SkScalar));
-  helper.Write(scale_adjustment.width());
-  helper.Write(scale_adjustment.height());
-
-  helper.Write(op->src);
-  helper.Write(op->dst);
-  helper.Write(op->sampling);
-  helper.Write(op->constraint);
-  return helper.size();
-}
-
-size_t DrawIRectOp::Serialize(const PaintOp* base_op,
-                              void* memory,
-                              size_t size,
-                              const SerializeOptions& options,
-                              const PaintFlags* flags_to_serialize,
-                              const SkM44& current_ctm,
-                              const SkM44& original_ctm) {
-  auto* op = static_cast<const DrawIRectOp*>(base_op);
-  PaintOpWriter helper(memory, size, options);
-  if (!flags_to_serialize)
-    flags_to_serialize = &op->flags;
-  helper.Write(*flags_to_serialize, current_ctm);
-  helper.Write(op->rect);
-  return helper.size();
-}
-
-size_t DrawLineOp::Serialize(const PaintOp* base_op,
-                             void* memory,
-                             size_t size,
-                             const SerializeOptions& options,
-                             const PaintFlags* flags_to_serialize,
-                             const SkM44& current_ctm,
-                             const SkM44& original_ctm) {
-  auto* op = static_cast<const DrawLineOp*>(base_op);
-  PaintOpWriter helper(memory, size, options);
-  if (!flags_to_serialize)
-    flags_to_serialize = &op->flags;
-  helper.Write(*flags_to_serialize, current_ctm);
-  helper.AssertAlignment(alignof(SkScalar));
-  helper.Write(op->x0);
-  helper.Write(op->y0);
-  helper.Write(op->x1);
-  helper.Write(op->y1);
-  return helper.size();
-}
-
-size_t DrawOvalOp::Serialize(const PaintOp* base_op,
-                             void* memory,
-                             size_t size,
-                             const SerializeOptions& options,
-                             const PaintFlags* flags_to_serialize,
-                             const SkM44& current_ctm,
-                             const SkM44& original_ctm) {
-  auto* op = static_cast<const DrawOvalOp*>(base_op);
-  PaintOpWriter helper(memory, size, options);
-  if (!flags_to_serialize)
-    flags_to_serialize = &op->flags;
-  helper.Write(*flags_to_serialize, current_ctm);
-  helper.Write(op->oval);
-  return helper.size();
-}
-
-size_t DrawPathOp::Serialize(const PaintOp* base_op,
-                             void* memory,
-                             size_t size,
-                             const SerializeOptions& options,
-                             const PaintFlags* flags_to_serialize,
-                             const SkM44& current_ctm,
-                             const SkM44& original_ctm) {
-  auto* op = static_cast<const DrawPathOp*>(base_op);
-  PaintOpWriter helper(memory, size, options);
-  if (!flags_to_serialize)
-    flags_to_serialize = &op->flags;
-  helper.Write(*flags_to_serialize, current_ctm);
-  helper.Write(op->path, op->use_cache);
-  helper.Write(op->sk_path_fill_type);
-  return helper.size();
-}
-
-size_t DrawRecordOp::Serialize(const PaintOp* op,
-                               void* memory,
-                               size_t size,
-                               const SerializeOptions& options,
-                               const PaintFlags* flags_to_serialize,
-                               const SkM44& current_ctm,
-                               const SkM44& original_ctm) {
-  // TODO(enne): these must be flattened.  Serializing this will not do
-  // anything.
-  NOTREACHED();
-  return 0u;
-}
-
-size_t DrawRectOp::Serialize(const PaintOp* base_op,
-                             void* memory,
-                             size_t size,
-                             const SerializeOptions& options,
-                             const PaintFlags* flags_to_serialize,
-                             const SkM44& current_ctm,
-                             const SkM44& original_ctm) {
-  auto* op = static_cast<const DrawRectOp*>(base_op);
-  PaintOpWriter helper(memory, size, options);
-  if (!flags_to_serialize)
-    flags_to_serialize = &op->flags;
-  helper.Write(*flags_to_serialize, current_ctm);
-  helper.Write(op->rect);
-  return helper.size();
-}
-
-size_t DrawRRectOp::Serialize(const PaintOp* base_op,
-                              void* memory,
-                              size_t size,
-                              const SerializeOptions& options,
-                              const PaintFlags* flags_to_serialize,
-                              const SkM44& current_ctm,
-                              const SkM44& original_ctm) {
-  auto* op = static_cast<const DrawRRectOp*>(base_op);
-  PaintOpWriter helper(memory, size, options);
-  if (!flags_to_serialize)
-    flags_to_serialize = &op->flags;
-  helper.Write(*flags_to_serialize, current_ctm);
-  helper.Write(op->rrect);
-  return helper.size();
-}
-
-namespace {
-
-template <typename T>
-void SerializeSkottieMap(
-    const base::flat_map<SkottieResourceIdHash, T>& map,
-    PaintOpWriter& helper,
-    const base::RepeatingCallback<void(const T&, PaintOpWriter&)>&
-        value_serializer) {
-  // Write the size of the map first so that we know how many entries to read
-  // from the buffer during deserialization.
-  helper.WriteSize(map.size());
-  for (const auto& [resource_id, val] : map) {
-    helper.WriteSize(resource_id.GetUnsafeValue());
-    value_serializer.Run(val, helper);
-  }
-}
-
-void SerializeSkottieFrameData(const SkM44& current_ctm,
-                               const SkottieFrameData& frame_data,
-                               PaintOpWriter& helper) {
-  // |scale_adjustment| is not ultimately used; Skottie handles image
-  // scale adjustment internally when rastering.
-  SkSize scale_adjustment = SkSize::MakeEmpty();
-  DrawImage draw_image;
-  if (frame_data.image) {
-    draw_image = DrawImage(
-        frame_data.image, /*use_dark_mode=*/false,
-        SkIRect::MakeWH(frame_data.image.width(), frame_data.image.height()),
-        frame_data.quality, current_ctm);
-  }
-  helper.Write(draw_image, &scale_adjustment);
-  helper.Write(frame_data.quality);
-}
-
-}  // namespace
-
-size_t DrawSkottieOp::Serialize(const PaintOp* base_op,
-                                void* memory,
-                                size_t size,
-                                const SerializeOptions& options,
-                                const PaintFlags* flags_to_serialize,
-                                const SkM44& current_ctm,
-                                const SkM44& original_ctm) {
-  auto* op = static_cast<const DrawSkottieOp*>(base_op);
-  PaintOpWriter helper(memory, size, options);
-  helper.Write(op->dst);
-  helper.Write(SkFloatToScalar(op->t));
-  helper.Write(op->skottie);
-
-  SkottieFrameDataMap images_to_serialize = op->images;
-  SkottieTextPropertyValueMap text_map_to_serialize = op->text_map;
-  if (options.skottie_serialization_history) {
-    options.skottie_serialization_history->FilterNewSkottieFrameState(
-        *op->skottie, images_to_serialize, text_map_to_serialize);
-  }
-
-  SerializeSkottieMap(
-      images_to_serialize, helper,
-      base::BindRepeating(&SerializeSkottieFrameData, std::cref(current_ctm)));
-  SerializeSkottieMap(
-      op->color_map, helper,
-      base::BindRepeating([](const SkColor& color, PaintOpWriter& helper) {
-        helper.Write(color);
-      }));
-  SerializeSkottieMap(
-      text_map_to_serialize, helper,
-      base::BindRepeating([](const SkottieTextPropertyValue& text_property_val,
-                             PaintOpWriter& helper) {
-        helper.WriteSize(text_property_val.text().size());
-        // If there is not enough space in the underlying buffer, WriteData()
-        // will mark the |helper| as invalid and the buffer will keep growing
-        // until a max size is reached (currently 64MB which should be ample for
-        // text).
-        helper.WriteData(text_property_val.text().size(),
-                         text_property_val.text().c_str());
-        helper.Write(gfx::RectFToSkRect(text_property_val.box()));
-      }));
-  return helper.size();
-}
-
-size_t DrawTextBlobOp::Serialize(const PaintOp* base_op,
-                                 void* memory,
-                                 size_t size,
-                                 const SerializeOptions& options,
-                                 const PaintFlags* flags_to_serialize,
-                                 const SkM44& current_ctm,
-                                 const SkM44& original_ctm) {
-  auto* op = static_cast<const DrawTextBlobOp*>(base_op);
-  PaintOpWriter helper(memory, size, options);
-  if (!flags_to_serialize)
-    flags_to_serialize = &op->flags;
-  helper.Write(*flags_to_serialize, current_ctm);
-  unsigned int count = op->extra_slugs.size() + 1;
-  helper.Write(count);
-  helper.Write(op->slug);
-  for (const auto& slug : op->extra_slugs) {
-    helper.Write(slug);
-  }
-  return helper.size();
-}
-
-size_t NoopOp::Serialize(const PaintOp* base_op,
-                         void* memory,
-                         size_t size,
-                         const SerializeOptions& options,
-                         const PaintFlags* flags_to_serialize,
-                         const SkM44& current_ctm,
-                         const SkM44& original_ctm) {
-  PaintOpWriter helper(memory, size, options);
-  return helper.size();
-}
-
-size_t RestoreOp::Serialize(const PaintOp* base_op,
-                            void* memory,
-                            size_t size,
-                            const SerializeOptions& options,
-                            const PaintFlags* flags_to_serialize,
-                            const SkM44& current_ctm,
-                            const SkM44& original_ctm) {
-  PaintOpWriter helper(memory, size, options);
-  return helper.size();
-}
-
-size_t RotateOp::Serialize(const PaintOp* base_op,
-                           void* memory,
-                           size_t size,
-                           const SerializeOptions& options,
-                           const PaintFlags* flags_to_serialize,
-                           const SkM44& current_ctm,
-                           const SkM44& original_ctm) {
-  auto* op = static_cast<const RotateOp*>(base_op);
-  PaintOpWriter helper(memory, size, options);
-  helper.Write(op->degrees);
-  return helper.size();
-}
-
-size_t SaveOp::Serialize(const PaintOp* base_op,
-                         void* memory,
-                         size_t size,
-                         const SerializeOptions& options,
-                         const PaintFlags* flags_to_serialize,
-                         const SkM44& current_ctm,
-                         const SkM44& original_ctm) {
-  PaintOpWriter helper(memory, size, options);
-  return helper.size();
-}
-
-size_t SaveLayerOp::Serialize(const PaintOp* base_op,
-                              void* memory,
-                              size_t size,
-                              const SerializeOptions& options,
-                              const PaintFlags* flags_to_serialize,
-                              const SkM44& current_ctm,
-                              const SkM44& original_ctm) {
-  auto* op = static_cast<const SaveLayerOp*>(base_op);
-  PaintOpWriter helper(memory, size, options);
-  if (!flags_to_serialize)
-    flags_to_serialize = &op->flags;
-  helper.Write(*flags_to_serialize, current_ctm);
-  helper.Write(op->bounds);
-  return helper.size();
-}
-
-size_t SaveLayerAlphaOp::Serialize(const PaintOp* base_op,
-                                   void* memory,
-                                   size_t size,
-                                   const SerializeOptions& options,
-                                   const PaintFlags* flags_to_serialize,
-                                   const SkM44& current_ctm,
-                                   const SkM44& original_ctm) {
-  auto* op = static_cast<const SaveLayerAlphaOp*>(base_op);
-  PaintOpWriter helper(memory, size, options);
-  helper.Write(op->bounds);
-  helper.Write(op->alpha);
-  return helper.size();
-}
-
-size_t ScaleOp::Serialize(const PaintOp* base_op,
-                          void* memory,
-                          size_t size,
-                          const SerializeOptions& options,
-                          const PaintFlags* flags_to_serialize,
-                          const SkM44& current_ctm,
-                          const SkM44& original_ctm) {
-  auto* op = static_cast<const ScaleOp*>(base_op);
-  PaintOpWriter helper(memory, size, options);
-  helper.Write(op->sx);
-  helper.Write(op->sy);
-  return helper.size();
-}
-
-size_t SetMatrixOp::Serialize(const PaintOp* base_op,
-                              void* memory,
-                              size_t size,
-                              const SerializeOptions& options,
-                              const PaintFlags* flags_to_serialize,
-                              const SkM44& current_ctm,
-                              const SkM44& original_ctm) {
-  auto* op = static_cast<const SetMatrixOp*>(base_op);
-  PaintOpWriter helper(memory, size, options);
-  // Use original_ctm here because SetMatrixOp replaces current_ctm
-  helper.Write(original_ctm * op->matrix);
-  return helper.size();
-}
-
-size_t SetNodeIdOp::Serialize(const PaintOp* base_op,
-                              void* memory,
-                              size_t size,
-                              const SerializeOptions& options,
-                              const PaintFlags* flags_to_serialize,
-                              const SkM44& current_ctm,
-                              const SkM44& original_ctm) {
-  auto* op = static_cast<const SetNodeIdOp*>(base_op);
-  PaintOpWriter helper(memory, size, options);
-  helper.Write(op->node_id);
-  return helper.size();
-}
-
-size_t TranslateOp::Serialize(const PaintOp* base_op,
-                              void* memory,
-                              size_t size,
-                              const SerializeOptions& options,
-                              const PaintFlags* flags_to_serialize,
-                              const SkM44& current_ctm,
-                              const SkM44& original_ctm) {
-  auto* op = static_cast<const TranslateOp*>(base_op);
-  PaintOpWriter helper(memory, size, options);
-  helper.Write(op->dx);
-  helper.Write(op->dy);
-  return helper.size();
-}
-
-template <typename T>
-void UpdateTypeAndSkip(T* op) {
-  op->type = static_cast<uint8_t>(T::kType);
-  op->skip = PaintOpBuffer::ComputeOpSkip(sizeof(T));
-}
-
-template <typename T>
-class PaintOpDeserializer {
- public:
-  static_assert(std::is_base_of<PaintOp, T>::value, "T not a PaintOp.");
-
-  explicit PaintOpDeserializer(const volatile void* input,
-                               size_t input_size,
-                               const PaintOp::DeserializeOptions& options,
-                               T* op)
-      : reader_(input, input_size, options), op_(op) {
-    DCHECK(op_);
-  }
-  PaintOpDeserializer(const PaintOpDeserializer&) = delete;
-  PaintOpDeserializer& operator=(const PaintOpDeserializer&) = delete;
-
-  ~PaintOpDeserializer() {
-    DCHECK(!op_)
-        << "FinalizeOp must be called before PaintOpDeserializer is destroyed. "
-           "type="
-        << T::kType;
-  }
-
-  PaintOp* FinalizeOp(bool force_invalid = false) {
-    DCHECK(op_) << "PaintOp has already been finalized. type=" << T::kType;
-
-    if (force_invalid || !reader_.valid() || !op_->IsValid()) {
-      op_->~T();
-      op_ = nullptr;
-      return nullptr;
-    }
-
-    UpdateTypeAndSkip(op_.get());
-    T* op_snapshot = op_;
-    op_ = nullptr;
-    return op_snapshot;
-  }
-
-  PaintOp* InvalidateAndFinalizeOp() {
-    return FinalizeOp(/*force_invalid=*/true);
-  }
-
-  T* operator->() { return op_; }
-
-  template <typename... Args>
-  void Read(Args&&... args) {
-    reader_.Read(std::forward<Args>(args)...);
-  }
-
-  void ReadData(size_t bytes, void* data) { reader_.ReadData(bytes, data); }
-
-  void ReadSize(size_t* size) { reader_.ReadSize(size); }
-
-  void AssertAlignment(size_t alignment) { reader_.AssertAlignment(alignment); }
-
- private:
-  PaintOpReader reader_;
-  raw_ptr<T> op_;
-};
-
-PaintOp* AnnotateOp::Deserialize(const volatile void* input,
-                                 size_t input_size,
-                                 void* output,
-                                 size_t output_size,
-                                 const DeserializeOptions& options) {
-  DCHECK_GE(output_size, sizeof(AnnotateOp));
-  PaintOpDeserializer<AnnotateOp> deserializer(input, input_size, options,
-                                               new (output) AnnotateOp);
-
-  deserializer.Read(&deserializer->annotation_type);
-  deserializer.Read(&deserializer->rect);
-  deserializer.Read(&deserializer->data);
-  return deserializer.FinalizeOp();
-}
-
-PaintOp* ClipPathOp::Deserialize(const volatile void* input,
-                                 size_t input_size,
-                                 void* output,
-                                 size_t output_size,
-                                 const DeserializeOptions& options) {
-  DCHECK_GE(output_size, sizeof(ClipPathOp));
-  PaintOpDeserializer<ClipPathOp> deserializer(input, input_size, options,
-                                               new (output) ClipPathOp);
-
-  deserializer.Read(&deserializer->path);
-  deserializer.Read(&deserializer->op);
-  deserializer.Read(&deserializer->antialias);
-  return deserializer.FinalizeOp();
-}
-
-PaintOp* ClipRectOp::Deserialize(const volatile void* input,
-                                 size_t input_size,
-                                 void* output,
-                                 size_t output_size,
-                                 const DeserializeOptions& options) {
-  DCHECK_GE(output_size, sizeof(ClipRectOp));
-  PaintOpDeserializer<ClipRectOp> deserializer(input, input_size, options,
-                                               new (output) ClipRectOp);
-  deserializer.Read(&deserializer->rect);
-  deserializer.Read(&deserializer->op);
-  deserializer.Read(&deserializer->antialias);
-  return deserializer.FinalizeOp();
-}
-
-PaintOp* ClipRRectOp::Deserialize(const volatile void* input,
-                                  size_t input_size,
-                                  void* output,
-                                  size_t output_size,
-                                  const DeserializeOptions& options) {
-  DCHECK_GE(output_size, sizeof(ClipRRectOp));
-  PaintOpDeserializer<ClipRRectOp> deserializer(input, input_size, options,
-                                                new (output) ClipRRectOp);
-  deserializer.Read(&deserializer->rrect);
-  deserializer.Read(&deserializer->op);
-  deserializer.Read(&deserializer->antialias);
-  return deserializer.FinalizeOp();
-}
-
-PaintOp* ConcatOp::Deserialize(const volatile void* input,
-                               size_t input_size,
-                               void* output,
-                               size_t output_size,
-                               const DeserializeOptions& options) {
-  DCHECK_GE(output_size, sizeof(ConcatOp));
-  PaintOpDeserializer<ConcatOp> deserializer(input, input_size, options,
-                                             new (output) ConcatOp);
-  deserializer.Read(&deserializer->matrix);
-  return deserializer.FinalizeOp();
-}
-
-PaintOp* CustomDataOp::Deserialize(const volatile void* input,
-                                   size_t input_size,
-                                   void* output,
-                                   size_t output_size,
-                                   const DeserializeOptions& options) {
-  DCHECK_GE(output_size, sizeof(CustomDataOp));
-  PaintOpDeserializer<CustomDataOp> deserializer(input, input_size, options,
-                                                 new (output) CustomDataOp);
-  deserializer.Read(&deserializer->id);
-  return deserializer.FinalizeOp();
-}
-
-PaintOp* DrawColorOp::Deserialize(const volatile void* input,
-                                  size_t input_size,
-                                  void* output,
-                                  size_t output_size,
-                                  const DeserializeOptions& options) {
-  DCHECK_GE(output_size, sizeof(DrawColorOp));
-  PaintOpDeserializer<DrawColorOp> deserializer(input, input_size, options,
-                                                new (output) DrawColorOp);
-  deserializer.Read(&deserializer->color);
-  deserializer.Read(&deserializer->mode);
-  return deserializer.FinalizeOp();
-}
-
-PaintOp* DrawDRRectOp::Deserialize(const volatile void* input,
-                                   size_t input_size,
-                                   void* output,
-                                   size_t output_size,
-                                   const DeserializeOptions& options) {
-  DCHECK_GE(output_size, sizeof(DrawDRRectOp));
-  PaintOpDeserializer<DrawDRRectOp> deserializer(input, input_size, options,
-                                                 new (output) DrawDRRectOp);
-  deserializer.Read(&deserializer->flags);
-  deserializer.Read(&deserializer->outer);
-  deserializer.Read(&deserializer->inner);
-  return deserializer.FinalizeOp();
-}
-
-PaintOp* DrawImageOp::Deserialize(const volatile void* input,
-                                  size_t input_size,
-                                  void* output,
-                                  size_t output_size,
-                                  const DeserializeOptions& options) {
-  DCHECK_GE(output_size, sizeof(DrawImageOp));
-  PaintOpDeserializer<DrawImageOp> deserializer(input, input_size, options,
-                                                new (output) DrawImageOp);
-  deserializer.Read(&deserializer->flags);
-
-  deserializer.Read(&deserializer->image);
-  deserializer.AssertAlignment(alignof(SkScalar));
-  deserializer.Read(&deserializer->scale_adjustment.fWidth);
-  deserializer.Read(&deserializer->scale_adjustment.fHeight);
-
-  deserializer.Read(&deserializer->left);
-  deserializer.Read(&deserializer->top);
-  deserializer.Read(&deserializer->sampling);
-  return deserializer.FinalizeOp();
-}
-
-PaintOp* DrawImageRectOp::Deserialize(const volatile void* input,
-                                      size_t input_size,
-                                      void* output,
-                                      size_t output_size,
-                                      const DeserializeOptions& options) {
-  DCHECK_GE(output_size, sizeof(DrawImageRectOp));
-  PaintOpDeserializer<DrawImageRectOp> deserializer(
-      input, input_size, options, new (output) DrawImageRectOp);
-  deserializer.Read(&deserializer->flags);
-
-  deserializer.Read(&deserializer->image);
-  deserializer.AssertAlignment(alignof(SkScalar));
-  deserializer.Read(&deserializer->scale_adjustment.fWidth);
-  deserializer.Read(&deserializer->scale_adjustment.fHeight);
-
-  deserializer.Read(&deserializer->src);
-  deserializer.Read(&deserializer->dst);
-  deserializer.Read(&deserializer->sampling);
-  deserializer.Read(&deserializer->constraint);
-  return deserializer.FinalizeOp();
-}
-
-PaintOp* DrawIRectOp::Deserialize(const volatile void* input,
-                                  size_t input_size,
-                                  void* output,
-                                  size_t output_size,
-                                  const DeserializeOptions& options) {
-  DCHECK_GE(output_size, sizeof(DrawIRectOp));
-  PaintOpDeserializer<DrawIRectOp> deserializer(input, input_size, options,
-                                                new (output) DrawIRectOp);
-  deserializer.Read(&deserializer->flags);
-  deserializer.Read(&deserializer->rect);
-  return deserializer.FinalizeOp();
-}
-
-PaintOp* DrawLineOp::Deserialize(const volatile void* input,
-                                 size_t input_size,
-                                 void* output,
-                                 size_t output_size,
-                                 const DeserializeOptions& options) {
-  DCHECK_GE(output_size, sizeof(DrawLineOp));
-  PaintOpDeserializer<DrawLineOp> deserializer(input, input_size, options,
-                                               new (output) DrawLineOp);
-  deserializer.Read(&deserializer->flags);
-  deserializer.AssertAlignment(alignof(SkScalar));
-  deserializer.Read(&deserializer->x0);
-  deserializer.Read(&deserializer->y0);
-  deserializer.Read(&deserializer->x1);
-  deserializer.Read(&deserializer->y1);
-  return deserializer.FinalizeOp();
-}
-
-PaintOp* DrawOvalOp::Deserialize(const volatile void* input,
-                                 size_t input_size,
-                                 void* output,
-                                 size_t output_size,
-                                 const DeserializeOptions& options) {
-  DCHECK_GE(output_size, sizeof(DrawOvalOp));
-  PaintOpDeserializer<DrawOvalOp> deserializer(input, input_size, options,
-                                               new (output) DrawOvalOp);
-  deserializer.Read(&deserializer->flags);
-  deserializer.Read(&deserializer->oval);
-  return deserializer.FinalizeOp();
-}
-
-PaintOp* DrawPathOp::Deserialize(const volatile void* input,
-                                 size_t input_size,
-                                 void* output,
-                                 size_t output_size,
-                                 const DeserializeOptions& options) {
-  DCHECK_GE(output_size, sizeof(DrawPathOp));
-  PaintOpDeserializer<DrawPathOp> deserializer(input, input_size, options,
-                                               new (output) DrawPathOp);
-  deserializer.Read(&deserializer->flags);
-  deserializer.Read(&deserializer->path);
-  deserializer.Read(&deserializer->sk_path_fill_type);
-  deserializer->path.setFillType(
-      static_cast<SkPathFillType>(deserializer->sk_path_fill_type));
-  return deserializer.FinalizeOp();
-}
-
-PaintOp* DrawRecordOp::Deserialize(const volatile void* input,
-                                   size_t input_size,
-                                   void* output,
-                                   size_t output_size,
-                                   const DeserializeOptions& options) {
-  // TODO(enne): these must be flattened and not sent directly.
-  // TODO(enne): could also consider caching these service side.
-  return nullptr;
-}
-
-PaintOp* DrawRectOp::Deserialize(const volatile void* input,
-                                 size_t input_size,
-                                 void* output,
-                                 size_t output_size,
-                                 const DeserializeOptions& options) {
-  DCHECK_GE(output_size, sizeof(DrawRectOp));
-  PaintOpDeserializer<DrawRectOp> deserializer(input, input_size, options,
-                                               new (output) DrawRectOp);
-  deserializer.Read(&deserializer->flags);
-  deserializer.Read(&deserializer->rect);
-  return deserializer.FinalizeOp();
-}
-
-PaintOp* DrawRRectOp::Deserialize(const volatile void* input,
-                                  size_t input_size,
-                                  void* output,
-                                  size_t output_size,
-                                  const DeserializeOptions& options) {
-  DCHECK_GE(output_size, sizeof(DrawRRectOp));
-  PaintOpDeserializer<DrawRRectOp> deserializer(input, input_size, options,
-                                                new (output) DrawRRectOp);
-  deserializer.Read(&deserializer->flags);
-  deserializer.Read(&deserializer->rrect);
-  return deserializer.FinalizeOp();
-}
-
-namespace {
-
-// |max_map_size| is purely a safety mechanism to prevent disastrous behavior
-// (trying to allocate an enormous map, looping for long periods of time, etc)
-// in case the serialization buffer is corrupted somehow.
-template <typename T>
-bool DeserializeSkottieMap(
-    base::flat_map<SkottieResourceIdHash, T>& map,
-    absl::optional<size_t> max_map_size,
-    PaintOpDeserializer<DrawSkottieOp>& deserializer,
-    const base::RepeatingCallback<absl::optional<T>(
-        PaintOpDeserializer<DrawSkottieOp>&)>& value_deserializer) {
-  size_t map_size = 0;
-  deserializer.ReadSize(&map_size);
-  if (max_map_size && map_size > *max_map_size)
-    return false;
-
-  for (size_t i = 0; i < map_size; ++i) {
-    size_t resource_id_hash_raw = 0;
-    deserializer.ReadSize(&resource_id_hash_raw);
-    SkottieResourceIdHash resource_id_hash =
-        SkottieResourceIdHash::FromUnsafeValue(resource_id_hash_raw);
-    if (!resource_id_hash)
-      return false;
-
-    absl::optional<T> value = value_deserializer.Run(deserializer);
-    if (!value)
-      return false;
-
-    // Duplicate keys should not happen by design, but defend against it
-    // gracefully in case the underlying buffer is corrupted.
-    bool is_new_entry = map.emplace(resource_id_hash, std::move(*value)).second;
-    if (!is_new_entry)
-      return false;
-  }
-  return true;
-}
-
-absl::optional<SkottieFrameData> DeserializeSkottieFrameData(
-    PaintOpDeserializer<DrawSkottieOp>& deserializer) {
-  SkottieFrameData frame_data;
-  deserializer.Read(&frame_data.image);
-  deserializer.Read(&frame_data.quality);
-  return frame_data;
-}
-
-absl::optional<SkColor> DeserializeSkottieColor(
-    PaintOpDeserializer<DrawSkottieOp>& deserializer) {
-  SkColor color = SK_ColorTRANSPARENT;
-  deserializer.Read(&color);
-  return color;
-}
-
-absl::optional<SkottieTextPropertyValue> DeserializeSkottieTextPropertyValue(
-    PaintOpDeserializer<DrawSkottieOp>& deserializer) {
-  size_t text_size = 0u;
-  deserializer.ReadSize(&text_size);
-  std::string text(text_size, char());
-  deserializer.ReadData(text_size, const_cast<char*>(text.c_str()));
-  SkRect box;
-  deserializer.Read(&box);
-  return SkottieTextPropertyValue(std::move(text), gfx::SkRectToRectF(box));
-}
-
-}  // namespace
-
-PaintOp* DrawSkottieOp::Deserialize(const volatile void* input,
-                                    size_t input_size,
-                                    void* output,
-                                    size_t output_size,
-                                    const DeserializeOptions& options) {
-  DCHECK_GE(output_size, sizeof(DrawSkottieOp));
-  PaintOpDeserializer<DrawSkottieOp> deserializer(input, input_size, options,
-                                                  new (output) DrawSkottieOp);
-  deserializer.Read(&deserializer->dst);
-
-  SkScalar t;
-  deserializer.Read(&t);
-  deserializer->t = SkScalarToFloat(t);
-
-  deserializer.Read(&deserializer->skottie);
-  // The |skottie| object gets used below, so no point in continuing if it's
-  // invalid. That can lead to crashing or unexpected behavior.
-  if (!deserializer->skottie || !deserializer->skottie->is_valid())
-    return deserializer.InvalidateAndFinalizeOp();
-
-  size_t num_assets_in_animation =
-      deserializer->skottie->GetImageAssetMetadata().asset_storage().size();
-  size_t num_text_nodes_in_animation =
-      deserializer->skottie->GetTextNodeNames().size();
-  bool deserialized_all_maps =
-      DeserializeSkottieMap(
-          deserializer->images, /*max_map_size=*/num_assets_in_animation,
-          deserializer, base::BindRepeating(&DeserializeSkottieFrameData)) &&
-      DeserializeSkottieMap(deserializer->color_map,
-                            /*max_map_size=*/absl::nullopt, deserializer,
-                            base::BindRepeating(&DeserializeSkottieColor)) &&
-      DeserializeSkottieMap(
-          deserializer->text_map, /*max_map_size=*/num_text_nodes_in_animation,
-          deserializer,
-          base::BindRepeating(&DeserializeSkottieTextPropertyValue));
-  return deserialized_all_maps ? deserializer.FinalizeOp()
-                               : deserializer.InvalidateAndFinalizeOp();
-}
-
-PaintOp* DrawTextBlobOp::Deserialize(const volatile void* input,
-                                     size_t input_size,
-                                     void* output,
-                                     size_t output_size,
-                                     const DeserializeOptions& options) {
-  DCHECK_GE(output_size, sizeof(DrawTextBlobOp));
-  PaintOpDeserializer<DrawTextBlobOp> deserializer(input, input_size, options,
-                                                   new (output) DrawTextBlobOp);
-  deserializer.Read(&deserializer->flags);
-  unsigned int count = 0;
-  deserializer.Read(&count);
-  deserializer.Read(&deserializer->slug);
-  deserializer->extra_slugs.resize(count - 1);
-  for (auto& slug : deserializer->extra_slugs) {
-    deserializer.Read(&slug);
-  }
-  return deserializer.FinalizeOp();
-}
-
-PaintOp* NoopOp::Deserialize(const volatile void* input,
-                             size_t input_size,
-                             void* output,
-                             size_t output_size,
-                             const DeserializeOptions& options) {
-  DCHECK_GE(output_size, sizeof(NoopOp));
-  PaintOpDeserializer<NoopOp> deserializer(input, input_size, options,
-                                           new (output) NoopOp);
-  return deserializer.FinalizeOp();
-}
-
-PaintOp* RestoreOp::Deserialize(const volatile void* input,
-                                size_t input_size,
-                                void* output,
-                                size_t output_size,
-                                const DeserializeOptions& options) {
-  DCHECK_GE(output_size, sizeof(RestoreOp));
-  PaintOpDeserializer<RestoreOp> deserializer(input, input_size, options,
-                                              new (output) RestoreOp);
-  return deserializer.FinalizeOp();
-}
-
-PaintOp* RotateOp::Deserialize(const volatile void* input,
-                               size_t input_size,
-                               void* output,
-                               size_t output_size,
-                               const DeserializeOptions& options) {
-  DCHECK_GE(output_size, sizeof(RotateOp));
-  PaintOpDeserializer<RotateOp> deserializer(input, input_size, options,
-                                             new (output) RotateOp);
-  deserializer.Read(&deserializer->degrees);
-  return deserializer.FinalizeOp();
-}
-
-PaintOp* SaveOp::Deserialize(const volatile void* input,
-                             size_t input_size,
-                             void* output,
-                             size_t output_size,
-                             const DeserializeOptions& options) {
-  DCHECK_GE(output_size, sizeof(SaveOp));
-  PaintOpDeserializer<SaveOp> deserializer(input, input_size, options,
-                                           new (output) SaveOp);
-  return deserializer.FinalizeOp();
-}
-
-PaintOp* SaveLayerOp::Deserialize(const volatile void* input,
-                                  size_t input_size,
-                                  void* output,
-                                  size_t output_size,
-                                  const DeserializeOptions& options) {
-  DCHECK_GE(output_size, sizeof(SaveLayerOp));
-  PaintOpDeserializer<SaveLayerOp> deserializer(input, input_size, options,
-                                                new (output) SaveLayerOp);
-  deserializer.Read(&deserializer->flags);
-  deserializer.Read(&deserializer->bounds);
-  return deserializer.FinalizeOp();
-}
-
-PaintOp* SaveLayerAlphaOp::Deserialize(const volatile void* input,
-                                       size_t input_size,
-                                       void* output,
-                                       size_t output_size,
-                                       const DeserializeOptions& options) {
-  DCHECK_GE(output_size, sizeof(SaveLayerAlphaOp));
-  PaintOpDeserializer<SaveLayerAlphaOp> deserializer(
-      input, input_size, options, new (output) SaveLayerAlphaOp);
-  deserializer.Read(&deserializer->bounds);
-  deserializer.Read(&deserializer->alpha);
-  return deserializer.FinalizeOp();
-}
-
-PaintOp* ScaleOp::Deserialize(const volatile void* input,
-                              size_t input_size,
-                              void* output,
-                              size_t output_size,
-                              const DeserializeOptions& options) {
-  DCHECK_GE(output_size, sizeof(ScaleOp));
-  PaintOpDeserializer<ScaleOp> deserializer(input, input_size, options,
-                                            new (output) ScaleOp);
-  deserializer.Read(&deserializer->sx);
-  deserializer.Read(&deserializer->sy);
-  return deserializer.FinalizeOp();
-}
-
-PaintOp* SetMatrixOp::Deserialize(const volatile void* input,
-                                  size_t input_size,
-                                  void* output,
-                                  size_t output_size,
-                                  const DeserializeOptions& options) {
-  DCHECK_GE(output_size, sizeof(SetMatrixOp));
-  PaintOpDeserializer<SetMatrixOp> deserializer(input, input_size, options,
-                                                new (output) SetMatrixOp);
-  deserializer.Read(&deserializer->matrix);
-  return deserializer.FinalizeOp();
-}
-
-PaintOp* SetNodeIdOp::Deserialize(const volatile void* input,
-                                  size_t input_size,
-                                  void* output,
-                                  size_t output_size,
-                                  const DeserializeOptions& options) {
-  DCHECK_GE(output_size, sizeof(SetNodeIdOp));
-  PaintOpDeserializer<SetNodeIdOp> deserializer(input, input_size, options,
-                                                new (output) SetNodeIdOp);
-  deserializer.Read(&deserializer->node_id);
-  return deserializer.FinalizeOp();
-}
-
-PaintOp* TranslateOp::Deserialize(const volatile void* input,
-                                  size_t input_size,
-                                  void* output,
-                                  size_t output_size,
-                                  const DeserializeOptions& options) {
-  DCHECK_GE(output_size, sizeof(TranslateOp));
-  PaintOpDeserializer<TranslateOp> deserializer(input, input_size, options,
-                                                new (output) TranslateOp);
-  deserializer.Read(&deserializer->dx);
-  deserializer.Read(&deserializer->dy);
-  return deserializer.FinalizeOp();
-}
-
-void AnnotateOp::Raster(const AnnotateOp* op,
-                        SkCanvas* canvas,
-                        const PlaybackParams& params) {
-  switch (op->annotation_type) {
-    case PaintCanvas::AnnotationType::URL:
-      SkAnnotateRectWithURL(canvas, op->rect, op->data.get());
-      break;
-    case PaintCanvas::AnnotationType::LINK_TO_DESTINATION:
-      SkAnnotateLinkToDestination(canvas, op->rect, op->data.get());
-      break;
-    case PaintCanvas::AnnotationType::NAMED_DESTINATION: {
-      SkPoint point = SkPoint::Make(op->rect.x(), op->rect.y());
-      SkAnnotateNamedDestination(canvas, point, op->data.get());
-      break;
-    }
-  }
-}
-
-void ClipPathOp::Raster(const ClipPathOp* op,
-                        SkCanvas* canvas,
-                        const PlaybackParams& params) {
-  canvas->clipPath(op->path, op->op, op->antialias);
-}
-
-void ClipRectOp::Raster(const ClipRectOp* op,
-                        SkCanvas* canvas,
-                        const PlaybackParams& params) {
-  canvas->clipRect(op->rect, op->op, op->antialias);
-}
-
-void ClipRRectOp::Raster(const ClipRRectOp* op,
-                         SkCanvas* canvas,
-                         const PlaybackParams& params) {
-  canvas->clipRRect(op->rrect, op->op, op->antialias);
-}
-
-void ConcatOp::Raster(const ConcatOp* op,
-                        SkCanvas* canvas,
-                        const PlaybackParams& params) {
-  canvas->concat(op->matrix);
-}
-
-void CustomDataOp::Raster(const CustomDataOp* op,
-                          SkCanvas* canvas,
-                          const PlaybackParams& params) {
-  if (params.custom_callback)
-    params.custom_callback.Run(canvas, op->id);
-}
-
-void DrawColorOp::Raster(const DrawColorOp* op,
-                         SkCanvas* canvas,
-                         const PlaybackParams& params) {
-  canvas->drawColor(op->color, op->mode);
-}
-
-void DrawDRRectOp::RasterWithFlags(const DrawDRRectOp* op,
-                                   const PaintFlags* flags,
-                                   SkCanvas* canvas,
-                                   const PlaybackParams& params) {
-  flags->DrawToSk(canvas, [op](SkCanvas* c, const SkPaint& p) {
-    c->drawDRRect(op->outer, op->inner, p);
-  });
-}
-
-void DrawImageOp::RasterWithFlags(const DrawImageOp* op,
-                                  const PaintFlags* flags,
-                                  SkCanvas* canvas,
-                                  const PlaybackParams& params) {
-  DCHECK(!op->image.IsPaintWorklet());
-  SkPaint paint = flags ? flags->ToSkPaint() : SkPaint();
-
-  if (!params.image_provider) {
-    const bool needs_scale = !IsScaleAdjustmentIdentity(op->scale_adjustment);
-    SkAutoCanvasRestore save_restore(canvas, needs_scale);
-    if (needs_scale) {
-      canvas->scale(1.f / op->scale_adjustment.width(),
-                    1.f / op->scale_adjustment.height());
-    }
-    sk_sp<SkImage> sk_image;
-    if (op->image.IsTextureBacked()) {
-      sk_image = op->image.GetAcceleratedSkImage();
-      DCHECK(sk_image || !canvas->recordingContext());
-    }
-    if (!sk_image)
-      sk_image = op->image.GetSwSkImage();
-
-    canvas->drawImage(sk_image.get(), op->left, op->top, op->sampling, &paint);
-    return;
-  }
-
-  // Dark mode is applied only for OOP raster during serialization.
-  DrawImage draw_image(
-      op->image, false, SkIRect::MakeWH(op->image.width(), op->image.height()),
-      sampling_to_quality(op->sampling), canvas->getLocalToDevice());
-  auto scoped_result = params.image_provider->GetRasterContent(draw_image);
-  if (!scoped_result)
-    return;
-
-  const auto& decoded_image = scoped_result.decoded_image();
-  DCHECK(decoded_image.image());
-
-  DCHECK_EQ(0, static_cast<int>(decoded_image.src_rect_offset().width()));
-  DCHECK_EQ(0, static_cast<int>(decoded_image.src_rect_offset().height()));
-  SkSize scale_adjustment = SkSize::Make(
-      op->scale_adjustment.width() * decoded_image.scale_adjustment().width(),
-      op->scale_adjustment.height() *
-          decoded_image.scale_adjustment().height());
-  const bool needs_scale = !IsScaleAdjustmentIdentity(scale_adjustment);
-  SkAutoCanvasRestore save_restore(canvas, needs_scale);
-  if (needs_scale) {
-    canvas->scale(1.f / scale_adjustment.width(),
-                  1.f / scale_adjustment.height());
-  }
-  canvas->drawImage(decoded_image.image().get(), op->left, op->top,
-                    PaintFlags::FilterQualityToSkSamplingOptions(
-                        decoded_image.filter_quality()),
-                    &paint);
-}
-
-void DrawImageRectOp::RasterWithFlags(const DrawImageRectOp* op,
-                                      const PaintFlags* flags,
-                                      SkCanvas* canvas,
-                                      const PlaybackParams& params) {
-  // TODO(crbug.com/931704): make sure to support the case where paint worklet
-  // generated images are used in other raster work such as canvas2d.
-  if (op->image.IsPaintWorklet()) {
-    // When rasterizing on the main thread (e.g. paint invalidation checking,
-    // see https://crbug.com/990382), an image provider may not be available, so
-    // we should draw nothing.
-    if (!params.image_provider)
-      return;
-    ImageProvider::ScopedResult result =
-        params.image_provider->GetRasterContent(DrawImage(op->image));
-
-    // Check that we are not using loopers with paint worklets, since converting
-    // PaintFlags to SkPaint drops loopers.
-    DCHECK(!flags->getLooper());
-    SkPaint paint = flags ? flags->ToSkPaint() : SkPaint();
-
-    DCHECK(IsScaleAdjustmentIdentity(op->scale_adjustment));
-    SkAutoCanvasRestore save_restore(canvas, true);
-    canvas->concat(SkMatrix::RectToRect(op->src, op->dst));
-    canvas->clipRect(op->src);
-    canvas->saveLayer(&op->src, &paint);
-    // Compositor thread animations can cause PaintWorklet jobs to be dispatched
-    // to the worklet thread even after main has torn down the worklet (e.g.
-    // because a navigation is happening). In that case the PaintWorklet jobs
-    // will fail and there will be no result to raster here. This state is
-    // transient as the next main frame commit will remove the PaintWorklets.
-    if (result && result.paint_record())
-      result.paint_record()->Playback(canvas, params);
-    return;
-  }
-
-  if (!params.image_provider) {
-    SkRect adjusted_src = AdjustSrcRectForScale(op->src, op->scale_adjustment);
-    flags->DrawToSk(canvas, [op, adjusted_src](SkCanvas* c, const SkPaint& p) {
-      sk_sp<SkImage> sk_image;
-      if (op->image.IsTextureBacked()) {
-        sk_image = op->image.GetAcceleratedSkImage();
-        DCHECK(sk_image || !c->recordingContext());
-      }
-      if (!sk_image)
-        sk_image = op->image.GetSwSkImage();
-      DrawImageRect(c, sk_image.get(), adjusted_src, op->dst, op->sampling, &p,
-                    op->constraint);
-    });
-    return;
-  }
-
-  SkM44 matrix = canvas->getLocalToDevice() *
-                 SkM44(SkMatrix::RectToRect(op->src, op->dst));
-
-  SkIRect int_src_rect;
-  op->src.roundOut(&int_src_rect);
-
-  // Dark mode is applied only for OOP raster during serialization.
-  DrawImage draw_image(op->image, false, int_src_rect,
-                       sampling_to_quality(op->sampling), matrix);
-  auto scoped_result = params.image_provider->GetRasterContent(draw_image);
-  if (!scoped_result)
-    return;
-
-  const auto& decoded_image = scoped_result.decoded_image();
-  DCHECK(decoded_image.image());
-
-  SkSize scale_adjustment = SkSize::Make(
-      op->scale_adjustment.width() * decoded_image.scale_adjustment().width(),
-      op->scale_adjustment.height() *
-          decoded_image.scale_adjustment().height());
-  SkRect adjusted_src =
-      op->src.makeOffset(decoded_image.src_rect_offset().width(),
-                         decoded_image.src_rect_offset().height());
-  adjusted_src = AdjustSrcRectForScale(adjusted_src, scale_adjustment);
-  flags->DrawToSk(canvas, [op, &decoded_image, adjusted_src](SkCanvas* c,
-                                                             const SkPaint& p) {
-    SkSamplingOptions options = PaintFlags::FilterQualityToSkSamplingOptions(
-        decoded_image.filter_quality());
-    DrawImageRect(c, decoded_image.image().get(), adjusted_src, op->dst,
-                  options, &p, op->constraint);
-  });
-}
-
-void DrawIRectOp::RasterWithFlags(const DrawIRectOp* op,
-                                  const PaintFlags* flags,
-                                  SkCanvas* canvas,
-                                  const PlaybackParams& params) {
-  flags->DrawToSk(canvas, [op](SkCanvas* c, const SkPaint& p) {
-    c->drawIRect(op->rect, p);
-  });
-}
-
-void DrawLineOp::RasterWithFlags(const DrawLineOp* op,
-                                 const PaintFlags* flags,
-                                 SkCanvas* canvas,
-                                 const PlaybackParams& params) {
-  SkPaint paint = flags->ToSkPaint();
-  flags->DrawToSk(canvas, [op](SkCanvas* c, const SkPaint& p) {
-    c->drawLine(op->x0, op->y0, op->x1, op->y1, p);
-  });
-}
-
-void DrawOvalOp::RasterWithFlags(const DrawOvalOp* op,
-                                 const PaintFlags* flags,
-                                 SkCanvas* canvas,
-                                 const PlaybackParams& params) {
-  flags->DrawToSk(canvas, [op](SkCanvas* c, const SkPaint& p) {
-    c->drawOval(op->oval, p);
-  });
-}
-
-void DrawPathOp::RasterWithFlags(const DrawPathOp* op,
-                                 const PaintFlags* flags,
-                                 SkCanvas* canvas,
-                                 const PlaybackParams& params) {
-  flags->DrawToSk(canvas, [op](SkCanvas* c, const SkPaint& p) {
-    c->drawPath(op->path, p);
-  });
-}
-
-void DrawRecordOp::Raster(const DrawRecordOp* op,
-                          SkCanvas* canvas,
-                          const PlaybackParams& params) {
-  // Don't use drawPicture here, as it adds an implicit clip.
-  // TODO(enne): Temporary CHECK debugging for http://crbug.com/823835
-  CHECK(op->record);
-  op->record->Playback(canvas, params);
-}
-
-void DrawRectOp::RasterWithFlags(const DrawRectOp* op,
-                                 const PaintFlags* flags,
-                                 SkCanvas* canvas,
-                                 const PlaybackParams& params) {
-  flags->DrawToSk(canvas, [op](SkCanvas* c, const SkPaint& p) {
-    c->drawRect(op->rect, p);
-  });
-}
-
-void DrawRRectOp::RasterWithFlags(const DrawRRectOp* op,
-                                  const PaintFlags* flags,
-                                  SkCanvas* canvas,
-                                  const PlaybackParams& params) {
-  flags->DrawToSk(canvas, [op](SkCanvas* c, const SkPaint& p) {
-    c->drawRRect(op->rrect, p);
-  });
-}
-
-void DrawSkottieOp::Raster(const DrawSkottieOp* op,
-                           SkCanvas* canvas,
-                           const PlaybackParams& params) {
-  // Binding unretained references in the callback is safe because Draw()'s API
-  // guarantees that the callback is invoked synchronously.
-  op->skottie->Draw(
-      canvas, op->t, op->dst,
-      base::BindRepeating(&DrawSkottieOp::GetImageAssetForRaster,
-                          base::Unretained(op), canvas, std::cref(params)),
-      op->color_map, op->text_map);
-}
-
-SkottieWrapper::FrameDataFetchResult DrawSkottieOp::GetImageAssetForRaster(
-    SkCanvas* canvas,
-    const PlaybackParams& params,
-    SkottieResourceIdHash asset_id,
-    float t_frame,
-    sk_sp<SkImage>& sk_image,
-    SkSamplingOptions& sampling_out) const {
-  auto images_iter = images.find(asset_id);
-  if (images_iter == images.end())
-    return SkottieWrapper::FrameDataFetchResult::NO_UPDATE;
-
-  const SkottieFrameData& frame_data = images_iter->second;
-  if (!frame_data.image) {
-    sk_image = nullptr;
-  } else if (params.image_provider) {
-    // There is no use case for applying dark mode filters to skottie images
-    // currently.
-    DrawImage draw_image(
-        frame_data.image, /*use_dark_mode=*/false,
-        SkIRect::MakeWH(frame_data.image.width(), frame_data.image.height()),
-        frame_data.quality, canvas->getLocalToDevice());
-    auto scoped_result = params.image_provider->GetRasterContent(draw_image);
-    if (scoped_result) {
-      sk_image = scoped_result.decoded_image().image();
-      DCHECK(sk_image);
-    }
-  } else {
-    if (frame_data.image.IsTextureBacked()) {
-      sk_image = frame_data.image.GetAcceleratedSkImage();
-      DCHECK(sk_image || !canvas->recordingContext());
-    }
-    if (!sk_image)
-      sk_image = frame_data.image.GetSwSkImage();
-  }
-  sampling_out =
-      PaintFlags::FilterQualityToSkSamplingOptions(frame_data.quality);
-  return SkottieWrapper::FrameDataFetchResult::NEW_DATA_AVAILABLE;
-}
-
-void DrawTextBlobOp::RasterWithFlags(const DrawTextBlobOp* op,
-                                     const PaintFlags* flags,
-                                     SkCanvas* canvas,
-                                     const PlaybackParams& params) {
-  if (op->node_id)
-    SkPDF::SetNodeId(canvas, op->node_id);
-
-  // The PaintOpBuffer could be rasterized with different global matrix. It is
-  // used for over scall on Android. So we cannot reuse slugs, they have to be
-  // recreated.
-  if (params.is_analyzing) {
-    const_cast<DrawTextBlobOp*>(op)->slug.reset();
-    const_cast<DrawTextBlobOp*>(op)->extra_slugs.clear();
-  }
-
-  // flags may contain SkDrawLooper for shadow effect, so we need to convert
-  // SkTextBlob to slug for each run.
-  size_t i = 0;
-  flags->DrawToSk(canvas, [op, &params, &i](SkCanvas* c, const SkPaint& p) {
-    if (op->blob) {
-      c->drawTextBlob(op->blob.get(), op->x, op->y, p);
-      if (params.is_analyzing) {
-        auto s = GrSlug::ConvertBlob(c, *op->blob, {op->x, op->y}, p);
-        if (i == 0) {
-          const_cast<DrawTextBlobOp*>(op)->slug = std::move(s);
-        } else {
-          const_cast<DrawTextBlobOp*>(op)->extra_slugs.push_back(std::move(s));
-        }
-      }
-    } else if (i < 1 + op->extra_slugs.size()) {
-      DCHECK(!params.is_analyzing);
-      const auto& draw_slug = i == 0 ? op->slug : op->extra_slugs[i - 1];
-      if (draw_slug)
-        draw_slug->draw(c);
-    }
-    ++i;
-  });
-
-  if (op->node_id)
-    SkPDF::SetNodeId(canvas, 0);
-}
-
-void RestoreOp::Raster(const RestoreOp* op,
-                       SkCanvas* canvas,
-                       const PlaybackParams& params) {
-  canvas->restore();
-}
-
-void RotateOp::Raster(const RotateOp* op,
-                      SkCanvas* canvas,
-                      const PlaybackParams& params) {
-  canvas->rotate(op->degrees);
-}
-
-void SaveOp::Raster(const SaveOp* op,
-                    SkCanvas* canvas,
-                    const PlaybackParams& params) {
-  canvas->save();
-}
-
-void SaveLayerOp::RasterWithFlags(const SaveLayerOp* op,
-                                  const PaintFlags* flags,
-                                  SkCanvas* canvas,
-                                  const PlaybackParams& params) {
-  // See PaintOp::kUnsetRect
-  SkPaint paint = flags->ToSkPaint();
-  bool unset = op->bounds.left() == SK_ScalarInfinity;
-  canvas->saveLayer(unset ? nullptr : &op->bounds, &paint);
-}
-
-void SaveLayerAlphaOp::Raster(const SaveLayerAlphaOp* op,
-                              SkCanvas* canvas,
-                              const PlaybackParams& params) {
-  // See PaintOp::kUnsetRect
-  bool unset = op->bounds.left() == SK_ScalarInfinity;
-  absl::optional<SkPaint> paint;
-  if (op->alpha != 1.0f) {
-    paint.emplace();
-    paint->setAlpha(op->alpha * 255.0f);
-  }
-  SkCanvas::SaveLayerRec rec(unset ? nullptr : &op->bounds,
-                             base::OptionalToPtr(paint));
-  if (params.save_layer_alpha_should_preserve_lcd_text.has_value() &&
-      *params.save_layer_alpha_should_preserve_lcd_text) {
-    rec.fSaveLayerFlags = SkCanvas::kPreserveLCDText_SaveLayerFlag |
-                          SkCanvas::kInitWithPrevious_SaveLayerFlag;
-  }
-  canvas->saveLayer(rec);
-}
-
-void ScaleOp::Raster(const ScaleOp* op,
-                     SkCanvas* canvas,
-                     const PlaybackParams& params) {
-  canvas->scale(op->sx, op->sy);
-}
-
-void SetMatrixOp::Raster(const SetMatrixOp* op,
-                           SkCanvas* canvas,
-                           const PlaybackParams& params) {
-  canvas->setMatrix(params.original_ctm * op->matrix);
-}
-
-void SetNodeIdOp::Raster(const SetNodeIdOp* op,
-                         SkCanvas* canvas,
-                         const PlaybackParams& params) {
-  SkPDF::SetNodeId(canvas, op->node_id);
-}
-
-void TranslateOp::Raster(const TranslateOp* op,
-                         SkCanvas* canvas,
-                         const PlaybackParams& params) {
-  canvas->translate(op->dx, op->dy);
-}
-
-// static
-bool PaintOp::AreSkPointsEqual(const SkPoint& left, const SkPoint& right) {
-  if (!AreEqualEvenIfNaN(left.fX, right.fX))
-    return false;
-  if (!AreEqualEvenIfNaN(left.fY, right.fY))
-    return false;
-  return true;
-}
-
-// static
-bool PaintOp::AreSkPoint3sEqual(const SkPoint3& left, const SkPoint3& right) {
-  if (!AreEqualEvenIfNaN(left.fX, right.fX))
-    return false;
-  if (!AreEqualEvenIfNaN(left.fY, right.fY))
-    return false;
-  if (!AreEqualEvenIfNaN(left.fZ, right.fZ))
-    return false;
-  return true;
-}
-
-// static
-bool PaintOp::AreSkRectsEqual(const SkRect& left, const SkRect& right) {
-  if (!AreEqualEvenIfNaN(left.fLeft, right.fLeft))
-    return false;
-  if (!AreEqualEvenIfNaN(left.fTop, right.fTop))
-    return false;
-  if (!AreEqualEvenIfNaN(left.fRight, right.fRight))
-    return false;
-  if (!AreEqualEvenIfNaN(left.fBottom, right.fBottom))
-    return false;
-  return true;
-}
-
-// static
-bool PaintOp::AreSkRRectsEqual(const SkRRect& left, const SkRRect& right) {
-  char left_buffer[SkRRect::kSizeInMemory];
-  left.writeToMemory(left_buffer);
-  char right_buffer[SkRRect::kSizeInMemory];
-  right.writeToMemory(right_buffer);
-  return !memcmp(left_buffer, right_buffer, SkRRect::kSizeInMemory);
-}
-
-// static
-bool PaintOp::AreSkMatricesEqual(const SkMatrix& left, const SkMatrix& right) {
-  for (int i = 0; i < 9; ++i) {
-    if (!AreEqualEvenIfNaN(left.get(i), right.get(i)))
-      return false;
-  }
-
-  // If a serialized matrix says it is identity, then the original must have
-  // those values, as the serialization process clobbers the matrix values.
-  if (left.isIdentity()) {
-    if (SkMatrix::I() != left)
-      return false;
-    if (SkMatrix::I() != right)
-      return false;
-  }
-
-  if (left.getType() != right.getType())
-    return false;
-
-  return true;
-}
-
-// static
-bool PaintOp::AreSkM44sEqual(const SkM44& left, const SkM44& right) {
-  for (int r = 0; r < 4; ++r) {
-    for (int c = 0; c < 4; ++c) {
-      if (!AreEqualEvenIfNaN(left.rc(r, c), right.rc(r, c)))
-        return false;
-    }
-  }
-
-  return true;
-}
-
-// static
-bool PaintOp::AreSkFlattenablesEqual(SkFlattenable* left,
-                                     SkFlattenable* right) {
-  if (!right || !left)
-    return !right && !left;
-
-  sk_sp<SkData> left_data = left->serialize();
-  sk_sp<SkData> right_data = right->serialize();
-  if (left_data->size() != right_data->size())
-    return false;
-  if (!left_data->equals(right_data.get()))
-    return false;
-  return true;
-}
-
-bool AnnotateOp::AreEqual(const PaintOp* base_left, const PaintOp* base_right) {
-  auto* left = static_cast<const AnnotateOp*>(base_left);
-  auto* right = static_cast<const AnnotateOp*>(base_right);
-  DCHECK(left->IsValid());
-  DCHECK(right->IsValid());
-  if (left->annotation_type != right->annotation_type)
-    return false;
-  if (!AreSkRectsEqual(left->rect, right->rect))
-    return false;
-  if (!left->data != !right->data)
-    return false;
-  if (left->data) {
-    if (left->data->size() != right->data->size())
-      return false;
-    if (0 !=
-        memcmp(left->data->data(), right->data->data(), right->data->size()))
-      return false;
-  }
-  return true;
-}
-
-bool ClipPathOp::AreEqual(const PaintOp* base_left, const PaintOp* base_right) {
-  auto* left = static_cast<const ClipPathOp*>(base_left);
-  auto* right = static_cast<const ClipPathOp*>(base_right);
-  DCHECK(left->IsValid());
-  DCHECK(right->IsValid());
-  if (left->path != right->path)
-    return false;
-  if (left->op != right->op)
-    return false;
-  if (left->antialias != right->antialias)
-    return false;
-  return true;
-}
-
-bool ClipRectOp::AreEqual(const PaintOp* base_left, const PaintOp* base_right) {
-  auto* left = static_cast<const ClipRectOp*>(base_left);
-  auto* right = static_cast<const ClipRectOp*>(base_right);
-  DCHECK(left->IsValid());
-  DCHECK(right->IsValid());
-  if (!AreSkRectsEqual(left->rect, right->rect))
-    return false;
-  if (left->op != right->op)
-    return false;
-  if (left->antialias != right->antialias)
-    return false;
-  return true;
-}
-
-bool ClipRRectOp::AreEqual(const PaintOp* base_left,
-                           const PaintOp* base_right) {
-  auto* left = static_cast<const ClipRRectOp*>(base_left);
-  auto* right = static_cast<const ClipRRectOp*>(base_right);
-  DCHECK(left->IsValid());
-  DCHECK(right->IsValid());
-  if (!AreSkRRectsEqual(left->rrect, right->rrect))
-    return false;
-  if (left->op != right->op)
-    return false;
-  if (left->antialias != right->antialias)
-    return false;
-  return true;
-}
-
-bool ConcatOp::AreEqual(const PaintOp* base_left, const PaintOp* base_right) {
-  auto* left = static_cast<const ConcatOp*>(base_left);
-  auto* right = static_cast<const ConcatOp*>(base_right);
-  DCHECK(left->IsValid());
-  DCHECK(right->IsValid());
-  return AreSkM44sEqual(left->matrix, right->matrix);
-}
-
-bool CustomDataOp::AreEqual(const PaintOp* base_left,
-                            const PaintOp* base_right) {
-  auto* left = static_cast<const CustomDataOp*>(base_left);
-  auto* right = static_cast<const CustomDataOp*>(base_right);
-  DCHECK(left->IsValid());
-  DCHECK(right->IsValid());
-  return left->id == right->id;
-}
-
-bool DrawColorOp::AreEqual(const PaintOp* base_left,
-                           const PaintOp* base_right) {
-  auto* left = static_cast<const DrawColorOp*>(base_left);
-  auto* right = static_cast<const DrawColorOp*>(base_right);
-  DCHECK(left->IsValid());
-  DCHECK(right->IsValid());
-  return left->color == right->color;
-}
-
-bool DrawDRRectOp::AreEqual(const PaintOp* base_left,
-                            const PaintOp* base_right) {
-  auto* left = static_cast<const DrawDRRectOp*>(base_left);
-  auto* right = static_cast<const DrawDRRectOp*>(base_right);
-  DCHECK(left->IsValid());
-  DCHECK(right->IsValid());
-  if (left->flags != right->flags)
-    return false;
-  if (!AreSkRRectsEqual(left->outer, right->outer))
-    return false;
-  if (!AreSkRRectsEqual(left->inner, right->inner))
-    return false;
-  return true;
-}
-
-bool DrawImageOp::AreEqual(const PaintOp* base_left,
-                           const PaintOp* base_right) {
-  auto* left = static_cast<const DrawImageOp*>(base_left);
-  auto* right = static_cast<const DrawImageOp*>(base_right);
-  DCHECK(left->IsValid());
-  DCHECK(right->IsValid());
-  if (left->flags != right->flags)
-    return false;
-  // TODO(enne): Test PaintImage equality once implemented
-  if (!AreEqualEvenIfNaN(left->left, right->left))
-    return false;
-  if (!AreEqualEvenIfNaN(left->top, right->top))
-    return false;
-
-  // scale_adjustment intentionally omitted because it is added during
-  // serialization based on raster scale.
-  return true;
-}
-
-bool DrawImageRectOp::AreEqual(const PaintOp* base_left,
-                               const PaintOp* base_right) {
-  auto* left = static_cast<const DrawImageRectOp*>(base_left);
-  auto* right = static_cast<const DrawImageRectOp*>(base_right);
-  DCHECK(left->IsValid());
-  DCHECK(right->IsValid());
-  if (left->flags != right->flags)
-    return false;
-  // TODO(enne): Test PaintImage equality once implemented
-  if (!AreSkRectsEqual(left->src, right->src))
-    return false;
-  if (!AreSkRectsEqual(left->dst, right->dst))
-    return false;
-
-  // scale_adjustment intentionally omitted because it is added during
-  // serialization based on raster scale.
-  return true;
-}
-
-bool DrawIRectOp::AreEqual(const PaintOp* base_left,
-                           const PaintOp* base_right) {
-  auto* left = static_cast<const DrawIRectOp*>(base_left);
-  auto* right = static_cast<const DrawIRectOp*>(base_right);
-  DCHECK(left->IsValid());
-  DCHECK(right->IsValid());
-  if (left->flags != right->flags)
-    return false;
-  if (left->rect != right->rect)
-    return false;
-  return true;
-}
-
-bool DrawLineOp::AreEqual(const PaintOp* base_left, const PaintOp* base_right) {
-  auto* left = static_cast<const DrawLineOp*>(base_left);
-  auto* right = static_cast<const DrawLineOp*>(base_right);
-  DCHECK(left->IsValid());
-  DCHECK(right->IsValid());
-  if (left->flags != right->flags)
-    return false;
-  if (!AreEqualEvenIfNaN(left->x0, right->x0))
-    return false;
-  if (!AreEqualEvenIfNaN(left->y0, right->y0))
-    return false;
-  if (!AreEqualEvenIfNaN(left->x1, right->x1))
-    return false;
-  if (!AreEqualEvenIfNaN(left->y1, right->y1))
-    return false;
-  return true;
-}
-
-bool DrawOvalOp::AreEqual(const PaintOp* base_left, const PaintOp* base_right) {
-  auto* left = static_cast<const DrawOvalOp*>(base_left);
-  auto* right = static_cast<const DrawOvalOp*>(base_right);
-  DCHECK(left->IsValid());
-  DCHECK(right->IsValid());
-  if (left->flags != right->flags)
-    return false;
-  if (!AreSkRectsEqual(left->oval, right->oval))
-    return false;
-  return true;
-}
-
-bool DrawPathOp::AreEqual(const PaintOp* base_left, const PaintOp* base_right) {
-  auto* left = static_cast<const DrawPathOp*>(base_left);
-  auto* right = static_cast<const DrawPathOp*>(base_right);
-  DCHECK(left->IsValid());
-  DCHECK(right->IsValid());
-  if (left->flags != right->flags)
-    return false;
-  if (left->path != right->path)
-    return false;
-  return true;
-}
-
-bool DrawRecordOp::AreEqual(const PaintOp* base_left,
-                            const PaintOp* base_right) {
-  auto* left = static_cast<const DrawRecordOp*>(base_left);
-  auto* right = static_cast<const DrawRecordOp*>(base_right);
-  DCHECK(left->IsValid());
-  DCHECK(right->IsValid());
-  if (!left->record != !right->record)
-    return false;
-  if (*left->record != *right->record)
-    return false;
-  return true;
-}
-
-bool DrawRectOp::AreEqual(const PaintOp* base_left, const PaintOp* base_right) {
-  auto* left = static_cast<const DrawRectOp*>(base_left);
-  auto* right = static_cast<const DrawRectOp*>(base_right);
-  DCHECK(left->IsValid());
-  DCHECK(right->IsValid());
-  if (left->flags != right->flags)
-    return false;
-  if (!AreSkRectsEqual(left->rect, right->rect))
-    return false;
-  return true;
-}
-
-bool DrawRRectOp::AreEqual(const PaintOp* base_left,
-                           const PaintOp* base_right) {
-  auto* left = static_cast<const DrawRRectOp*>(base_left);
-  auto* right = static_cast<const DrawRRectOp*>(base_right);
-  DCHECK(left->IsValid());
-  DCHECK(right->IsValid());
-  if (left->flags != right->flags)
-    return false;
-  if (!AreSkRRectsEqual(left->rrect, right->rrect))
-    return false;
-  return true;
-}
-
-bool DrawSkottieOp::AreEqual(const PaintOp* base_left,
-                             const PaintOp* base_right) {
-  auto* left = static_cast<const DrawSkottieOp*>(base_left);
-  auto* right = static_cast<const DrawSkottieOp*>(base_right);
-  DCHECK(left->IsValid());
-  DCHECK(right->IsValid());
-  // TODO(malaykeshav): Verify the skottie objects of each PaintOb are equal
-  // based on the serialized bytes.
-  if (left->t != right->t)
-    return false;
-  if (!AreSkRectsEqual(left->dst, right->dst))
-    return false;
-  if (left->images.size() != right->images.size())
-    return false;
-
-  auto left_iter = left->images.begin();
-  auto right_iter = right->images.begin();
-  for (; left_iter != left->images.end(); ++left_iter, ++right_iter) {
-    if (left_iter->first != right_iter->first ||
-        // PaintImage's comparison operator compares the underlying SkImage's
-        // pointer address. This does not necessarily hold in cases where the
-        // image's content may be the same, but it got realloacted to a
-        // different spot somewhere in memory via the transfer cache. The next
-        // best thing is to just compare the dimensions of the PaintImage.
-        left_iter->second.image.width() != right_iter->second.image.width() ||
-        left_iter->second.image.height() != right_iter->second.image.height() ||
-        left_iter->second.quality != right_iter->second.quality) {
-      return false;
-    }
-  }
-
-  if (left->color_map != right->color_map)
-    return false;
-
-  if (left->text_map != right->text_map)
-    return false;
-
-  return true;
-}
-
-bool DrawTextBlobOp::AreEqual(const PaintOp* base_left,
-                              const PaintOp* base_right) {
-  auto* left = static_cast<const DrawTextBlobOp*>(base_left);
-  auto* right = static_cast<const DrawTextBlobOp*>(base_right);
-  DCHECK(left->IsValid());
-  DCHECK(right->IsValid());
-  if (left->flags != right->flags)
-    return false;
-  if (!AreEqualEvenIfNaN(left->x, right->x))
-    return false;
-  if (!AreEqualEvenIfNaN(left->y, right->y))
-    return false;
-  if (left->node_id != right->node_id)
-    return false;
-  return GrSlugAreEqual(left->slug, right->slug);
-}
-
-bool NoopOp::AreEqual(const PaintOp* base_left, const PaintOp* base_right) {
-  return true;
-}
-
-bool RestoreOp::AreEqual(const PaintOp* base_left, const PaintOp* base_right) {
-  return true;
-}
-
-bool RotateOp::AreEqual(const PaintOp* base_left, const PaintOp* base_right) {
-  auto* left = static_cast<const RotateOp*>(base_left);
-  auto* right = static_cast<const RotateOp*>(base_right);
-  DCHECK(left->IsValid());
-  DCHECK(right->IsValid());
-  if (!AreEqualEvenIfNaN(left->degrees, right->degrees))
-    return false;
-  return true;
-}
-
-bool SaveOp::AreEqual(const PaintOp* base_left, const PaintOp* base_right) {
-  return true;
-}
-
-bool SaveLayerOp::AreEqual(const PaintOp* base_left,
-                           const PaintOp* base_right) {
-  auto* left = static_cast<const SaveLayerOp*>(base_left);
-  auto* right = static_cast<const SaveLayerOp*>(base_right);
-  DCHECK(left->IsValid());
-  DCHECK(right->IsValid());
-  if (left->flags != right->flags)
-    return false;
-  if (!AreSkRectsEqual(left->bounds, right->bounds))
-    return false;
-  return true;
-}
-
-bool SaveLayerAlphaOp::AreEqual(const PaintOp* base_left,
-                                const PaintOp* base_right) {
-  auto* left = static_cast<const SaveLayerAlphaOp*>(base_left);
-  auto* right = static_cast<const SaveLayerAlphaOp*>(base_right);
-  DCHECK(left->IsValid());
-  DCHECK(right->IsValid());
-  if (!AreSkRectsEqual(left->bounds, right->bounds))
-    return false;
-  if (left->alpha != right->alpha)
-    return false;
-  return true;
-}
-
-bool ScaleOp::AreEqual(const PaintOp* base_left, const PaintOp* base_right) {
-  auto* left = static_cast<const ScaleOp*>(base_left);
-  auto* right = static_cast<const ScaleOp*>(base_right);
-  DCHECK(left->IsValid());
-  DCHECK(right->IsValid());
-  if (!AreEqualEvenIfNaN(left->sx, right->sx))
-    return false;
-  if (!AreEqualEvenIfNaN(left->sy, right->sy))
-    return false;
-  return true;
-}
-
-bool SetMatrixOp::AreEqual(const PaintOp* base_left,
-                           const PaintOp* base_right) {
-  auto* left = static_cast<const SetMatrixOp*>(base_left);
-  auto* right = static_cast<const SetMatrixOp*>(base_right);
-  DCHECK(left->IsValid());
-  DCHECK(right->IsValid());
-  if (!AreSkM44sEqual(left->matrix, right->matrix))
-    return false;
-  return true;
-}
-
-bool SetNodeIdOp::AreEqual(const PaintOp* base_left,
-                           const PaintOp* base_right) {
-  auto* left = static_cast<const SetNodeIdOp*>(base_left);
-  auto* right = static_cast<const SetNodeIdOp*>(base_right);
-
-  DCHECK(left->IsValid());
-  DCHECK(right->IsValid());
-  return left->node_id == right->node_id;
-}
-
-bool TranslateOp::AreEqual(const PaintOp* base_left,
-                           const PaintOp* base_right) {
-  auto* left = static_cast<const TranslateOp*>(base_left);
-  auto* right = static_cast<const TranslateOp*>(base_right);
-  DCHECK(left->IsValid());
-  DCHECK(right->IsValid());
-  if (!AreEqualEvenIfNaN(left->dx, right->dx))
-    return false;
-  if (!AreEqualEvenIfNaN(left->dy, right->dy))
-    return false;
-  return true;
-}
-
-bool PaintOp::IsDrawOp() const {
-  return g_is_draw_op[type];
-}
-
-bool PaintOp::IsPaintOpWithFlags() const {
-  return g_has_paint_flags[type];
-}
-
-bool PaintOp::operator==(const PaintOp& other) const {
-  if (GetType() != other.GetType())
-    return false;
-  return g_equals_operator[type](this, &other);
-}
-
-// static
-bool PaintOp::TypeHasFlags(PaintOpType type) {
-  return g_has_paint_flags[static_cast<uint8_t>(type)];
-}
-
-void PaintOp::Raster(SkCanvas* canvas, const PlaybackParams& params) const {
-  g_raster_functions[type](this, canvas, params);
-}
-
-size_t PaintOp::Serialize(void* memory,
-                          size_t size,
-                          const SerializeOptions& options,
-                          const PaintFlags* flags_to_serialize,
-                          const SkM44& current_ctm,
-                          const SkM44& original_ctm) const {
-  // Need at least enough room for a skip/type header.
-  if (size < 4)
-    return 0u;
-
-  DCHECK_EQ(0u,
-            reinterpret_cast<uintptr_t>(memory) % PaintOpBuffer::PaintOpAlign);
-
-  size_t written = g_serialize_functions[type](this, memory, size, options,
-                                               flags_to_serialize, current_ctm,
-                                               original_ctm);
-  DCHECK_LE(written, size);
-  if (written < 4)
-    return 0u;
-
-  size_t aligned_written = ((written + PaintOpBuffer::PaintOpAlign - 1) &
-                            ~(PaintOpBuffer::PaintOpAlign - 1));
-  if (aligned_written >= kMaxSkip)
-    return 0u;
-  if (aligned_written > size)
-    return 0u;
-
-  // Update skip and type now that the size is known.
-  uint32_t bytes_to_skip = static_cast<uint32_t>(aligned_written);
-  static_cast<uint32_t*>(memory)[0] = type | bytes_to_skip << 8;
-  return bytes_to_skip;
-}
-
-PaintOp* PaintOp::Deserialize(const volatile void* input,
-                              size_t input_size,
-                              void* output,
-                              size_t output_size,
-                              size_t* read_bytes,
-                              const DeserializeOptions& options) {
-  DCHECK_GE(output_size, sizeof(LargestPaintOp));
-
-  uint8_t type;
-  uint32_t skip;
-  if (!PaintOpReader::ReadAndValidateOpHeader(input, input_size, &type, &skip))
-    return nullptr;
-
-  *read_bytes = skip;
-  return g_deserialize_functions[type](input, skip, output, output_size,
-                                       options);
-}
-
-// static
-bool PaintOp::GetBounds(const PaintOp& op, SkRect* rect) {
-  DCHECK(op.IsDrawOp());
-
-  switch (op.GetType()) {
-    case PaintOpType::DrawColor:
-      return false;
-    case PaintOpType::DrawDRRect: {
-      const auto& rect_op = static_cast<const DrawDRRectOp&>(op);
-      *rect = rect_op.outer.getBounds();
-      rect->sort();
-      return true;
-    }
-    case PaintOpType::DrawImage: {
-      const auto& image_op = static_cast<const DrawImageOp&>(op);
-      *rect = SkRect::MakeXYWH(image_op.left, image_op.top,
-                               image_op.image.width(), image_op.image.height());
-      rect->sort();
-      return true;
-    }
-    case PaintOpType::DrawImageRect: {
-      const auto& image_rect_op = static_cast<const DrawImageRectOp&>(op);
-      *rect = image_rect_op.dst;
-      rect->sort();
-      return true;
-    }
-    case PaintOpType::DrawIRect: {
-      const auto& rect_op = static_cast<const DrawIRectOp&>(op);
-      *rect = SkRect::Make(rect_op.rect);
-      rect->sort();
-      return true;
-    }
-    case PaintOpType::DrawLine: {
-      const auto& line_op = static_cast<const DrawLineOp&>(op);
-      rect->setLTRB(line_op.x0, line_op.y0, line_op.x1, line_op.y1);
-      rect->sort();
-      return true;
-    }
-    case PaintOpType::DrawOval: {
-      const auto& oval_op = static_cast<const DrawOvalOp&>(op);
-      *rect = oval_op.oval;
-      rect->sort();
-      return true;
-    }
-    case PaintOpType::DrawPath: {
-      const auto& path_op = static_cast<const DrawPathOp&>(op);
-      *rect = path_op.path.getBounds();
-      rect->sort();
-      return true;
-    }
-    case PaintOpType::DrawRect: {
-      const auto& rect_op = static_cast<const DrawRectOp&>(op);
-      *rect = rect_op.rect;
-      rect->sort();
-      return true;
-    }
-    case PaintOpType::DrawRRect: {
-      const auto& rect_op = static_cast<const DrawRRectOp&>(op);
-      *rect = rect_op.rrect.rect();
-      rect->sort();
-      return true;
-    }
-    case PaintOpType::DrawRecord:
-      return false;
-    case PaintOpType::DrawSkottie: {
-      const auto& skottie_op = static_cast<const DrawSkottieOp&>(op);
-      *rect = skottie_op.dst;
-      rect->sort();
-      return true;
-    }
-    case PaintOpType::DrawTextBlob: {
-      const auto& text_op = static_cast<const DrawTextBlobOp&>(op);
-      *rect = text_op.blob->bounds().makeOffset(text_op.x, text_op.y);
-      rect->sort();
-      return true;
-    }
-    default:
-      NOTREACHED();
-  }
-  return false;
-}
-
-// static
-gfx::Rect PaintOp::ComputePaintRect(const PaintOp& op,
-                                    const SkRect& clip_rect,
-                                    const SkMatrix& ctm) {
-  gfx::Rect transformed_rect;
-  SkRect op_rect;
-  if (!op.IsDrawOp() || !PaintOp::GetBounds(op, &op_rect)) {
-    // If we can't provide a conservative bounding rect for the op, assume it
-    // covers the complete current clip.
-    // TODO(khushalsagar): See if we can do something better for non-draw ops.
-    transformed_rect = gfx::ToEnclosingRect(gfx::SkRectToRectF(clip_rect));
-  } else {
-    const PaintFlags* flags =
-        op.IsPaintOpWithFlags()
-            ? &(static_cast<const PaintOpWithFlags&>(op).flags)
-            : nullptr;
-    SkRect paint_rect = MapRect(ctm, op_rect);
-    if (flags) {
-      SkPaint paint = flags->ToSkPaint();
-      paint_rect = paint.canComputeFastBounds() && paint_rect.isFinite()
-                       ? paint.computeFastBounds(paint_rect, &paint_rect)
-                       : clip_rect;
-    }
-    // Clamp the image rect by the current clip rect.
-    if (!paint_rect.intersect(clip_rect))
-      return gfx::Rect();
-
-    transformed_rect = gfx::ToEnclosingRect(gfx::SkRectToRectF(paint_rect));
-  }
-
-  // During raster, we use the device clip bounds on the canvas, which outsets
-  // the actual clip by 1 due to the possibility of antialiasing. Account for
-  // this here by outsetting the image rect by 1. Note that this only affects
-  // queries into the rtree, which will now return images that only touch the
-  // bounds of the query rect.
-  //
-  // Note that it's not sufficient for us to inset the device clip bounds at
-  // raster time, since we might be sending a larger-than-one-item display
-  // item to skia, which means that skia will internally determine whether to
-  // raster the picture (using device clip bounds that are outset).
-  transformed_rect.Inset(-1);
-  return transformed_rect;
-}
-
-// static
-bool PaintOp::QuickRejectDraw(const PaintOp& op, const SkCanvas* canvas) {
-  if (!op.IsDrawOp())
-    return false;
-
-  SkRect rect;
-  if (!PaintOp::GetBounds(op, &rect))
-    return false;
-  if (!rect.isFinite())
-    return true;
-
-  if (op.IsPaintOpWithFlags()) {
-    SkPaint paint = static_cast<const PaintOpWithFlags&>(op).flags.ToSkPaint();
-    if (!paint.canComputeFastBounds())
-      return false;
-    // canvas->quickReject tried to be very fast, and sometimes give a false
-    // but conservative result. That's why we need the additional check for
-    // |local_op_rect| because it could quickReject could return false even if
-    // |local_op_rect| is empty.
-    const SkRect& clip_rect = SkRect::Make(canvas->getDeviceClipBounds());
-    const SkMatrix& ctm = canvas->getTotalMatrix();
-    gfx::Rect local_op_rect = PaintOp::ComputePaintRect(op, clip_rect, ctm);
-    if (local_op_rect.IsEmpty())
-      return true;
-    paint.computeFastBounds(rect, &rect);
-  }
-
-  return canvas->quickReject(rect);
-}
-
-// static
-bool PaintOp::OpHasDiscardableImages(const PaintOp& op) {
-  if (op.IsPaintOpWithFlags() && static_cast<const PaintOpWithFlags&>(op)
-                                     .HasDiscardableImagesFromFlags()) {
-    return true;
-  }
-
-  if (op.GetType() == PaintOpType::DrawImage &&
-      static_cast<const DrawImageOp&>(op).HasDiscardableImages()) {
-    return true;
-  } else if (op.GetType() == PaintOpType::DrawImageRect &&
-             static_cast<const DrawImageRectOp&>(op).HasDiscardableImages()) {
-    return true;
-  } else if (op.GetType() == PaintOpType::DrawRecord &&
-             static_cast<const DrawRecordOp&>(op).HasDiscardableImages()) {
-    return true;
-  } else if (op.GetType() == PaintOpType::DrawSkottie &&
-             static_cast<const DrawSkottieOp&>(op).HasDiscardableImages()) {
-    return true;
-  }
-
-  return false;
-}
-
-void PaintOp::DestroyThis() {
-  auto func = g_destructor_functions[type];
-  if (func)
-    func(this);
-}
-
-bool PaintOpWithFlags::HasDiscardableImagesFromFlags() const {
-  return flags.HasDiscardableImages();
-}
-
-void PaintOpWithFlags::RasterWithFlags(SkCanvas* canvas,
-                                       const PaintFlags* raster_flags,
-                                       const PlaybackParams& params) const {
-  g_raster_with_flags_functions[type](this, raster_flags, canvas, params);
-}
-
-int ClipPathOp::CountSlowPaths() const {
-  return antialias && !path.isConvex() ? 1 : 0;
-}
-
-int DrawLineOp::CountSlowPaths() const {
-  if (const SkPathEffect* effect = flags.getPathEffect().get()) {
-    SkPathEffect::DashInfo info;
-    SkPathEffect::DashType dashType = effect->asADash(&info);
-    if (flags.getStrokeCap() != PaintFlags::kRound_Cap &&
-        dashType == SkPathEffect::kDash_DashType && info.fCount == 2) {
-      // The PaintFlags will count this as 1, so uncount that here as
-      // this kind of line is special cased and not slow.
-      return -1;
-    }
-  }
-  return 0;
-}
-
-int DrawPathOp::CountSlowPaths() const {
-  // This logic is copied from SkPathCounter instead of attempting to expose
-  // that from Skia.
-  if (!flags.isAntiAlias() || path.isConvex())
-    return 0;
-
-  PaintFlags::Style paintStyle = flags.getStyle();
-  const SkRect& pathBounds = path.getBounds();
-  if (paintStyle == PaintFlags::kStroke_Style && flags.getStrokeWidth() == 0) {
-    // AA hairline concave path is not slow.
-    return 0;
-  } else if (paintStyle == PaintFlags::kFill_Style &&
-             pathBounds.width() < 64.f && pathBounds.height() < 64.f &&
-             !path.isVolatile()) {
-    // AADF eligible concave path is not slow.
-    return 0;
-  } else {
-    return 1;
-  }
-}
-
-int DrawRecordOp::CountSlowPaths() const {
-  return record->num_slow_paths_up_to_min_for_MSAA();
-}
-
-bool DrawRecordOp::HasNonAAPaint() const {
-  return record->HasNonAAPaint();
-}
-
-bool DrawRecordOp::HasDrawTextOps() const {
-  return record->has_draw_text_ops();
-}
-
-bool DrawRecordOp::HasSaveLayerOps() const {
-  return record->has_save_layer_ops();
-}
-
-bool DrawRecordOp::HasSaveLayerAlphaOps() const {
-  return record->has_save_layer_alpha_ops();
-}
-
-bool DrawRecordOp::HasEffectsPreventingLCDTextForSaveLayerAlpha() const {
-  return record->has_effects_preventing_lcd_text_for_save_layer_alpha();
-}
-
-AnnotateOp::AnnotateOp() : PaintOp(kType) {}
-
-AnnotateOp::AnnotateOp(PaintCanvas::AnnotationType annotation_type,
-                       const SkRect& rect,
-                       sk_sp<SkData> data)
-    : PaintOp(kType),
-      annotation_type(annotation_type),
-      rect(rect),
-      data(std::move(data)) {}
-
-AnnotateOp::~AnnotateOp() = default;
-AnnotateOp::AnnotateOp(const AnnotateOp&) = default;
-AnnotateOp& AnnotateOp::operator=(const AnnotateOp&) = default;
-
-DrawImageOp::DrawImageOp() : PaintOpWithFlags(kType) {}
-
-DrawImageOp::DrawImageOp(const PaintImage& image, SkScalar left, SkScalar top)
-    : PaintOpWithFlags(kType, PaintFlags()),
-      image(image),
-      left(left),
-      top(top) {}
-
-DrawImageOp::DrawImageOp(const PaintImage& image,
-                         SkScalar left,
-                         SkScalar top,
-                         const SkSamplingOptions& sampling,
-                         const PaintFlags* flags)
-    : PaintOpWithFlags(kType, flags ? *flags : PaintFlags()),
-      image(image),
-      left(left),
-      top(top),
-      sampling(sampling) {}
-
-bool DrawImageOp::HasDiscardableImages() const {
-  return image && !image.IsTextureBacked();
-}
-
-DrawImageOp::~DrawImageOp() = default;
-
-DrawImageRectOp::DrawImageRectOp() : PaintOpWithFlags(kType) {}
-
-DrawImageRectOp::DrawImageRectOp(const PaintImage& image,
-                                 const SkRect& src,
-                                 const SkRect& dst,
-                                 SkCanvas::SrcRectConstraint constraint)
-    : PaintOpWithFlags(kType, PaintFlags()),
-      image(image),
-      src(src),
-      dst(dst),
-      constraint(constraint) {}
-
-DrawImageRectOp::DrawImageRectOp(const PaintImage& image,
-                                 const SkRect& src,
-                                 const SkRect& dst,
-                                 const SkSamplingOptions& sampling,
-                                 const PaintFlags* flags,
-                                 SkCanvas::SrcRectConstraint constraint)
-    : PaintOpWithFlags(kType, flags ? *flags : PaintFlags()),
-      image(image),
-      src(src),
-      dst(dst),
-      sampling(sampling),
-      constraint(constraint) {}
-
-bool DrawImageRectOp::HasDiscardableImages() const {
-  return image && !image.IsTextureBacked();
-}
-
-DrawImageRectOp::~DrawImageRectOp() = default;
-
-DrawRecordOp::DrawRecordOp(sk_sp<const PaintRecord> record)
-    : PaintOp(kType), record(std::move(record)) {}
-
-DrawRecordOp::~DrawRecordOp() = default;
-DrawRecordOp::DrawRecordOp(const DrawRecordOp&) = default;
-DrawRecordOp& DrawRecordOp::operator=(const DrawRecordOp&) = default;
-
-size_t DrawRecordOp::AdditionalBytesUsed() const {
-  return record->bytes_used();
-}
-
-size_t DrawRecordOp::AdditionalOpCount() const {
-  return record->total_op_count();
-}
-
-DrawSkottieOp::DrawSkottieOp(scoped_refptr<SkottieWrapper> skottie,
-                             SkRect dst,
-                             float t,
-                             SkottieFrameDataMap images,
-                             const SkottieColorMap& color_map,
-                             SkottieTextPropertyValueMap text_map)
-    : PaintOp(kType),
-      skottie(std::move(skottie)),
-      dst(dst),
-      t(t),
-      images(std::move(images)),
-      color_map(color_map),
-      text_map(std::move(text_map)) {}
-
-DrawSkottieOp::DrawSkottieOp() : PaintOp(kType) {}
-
-DrawSkottieOp::~DrawSkottieOp() = default;
-DrawSkottieOp::DrawSkottieOp(const DrawSkottieOp&) = default;
-DrawSkottieOp& DrawSkottieOp::operator=(const DrawSkottieOp&) = default;
-
-bool DrawSkottieOp::HasDiscardableImages() const {
-  return !images.empty();
-}
-
-bool DrawRecordOp::HasDiscardableImages() const {
-  return record->HasDiscardableImages();
-}
-
-DrawTextBlobOp::DrawTextBlobOp() : PaintOpWithFlags(kType) {}
-
-DrawTextBlobOp::DrawTextBlobOp(sk_sp<SkTextBlob> blob,
-                               SkScalar x,
-                               SkScalar y,
-                               const PaintFlags& flags)
-    : PaintOpWithFlags(kType, flags), blob(std::move(blob)), x(x), y(y) {}
-
-DrawTextBlobOp::DrawTextBlobOp(sk_sp<SkTextBlob> blob,
-                               SkScalar x,
-                               SkScalar y,
-                               NodeId node_id,
-                               const PaintFlags& flags)
-    : PaintOpWithFlags(kType, flags),
-      blob(std::move(blob)),
-      x(x),
-      y(y),
-      node_id(node_id) {}
-
-DrawTextBlobOp::~DrawTextBlobOp() = default;
-DrawTextBlobOp::DrawTextBlobOp(const DrawTextBlobOp&) = default;
-DrawTextBlobOp& DrawTextBlobOp::operator=(const DrawTextBlobOp&) = default;
-
-PaintOpBuffer::CompositeIterator::CompositeIterator(
-    const PaintOpBuffer* buffer,
-    const std::vector<size_t>* offsets)
-    : iter_(offsets == nullptr ? absl::variant<Iterator, OffsetIterator>(
-                                     absl::in_place_type<Iterator>,
-                                     buffer)
-                               : absl::variant<Iterator, OffsetIterator>(
-                                     absl::in_place_type<OffsetIterator>,
-                                     buffer,
-                                     offsets)) {
-  DCHECK(!buffer->are_ops_destroyed());
-}
-
-PaintOpBuffer::CompositeIterator::CompositeIterator(
-    const CompositeIterator& other) = default;
-PaintOpBuffer::CompositeIterator::CompositeIterator(CompositeIterator&& other) =
-    default;
-
 PaintOpBuffer::PaintOpBuffer()
     : has_non_aa_paint_(false),
       has_discardable_images_(false),
@@ -2988,31 +158,6 @@
   are_ops_destroyed_ = false;
 }
 
-// When |op| is a nested PaintOpBuffer, this returns the PaintOp inside
-// that buffer if the buffer contains a single drawing op, otherwise it
-// returns null. This searches recursively if the PaintOpBuffer contains only
-// another PaintOpBuffer.
-static const PaintOp* GetNestedSingleDrawingOp(const PaintOp* op) {
-  if (!op->IsDrawOp())
-    return nullptr;
-  while (op->GetType() == PaintOpType::DrawRecord) {
-    auto* draw_record_op = static_cast<const DrawRecordOp*>(op);
-    if (draw_record_op->record->size() > 1) {
-      // If there's more than one op, then we need to keep the
-      // SaveLayer.
-      return nullptr;
-    }
-
-    // Recurse into the single-op DrawRecordOp and make sure it's a
-    // drawing op.
-    op = &draw_record_op->record->GetFirstOp();
-    if (!op->IsDrawOp())
-      return nullptr;
-  }
-
-  return op;
-}
-
 void PaintOpBuffer::Playback(SkCanvas* canvas) const {
   Playback(canvas, PlaybackParams(nullptr), nullptr);
 }
@@ -3022,89 +167,6 @@
   Playback(canvas, params, nullptr);
 }
 
-PaintOpBuffer::PlaybackFoldingIterator::PlaybackFoldingIterator(
-    const PaintOpBuffer* buffer,
-    const std::vector<size_t>* offsets)
-    : iter_(buffer, offsets),
-      folded_draw_color_(SkColors::kTransparent, SkBlendMode::kSrcOver) {
-  DCHECK(!buffer->are_ops_destroyed());
-  FindNextOp();
-}
-
-PaintOpBuffer::PlaybackFoldingIterator::~PlaybackFoldingIterator() = default;
-
-void PaintOpBuffer::PlaybackFoldingIterator::FindNextOp() {
-  current_alpha_ = 1.0f;
-  for (current_op_ = NextUnfoldedOp(); current_op_;
-       current_op_ = NextUnfoldedOp()) {
-    if (current_op_->GetType() != PaintOpType::SaveLayerAlpha)
-      break;
-    const PaintOp* second = NextUnfoldedOp();
-    if (!second)
-      break;
-
-    if (second->GetType() == PaintOpType::Restore) {
-      // Drop a SaveLayerAlpha/Restore combo.
-      continue;
-    }
-
-    // Find a nested drawing PaintOp to replace |second| if possible, while
-    // holding onto the pointer to |second| in case we can't find a nested
-    // drawing op to replace it with.
-    const PaintOp* draw_op = GetNestedSingleDrawingOp(second);
-
-    const PaintOp* third = nullptr;
-    if (draw_op) {
-      third = NextUnfoldedOp();
-      if (third && third->GetType() == PaintOpType::Restore) {
-        auto* save_op = static_cast<const SaveLayerAlphaOp*>(current_op_);
-        if (draw_op->IsPaintOpWithFlags() &&
-            // SkPaint::drawTextBlob() applies alpha on each glyph so we don't
-            // fold SaveLayerAlpha into DrwaTextBlob to ensure correct alpha
-            // even if some glyphs overlap.
-            draw_op->GetType() != PaintOpType::DrawTextBlob) {
-          auto* flags_op = static_cast<const PaintOpWithFlags*>(draw_op);
-          if (flags_op->flags.SupportsFoldingAlpha()) {
-            current_alpha_ = save_op->alpha;
-            current_op_ = draw_op;
-            break;
-          }
-        } else if (draw_op->GetType() == PaintOpType::DrawColor &&
-                   static_cast<const DrawColorOp*>(draw_op)->mode ==
-                       SkBlendMode::kSrcOver) {
-          auto* draw_color_op = static_cast<const DrawColorOp*>(draw_op);
-          SkColor4f color = draw_color_op->color;
-          folded_draw_color_.color = {color.fR, color.fG, color.fB,
-                                      save_op->alpha * color.fA};
-          current_op_ = &folded_draw_color_;
-          break;
-        }
-      }
-    }
-
-    // If we get here, then we could not find a foldable sequence after
-    // this SaveLayerAlpha, so store any peeked at ops.
-    stack_->push_back(second);
-    if (third)
-      stack_->push_back(third);
-    break;
-  }
-}
-
-const PaintOp* PaintOpBuffer::PlaybackFoldingIterator::NextUnfoldedOp() {
-  if (stack_->size()) {
-    const PaintOp* op = stack_->front();
-    // Shift paintops forward.
-    stack_->erase(stack_->begin());
-    return op;
-  }
-  if (!iter_)
-    return nullptr;
-  const PaintOp& op = *iter_;
-  ++iter_;
-  return &op;
-}
-
 sk_sp<PaintRecord> PaintOpBuffer::MoveRetainingBufferIfPossible() {
   const size_t old_reserved = reserved_;
   sk_sp<PaintRecord> result = sk_make_sp<PaintRecord>(std::move(*this));
@@ -3189,28 +251,13 @@
   while (total_bytes_read < input_size) {
     const volatile void* next_op =
         static_cast<const volatile char*>(input) + total_bytes_read;
-
-    uint8_t type;
-    uint32_t skip;
-    if (!PaintOpReader::ReadAndValidateOpHeader(
-            next_op, input_size - total_bytes_read, &type, &skip)) {
+    size_t read_bytes = 0;
+    if (!PaintOp::DeserializeIntoPaintOpBuffer(next_op,
+                                               input_size - total_bytes_read,
+                                               this, &read_bytes, options)) {
       return false;
     }
-
-    size_t op_skip = ComputeOpSkip(g_type_to_size[type]);
-    const auto* op = g_deserialize_functions[type](
-        next_op, skip, AllocatePaintOp(op_skip), op_skip, options);
-    if (!op) {
-      // The last allocated op has already been destroyed if it failed to
-      // deserialize. Update the buffer's op tracking to exclude it to avoid
-      // access during cleanup at destruction.
-      used_ -= op_skip;
-      op_count_--;
-      return false;
-    }
-
-    g_analyze_op_functions[type](this, op);
-    total_bytes_read += skip;
+    total_bytes_read += read_bytes;
   }
 
   DCHECK_GT(size(), 0u);
@@ -3272,7 +319,7 @@
 PaintOpBuffer::BufferDataPtr PaintOpBuffer::ReallocBuffer(size_t new_size) {
   DCHECK_GE(new_size, used_);
   std::unique_ptr<char, base::AlignedFreeDeleter> new_data(
-      static_cast<char*>(base::AlignedAlloc(new_size, PaintOpAlign)));
+      static_cast<char*>(base::AlignedAlloc(new_size, kPaintOpAlign)));
   if (data_)
     memcpy(new_data.get(), data_.get(), used_);
   BufferDataPtr old_data = std::move(data_);
@@ -3358,4 +405,35 @@
          old_buffer.has_effects_preventing_lcd_text_for_save_layer_alpha();
 }
 
+void PaintOpBuffer::UpdateSaveLayerBounds(size_t offset, const SkRect& bounds) {
+  CHECK_LT(offset, used_);
+  CHECK_LE(offset + sizeof(PaintOp), used_);
+
+  auto* op = reinterpret_cast<PaintOp*>(data_.get() + offset);
+  switch (op->GetType()) {
+    case SaveLayerOp::kType:
+      CHECK_LE(offset + sizeof(SaveLayerOp), used_);
+      static_cast<SaveLayerOp*>(op)->bounds = bounds;
+      break;
+    case SaveLayerAlphaOp::kType:
+      CHECK_LE(offset + sizeof(SaveLayerAlphaOp), used_);
+      static_cast<SaveLayerAlphaOp*>(op)->bounds = bounds;
+      break;
+    default:
+      NOTREACHED();
+  }
+}
+
+const PaintOp* PaintOpBuffer::GetOpAtForTesting(size_t index,
+                                                PaintOpType type) const {
+  size_t i = 0;
+  for (const auto& op : Iterator(this)) {
+    if (i == index) {
+      return op.GetType() == type ? &op : nullptr;
+    }
+    i++;
+  }
+  return nullptr;
+}
+
 }  // namespace cc
diff --git a/cc/paint/paint_op_buffer.h b/cc/paint/paint_op_buffer.h
index 44d11da..f751e6c 100644
--- a/cc/paint/paint_op_buffer.h
+++ b/cc/paint/paint_op_buffer.h
@@ -5,128 +5,44 @@
 #ifndef CC_PAINT_PAINT_OP_BUFFER_H_
 #define CC_PAINT_PAINT_OP_BUFFER_H_
 
-#include <stdint.h>
-
 #include <limits>
 #include <memory>
-#include <string>
-#include <type_traits>
 #include <utility>
 #include <vector>
 
 #include "base/callback.h"
 #include "base/check_op.h"
-#include "base/containers/stack_container.h"
 #include "base/debug/alias.h"
 #include "base/memory/aligned_memory.h"
 #include "base/memory/raw_ptr.h"
+#include "base/memory/raw_ptr_exclusion.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/notreached.h"
 #include "cc/base/math_util.h"
-#include "cc/paint/node_id.h"
-#include "cc/paint/paint_canvas.h"
 #include "cc/paint/paint_export.h"
-#include "cc/paint/paint_flags.h"
-#include "cc/paint/skottie_color_map.h"
-#include "cc/paint/skottie_frame_data.h"
-#include "cc/paint/skottie_resource_metadata.h"
-#include "cc/paint/skottie_text_property_value.h"
-#include "cc/paint/skottie_wrapper.h"
+#include "gpu/command_buffer/common/mailbox.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/abseil-cpp/absl/types/variant.h"
-#include "third_party/skia/include/core/SkColor.h"
-#include "third_party/skia/include/core/SkPath.h"
-#include "third_party/skia/include/core/SkRRect.h"
-#include "third_party/skia/include/core/SkRect.h"
+#include "third_party/skia/include/core/SkM44.h"
 #include "third_party/skia/include/core/SkRefCnt.h"
-#include "third_party/skia/include/core/SkScalar.h"
-#include "ui/gfx/geometry/rect.h"
 
+class SkCanvas;
 class SkColorSpace;
 class SkImage;
 class SkStrikeClient;
 class SkStrikeServer;
-class SkTextBlob;
 
-// PaintOpBuffer is a reimplementation of SkLiteDL.
-// See: third_party/skia/src/core/SkLiteDL.h.
 namespace cc {
+
 class ClientPaintCache;
 class ImageProvider;
+class PaintOp;
 class ServicePaintCache;
 class SkottieSerializationHistory;
 class TransferCacheDeserializeHelper;
 class TransferCacheSerializeHelper;
 
-class CC_PAINT_EXPORT ThreadsafePath : public SkPath {
- public:
-  explicit ThreadsafePath(const SkPath& path) : SkPath(path) {
-    updateBoundsCache();
-  }
-  ThreadsafePath() { updateBoundsCache(); }
-};
-
-class CC_PAINT_EXPORT SharedImageProvider {
- public:
-  enum class Error {
-    kNoError,
-    kUnknownMailbox,
-    kNoAccess,
-    kSkImageCreationFailed,
-  };
-
-  virtual ~SharedImageProvider() = default;
-  virtual sk_sp<SkImage> OpenSharedImageForRead(const gpu::Mailbox& mailbox,
-                                                Error& error) = 0;
-};
-
-// See PaintOp::Serialize/Deserialize for comments.  Derived Serialize types
-// don't write the 4 byte type/skip header because they don't know how much
-// data they will need to write.  PaintOp::Serialize itself must update it.
-#define HAS_SERIALIZATION_FUNCTIONS()                                        \
-  static size_t Serialize(                                                   \
-      const PaintOp* op, void* memory, size_t size,                          \
-      const SerializeOptions& options, const PaintFlags* flags_to_serialize, \
-      const SkM44& current_ctm, const SkM44& original_ctm);                  \
-  static PaintOp* Deserialize(const volatile void* input, size_t input_size, \
-                              void* output, size_t output_size,              \
-                              const DeserializeOptions& options)
-
-enum class PaintOpType : uint8_t {
-  Annotate,
-  ClipPath,
-  ClipRect,
-  ClipRRect,
-  Concat,
-  CustomData,
-  DrawColor,
-  DrawDRRect,
-  DrawImage,
-  DrawImageRect,
-  DrawIRect,
-  DrawLine,
-  DrawOval,
-  DrawPath,
-  DrawRecord,
-  DrawRect,
-  DrawRRect,
-  DrawSkottie,
-  DrawTextBlob,
-  Noop,
-  Restore,
-  Rotate,
-  Save,
-  SaveLayer,
-  SaveLayerAlpha,
-  Scale,
-  SetMatrix,
-  SetNodeId,
-  Translate,
-  LastPaintOpType = Translate,
-};
-
-CC_PAINT_EXPORT std::string PaintOpTypeToString(PaintOpType type);
-CC_PAINT_EXPORT std::ostream& operator<<(std::ostream&, PaintOpType);
+enum class PaintOpType : uint8_t;
 
 struct CC_PAINT_EXPORT PlaybackParams {
   using CustomDataRasterCallback =
@@ -155,26 +71,27 @@
   bool is_analyzing = false;
 };
 
-class CC_PAINT_EXPORT PaintOp {
+class CC_PAINT_EXPORT SharedImageProvider {
  public:
-  uint32_t type : 8;
-  uint32_t skip : 24;
+  enum class Error {
+    kNoError,
+    kUnknownMailbox,
+    kNoAccess,
+    kSkImageCreationFailed,
+  };
 
-  explicit PaintOp(PaintOpType type) : type(static_cast<uint8_t>(type)) {}
+  virtual ~SharedImageProvider() = default;
+  virtual sk_sp<SkImage> OpenSharedImageForRead(const gpu::Mailbox& mailbox,
+                                                Error& error) = 0;
+};
 
-  PaintOpType GetType() const { return static_cast<PaintOpType>(type); }
+// Defined outside of the class as this const is used in multiple files.
+static constexpr int kMinNumberOfSlowPathsForMSAA = 6;
 
-  // Subclasses should provide a static Raster() method which is called from
-  // here. The Raster method should take a const PaintOp* parameter. It is
-  // static with a pointer to the base type so that we can use it as a function
-  // pointer.
-  void Raster(SkCanvas* canvas, const PlaybackParams& params) const;
-  bool IsDrawOp() const;
-  bool IsPaintOpWithFlags() const;
-
-  bool operator==(const PaintOp& other) const;
-  bool operator!=(const PaintOp& other) const { return !(*this == other); }
-
+// PaintOpBuffer is a reimplementation of SkLiteDL.
+// See: third_party/skia/src/core/SkLiteDL.h.
+class CC_PAINT_EXPORT PaintOpBuffer : public SkRefCnt {
+ public:
   struct CC_PAINT_EXPORT SerializeOptions {
     SerializeOptions();
     SerializeOptions(ImageProvider* image_provider,
@@ -230,930 +147,10 @@
     raw_ptr<SharedImageProvider> shared_image_provider = nullptr;
   };
 
-  // Indicates how PaintImages are serialized.
-  enum class SerializedImageType : uint8_t {
-    kNoImage,
-    kImageData,
-    kTransferCacheEntry,
-    kMailbox,
-    kLastType = kMailbox
-  };
-
-  // Subclasses should provide a static Serialize() method called from here.
-  // If the op can be serialized to |memory| in no more than |size| bytes,
-  // then return the number of bytes written.  If it won't fit, return 0.
-  // If |flags_to_serialize| is non-null, it overrides any flags within the op.
-  // |current_ctm| is the transform that will affect the op when rasterized.
-  // |original_ctm| is the transform that SetMatrixOps must be made relative to.
-  size_t Serialize(void* memory,
-                   size_t size,
-                   const SerializeOptions& options,
-                   const PaintFlags* flags_to_serialize,
-                   const SkM44& current_ctm,
-                   const SkM44& original_ctm) const;
-
-  // Deserializes a PaintOp of this type from a given buffer |input| of
-  // at most |input_size| bytes.  Returns null on any errors.
-  // The PaintOp is deserialized into the |output| buffer and returned
-  // if valid.  nullptr is returned if the deserialization fails.
-  // |output_size| must be at least LargestPaintOp + serialized->skip,
-  // to fit all ops.  The caller is responsible for destroying these ops.
-  // After reading, it returns the number of bytes read in |read_bytes|.
-  static PaintOp* Deserialize(const volatile void* input,
-                              size_t input_size,
-                              void* output,
-                              size_t output_size,
-                              size_t* read_bytes,
-                              const DeserializeOptions& options);
-
-  // For draw ops, returns true if a conservative bounding rect can be provided
-  // for the op.
-  static bool GetBounds(const PaintOp& op, SkRect* rect);
-
-  // Returns the minimum conservative bounding rect that |op| draws to on a
-  // canvas. |clip_rect| and |ctm| are the current clip rect and transform on
-  // this canvas.
-  static gfx::Rect ComputePaintRect(const PaintOp& op,
-                                    const SkRect& clip_rect,
-                                    const SkMatrix& ctm);
-
-  // Returns true if the op lies outside the current clip and should be skipped.
-  // Should only be used with draw ops.
-  static bool QuickRejectDraw(const PaintOp& op, const SkCanvas* canvas);
-
-  // Returns true if executing this op will require decoding of any lazy
-  // generated images.
-  static bool OpHasDiscardableImages(const PaintOp& op);
-
-  // Returns true if the given op type has PaintFlags.
-  static bool TypeHasFlags(PaintOpType type);
-
-  int CountSlowPaths() const { return 0; }
-  int CountSlowPathsFromFlags() const { return 0; }
-
-  bool HasNonAAPaint() const { return false; }
-  bool HasDrawTextOps() const { return false; }
-  bool HasSaveLayerOps() const { return false; }
-  bool HasSaveLayerAlphaOps() const { return false; }
-  // Returns true if effects are present that would break LCD text or be broken
-  // by the flags for SaveLayerAlpha to preserving LCD text.
-  bool HasEffectsPreventingLCDTextForSaveLayerAlpha() const { return false; }
-
-  bool HasDiscardableImages() const { return false; }
-  bool HasDiscardableImagesFromFlags() const { return false; }
-
-  // Returns the number of bytes used by this op in referenced sub records
-  // and display lists.  This doesn't count other objects like paths or blobs.
-  size_t AdditionalBytesUsed() const { return 0; }
-
-  // Returns the number of ops in referenced sub records and display lists.
-  size_t AdditionalOpCount() const { return 0; }
-
-  // Run the destructor for the derived op type.  Ops are usually contained in
-  // memory buffers and so don't have their destructors run automatically.
-  void DestroyThis();
-
-  // DrawColor is more restrictive on the blend modes that can be used.
-  static bool IsValidDrawColorSkBlendMode(SkBlendMode mode) {
-    return static_cast<uint32_t>(mode) <=
-           static_cast<uint32_t>(SkBlendMode::kLastCoeffMode);
-  }
-
-  // PaintFlags can have more complex blend modes than DrawColor.
-  static bool IsValidPaintFlagsSkBlendMode(SkBlendMode mode) {
-    return static_cast<uint32_t>(mode) <=
-           static_cast<uint32_t>(SkBlendMode::kLastMode);
-  }
-
-  static bool IsValidSkClipOp(SkClipOp op) {
-    return static_cast<uint32_t>(op) <=
-           static_cast<uint32_t>(SkClipOp::kMax_EnumValue);
-  }
-
-  static bool IsValidPath(const SkPath& path) { return path.isValid(); }
-
-  static bool IsUnsetRect(const SkRect& rect) {
-    return rect.fLeft == SK_ScalarInfinity;
-  }
-
-  static bool IsValidOrUnsetRect(const SkRect& rect) {
-    return IsUnsetRect(rect) || rect.isFinite();
-  }
-
-  // PaintOp supports having nans, but some tests want to make sure
-  // that operator== is true on two objects.  These helpers compare
-  // various types in a way where nan == nan is true.
-  static bool AreEqualEvenIfNaN(float left, float right) {
-    if (std::isnan(left) && std::isnan(right))
-      return true;
-    return left == right;
-  }
-  static bool AreSkPointsEqual(const SkPoint& left, const SkPoint& right);
-  static bool AreSkPoint3sEqual(const SkPoint3& left, const SkPoint3& right);
-  static bool AreSkRectsEqual(const SkRect& left, const SkRect& right);
-  static bool AreSkRRectsEqual(const SkRRect& left, const SkRRect& right);
-  static bool AreSkMatricesEqual(const SkMatrix& left, const SkMatrix& right);
-  static bool AreSkM44sEqual(const SkM44& left, const SkM44& right);
-  static bool AreSkFlattenablesEqual(SkFlattenable* left, SkFlattenable* right);
-
-  static constexpr bool kIsDrawOp = false;
-  static constexpr bool kHasPaintFlags = false;
-  // Since skip and type fit in a uint32_t, this is the max size of skip.
-  static constexpr size_t kMaxSkip = static_cast<size_t>(1 << 24);
-  static const SkRect kUnsetRect;
-
- protected:
-  PaintOp(const PaintOp&) = default;
-  PaintOp& operator=(const PaintOp&) = default;
-};
-
-class CC_PAINT_EXPORT PaintOpWithFlags : public PaintOp {
- public:
-  static constexpr bool kHasPaintFlags = true;
-  PaintOpWithFlags(PaintOpType type, const PaintFlags& flags)
-      : PaintOp(type), flags(flags) {}
-
-  int CountSlowPathsFromFlags() const { return flags.getPathEffect() ? 1 : 0; }
-  bool HasNonAAPaint() const { return !flags.isAntiAlias(); }
-  bool HasDiscardableImagesFromFlags() const;
-
-  void RasterWithFlags(SkCanvas* canvas,
-                       const PaintFlags* flags,
-                       const PlaybackParams& params) const;
-
-  // Subclasses should provide a static RasterWithFlags() method which is called
-  // from the Raster() method. The RasterWithFlags() should use the SkPaint
-  // passed to it, instead of the |flags| member directly, as some callers may
-  // provide a modified PaintFlags. The RasterWithFlags() method is static with
-  // a const PaintOpWithFlags* parameter so that it can be used as a function
-  // pointer.
-  PaintFlags flags;
-
- protected:
-  PaintOpWithFlags(const PaintOpWithFlags&) = default;
-  PaintOpWithFlags& operator=(const PaintOpWithFlags&) = default;
-
-  explicit PaintOpWithFlags(PaintOpType type) : PaintOp(type) {}
-};
-
-class CC_PAINT_EXPORT AnnotateOp final : public PaintOp {
- public:
-  static constexpr PaintOpType kType = PaintOpType::Annotate;
-  AnnotateOp(PaintCanvas::AnnotationType annotation_type,
-             const SkRect& rect,
-             sk_sp<SkData> data);
-  ~AnnotateOp();
-  AnnotateOp(const AnnotateOp&);
-  AnnotateOp& operator=(const AnnotateOp&);
-  static void Raster(const AnnotateOp* op,
-                     SkCanvas* canvas,
-                     const PlaybackParams& params);
-  bool IsValid() const { return rect.isFinite(); }
-  static bool AreEqual(const PaintOp* left, const PaintOp* right);
-  HAS_SERIALIZATION_FUNCTIONS();
-
-  PaintCanvas::AnnotationType annotation_type;
-  SkRect rect;
-  sk_sp<SkData> data;
-
- private:
-  AnnotateOp();
-};
-
-class CC_PAINT_EXPORT ClipPathOp final : public PaintOp {
- public:
-  static constexpr PaintOpType kType = PaintOpType::ClipPath;
-  ClipPathOp(SkPath path,
-             SkClipOp op,
-             bool antialias,
-             UsePaintCache use_paint_cache = UsePaintCache::kEnabled)
-      : PaintOp(kType),
-        path(path),
-        op(op),
-        antialias(antialias),
-        use_cache(use_paint_cache) {}
-  ClipPathOp(const ClipPathOp&) = default;
-  ClipPathOp& operator=(const ClipPathOp&) = default;
-  static void Raster(const ClipPathOp* op,
-                     SkCanvas* canvas,
-                     const PlaybackParams& params);
-  bool IsValid() const { return IsValidSkClipOp(op) && IsValidPath(path); }
-  static bool AreEqual(const PaintOp* left, const PaintOp* right);
-  int CountSlowPaths() const;
-  bool HasNonAAPaint() const { return !antialias; }
-  HAS_SERIALIZATION_FUNCTIONS();
-
-  ThreadsafePath path;
-  SkClipOp op;
-  bool antialias;
-  UsePaintCache use_cache;
-
- private:
-  ClipPathOp() : PaintOp(kType) {}
-};
-
-class CC_PAINT_EXPORT ClipRectOp final : public PaintOp {
- public:
-  static constexpr PaintOpType kType = PaintOpType::ClipRect;
-  ClipRectOp(const SkRect& rect, SkClipOp op, bool antialias)
-      : PaintOp(kType), rect(rect), op(op), antialias(antialias) {}
-  ClipRectOp(const ClipRectOp&) = default;
-  ClipRectOp& operator=(const ClipRectOp&) = default;
-  static void Raster(const ClipRectOp* op,
-                     SkCanvas* canvas,
-                     const PlaybackParams& params);
-  bool IsValid() const { return IsValidSkClipOp(op) && rect.isFinite(); }
-  static bool AreEqual(const PaintOp* left, const PaintOp* right);
-  HAS_SERIALIZATION_FUNCTIONS();
-
-  SkRect rect;
-  SkClipOp op;
-  bool antialias;
-
- private:
-  ClipRectOp() : PaintOp(kType) {}
-};
-
-class CC_PAINT_EXPORT ClipRRectOp final : public PaintOp {
- public:
-  static constexpr PaintOpType kType = PaintOpType::ClipRRect;
-  ClipRRectOp(const SkRRect& rrect, SkClipOp op, bool antialias)
-      : PaintOp(kType), rrect(rrect), op(op), antialias(antialias) {}
-  ClipRRectOp(const ClipRRectOp&) = default;
-  ClipRRectOp& operator=(const ClipRRectOp&) = default;
-  static void Raster(const ClipRRectOp* op,
-                     SkCanvas* canvas,
-                     const PlaybackParams& params);
-  bool IsValid() const { return IsValidSkClipOp(op) && rrect.isValid(); }
-  static bool AreEqual(const PaintOp* left, const PaintOp* right);
-  bool HasNonAAPaint() const { return !antialias; }
-  HAS_SERIALIZATION_FUNCTIONS();
-
-  SkRRect rrect;
-  SkClipOp op;
-  bool antialias;
-
- private:
-  ClipRRectOp() : PaintOp(kType) {}
-};
-
-class CC_PAINT_EXPORT ConcatOp final : public PaintOp {
- public:
-  static constexpr PaintOpType kType = PaintOpType::Concat;
-  explicit ConcatOp(const SkM44& matrix) : PaintOp(kType), matrix(matrix) {}
-  ConcatOp(const ConcatOp&) = default;
-  ConcatOp& operator=(const ConcatOp&) = default;
-  static void Raster(const ConcatOp* op,
-                     SkCanvas* canvas,
-                     const PlaybackParams& params);
-  bool IsValid() const { return true; }
-  static bool AreEqual(const PaintOp* left, const PaintOp* right);
-  HAS_SERIALIZATION_FUNCTIONS();
-
-  SkM44 matrix;
-
- private:
-  ConcatOp() : PaintOp(kType) {}
-};
-
-class CC_PAINT_EXPORT CustomDataOp final : public PaintOp {
- public:
-  static constexpr PaintOpType kType = PaintOpType::CustomData;
-  explicit CustomDataOp(uint32_t id) : PaintOp(kType), id(id) {}
-  CustomDataOp(const CustomDataOp&) = default;
-  CustomDataOp& operator=(const CustomDataOp&) = default;
-  static void Raster(const CustomDataOp* op,
-                     SkCanvas* canvas,
-                     const PlaybackParams& params);
-  bool IsValid() const { return true; }
-  static bool AreEqual(const PaintOp* left, const PaintOp* right);
-  HAS_SERIALIZATION_FUNCTIONS();
-
-  // Stores user defined id as a placeholder op.
-  uint32_t id;
-
- private:
-  CustomDataOp() : PaintOp(kType) {}
-};
-
-class CC_PAINT_EXPORT DrawColorOp final : public PaintOp {
- public:
-  static constexpr PaintOpType kType = PaintOpType::DrawColor;
-  static constexpr bool kIsDrawOp = true;
-  DrawColorOp(SkColor4f color, SkBlendMode mode)
-      : PaintOp(kType), color(color), mode(mode) {}
-  DrawColorOp(const DrawColorOp&) = default;
-  DrawColorOp& operator=(const DrawColorOp&) = default;
-  static void Raster(const DrawColorOp* op,
-                     SkCanvas* canvas,
-                     const PlaybackParams& params);
-  bool IsValid() const { return IsValidDrawColorSkBlendMode(mode); }
-  static bool AreEqual(const PaintOp* left, const PaintOp* right);
-  HAS_SERIALIZATION_FUNCTIONS();
-
-  SkColor4f color;
-  SkBlendMode mode;
-
- private:
-  DrawColorOp() : PaintOp(kType) {}
-};
-
-class CC_PAINT_EXPORT DrawDRRectOp final : public PaintOpWithFlags {
- public:
-  static constexpr PaintOpType kType = PaintOpType::DrawDRRect;
-  static constexpr bool kIsDrawOp = true;
-  DrawDRRectOp(const SkRRect& outer,
-               const SkRRect& inner,
-               const PaintFlags& flags)
-      : PaintOpWithFlags(kType, flags), outer(outer), inner(inner) {}
-  DrawDRRectOp(const DrawDRRectOp&) = default;
-  DrawDRRectOp& operator=(const DrawDRRectOp&) = default;
-  static void RasterWithFlags(const DrawDRRectOp* op,
-                              const PaintFlags* flags,
-                              SkCanvas* canvas,
-                              const PlaybackParams& params);
-  bool IsValid() const {
-    return flags.IsValid() && outer.isValid() && inner.isValid();
-  }
-  static bool AreEqual(const PaintOp* left, const PaintOp* right);
-  HAS_SERIALIZATION_FUNCTIONS();
-
-  SkRRect outer;
-  SkRRect inner;
-
- private:
-  DrawDRRectOp() : PaintOpWithFlags(kType) {}
-};
-
-class CC_PAINT_EXPORT DrawImageOp final : public PaintOpWithFlags {
- public:
-  static constexpr PaintOpType kType = PaintOpType::DrawImage;
-  static constexpr bool kIsDrawOp = true;
-  DrawImageOp(const PaintImage& image, SkScalar left, SkScalar top);
-  DrawImageOp(const PaintImage& image,
-              SkScalar left,
-              SkScalar top,
-              const SkSamplingOptions&,
-              const PaintFlags* flags);
-  ~DrawImageOp();
-  DrawImageOp(const DrawImageOp&) = default;
-  DrawImageOp& operator=(const DrawImageOp&) = default;
-  static void RasterWithFlags(const DrawImageOp* op,
-                              const PaintFlags* flags,
-                              SkCanvas* canvas,
-                              const PlaybackParams& params);
-  bool IsValid() const {
-    return flags.IsValid() && SkScalarIsFinite(scale_adjustment.width()) &&
-           SkScalarIsFinite(scale_adjustment.height());
-  }
-  static bool AreEqual(const PaintOp* left, const PaintOp* right);
-  bool HasDiscardableImages() const;
-  bool HasNonAAPaint() const { return false; }
-  HAS_SERIALIZATION_FUNCTIONS();
-
-  PaintImage image;
-  SkScalar left;
-  SkScalar top;
-  SkSamplingOptions sampling;
-
- private:
-  DrawImageOp();
-
-  // Scale that has already been applied to the decoded image during
-  // serialization. Used with OOP raster.
-  SkSize scale_adjustment = SkSize::Make(1.f, 1.f);
-};
-
-class CC_PAINT_EXPORT DrawImageRectOp final : public PaintOpWithFlags {
- public:
-  static constexpr PaintOpType kType = PaintOpType::DrawImageRect;
-  static constexpr bool kIsDrawOp = true;
-  DrawImageRectOp(const PaintImage& image,
-                  const SkRect& src,
-                  const SkRect& dst,
-                  SkCanvas::SrcRectConstraint constraint);
-  DrawImageRectOp(const PaintImage& image,
-                  const SkRect& src,
-                  const SkRect& dst,
-                  const SkSamplingOptions&,
-                  const PaintFlags* flags,
-                  SkCanvas::SrcRectConstraint constraint);
-  ~DrawImageRectOp();
-  DrawImageRectOp(const DrawImageRectOp&) = default;
-  DrawImageRectOp& operator=(const DrawImageRectOp&) = default;
-  static void RasterWithFlags(const DrawImageRectOp* op,
-                              const PaintFlags* flags,
-                              SkCanvas* canvas,
-                              const PlaybackParams& params);
-  bool IsValid() const {
-    return flags.IsValid() && src.isFinite() && dst.isFinite() &&
-           SkScalarIsFinite(scale_adjustment.width()) &&
-           SkScalarIsFinite(scale_adjustment.height());
-  }
-  static bool AreEqual(const PaintOp* left, const PaintOp* right);
-  bool HasDiscardableImages() const;
-  HAS_SERIALIZATION_FUNCTIONS();
-
-  PaintImage image;
-  SkRect src;
-  SkRect dst;
-  SkSamplingOptions sampling;
-  SkCanvas::SrcRectConstraint constraint;
-
- private:
-  DrawImageRectOp();
-
-  // Scale that has already been applied to the decoded image during
-  // serialization. Used with OOP raster.
-  SkSize scale_adjustment = SkSize::Make(1.f, 1.f);
-};
-
-class CC_PAINT_EXPORT DrawIRectOp final : public PaintOpWithFlags {
- public:
-  static constexpr PaintOpType kType = PaintOpType::DrawIRect;
-  static constexpr bool kIsDrawOp = true;
-  DrawIRectOp(const SkIRect& rect, const PaintFlags& flags)
-      : PaintOpWithFlags(kType, flags), rect(rect) {}
-  DrawIRectOp(const DrawIRectOp&) = default;
-  DrawIRectOp& operator=(const DrawIRectOp&) = default;
-  static void RasterWithFlags(const DrawIRectOp* op,
-                              const PaintFlags* flags,
-                              SkCanvas* canvas,
-                              const PlaybackParams& params);
-  bool IsValid() const { return flags.IsValid(); }
-  static bool AreEqual(const PaintOp* left, const PaintOp* right);
-  bool HasNonAAPaint() const { return false; }
-  HAS_SERIALIZATION_FUNCTIONS();
-
-  SkIRect rect;
-
- private:
-  DrawIRectOp() : PaintOpWithFlags(kType) {}
-};
-
-class CC_PAINT_EXPORT DrawLineOp final : public PaintOpWithFlags {
- public:
-  static constexpr PaintOpType kType = PaintOpType::DrawLine;
-  static constexpr bool kIsDrawOp = true;
-  DrawLineOp(SkScalar x0,
-             SkScalar y0,
-             SkScalar x1,
-             SkScalar y1,
-             const PaintFlags& flags)
-      : PaintOpWithFlags(kType, flags), x0(x0), y0(y0), x1(x1), y1(y1) {}
-  DrawLineOp(const DrawLineOp&) = default;
-  DrawLineOp& operator=(const DrawLineOp&) = default;
-  static void RasterWithFlags(const DrawLineOp* op,
-                              const PaintFlags* flags,
-                              SkCanvas* canvas,
-                              const PlaybackParams& params);
-  bool IsValid() const { return flags.IsValid(); }
-  static bool AreEqual(const PaintOp* left, const PaintOp* right);
-  HAS_SERIALIZATION_FUNCTIONS();
-
-  int CountSlowPaths() const;
-
-  SkScalar x0;
-  SkScalar y0;
-  SkScalar x1;
-  SkScalar y1;
-
- private:
-  DrawLineOp() : PaintOpWithFlags(kType) {}
-};
-
-class CC_PAINT_EXPORT DrawOvalOp final : public PaintOpWithFlags {
- public:
-  static constexpr PaintOpType kType = PaintOpType::DrawOval;
-  static constexpr bool kIsDrawOp = true;
-  DrawOvalOp(const SkRect& oval, const PaintFlags& flags)
-      : PaintOpWithFlags(kType, flags), oval(oval) {}
-  DrawOvalOp(const DrawOvalOp&) = default;
-  DrawOvalOp& operator=(const DrawOvalOp&) = default;
-  static void RasterWithFlags(const DrawOvalOp* op,
-                              const PaintFlags* flags,
-                              SkCanvas* canvas,
-                              const PlaybackParams& params);
-  bool IsValid() const {
-    // Reproduce SkRRect::isValid without converting.
-    return flags.IsValid() && oval.isFinite() && oval.isSorted();
-  }
-  static bool AreEqual(const PaintOp* left, const PaintOp* right);
-  HAS_SERIALIZATION_FUNCTIONS();
-
-  SkRect oval;
-
- private:
-  DrawOvalOp() : PaintOpWithFlags(kType) {}
-};
-
-class CC_PAINT_EXPORT DrawPathOp final : public PaintOpWithFlags {
- public:
-  static constexpr PaintOpType kType = PaintOpType::DrawPath;
-  static constexpr bool kIsDrawOp = true;
-  DrawPathOp(const SkPath& path,
-             const PaintFlags& flags,
-             UsePaintCache use_paint_cache = UsePaintCache::kEnabled)
-      : PaintOpWithFlags(kType, flags),
-        path(path),
-        sk_path_fill_type(static_cast<uint8_t>(path.getFillType())),
-        use_cache(use_paint_cache) {}
-  DrawPathOp(const DrawPathOp&) = default;
-  DrawPathOp& operator=(const DrawPathOp&) = default;
-  static void RasterWithFlags(const DrawPathOp* op,
-                              const PaintFlags* flags,
-                              SkCanvas* canvas,
-                              const PlaybackParams& params);
-  bool IsValid() const { return flags.IsValid() && IsValidPath(path); }
-  static bool AreEqual(const PaintOp* left, const PaintOp* right);
-  int CountSlowPaths() const;
-  HAS_SERIALIZATION_FUNCTIONS();
-
-  ThreadsafePath path;
-
-  // Changing the fill type on an SkPath does not change the
-  // generation id. This can lead to caching issues so we explicitly
-  // serialize/deserialize this value and set it on the SkPath before handing it
-  // to Skia.
-  uint8_t sk_path_fill_type;
-  UsePaintCache use_cache;
-
- private:
-  DrawPathOp() : PaintOpWithFlags(kType) {}
-};
-
-class CC_PAINT_EXPORT DrawRecordOp final : public PaintOp {
- public:
-  static constexpr PaintOpType kType = PaintOpType::DrawRecord;
-  static constexpr bool kIsDrawOp = true;
-  explicit DrawRecordOp(sk_sp<const PaintRecord> record);
-  ~DrawRecordOp();
-  DrawRecordOp(const DrawRecordOp&);
-  DrawRecordOp& operator=(const DrawRecordOp&);
-  static void Raster(const DrawRecordOp* op,
-                     SkCanvas* canvas,
-                     const PlaybackParams& params);
-  bool IsValid() const { return true; }
-  static bool AreEqual(const PaintOp* left, const PaintOp* right);
-  size_t AdditionalBytesUsed() const;
-  size_t AdditionalOpCount() const;
-  bool HasDiscardableImages() const;
-  int CountSlowPaths() const;
-  bool HasNonAAPaint() const;
-  bool HasDrawTextOps() const;
-  bool HasSaveLayerOps() const;
-  bool HasSaveLayerAlphaOps() const;
-  bool HasEffectsPreventingLCDTextForSaveLayerAlpha() const;
-  HAS_SERIALIZATION_FUNCTIONS();
-
-  sk_sp<const PaintRecord> record;
-};
-
-class CC_PAINT_EXPORT DrawRectOp final : public PaintOpWithFlags {
- public:
-  static constexpr PaintOpType kType = PaintOpType::DrawRect;
-  static constexpr bool kIsDrawOp = true;
-  DrawRectOp(const SkRect& rect, const PaintFlags& flags)
-      : PaintOpWithFlags(kType, flags), rect(rect) {}
-  DrawRectOp(const DrawRectOp&) = default;
-  DrawRectOp& operator=(const DrawRectOp&) = default;
-  static void RasterWithFlags(const DrawRectOp* op,
-                              const PaintFlags* flags,
-                              SkCanvas* canvas,
-                              const PlaybackParams& params);
-  bool IsValid() const { return flags.IsValid() && rect.isFinite(); }
-  static bool AreEqual(const PaintOp* left, const PaintOp* right);
-  HAS_SERIALIZATION_FUNCTIONS();
-
-  SkRect rect;
-
- private:
-  DrawRectOp() : PaintOpWithFlags(kType) {}
-};
-
-class CC_PAINT_EXPORT DrawRRectOp final : public PaintOpWithFlags {
- public:
-  static constexpr PaintOpType kType = PaintOpType::DrawRRect;
-  static constexpr bool kIsDrawOp = true;
-  DrawRRectOp(const SkRRect& rrect, const PaintFlags& flags)
-      : PaintOpWithFlags(kType, flags), rrect(rrect) {}
-  DrawRRectOp(const DrawRRectOp&) = default;
-  DrawRRectOp& operator=(const DrawRRectOp&) = default;
-  static void RasterWithFlags(const DrawRRectOp* op,
-                              const PaintFlags* flags,
-                              SkCanvas* canvas,
-                              const PlaybackParams& params);
-  bool IsValid() const { return flags.IsValid() && rrect.isValid(); }
-  static bool AreEqual(const PaintOp* left, const PaintOp* right);
-  HAS_SERIALIZATION_FUNCTIONS();
-
-  SkRRect rrect;
-
- private:
-  DrawRRectOp() : PaintOpWithFlags(kType) {}
-};
-
-class CC_PAINT_EXPORT DrawSkottieOp final : public PaintOp {
- public:
-  static constexpr PaintOpType kType = PaintOpType::DrawSkottie;
-  static constexpr bool kIsDrawOp = true;
-  DrawSkottieOp(scoped_refptr<SkottieWrapper> skottie,
-                SkRect dst,
-                float t,
-                SkottieFrameDataMap images,
-                const SkottieColorMap& color_map,
-                SkottieTextPropertyValueMap text_map);
-  ~DrawSkottieOp();
-  DrawSkottieOp(const DrawSkottieOp&);
-  DrawSkottieOp& operator=(const DrawSkottieOp&);
-  static void Raster(const DrawSkottieOp* op,
-                     SkCanvas* canvas,
-                     const PlaybackParams& params);
-  bool IsValid() const {
-    return !!skottie && !dst.isEmpty() && t >= 0 && t <= 1.f;
-  }
-  static bool AreEqual(const PaintOp* left, const PaintOp* right);
-  bool HasDiscardableImages() const;
-  HAS_SERIALIZATION_FUNCTIONS();
-
-  scoped_refptr<SkottieWrapper> skottie;
-  SkRect dst;
-  float t;
-  // Image to use for each asset in this frame of the animation. If an asset is
-  // missing, the most recently used image for that asset (from a previous
-  // DrawSkottieOp) gets reused when rendering this frame. Given that image
-  // assets generally do not change from frame to frame in most animations, that
-  // means in practice, this map is often empty.
-  SkottieFrameDataMap images;
-  // Node name hashes and corresponding colors to use for dynamic coloration.
-  SkottieColorMap color_map;
-  SkottieTextPropertyValueMap text_map;
-
- private:
-  SkottieWrapper::FrameDataFetchResult GetImageAssetForRaster(
-      SkCanvas* canvas,
-      const PlaybackParams& params,
-      SkottieResourceIdHash asset_id,
-      float t_frame,
-      sk_sp<SkImage>& image_out,
-      SkSamplingOptions& sampling_out) const;
-
-  DrawSkottieOp();
-};
-
-// TODO(penghuang): Replace DrawTextBlobOp with DrawSlugOp, when GrSlug can be
-// serialized.
-class CC_PAINT_EXPORT DrawTextBlobOp final : public PaintOpWithFlags {
- public:
-  static constexpr PaintOpType kType = PaintOpType::DrawTextBlob;
-  static constexpr bool kIsDrawOp = true;
-  DrawTextBlobOp(sk_sp<SkTextBlob> blob,
-                 SkScalar x,
-                 SkScalar y,
-                 const PaintFlags& flags);
-  DrawTextBlobOp(sk_sp<SkTextBlob> blob,
-                 SkScalar x,
-                 SkScalar y,
-                 NodeId node_id,
-                 const PaintFlags& flags);
-  ~DrawTextBlobOp();
-  DrawTextBlobOp(const DrawTextBlobOp&);
-  DrawTextBlobOp& operator=(const DrawTextBlobOp&);
-  static void RasterWithFlags(const DrawTextBlobOp* op,
-                              const PaintFlags* flags,
-                              SkCanvas* canvas,
-                              const PlaybackParams& params);
-  bool IsValid() const { return flags.IsValid(); }
-  bool HasDrawTextOps() const { return true; }
-  static bool AreEqual(const PaintOp* left, const PaintOp* right);
-  HAS_SERIALIZATION_FUNCTIONS();
-
-  sk_sp<SkTextBlob> blob;
-  sk_sp<GrSlug> slug;
-  std::vector<sk_sp<GrSlug>> extra_slugs;
-  SkScalar x;
-  SkScalar y;
-  // This field isn't serialized.
-  NodeId node_id = kInvalidNodeId;
-
- private:
-  DrawTextBlobOp();
-};
-
-class CC_PAINT_EXPORT NoopOp final : public PaintOp {
- public:
-  static constexpr PaintOpType kType = PaintOpType::Noop;
-  NoopOp() : PaintOp(kType) {}
-  NoopOp(const NoopOp&) = default;
-  NoopOp& operator=(const NoopOp&) = default;
-  static void Raster(const NoopOp* op,
-                     SkCanvas* canvas,
-                     const PlaybackParams& params) {}
-  bool IsValid() const { return true; }
-  static bool AreEqual(const PaintOp* left, const PaintOp* right);
-  HAS_SERIALIZATION_FUNCTIONS();
-};
-
-class CC_PAINT_EXPORT RestoreOp final : public PaintOp {
- public:
-  static constexpr PaintOpType kType = PaintOpType::Restore;
-  RestoreOp() : PaintOp(kType) {}
-  RestoreOp(const RestoreOp&) = default;
-  RestoreOp& operator=(const RestoreOp&) = default;
-  static void Raster(const RestoreOp* op,
-                     SkCanvas* canvas,
-                     const PlaybackParams& params);
-  bool IsValid() const { return true; }
-  static bool AreEqual(const PaintOp* left, const PaintOp* right);
-  HAS_SERIALIZATION_FUNCTIONS();
-};
-
-class CC_PAINT_EXPORT RotateOp final : public PaintOp {
- public:
-  static constexpr PaintOpType kType = PaintOpType::Rotate;
-  explicit RotateOp(SkScalar degrees) : PaintOp(kType), degrees(degrees) {}
-  RotateOp(const RotateOp&) = default;
-  RotateOp& operator=(const RotateOp&) = default;
-  static void Raster(const RotateOp* op,
-                     SkCanvas* canvas,
-                     const PlaybackParams& params);
-  bool IsValid() const { return true; }
-  static bool AreEqual(const PaintOp* left, const PaintOp* right);
-  HAS_SERIALIZATION_FUNCTIONS();
-
-  SkScalar degrees;
-
- private:
-  RotateOp() : PaintOp(kType) {}
-};
-
-class CC_PAINT_EXPORT SaveOp final : public PaintOp {
- public:
-  static constexpr PaintOpType kType = PaintOpType::Save;
-  SaveOp() : PaintOp(kType) {}
-  SaveOp(const SaveOp&) = default;
-  SaveOp& operator=(const SaveOp&) = default;
-  static void Raster(const SaveOp* op,
-                     SkCanvas* canvas,
-                     const PlaybackParams& params);
-  bool IsValid() const { return true; }
-  static bool AreEqual(const PaintOp* left, const PaintOp* right);
-  HAS_SERIALIZATION_FUNCTIONS();
-};
-
-class CC_PAINT_EXPORT SaveLayerOp final : public PaintOpWithFlags {
- public:
-  static constexpr PaintOpType kType = PaintOpType::SaveLayer;
-  SaveLayerOp(const SkRect* bounds, const PaintFlags* flags)
-      : PaintOpWithFlags(kType, flags ? *flags : PaintFlags()),
-        bounds(bounds ? *bounds : kUnsetRect) {}
-  SaveLayerOp(const SaveLayerOp&) = default;
-  SaveLayerOp& operator=(const SaveLayerOp&) = default;
-  static void RasterWithFlags(const SaveLayerOp* op,
-                              const PaintFlags* flags,
-                              SkCanvas* canvas,
-                              const PlaybackParams& params);
-  bool IsValid() const { return flags.IsValid() && IsValidOrUnsetRect(bounds); }
-  static bool AreEqual(const PaintOp* left, const PaintOp* right);
-  bool HasNonAAPaint() const { return false; }
-  // We simply assume any effects (or even no effects -- just starting an empty
-  // transparent layer) would break LCD text or be broken by the flags for
-  // SaveLayerAlpha to preserve LCD text.
-  bool HasEffectsPreventingLCDTextForSaveLayerAlpha() const { return true; }
-  bool HasSaveLayerOps() const { return true; }
-  HAS_SERIALIZATION_FUNCTIONS();
-
-  SkRect bounds;
-
- private:
-  SaveLayerOp() : PaintOpWithFlags(kType) {}
-};
-
-class CC_PAINT_EXPORT SaveLayerAlphaOp final : public PaintOp {
- public:
-  static constexpr PaintOpType kType = PaintOpType::SaveLayerAlpha;
-  template <class F, class = std::enable_if_t<std::is_same_v<F, float>>>
-  SaveLayerAlphaOp(const SkRect* bounds, F alpha)
-      : PaintOp(kType), bounds(bounds ? *bounds : kUnsetRect), alpha(alpha) {}
-  SaveLayerAlphaOp(const SaveLayerAlphaOp&) = default;
-  SaveLayerAlphaOp& operator=(const SaveLayerAlphaOp&) = default;
-  static void Raster(const SaveLayerAlphaOp* op,
-                     SkCanvas* canvas,
-                     const PlaybackParams& params);
-  bool IsValid() const { return IsValidOrUnsetRect(bounds); }
-  static bool AreEqual(const PaintOp* left, const PaintOp* right);
-  bool HasSaveLayerOps() const { return true; }
-  bool HasSaveLayerAlphaOps() const { return true; }
-  HAS_SERIALIZATION_FUNCTIONS();
-
-  SkRect bounds;
-  float alpha;
-
- private:
-  SaveLayerAlphaOp() : PaintOp(kType) {}
-};
-
-class CC_PAINT_EXPORT ScaleOp final : public PaintOp {
- public:
-  static constexpr PaintOpType kType = PaintOpType::Scale;
-  ScaleOp(SkScalar sx, SkScalar sy) : PaintOp(kType), sx(sx), sy(sy) {}
-  ScaleOp(const ScaleOp&) = default;
-  ScaleOp& operator=(const ScaleOp&) = default;
-  static void Raster(const ScaleOp* op,
-                     SkCanvas* canvas,
-                     const PlaybackParams& params);
-  bool IsValid() const { return true; }
-  static bool AreEqual(const PaintOp* left, const PaintOp* right);
-  HAS_SERIALIZATION_FUNCTIONS();
-
-  SkScalar sx;
-  SkScalar sy;
-
- private:
-  ScaleOp() : PaintOp(kType) {}
-};
-
-class CC_PAINT_EXPORT SetMatrixOp final : public PaintOp {
- public:
-  static constexpr PaintOpType kType = PaintOpType::SetMatrix;
-  explicit SetMatrixOp(const SkM44& matrix) : PaintOp(kType), matrix(matrix) {}
-  SetMatrixOp(const SetMatrixOp&) = default;
-  SetMatrixOp& operator=(const SetMatrixOp&) = default;
-  // This is the only op that needs the original ctm of the SkCanvas
-  // used for raster (since SetMatrix is relative to the recording origin and
-  // shouldn't clobber the SkCanvas raster origin).
-  //
-  // TODO(enne): Find some cleaner way to do this, possibly by making
-  // all SetMatrix calls Concat??
-  static void Raster(const SetMatrixOp* op,
-                     SkCanvas* canvas,
-                     const PlaybackParams& params);
-  bool IsValid() const { return true; }
-  static bool AreEqual(const PaintOp* left, const PaintOp* right);
-  HAS_SERIALIZATION_FUNCTIONS();
-
-  SkM44 matrix;
-
- private:
-  SetMatrixOp() : PaintOp(kType) {}
-};
-
-class CC_PAINT_EXPORT SetNodeIdOp final : public PaintOp {
- public:
-  static constexpr PaintOpType kType = PaintOpType::SetNodeId;
-  explicit SetNodeIdOp(int node_id) : PaintOp(kType), node_id(node_id) {}
-  SetNodeIdOp(const SetNodeIdOp&) = default;
-  SetNodeIdOp& operator=(const SetNodeIdOp&) = default;
-  static void Raster(const SetNodeIdOp* op,
-                     SkCanvas* canvas,
-                     const PlaybackParams& params);
-  bool IsValid() const { return true; }
-  static bool AreEqual(const PaintOp* left, const PaintOp* right);
-  HAS_SERIALIZATION_FUNCTIONS();
-
-  int node_id;
-
- private:
-  SetNodeIdOp() : PaintOp(kType) {}
-};
-
-class CC_PAINT_EXPORT TranslateOp final : public PaintOp {
- public:
-  static constexpr PaintOpType kType = PaintOpType::Translate;
-  TranslateOp(SkScalar dx, SkScalar dy) : PaintOp(kType), dx(dx), dy(dy) {}
-  TranslateOp(const TranslateOp&) = default;
-  TranslateOp& operator=(const TranslateOp&) = default;
-  static void Raster(const TranslateOp* op,
-                     SkCanvas* canvas,
-                     const PlaybackParams& params);
-  bool IsValid() const { return true; }
-  static bool AreEqual(const PaintOp* left, const PaintOp* right);
-  HAS_SERIALIZATION_FUNCTIONS();
-
-  SkScalar dx;
-  SkScalar dy;
-
- private:
-  TranslateOp() : PaintOp(kType) {}
-};
-
-#undef HAS_SERIALIZATION_FUNCTIONS
-
-// TODO(vmpstr): Revisit this when sizes of DrawImageRectOp change.
-using LargestPaintOp =
-    typename std::conditional<(sizeof(DrawImageRectOp) > sizeof(DrawDRRectOp)),
-                              DrawImageRectOp,
-                              DrawDRRectOp>::type;
-
-// Defined outside of the class as this const is used in multiple files.
-static constexpr int kMinNumberOfSlowPathsForMSAA = 6;
-
-class CC_PAINT_EXPORT PaintOpBuffer : public SkRefCnt {
- public:
   enum { kInitialBufferSize = 4096 };
-  static constexpr size_t PaintOpAlign = 8;
+  static constexpr size_t kPaintOpAlign = 8;
   static inline size_t ComputeOpSkip(size_t sizeof_op) {
-    return MathUtil::UncheckedRoundUp(sizeof_op, PaintOpBuffer::PaintOpAlign);
+    return MathUtil::UncheckedRoundUp(sizeof_op, kPaintOpAlign);
   }
 
   PaintOpBuffer();
@@ -1178,12 +175,11 @@
   // overwritten.
   bool Deserialize(const volatile void* input,
                    size_t input_size,
-                   const PaintOp::DeserializeOptions& options);
+                   const DeserializeOptions& options);
 
-  static sk_sp<PaintOpBuffer> MakeFromMemory(
-      const volatile void* input,
-      size_t input_size,
-      const PaintOp::DeserializeOptions& options);
+  static sk_sp<PaintOpBuffer> MakeFromMemory(const volatile void* input,
+                                             size_t input_size,
+                                             const DeserializeOptions& options);
 
   // Given the |bounds| of a PaintOpBuffer that would be transformed by |ctm|
   // when rendered, compute the bounds needed to raster the buffer at a fixed
@@ -1246,7 +242,7 @@
   template <typename T, typename... Args>
   const T& push(Args&&... args) {
     static_assert(std::is_base_of<PaintOp, T>::value, "T not a PaintOp.");
-    static_assert(alignof(T) <= PaintOpAlign, "");
+    static_assert(alignof(T) <= kPaintOpAlign, "");
     static_assert(sizeof(T) < std::numeric_limits<uint16_t>::max(),
                   "Cannot fit op code in skip");
     uint16_t skip = static_cast<uint16_t>(ComputeOpSkip(sizeof(T)));
@@ -1259,24 +255,7 @@
     return *op;
   }
 
-  void UpdateSaveLayerBounds(size_t offset, const SkRect& bounds) {
-    CHECK_LT(offset, used_);
-    CHECK_LE(offset + sizeof(PaintOp), used_);
-
-    auto* op = reinterpret_cast<PaintOp*>(data_.get() + offset);
-    switch (op->GetType()) {
-      case SaveLayerOp::kType:
-        CHECK_LE(offset + sizeof(SaveLayerOp), used_);
-        static_cast<SaveLayerOp*>(op)->bounds = bounds;
-        break;
-      case SaveLayerAlphaOp::kType:
-        CHECK_LE(offset + sizeof(SaveLayerAlphaOp), used_);
-        static_cast<SaveLayerAlphaOp*>(op)->bounds = bounds;
-        break;
-      default:
-        NOTREACHED();
-    }
-  }
+  void UpdateSaveLayerBounds(size_t offset, const SkRect& bounds);
 
   template <typename T>
   void AnalyzeAddedOp(const T* op) {
@@ -1306,13 +285,9 @@
 
   template <typename T>
   const T* GetOpAtForTesting(size_t index) const {
-    size_t i = 0;
-    for (PaintOpBuffer::Iterator it(this); it && i <= index; ++it, ++i) {
-      if (i == index && it->GetType() == T::kType)
-        return static_cast<const T*>(it.get());
-    }
-    return nullptr;
+    return static_cast<const T*>(GetOpAtForTesting(index, T::kType));
   }
+  const PaintOp* GetOpAtForTesting(size_t index, PaintOpType type) const;
 
   size_t GetOpOffsetForTracing(const PaintOp& op) const {
     DCHECK_GE(reinterpret_cast<const char*>(&op), data_.get());
@@ -1322,213 +297,14 @@
     return result;
   }
 
-  class CC_PAINT_EXPORT Iterator {
-   public:
-    using value_type = PaintOp;
-    explicit Iterator(const PaintOpBuffer* buffer)
-        : Iterator(buffer, buffer->data_.get(), 0u) {}
-
-    PaintOp* get() const { return reinterpret_cast<PaintOp*>(ptr_); }
-    PaintOp* operator->() const { return get(); }
-    PaintOp& operator*() const { return *get(); }
-    Iterator begin() const { return Iterator(buffer_); }
-    Iterator end() const {
-      return Iterator(buffer_, buffer_->data_.get() + buffer_->used_,
-                      buffer_->used_);
-    }
-    bool operator==(const Iterator& other) const {
-      // Not valid to compare iterators on different buffers.
-      DCHECK_EQ(other.buffer_, buffer_);
-      return other.op_offset_ == op_offset_;
-    }
-    bool operator!=(const Iterator& other) const { return !(*this == other); }
-    Iterator& operator++() {
-      DCHECK(*this);
-      const PaintOp& op = **this;
-      ptr_ += op.skip;
-      op_offset_ += op.skip;
-
-      CHECK_LE(op_offset_, buffer_->used_);
-      return *this;
-    }
-    explicit operator bool() const { return op_offset_ < buffer_->used_; }
-
-   private:
-    Iterator(const PaintOpBuffer* buffer, char* ptr, size_t op_offset)
-        : buffer_(buffer), ptr_(ptr), op_offset_(op_offset) {
-      DCHECK(!buffer->are_ops_destroyed());
-    }
-
-    // `buffer_` and `ptr_` are not a raw_ptr<...> for performance reasons
-    // (based on analysis of sampling profiler data and tab_search:top100:2020).
-    RAW_PTR_EXCLUSION const PaintOpBuffer* buffer_ = nullptr;
-    RAW_PTR_EXCLUSION char* ptr_ = nullptr;
-
-    size_t op_offset_ = 0;
-  };
-
-  class CC_PAINT_EXPORT OffsetIterator {
-   public:
-    using value_type = PaintOp;
-    // Offsets and paint op buffer must come from the same DisplayItemList.
-    OffsetIterator(const PaintOpBuffer* buffer,
-                   const std::vector<size_t>* offsets)
-        : buffer_(buffer), ptr_(buffer_->data_.get()), offsets_(offsets) {
-      DCHECK(!buffer->are_ops_destroyed());
-      if (!offsets || offsets->empty()) {
-        *this = end();
-        return;
-      }
-      op_offset_ = (*offsets)[0];
-      ptr_ += op_offset_;
-    }
-
-    PaintOp* get() const { return reinterpret_cast<PaintOp*>(ptr_); }
-    PaintOp* operator->() const { return get(); }
-    PaintOp& operator*() const { return *get(); }
-    OffsetIterator begin() const { return OffsetIterator(buffer_, offsets_); }
-    OffsetIterator end() const {
-      return OffsetIterator(buffer_, buffer_->data_.get() + buffer_->used_,
-                            buffer_->used_, offsets_);
-    }
-    bool operator==(const OffsetIterator& other) const {
-      // Not valid to compare iterators on different buffers.
-      DCHECK_EQ(other.buffer_, buffer_);
-      return other.op_offset_ == op_offset_;
-    }
-    bool operator!=(const OffsetIterator& other) const {
-      return !(*this == other);
-    }
-    OffsetIterator& operator++() {
-      if (++offsets_index_ >= offsets_->size()) {
-        *this = end();
-        return *this;
-      }
-
-      size_t target_offset = (*offsets_)[offsets_index_];
-      // Sanity checks.
-      CHECK_GE(target_offset, op_offset_);
-      // Debugging crbug.com/738182.
-      base::debug::Alias(&target_offset);
-      CHECK_LT(target_offset, buffer_->used_);
-
-      // Advance the iterator to the target offset.
-      ptr_ += (target_offset - op_offset_);
-      op_offset_ = target_offset;
-
-      DCHECK(!*this || (*this)->type <=
-                           static_cast<uint32_t>(PaintOpType::LastPaintOpType));
-      return *this;
-    }
-
-    explicit operator bool() const { return op_offset_ < buffer_->used_; }
-
-   private:
-    OffsetIterator(const PaintOpBuffer* buffer,
-                   char* ptr,
-                   size_t op_offset,
-                   const std::vector<size_t>* offsets)
-        : buffer_(buffer), ptr_(ptr), offsets_(offsets), op_offset_(op_offset) {
-      DCHECK(!buffer->are_ops_destroyed());
-    }
-
-    // `buffer_`, `ptr_`, and `offsets_` are not a raw_ptr<...> for performance
-    // reasons (based on analysis of sampling profiler data and
-    // tab_search:top100:2020).
-    RAW_PTR_EXCLUSION const PaintOpBuffer* buffer_ = nullptr;
-    RAW_PTR_EXCLUSION char* ptr_ = nullptr;
-    RAW_PTR_EXCLUSION const std::vector<size_t>* offsets_;
-
-    size_t op_offset_ = 0;
-    size_t offsets_index_ = 0;
-  };
-
-  class CC_PAINT_EXPORT CompositeIterator {
-   public:
-    using value_type = PaintOp;
-    // Offsets and paint op buffer must come from the same DisplayItemList.
-    CompositeIterator(const PaintOpBuffer* buffer,
-                      const std::vector<size_t>* offsets);
-    CompositeIterator(const CompositeIterator& other);
-    CompositeIterator(CompositeIterator&& other);
-
-    PaintOp* get() const {
-      return absl::visit([](const auto& iter) { return iter.get(); }, iter_);
-    }
-    PaintOp* operator->() const { return get(); }
-    PaintOp& operator*() const { return *get(); }
-    CompositeIterator begin() const {
-      return absl::holds_alternative<Iterator>(iter_)
-                 ? CompositeIterator(absl::get<Iterator>(iter_).begin())
-                 : CompositeIterator(absl::get<OffsetIterator>(iter_).begin());
-    }
-    CompositeIterator end() const {
-      return absl::holds_alternative<Iterator>(iter_)
-                 ? CompositeIterator(absl::get<Iterator>(iter_).end())
-                 : CompositeIterator(absl::get<OffsetIterator>(iter_).end());
-    }
-    bool operator==(const CompositeIterator& other) const {
-      return iter_ == other.iter_;
-    }
-    bool operator!=(const CompositeIterator& other) const {
-      return !(*this == other);
-    }
-    CompositeIterator& operator++() {
-      absl::visit([](auto& iter) { ++iter; }, iter_);
-      return *this;
-    }
-    explicit operator bool() const {
-      return absl::visit([](const auto& iter) { return !!iter; }, iter_);
-    }
-
-   private:
-    explicit CompositeIterator(OffsetIterator iter) : iter_(std::move(iter)) {}
-    explicit CompositeIterator(Iterator iter) : iter_(std::move(iter)) {}
-
-    absl::variant<Iterator, OffsetIterator> iter_;
-  };
-
-  class CC_PAINT_EXPORT PlaybackFoldingIterator {
-   public:
-    PlaybackFoldingIterator(const PaintOpBuffer* buffer,
-                            const std::vector<size_t>* offsets);
-    ~PlaybackFoldingIterator();
-
-    const PaintOp* get() const { return current_op_; }
-    const PaintOp* operator->() const { return current_op_; }
-    const PaintOp& operator*() const { return *current_op_; }
-
-    PlaybackFoldingIterator& operator++() {
-      FindNextOp();
-      return *this;
-    }
-
-    explicit operator bool() const { return !!current_op_; }
-
-    // Guaranteed to be 255 for all ops without flags.
-    uint8_t alpha() const {
-      return static_cast<uint8_t>(current_alpha_ * 255.0f);
-    }
-
-   private:
-    void FindNextOp();
-    const PaintOp* NextUnfoldedOp();
-
-    PaintOpBuffer::CompositeIterator iter_;
-
-    // FIFO queue of paint ops that have been peeked at.
-    base::StackVector<const PaintOp*, 3> stack_;
-    DrawColorOp folded_draw_color_;
-
-    // `current_op_` is not a raw_ptr<...> for performance reasons (based on
-    // analysis of sampling profiler data and tab_search:top100:2020).
-    RAW_PTR_EXCLUSION const PaintOp* current_op_ = nullptr;
-
-    float current_alpha_ = 1.0f;
-  };
+  class Iterator;
+  class OffsetIterator;
+  class CompositeIterator;
+  class PlaybackFoldingIterator;
 
  private:
   friend class DisplayItemList;
+  friend class PaintOp;
   friend class PaintOpBufferOffsetsTest;
   friend class SolidColorAnalyzer;
   using BufferDataPtr = std::unique_ptr<char, base::AlignedFreeDeleter>;
diff --git a/cc/paint/paint_op_buffer_fuzzer.cc b/cc/paint/paint_op_buffer_fuzzer.cc
index 44b6787..cb54c77 100644
--- a/cc/paint/paint_op_buffer_fuzzer.cc
+++ b/cc/paint/paint_op_buffer_fuzzer.cc
@@ -95,7 +95,7 @@
 
     std::unique_ptr<char, base::AlignedFreeDeleter> deserialized(
         static_cast<char*>(base::AlignedAlloc(
-            sizeof(cc::LargestPaintOp), cc::PaintOpBuffer::PaintOpAlign)));
+            sizeof(cc::LargestPaintOp), cc::PaintOpBuffer::kPaintOpAlign)));
     size_t bytes_read = 0;
     cc::PaintOp* deserialized_op = cc::PaintOp::Deserialize(
         data, size, deserialized.get(), sizeof(cc::LargestPaintOp), &bytes_read,
diff --git a/cc/paint/paint_op_buffer_iterator.cc b/cc/paint/paint_op_buffer_iterator.cc
new file mode 100644
index 0000000..2f0ddb865
--- /dev/null
+++ b/cc/paint/paint_op_buffer_iterator.cc
@@ -0,0 +1,139 @@
+// 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.
+
+#include "cc/paint/paint_op_buffer_iterator.h"
+
+namespace cc {
+
+namespace {
+
+// When |op| is a nested PaintOpBuffer, this returns the PaintOp inside
+// that buffer if the buffer contains a single drawing op, otherwise it
+// returns null. This searches recursively if the PaintOpBuffer contains only
+// another PaintOpBuffer.
+static const PaintOp* GetNestedSingleDrawingOp(const PaintOp* op) {
+  if (!op->IsDrawOp())
+    return nullptr;
+  while (op->GetType() == PaintOpType::DrawRecord) {
+    auto* draw_record_op = static_cast<const DrawRecordOp*>(op);
+    if (draw_record_op->record->size() > 1) {
+      // If there's more than one op, then we need to keep the
+      // SaveLayer.
+      return nullptr;
+    }
+
+    // Recurse into the single-op DrawRecordOp and make sure it's a
+    // drawing op.
+    op = &draw_record_op->record->GetFirstOp();
+    if (!op->IsDrawOp())
+      return nullptr;
+  }
+
+  return op;
+}
+
+}  // anonymous namespace
+
+PaintOpBuffer::CompositeIterator::CompositeIterator(
+    const PaintOpBuffer* buffer,
+    const std::vector<size_t>* offsets)
+    : iter_(offsets == nullptr ? absl::variant<Iterator, OffsetIterator>(
+                                     absl::in_place_type<Iterator>,
+                                     buffer)
+                               : absl::variant<Iterator, OffsetIterator>(
+                                     absl::in_place_type<OffsetIterator>,
+                                     buffer,
+                                     offsets)) {
+  DCHECK(!buffer->are_ops_destroyed());
+}
+
+PaintOpBuffer::CompositeIterator::CompositeIterator(
+    const CompositeIterator& other) = default;
+PaintOpBuffer::CompositeIterator::CompositeIterator(CompositeIterator&& other) =
+    default;
+
+PaintOpBuffer::PlaybackFoldingIterator::PlaybackFoldingIterator(
+    const PaintOpBuffer* buffer,
+    const std::vector<size_t>* offsets)
+    : iter_(buffer, offsets),
+      folded_draw_color_(SkColors::kTransparent, SkBlendMode::kSrcOver) {
+  DCHECK(!buffer->are_ops_destroyed());
+  FindNextOp();
+}
+
+PaintOpBuffer::PlaybackFoldingIterator::~PlaybackFoldingIterator() = default;
+
+void PaintOpBuffer::PlaybackFoldingIterator::FindNextOp() {
+  current_alpha_ = 1.0f;
+  for (current_op_ = NextUnfoldedOp(); current_op_;
+       current_op_ = NextUnfoldedOp()) {
+    if (current_op_->GetType() != PaintOpType::SaveLayerAlpha)
+      break;
+    const PaintOp* second = NextUnfoldedOp();
+    if (!second)
+      break;
+
+    if (second->GetType() == PaintOpType::Restore) {
+      // Drop a SaveLayerAlpha/Restore combo.
+      continue;
+    }
+
+    // Find a nested drawing PaintOp to replace |second| if possible, while
+    // holding onto the pointer to |second| in case we can't find a nested
+    // drawing op to replace it with.
+    const PaintOp* draw_op = GetNestedSingleDrawingOp(second);
+
+    const PaintOp* third = nullptr;
+    if (draw_op) {
+      third = NextUnfoldedOp();
+      if (third && third->GetType() == PaintOpType::Restore) {
+        auto* save_op = static_cast<const SaveLayerAlphaOp*>(current_op_);
+        if (draw_op->IsPaintOpWithFlags() &&
+            // SkPaint::drawTextBlob() applies alpha on each glyph so we don't
+            // fold SaveLayerAlpha into DrwaTextBlob to ensure correct alpha
+            // even if some glyphs overlap.
+            draw_op->GetType() != PaintOpType::DrawTextBlob) {
+          auto* flags_op = static_cast<const PaintOpWithFlags*>(draw_op);
+          if (flags_op->flags.SupportsFoldingAlpha()) {
+            current_alpha_ = save_op->alpha;
+            current_op_ = draw_op;
+            break;
+          }
+        } else if (draw_op->GetType() == PaintOpType::DrawColor &&
+                   static_cast<const DrawColorOp*>(draw_op)->mode ==
+                       SkBlendMode::kSrcOver) {
+          auto* draw_color_op = static_cast<const DrawColorOp*>(draw_op);
+          SkColor4f color = draw_color_op->color;
+          folded_draw_color_.color = {color.fR, color.fG, color.fB,
+                                      save_op->alpha * color.fA};
+          current_op_ = &folded_draw_color_;
+          break;
+        }
+      }
+    }
+
+    // If we get here, then we could not find a foldable sequence after
+    // this SaveLayerAlpha, so store any peeked at ops.
+    stack_->push_back(second);
+    if (third)
+      stack_->push_back(third);
+    break;
+  }
+}
+
+const PaintOp* PaintOpBuffer::PlaybackFoldingIterator::NextUnfoldedOp() {
+  if (stack_->size()) {
+    const PaintOp* op = stack_->front();
+    // Shift paintops forward.
+    stack_->erase(stack_->begin());
+    return op;
+  }
+  if (!iter_)
+    return nullptr;
+  const PaintOp& op = *iter_;
+  ++iter_;
+  return &op;
+}
+
+}  // namespace cc
diff --git a/cc/paint/paint_op_buffer_iterator.h b/cc/paint/paint_op_buffer_iterator.h
new file mode 100644
index 0000000..9050815
--- /dev/null
+++ b/cc/paint/paint_op_buffer_iterator.h
@@ -0,0 +1,223 @@
+// 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.
+
+#ifndef CC_PAINT_PAINT_OP_BUFFER_ITERATOR_H_
+#define CC_PAINT_PAINT_OP_BUFFER_ITERATOR_H_
+
+#include <utility>
+#include <vector>
+
+#include "cc/paint/paint_op.h"
+#include "cc/paint/paint_op_buffer.h"
+
+namespace cc {
+
+class CC_PAINT_EXPORT PaintOpBuffer::Iterator {
+ public:
+  using value_type = PaintOp;
+  explicit Iterator(const PaintOpBuffer* buffer)
+      : Iterator(buffer, buffer->data_.get(), 0u) {}
+
+  PaintOp* get() const { return reinterpret_cast<PaintOp*>(ptr_); }
+  PaintOp* operator->() const { return get(); }
+  PaintOp& operator*() const { return *get(); }
+  Iterator begin() const { return Iterator(buffer_); }
+  Iterator end() const {
+    return Iterator(buffer_, buffer_->data_.get() + buffer_->used_,
+                    buffer_->used_);
+  }
+  bool operator==(const Iterator& other) const {
+    // Not valid to compare iterators on different buffers.
+    DCHECK_EQ(other.buffer_, buffer_);
+    return other.op_offset_ == op_offset_;
+  }
+  bool operator!=(const Iterator& other) const { return !(*this == other); }
+  Iterator& operator++() {
+    DCHECK(*this);
+    const PaintOp& op = **this;
+    ptr_ += op.skip;
+    op_offset_ += op.skip;
+
+    CHECK_LE(op_offset_, buffer_->used_);
+    return *this;
+  }
+  explicit operator bool() const { return op_offset_ < buffer_->used_; }
+
+ private:
+  Iterator(const PaintOpBuffer* buffer, char* ptr, size_t op_offset)
+      : buffer_(buffer), ptr_(ptr), op_offset_(op_offset) {
+    DCHECK(!buffer->are_ops_destroyed());
+  }
+
+  // `buffer_` and `ptr_` are not a raw_ptr<...> for performance reasons
+  // (based on analysis of sampling profiler data and tab_search:top100:2020).
+  RAW_PTR_EXCLUSION const PaintOpBuffer* buffer_ = nullptr;
+  RAW_PTR_EXCLUSION char* ptr_ = nullptr;
+
+  size_t op_offset_ = 0;
+};
+
+class CC_PAINT_EXPORT PaintOpBuffer::OffsetIterator {
+ public:
+  using value_type = PaintOp;
+  // Offsets and paint op buffer must come from the same DisplayItemList.
+  OffsetIterator(const PaintOpBuffer* buffer,
+                 const std::vector<size_t>* offsets)
+      : buffer_(buffer), ptr_(buffer_->data_.get()), offsets_(offsets) {
+    DCHECK(!buffer->are_ops_destroyed());
+    if (!offsets || offsets->empty()) {
+      *this = end();
+      return;
+    }
+    op_offset_ = (*offsets)[0];
+    ptr_ += op_offset_;
+  }
+
+  PaintOp* get() const { return reinterpret_cast<PaintOp*>(ptr_); }
+  PaintOp* operator->() const { return get(); }
+  PaintOp& operator*() const { return *get(); }
+  OffsetIterator begin() const { return OffsetIterator(buffer_, offsets_); }
+  OffsetIterator end() const {
+    return OffsetIterator(buffer_, buffer_->data_.get() + buffer_->used_,
+                          buffer_->used_, offsets_);
+  }
+  bool operator==(const OffsetIterator& other) const {
+    // Not valid to compare iterators on different buffers.
+    DCHECK_EQ(other.buffer_, buffer_);
+    return other.op_offset_ == op_offset_;
+  }
+  bool operator!=(const OffsetIterator& other) const {
+    return !(*this == other);
+  }
+  OffsetIterator& operator++() {
+    if (++offsets_index_ >= offsets_->size()) {
+      *this = end();
+      return *this;
+    }
+
+    size_t target_offset = (*offsets_)[offsets_index_];
+    // Sanity checks.
+    CHECK_GE(target_offset, op_offset_);
+    // Debugging crbug.com/738182.
+    base::debug::Alias(&target_offset);
+    CHECK_LT(target_offset, buffer_->used_);
+
+    // Advance the iterator to the target offset.
+    ptr_ += (target_offset - op_offset_);
+    op_offset_ = target_offset;
+
+    DCHECK(!*this || (*this)->type <=
+                         static_cast<uint32_t>(PaintOpType::LastPaintOpType));
+    return *this;
+  }
+
+  explicit operator bool() const { return op_offset_ < buffer_->used_; }
+
+ private:
+  OffsetIterator(const PaintOpBuffer* buffer,
+                 char* ptr,
+                 size_t op_offset,
+                 const std::vector<size_t>* offsets)
+      : buffer_(buffer), ptr_(ptr), offsets_(offsets), op_offset_(op_offset) {
+    DCHECK(!buffer->are_ops_destroyed());
+  }
+
+  // `buffer_`, `ptr_`, and `offsets_` are not a raw_ptr<...> for performance
+  // reasons (based on analysis of sampling profiler data and
+  // tab_search:top100:2020).
+  RAW_PTR_EXCLUSION const PaintOpBuffer* buffer_ = nullptr;
+  RAW_PTR_EXCLUSION char* ptr_ = nullptr;
+  RAW_PTR_EXCLUSION const std::vector<size_t>* offsets_;
+
+  size_t op_offset_ = 0;
+  size_t offsets_index_ = 0;
+};
+
+class CC_PAINT_EXPORT PaintOpBuffer::CompositeIterator {
+ public:
+  using value_type = PaintOp;
+  // Offsets and paint op buffer must come from the same DisplayItemList.
+  CompositeIterator(const PaintOpBuffer* buffer,
+                    const std::vector<size_t>* offsets);
+  CompositeIterator(const CompositeIterator& other);
+  CompositeIterator(CompositeIterator&& other);
+
+  PaintOp* get() const {
+    return absl::visit([](const auto& iter) { return iter.get(); }, iter_);
+  }
+  PaintOp* operator->() const { return get(); }
+  PaintOp& operator*() const { return *get(); }
+  CompositeIterator begin() const {
+    return absl::holds_alternative<Iterator>(iter_)
+               ? CompositeIterator(absl::get<Iterator>(iter_).begin())
+               : CompositeIterator(absl::get<OffsetIterator>(iter_).begin());
+  }
+  CompositeIterator end() const {
+    return absl::holds_alternative<Iterator>(iter_)
+               ? CompositeIterator(absl::get<Iterator>(iter_).end())
+               : CompositeIterator(absl::get<OffsetIterator>(iter_).end());
+  }
+  bool operator==(const CompositeIterator& other) const {
+    return iter_ == other.iter_;
+  }
+  bool operator!=(const CompositeIterator& other) const {
+    return !(*this == other);
+  }
+  CompositeIterator& operator++() {
+    absl::visit([](auto& iter) { ++iter; }, iter_);
+    return *this;
+  }
+  explicit operator bool() const {
+    return absl::visit([](const auto& iter) { return !!iter; }, iter_);
+  }
+
+ private:
+  explicit CompositeIterator(OffsetIterator iter) : iter_(std::move(iter)) {}
+  explicit CompositeIterator(Iterator iter) : iter_(std::move(iter)) {}
+
+  absl::variant<Iterator, OffsetIterator> iter_;
+};
+
+class CC_PAINT_EXPORT PaintOpBuffer::PlaybackFoldingIterator {
+ public:
+  PlaybackFoldingIterator(const PaintOpBuffer* buffer,
+                          const std::vector<size_t>* offsets);
+  ~PlaybackFoldingIterator();
+
+  const PaintOp* get() const { return current_op_; }
+  const PaintOp* operator->() const { return current_op_; }
+  const PaintOp& operator*() const { return *current_op_; }
+
+  PlaybackFoldingIterator& operator++() {
+    FindNextOp();
+    return *this;
+  }
+
+  explicit operator bool() const { return !!current_op_; }
+
+  // Guaranteed to be 255 for all ops without flags.
+  uint8_t alpha() const {
+    return static_cast<uint8_t>(current_alpha_ * 255.0f);
+  }
+
+ private:
+  void FindNextOp();
+  const PaintOp* NextUnfoldedOp();
+
+  PaintOpBuffer::CompositeIterator iter_;
+
+  // FIFO queue of paint ops that have been peeked at.
+  base::StackVector<const PaintOp*, 3> stack_;
+  DrawColorOp folded_draw_color_;
+
+  // `current_op_` is not a raw_ptr<...> for performance reasons (based on
+  // analysis of sampling profiler data and tab_search:top100:2020).
+  RAW_PTR_EXCLUSION const PaintOp* current_op_ = nullptr;
+
+  float current_alpha_ = 1.0f;
+};
+
+}  // namespace cc
+
+#endif  // CC_PAINT_PAINT_OP_BUFFER_ITERATOR_H_
diff --git a/cc/paint/paint_op_buffer_serializer.cc b/cc/paint/paint_op_buffer_serializer.cc
index 288b4276..b3e90a6 100644
--- a/cc/paint/paint_op_buffer_serializer.cc
+++ b/cc/paint/paint_op_buffer_serializer.cc
@@ -12,6 +12,7 @@
 #include "base/bind.h"
 #include "base/trace_event/trace_event.h"
 #include "cc/paint/clear_for_opaque_raster.h"
+#include "cc/paint/paint_op_buffer_iterator.h"
 #include "cc/paint/scoped_raster_flags.h"
 #include "skia/ext/legacy_display_globals.h"
 #include "third_party/skia/include/core/SkColorSpace.h"
@@ -353,7 +354,7 @@
   }
 
   DCHECK_GE(bytes, 4u);
-  DCHECK_EQ(bytes % PaintOpBuffer::PaintOpAlign, 0u);
+  DCHECK_EQ(bytes % PaintOpBuffer::kPaintOpAlign, 0u);
   return true;
 }
 
diff --git a/cc/paint/paint_op_buffer_serializer.h b/cc/paint/paint_op_buffer_serializer.h
index eaf1816..e81b3646 100644
--- a/cc/paint/paint_op_buffer_serializer.h
+++ b/cc/paint/paint_op_buffer_serializer.h
@@ -9,8 +9,8 @@
 #include <vector>
 
 #include "base/memory/raw_ptr.h"
+#include "cc/paint/paint_op.h"
 #include "cc/paint/paint_op_buffer.h"
-
 #include "third_party/skia/include/core/SkColor.h"
 #include "third_party/skia/include/private/chromium/SkChromeRemoteGlyphCache.h"
 #include "ui/gfx/geometry/rect_f.h"
diff --git a/cc/paint/paint_op_buffer_unittest.cc b/cc/paint/paint_op_buffer_unittest.cc
index d650c7d9..610af36a 100644
--- a/cc/paint/paint_op_buffer_unittest.cc
+++ b/cc/paint/paint_op_buffer_unittest.cc
@@ -5,6 +5,7 @@
 #include "cc/paint/paint_op_buffer.h"
 
 #include <algorithm>
+#include <string>
 
 #include "base/bind.h"
 #include "base/memory/raw_ptr.h"
@@ -17,6 +18,7 @@
 #include "cc/paint/image_transfer_cache_entry.h"
 #include "cc/paint/paint_flags.h"
 #include "cc/paint/paint_image_builder.h"
+#include "cc/paint/paint_op_buffer_iterator.h"
 #include "cc/paint/paint_op_buffer_serializer.h"
 #include "cc/paint/paint_op_reader.h"
 #include "cc/paint/paint_op_writer.h"
@@ -1292,8 +1294,9 @@
 
       // Number of bytes bytes_written must be a multiple of PaintOpAlign
       // unless the buffer is filled entirely.
-      if (remaining_ != 0u)
-        DCHECK_EQ(0u, bytes_written % PaintOpBuffer::PaintOpAlign);
+      if (remaining_ != 0u) {
+        DCHECK_EQ(0u, bytes_written % PaintOpBuffer::kPaintOpAlign);
+      }
     }
   }
 
@@ -1367,7 +1370,7 @@
         remaining_(remaining),
         options_(options) {
     data_.reset(static_cast<char*>(base::AlignedAlloc(
-        sizeof(LargestPaintOp), PaintOpBuffer::PaintOpAlign)));
+        sizeof(LargestPaintOp), PaintOpBuffer::kPaintOpAlign)));
     DeserializeCurrentOp();
   }
 
@@ -1882,7 +1885,7 @@
     // in the buffer_.
     output_size_ = kBufferBytesPerOp * buffer_.size();
     output_.reset(static_cast<char*>(
-        base::AlignedAlloc(output_size_, PaintOpBuffer::PaintOpAlign)));
+        base::AlignedAlloc(output_size_, PaintOpBuffer::kPaintOpAlign)));
   }
 
   bool IsTypeSupported() {
@@ -2018,7 +2021,7 @@
   char* first = static_cast<char*>(output_.get());
   char* current = first;
 
-  static constexpr size_t kAlign = PaintOpBuffer::PaintOpAlign;
+  static constexpr size_t kAlign = PaintOpBuffer::kPaintOpAlign;
   static constexpr size_t kOutputOpSize = kBufferBytesPerOp;
   std::unique_ptr<char, base::AlignedFreeDeleter> deserialize_buffer_(
       static_cast<char*>(base::AlignedAlloc(kOutputOpSize, kAlign)));
@@ -2113,7 +2116,7 @@
   size_t deserialized_size = sizeof(LargestPaintOp) + PaintOp::kMaxSkip;
   std::unique_ptr<char, base::AlignedFreeDeleter> deserialized(
       static_cast<char*>(
-          base::AlignedAlloc(deserialized_size, PaintOpBuffer::PaintOpAlign)));
+          base::AlignedAlloc(deserialized_size, PaintOpBuffer::kPaintOpAlign)));
   for (const PaintOp& op : PaintOpBuffer::Iterator(&buffer_)) {
     size_t bytes_written = op.Serialize(output_.get(), output_size_,
                                         options_provider.serialize_options(),
@@ -2156,7 +2159,7 @@
 
   std::unique_ptr<char, base::AlignedFreeDeleter> memory(
       static_cast<char*>(base::AlignedAlloc(PaintOpBuffer::kInitialBufferSize,
-                                            PaintOpBuffer::PaintOpAlign)));
+                                            PaintOpBuffer::kPaintOpAlign)));
   TestOptionsProvider options_provider;
   SimpleBufferSerializer serializer(memory.get(),
                                     PaintOpBuffer::kInitialBufferSize,
@@ -2228,7 +2231,7 @@
 
   std::unique_ptr<char, base::AlignedFreeDeleter> memory(
       static_cast<char*>(base::AlignedAlloc(PaintOpBuffer::kInitialBufferSize,
-                                            PaintOpBuffer::PaintOpAlign)));
+                                            PaintOpBuffer::kPaintOpAlign)));
   TestOptionsProvider options_provider;
   SimpleBufferSerializer serializer(memory.get(),
                                     PaintOpBuffer::kInitialBufferSize,
@@ -2253,7 +2256,7 @@
 
   std::unique_ptr<char, base::AlignedFreeDeleter> memory(
       static_cast<char*>(base::AlignedAlloc(PaintOpBuffer::kInitialBufferSize,
-                                            PaintOpBuffer::PaintOpAlign)));
+                                            PaintOpBuffer::kPaintOpAlign)));
   TestOptionsProvider options_provider;
   SimpleBufferSerializer serializer(memory.get(),
                                     PaintOpBuffer::kInitialBufferSize,
@@ -2350,7 +2353,7 @@
 
   std::unique_ptr<char, base::AlignedFreeDeleter> memory(
       static_cast<char*>(base::AlignedAlloc(PaintOpBuffer::kInitialBufferSize,
-                                            PaintOpBuffer::PaintOpAlign)));
+                                            PaintOpBuffer::kPaintOpAlign)));
   TestOptionsProvider options_provider;
   SimpleBufferSerializer serializer(memory.get(),
                                     PaintOpBuffer::kInitialBufferSize,
@@ -2421,7 +2424,7 @@
 
     std::unique_ptr<char, base::AlignedFreeDeleter> memory(
         static_cast<char*>(base::AlignedAlloc(PaintOpBuffer::kInitialBufferSize,
-                                              PaintOpBuffer::PaintOpAlign)));
+                                              PaintOpBuffer::kPaintOpAlign)));
     TestOptionsProvider options_provider;
     SimpleBufferSerializer serializer(memory.get(),
                                       PaintOpBuffer::kInitialBufferSize,
@@ -2481,7 +2484,7 @@
 
   std::unique_ptr<char, base::AlignedFreeDeleter> memory(
       static_cast<char*>(base::AlignedAlloc(PaintOpBuffer::kInitialBufferSize,
-                                            PaintOpBuffer::PaintOpAlign)));
+                                            PaintOpBuffer::kPaintOpAlign)));
   TestOptionsProvider options_provider;
   SimpleBufferSerializer serializer(memory.get(),
                                     PaintOpBuffer::kInitialBufferSize,
@@ -2527,7 +2530,7 @@
 // Test generic PaintOp deserializing failure cases.
 TEST(PaintOpBufferTest, PaintOpDeserialize) {
   static constexpr size_t kSize = sizeof(LargestPaintOp) + 100;
-  static constexpr size_t kAlign = PaintOpBuffer::PaintOpAlign;
+  static constexpr size_t kAlign = PaintOpBuffer::kPaintOpAlign;
   std::unique_ptr<char, base::AlignedFreeDeleter> input_(
       static_cast<char*>(base::AlignedAlloc(kSize, kAlign)));
   std::unique_ptr<char, base::AlignedFreeDeleter> output_(
@@ -2584,10 +2587,10 @@
 TEST(PaintOpBufferTest, ValidateSkClip) {
   size_t buffer_size = kBufferBytesPerOp;
   std::unique_ptr<char, base::AlignedFreeDeleter> serialized(static_cast<char*>(
-      base::AlignedAlloc(buffer_size, PaintOpBuffer::PaintOpAlign)));
+      base::AlignedAlloc(buffer_size, PaintOpBuffer::kPaintOpAlign)));
   std::unique_ptr<char, base::AlignedFreeDeleter> deserialized(
       static_cast<char*>(
-          base::AlignedAlloc(buffer_size, PaintOpBuffer::PaintOpAlign)));
+          base::AlignedAlloc(buffer_size, PaintOpBuffer::kPaintOpAlign)));
 
   PaintOpBuffer buffer;
 
@@ -2642,10 +2645,10 @@
 TEST(PaintOpBufferTest, ValidateSkBlendMode) {
   size_t buffer_size = kBufferBytesPerOp;
   std::unique_ptr<char, base::AlignedFreeDeleter> serialized(static_cast<char*>(
-      base::AlignedAlloc(buffer_size, PaintOpBuffer::PaintOpAlign)));
+      base::AlignedAlloc(buffer_size, PaintOpBuffer::kPaintOpAlign)));
   std::unique_ptr<char, base::AlignedFreeDeleter> deserialized(
       static_cast<char*>(
-          base::AlignedAlloc(buffer_size, PaintOpBuffer::PaintOpAlign)));
+          base::AlignedAlloc(buffer_size, PaintOpBuffer::kPaintOpAlign)));
 
   PaintOpBuffer buffer;
 
@@ -2723,10 +2726,10 @@
 TEST(PaintOpBufferTest, ValidateRects) {
   size_t buffer_size = kBufferBytesPerOp;
   std::unique_ptr<char, base::AlignedFreeDeleter> serialized(static_cast<char*>(
-      base::AlignedAlloc(buffer_size, PaintOpBuffer::PaintOpAlign)));
+      base::AlignedAlloc(buffer_size, PaintOpBuffer::kPaintOpAlign)));
   std::unique_ptr<char, base::AlignedFreeDeleter> deserialized(
       static_cast<char*>(
-          base::AlignedAlloc(buffer_size, PaintOpBuffer::PaintOpAlign)));
+          base::AlignedAlloc(buffer_size, PaintOpBuffer::kPaintOpAlign)));
 
   // Used for QuickRejectDraw
   SkCanvas device(256, 256);
@@ -3353,7 +3356,7 @@
 
   std::unique_ptr<char, base::AlignedFreeDeleter> memory(
       static_cast<char*>(base::AlignedAlloc(PaintOpBuffer::kInitialBufferSize,
-                                            PaintOpBuffer::PaintOpAlign)));
+                                            PaintOpBuffer::kPaintOpAlign)));
   TestOptionsProvider options_provider;
   SimpleBufferSerializer serializer(memory.get(),
                                     PaintOpBuffer::kInitialBufferSize,
@@ -3521,7 +3524,7 @@
 TEST(PaintOpBufferTest, PaintRecordShaderSerialization) {
   std::unique_ptr<char, base::AlignedFreeDeleter> memory(
       static_cast<char*>(base::AlignedAlloc(PaintOpBuffer::kInitialBufferSize,
-                                            PaintOpBuffer::PaintOpAlign)));
+                                            PaintOpBuffer::kPaintOpAlign)));
   sk_sp<PaintOpBuffer> record_buffer(new PaintOpBuffer);
   record_buffer->push<DrawRectOp>(SkRect::MakeXYWH(0, 0, 1, 1), PaintFlags());
 
@@ -3573,7 +3576,7 @@
      DrawSkottieOpSerializationFailureFromUnPrivilegedProcess) {
   std::unique_ptr<char, base::AlignedFreeDeleter> memory(
       static_cast<char*>(base::AlignedAlloc(PaintOpBuffer::kInitialBufferSize,
-                                            PaintOpBuffer::PaintOpAlign)));
+                                            PaintOpBuffer::kPaintOpAlign)));
 
   scoped_refptr<SkottieWrapper> skottie =
       CreateSkottie(gfx::Size(100, 100), /*duration_secs=*/1);
@@ -3808,7 +3811,7 @@
 
   std::unique_ptr<char, base::AlignedFreeDeleter> memory(
       static_cast<char*>(base::AlignedAlloc(PaintOpBuffer::kInitialBufferSize,
-                                            PaintOpBuffer::PaintOpAlign)));
+                                            PaintOpBuffer::kPaintOpAlign)));
   TestOptionsProvider options_provider;
   PaintOpWriter writer(memory.get(), PaintOpBuffer::kInitialBufferSize,
                        options_provider.serialize_options(),
@@ -3842,7 +3845,7 @@
                                 src, dst, SkCanvas::kStrict_SrcRectConstraint);
   std::unique_ptr<char, base::AlignedFreeDeleter> memory(
       static_cast<char*>(base::AlignedAlloc(PaintOpBuffer::kInitialBufferSize,
-                                            PaintOpBuffer::PaintOpAlign)));
+                                            PaintOpBuffer::kPaintOpAlign)));
   TestOptionsProvider options_provider;
   SimpleBufferSerializer serializer(memory.get(),
                                     PaintOpBuffer::kInitialBufferSize,
@@ -3872,7 +3875,7 @@
 
   std::unique_ptr<char, base::AlignedFreeDeleter> memory(
       static_cast<char*>(base::AlignedAlloc(PaintOpBuffer::kInitialBufferSize,
-                                            PaintOpBuffer::PaintOpAlign)));
+                                            PaintOpBuffer::kPaintOpAlign)));
   TestOptionsProvider options_provider;
   SimpleBufferSerializer serializer(memory.get(),
                                     PaintOpBuffer::kInitialBufferSize,
@@ -3900,7 +3903,7 @@
   // Generate serialized |memory|.
   std::unique_ptr<char, base::AlignedFreeDeleter> memory(
       static_cast<char*>(base::AlignedAlloc(PaintOpBuffer::kInitialBufferSize,
-                                            PaintOpBuffer::PaintOpAlign)));
+                                            PaintOpBuffer::kPaintOpAlign)));
   size_t memory_written = 0;
   {
     auto buffer = sk_make_sp<PaintOpBuffer>();
@@ -3919,7 +3922,7 @@
   // a scale factor.
   std::unique_ptr<char, base::AlignedFreeDeleter> memory_scaled(
       static_cast<char*>(base::AlignedAlloc(PaintOpBuffer::kInitialBufferSize,
-                                            PaintOpBuffer::PaintOpAlign)));
+                                            PaintOpBuffer::kPaintOpAlign)));
   size_t memory_scaled_written = 0;
   {
     auto buffer = sk_make_sp<PaintOpBuffer>();
@@ -4010,7 +4013,7 @@
   // Generate serialized |memory|.
   std::unique_ptr<char, base::AlignedFreeDeleter> memory(
       static_cast<char*>(base::AlignedAlloc(PaintOpBuffer::kInitialBufferSize,
-                                            PaintOpBuffer::PaintOpAlign)));
+                                            PaintOpBuffer::kPaintOpAlign)));
   auto buffer = sk_make_sp<PaintOpBuffer>();
   PaintFlags flags;
   flags.setShader(shader);
@@ -4054,7 +4057,7 @@
 
   std::unique_ptr<char, base::AlignedFreeDeleter> memory(
       static_cast<char*>(base::AlignedAlloc(PaintOpBuffer::kInitialBufferSize,
-                                            PaintOpBuffer::PaintOpAlign)));
+                                            PaintOpBuffer::kPaintOpAlign)));
   TestOptionsProvider options_provider;
   SimpleBufferSerializer serializer(memory.get(),
                                     PaintOpBuffer::kInitialBufferSize,
@@ -4089,7 +4092,7 @@
 
   std::unique_ptr<char, base::AlignedFreeDeleter> memory(
       static_cast<char*>(base::AlignedAlloc(PaintOpBuffer::kInitialBufferSize,
-                                            PaintOpBuffer::PaintOpAlign)));
+                                            PaintOpBuffer::kPaintOpAlign)));
   TestOptionsProvider options_provider;
   SimpleBufferSerializer serializer(memory.get(),
                                     PaintOpBuffer::kInitialBufferSize,
@@ -4242,7 +4245,7 @@
 
   std::unique_ptr<char, base::AlignedFreeDeleter> memory(
       static_cast<char*>(base::AlignedAlloc(PaintOpBuffer::kInitialBufferSize,
-                                            PaintOpBuffer::PaintOpAlign)));
+                                            PaintOpBuffer::kPaintOpAlign)));
   auto buffer = sk_make_sp<PaintOpBuffer>();
   buffer->push<DrawPathOp>(path, flags, UsePaintCache::kEnabled);
   SimpleBufferSerializer serializer(memory.get(),
diff --git a/cc/paint/paint_op_perftest.cc b/cc/paint/paint_op_perftest.cc
index 08fd413..d38a6db6 100644
--- a/cc/paint/paint_op_perftest.cc
+++ b/cc/paint/paint_op_perftest.cc
@@ -34,10 +34,10 @@
                kTimeCheckInterval),
         serialized_data_(static_cast<char*>(
             base::AlignedAlloc(kMaxSerializedBufferBytes,
-                               PaintOpBuffer::PaintOpAlign))),
+                               PaintOpBuffer::kPaintOpAlign))),
         deserialized_data_(static_cast<char*>(
             base::AlignedAlloc(sizeof(LargestPaintOp),
-                               PaintOpBuffer::PaintOpAlign))) {}
+                               PaintOpBuffer::kPaintOpAlign))) {}
 
   void RunTest(const std::string& name, const PaintOpBuffer& buffer) {
     TestOptionsProvider test_options_provider;
diff --git a/cc/paint/paint_op_reader.cc b/cc/paint/paint_op_reader.cc
index 76f4dc87..df423a5 100644
--- a/cc/paint/paint_op_reader.cc
+++ b/cc/paint/paint_op_reader.cc
@@ -77,7 +77,7 @@
 
   if (input_size < *skip)
     return false;
-  if (*skip % PaintOpBuffer::PaintOpAlign != 0)
+  if (*skip % PaintOpBuffer::kPaintOpAlign != 0)
     return false;
   if (*type > static_cast<uint8_t>(PaintOpType::LastPaintOpType))
     return false;
@@ -1365,7 +1365,7 @@
 size_t PaintOpReader::Read(sk_sp<PaintRecord>* record) {
   size_t size_bytes = 0;
   ReadSize(&size_bytes);
-  AlignMemory(PaintOpBuffer::PaintOpAlign);
+  AlignMemory(PaintOpBuffer::kPaintOpAlign);
   if (enable_security_constraints_) {
     // Validate that the record was not serialized if security constraints are
     // enabled.
diff --git a/cc/paint/paint_op_writer.cc b/cc/paint/paint_op_writer.cc
index 0ac2a3c..758702a 100644
--- a/cc/paint/paint_op_writer.cc
+++ b/cc/paint/paint_op_writer.cc
@@ -919,7 +919,7 @@
 void PaintOpWriter::Write(const PaintRecord* record,
                           const gfx::Rect& playback_rect,
                           const gfx::SizeF& post_scale) {
-  AlignMemory(PaintOpBuffer::PaintOpAlign);
+  AlignMemory(PaintOpBuffer::kPaintOpAlign);
 
   // We need to record how many bytes we will serialize, but we don't know this
   // information until we do the serialization. So, skip the amount needed
diff --git a/cc/paint/paint_shader_unittest.cc b/cc/paint/paint_shader_unittest.cc
index 8a9a1fb..32fb20e 100644
--- a/cc/paint/paint_shader_unittest.cc
+++ b/cc/paint/paint_shader_unittest.cc
@@ -7,7 +7,7 @@
 #include "cc/paint/draw_image.h"
 #include "cc/paint/image_provider.h"
 #include "cc/paint/paint_image_builder.h"
-#include "cc/paint/paint_op_buffer.h"
+#include "cc/paint/paint_op.h"
 #include "cc/test/fake_paint_image_generator.h"
 #include "cc/test/skia_common.h"
 #include "cc/test/test_skcanvas.h"
diff --git a/cc/paint/record_paint_canvas.cc b/cc/paint/record_paint_canvas.cc
index 82871b04..d088663 100644
--- a/cc/paint/record_paint_canvas.cc
+++ b/cc/paint/record_paint_canvas.cc
@@ -7,6 +7,7 @@
 #include <utility>
 
 #include "cc/paint/paint_image_builder.h"
+#include "cc/paint/paint_op.h"
 #include "cc/paint/paint_record.h"
 #include "cc/paint/paint_recorder.h"
 #include "cc/paint/skottie_frame_data.h"
diff --git a/cc/paint/scoped_raster_flags_unittest.cc b/cc/paint/scoped_raster_flags_unittest.cc
index 1abb9462..f971db98 100644
--- a/cc/paint/scoped_raster_flags_unittest.cc
+++ b/cc/paint/scoped_raster_flags_unittest.cc
@@ -7,7 +7,7 @@
 #include <utility>
 #include "base/bind.h"
 #include "base/callback.h"
-#include "cc/paint/paint_op_buffer.h"
+#include "cc/paint/paint_op.h"
 #include "cc/paint/paint_shader.h"
 #include "cc/test/skia_common.h"
 #include "cc/test/test_paint_worklet_input.h"
diff --git a/cc/paint/solid_color_analyzer.cc b/cc/paint/solid_color_analyzer.cc
index dc8eca0..15967c6 100644
--- a/cc/paint/solid_color_analyzer.cc
+++ b/cc/paint/solid_color_analyzer.cc
@@ -8,7 +8,8 @@
 
 #include "base/trace_event/trace_event.h"
 #include "build/build_config.h"
-#include "cc/paint/paint_op_buffer.h"
+#include "cc/paint/paint_op.h"
+#include "cc/paint/paint_op_buffer_iterator.h"
 #include "third_party/skia/include/core/SkTypes.h"
 #include "third_party/skia/include/utils/SkNoDrawCanvas.h"
 
diff --git a/cc/scheduler/scheduler.cc b/cc/scheduler/scheduler.cc
index 8074e32..70d85187 100644
--- a/cc/scheduler/scheduler.cc
+++ b/cc/scheduler/scheduler.cc
@@ -13,6 +13,7 @@
 #include "base/check_op.h"
 #include "base/location.h"
 #include "base/metrics/histogram_macros.h"
+#include "base/task/delay_policy.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/trace_event/trace_event.h"
 #include "base/trace_event/traced_value.h"
@@ -776,7 +777,9 @@
         FROM_HERE, deadline_,
         base::BindOnce(&Scheduler::OnBeginImplFrameDeadline,
                        base::Unretained(this)),
-        base::ExactDeadline(true));
+        deadline_mode_ == DeadlineMode::LATE
+            ? base::subtle::DelayPolicy::kFlexibleNoSooner
+            : base::subtle::DelayPolicy::kPrecise);
   }
 }
 
diff --git a/cc/test/paint_op_helper.h b/cc/test/paint_op_helper.h
index d510ecac..4902217 100644
--- a/cc/test/paint_op_helper.h
+++ b/cc/test/paint_op_helper.h
@@ -10,7 +10,7 @@
 
 #include "base/strings/stringprintf.h"
 #include "cc/paint/paint_filter.h"
-#include "cc/paint/paint_op_buffer.h"
+#include "cc/paint/paint_op.h"
 #include "cc/paint/skottie_wrapper.h"
 
 namespace cc {
diff --git a/cc/test/test_options_provider.h b/cc/test/test_options_provider.h
index 4fce5ab..f5d0036 100644
--- a/cc/test/test_options_provider.h
+++ b/cc/test/test_options_provider.h
@@ -10,7 +10,7 @@
 #include "cc/paint/image_provider.h"
 #include "cc/paint/image_transfer_cache_entry.h"
 #include "cc/paint/paint_cache.h"
-#include "cc/paint/paint_op_buffer.h"
+#include "cc/paint/paint_op.h"
 #include "cc/paint/skottie_serialization_history.h"
 #include "cc/test/test_skcanvas.h"
 #include "cc/test/transfer_cache_test_helper.h"
diff --git a/cc/trees/property_tree_builder.cc b/cc/trees/property_tree_builder.cc
index cdf5e70..8f56fef4 100644
--- a/cc/trees/property_tree_builder.cc
+++ b/cc/trees/property_tree_builder.cc
@@ -14,7 +14,6 @@
 
 #include "base/auto_reset.h"
 #include "base/memory/raw_ptr.h"
-#include "base/memory/raw_ref.h"
 #include "cc/base/math_util.h"
 #include "cc/layers/layer.h"
 #include "cc/layers/layer_impl.h"
@@ -59,10 +58,10 @@
         root_layer_(layer_tree_host->root_layer()),
         mutator_host_(*layer_tree_host->mutator_host()),
         property_trees_(*layer_tree_host->property_trees()),
-        transform_tree_(property_trees_->transform_tree_mutable()),
-        clip_tree_(property_trees_->clip_tree_mutable()),
-        effect_tree_(property_trees_->effect_tree_mutable()),
-        scroll_tree_(property_trees_->scroll_tree_mutable()) {}
+        transform_tree_(property_trees_.transform_tree_mutable()),
+        clip_tree_(property_trees_.clip_tree_mutable()),
+        effect_tree_(property_trees_.effect_tree_mutable()),
+        scroll_tree_(property_trees_.scroll_tree_mutable()) {}
 
   void BuildPropertyTrees();
 
@@ -97,18 +96,18 @@
 
   raw_ptr<LayerTreeHost> layer_tree_host_;
   raw_ptr<Layer> root_layer_;
-  const raw_ref<MutatorHost> mutator_host_;
-  const raw_ref<PropertyTrees> property_trees_;
+  MutatorHost& mutator_host_;
+  PropertyTrees& property_trees_;
 
   // Ordinarily, it would not be OK to store references to these instances,
   // because doing so evades the protections of ProtectedSequenceSynchronizer.
   // It's permitted in this case because PropertyTreeBuilderContext is only ever
   // allocated on the stack, and it cannot initiate a protected sequence (by
   // calling into LayerTreeHost::WillCommit).
-  const raw_ref<TransformTree> transform_tree_;
-  const raw_ref<ClipTree> clip_tree_;
-  const raw_ref<EffectTree> effect_tree_;
-  const raw_ref<ScrollTree> scroll_tree_;
+  TransformTree& transform_tree_;
+  ClipTree& clip_tree_;
+  EffectTree& effect_tree_;
+  ScrollTree& scroll_tree_;
 };
 
 // Methods to query state from the AnimationHost ----------------------
@@ -227,7 +226,7 @@
     node.transform_id = created_transform_node
                             ? data_for_children->transform_tree_parent
                             : data_from_ancestor.transform_tree_parent;
-    data_for_children->clip_tree_parent = clip_tree_->Insert(node, parent_id);
+    data_for_children->clip_tree_parent = clip_tree_.Insert(node, parent_id);
   }
 
   layer->SetHasClipNode(requires_node);
@@ -251,19 +250,19 @@
       !layer->transform().IsIdentityOr2DTranslation();
 
   const bool has_potentially_animated_transform =
-      HasPotentiallyRunningTransformAnimation(*mutator_host_, layer);
+      HasPotentiallyRunningTransformAnimation(mutator_host_, layer);
 
   // A transform node is needed even for a finished animation, since differences
   // in the timing of animation state updates can mean that an animation that's
   // in the Finished state at tree-building time on the main thread is still in
   // the Running state right after commit on the compositor thread.
   const bool has_any_transform_animation = HasAnyAnimationTargetingProperty(
-      *mutator_host_, layer, TargetProperty::TRANSFORM);
-  DCHECK(!HasAnyAnimationTargetingProperty(*mutator_host_, layer,
+      mutator_host_, layer, TargetProperty::TRANSFORM);
+  DCHECK(!HasAnyAnimationTargetingProperty(mutator_host_, layer,
                                            TargetProperty::SCALE) &&
-         !HasAnyAnimationTargetingProperty(*mutator_host_, layer,
+         !HasAnyAnimationTargetingProperty(mutator_host_, layer,
                                            TargetProperty::ROTATE) &&
-         !HasAnyAnimationTargetingProperty(*mutator_host_, layer,
+         !HasAnyAnimationTargetingProperty(mutator_host_, layer,
                                            TargetProperty::TRANSLATE))
       << "individual transform properties only supported in layer lists mode";
 
@@ -292,15 +291,15 @@
     return false;
   }
 
-  transform_tree_->Insert(TransformNode(), parent_index);
-  TransformNode* node = transform_tree_->back();
+  transform_tree_.Insert(TransformNode(), parent_index);
+  TransformNode* node = transform_tree_.back();
   layer->SetTransformTreeIndex(node->id);
   data_for_children->transform_tree_parent = node->id;
 
   // For animation subsystem purposes, if this layer has a compositor element
   // id, we build a map from that id to this transform node.
   if (layer->element_id()) {
-    transform_tree_->SetElementIdForNodeId(node->id, layer->element_id());
+    transform_tree_.SetElementIdForNodeId(node->id, layer->element_id());
     node->element_id = layer->element_id();
   }
 
@@ -313,8 +312,8 @@
     DCHECK(parent_offset.IsZero());
     DCHECK(layer->transform().IsIdentity());
 
-    transform_tree_->SetRootScaleAndTransform(
-        transform_tree_->device_scale_factor(), gfx::Transform());
+    transform_tree_.SetRootScaleAndTransform(
+        transform_tree_.device_scale_factor(), gfx::Transform());
   } else {
     node->local = layer->transform();
     node->origin = layer->transform_origin();
@@ -323,13 +322,13 @@
   }
 
   node->has_potential_animation = has_potentially_animated_transform;
-  node->is_currently_animating = TransformIsAnimating(*mutator_host_, layer);
-  node->maximum_animation_scale = MaximumAnimationScale(*mutator_host_, layer);
+  node->is_currently_animating = TransformIsAnimating(mutator_host_, layer);
+  node->maximum_animation_scale = MaximumAnimationScale(mutator_host_, layer);
 
   node->scroll_offset = layer->scroll_offset();
 
   node->needs_local_transform_update = true;
-  transform_tree_->UpdateTransforms(node->id);
+  transform_tree_.UpdateTransforms(node->id);
 
   layer->SetOffsetToTransformParent(gfx::Vector2dF());
 
@@ -454,16 +453,16 @@
   const bool is_root = !layer->parent();
   const bool has_transparency = layer->EffectiveOpacity() != 1.f;
   const bool has_potential_opacity_animation =
-      HasPotentialOpacityAnimation(*mutator_host_, layer);
+      HasPotentialOpacityAnimation(mutator_host_, layer);
   const bool has_potential_filter_animation =
-      HasPotentiallyRunningFilterAnimation(*mutator_host_, layer);
+      HasPotentiallyRunningFilterAnimation(mutator_host_, layer);
 
   data_for_children->animation_axis_aligned_since_render_target &=
-      AnimationsPreserveAxisAlignment(*mutator_host_, layer);
+      AnimationsPreserveAxisAlignment(mutator_host_, layer);
   data_for_children->compound_transform_since_render_target *=
       layer->transform();
   auto render_surface_reason = ComputeRenderSurfaceReason(
-      *mutator_host_, layer,
+      mutator_host_, layer,
       data_for_children->compound_transform_since_render_target,
       data_for_children->animation_axis_aligned_since_render_target);
   bool should_create_render_surface =
@@ -472,7 +471,7 @@
   bool not_axis_aligned_since_last_clip =
       data_from_ancestor.not_axis_aligned_since_last_clip
           ? true
-          : !AnimationsPreserveAxisAlignment(*mutator_host_, layer) ||
+          : !AnimationsPreserveAxisAlignment(mutator_host_, layer) ||
                 !layer->transform().Preserves2dAxisAlignment();
   // A non-axis aligned clip may need a render surface. So, we create an effect
   // node.
@@ -492,8 +491,8 @@
     return false;
   }
 
-  int node_id = effect_tree_->Insert(EffectNode(), parent_id);
-  EffectNode* node = effect_tree_->back();
+  int node_id = effect_tree_.Insert(EffectNode(), parent_id);
+  EffectNode* node = effect_tree_.back();
 
   node->stable_id = layer->id();
   node->opacity = layer->opacity();
@@ -532,9 +531,8 @@
   node->has_potential_filter_animation = has_potential_filter_animation;
   node->subtree_hidden = layer->hide_layer_and_subtree();
   node->is_currently_animating_opacity =
-      OpacityIsAnimating(*mutator_host_, layer);
-  node->is_currently_animating_filter =
-      FilterIsAnimating(*mutator_host_, layer);
+      OpacityIsAnimating(mutator_host_, layer);
+  node->is_currently_animating_filter = FilterIsAnimating(mutator_host_, layer);
   node->effect_changed = layer->subtree_property_changed();
   node->subtree_has_copy_request = layer->subtree_has_copy_request();
   node->render_surface_reason = render_surface_reason;
@@ -568,7 +566,7 @@
       // In this case, we will create a transform node, so it's safe to use the
       // next available id from the transform tree as this effect node's
       // transform id.
-      node->transform_id = transform_tree_->next_available_id();
+      node->transform_id = transform_tree_.next_available_id();
     }
     node->clip_id = data_from_ancestor.clip_tree_parent;
   } else {
@@ -593,13 +591,13 @@
   // For animation subsystem purposes, if this layer has a compositor element
   // id, we build a map from that id to this effect node.
   if (layer->element_id()) {
-    effect_tree_->SetElementIdForNodeId(node_id, layer->element_id());
+    effect_tree_.SetElementIdForNodeId(node_id, layer->element_id());
   }
 
   std::vector<std::unique_ptr<viz::CopyOutputRequest>> layer_copy_requests;
   layer->TakeCopyRequests(&layer_copy_requests);
   for (auto& it : layer_copy_requests) {
-    effect_tree_->AddCopyRequest(node_id, std::move(it));
+    effect_tree_.AddCopyRequest(node_id, std::move(it));
   }
   layer_copy_requests.clear();
 
@@ -625,7 +623,7 @@
   }
 
   EffectNode* effect_node =
-      effect_tree_->Node(data_for_children->effect_tree_parent);
+      effect_tree_.Node(data_for_children->effect_tree_parent);
   const bool has_rounded_corner =
       effect_node->mask_filter_info.HasRoundedCorners();
   const bool has_gradient_mask =
@@ -693,18 +691,18 @@
     node.transform_id = data_for_children->transform_tree_parent;
     node.is_composited = true;
 
-    node_id = scroll_tree_->Insert(node, parent_id);
+    node_id = scroll_tree_.Insert(node, parent_id);
     data_for_children->scroll_tree_parent = node_id;
 
     // For animation subsystem purposes, if this layer has a compositor element
     // id, we build a map from that id to this scroll node.
     if (layer->element_id()) {
-      scroll_tree_->SetElementIdForNodeId(node_id, layer->element_id());
+      scroll_tree_.SetElementIdForNodeId(node_id, layer->element_id());
     }
 
     if (node.scrollable) {
-      scroll_tree_->SetBaseScrollOffset(layer->element_id(),
-                                        layer->scroll_offset());
+      scroll_tree_.SetBaseScrollOffset(layer->element_id(),
+                                       layer->scroll_offset());
     }
   }
 
@@ -726,7 +724,7 @@
 void PropertyTreeBuilderContext::BuildPropertyTreesInternal(
     Layer* layer,
     const DataForRecursion& data_from_parent) const {
-  layer->set_property_tree_sequence_number(property_trees_->sequence_number());
+  layer->set_property_tree_sequence_number(property_trees_.sequence_number());
 
   DataForRecursion data_for_children(data_from_parent);
   *data_for_children.subtree_has_rounded_corner = false;
@@ -748,7 +746,7 @@
   bool not_axis_aligned_since_last_clip =
       data_from_parent.not_axis_aligned_since_last_clip
           ? true
-          : !AnimationsPreserveAxisAlignment(*mutator_host_, layer) ||
+          : !AnimationsPreserveAxisAlignment(mutator_host_, layer) ||
                 !layer->transform().Preserves2dAxisAlignment();
   bool has_non_axis_aligned_clip =
       not_axis_aligned_since_last_clip && LayerClipsSubtree(layer);
@@ -772,18 +770,18 @@
 }
 
 void PropertyTreeBuilderContext::BuildPropertyTrees() {
-  property_trees_->set_is_main_thread(true);
-  property_trees_->set_is_active(false);
+  property_trees_.set_is_main_thread(true);
+  property_trees_.set_is_active(false);
 
   UpdateSubtreeHasCopyRequestRecursive(root_layer_);
 
-  if (!property_trees_->needs_rebuild()) {
-    clip_tree_->SetViewportClip(
+  if (!property_trees_.needs_rebuild()) {
+    clip_tree_.SetViewportClip(
         gfx::RectF(layer_tree_host_->device_viewport_rect()));
     // SetRootScaleAndTransform will be incorrect if the root layer has
     // non-zero position, so ensure it is zero.
     DCHECK(root_layer_->position().IsOrigin());
-    transform_tree_->SetRootScaleAndTransform(
+    transform_tree_.SetRootScaleAndTransform(
         layer_tree_host_->device_scale_factor(), gfx::Transform());
     return;
   }
@@ -807,14 +805,14 @@
           ? layer_tree_host_->background_color()
           : layer_tree_host_->background_color().makeOpaque();
 
-  property_trees_->clear();
-  transform_tree_->set_device_scale_factor(
+  property_trees_.clear();
+  transform_tree_.set_device_scale_factor(
       layer_tree_host_->device_scale_factor());
   ClipNode root_clip;
   root_clip.clip = gfx::RectF(layer_tree_host_->device_viewport_rect());
   root_clip.transform_id = kRootPropertyNodeId;
   data_for_recursion.clip_tree_parent =
-      clip_tree_->Insert(root_clip, kRootPropertyNodeId);
+      clip_tree_.Insert(root_clip, kRootPropertyNodeId);
 
   bool subtree_has_rounded_corner;
   data_for_recursion.subtree_has_rounded_corner = &subtree_has_rounded_corner;
@@ -822,14 +820,14 @@
   data_for_recursion.subtree_has_gradient_mask = &subtree_has_gradient_mask;
 
   BuildPropertyTreesInternal(root_layer_, data_for_recursion);
-  property_trees_->set_needs_rebuild(false);
+  property_trees_.set_needs_rebuild(false);
 
   // The transform tree is kept up to date as it is built, but the
   // combined_clips stored in the clip tree and the screen_space_opacity and
   // is_drawn in the effect tree aren't computed during tree building.
-  transform_tree_->set_needs_update(false);
-  clip_tree_->set_needs_update(true);
-  effect_tree_->set_needs_update(true);
+  transform_tree_.set_needs_update(false);
+  clip_tree_.set_needs_update(true);
+  effect_tree_.set_needs_update(true);
 }
 
 }  // namespace
diff --git a/cc/trees/single_thread_proxy.cc b/cc/trees/single_thread_proxy.cc
index 0d26622..e6a0d44 100644
--- a/cc/trees/single_thread_proxy.cc
+++ b/cc/trees/single_thread_proxy.cc
@@ -10,7 +10,6 @@
 
 #include "base/auto_reset.h"
 #include "base/bind.h"
-#include "base/logging.h"
 #include "base/memory/ptr_util.h"
 #include "base/notreached.h"
 #include "base/synchronization/waitable_event.h"
diff --git a/cc/trees/swap_promise_manager.cc b/cc/trees/swap_promise_manager.cc
index 011af65..cee8978b 100644
--- a/cc/trees/swap_promise_manager.cc
+++ b/cc/trees/swap_promise_manager.cc
@@ -6,8 +6,6 @@
 
 #include <utility>
 
-#include "base/logging.h"
-#include "base/time/time.h"
 #include "cc/trees/latency_info_swap_promise_monitor.h"
 #include "cc/trees/swap_promise.h"
 
diff --git a/chrome/VERSION b/chrome/VERSION
index 1530c7df..ba8010f 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=110
 MINOR=0
-BUILD=5466
+BUILD=5468
 PATCH=0
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinator.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinator.java
index 222f167..0541ec8 100644
--- a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinator.java
+++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinator.java
@@ -586,6 +586,13 @@
         getFeatureEngagementTracker().notifyEvent(EventConstants.FEED_SWIPE_REFRESHED);
     }
 
+    public void nonSwipeRefresh() {
+        if (mSwipeRefreshLayout != null) {
+            mSwipeRefreshLayout.startRefreshingAtTheBottom();
+        }
+        onRefresh();
+    }
+
     void updateReloadButtonVisibility(boolean isReloading) {
         Toolbar toolbar = mToolbarSupplier.get();
         if (toolbar != null) {
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceMediator.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceMediator.java
index 720ea56..729eec6 100644
--- a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceMediator.java
+++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceMediator.java
@@ -176,7 +176,7 @@
 
         @Override
         public void refreshStream() {
-            mCoordinator.onRefresh();
+            mCoordinator.nonSwipeRefresh();
         }
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
index 776975c7..d488e73 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
@@ -198,6 +198,7 @@
 import org.chromium.components.browser_ui.settings.SettingsLauncher;
 import org.chromium.components.browser_ui.util.BrowserControlsVisibilityDelegate;
 import org.chromium.components.browser_ui.util.ComposedBrowserControlsVisibilityDelegate;
+import org.chromium.components.browser_ui.util.FirstDrawDetector;
 import org.chromium.components.browser_ui.widget.gesture.BackPressHandler;
 import org.chromium.components.embedder_support.util.UrlConstants;
 import org.chromium.components.embedder_support.util.UrlUtilities;
@@ -1798,6 +1799,12 @@
         mContentContainer = (ViewGroup) findViewById(android.R.id.content);
         mControlContainer = (ToolbarControlContainer) findViewById(R.id.control_container);
 
+        // Instead of overriding AsyncInitializationActivity#onFirstDrawComplete like the other
+        // activities, we're adding our own draw detector here because this activity's draw can be
+        // blocked by AppLaunchDrawBlocker, and #onFirstDrawComplete doesn't account for that.
+        FirstDrawDetector.waitForFirstDrawStrict(
+                mContentContainer, () -> FontPreloader.getInstance().onFirstDrawTabbedActivity());
+
         Supplier<Boolean> dialogVisibilitySupplier = null;
         if (TabUiFeatureUtilities.isTabGroupsAndroidEnabled(this)) {
             dialogVisibilitySupplier = () -> {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/creator/CreatorActionDelegateImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/app/creator/CreatorActionDelegateImpl.java
index b20f3b4..03b7cf1 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/app/creator/CreatorActionDelegateImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/creator/CreatorActionDelegateImpl.java
@@ -4,28 +4,65 @@
 
 package org.chromium.chrome.browser.app.creator;
 
+import android.content.Context;
+
 import org.chromium.base.Callback;
+import org.chromium.base.Log;
+import org.chromium.base.ThreadUtils;
+import org.chromium.chrome.browser.bookmarks.BookmarkModel;
+import org.chromium.chrome.browser.bookmarks.BookmarkUtils;
+import org.chromium.chrome.browser.creator.CreatorCoordinator;
 import org.chromium.chrome.browser.feed.FeedActionDelegate;
+import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.browser.tab.TabLaunchType;
+import org.chromium.chrome.browser.tabmodel.document.TabDelegate;
+import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
 import org.chromium.content_public.browser.LoadUrlParams;
+import org.chromium.ui.mojom.WindowOpenDisposition;
+import org.chromium.url.GURL;
 
 /** Implements some actions for the Feed */
 public class CreatorActionDelegateImpl implements FeedActionDelegate {
-    public CreatorActionDelegateImpl() {}
+    private static final String TAG = "Cormorant";
+
+    private final Context mActivityContext;
+    private final Profile mProfile;
+    private final SnackbarManager mSnackbarManager;
+    private CreatorCoordinator mCreatorCoordinator;
+
+    public CreatorActionDelegateImpl(Context activityContext, Profile profile,
+            SnackbarManager snackbarManager, CreatorCoordinator creatorCoordinator) {
+        mActivityContext = activityContext;
+        mProfile = profile;
+        mSnackbarManager = snackbarManager;
+        mCreatorCoordinator = creatorCoordinator;
+    }
+
     @Override
     public void downloadPage(String url) {
-        // TODO(crbug/1385185) Add glue code to access download page action.
+        // Unimplemented.
     }
 
     @Override
     public void openSuggestionUrl(int disposition, LoadUrlParams params, boolean inGroup,
             Runnable onPageLoaded, Callback<VisitResult> onVisitComplete) {
+        // Back-of-card actions
+        if (disposition == WindowOpenDisposition.NEW_FOREGROUND_TAB
+                || disposition == WindowOpenDisposition.NEW_BACKGROUND_TAB
+                || disposition == WindowOpenDisposition.OFF_THE_RECORD) {
+            boolean offTheRecord = (disposition == WindowOpenDisposition.OFF_THE_RECORD);
+            new TabDelegate(offTheRecord).createNewTab(params, TabLaunchType.FROM_LINK, null);
+            return;
+        } else if (disposition == WindowOpenDisposition.CURRENT_TAB) {
+            mCreatorCoordinator.requestOpenSheet(new GURL(params.getUrl()));
+            return;
+        }
         // TODO(crbug.com/1395448) open in ephemeral tab or thin web view.
+        Log.w(TAG, "OpenSuggestionUrl: Unhandled disposition " + disposition);
     }
 
     @Override
-    public void openUrl(int disposition, LoadUrlParams params) {
-        // TODO(crbug.com/1395448) open in ephemeral tab or thin web view.
-    }
+    public void openUrl(int disposition, LoadUrlParams params) {}
 
     @Override
     public void openHelpPage() {
@@ -34,12 +71,19 @@
 
     @Override
     public void addToReadingList(String title, String url) {
-        // TODO(crbug/1385187) create glue code for accessing bookmark model.
+        // TODO(crbug/1399617) Eliminate code duplication with
+        //     FeedActionDelegateImpl
+        BookmarkModel bookmarkModel = BookmarkModel.getForProfile(mProfile);
+        bookmarkModel.finishLoadingBookmarkModel(() -> {
+            assert ThreadUtils.runningOnUiThread();
+            BookmarkUtils.addToReadingList(
+                    new GURL(url), title, mSnackbarManager, bookmarkModel, mActivityContext);
+        });
     }
 
     @Override
     public void openCrow(String url) {
-        // Not sure if this needs to be implemented.
+        // Unused; feature is deprecated and does not launch from the feed.
     }
 
     @Override
@@ -56,4 +100,4 @@
     public void showSignInActivity() {
         // TODO(crbug.com/1395449)
     }
-}
\ No newline at end of file
+}
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 4f26754b..20851dd 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
@@ -15,6 +15,7 @@
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ActivityTabProvider;
 import org.chromium.chrome.browser.SnackbarActivity;
+import org.chromium.chrome.browser.WebContentsFactory;
 import org.chromium.chrome.browser.compositor.bottombar.ephemeraltab.EphemeralTabCoordinator;
 import org.chromium.chrome.browser.creator.CreatorCoordinator;
 import org.chromium.chrome.browser.feedback.HelpAndFeedbackLauncherImpl;
@@ -23,8 +24,12 @@
 import org.chromium.chrome.browser.share.ShareDelegate;
 import org.chromium.chrome.browser.share.ShareDelegateImpl;
 import org.chromium.chrome.browser.share.ShareDelegateSupplier;
+import org.chromium.chrome.browser.tab.TabLaunchType;
+import org.chromium.chrome.browser.tabmodel.document.TabDelegate;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
 import org.chromium.components.browser_ui.widget.scrim.ScrimCoordinator;
+import org.chromium.content_public.browser.LoadUrlParams;
+import org.chromium.content_public.browser.WebContents;
 import org.chromium.ui.base.ActivityWindowAndroid;
 import org.chromium.ui.base.IntentRequestTracker;
 
@@ -64,8 +69,9 @@
         super.onCreate(savedInstanceState);
         IntentRequestTracker intentRequestTracker = IntentRequestTracker.createFromActivity(this);
         mWindowAndroid = new ActivityWindowAndroid(this, false, intentRequestTracker);
-        CreatorCoordinator coordinator = new CreatorCoordinator(
-                this, mWebFeedId, getSnackbarManager(), mWindowAndroid, mProfile, mTitle, mUrl);
+        CreatorCoordinator coordinator = new CreatorCoordinator(this, mWebFeedId,
+                getSnackbarManager(), mWindowAndroid, mProfile, mTitle, mUrl,
+                this::createWebContents, this::createNewTab, new ShareDelegateSupplier());
 
         mBottomSheetController = coordinator.getBottomSheetController();
         ShareDelegate shareDelegate = new ShareDelegateImpl(mBottomSheetController,
@@ -74,7 +80,8 @@
                 new ShareDelegateImpl.ShareSheetDelegate(),
                 /* isCustomTab */ false);
         mShareDelegateSupplier.set(shareDelegate);
-        mCreatorActionDelegate = new CreatorActionDelegateImpl();
+        mCreatorActionDelegate =
+                new CreatorActionDelegateImpl(this, mProfile, getSnackbarManager(), coordinator);
         coordinator.initFeedStream(mCreatorActionDelegate,
                 HelpAndFeedbackLauncherImpl.getInstance(), mShareDelegateSupplier);
 
@@ -94,4 +101,14 @@
         }
         return super.onOptionsItemSelected(item);
     }
+
+    // This implements the CreatorWebContents interface.
+    public WebContents createWebContents() {
+        return WebContentsFactory.createWebContents(mProfile, true);
+    }
+
+    // This implements the CreatorOpenTab interface.
+    public void createNewTab(LoadUrlParams params) {
+        new TabDelegate(false).createNewTab(params, TabLaunchType.FROM_LINK, null);
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/base/ServiceTracingProxyProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/base/ServiceTracingProxyProvider.java
index 3a00d99..813fc473 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/base/ServiceTracingProxyProvider.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/base/ServiceTracingProxyProvider.java
@@ -129,6 +129,10 @@
             for (int i = 0; i < mServiceCacheProxied.length; ++i) {
                 mServiceCacheProxied[i] = new AtomicBoolean(false);
             }
+            // Force the window service to be accessed and added to the service cache immediately.
+            // This will make sure it is proxied before ViewRootImpl can cache an unproxied
+            // WindowSession.
+            unwrappedBaseContext.getSystemService(Context.WINDOW_SERVICE);
         } catch (Throwable throwable) {
             Log.d(TAG, TRACE_FAILED, throwable);
             mServiceCache = new Object[0];
@@ -168,8 +172,10 @@
             // Class defers to WindowManagerGlobal.
             Class clazz = Class.forName("android.view.WindowManagerGlobal");
             Object managerGlobal = callNoArgMethod(null, clazz, "getInstance");
-            // Static service is unpopulated until used.
+            // Static service and WindowSession are unpopulated until used. Access them now so that
+            // the fields are populated when we inspect them for proxying.
             callNoArgMethod(null, managerGlobal.getClass(), "getWindowManagerService");
+            callNoArgMethod(null, managerGlobal.getClass(), "getWindowSession");
             return managerGlobal;
         }
         if (service.getClass().equals(ActivityManager.class)) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/content/TabContentManager.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/content/TabContentManager.java
index 45f6b4f..b75bd3b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/content/TabContentManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/content/TabContentManager.java
@@ -303,12 +303,15 @@
             topMargin = params.topMargin;
         }
 
+        int width = (int) ((viewToDraw.getMeasuredWidth() + leftMargin) * mThumbnailScale);
+        int height = (int) ((viewToDraw.getMeasuredHeight() + topMargin - overlayTranslateY)
+                * mThumbnailScale);
+        if (width <= 0 || height <= 0) {
+            return null;
+        }
+
         try {
-            bitmap = Bitmap.createBitmap(
-                    (int) ((viewToDraw.getMeasuredWidth() + leftMargin) * mThumbnailScale),
-                    (int) ((viewToDraw.getMeasuredHeight() + topMargin - overlayTranslateY)
-                            * mThumbnailScale),
-                    Bitmap.Config.ARGB_8888);
+            bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
         } catch (OutOfMemoryError ex) {
             return null;
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
index 5d00a1b3..a8d425fd 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
@@ -149,6 +149,13 @@
     }
 
     @Override
+    protected void onFirstDrawComplete() {
+        super.onFirstDrawComplete();
+
+        FontPreloader.getInstance().onFirstDrawCustomTabActivity();
+    }
+
+    @Override
     public void finishNativeInitialization() {
         if (!mIntentDataProvider.isInfoPage()) {
             FirstRunSignInProcessor.openSyncSettingsIfScheduled(this);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunActivity.java
index 5c8d7c2..fdd031b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunActivity.java
@@ -319,6 +319,13 @@
     }
 
     @Override
+    protected void onFirstDrawComplete() {
+        super.onFirstDrawComplete();
+
+        FontPreloader.getInstance().onFirstDrawFre();
+    }
+
+    @Override
     public void finishNativeInitialization() {
         super.finishNativeInitialization();
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/fonts/FontPreloader.java b/chrome/android/java/src/org/chromium/chrome/browser/fonts/FontPreloader.java
index d6b3ddc..8589d01 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/fonts/FontPreloader.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/fonts/FontPreloader.java
@@ -17,21 +17,31 @@
 import org.chromium.chrome.R;
 
 /**
- * Class to load downloadable fonts async. It should be used by calling {@link FontPreloader#load)
- * early in the app start-up, e.g. {@link Application#onCreate}.
+ * Class to load downloadable fonts async and emit histograms related to the availability of these
+ * fonts. It should be used by calling {@link FontPreloader#load) early in the app start-up, e.g.
+ * {@link Application#onCreate}. The {@link Activity}s should also call the #on* methods to notify
+ * this class of the events as they are used to record metrics.
  */
 public class FontPreloader {
     private static FontPreloader sInstance;
 
     private static final Integer[] FONTS = {R.font.chrome_google_sans,
             R.font.chrome_google_sans_medium, R.font.chrome_google_sans_bold};
+
     private static final String UMA_PREFIX = "Android.Fonts";
+
     private static final String UMA_FONTS_RETRIEVED_BEFORE_INFLATION =
             "TimeDownloadableFontsRetrievedBeforePostInflationStartup";
     private static final String UMA_FONTS_RETRIEVED_AFTER_ON_CREATE =
             "TimeToRetrieveDownloadableFontsAfterOnCreate";
     private static final String UMA_FONTS_RETRIEVED_AFTER_INFLATION =
             "TimeDownloadableFontsRetrievedAfterPostInflationStartup";
+
+    private static final String UMA_FONTS_RETRIEVED_BEFORE_FIRST_DRAW =
+            "TimeDownloadableFontsRetrievedBeforeFirstDraw";
+    private static final String UMA_FONTS_RETRIEVED_AFTER_FIRST_DRAW =
+            "TimeDownloadableFontsRetrievedAfterFirstDraw";
+
     private static final String UMA_FRE = "FirstRunActivity";
     private static final String UMA_TABBED_ACTIVITY = "ChromeTabbedActivity";
     private static final String UMA_CUSTOM_TAB_ACTIVITY = "CustomTabActivity";
@@ -40,9 +50,13 @@
 
     private Integer[] mFonts = FONTS;
     private boolean mInitialized;
-    private Long mTimeOfFirstEvent;
+    // Time of first event between |#onAllFontsRetrieved()| and |#onPostInflationStartup*()|.
+    private Long mTimeOfFirstEventForPostInflation;
     private long mTimeOfLoadCall;
-    private String mActivityName;
+    private String mActivityNameForPostInflation;
+    // Time of first event between |#onAllFontsRetrieved()| and |#onFirstDraw*()|.
+    private Long mTimeOfFirstEventForFirstDraw;
+    private String mActivityNameForFirstDraw;
 
     @VisibleForTesting
     FontPreloader(Integer[] fonts) {
@@ -88,6 +102,14 @@
     }
 
     /**
+     * Should be called from FirstRunActivity to notify this class of the first draw.
+     */
+    public void onFirstDrawFre() {
+        mThreadChecker.assertOnValidThread();
+        onFirstDraw(UMA_FRE);
+    }
+
+    /**
      * Should be called from ChromeTabbedActivity to notify this class of post-inflation startup.
      */
     public void onPostInflationStartupTabbedActivity() {
@@ -96,6 +118,16 @@
     }
 
     /**
+     * Should be called from ChromeTabbedActivity to notify this class of the first draw. The first
+     * draw of ChromeTabbedActivity may be blocked by AppLaunchDrawBlocker, so the caller should
+     * account for that.
+     */
+    public void onFirstDrawTabbedActivity() {
+        mThreadChecker.assertOnValidThread();
+        onFirstDraw(UMA_TABBED_ACTIVITY);
+    }
+
+    /**
      * Should be called from CustomTabActivity to notify this class of post-inflation startup.
      */
     public void onPostInflationStartupCustomTabActivity() {
@@ -103,20 +135,49 @@
         onPostInflationStartup(UMA_CUSTOM_TAB_ACTIVITY);
     }
 
+    /**
+     * Should be called from CustomTabActivity to notify this class of the first draw.
+     */
+    public void onFirstDrawCustomTabActivity() {
+        mThreadChecker.assertOnValidThread();
+        onFirstDraw(UMA_CUSTOM_TAB_ACTIVITY);
+    }
+
     private void onPostInflationStartup(String activityName) {
         // Multiple activities will notify us when they are post inflation, but only the first one
         // matters. It is the one we're racing against to load fonts before they're needed.
-        if (mActivityName != null) return;
-        mActivityName = activityName;
+        if (mActivityNameForPostInflation != null) return;
+        mActivityNameForPostInflation = activityName;
 
         final long time = SystemClock.elapsedRealtime();
-        if (mTimeOfFirstEvent == null) {
-            mTimeOfFirstEvent = time;
+        if (mTimeOfFirstEventForPostInflation == null) {
+            mTimeOfFirstEventForPostInflation = time;
         } else {
             RecordHistogram.recordTimesHistogram(
                     String.format("%s.%s.%s", UMA_PREFIX, UMA_FONTS_RETRIEVED_BEFORE_INFLATION,
                             activityName),
-                    time - mTimeOfFirstEvent);
+                    time - mTimeOfFirstEventForPostInflation);
+        }
+    }
+
+    private void onFirstDraw(String activityName) {
+        // Multiple activities will notify us when they do their first draw, but only the first one
+        // matters.
+        if (mActivityNameForFirstDraw != null) return;
+        mActivityNameForFirstDraw = activityName;
+
+        long time = SystemClock.elapsedRealtime();
+        if (mTimeOfFirstEventForFirstDraw == null) {
+            mTimeOfFirstEventForFirstDraw = time;
+        } else {
+            RecordHistogram.recordTimesHistogram(
+                    String.format("%s.%s.%s", UMA_PREFIX, UMA_FONTS_RETRIEVED_BEFORE_FIRST_DRAW,
+                            mActivityNameForFirstDraw),
+                    time - mTimeOfFirstEventForFirstDraw);
+            // Also record one without the activity name for aggregation across all activities.
+            RecordHistogram.recordTimesHistogram(
+                    String.format("%s.%s", UMA_PREFIX, UMA_FONTS_RETRIEVED_BEFORE_FIRST_DRAW),
+                    time - mTimeOfFirstEventForFirstDraw);
         }
     }
 
@@ -125,13 +186,27 @@
         RecordHistogram.recordTimesHistogram(
                 String.format("%s.%s", UMA_PREFIX, UMA_FONTS_RETRIEVED_AFTER_ON_CREATE),
                 time - mTimeOfLoadCall);
-        if (mTimeOfFirstEvent == null) {
-            mTimeOfFirstEvent = time;
+
+        if (mTimeOfFirstEventForPostInflation == null) {
+            mTimeOfFirstEventForPostInflation = time;
         } else {
             RecordHistogram.recordTimesHistogram(
                     String.format("%s.%s.%s", UMA_PREFIX, UMA_FONTS_RETRIEVED_AFTER_INFLATION,
-                            mActivityName),
-                    time - mTimeOfFirstEvent);
+                            mActivityNameForPostInflation),
+                    time - mTimeOfFirstEventForPostInflation);
+        }
+
+        if (mTimeOfFirstEventForFirstDraw == null) {
+            mTimeOfFirstEventForFirstDraw = time;
+        } else {
+            RecordHistogram.recordTimesHistogram(
+                    String.format("%s.%s.%s", UMA_PREFIX, UMA_FONTS_RETRIEVED_AFTER_FIRST_DRAW,
+                            mActivityNameForFirstDraw),
+                    time - mTimeOfFirstEventForFirstDraw);
+            // Also record one without the activity name for aggregation across all activities.
+            RecordHistogram.recordTimesHistogram(
+                    String.format("%s.%s", UMA_PREFIX, UMA_FONTS_RETRIEVED_AFTER_FIRST_DRAW),
+                    time - mTimeOfFirstEventForFirstDraw);
         }
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/init/AsyncInitializationActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/init/AsyncInitializationActivity.java
index 19d5c70..222893d8 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/init/AsyncInitializationActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/init/AsyncInitializationActivity.java
@@ -615,7 +615,12 @@
         if (mWindowAndroid != null) mWindowAndroid.onContextMenuClosed();
     }
 
-    private void onFirstDrawComplete() {
+    /**
+     * Called when the content view gets drawn for the first time. See {@link FirstDrawDetector} for
+     * details on the exact signals used to call this.
+     */
+    @CallSuper
+    protected void onFirstDrawComplete() {
         assert mFirstDrawComplete;
         assert !mStartupDelayed;
         TraceEvent.instant("onFirstDrawComplete");
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/fonts/FontPreloaderUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/fonts/FontPreloaderUnitTest.java
index 9f9fbe5..4804fd21c2 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/fonts/FontPreloaderUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/fonts/FontPreloaderUnitTest.java
@@ -54,6 +54,10 @@
             "Android.Fonts.TimeDownloadableFontsRetrievedAfterPostInflationStartup";
     private static final String BEFORE_INFLATION =
             "Android.Fonts.TimeDownloadableFontsRetrievedBeforePostInflationStartup";
+    private static final String BEFORE_FIRST_DRAW =
+            "Android.Fonts.TimeDownloadableFontsRetrievedBeforeFirstDraw";
+    private static final String AFTER_FIRST_DRAW =
+            "Android.Fonts.TimeDownloadableFontsRetrievedAfterFirstDraw";
     private static final String FRE = ".FirstRunActivity";
     private static final String TABBED = ".ChromeTabbedActivity";
     private static final String CUSTOM_TAB = ".CustomTabActivity";
@@ -211,6 +215,14 @@
         assertHistogramNotRecorded(AFTER_INFLATION + FRE);
         assertHistogramNotRecorded(BEFORE_INFLATION + CUSTOM_TAB);
         assertHistogramNotRecorded(AFTER_INFLATION + CUSTOM_TAB);
+        assertHistogramNotRecorded(BEFORE_FIRST_DRAW + TABBED);
+        assertHistogramNotRecorded(AFTER_FIRST_DRAW + TABBED);
+        assertHistogramNotRecorded(BEFORE_FIRST_DRAW + FRE);
+        assertHistogramNotRecorded(AFTER_FIRST_DRAW + FRE);
+        assertHistogramNotRecorded(BEFORE_FIRST_DRAW + CUSTOM_TAB);
+        assertHistogramNotRecorded(AFTER_FIRST_DRAW + CUSTOM_TAB);
+        assertHistogramNotRecorded(BEFORE_FIRST_DRAW);
+        assertHistogramNotRecorded(AFTER_FIRST_DRAW);
 
         SystemClock.setCurrentTimeMillis(INITIAL_TIME + 500);
         ShadowResourcesCompat.loadFont();
@@ -219,7 +231,7 @@
     }
 
     @Test
-    public void testHistogramRecordedForOnlyOneActivity_BeforeFREInflation() {
+    public void testHistogramRecordedForOnlyFirstActivity_BeforeFREInflation() {
         SystemClock.setCurrentTimeMillis(INITIAL_TIME + 10);
         fakeLoadAllFonts();
         SystemClock.setCurrentTimeMillis(INITIAL_TIME + 20);
@@ -232,7 +244,7 @@
     }
 
     @Test
-    public void testHistogramRecordedForOnlyOneActivity_AfterFREInflation() {
+    public void testHistogramRecordedForOnlyFirstActivity_AfterFREInflation() {
         SystemClock.setCurrentTimeMillis(INITIAL_TIME + 64);
         mFontPreloader.onPostInflationStartupFre();
         SystemClock.setCurrentTimeMillis(INITIAL_TIME + 96);
@@ -245,7 +257,7 @@
     }
 
     @Test
-    public void testHistogramRecordedForOnlyOneActivity_BeforeCCTInflation() {
+    public void testHistogramRecordedForOnlyFirstActivity_BeforeCCTInflation() {
         SystemClock.setCurrentTimeMillis(INITIAL_TIME + 1);
         fakeLoadAllFonts();
         SystemClock.setCurrentTimeMillis(INITIAL_TIME + 11);
@@ -258,7 +270,7 @@
     }
 
     @Test
-    public void testHistogramRecordedForOnlyOneActivity_AfterCCTInflation() {
+    public void testHistogramRecordedForOnlyFirstActivity_AfterCCTInflation() {
         SystemClock.setCurrentTimeMillis(INITIAL_TIME + 32);
         mFontPreloader.onPostInflationStartupCustomTabActivity();
         SystemClock.setCurrentTimeMillis(INITIAL_TIME + 64);
@@ -286,6 +298,154 @@
         assertHistogramRecorded(AFTER_INFLATION + TABBED, 10);
     }
 
+    @Test
+    public void testAllFontsRetrievedAfterFirstDraw_FRE() {
+        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 100);
+        mFontPreloader.onFirstDrawFre();
+        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 228);
+        fakeLoadAllFonts();
+
+        assertHistogramRecorded(AFTER_FIRST_DRAW, 128);
+        assertHistogramRecorded(AFTER_FIRST_DRAW + FRE, 128);
+        assertHistogramNotRecorded(BEFORE_FIRST_DRAW + FRE);
+        assertHistogramNotRecorded(BEFORE_FIRST_DRAW + TABBED);
+        assertHistogramNotRecorded(AFTER_FIRST_DRAW + TABBED);
+        assertHistogramNotRecorded(BEFORE_FIRST_DRAW + CUSTOM_TAB);
+        assertHistogramNotRecorded(AFTER_FIRST_DRAW + CUSTOM_TAB);
+    }
+
+    @Test
+    public void testAllFontsRetrievedAfterFirstDraw_Tabbed() {
+        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 100);
+        mFontPreloader.onFirstDrawTabbedActivity();
+        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 113);
+        fakeLoadAllFonts();
+
+        assertHistogramRecorded(AFTER_FIRST_DRAW, 13);
+        assertHistogramRecorded(AFTER_FIRST_DRAW + TABBED, 13);
+        assertHistogramNotRecorded(BEFORE_FIRST_DRAW + TABBED);
+        assertHistogramNotRecorded(BEFORE_FIRST_DRAW + FRE);
+        assertHistogramNotRecorded(AFTER_FIRST_DRAW + FRE);
+        assertHistogramNotRecorded(BEFORE_FIRST_DRAW + CUSTOM_TAB);
+        assertHistogramNotRecorded(AFTER_FIRST_DRAW + CUSTOM_TAB);
+    }
+
+    @Test
+    public void testAllFontsRetrievedAfterFirstDraw_CustomTab() {
+        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 50);
+        mFontPreloader.onFirstDrawCustomTabActivity();
+        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 100);
+        fakeLoadAllFonts();
+
+        assertHistogramRecorded(AFTER_FIRST_DRAW, 50);
+        assertHistogramRecorded(AFTER_FIRST_DRAW + CUSTOM_TAB, 50);
+        assertHistogramNotRecorded(BEFORE_FIRST_DRAW + CUSTOM_TAB);
+        assertHistogramNotRecorded(BEFORE_FIRST_DRAW + FRE);
+        assertHistogramNotRecorded(AFTER_FIRST_DRAW + FRE);
+        assertHistogramNotRecorded(BEFORE_FIRST_DRAW + TABBED);
+        assertHistogramNotRecorded(AFTER_FIRST_DRAW + TABBED);
+    }
+
+    @Test
+    public void testAllFontsRetrievedBeforeFirstDraw_FRE() {
+        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 90);
+        fakeLoadAllFonts();
+        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 100);
+        mFontPreloader.onFirstDrawFre();
+
+        assertHistogramRecorded(BEFORE_FIRST_DRAW, 10);
+        assertHistogramRecorded(BEFORE_FIRST_DRAW + FRE, 10);
+        assertHistogramNotRecorded(AFTER_FIRST_DRAW + FRE);
+        assertHistogramNotRecorded(BEFORE_FIRST_DRAW + TABBED);
+        assertHistogramNotRecorded(AFTER_FIRST_DRAW + TABBED);
+        assertHistogramNotRecorded(BEFORE_FIRST_DRAW + CUSTOM_TAB);
+        assertHistogramNotRecorded(AFTER_FIRST_DRAW + CUSTOM_TAB);
+    }
+
+    @Test
+    public void testAllFontsRetrievedBeforeFirstDraw_Tabbed() {
+        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 123);
+        fakeLoadAllFonts();
+        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 246);
+        mFontPreloader.onFirstDrawTabbedActivity();
+
+        assertHistogramRecorded(BEFORE_FIRST_DRAW, 123);
+        assertHistogramRecorded(BEFORE_FIRST_DRAW + TABBED, 123);
+        assertHistogramNotRecorded(AFTER_FIRST_DRAW + TABBED);
+        assertHistogramNotRecorded(BEFORE_FIRST_DRAW + FRE);
+        assertHistogramNotRecorded(AFTER_FIRST_DRAW + FRE);
+        assertHistogramNotRecorded(BEFORE_FIRST_DRAW + CUSTOM_TAB);
+        assertHistogramNotRecorded(AFTER_FIRST_DRAW + CUSTOM_TAB);
+    }
+
+    @Test
+    public void testAllFontsRetrievedBeforeFirstDraw_CustomTab() {
+        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 100);
+        fakeLoadAllFonts();
+        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 119);
+        mFontPreloader.onFirstDrawCustomTabActivity();
+
+        assertHistogramRecorded(BEFORE_FIRST_DRAW, 19);
+        assertHistogramRecorded(BEFORE_FIRST_DRAW + CUSTOM_TAB, 19);
+        assertHistogramNotRecorded(AFTER_FIRST_DRAW + CUSTOM_TAB);
+        assertHistogramNotRecorded(BEFORE_FIRST_DRAW + FRE);
+        assertHistogramNotRecorded(AFTER_FIRST_DRAW + FRE);
+        assertHistogramNotRecorded(BEFORE_FIRST_DRAW + TABBED);
+        assertHistogramNotRecorded(AFTER_FIRST_DRAW + TABBED);
+    }
+
+    @Test
+    public void testHistogramRecordedForOnlyFirstActivity_BeforeFREDraw() {
+        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 10);
+        fakeLoadAllFonts();
+        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 20);
+        mFontPreloader.onFirstDrawFre();
+        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 30);
+        mFontPreloader.onFirstDrawTabbedActivity();
+
+        assertHistogramRecorded(BEFORE_FIRST_DRAW + FRE, 10);
+        assertHistogramNotRecorded(BEFORE_FIRST_DRAW + TABBED);
+    }
+
+    @Test
+    public void testHistogramRecordedForOnlyFirstActivity_AfterFREDraw() {
+        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 10);
+        mFontPreloader.onFirstDrawFre();
+        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 20);
+        fakeLoadAllFonts();
+        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 30);
+        mFontPreloader.onFirstDrawTabbedActivity();
+
+        assertHistogramRecorded(AFTER_FIRST_DRAW + FRE, 10);
+        assertHistogramNotRecorded(AFTER_FIRST_DRAW + TABBED);
+    }
+
+    @Test
+    public void testHistogramRecordedForOnlyFirstActivity_BeforeCCTDraw() {
+        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 100);
+        fakeLoadAllFonts();
+        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 200);
+        mFontPreloader.onFirstDrawCustomTabActivity();
+        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 300);
+        mFontPreloader.onFirstDrawTabbedActivity();
+
+        assertHistogramRecorded(BEFORE_FIRST_DRAW + CUSTOM_TAB, 100);
+        assertHistogramNotRecorded(BEFORE_FIRST_DRAW + TABBED);
+    }
+
+    @Test
+    public void testHistogramRecordedForOnlyFirstActivity_AfterCCTDraw() {
+        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 111);
+        mFontPreloader.onFirstDrawCustomTabActivity();
+        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 222);
+        fakeLoadAllFonts();
+        SystemClock.setCurrentTimeMillis(INITIAL_TIME + 300);
+        mFontPreloader.onFirstDrawTabbedActivity();
+
+        assertHistogramRecorded(AFTER_FIRST_DRAW + CUSTOM_TAB, 111);
+        assertHistogramNotRecorded(AFTER_FIRST_DRAW + TABBED);
+    }
+
     private void fakeLoadAllFonts() {
         for (int i = 0; i < 3; i++) {
             ShadowResourcesCompat.loadFont();
diff --git a/chrome/app/chrome_main_delegate.cc b/chrome/app/chrome_main_delegate.cc
index c873eaa..400cc6f 100644
--- a/chrome/app/chrome_main_delegate.cc
+++ b/chrome/app/chrome_main_delegate.cc
@@ -1005,6 +1005,50 @@
 }
 #endif
 
+void ChromeMainDelegate::SetupTracing() {
+  // It is necessary to reset the unique_ptr before assigning a new value to it.
+  // This is to ensure that g_main_thread_instance inside
+  // tracing_sampler_profiler.cc comes out correctly -- the old
+  // TracingSamplerProfiler must destruct and clear g_main_thread_instance
+  // before CreateOnMainThread() runs.
+  tracing_sampler_profiler_.reset();
+
+#if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX)
+  // Don't set up tracing in zygotes. Zygotes don't do much, and the tracing
+  // system won't work after a fork because all the thread IDs will change.
+  if (base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+          switches::kProcessType) == switches::kZygoteProcess) {
+    return;
+  }
+#endif  // #if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX)
+
+  // We pass in CreateCoreUnwindersFactory here since it lives in the chrome/
+  // layer while TracingSamplerProfiler is outside of chrome/.
+  //
+  // When we're the browser on android, use only libunwindstack for the tracing
+  // sampler profiler because it can support java frames which is essential for
+  // the main thread.
+  base::RepeatingCallback tracing_factory =
+      base::BindRepeating(&CreateCoreUnwindersFactory);
+  tracing::TracingSamplerProfiler::UnwinderType unwinder_type =
+      tracing::TracingSamplerProfiler::UnwinderType::kCustomAndroid;
+#if BUILDFLAG(IS_ANDROID)
+  // If we are the browser process (missing process type), then use the
+  // experimental libunwindstack unwinder.
+  if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
+          switches::kProcessType) &&
+      chrome::android::IsJavaDrivenFeatureEnabled(
+          chrome::android::kUseLibunwindstackNativeUnwinderAndroid)) {
+    tracing_factory = base::BindRepeating(&CreateLibunwindstackUnwinderFactory);
+    unwinder_type = tracing::TracingSamplerProfiler::UnwinderType::
+        kLibunwindstackUnwinderAndroid;
+  }
+#endif
+  tracing_sampler_profiler_ =
+      tracing::TracingSamplerProfiler::CreateOnMainThread(
+          std::move(tracing_factory), unwinder_type);
+}
+
 absl::optional<int> ChromeMainDelegate::BasicStartupComplete() {
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   ash::BootTimesRecorder::Get()->SaveChromeMainStats();
@@ -1053,30 +1097,7 @@
   content::Profiling::ProcessStarted();
 
   // Setup tracing sampler profiler as early as possible at startup if needed.
-  // We pass in CreateCoreUnwindersFactory here since it lives in the chrome/
-  // layer while TracingSamplerProfiler is outside of chrome/.
-  //
-  // When we're the browser on android use libunwindstack completely for tracing
-  // sampler profiler because it can support java frames which is essential for
-  // the main thread.
-  base::RepeatingCallback tracing_factory =
-      base::BindRepeating(&CreateCoreUnwindersFactory);
-  tracing::TracingSamplerProfiler::UnwinderType unwinder_type =
-      tracing::TracingSamplerProfiler::UnwinderType::kCustomAndroid;
-#if BUILDFLAG(IS_ANDROID)
-  // If we are the browser process (missing process type), then use the
-  // experimental libunwindstack unwinder.
-  if (!command_line.HasSwitch(switches::kProcessType) &&
-      chrome::android::IsJavaDrivenFeatureEnabled(
-          chrome::android::kUseLibunwindstackNativeUnwinderAndroid)) {
-    tracing_factory = base::BindRepeating(&CreateLibunwindstackUnwinderFactory);
-    unwinder_type = tracing::TracingSamplerProfiler::UnwinderType::
-        kLibunwindstackUnwinderAndroid;
-  }
-#endif
-  tracing_sampler_profiler_ =
-      tracing::TracingSamplerProfiler::CreateOnMainThread(
-          std::move(tracing_factory), unwinder_type);
+  SetupTracing();
 
 #if BUILDFLAG(IS_WIN)
   v8_crashpad_support::SetUp();
@@ -1645,6 +1666,9 @@
 }
 
 void ChromeMainDelegate::ZygoteForked() {
+  // Set up tracing for processes forked off a zygote.
+  SetupTracing();
+
   content::Profiling::ProcessStarted();
   if (content::Profiling::BeingProfiled()) {
     base::debug::RestartProfilingAfterFork();
diff --git a/chrome/app/chrome_main_delegate.h b/chrome/app/chrome_main_delegate.h
index de1b45f92..49993fbc 100644
--- a/chrome/app/chrome_main_delegate.h
+++ b/chrome/app/chrome_main_delegate.h
@@ -83,6 +83,10 @@
   // Initialization that happens in all process types.
   void CommonEarlyInitialization();
 
+  // Initializes |tracing_sampler_profiler_|. Deletes any existing
+  // |tracing_sampler_profiler_| as well.
+  void SetupTracing();
+
 #if BUILDFLAG(IS_MAC)
   void InitMacCrashReporter(const base::CommandLine& command_line,
                             const std::string& process_type);
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 94cb8422..eeb8ba3 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -790,12 +790,6 @@
           <message name="IDS_CONTENT_CONTEXT_LENS_REGION_SEARCH_ALT1" desc="The name of the Lens Region Search command in the content area context menu">
             Search page with <ph name="VISUAL_SEARCH_PROVIDER">$1<ex>Google Lens</ex></ph>
           </message>
-          <message name="IDS_CONTENT_CONTEXT_LENS_REGION_SEARCH_ALT2" desc="The name of the Lens Region Search command in the content area context menu">
-            Search any part of the page with <ph name="VISUAL_SEARCH_PROVIDER">$1<ex>Google Lens</ex></ph>
-          </message>
-          <message name="IDS_CONTENT_CONTEXT_LENS_REGION_SEARCH_ALT3" desc="The name of the Lens Region Search command in the content area context menu">
-            Search inside image with <ph name="VISUAL_SEARCH_PROVIDER">$1<ex>Google Lens</ex></ph>
-          </message>
           <message name="IDS_CONTENT_CONTEXT_LENS_REGION_SEARCH" desc="The name of the Lens Region Search command in the content area context menu">
             Search images with <ph name="VISUAL_SEARCH_PROVIDER">$1<ex>Google Lens</ex></ph>
           </message>
@@ -1059,12 +1053,6 @@
           <message name="IDS_CONTENT_CONTEXT_LENS_REGION_SEARCH_ALT1" desc="In Title Case: The name of the Lens Region Search command in the content area context menu">
             Search Page with <ph name="VISUAL_SEARCH_PROVIDER">$1<ex>Google</ex></ph>
           </message>
-          <message name="IDS_CONTENT_CONTEXT_LENS_REGION_SEARCH_ALT2" desc="In Title Case: The name of the Lens Region Search command in the content area context menu">
-            Search Any Part of the Page with <ph name="VISUAL_SEARCH_PROVIDER">$1<ex>Google Lens</ex></ph>
-          </message>
-          <message name="IDS_CONTENT_CONTEXT_LENS_REGION_SEARCH_ALT3" desc="In Title Case: The name of the Lens Region Search command in the content area context menu">
-              Search inside Image with <ph name="VISUAL_SEARCH_PROVIDER">$1<ex>Google Lens</ex></ph>
-          </message>
           <message name="IDS_CONTENT_CONTEXT_LENS_REGION_SEARCH" desc="In Title Case: The name of the Lens Region Search command in the content area context menu">
             Search Images with <ph name="VISUAL_SEARCH_PROVIDER">$1<ex>Google Lens</ex></ph>
           </message>
@@ -13818,14 +13806,8 @@
     </message>
     <!-- Lens Region Search bubble dialog -->
     <message name="IDS_LENS_REGION_SEARCH_BUBBLE_TEXT" desc="Text that is shown in the Lens Region Search education bubble when starting the feature. Informs the user to drag over the screen to select a region to search with Google Lens.">
-      Drag over any image to search
-    </message>
-    <message name="IDS_LENS_REGION_SEARCH_BUBBLE_TEXT_ALT1" desc="Text that is shown in the Lens Region Search education bubble when starting the feature. Informs the user to drag over the screen to select a region to search with Google Lens.">
       Select image area to search
     </message>
-    <message name="IDS_LENS_REGION_SEARCH_BUBBLE_TEXT_ALT2" desc="Text that is shown in the Lens Region Search education bubble when starting the feature. Informs the user to drag over the screen to select a region to search with Google Lens.">
-     Select any part of the page to search
-    </message>
     <!-- ChromeLabs Thumbnail Tab Strip -->
     <message name="IDS_THUMBNAIL_TAB_STRIP_EXPERIMENT_NAME" desc="Name for Thumbnail Tab Strip experiment">
       Thumbnail tab strip for tablet mode
diff --git a/chrome/app/generated_resources_grd/IDS_CONTENT_CONTEXT_LENS_REGION_SEARCH_ALT2.png.sha1 b/chrome/app/generated_resources_grd/IDS_CONTENT_CONTEXT_LENS_REGION_SEARCH_ALT2.png.sha1
deleted file mode 100644
index 9ea03280..0000000
--- a/chrome/app/generated_resources_grd/IDS_CONTENT_CONTEXT_LENS_REGION_SEARCH_ALT2.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-a8c8c633721049fd05d13803897c77a4fbe5364c
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_CONTENT_CONTEXT_LENS_REGION_SEARCH_ALT3.png.sha1 b/chrome/app/generated_resources_grd/IDS_CONTENT_CONTEXT_LENS_REGION_SEARCH_ALT3.png.sha1
deleted file mode 100644
index 6b2b0cd..0000000
--- a/chrome/app/generated_resources_grd/IDS_CONTENT_CONTEXT_LENS_REGION_SEARCH_ALT3.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-2c85687183621a3da2c64badfdfa2206f87fee33
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_LENS_REGION_SEARCH_BUBBLE_TEXT.png.sha1 b/chrome/app/generated_resources_grd/IDS_LENS_REGION_SEARCH_BUBBLE_TEXT.png.sha1
index 3dd0eca..9a0e89c 100644
--- a/chrome/app/generated_resources_grd/IDS_LENS_REGION_SEARCH_BUBBLE_TEXT.png.sha1
+++ b/chrome/app/generated_resources_grd/IDS_LENS_REGION_SEARCH_BUBBLE_TEXT.png.sha1
@@ -1 +1 @@
-1e37bad744d5fc67913c326b035d95dc2f68944d
\ No newline at end of file
+73948381617ec624ee35e1c684b084a3b2c8b1e1
diff --git a/chrome/app/generated_resources_grd/IDS_LENS_REGION_SEARCH_BUBBLE_TEXT_ALT1.png.sha1 b/chrome/app/generated_resources_grd/IDS_LENS_REGION_SEARCH_BUBBLE_TEXT_ALT1.png.sha1
deleted file mode 100644
index 9a0e89c..0000000
--- a/chrome/app/generated_resources_grd/IDS_LENS_REGION_SEARCH_BUBBLE_TEXT_ALT1.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-73948381617ec624ee35e1c684b084a3b2c8b1e1
diff --git a/chrome/app/generated_resources_grd/IDS_LENS_REGION_SEARCH_BUBBLE_TEXT_ALT2.png.sha1 b/chrome/app/generated_resources_grd/IDS_LENS_REGION_SEARCH_BUBBLE_TEXT_ALT2.png.sha1
deleted file mode 100644
index 0fdf68e64..0000000
--- a/chrome/app/generated_resources_grd/IDS_LENS_REGION_SEARCH_BUBBLE_TEXT_ALT2.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-58d00fd07659c815888b20431a2cfc1fafe8d122
diff --git a/chrome/app/os_settings_strings.grdp b/chrome/app/os_settings_strings.grdp
index 9a9e5b9..50022e6 100644
--- a/chrome/app/os_settings_strings.grdp
+++ b/chrome/app/os_settings_strings.grdp
@@ -1236,6 +1236,21 @@
   <message name="IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_SPEECH" desc="In the Select-to-speak settings subpage, the label for the group of options where the user can adjust synthesized speech properties.">
     Speech
   </message>
+  <message name="IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_LANGUAGES_FILTER_DESCRIPTION" desc="In the Select-to-speak settings subpage, the label for the control where the user can choose a language from a list, to filter the list of voices below.">
+    Language
+  </message>
+  <message name="IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_DEVICE_LANGUAGE" desc="In the Select-to-speak settings subpage, the option in the language list where the default language of the device is used as the speech language.">
+    Device language
+  </message>
+  <message name="IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_VOICE_DESCRIPTION" desc="In the Select-to-speak settings subpage, the label for the control where the user can choose a voice.">
+    Voice
+  </message>
+  <message name="IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_SYSTEM_VOICE" desc="In the Select-to-speak settings subpage, the label for the menu option for the voice name for the system default Text-to-Speech voice">
+    System voice
+  </message>
+  <message name="IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_DEFAULT_NETWORK_VOICE" desc="In the Select-to-speak settings subpage, the label for the natural voices option that uses a default, server-supplied Text-to-Speech voice">
+    Default natural voice
+  </message>
   <message name="IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_HIGHLIGHT" desc="In the Select-to-speak settings subpage, the label for the group of options controlling highlighting.">
     Highlighting
   </message>
@@ -1251,6 +1266,12 @@
   <message name="IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_ENHANCED_NETWORK_VOICES_SUBTITLE" desc="In the Select-to-speak settings subpage, informational text related to the Select-to-speak control that enables support for high-quality voices, mentioning that enabling these voices will send text to Google for processing and generating audio.">
     Text will be sent to Google for processing.
   </message>
+  <message name="IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_ENHANCED_NETWORK_VOICE" desc="In the Select-to-speak settings subpage, the label for the control to choose a high-quality, natural-sounding voice to be streamed from the internet from a list.">
+    Natural voice
+  </message>
+  <message name="IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_NATURAL_VOICE_NAME" desc="In the Select-to-speak settings subpage, the label for a voice name of a natural voice, generated by combining a localized language name like English (US) with a serial number">
+    <ph name="DISPLAY_NAME">$1<ex>English (United States)</ex></ph> Voice <ph name="COUNT">$2<ex>2</ex></ph>
+  </message>
   <message name="IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_HIGHLIGHT_COLOR_DESCRIPTION" desc="In the Select-to-speak settings subpage, the label for option to pick word highlight color.">
     Highlight color
   </message>
@@ -4341,6 +4362,9 @@
   <message name="IDS_SETTINGS_APP_BADGING_TOGGLE_LABEL" desc="The label for the app badging toggle in the App Notifications page of OS Settings.">
     App badging
   </message>
+  <message name="IDS_SETTINGS_APP_BADGING_TOGGLE_SUBLABEL" desc="The sublabel for the app badging toggle in the App Notifications page of OS Settings.">
+    Show notification dot on app icon
+  </message>
   <message name="IDS_SETTINGS_ANDROID_APPS_TITLE" desc="The title of Google Play Store (Arc++ / Android Apps) section.">
     Google Play Store
   </message>
@@ -4562,6 +4586,9 @@
   <message name="IDS_OS_SETTINGS_PRIVACY_HUB_NO_MICROPHONE_CONNECTED_TEXT" desc="Text to display in the Privacy Hub subpage when no microphone is connected to the device.">
     No microphone
   </message>
+  <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_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_HW_MICROPHONE_TOGGLE_TOOLTIP.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_PRIVACY_HUB_HW_MICROPHONE_TOGGLE_TOOLTIP.png.sha1
new file mode 100644
index 0000000..f7f36a5
--- /dev/null
+++ b/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_PRIVACY_HUB_HW_MICROPHONE_TOGGLE_TOOLTIP.png.sha1
@@ -0,0 +1 @@
+f775aaae9e1aa3018a212ff2a225fdf197106fc3
\ No newline at end of file
diff --git a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_DEFAULT_NETWORK_VOICE.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_DEFAULT_NETWORK_VOICE.png.sha1
new file mode 100644
index 0000000..8095b6d9
--- /dev/null
+++ b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_DEFAULT_NETWORK_VOICE.png.sha1
@@ -0,0 +1 @@
+8a2f164522408dc91755712530b9b6d7266e4bb7
\ No newline at end of file
diff --git a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_DEVICE_LANGUAGE.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_DEVICE_LANGUAGE.png.sha1
new file mode 100644
index 0000000..faaebe1
--- /dev/null
+++ b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_DEVICE_LANGUAGE.png.sha1
@@ -0,0 +1 @@
+34f83d6aff14b9f8dea8ffe40afcf2be716bce0a
\ No newline at end of file
diff --git a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_ENHANCED_NETWORK_VOICE.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_ENHANCED_NETWORK_VOICE.png.sha1
new file mode 100644
index 0000000..866cea59
--- /dev/null
+++ b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_ENHANCED_NETWORK_VOICE.png.sha1
@@ -0,0 +1 @@
+b2d384152e084c5e08603d68eb4e1b0d42b685a9
\ No newline at end of file
diff --git a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_LANGUAGES_FILTER_DESCRIPTION.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_LANGUAGES_FILTER_DESCRIPTION.png.sha1
new file mode 100644
index 0000000..1a0627a3
--- /dev/null
+++ b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_LANGUAGES_FILTER_DESCRIPTION.png.sha1
@@ -0,0 +1 @@
+bc86603fa698ae887be48498958436b848f2c6dd
\ No newline at end of file
diff --git a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_NATURAL_VOICE_NAME.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_NATURAL_VOICE_NAME.png.sha1
new file mode 100644
index 0000000..39912ae
--- /dev/null
+++ b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_NATURAL_VOICE_NAME.png.sha1
@@ -0,0 +1 @@
+942b2517a260c40db81d7e7d922dd7683ff7ecac
\ No newline at end of file
diff --git a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_SYSTEM_VOICE.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_SYSTEM_VOICE.png.sha1
new file mode 100644
index 0000000..27bb3cc6
--- /dev/null
+++ b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_SYSTEM_VOICE.png.sha1
@@ -0,0 +1 @@
+1783b92cfbe5e8eaf6ee7ac6ecd2876e42cef0b0
\ No newline at end of file
diff --git a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_VOICE_DESCRIPTION.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_VOICE_DESCRIPTION.png.sha1
new file mode 100644
index 0000000..e685605
--- /dev/null
+++ b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_VOICE_DESCRIPTION.png.sha1
@@ -0,0 +1 @@
+dc0ec5bf41be15657ade008da68ef77db125c88e
\ No newline at end of file
diff --git a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_APP_BADGING_TOGGLE_SUBLABEL.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_APP_BADGING_TOGGLE_SUBLABEL.png.sha1
new file mode 100644
index 0000000..8b8f085
--- /dev/null
+++ b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_APP_BADGING_TOGGLE_SUBLABEL.png.sha1
@@ -0,0 +1 @@
+eba44488c9aac4dd7299e0f3839c53dd47447f85
\ No newline at end of file
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 65c1ee9..0860317 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -3260,8 +3260,8 @@
       "webauthn/android/cable_module_android.cc",
       "webauthn/android/chrome_webauthn_client_android.cc",
       "webauthn/android/chrome_webauthn_client_android.h",
-      "webauthn/android/conditional_ui_delegate_android.cc",
-      "webauthn/android/conditional_ui_delegate_android.h",
+      "webauthn/android/webauthn_request_delegate_android.cc",
+      "webauthn/android/webauthn_request_delegate_android.h",
     ]
     public_deps += [
       "//chrome/android/features/dev_ui:buildflags",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index f188f6a9..c144068 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -35,6 +35,7 @@
 #include "cc/base/switches.h"
 #include "chrome/browser/ash/android_sms/android_sms_switches.h"
 #include "chrome/browser/ash/app_list/search/files/item_suggest_cache.h"
+#include "chrome/browser/ash/app_list/search/search_features.h"
 #include "chrome/browser/browser_features.h"
 #include "chrome/browser/fast_checkout/fast_checkout_features.h"
 #include "chrome/browser/feature_guide/notifications/feature_notification_guide_service.h"
@@ -60,7 +61,6 @@
 #include "chrome/browser/sharing_hub/sharing_hub_features.h"
 #include "chrome/browser/signin/signin_features.h"
 #include "chrome/browser/site_isolation/about_flags.h"
-#include "chrome/browser/ui/app_list/search/search_features.h"
 #include "chrome/browser/ui/browser_navigator_params.h"
 #include "chrome/browser/ui/ui_features.h"
 #include "chrome/browser/unexpire_flags.h"
@@ -1550,14 +1550,14 @@
          std::size(kOmniboxDynamicMaxAutocomplete102), nullptr}};
 
 const FeatureEntry::FeatureParam kOmniboxUniformRowHeight36[] = {
-    {"OmniboxSuggestionVerticalMargin", "8"}};
+    {"OmniboxRichSuggestionVerticalMargin", "4"}};
 const FeatureEntry::FeatureParam kOmniboxUniformRowHeight40[] = {
-    {"OmniboxSuggestionVerticalMargin", "10"}};
+    {"OmniboxRichSuggestionVerticalMargin", "6"}};
 
 const FeatureEntry::FeatureVariation kOmniboxSuggestionHeightVariations[] = {
-    {"36px height for all omnibox suggestions", kOmniboxUniformRowHeight36,
+    {"36px omnibox suggestions", kOmniboxUniformRowHeight36,
      std::size(kOmniboxUniformRowHeight36), nullptr},
-    {"40px height for all omnibox suggestions", kOmniboxUniformRowHeight40,
+    {"40px omnibox suggestions", kOmniboxUniformRowHeight40,
      std::size(kOmniboxUniformRowHeight40), nullptr},
 };
 
@@ -3117,18 +3117,6 @@
      std::size(kLensStandaloneWithSidePanel), nullptr},
 };
 
-constexpr FeatureEntry::FeatureParam
-    kLensInstructionChipWithImageSelectionIcon[] = {
-        {"use-selection-icon-with-image", "true"}};
-constexpr FeatureEntry::FeatureParam kLensInstructionChipWithAltString[] = {
-    {"use-alt-chip-string", "true"}};
-constexpr FeatureEntry::FeatureVariation kLensInstructionChipVariations[] = {
-    {"With Image Selection Icon", kLensInstructionChipWithImageSelectionIcon,
-     std::size(kLensInstructionChipWithImageSelectionIcon), nullptr},
-    {"With Alt Text", kLensInstructionChipWithAltString,
-     std::size(kLensInstructionChipWithAltString), nullptr},
-};
-
 constexpr FeatureEntry::FeatureParam kLensFormatOptimizationJPEG[] = {
     {"use-webp-image-search", "false"},
     {"use-webp-region-search", "false"},
@@ -3704,6 +3692,9 @@
     {"bluetooth-use-llprivacy", flag_descriptions::kBluetoothUseLLPrivacyName,
      flag_descriptions::kBluetoothUseLLPrivacyDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(bluez::features::kLinkLayerPrivacy)},
+    {"calendar-jelly", flag_descriptions::kCalendarJellyName,
+     flag_descriptions::kCalendarJellyDescription, kOsCrOS,
+     FEATURE_VALUE_TYPE(ash::features::kCalendarJelly)},
     {"calendar-view", flag_descriptions::kCalendarViewName,
      flag_descriptions::kCalendarViewDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(ash::features::kCalendarView)},
@@ -3929,6 +3920,9 @@
      flag_descriptions::kAmbientSubpageUIChangeName,
      flag_descriptions::kAmbientSubpageUIChangeDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(ash::features::kAmbientSubpageUIChange)},
+    {"screen-saver-preview", flag_descriptions::kScreenSaverPreviewName,
+     flag_descriptions::kScreenSaverPreviewDescription, kOsCrOS,
+     FEATURE_VALUE_TYPE(ash::features::kScreenSaverPreview)},
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 #if BUILDFLAG(IS_CHROMEOS)
@@ -5142,9 +5136,6 @@
     {"auto-framing-override", flag_descriptions::kAutoFramingOverrideName,
      flag_descriptions::kAutoFramingOverrideDescription, kOsCrOS,
      MULTI_VALUE_TYPE(kAutoFramingOverrideChoices)},
-    {"camera-app-doc-scan-dlc", flag_descriptions::kCameraAppDocScanDlcName,
-     flag_descriptions::kCameraAppDocScanDlcDescription, kOsCrOS,
-     FEATURE_VALUE_TYPE(ash::features::kCameraAppDocScanDlc)},
     {"camera-app-low-storage-warning",
      flag_descriptions::kCameraAppLowStorageWarningName,
      flag_descriptions::kCameraAppLowStorageWarningDescription, kOsCrOS,
@@ -5371,7 +5362,7 @@
 
     {"omnibox-default-browser-pedal",
      flag_descriptions::kOmniboxDefaultBrowserPedalName,
-     flag_descriptions::kOmniboxDefaultBrowserPedalDescription, kOsAll,
+     flag_descriptions::kOmniboxDefaultBrowserPedalDescription, kOsDesktop,
      FEATURE_VALUE_TYPE(omnibox::kOmniboxDefaultBrowserPedal)},
 
     {"omnibox-report-assisted-query-stats",
@@ -5668,6 +5659,13 @@
          kJourneysVisitDedupingVariations,
          "HistoryJourneysVisitDeduping")},
 
+    {"history-journeys-include-synced-visits",
+     flag_descriptions::kJourneysIncludeSyncedVisitsName,
+     flag_descriptions::kJourneysIncludeSyncedVisitsDescription,
+     kOsDesktop | kOsAndroid,
+     FEATURE_VALUE_TYPE(
+         history_clusters::internal::kJourneysIncludeSyncedVisits)},
+
     {"extract-related-searches-from-prefetched-zps-response",
      flag_descriptions::kExtractRelatedSearchesFromPrefetchedZPSResponseName,
      flag_descriptions::
@@ -6641,6 +6639,10 @@
     {"cros-labs-float-window", flag_descriptions::kFloatWindow,
      flag_descriptions::kFloatWindowDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(chromeos::wm::features::kFloatWindow)},
+    {"cros-labs-overview-desk-navigation",
+     flag_descriptions::kOverviewDeskNavigationName,
+     flag_descriptions::kOverviewDeskNavigationDescription, kOsCrOS,
+     FEATURE_VALUE_TYPE(ash::features::kOverviewDeskNavigation)},
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
@@ -8255,15 +8257,6 @@
                                     kLensStandaloneVariations,
                                     "GoogleLensDesktopContextMenuSearch")},
 
-    {"enable-lens-instruction-chip-improvements",
-     flag_descriptions::kEnableLensInstructionChipImprovementsName,
-     flag_descriptions::kEnableLensInstructionChipImprovementsDescription,
-     kOsDesktop,
-     FEATURE_WITH_PARAMS_VALUE_TYPE(
-         lens::features::kLensInstructionChipImprovements,
-         kLensInstructionChipVariations,
-         "LensInstructionChipImprovements")},
-
     {"enable-lens-region-search-static-page",
      flag_descriptions::kLensRegionSearchStaticPageName,
      flag_descriptions::kLensRegionSearchStaticPageDescription, kOsDesktop,
diff --git a/chrome/browser/app_controller_mac.mm b/chrome/browser/app_controller_mac.mm
index 2c7448b..8c0301f 100644
--- a/chrome/browser/app_controller_mac.mm
+++ b/chrome/browser/app_controller_mac.mm
@@ -2002,7 +2002,6 @@
   } else if (!browser) {
     // if no browser window exists then create one with no tabs to be filled in.
     browser = Browser::Create(Browser::CreateParams(profile, true));
-    browser->window()->Show();
   }
 
   // Various methods to open URLs that we get in a native fashion. We use
diff --git a/chrome/browser/app_controller_mac_interactive_uitest.mm b/chrome/browser/app_controller_mac_interactive_uitest.mm
new file mode 100644
index 0000000..2a160aa2
--- /dev/null
+++ b/chrome/browser/app_controller_mac_interactive_uitest.mm
@@ -0,0 +1,56 @@
+// 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.
+
+#import <AppKit/AppKit.h>
+#import <Foundation/Foundation.h>
+
+#include "base/mac/foundation_util.h"
+#import "chrome/browser/app_controller_mac.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_finder.h"
+#include "chrome/browser/ui/browser_window.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "content/public/test/browser_test.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using AppControllerInteractiveUITest = InProcessBrowserTest;
+
+namespace {
+
+IN_PROC_BROWSER_TEST_F(AppControllerInteractiveUITest,
+                       OpenURLsDontActivateBrowserWindow) {
+  // Tests that application:openURLs does not activate the browser window. The
+  // LaunchServices will send activation events if necessary. Force activating
+  // the browser window can cause undesirable behavior, for example, when the
+  // URL was opened by another application with
+  // NSWorkspaceLaunchWithoutActivation or
+  // NSWorkspaceOpenConfiguration.activates set to NO, the browser window
+  // becomes activated anyway.
+  Profile* default_profile = browser()->profile();
+
+  CloseBrowserSynchronously(browser());
+  ASSERT_EQ(0u, chrome::GetTotalBrowserCount());
+
+  AppController* ac = base::mac::ObjCCast<AppController>([NSApp delegate]);
+  ASSERT_TRUE(ac);
+  [ac application:NSApp
+         openURLs:@[ [NSURL URLWithString:@"http://example.com"] ]];
+  EXPECT_EQ(1u, chrome::GetTotalBrowserCount());
+
+  Browser* new_browser = chrome::FindBrowserWithProfile(default_profile);
+  ASSERT_TRUE(new_browser);
+  EXPECT_FALSE(new_browser->window()->IsActive());
+
+  [ac application:NSApp
+         openURLs:@[ [NSURL URLWithString:@"http://example.com"] ]];
+  EXPECT_EQ(1u, chrome::GetTotalBrowserCount());
+  EXPECT_FALSE(new_browser->window()->IsActive());
+
+  TabStripModel* tab_strip = new_browser->tab_strip_model();
+  EXPECT_EQ(2, tab_strip->count());
+}
+
+}  // namespace
diff --git a/chrome/browser/apps/app_service/app_icon/app_icon_factory.cc b/chrome/browser/apps/app_service/app_icon/app_icon_factory.cc
index acfd4c1..191b93b 100644
--- a/chrome/browser/apps/app_service/app_icon/app_icon_factory.cc
+++ b/chrome/browser/apps/app_service/app_icon/app_icon_factory.cc
@@ -486,7 +486,8 @@
           icon_type, size_hint_in_dip, is_placeholder_icon, icon_effects,
           IDR_APP_DEFAULT_ICON, std::move(callback));
   icon_loader->LoadWebAppIcon(
-      web_app_id, web_app_provider->registrar().GetAppStartUrl(web_app_id),
+      web_app_id,
+      web_app_provider->registrar_unsafe().GetAppStartUrl(web_app_id),
       web_app_provider->icon_manager(), Profile::FromBrowserContext(context));
 }
 
diff --git a/chrome/browser/apps/app_service/app_icon/chrome_apps_icon_unittest.cc b/chrome/browser/apps/app_service/app_icon/chrome_apps_icon_unittest.cc
index ed9af07..a402c96 100644
--- a/chrome/browser/apps/app_service/app_icon/chrome_apps_icon_unittest.cc
+++ b/chrome/browser/apps/app_service/app_icon/chrome_apps_icon_unittest.cc
@@ -5,13 +5,13 @@
 #include <string>
 #include <vector>
 
+#include "base/barrier_callback.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/run_loop.h"
 #include "base/test/test_future.h"
 #include "chrome/browser/apps/app_service/app_icon/app_icon_factory.h"
 #include "chrome/browser/apps/app_service/app_icon/app_icon_test_util.h"
-#include "chrome/browser/apps/icon_standardizer.h"
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/extensions/extension_service_test_base.h"
 #include "chrome/browser/profiles/profile.h"
@@ -28,6 +28,13 @@
 #include "ui/gfx/image/image_skia_rep.h"
 #include "ui/gfx/image/image_unittest_util.h"
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+#include "chrome/browser/apps/app_service/app_icon/app_icon_decoder.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/apps/icon_standardizer.h"
+#endif
+
 namespace apps {
 
 const char kPackagedApp1Id[] = "emfkafnhnpcmabnnkckkchdilgeoekbo";
@@ -223,6 +230,116 @@
                             ui::ResourceScaleFactor::k100Percent);
   VerifyCompressedIcon(src_data, *icon);
 }
+
+class AppServiceChromeAppIconTest : public ChromeAppsIconFactoryTest {
+ public:
+  void SetUp() override {
+    ChromeAppsIconFactoryTest::SetUp();
+
+    proxy_ = AppServiceProxyFactory::GetForProfile(profile());
+    fake_icon_loader_ = std::make_unique<apps::FakeIconLoader>(proxy_);
+    OverrideAppServiceProxyInnerIconLoader(fake_icon_loader_.get());
+    scoped_decode_request_for_testing_ =
+        std::make_unique<ScopedDecodeRequestForTesting>();
+  }
+
+  void OverrideAppServiceProxyInnerIconLoader(apps::IconLoader* icon_loader) {
+    app_service_proxy().OverrideInnerIconLoaderForTesting(icon_loader);
+  }
+
+  apps::IconValuePtr LoadIcon(const std::string& app_id, IconType icon_type) {
+    base::test::TestFuture<apps::IconValuePtr> result;
+    app_service_proxy().LoadIcon(
+        AppType::kChromeApp, app_id, icon_type, kSizeInDip,
+        /*allow_placeholder_icon=*/false, result.GetCallback());
+    return result.Take();
+  }
+
+  apps::IconValuePtr LoadIconFromIconKey(const std::string& app_id,
+                                         const IconKey& icon_key,
+                                         IconType icon_type) {
+    base::test::TestFuture<apps::IconValuePtr> result;
+    app_service_proxy().LoadIconFromIconKey(
+        AppType::kChromeApp, app_id, icon_key, icon_type, kSizeInDip,
+        /*allow_placeholder_icon=*/false, result.GetCallback());
+    return result.Take();
+  }
+
+  // Call LoadIconFromIconKey twice with the same parameters, to verify the icon
+  // loading process can handle the icon loading request multiple times with the
+  // same params.
+  std::vector<apps::IconValuePtr> MultipleLoadIconFromIconKey(
+      const std::string& app_id,
+      const IconKey& icon_key,
+      IconType icon_type) {
+    base::test::TestFuture<std::vector<apps::IconValuePtr>> result;
+    auto barrier_callback =
+        base::BarrierCallback<apps::IconValuePtr>(2, result.GetCallback());
+
+    app_service_proxy().LoadIconFromIconKey(
+        AppType::kChromeApp, app_id, icon_key, icon_type, kSizeInDip,
+        /*allow_placeholder_icon=*/false, barrier_callback);
+    app_service_proxy().LoadIconFromIconKey(
+        AppType::kChromeApp, app_id, icon_key, icon_type, kSizeInDip,
+        /*allow_placeholder_icon=*/false, barrier_callback);
+
+    return result.Take();
+  }
+
+  AppServiceProxy& app_service_proxy() { return *proxy_; }
+
+ private:
+  raw_ptr<AppServiceProxy> proxy_;
+  std::unique_ptr<apps::FakeIconLoader> fake_icon_loader_;
+  std::unique_ptr<ScopedDecodeRequestForTesting>
+      scoped_decode_request_for_testing_;
+};
+
+TEST_F(AppServiceChromeAppIconTest, GetCompressedIconDataForCompressedIcon) {
+  // Generate the source compressed icon for comparing.
+  std::vector<uint8_t> src_data;
+  GenerateExtensionAppCompressedIcon(kPackagedApp1Id, /*scale=*/1.0, src_data,
+                                     /*skip_effects=*/true);
+
+  // Verify the icon reading and writing function in AppService for the
+  // compressed icon.
+  VerifyCompressedIcon(src_data,
+                       *LoadIcon(kPackagedApp1Id, IconType::kCompressed));
+}
+
+TEST_F(AppServiceChromeAppIconTest, GetCompressedIconDataForStandardIcon) {
+  // Generate the source uncompressed icon for comparing.
+  gfx::ImageSkia src_image_skia;
+  GenerateExtensionAppIcon(kPackagedApp1Id, src_image_skia);
+
+  // Verify the icon reading and writing function in AppService for the
+  // kStandard icon.
+  IconKey icon_key;
+  icon_key.icon_effects = IconEffects::kCrOsStandardIcon;
+  auto ret = MultipleLoadIconFromIconKey(kPackagedApp1Id, icon_key,
+                                         IconType::kStandard);
+
+  ASSERT_EQ(2U, ret.size());
+  ASSERT_EQ(apps::IconType::kStandard, ret[0]->icon_type);
+  VerifyIcon(src_image_skia, ret[0]->uncompressed);
+  ASSERT_EQ(apps::IconType::kStandard, ret[1]->icon_type);
+  VerifyIcon(src_image_skia, ret[1]->uncompressed);
+}
+
+TEST_F(AppServiceChromeAppIconTest, GetCompressedIconDataForUncompressedIcon) {
+  // Generate the source uncompressed icon for comparing.
+  gfx::ImageSkia src_image_skia;
+  GenerateExtensionAppIcon(kPackagedApp1Id, src_image_skia,
+                           /*skip_effects=*/true);
+
+  // Verify the icon reading and writing function in AppService for the
+  // kUncompressed icon.
+  auto ret =
+      LoadIconFromIconKey(kPackagedApp1Id, IconKey(), IconType::kUncompressed);
+
+  ASSERT_EQ(apps::IconType::kUncompressed, ret->icon_type);
+  VerifyIcon(src_image_skia, ret->uncompressed);
+}
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 }  // namespace apps
diff --git a/chrome/browser/apps/app_service/app_service_proxy_base.cc b/chrome/browser/apps/app_service/app_service_proxy_base.cc
index 9148585..44dc577 100644
--- a/chrome/browser/apps/app_service/app_service_proxy_base.cc
+++ b/chrome/browser/apps/app_service/app_service_proxy_base.cc
@@ -734,11 +734,6 @@
   app_capability_access_cache_.OnCapabilityAccesses(std::move(deltas));
 }
 
-void AppServiceProxyBase::OnCapabilityAccesses(
-    std::vector<apps::mojom::CapabilityAccessPtr> deltas) {
-  app_capability_access_cache_.OnCapabilityAccesses(std::move(deltas));
-}
-
 void AppServiceProxyBase::Clone(
     mojo::PendingReceiver<apps::mojom::Subscriber> receiver) {
   receivers_.Add(this, std::move(receiver));
diff --git a/chrome/browser/apps/app_service/app_service_proxy_base.h b/chrome/browser/apps/app_service/app_service_proxy_base.h
index 303b61c..dd81835 100644
--- a/chrome/browser/apps/app_service/app_service_proxy_base.h
+++ b/chrome/browser/apps/app_service/app_service_proxy_base.h
@@ -376,8 +376,6 @@
   void OnApps(std::vector<apps::mojom::AppPtr> deltas,
               apps::mojom::AppType app_type,
               bool should_notify_initialized) override;
-  void OnCapabilityAccesses(
-      std::vector<apps::mojom::CapabilityAccessPtr> deltas) override;
   void Clone(mojo::PendingReceiver<apps::mojom::Subscriber> receiver) override;
 
   IntentFilterPtr FindBestMatchingFilter(const IntentPtr& intent);
diff --git a/chrome/browser/apps/app_service/metrics/app_platform_metrics.cc b/chrome/browser/apps/app_service/metrics/app_platform_metrics.cc
index abdcad5..cd67b18 100644
--- a/chrome/browser/apps/app_service/metrics/app_platform_metrics.cc
+++ b/chrome/browser/apps/app_service/metrics/app_platform_metrics.cc
@@ -1191,7 +1191,8 @@
 
   // Also notify registered observers.
   for (auto& observer : observers_) {
-    observer.OnAppUsage(app_id, GetAppType(profile_, app_id), running_time);
+    observer.OnAppUsage(app_id, GetAppType(profile_, app_id), instance_id,
+                        running_time);
   }
 }
 
diff --git a/chrome/browser/apps/app_service/metrics/app_platform_metrics.h b/chrome/browser/apps/app_service/metrics/app_platform_metrics.h
index 34f5c3b1..1dd7e6076b 100644
--- a/chrome/browser/apps/app_service/metrics/app_platform_metrics.h
+++ b/chrome/browser/apps/app_service/metrics/app_platform_metrics.h
@@ -100,9 +100,11 @@
                                   AppType app_type,
                                   UninstallSource app_uninstall_source) {}
 
-    // Invoked when app usage metrics are being recorded (every 5 mins).
+    // Invoked when app usage metrics are being recorded (every 5 mins). Since
+    // apps can have multiple instances, we also include the instance id here.
     virtual void OnAppUsage(const std::string& app_id,
                             AppType app_type,
+                            const base::UnguessableToken& instance_id,
                             base::TimeDelta running_time) {}
 
     // Invoked when the `AppPlatformMetrics` component (being observed) is being
diff --git a/chrome/browser/apps/app_service/metrics/app_platform_metrics_service_unittest.cc b/chrome/browser/apps/app_service/metrics/app_platform_metrics_service_unittest.cc
index 3819c20..b0beb946 100644
--- a/chrome/browser/apps/app_service/metrics/app_platform_metrics_service_unittest.cc
+++ b/chrome/browser/apps/app_service/metrics/app_platform_metrics_service_unittest.cc
@@ -114,6 +114,7 @@
               OnAppUsage,
               (const std::string& app_id,
                AppType app_type,
+               const base::UnguessableToken& instance_id,
                base::TimeDelta running_time),
               (override));
 
@@ -2651,17 +2652,19 @@
   // Set the window active state and verify the observer is notified
   // with the appropriate running time with every notification.
   const std::string& app_id = "a";
-  ModifyInstance(base::UnguessableToken::Create(), app_id, window.get(),
-                 kActiveInstanceState);
+  const base::UnguessableToken& instance_id = base::UnguessableToken::Create();
+  ModifyInstance(instance_id, app_id, window.get(), kActiveInstanceState);
 
   // Usage metrics are recorded every 5 minutes and on window inactivation, so
   // we can expect two notifications with relevant usage times (5 minutes + 3
   // minutes) across a 8 minute usage window.
   Sequence s;
-  EXPECT_CALL(observer_, OnAppUsage(app_id, AppType::kArc, base::Minutes(5)))
+  EXPECT_CALL(observer_,
+              OnAppUsage(app_id, AppType::kArc, instance_id, base::Minutes(5)))
       .Times(1)
       .InSequence(s);
-  EXPECT_CALL(observer_, OnAppUsage(app_id, AppType::kArc, base::Minutes(3)))
+  EXPECT_CALL(observer_,
+              OnAppUsage(app_id, AppType::kArc, instance_id, base::Minutes(3)))
       .Times(1)
       .InSequence(s);
 
@@ -2670,7 +2673,7 @@
 
   // Set app inactive. This should also trigger the second notification with
   // usage time delta after the first one.
-  ModifyInstance(app_id, window.get(), kInactiveInstanceState);
+  ModifyInstance(instance_id, app_id, window.get(), kInactiveInstanceState);
 }
 
 TEST_P(AppPlatformMetricsObserverTest, ShouldNotNotifyUnregisteredObservers) {
diff --git a/chrome/browser/apps/app_service/publishers/arc_apps.cc b/chrome/browser/apps/app_service/publishers/arc_apps.cc
index 451b2eac3..3fc9f544 100644
--- a/chrome/browser/apps/app_service/publishers/arc_apps.cc
+++ b/chrome/browser/apps/app_service/publishers/arc_apps.cc
@@ -1510,24 +1510,11 @@
   }
 
   // Write the record to `AppCapabilityAccessCache`.
-  if (base::FeatureList::IsEnabled(
-          apps::kAppServiceCapabilityAccessWithoutMojom)) {
-    std::vector<CapabilityAccessPtr> accesses;
-    for (auto& item : capability_accesses) {
-      accesses.push_back(std::move(item.second));
-    }
-    proxy()->OnCapabilityAccesses(std::move(accesses));
-    return;
+  std::vector<CapabilityAccessPtr> accesses;
+  for (auto& item : capability_accesses) {
+    accesses.push_back(std::move(item.second));
   }
-
-  for (auto& subscriber : subscribers_) {
-    std::vector<apps::mojom::CapabilityAccessPtr> accesses;
-    for (const auto& item : capability_accesses) {
-      accesses.push_back(
-          ConvertCapabilityAccessToMojomCapabilityAccess(item.second));
-    }
-    subscriber->OnCapabilityAccesses(std::move(accesses));
-  }
+  proxy()->OnCapabilityAccesses(std::move(accesses));
 }
 
 void ArcApps::OnInstanceUpdate(const apps::InstanceUpdate& update) {
diff --git a/chrome/browser/apps/app_service/publishers/extension_apps_chromeos.cc b/chrome/browser/apps/app_service/publishers/extension_apps_chromeos.cc
index b414b88..e3ebe3d 100644
--- a/chrome/browser/apps/app_service/publishers/extension_apps_chromeos.cc
+++ b/chrome/browser/apps/app_service/publishers/extension_apps_chromeos.cc
@@ -68,7 +68,6 @@
 #include "components/app_restore/app_launch_info.h"
 #include "components/app_restore/full_restore_utils.h"
 #include "components/policy/core/common/policy_pref_names.h"
-#include "components/services/app_service/public/cpp/features.h"
 #include "components/services/app_service/public/cpp/instance.h"
 #include "components/services/app_service/public/cpp/intent.h"
 #include "components/services/app_service/public/cpp/intent_filter.h"
@@ -181,6 +180,19 @@
   }
 }
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+void ExtensionAppsChromeOs::GetCompressedIconData(
+    const std::string& app_id,
+    apps::IconType icon_type,
+    int32_t size_in_dip,
+    ui::ResourceScaleFactor scale_factor,
+    LoadIconCallback callback) {
+  apps::GetChromeAppCompressedIconData(profile(), app_id, icon_type,
+                                       size_in_dip, scale_factor,
+                                       std::move(callback));
+}
+#endif
+
 void ExtensionAppsChromeOs::LaunchAppWithParamsImpl(AppLaunchParams&& params,
                                                     LaunchCallback callback) {
   const auto* extension = MaybeGetExtension(params.app_id);
@@ -450,14 +462,8 @@
 
   auto result = media_requests_.RemoveRequests(extension->id());
 
-  if (base::FeatureList::IsEnabled(
-          apps::kAppServiceCapabilityAccessWithoutMojom)) {
-    apps::AppPublisher::ModifyCapabilityAccess(extension->id(), result.camera,
-                                               result.microphone);
-  } else {
-    PublisherBase::ModifyCapabilityAccess(subscribers(), extension->id(),
-                                          result.camera, result.microphone);
-  }
+  apps::AppPublisher::ModifyCapabilityAccess(extension->id(), result.camera,
+                                             result.microphone);
 
   ExtensionAppsBase::OnExtensionUninstalled(browser_context, extension, reason);
 }
@@ -532,14 +538,8 @@
   auto result =
       media_requests_.UpdateRequests(app_id, web_contents, stream_type, state);
 
-  if (base::FeatureList::IsEnabled(
-          apps::kAppServiceCapabilityAccessWithoutMojom)) {
-    apps::AppPublisher::ModifyCapabilityAccess(app_id, result.camera,
-                                               result.microphone);
-  } else {
-    PublisherBase::ModifyCapabilityAccess(subscribers(), app_id, result.camera,
-                                          result.microphone);
-  }
+  apps::AppPublisher::ModifyCapabilityAccess(app_id, result.camera,
+                                             result.microphone);
 }
 
 void ExtensionAppsChromeOs::OnWebContentsDestroyed(
@@ -558,14 +558,8 @@
   }
 
   auto result = media_requests_.OnWebContentsDestroyed(app_id, web_contents);
-  if (base::FeatureList::IsEnabled(
-          apps::kAppServiceCapabilityAccessWithoutMojom)) {
-    apps::AppPublisher::ModifyCapabilityAccess(app_id, result.camera,
-                                               result.microphone);
-  } else {
-    PublisherBase::ModifyCapabilityAccess(subscribers(), app_id, result.camera,
-                                          result.microphone);
-  }
+  apps::AppPublisher::ModifyCapabilityAccess(app_id, result.camera,
+                                             result.microphone);
 }
 
 void ExtensionAppsChromeOs::OnNotificationDisplayed(
diff --git a/chrome/browser/apps/app_service/publishers/extension_apps_chromeos.h b/chrome/browser/apps/app_service/publishers/extension_apps_chromeos.h
index d3580d5..ddeb0b0e 100644
--- a/chrome/browser/apps/app_service/publishers/extension_apps_chromeos.h
+++ b/chrome/browser/apps/app_service/publishers/extension_apps_chromeos.h
@@ -75,6 +75,18 @@
 
   // ExtensionAppsBase overrides.
   void Initialize() override;
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  // Requests a compressed icon data for an app identified by `app_id`. The icon
+  // is identified by `size_in_dip` and `scale_factor`. Calls `callback` with
+  // the result.
+  void GetCompressedIconData(const std::string& app_id,
+                             IconType icon_type,
+                             int32_t size_in_dip,
+                             ui::ResourceScaleFactor scale_factor,
+                             LoadIconCallback callback) override;
+#endif
+
   void LaunchAppWithParamsImpl(AppLaunchParams&& params,
                                LaunchCallback callback) override;
 
diff --git a/chrome/browser/apps/app_service/publishers/web_apps_crosapi.cc b/chrome/browser/apps/app_service/publishers/web_apps_crosapi.cc
index f55a257..baacb1e 100644
--- a/chrome/browser/apps/app_service/publishers/web_apps_crosapi.cc
+++ b/chrome/browser/apps/app_service/publishers/web_apps_crosapi.cc
@@ -462,16 +462,7 @@
 
 void WebAppsCrosapi::PublishCapabilityAccessesImpl(
     std::vector<CapabilityAccessPtr> deltas) {
-  if (base::FeatureList::IsEnabled(
-          apps::kAppServiceCapabilityAccessWithoutMojom)) {
-    proxy()->OnCapabilityAccesses(std::move(deltas));
-    return;
-  }
-
-  for (auto& subscriber : subscribers_) {
-    subscriber->OnCapabilityAccesses(
-        apps::ConvertCapabilityAccessesToMojomCapabilityAccesses(deltas));
-  }
+  proxy()->OnCapabilityAccesses(std::move(deltas));
 }
 
 }  // namespace apps
diff --git a/chrome/browser/apps/app_service/subscriber_crosapi.cc b/chrome/browser/apps/app_service/subscriber_crosapi.cc
index e770b1f..fdd3c04 100644
--- a/chrome/browser/apps/app_service/subscriber_crosapi.cc
+++ b/chrome/browser/apps/app_service/subscriber_crosapi.cc
@@ -118,11 +118,6 @@
   return;
 }
 
-void SubscriberCrosapi::OnCapabilityAccesses(
-    std::vector<apps::mojom::CapabilityAccessPtr> deltas) {
-  NOTIMPLEMENTED();
-}
-
 void SubscriberCrosapi::Clone(
     mojo::PendingReceiver<apps::mojom::Subscriber> receiver) {
   receivers_.Add(this, std::move(receiver));
diff --git a/chrome/browser/apps/app_service/subscriber_crosapi.h b/chrome/browser/apps/app_service/subscriber_crosapi.h
index 1d92ec4a..720f612 100644
--- a/chrome/browser/apps/app_service/subscriber_crosapi.h
+++ b/chrome/browser/apps/app_service/subscriber_crosapi.h
@@ -59,8 +59,6 @@
   void OnApps(std::vector<apps::mojom::AppPtr> deltas,
               apps::mojom::AppType mojom_app_type,
               bool should_notify_initialized) override;
-  void OnCapabilityAccesses(
-      std::vector<apps::mojom::CapabilityAccessPtr> deltas) override;
   void Clone(mojo::PendingReceiver<apps::mojom::Subscriber> receiver) override;
   void OnCrosapiDisconnected();
 
diff --git a/chrome/browser/apps/app_service/uninstall_dialog.cc b/chrome/browser/apps/app_service/uninstall_dialog.cc
index d29c502..0ba6d1f 100644
--- a/chrome/browser/apps/app_service/uninstall_dialog.cc
+++ b/chrome/browser/apps/app_service/uninstall_dialog.cc
@@ -8,9 +8,9 @@
 #include "chrome/browser/apps/app_service/app_icon/app_icon_factory.h"
 #include "chrome/browser/apps/app_service/publishers/extension_apps_chromeos.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/native_window_tracker.h"
 #include "components/services/app_service/public/cpp/icon_loader.h"
 #include "extensions/browser/uninstall_reason.h"
+#include "ui/views/native_window_tracker.h"
 
 namespace {
 
@@ -33,7 +33,7 @@
       parent_window_(parent_window),
       uninstall_callback_(std::move(uninstall_callback)) {
   if (parent_window)
-    parent_window_tracker_ = NativeWindowTracker::Create(parent_window);
+    parent_window_tracker_ = views::NativeWindowTracker::Create(parent_window);
 }
 
 UninstallDialog::~UninstallDialog() = default;
diff --git a/chrome/browser/apps/app_service/uninstall_dialog.h b/chrome/browser/apps/app_service/uninstall_dialog.h
index e933dc3..54aa6543 100644
--- a/chrome/browser/apps/app_service/uninstall_dialog.h
+++ b/chrome/browser/apps/app_service/uninstall_dialog.h
@@ -16,13 +16,16 @@
 #include "ui/gfx/native_widget_types.h"
 #include "ui/views/widget/widget.h"
 
-class NativeWindowTracker;
 class Profile;
 
 namespace gfx {
 class ImageSkia;
 }
 
+namespace views {
+class NativeWindowTracker;
+}
+
 namespace apps {
 class IconLoader;
 class UninstallDialog;
@@ -123,7 +126,7 @@
   raw_ptr<views::Widget, DanglingUntriaged> widget_ = nullptr;
 
   // Tracks whether |parent_window_| got destroyed.
-  std::unique_ptr<NativeWindowTracker> parent_window_tracker_;
+  std::unique_ptr<views::NativeWindowTracker> parent_window_tracker_;
 
   base::WeakPtrFactory<UninstallDialog> weak_ptr_factory_{this};
 };
diff --git a/chrome/browser/apps/app_service/web_contents_app_id_utils.cc b/chrome/browser/apps/app_service/web_contents_app_id_utils.cc
index 6b69b56..c50a13b 100644
--- a/chrome/browser/apps/app_service/web_contents_app_id_utils.cc
+++ b/chrome/browser/apps/app_service/web_contents_app_id_utils.cc
@@ -92,10 +92,11 @@
     }
 
     absl::optional<web_app::AppId> app_id =
-        provider->registrar().FindAppWithUrlInScope(tab->GetVisibleURL());
+        provider->registrar_unsafe().FindAppWithUrlInScope(
+            tab->GetVisibleURL());
     if (app_id) {
       const web_app::WebApp* web_app =
-          provider->registrar().GetAppById(*app_id);
+          provider->registrar_unsafe().GetAppById(*app_id);
       DCHECK(web_app);
       if (web_app->user_display_mode() == web_app::UserDisplayMode::kBrowser &&
           !web_app->is_uninstalling()) {
diff --git a/chrome/browser/apps/app_shim/web_app_shim_manager_delegate_mac.cc b/chrome/browser/apps/app_shim/web_app_shim_manager_delegate_mac.cc
index 9316e5b..96257df 100644
--- a/chrome/browser/apps/app_shim/web_app_shim_manager_delegate_mac.cc
+++ b/chrome/browser/apps/app_shim/web_app_shim_manager_delegate_mac.cc
@@ -181,7 +181,7 @@
     return fallback_delegate_->AppIsInstalled(profile, app_id);
   }
   return profile &&
-         WebAppProvider::GetForWebApps(profile)->registrar().IsInstalled(
+         WebAppProvider::GetForWebApps(profile)->registrar_unsafe().IsInstalled(
              app_id);
 }
 
@@ -201,7 +201,7 @@
   // window) can attach to a host.
   if (!profile)
     return false;
-  auto& registrar = WebAppProvider::GetForWebApps(profile)->registrar();
+  auto& registrar = WebAppProvider::GetForWebApps(profile)->registrar_unsafe();
   return registrar.IsInstalled(app_id) &&
          registrar.GetAppEffectiveDisplayMode(app_id) !=
              web_app::DisplayMode::kBrowser;
@@ -241,7 +241,7 @@
     return;
   }
   DisplayMode effective_display_mode = WebAppProvider::GetForWebApps(profile)
-                                           ->registrar()
+                                           ->registrar_unsafe()
                                            .GetAppEffectiveDisplayMode(app_id);
 
   apps::LaunchContainer launch_container =
@@ -315,7 +315,7 @@
     // unless the user has granted or denied permission to this protocol scheme
     // previously.
     web_app::WebAppRegistrar& registrar =
-        WebAppProvider::GetForWebApps(profile)->registrar();
+        WebAppProvider::GetForWebApps(profile)->registrar_unsafe();
     if (registrar.IsDisallowedLaunchProtocol(app_id, protocol_url.scheme())) {
       CancelAppLaunch(profile, app_id);
       return;
@@ -333,7 +333,7 @@
   // If there is no matching file handling URL (such as when the API has been
   // disabled), fall back to a normal app launch.
   if (!file_launches.empty()) {
-    const WebApp* web_app = provider->registrar().GetAppById(app_id);
+    const WebApp* web_app = provider->registrar_unsafe().GetAppById(app_id);
     DCHECK(web_app);
 
     if (web_app->file_handler_approval_state() ==
@@ -392,7 +392,7 @@
   // If |app_id| is installed via WebAppProvider, then use |this| as the
   // delegate.
   auto* provider = WebAppProvider::GetForWebApps(profile);
-  if (provider->registrar().IsInstalled(app_id))
+  if (provider->registrar_unsafe().IsInstalled(app_id))
     return false;
 
   // Use |fallback_delegate_| only if |app_id| is installed for |profile|
@@ -411,7 +411,7 @@
   DCHECK(profile);
 
   auto shortcuts_menu_item_infos = WebAppProvider::GetForWebApps(profile)
-                                       ->registrar()
+                                       ->registrar_unsafe()
                                        .GetAppShortcutsMenuItemInfos(app_id);
 
   DCHECK_LE(shortcuts_menu_item_infos.size(), kMaxApplicationDockMenuItems);
diff --git a/chrome/browser/apps/icon_standardizer.cc b/chrome/browser/apps/icon_standardizer.cc
index 996bdf8..937dd6c 100644
--- a/chrome/browser/apps/icon_standardizer.cc
+++ b/chrome/browser/apps/icon_standardizer.cc
@@ -4,6 +4,8 @@
 
 #include "chrome/browser/apps/icon_standardizer.h"
 
+#include "third_party/skia/include/core/SkCanvas.h"
+#include "third_party/skia/include/core/SkMaskFilter.h"
 #include "ui/gfx/image/image_skia.h"
 #include "ui/gfx/image/image_skia_operations.h"
 #include "ui/gfx/image/image_skia_rep.h"
diff --git a/chrome/browser/apps/intent_helper/intent_picker_internal.cc b/chrome/browser/apps/intent_helper/intent_picker_internal.cc
index 435c15e..f12b31c 100644
--- a/chrome/browser/apps/intent_helper/intent_picker_internal.cc
+++ b/chrome/browser/apps/intent_helper/intent_picker_internal.cc
@@ -78,7 +78,7 @@
     return apps;
 
   auto* const provider = web_app::WebAppProvider::GetForWebApps(profile);
-  if (provider->registrar().GetAppUserDisplayMode(*app_id) ==
+  if (provider->registrar_unsafe().GetAppUserDisplayMode(*app_id) ==
       web_app::UserDisplayMode::kBrowser) {
     return apps;
   }
@@ -90,7 +90,7 @@
   // Prefer the web and place apps of type PWA before apps of type ARC.
   // TODO(crbug.com/824598): deterministically sort this list.
   apps.emplace(apps.begin(), PickerEntryType::kWeb, icon_model, *app_id,
-               provider->registrar().GetAppShortName(*app_id));
+               provider->registrar_unsafe().GetAppShortName(*app_id));
 
   return apps;
 }
diff --git a/chrome/browser/ash/BUILD.gn b/chrome/browser/ash/BUILD.gn
index 7f7d1778..319fa268 100644
--- a/chrome/browser/ash/BUILD.gn
+++ b/chrome/browser/ash/BUILD.gn
@@ -1097,10 +1097,14 @@
     "file_system_provider/icon_set.h",
     "file_system_provider/mount_path_util.cc",
     "file_system_provider/mount_path_util.h",
+    "file_system_provider/mount_request_handler.cc",
+    "file_system_provider/mount_request_handler.h",
     "file_system_provider/notification_manager.cc",
     "file_system_provider/notification_manager.h",
     "file_system_provider/notification_manager_interface.h",
     "file_system_provider/observer.h",
+    "file_system_provider/operation_request_manager.cc",
+    "file_system_provider/operation_request_manager.h",
     "file_system_provider/operations/abort.cc",
     "file_system_provider/operations/abort.h",
     "file_system_provider/operations/add_watcher.cc",
@@ -1649,6 +1653,8 @@
     "login/screens/assistant_optin_flow_screen.h",
     "login/screens/base_screen.cc",
     "login/screens/base_screen.h",
+    "login/screens/choobe_screen.cc",
+    "login/screens/choobe_screen.h",
     "login/screens/chrome_user_selection_screen.cc",
     "login/screens/chrome_user_selection_screen.h",
     "login/screens/chromevox_hint/chromevox_hint_detector.cc",
@@ -2337,6 +2343,8 @@
     "policy/remote_commands/crd_host_delegate.cc",
     "policy/remote_commands/crd_host_delegate.h",
     "policy/remote_commands/crd_logging.h",
+    "policy/remote_commands/crd_remote_command_utils.cc",
+    "policy/remote_commands/crd_remote_command_utils.h",
     "policy/remote_commands/device_command_fetch_status_job.cc",
     "policy/remote_commands/device_command_fetch_status_job.h",
     "policy/remote_commands/device_command_get_available_routines_job.cc",
@@ -3626,6 +3634,7 @@
     "//chromeos/ash/components/tpm",
     "//chromeos/ash/components/tpm:buildflags",
     "//chromeos/ash/services/assistant/public/cpp",
+    "//chromeos/ash/services/auth_factor_config",
     "//chromeos/ash/services/bluetooth_config:in_process_bluetooth_config",
     "//chromeos/ash/services/cros_healthd/private/cpp",
     "//chromeos/ash/services/device_sync",
@@ -4208,6 +4217,7 @@
     "../ui/webui/settings/ash/device_name_handler_unittest.cc",
     "../ui/webui/settings/ash/device_storage_handler_unittest.cc",
     "../ui/webui/settings/ash/fast_pair_saved_devices_handler_unittest.cc",
+    "../ui/webui/settings/ash/hierarchy_unittest.cc",
     "../ui/webui/settings/ash/internet_handler_unittest.cc",
     "../ui/webui/settings/ash/metrics_consent_handler_unittest.cc",
     "../ui/webui/settings/ash/multidevice_handler_unittest.cc",
@@ -4519,6 +4529,7 @@
     "file_system_provider/logging_observer.cc",
     "file_system_provider/logging_observer.h",
     "file_system_provider/mount_path_util_unittest.cc",
+    "file_system_provider/operation_request_manager_unittest.cc",
     "file_system_provider/operations/abort_unittest.cc",
     "file_system_provider/operations/add_watcher_unittest.cc",
     "file_system_provider/operations/close_file_unittest.cc",
@@ -4543,7 +4554,6 @@
     "file_system_provider/provided_file_system_unittest.cc",
     "file_system_provider/queue_unittest.cc",
     "file_system_provider/registry_unittest.cc",
-    "file_system_provider/request_manager_unittest.cc",
     "file_system_provider/scoped_file_opener_unittest.cc",
     "file_system_provider/service_unittest.cc",
     "file_system_provider/throttled_file_system_unittest.cc",
diff --git a/chrome/browser/ash/accessibility/OWNERS b/chrome/browser/ash/accessibility/OWNERS
index 0ce2304..ce64734 100644
--- a/chrome/browser/ash/accessibility/OWNERS
+++ b/chrome/browser/ash/accessibility/OWNERS
@@ -1,5 +1,5 @@
 file://ash/accessibility/OWNERS
 
 # For updates related to the launcher.
-per-file spoken_feedback_app_list_browsertest.cc=file://chrome/browser/ui/app_list/search/OWNERS
+per-file spoken_feedback_app_list_browsertest.cc=file://chrome/browser/ash/app_list/search/OWNERS
 per-file spoken_feedback_app_list_browsertest.cc=file://ash/app_list/OWNERS
diff --git a/chrome/browser/ash/accessibility/accessibility_highlights_browsertest.cc b/chrome/browser/ash/accessibility/accessibility_highlights_browsertest.cc
new file mode 100644
index 0000000..b572da8c
--- /dev/null
+++ b/chrome/browser/ash/accessibility/accessibility_highlights_browsertest.cc
@@ -0,0 +1,240 @@
+// 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 "ash/accessibility/ui/accessibility_cursor_ring_layer.h"
+#include "ash/accessibility/ui/accessibility_focus_ring_controller_impl.h"
+#include "ash/accessibility/ui/accessibility_focus_ring_layer.h"
+#include "ash/accessibility/ui/accessibility_highlight_layer.h"
+#include "ash/constants/ash_pref_names.h"
+#include "ash/shell.h"
+#include "base/test/bind.h"
+#include "build/branding_buildflags.h"
+#include "build/build_config.h"
+#include "chrome/browser/ash/accessibility/accessibility_manager.h"
+#include "chrome/browser/ash/accessibility/accessibility_test_utils.h"
+#include "chrome/browser/ash/accessibility/html_test_utils.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_window.h"
+#include "chrome/browser/ui/views/frame/browser_view.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "components/omnibox/browser/omnibox_view.h"
+#include "components/prefs/pref_service.h"
+#include "content/public/browser/render_widget_host_view.h"
+#include "content/public/test/accessibility_notification_waiter.h"
+#include "content/public/test/browser_test.h"
+#include "ui/compositor/layer.h"
+#include "ui/events/test/event_generator.h"
+#include "ui/wm/core/coordinate_conversion.h"
+
+namespace ash {
+
+class AccessibilityHighlightsBrowserTest : public InProcessBrowserTest {
+ public:
+  AccessibilityHighlightsBrowserTest(
+      const AccessibilityHighlightsBrowserTest&) = delete;
+  AccessibilityHighlightsBrowserTest& operator=(
+      const AccessibilityHighlightsBrowserTest&) = delete;
+
+ protected:
+  AccessibilityHighlightsBrowserTest() = default;
+  ~AccessibilityHighlightsBrowserTest() override = default;
+
+  // InProcessBrowserTest:
+  void SetUpOnMainThread() override {
+    aura::Window* root_window = Shell::Get()->GetPrimaryRootWindow();
+    generator_ = std::make_unique<ui::test::EventGenerator>(root_window);
+    AccessibilityManager::Get()->SetFocusRingObserverForTest(
+        base::BindRepeating(
+            &AccessibilityHighlightsBrowserTest::OnFocusRingsChanged,
+            base::Unretained(this)));
+    Shell::Get()->accessibility_focus_ring_controller()->SetNoFadeForTesting();
+    ASSERT_TRUE(
+        ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));
+  }
+
+  content::WebContents* GetWebContents() {
+    return browser()->tab_strip_model()->GetActiveWebContents();
+  }
+
+  void OnFocusRingsChanged() {
+    if (focus_ring_waiter_)
+      std::move(focus_ring_waiter_).Run();
+  }
+
+  void WaitForFocusRingsChanged() {
+    base::RunLoop run_loop;
+    focus_ring_waiter_ = run_loop.QuitClosure();
+    run_loop.Run();
+  }
+
+  std::unique_ptr<ui::test::EventGenerator> generator_;
+  base::OnceClosure focus_ring_waiter_;
+};
+
+IN_PROC_BROWSER_TEST_F(AccessibilityHighlightsBrowserTest,
+                       CursorHighlightAddsFocusRing) {
+  AccessibilityFocusRingControllerImpl* controller =
+      Shell::Get()->accessibility_focus_ring_controller();
+  EXPECT_FALSE(controller->cursor_layer_for_testing());
+
+  PrefService* prefs = browser()->profile()->GetPrefs();
+  prefs->SetBoolean(prefs::kAccessibilityCursorHighlightEnabled, true);
+
+  gfx::Point mouse_location(100, 100);
+  generator_->MoveMouseTo(mouse_location);
+  AccessibilityCursorRingLayer* cursor_layer =
+      controller->cursor_layer_for_testing();
+  ASSERT_TRUE(cursor_layer);
+  gfx::Rect bounds = cursor_layer->layer()->GetTargetBounds();
+  EXPECT_EQ(bounds.CenterPoint(), mouse_location);
+
+  mouse_location = gfx::Point(200, 100);
+  generator_->MoveMouseTo(mouse_location);
+  bounds = cursor_layer->layer()->GetTargetBounds();
+  EXPECT_EQ(bounds.CenterPoint(), mouse_location);
+
+  // Turns off again.
+  prefs->SetBoolean(prefs::kAccessibilityCursorHighlightEnabled, false);
+  EXPECT_FALSE(controller->cursor_layer_for_testing());
+}
+
+IN_PROC_BROWSER_TEST_F(AccessibilityHighlightsBrowserTest,
+                       CaretHighlightWebContents) {
+  AccessibilityFocusRingControllerImpl* controller =
+      Shell::Get()->accessibility_focus_ring_controller();
+  EXPECT_FALSE(controller->caret_layer_for_testing());
+
+  PrefService* prefs = browser()->profile()->GetPrefs();
+  prefs->SetBoolean(prefs::kAccessibilityCaretHighlightEnabled, true);
+
+  // Still doesn't exist because no input text area is focused.
+  EXPECT_FALSE(controller->caret_layer_for_testing());
+
+  const std::string kTestCases[] = {
+      R"(data:text/html;charset=utf-8,
+      <textarea id="field">Hello there</textarea>
+    )",
+      R"(data:text/html;charset=utf-8,
+      <input id="field" type="text" value="How's it going?">
+    )",
+      R"(data:text/html;charset=utf-8,
+      <div id="field" contenteditable="true">
+        <p>Not bad, and <b>you</b>?</p>
+      </div>
+    )",
+  };
+
+  for (const auto& url : kTestCases) {
+    content::AccessibilityNotificationWaiter waiter(
+        GetWebContents(), ui::kAXModeComplete, ax::mojom::Event::kLoadComplete);
+    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL(url)));
+    std::ignore = waiter.WaitForNotification();
+
+    generator_->PressAndReleaseKey(ui::KeyboardCode::VKEY_TAB);
+    WaitForFocusRingsChanged();
+    AccessibilityCursorRingLayer* caret_layer =
+        controller->caret_layer_for_testing();
+    ASSERT_TRUE(caret_layer);
+    gfx::Rect initial_bounds = caret_layer->layer()->GetTargetBounds();
+
+    gfx::Rect element_bounds =
+        GetControlBoundsInRoot(GetWebContents(), "field");
+    EXPECT_TRUE(element_bounds.Contains(initial_bounds.CenterPoint()));
+
+    // Right arrow shifts the bounds to the right slightly.
+    generator_->PressAndReleaseKey(ui::KeyboardCode::VKEY_RIGHT);
+    WaitForFocusRingsChanged();
+    gfx::Rect new_bounds = caret_layer->layer()->GetTargetBounds();
+    EXPECT_EQ(initial_bounds.y(), new_bounds.y());
+    EXPECT_LT(initial_bounds.x(), new_bounds.x());
+
+    // Typing something shifts the bounds to the right also.
+    generator_->PressAndReleaseKey(ui::KeyboardCode::VKEY_A);
+    WaitForFocusRingsChanged();
+    initial_bounds = new_bounds;
+    new_bounds = caret_layer->layer()->GetTargetBounds();
+    EXPECT_EQ(initial_bounds.y(), new_bounds.y());
+    EXPECT_LT(initial_bounds.x(), new_bounds.x());
+  }
+
+  // Turns off again.
+  prefs->SetBoolean(prefs::kAccessibilityCaretHighlightEnabled, false);
+  EXPECT_FALSE(controller->caret_layer_for_testing());
+}
+
+IN_PROC_BROWSER_TEST_F(AccessibilityHighlightsBrowserTest,
+                       CaretHighlightOmnibox) {
+  AccessibilityFocusRingControllerImpl* controller =
+      Shell::Get()->accessibility_focus_ring_controller();
+  PrefService* prefs = browser()->profile()->GetPrefs();
+  prefs->SetBoolean(prefs::kAccessibilityCaretHighlightEnabled, true);
+
+  // Jump to the omnibox.
+  generator_->PressAndReleaseKey(ui::KeyboardCode::VKEY_L, ui::EF_CONTROL_DOWN);
+  WaitForFocusRingsChanged();
+  AccessibilityCursorRingLayer* caret_layer =
+      controller->caret_layer_for_testing();
+  ASSERT_TRUE(caret_layer);
+  gfx::Rect bounds = caret_layer->layer()->GetTargetBounds();
+
+  const gfx::Rect omnibox_bounds =
+      BrowserView::GetBrowserViewForBrowser(browser())
+          ->GetViewByID(VIEW_ID_OMNIBOX)
+          ->GetBoundsInScreen();
+
+  EXPECT_EQ(bounds.CenterPoint().y(), omnibox_bounds.CenterPoint().y());
+
+  // On the left edge of the omnibox.
+  EXPECT_LT(bounds.x(), omnibox_bounds.x());
+  EXPECT_GT(bounds.right(), omnibox_bounds.x());
+
+  // Typing something shifts the bounds to the right.
+  generator_->PressAndReleaseKey(ui::KeyboardCode::VKEY_K);
+  gfx::Rect new_bounds = caret_layer->layer()->GetTargetBounds();
+  EXPECT_EQ(bounds.y(), new_bounds.y());
+  EXPECT_LT(bounds.x(), new_bounds.x());
+}
+
+IN_PROC_BROWSER_TEST_F(AccessibilityHighlightsBrowserTest, FocusHighlight) {
+  AccessibilityFocusRingControllerImpl* controller =
+      Shell::Get()->accessibility_focus_ring_controller();
+  PrefService* prefs = browser()->profile()->GetPrefs();
+  prefs->SetBoolean(prefs::kAccessibilityFocusHighlightEnabled, true);
+
+  const std::string url = R"(
+    data:text/html;charset=utf-8,
+    <input type="text" id="focus1">
+    <input type="checkbox" id="focus2">
+    <input type="radio" id="focus3">
+    <input type="submit" id="focus4">
+    <a href="" id="focus5">link</a>
+  )";
+  content::AccessibilityNotificationWaiter waiter(
+      browser()->tab_strip_model()->GetActiveWebContents(), ui::kAXModeComplete,
+      ax::mojom::Event::kLoadComplete);
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL(url)));
+  std::ignore = waiter.WaitForNotification();
+
+  const std::string kTestCases[] = {"focus1", "focus2", "focus3", "focus4",
+                                    "focus5"};
+
+  for (const auto& element : kTestCases) {
+    generator_->PressAndReleaseKey(ui::KeyboardCode::VKEY_TAB);
+    WaitForFocusRingsChanged();
+    const AccessibilityFocusRingGroup* highlights =
+        controller->GetFocusRingGroupForTesting("HighlightController");
+    ASSERT_TRUE(highlights);
+    auto& focus_rings = highlights->focus_layers_for_testing();
+    EXPECT_EQ(focus_rings.size(), 1u);
+    gfx::Rect focus_bounds = focus_rings.at(0)->layer()->GetTargetBounds();
+    gfx::Rect element_bounds =
+        GetControlBoundsInRoot(GetWebContents(), element);
+    EXPECT_EQ(element_bounds.CenterPoint(), focus_bounds.CenterPoint());
+    EXPECT_TRUE(focus_bounds.Contains(element_bounds));
+  }
+}
+
+}  // namespace ash
diff --git a/chrome/browser/ash/accessibility/accessibility_manager.cc b/chrome/browser/ash/accessibility/accessibility_manager.cc
index abcf41c..249323f8 100644
--- a/chrome/browser/ash/accessibility/accessibility_manager.cc
+++ b/chrome/browser/ash/accessibility/accessibility_manager.cc
@@ -2020,15 +2020,10 @@
     std::unique_ptr<AccessibilityFocusRingInfo> focus_ring) {
   AccessibilityFocusRingController::Get()->SetFocusRing(focus_ring_id,
                                                         std::move(focus_ring));
-
-  if (focus_ring_observer_for_test_)
-    focus_ring_observer_for_test_.Run();
 }
 
 void AccessibilityManager::HideFocusRing(std::string focus_ring_id) {
   AccessibilityFocusRingController::Get()->HideFocusRing(focus_ring_id);
-  if (focus_ring_observer_for_test_)
-    focus_ring_observer_for_test_.Run();
 }
 
 void AccessibilityManager::SetHighlights(
@@ -2117,7 +2112,8 @@
 
 void AccessibilityManager::SetFocusRingObserverForTest(
     base::RepeatingCallback<void()> observer) {
-  focus_ring_observer_for_test_ = observer;
+  AccessibilityFocusRingController::Get()->SetFocusRingObserverForTesting(
+      observer);
 }
 
 void AccessibilityManager::SetHighlightsObserverForTest(
diff --git a/chrome/browser/ash/accessibility/accessibility_manager.h b/chrome/browser/ash/accessibility/accessibility_manager.h
index df1ad72..514774a 100644
--- a/chrome/browser/ash/accessibility/accessibility_manager.h
+++ b/chrome/browser/ash/accessibility/accessibility_manager.h
@@ -615,7 +615,6 @@
   bool dictation_triggered_by_user_ = false;
   bool ignore_dictation_locale_pref_change_ = false;
 
-  base::RepeatingCallback<void()> focus_ring_observer_for_test_;
   base::RepeatingCallback<void()> highlights_observer_for_test_;
   base::RepeatingCallback<void()> select_to_speak_state_observer_for_test_;
   base::RepeatingCallback<void(const gfx::Rect&)>
diff --git a/chrome/browser/ash/accessibility/autoclick_browsertest.cc b/chrome/browser/ash/accessibility/autoclick_browsertest.cc
index 41bf4851..dcc2422 100644
--- a/chrome/browser/ash/accessibility/autoclick_browsertest.cc
+++ b/chrome/browser/ash/accessibility/autoclick_browsertest.cc
@@ -44,6 +44,12 @@
   AutoclickBrowserTest(const AutoclickBrowserTest&) = delete;
   AutoclickBrowserTest& operator=(const AutoclickBrowserTest&) = delete;
 
+  void OnFocusRingChanged() {
+    if (loop_runner_ && loop_runner_->running()) {
+      loop_runner_->Quit();
+    }
+  }
+
  protected:
   AutoclickBrowserTest() = default;
   ~AutoclickBrowserTest() override = default;
@@ -174,6 +180,11 @@
     generator_->MoveMouseTo(bounds.CenterPoint());
   }
 
+  void WaitForFocusRingChanged() {
+    loop_runner_ = std::make_unique<base::RunLoop>();
+    loop_runner_->Run();
+  }
+
   base::WeakPtr<AutoclickBrowserTest> GetWeakPtr() {
     return weak_ptr_factory_.GetWeakPtr();
   }
@@ -184,6 +195,7 @@
   std::unique_ptr<ExtensionConsoleErrorObserver> console_observer_;
   std::unique_ptr<PrefChangeRegistrar> pref_change_registrar_;
   base::OnceClosure pref_change_waiter_;
+  std::unique_ptr<base::RunLoop> loop_runner_;
   base::WeakPtrFactory<AutoclickBrowserTest> weak_ptr_factory_{this};
 };
 
@@ -272,14 +284,11 @@
                   {IDC_CONTENT_CONTEXT_COPY, IDC_CONTENT_CONTEXT_PASTE}));
 }
 
-// TODO(b/261462562): Fix flaky crash and enable.
 IN_PROC_BROWSER_TEST_F(AutoclickBrowserTest,
-                       DISABLED_ScrollHoverHighlightsScrollableArea) {
+                       ScrollHoverHighlightsScrollableArea) {
   // Create a callback for the focus ring observer.
-  base::RunLoop runner;
-  base::RepeatingCallback<void()> callback =
-      base::BindLambdaForTesting([&runner]() { runner.Quit(); });
-  AccessibilityManager::Get()->SetFocusRingObserverForTest(callback);
+  AccessibilityManager::Get()->SetFocusRingObserverForTest(base::BindRepeating(
+      &AutoclickBrowserTest::OnFocusRingChanged, GetWeakPtr()));
 
   LoadURLAndAutoclick(R"(
       data:text/html;charset=utf-8,
@@ -301,7 +310,7 @@
   SetAutoclickEventType(AutoclickEventType::kScroll);
 
   HoverOverHtmlElement("test_textarea");
-  runner.Run();
+  WaitForFocusRingChanged();
 
   focus_ring_group = controller->GetFocusRingGroupForTesting(focus_ring_id);
   ASSERT_NE(nullptr, focus_ring_group);
diff --git a/chrome/browser/ash/accessibility/dictation_browsertest.cc b/chrome/browser/ash/accessibility/dictation_browsertest.cc
index 77da76d..e0dd6e93 100644
--- a/chrome/browser/ash/accessibility/dictation_browsertest.cc
+++ b/chrome/browser/ash/accessibility/dictation_browsertest.cc
@@ -310,8 +310,11 @@
 
   void SetUpOnMainThread() override {
     InProcessBrowserTest::SetUpOnMainThread();
-    test_helper_.SetUp(browser()->profile());
 
+    // Set up the Pumpkin dir before turning on Dictation because the
+    // extension will immediately request a Pumpkin installation once activated.
+    SetUpPumpkinDir();
+    test_helper_.SetUp(browser()->profile());
     ASSERT_FALSE(AccessibilityManager::Get()->IsDictationEnabled());
     console_observer_ = std::make_unique<ExtensionConsoleErrorObserver>(
         browser()->profile(), extension_misc::kAccessibilityCommonExtensionId);
@@ -350,6 +353,10 @@
       window.domAutomationController.send("done");
     )";
     ExecuteAccessibilityCommonScript(script);
+
+    // Dictation will request a Pumpkin install when it starts up. Wait for
+    // the install to succeed.
+    WaitForPumpkinTaggerReady();
   }
 
   void TearDownOnMainThread() override {
@@ -551,6 +558,14 @@
     return Dictation::GetAllSupportedLocales();
   }
 
+  void DisablePumpkin() {
+    std::string script = R"(
+      accessibilityCommon.dictation_.disablePumpkinForTesting_();
+      window.domAutomationController.send("done");
+    )";
+    ExecuteAccessibilityCommonScript(script);
+  }
+
   std::string ExecuteAccessibilityCommonScript(const std::string& script) {
     return extensions::browsertest_util::ExecuteScriptInBackgroundPage(
         /*context=*/browser()->profile(),
@@ -990,6 +1005,8 @@
     GetActiveUserPrefs()->SetString(prefs::kAccessibilityDictationLocale, "ja");
 
     DictationTestBase::SetUpOnMainThread();
+
+    DisablePumpkin();
   }
 };
 
@@ -1118,6 +1135,8 @@
  protected:
   void SetUpOnMainThread() override {
     DictationTest::SetUpOnMainThread();
+    // Disable Pumpkin so that we use regex parsing.
+    DisablePumpkin();
     ToggleDictationWithKeystroke();
     WaitForRecognitionStarted();
   }
@@ -1820,31 +1839,8 @@
   DictationPumpkinTest& operator=(const DictationPumpkinTest&) = delete;
 
  protected:
-  void SetUpCommandLine(base::CommandLine* command_line) override {
-    DictationTest::SetUpCommandLine(command_line);
-    std::vector<base::test::FeatureRef> enabled_features;
-    enabled_features.emplace_back(base::test::FeatureRef(
-        ::features::kExperimentalAccessibilityDictationWithPumpkin));
-    enabled_features.emplace_back(base::test::FeatureRef(
-        ::features::kExperimentalAccessibilityDictationMoreCommands));
-    scoped_feature_list_.InitWithFeatures(
-        enabled_features, std::vector<base::test::FeatureRef>());
-  }
-
   void SetUpOnMainThread() override {
-    // Set the path to the Pumpkin test files. For more details, see the
-    // `pumpkin_test_files` rule in the accessibility_common BUILD file.
-    // Must be done before DictationTest::SetUpOnMainThread because the parent
-    // class method will start up the extension and immediately request a
-    // Pumpkin installation.
-    SetUpPumpkinDir();
     DictationTest::SetUpOnMainThread();
-
-    // Dictation will request a Pumpkin install when it starts up. Wait for
-    // the install to succeed. Must be done after
-    // DictationTest::SetUpOnMainThread because the steps here assume that the
-    // extension is active.
-    WaitForPumpkinTaggerReady();
     ToggleDictationWithKeystroke();
     WaitForRecognitionStarted();
   }
diff --git a/chrome/browser/ash/accessibility/select_to_speak_browsertest.cc b/chrome/browser/ash/accessibility/select_to_speak_browsertest.cc
index 5819eb2..11c26ad 100644
--- a/chrome/browser/ash/accessibility/select_to_speak_browsertest.cc
+++ b/chrome/browser/ash/accessibility/select_to_speak_browsertest.cc
@@ -220,6 +220,9 @@
         module.selectToSpeak.setOnLoadDesktopCallbackForTest(() => {
             window.domAutomationController.send('ready');
           });
+        // Set enhanced network voices dialog as shown, because the pref
+        // change takes some time to propagate.
+        module.selectToSpeak.prefsManager_.enhancedVoicesDialogShown_ = true;
       })();
     )JS");
     std::string result =
diff --git a/chrome/browser/ash/accessibility/spoken_feedback_app_list_browsertest.cc b/chrome/browser/ash/accessibility/spoken_feedback_app_list_browsertest.cc
index 7be2ffc..e2e32691 100644
--- a/chrome/browser/ash/accessibility/spoken_feedback_app_list_browsertest.cc
+++ b/chrome/browser/ash/accessibility/spoken_feedback_app_list_browsertest.cc
@@ -16,13 +16,13 @@
 #include "base/test/scoped_feature_list.h"
 #include "base/test/simple_test_tick_clock.h"
 #include "chrome/browser/ash/accessibility/spoken_feedback_browsertest.h"
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
+#include "chrome/browser/ash/app_list/search/search_controller_impl.h"
+#include "chrome/browser/ash/app_list/search/search_provider.h"
 #include "chrome/browser/ash/app_list/test/chrome_app_list_test_support.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/app_list/app_list_client_impl.h"
 #include "chrome/browser/ui/app_list/chrome_app_list_model_updater.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
-#include "chrome/browser/ui/app_list/search/search_controller_impl.h"
-#include "chrome/browser/ui/app_list/search/search_provider.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_window.h"
 #include "chrome/common/chrome_switches.h"
diff --git a/chrome/browser/ash/accessibility/spoken_feedback_browsertest.cc b/chrome/browser/ash/accessibility/spoken_feedback_browsertest.cc
index 28ee04d..5b7dc45c 100644
--- a/chrome/browser/ash/accessibility/spoken_feedback_browsertest.cc
+++ b/chrome/browser/ash/accessibility/spoken_feedback_browsertest.cc
@@ -726,6 +726,58 @@
   sm_.Replay();
 }
 
+IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, ShowTablesList) {
+  EnableChromeVox();
+
+  sm_.Call([this]() {
+    ASSERT_TRUE(ui_test_utils::NavigateToURL(
+        browser(), GURL(R"(data:text/html;charset=utf-8,
+        <button autofocus>Start here</button>
+        <table>
+          <caption>Can Collection Drive Results</caption>
+          <tr><th>Name</th> <th>Date</th> <th>Cans collected</th></tr>
+          <tr><td>Joey</td> <td>12/1</td> <td>177</td></tr>
+          <tr><td>Moss</td> <td>12/2</td> <td>122</td></tr>
+        </table>
+
+        <table>
+          <caption>Types of Can Received</caption>
+          <tr><th>Food</th>   <th>Count</th></tr>
+          <tr><td>Soup</td>   <td>101</td></tr>
+          <tr><td>Beans</td>  <td>88</td></tr>
+          <tr><td>Pumpkin</td><td>87</td></tr>
+        </table>)")));
+  });
+  // Wait for the page to load.
+  sm_.ExpectSpeech("Start here");
+
+  // Open the Tables menu.
+  sm_.Call([this]() { SendKeyPressWithSearchAndControl(ui::VKEY_T); });
+  sm_.ExpectSpeech("Table Menu");
+
+  // Verify the first menu item is read.
+  sm_.ExpectSpeech("Can Collection Drive Results");
+  sm_.ExpectSpeech("Menu item 1 of 2");
+  // Move down to read the second item.
+  sm_.Call([this]() { SendKeyPress(ui::VKEY_DOWN); });
+  sm_.ExpectSpeech("Types of Can Received");
+  sm_.ExpectSpeech("Menu item 2 of 2");
+  // Return to the first item.
+  sm_.Call([this]() { SendKeyPress(ui::VKEY_UP); });
+  sm_.ExpectSpeech("Can Collection Drive Results");
+  // Select the first item.
+  sm_.Call([this]() { SendKeyPress(ui::VKEY_SPACE); });
+  sm_.ExpectSpeech("Can Collection Drive Results");
+
+  // Verify we're back on the web page and the menus are closed.
+  sm_.Call([this]() {
+    SendKeyPressWithSearch(ui::VKEY_LEFT);
+    SendKeyPressWithSearch(ui::VKEY_LEFT);
+  });
+  sm_.ExpectSpeech("Start here");
+  sm_.Replay();
+}
+
 IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, ShowHeadingList) {
   EnableChromeVox();
 
@@ -814,6 +866,29 @@
   sm_.Replay();
 }
 
+IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, NavigateChromeVoxMenu) {
+  EnableChromeVox();
+  sm_.Call([this]() { SendKeyPressWithSearch(ui::VKEY_OEM_PERIOD); });
+  sm_.ExpectSpeech("Search the menus");
+  sm_.Call([this]() {
+    SendKeyPress(ui::VKEY_RIGHT);
+    SendKeyPress(ui::VKEY_RIGHT);
+    SendKeyPress(ui::VKEY_RIGHT);
+    SendKeyPress(ui::VKEY_RIGHT);
+  });
+  sm_.ExpectSpeech("ChromeVox Menu");
+  sm_.Call([this]() {
+    SendKeyPress(ui::VKEY_DOWN);
+    SendKeyPress(ui::VKEY_DOWN);
+    SendKeyPress(ui::VKEY_DOWN);
+  });
+  sm_.ExpectSpeech("Open ChromeVox Tutorial");
+  sm_.Call([this]() { SendKeyPress(ui::VKEY_SPACE); });
+  sm_.ExpectSpeech("ChromeVox tutorial");
+
+  sm_.Replay();
+}
+
 IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, OpenStatusTray) {
   EnableChromeVox();
 
diff --git a/chrome/browser/ash/app_list/arc/arc_app_utils.cc b/chrome/browser/ash/app_list/arc/arc_app_utils.cc
index 79a2176b..5f9207f 100644
--- a/chrome/browser/ash/app_list/arc/arc_app_utils.cc
+++ b/chrome/browser/ash/app_list/arc/arc_app_utils.cc
@@ -30,6 +30,7 @@
 #include "chrome/browser/apps/app_service/intent_util.h"
 #include "chrome/browser/ash/app_list/arc/intent.h"
 #include "chrome/browser/ash/app_list/search/ranking/launch_data.h"
+#include "chrome/browser/ash/app_list/search/search_controller.h"
 #include "chrome/browser/ash/arc/arc_migration_guide_notification.h"
 #include "chrome/browser/ash/arc/arc_util.h"
 #include "chrome/browser/ash/arc/boot_phase_monitor/arc_boot_phase_monitor_bridge.h"
@@ -43,7 +44,6 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_observer.h"
 #include "chrome/browser/ui/app_list/app_list_client_impl.h"
-#include "chrome/browser/ui/app_list/search/search_controller.h"
 #include "chrome/browser/ui/ash/shelf/arc_app_shelf_id.h"
 #include "chrome/browser/ui/ash/shelf/arc_shelf_spinner_item_controller.h"
 #include "chrome/browser/ui/ash/shelf/chrome_shelf_controller.h"
diff --git a/chrome/browser/ash/app_list/search/DEPS b/chrome/browser/ash/app_list/search/DEPS
index ce5e7b9..98c13e7 100644
--- a/chrome/browser/ash/app_list/search/DEPS
+++ b/chrome/browser/ash/app_list/search/DEPS
@@ -1,6 +1,3 @@
-# TODO(https://crbug.com/1164001): When this file is edited, same file in
-# //chrome/browser/ui/app_list/search should be updated as well. We need
-# to sync both files until the migration is done.
 include_rules = [
   "+ash/accelerators",
   "+ash/assistant/model",
diff --git a/chrome/browser/ash/app_list/search/README.md b/chrome/browser/ash/app_list/search/README.md
new file mode 100644
index 0000000..baedab6
--- /dev/null
+++ b/chrome/browser/ash/app_list/search/README.md
@@ -0,0 +1,103 @@
+# About
+
+This folder contains the backend implementation of Chrome OS launcher search.
+
+# Overview of search infrastructure
+
+## Important classes
+
+### Core
+
+- **SearchController**. This controls all the core search functions such as
+  starting a search, collecting results, ranking and publishing. Implemented by
+  [`SearchControllerImplNew`](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ash/app_list/search/search_controller_impl.h;l=44;drc=ec05d2cd9ff57132c80e7071942626f98c6e3571).
+
+  To interact with the frontend, it calls the 
+  [`AppListController`](https://source.chromium.org/chromium/chromium/src/+/main:ash/public/cpp/app_list/app_list_controller.h;l=31;drc=16b9100fa38b90f93e29fb6d7e4578a7eaeb7a1f) and
+  [`AppListModelUpdater`](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/app_list/app_list_model_updater.h;l=26;drc=4a8573cb240df29b0e4d9820303538fb28e31d84), 
+  and is called by the [`AppListClient`](https://source.chromium.org/chromium/chromium/src/+/main:ash/public/cpp/app_list/app_list_client.h;l=36;drc=3a215d1e60a3b32928a50d00ea07ae52ea491a16).
+- **SearchProvider**. The base class for all search providers. Each search
+  provider typically handles one type of result, such as settings, apps or
+  files. Some search providers implement their search function locally, while
+  others call out to further backends.
+- **SearchControllerFactory**. Responsible for the creation of the search
+  controller and its providers at start-up time.
+- **ChromeSearchResult**. The base class for all search results. Each
+  [`ChromeSearchResult`](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ash/app_list/search/chrome_search_result.h;l=35;drc=f828fc7710b7922a4339c030da3cfe48497d4300) 
+  contains the information associated with one result. This information is stored in a 
+  [`SearchResultMetadata`](https://source.chromium.org/chromium/chromium/src/+/main:ash/public/cpp/app_list/app_list_types.h;l=571;drc=180c7396abb3e4aa0a020babde5b19e80035ca43) 
+  object which is piped to the frontend code.
+
+### Ranking
+
+Ranking is the process of assigning scores to each result and category to
+determine their final display order. Located inside the 
+[`ranking/`](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ash/app_list/search/ranking/) 
+subdirectory.
+
+- **RankerManager**. This owns the ranking stack and determines the order of
+  ranking steps.
+- **Ranker**. The base class for all rankers. Rankers can be used for all kinds
+  of post-processing steps, including but not limited to ranking.
+
+### Metrics
+
+- **AppListNotifierImpl**. Located in the parent directory
+  [`chrome/browser/ui/app_list/`](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/app_list/). 
+  Contains a state machine that converts raw UI events into information such as impressions and launches.
+- **SearchMetricsManager**. Observes the [`AppListNotifier`](https://source.chromium.org/chromium/chromium/src/+/main:ash/public/cpp/app_list/app_list_notifier.h;l=28;drc=ccc5ecdf824f172bf8675eb33f5377483289c334)
+  and logs metrics accordingly.
+
+## Life of a search query
+
+1. The user types a query into the launcher search box. This filters through UI
+   code until it eventually reaches 
+   [`SearchController::StartSearch(query)`](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ash/app_list/search/search_controller.h;l=70;drc=ec05d2cd9ff57132c80e7071942626f98c6e3571).
+2. The [`SearchController`](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ash/app_list/search/search_controller.h;l=50;drc=ec05d2cd9ff57132c80e7071942626f98c6e3571) 
+  forwards this query to its various search providers.
+3. Search providers return their results **asynchronously**.
+4. The [`SearchController`](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ash/app_list/search/search_controller.h;l=50;drc=ec05d2cd9ff57132c80e7071942626f98c6e3571) 
+collects these results and performs ranking on the results and their categories.
+5. Results are published to the UI.
+
+Steps #3-5 may be repeated several times due to the asynchronous nature of #3.
+The [`BurnInController`](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ash/app_list/search/burnin_controller.h;l=20;drc=f828fc7710b7922a4339c030da3cfe48497d4300) 
+contains timing logic to reduce the UI effect of results popping in.
+
+Training may be performed:
+
+6. The user clicks on a result.
+7. The [`SearchController`](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ash/app_list/search/search_controller.h;l=50;drc=ec05d2cd9ff57132c80e7071942626f98c6e3571) 
+forwards this information to its various search providers and rankers, 
+which can use this information to inform future searches and ranking.
+
+## Life of zero state
+
+Zero state is the UI shown before the user types any query. It consists of the
+Continue section (recent files), the recent apps row, as well as the app grid.
+The [`SearchController`](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ash/app_list/search/search_controller.h;l=50;drc=ec05d2cd9ff57132c80e7071942626f98c6e3571)
+handles ranking for continue files and recent apps.
+
+Steps #1-4 closely mirror query search, but publishing is handled differently.
+
+1. The user opens the launcher. This eventually reaches
+   [`SearchController::StartZeroState(callback, timeout)`](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ash/app_list/search/search_controller.h;l=72;drc=ec05d2cd9ff57132c80e7071942626f98c6e3571).
+   - The UI blocks itself until `callback` is run, which by contract should
+     happen no later than `timeout`.
+2. The [`SearchController`](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ash/app_list/search/search_controller.h;l=50;drc=ec05d2cd9ff57132c80e7071942626f98c6e3571) 
+  forwards this request to its various zero state providers.
+3. Providers return their results **asynchronously**.
+4. The [`SearchController`](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ash/app_list/search/search_controller.h;l=50;drc=ec05d2cd9ff57132c80e7071942626f98c6e3571) 
+  collects these results and performs ranking on the results and their categories.
+5. Once either of the following two conditions is satisfied, the
+   [`SearchController`](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ash/app_list/search/search_controller.h;l=50;drc=ec05d2cd9ff57132c80e7071942626f98c6e3571) 
+   will publish any existing results and unblock the UI:
+   - [`timeout`](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ash/app_list/search/search_controller.h;l=73;drc=ec05d2cd9ff57132c80e7071942626f98c6e3571) has elapsed,
+   - All zero state providers have returned.
+6. If there are any providers still pending, the [`SearchController`](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ash/app_list/search/search_controller.h;l=50;drc=ec05d2cd9ff57132c80e7071942626f98c6e3571) waits until
+   all of them have returned and publishes results once more to the UI.
+
+The most common situation is that recent apps return before the timeout, but the
+continue files providers return later.
+
+Training may be performed, the same as with query search.
diff --git a/chrome/browser/ui/app_list/search/app_result.cc b/chrome/browser/ash/app_list/search/app_result.cc
similarity index 95%
rename from chrome/browser/ui/app_list/search/app_result.cc
rename to chrome/browser/ash/app_list/search/app_result.cc
index 1ac0fbc..293f7ca7 100644
--- a/chrome/browser/ui/app_list/search/app_result.cc
+++ b/chrome/browser/ash/app_list/search/app_result.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/ui/app_list/search/app_result.h"
+#include "chrome/browser/ash/app_list/search/app_result.h"
 
 #include "ash/public/cpp/app_list/app_list_switches.h"
 #include "base/time/time.h"
diff --git a/chrome/browser/ui/app_list/search/app_result.h b/chrome/browser/ash/app_list/search/app_result.h
similarity index 86%
rename from chrome/browser/ui/app_list/search/app_result.h
rename to chrome/browser/ash/app_list/search/app_result.h
index 96984558..4c96d94 100644
--- a/chrome/browser/ui/app_list/search/app_result.h
+++ b/chrome/browser/ash/app_list/search/app_result.h
@@ -2,16 +2,16 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_UI_APP_LIST_SEARCH_APP_RESULT_H_
-#define CHROME_BROWSER_UI_APP_LIST_SEARCH_APP_RESULT_H_
+#ifndef CHROME_BROWSER_ASH_APP_LIST_SEARCH_APP_RESULT_H_
+#define CHROME_BROWSER_ASH_APP_LIST_SEARCH_APP_RESULT_H_
 
 #include <memory>
 #include <string>
 
 #include "ash/public/cpp/app_list/app_list_metrics.h"
 #include "base/memory/weak_ptr.h"
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
 #include "chrome/browser/ui/app_list/app_context_menu_delegate.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
 
 class AppListControllerDelegate;
 class Profile;
@@ -61,4 +61,4 @@
 
 }  // namespace app_list
 
-#endif  // CHROME_BROWSER_UI_APP_LIST_SEARCH_APP_RESULT_H_
+#endif  // CHROME_BROWSER_ASH_APP_LIST_SEARCH_APP_RESULT_H_
diff --git a/chrome/browser/ui/app_list/search/app_search_data_source.cc b/chrome/browser/ash/app_list/search/app_search_data_source.cc
similarity index 98%
rename from chrome/browser/ui/app_list/search/app_search_data_source.cc
rename to chrome/browser/ash/app_list/search/app_search_data_source.cc
index 7a919167..26fb42f 100644
--- a/chrome/browser/ui/app_list/search/app_search_data_source.cc
+++ b/chrome/browser/ash/app_list/search/app_search_data_source.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/ui/app_list/search/app_search_data_source.h"
+#include "chrome/browser/ash/app_list/search/app_search_data_source.h"
 
 #include <algorithm>
 #include <set>
@@ -18,11 +18,11 @@
 #include "chrome/browser/apps/app_service/app_service_proxy.h"
 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
 #include "chrome/browser/ash/app_list/arc/arc_app_utils.h"
+#include "chrome/browser/ash/app_list/search/app_result.h"
+#include "chrome/browser/ash/app_list/search/app_service_app_result.h"
 #include "chrome/browser/ash/extensions/gfx_utils.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/sync/session_sync_service_factory.h"
-#include "chrome/browser/ui/app_list/search/app_result.h"
-#include "chrome/browser/ui/app_list/search/app_service_app_result.h"
 #include "chrome/browser/web_applications/web_app_id_constants.h"
 #include "chromeos/ash/components/string_matching/fuzzy_tokenized_string_match.h"
 #include "chromeos/ash/components/string_matching/tokenized_string.h"
diff --git a/chrome/browser/ui/app_list/search/app_search_data_source.h b/chrome/browser/ash/app_list/search/app_search_data_source.h
similarity index 93%
rename from chrome/browser/ui/app_list/search/app_search_data_source.h
rename to chrome/browser/ash/app_list/search/app_search_data_source.h
index 77cf46fa..acadd3e6 100644
--- a/chrome/browser/ui/app_list/search/app_search_data_source.h
+++ b/chrome/browser/ash/app_list/search/app_search_data_source.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_APP_LIST_SEARCH_APP_SEARCH_DATA_SOURCE_H_
-#define CHROME_BROWSER_UI_APP_LIST_SEARCH_APP_SEARCH_DATA_SOURCE_H_
+#ifndef CHROME_BROWSER_ASH_APP_LIST_SEARCH_APP_SEARCH_DATA_SOURCE_H_
+#define CHROME_BROWSER_ASH_APP_LIST_SEARCH_APP_SEARCH_DATA_SOURCE_H_
 
 #include <memory>
 #include <string>
@@ -13,7 +13,7 @@
 #include "base/containers/flat_map.h"
 #include "base/memory/weak_ptr.h"
 #include "chrome/browser/apps/app_service/app_service_proxy_forward.h"
-#include "chrome/browser/ui/app_list/search/search_provider.h"
+#include "chrome/browser/ash/app_list/search/search_provider.h"
 #include "components/services/app_service/public/cpp/app_registry_cache.h"
 #include "components/services/app_service/public/cpp/icon_cache.h"
 
@@ -117,4 +117,4 @@
 
 }  // namespace app_list
 
-#endif  // CHROME_BROWSER_UI_APP_LIST_SEARCH_APP_SEARCH_DATA_SOURCE_H_
+#endif  // CHROME_BROWSER_ASH_APP_LIST_SEARCH_APP_SEARCH_DATA_SOURCE_H_
diff --git a/chrome/browser/ui/app_list/search/app_search_provider.cc b/chrome/browser/ash/app_list/search/app_search_provider.cc
similarity index 93%
rename from chrome/browser/ui/app_list/search/app_search_provider.cc
rename to chrome/browser/ash/app_list/search/app_search_provider.cc
index 1c5a2c6..8afb69ca 100644
--- a/chrome/browser/ui/app_list/search/app_search_provider.cc
+++ b/chrome/browser/ash/app_list/search/app_search_provider.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/ui/app_list/search/app_search_provider.h"
+#include "chrome/browser/ash/app_list/search/app_search_provider.h"
 
 #include <string>
 #include <utility>
@@ -15,9 +15,9 @@
 #include "base/metrics/histogram_macros.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
+#include "chrome/browser/ash/app_list/search/app_search_data_source.h"
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
 #include "chrome/browser/ui/app_list/app_list_model_updater.h"
-#include "chrome/browser/ui/app_list/search/app_search_data_source.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
 
 namespace app_list {
 
diff --git a/chrome/browser/ui/app_list/search/app_search_provider.h b/chrome/browser/ash/app_list/search/app_search_provider.h
similarity index 81%
rename from chrome/browser/ui/app_list/search/app_search_provider.h
rename to chrome/browser/ash/app_list/search/app_search_provider.h
index 29afa0a..6d56eca5 100644
--- a/chrome/browser/ui/app_list/search/app_search_provider.h
+++ b/chrome/browser/ash/app_list/search/app_search_provider.h
@@ -2,15 +2,15 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_UI_APP_LIST_SEARCH_APP_SEARCH_PROVIDER_H_
-#define CHROME_BROWSER_UI_APP_LIST_SEARCH_APP_SEARCH_PROVIDER_H_
+#ifndef CHROME_BROWSER_ASH_APP_LIST_SEARCH_APP_SEARCH_PROVIDER_H_
+#define CHROME_BROWSER_ASH_APP_LIST_SEARCH_APP_SEARCH_PROVIDER_H_
 
 #include <memory>
 #include <string>
 
 #include "base/callback_list.h"
 #include "base/time/time.h"
-#include "chrome/browser/ui/app_list/search/search_provider.h"
+#include "chrome/browser/ash/app_list/search/search_provider.h"
 
 namespace app_list {
 
@@ -48,4 +48,4 @@
 
 }  // namespace app_list
 
-#endif  // CHROME_BROWSER_UI_APP_LIST_SEARCH_APP_SEARCH_PROVIDER_H_
+#endif  // CHROME_BROWSER_ASH_APP_LIST_SEARCH_APP_SEARCH_PROVIDER_H_
diff --git a/chrome/browser/ui/app_list/search/app_search_provider_test_base.cc b/chrome/browser/ash/app_list/search/app_search_provider_test_base.cc
similarity index 94%
rename from chrome/browser/ui/app_list/search/app_search_provider_test_base.cc
rename to chrome/browser/ash/app_list/search/app_search_provider_test_base.cc
index c7a88233..c82eaf93 100644
--- a/chrome/browser/ui/app_list/search/app_search_provider_test_base.cc
+++ b/chrome/browser/ash/app_list/search/app_search_provider_test_base.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/ui/app_list/search/app_search_provider_test_base.h"
+#include "chrome/browser/ash/app_list/search/app_search_provider_test_base.h"
 
 #include <algorithm>
 #include <memory>
@@ -21,14 +21,14 @@
 #include "chrome/browser/ash/app_list/arc/arc_app_list_prefs.h"
 #include "chrome/browser/ash/app_list/arc/arc_app_test.h"
 #include "chrome/browser/ash/app_list/arc/arc_default_app_list.h"
+#include "chrome/browser/ash/app_list/search/app_search_data_source.h"
+#include "chrome/browser/ash/app_list/search/app_search_provider.h"
+#include "chrome/browser/ash/app_list/search/app_zero_state_provider.h"
+#include "chrome/browser/ash/app_list/search/search_provider.h"
 #include "chrome/browser/ash/app_list/search/test/test_search_controller.h"
 #include "chrome/browser/ash/app_list/test/fake_app_list_model_updater.h"
 #include "chrome/browser/ash/app_list/test/test_app_list_controller_delegate.h"
 #include "chrome/browser/extensions/extension_service.h"
-#include "chrome/browser/ui/app_list/search/app_search_data_source.h"
-#include "chrome/browser/ui/app_list/search/app_search_provider.h"
-#include "chrome/browser/ui/app_list/search/app_zero_state_provider.h"
-#include "chrome/browser/ui/app_list/search/search_provider.h"
 #include "chrome/test/base/testing_profile.h"
 #include "components/sync/model/string_ordinal.h"
 #include "extensions/browser/extension_prefs.h"
diff --git a/chrome/browser/ui/app_list/search/app_search_provider_test_base.h b/chrome/browser/ash/app_list/search/app_search_provider_test_base.h
similarity index 92%
rename from chrome/browser/ui/app_list/search/app_search_provider_test_base.h
rename to chrome/browser/ash/app_list/search/app_search_provider_test_base.h
index f919212..8c7ebe6 100644
--- a/chrome/browser/ui/app_list/search/app_search_provider_test_base.h
+++ b/chrome/browser/ash/app_list/search/app_search_provider_test_base.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_APP_LIST_SEARCH_APP_SEARCH_PROVIDER_TEST_BASE_H_
-#define CHROME_BROWSER_UI_APP_LIST_SEARCH_APP_SEARCH_PROVIDER_TEST_BASE_H_
+#ifndef CHROME_BROWSER_ASH_APP_LIST_SEARCH_APP_SEARCH_PROVIDER_TEST_BASE_H_
+#define CHROME_BROWSER_ASH_APP_LIST_SEARCH_APP_SEARCH_PROVIDER_TEST_BASE_H_
 
 #include <memory>
 #include <string>
@@ -93,4 +93,4 @@
 
 }  // namespace app_list
 
-#endif  // CHROME_BROWSER_UI_APP_LIST_SEARCH_APP_SEARCH_PROVIDER_TEST_BASE_H_
+#endif  // CHROME_BROWSER_ASH_APP_LIST_SEARCH_APP_SEARCH_PROVIDER_TEST_BASE_H_
diff --git a/chrome/browser/ui/app_list/search/app_search_provider_unittest.cc b/chrome/browser/ash/app_list/search/app_search_provider_unittest.cc
similarity index 99%
rename from chrome/browser/ui/app_list/search/app_search_provider_unittest.cc
rename to chrome/browser/ash/app_list/search/app_search_provider_unittest.cc
index 8d06f9b..657ccda5 100644
--- a/chrome/browser/ui/app_list/search/app_search_provider_unittest.cc
+++ b/chrome/browser/ash/app_list/search/app_search_provider_unittest.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/ui/app_list/search/app_search_provider.h"
+#include "chrome/browser/ash/app_list/search/app_search_provider.h"
 
 #include <stddef.h>
 
@@ -23,9 +23,9 @@
 #include "chrome/browser/ash/app_list/arc/arc_app_list_prefs.h"
 #include "chrome/browser/ash/app_list/arc/arc_app_test.h"
 #include "chrome/browser/ash/app_list/arc/arc_default_app_list.h"
+#include "chrome/browser/ash/app_list/search/app_search_provider_test_base.h"
 #include "chrome/browser/ash/crostini/crostini_test_helper.h"
 #include "chrome/browser/extensions/extension_service.h"
-#include "chrome/browser/ui/app_list/search/app_search_provider_test_base.h"
 #include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
 #include "chrome/test/base/testing_profile.h"
 #include "chromeos/ash/components/dbus/chunneld/chunneld_client.h"
diff --git a/chrome/browser/ui/app_list/search/app_service_app_result.cc b/chrome/browser/ash/app_list/search/app_service_app_result.cc
similarity index 99%
rename from chrome/browser/ui/app_list/search/app_service_app_result.cc
rename to chrome/browser/ash/app_list/search/app_service_app_result.cc
index ac5ccc8..8d09f5b 100644
--- a/chrome/browser/ui/app_list/search/app_service_app_result.cc
+++ b/chrome/browser/ash/app_list/search/app_service_app_result.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/ui/app_list/search/app_service_app_result.h"
+#include "chrome/browser/ash/app_list/search/app_service_app_result.h"
 
 #include "ash/public/cpp/app_list/app_list_types.h"
 #include "ash/public/cpp/app_list/internal_app_id_constants.h"
diff --git a/chrome/browser/ui/app_list/search/app_service_app_result.h b/chrome/browser/ash/app_list/search/app_service_app_result.h
similarity index 92%
rename from chrome/browser/ui/app_list/search/app_service_app_result.h
rename to chrome/browser/ash/app_list/search/app_service_app_result.h
index a77d2d5..2cf1dcb 100644
--- a/chrome/browser/ui/app_list/search/app_service_app_result.h
+++ b/chrome/browser/ash/app_list/search/app_service_app_result.h
@@ -2,15 +2,15 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_UI_APP_LIST_SEARCH_APP_SERVICE_APP_RESULT_H_
-#define CHROME_BROWSER_UI_APP_LIST_SEARCH_APP_SERVICE_APP_RESULT_H_
+#ifndef CHROME_BROWSER_ASH_APP_LIST_SEARCH_APP_SERVICE_APP_RESULT_H_
+#define CHROME_BROWSER_ASH_APP_LIST_SEARCH_APP_SERVICE_APP_RESULT_H_
 
 #include <memory>
 
 #include "ash/public/cpp/app_list/app_list_metrics.h"
 #include "base/memory/weak_ptr.h"
 #include "base/task/cancelable_task_tracker.h"
-#include "chrome/browser/ui/app_list/search/app_result.h"
+#include "chrome/browser/ash/app_list/search/app_result.h"
 #include "components/favicon_base/favicon_types.h"
 #include "components/services/app_service/public/cpp/app_launch_util.h"
 #include "components/services/app_service/public/cpp/app_types.h"
@@ -104,4 +104,4 @@
 
 }  // namespace app_list
 
-#endif  // CHROME_BROWSER_UI_APP_LIST_SEARCH_APP_SERVICE_APP_RESULT_H_
+#endif  // CHROME_BROWSER_ASH_APP_LIST_SEARCH_APP_SERVICE_APP_RESULT_H_
diff --git a/chrome/browser/ui/app_list/search/app_zero_state_provider.cc b/chrome/browser/ash/app_list/search/app_zero_state_provider.cc
similarity index 91%
rename from chrome/browser/ui/app_list/search/app_zero_state_provider.cc
rename to chrome/browser/ash/app_list/search/app_zero_state_provider.cc
index 6439c8b..8bf9c85 100644
--- a/chrome/browser/ui/app_list/search/app_zero_state_provider.cc
+++ b/chrome/browser/ash/app_list/search/app_zero_state_provider.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/ui/app_list/search/app_zero_state_provider.h"
+#include "chrome/browser/ash/app_list/search/app_zero_state_provider.h"
 
 #include <string>
 #include <utility>
@@ -11,9 +11,9 @@
 #include "base/bind.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/time/time.h"
+#include "chrome/browser/ash/app_list/search/app_search_data_source.h"
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
 #include "chrome/browser/ui/app_list/app_list_model_updater.h"
-#include "chrome/browser/ui/app_list/search/app_search_data_source.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
 
 namespace app_list {
 
diff --git a/chrome/browser/ui/app_list/search/app_zero_state_provider.h b/chrome/browser/ash/app_list/search/app_zero_state_provider.h
similarity index 82%
rename from chrome/browser/ui/app_list/search/app_zero_state_provider.h
rename to chrome/browser/ash/app_list/search/app_zero_state_provider.h
index 1fa0762..06a2ac8d 100644
--- a/chrome/browser/ui/app_list/search/app_zero_state_provider.h
+++ b/chrome/browser/ash/app_list/search/app_zero_state_provider.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_APP_LIST_SEARCH_APP_ZERO_STATE_PROVIDER_H_
-#define CHROME_BROWSER_UI_APP_LIST_SEARCH_APP_ZERO_STATE_PROVIDER_H_
+#ifndef CHROME_BROWSER_ASH_APP_LIST_SEARCH_APP_ZERO_STATE_PROVIDER_H_
+#define CHROME_BROWSER_ASH_APP_LIST_SEARCH_APP_ZERO_STATE_PROVIDER_H_
 
 #include <memory>
 #include <string>
@@ -11,7 +11,7 @@
 #include "base/containers/flat_map.h"
 #include "base/memory/weak_ptr.h"
 #include "base/time/time.h"
-#include "chrome/browser/ui/app_list/search/search_provider.h"
+#include "chrome/browser/ash/app_list/search/search_provider.h"
 
 class AppListModelUpdater;
 
@@ -50,4 +50,4 @@
 
 }  // namespace app_list
 
-#endif  // CHROME_BROWSER_UI_APP_LIST_SEARCH_APP_ZERO_STATE_PROVIDER_H_
+#endif  // CHROME_BROWSER_ASH_APP_LIST_SEARCH_APP_ZERO_STATE_PROVIDER_H_
diff --git a/chrome/browser/ui/app_list/search/app_zero_state_provider_unittest.cc b/chrome/browser/ash/app_list/search/app_zero_state_provider_unittest.cc
similarity index 98%
rename from chrome/browser/ui/app_list/search/app_zero_state_provider_unittest.cc
rename to chrome/browser/ash/app_list/search/app_zero_state_provider_unittest.cc
index da8ec20..17b08923 100644
--- a/chrome/browser/ui/app_list/search/app_zero_state_provider_unittest.cc
+++ b/chrome/browser/ash/app_list/search/app_zero_state_provider_unittest.cc
@@ -8,8 +8,8 @@
 #include "base/run_loop.h"
 #include "base/time/time.h"
 #include "chrome/browser/ash/app_list/arc/arc_app_list_prefs.h"
+#include "chrome/browser/ash/app_list/search/app_search_provider_test_base.h"
 #include "chrome/browser/extensions/extension_service.h"
-#include "chrome/browser/ui/app_list/search/app_search_provider_test_base.h"
 #include "chrome/browser/web_applications/web_app_id_constants.h"
 #include "chrome/test/base/testing_profile.h"
 #include "components/crx_file/id_util.h"
diff --git a/chrome/browser/ash/app_list/search/arc/arc_app_shortcut_search_result.h b/chrome/browser/ash/app_list/search/arc/arc_app_shortcut_search_result.h
index ebb26df..4e69ba7 100644
--- a/chrome/browser/ash/app_list/search/arc/arc_app_shortcut_search_result.h
+++ b/chrome/browser/ash/app_list/search/arc/arc_app_shortcut_search_result.h
@@ -10,8 +10,8 @@
 
 #include "ash/components/arc/mojom/app.mojom.h"
 #include "ash/public/cpp/app_list/app_list_metrics.h"
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
 #include "chrome/browser/ui/app_icon_loader_delegate.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
 #include "ui/gfx/image/image_skia.h"
 
 class AppListControllerDelegate;
diff --git a/chrome/browser/ash/app_list/search/arc/arc_app_shortcuts_search_provider.h b/chrome/browser/ash/app_list/search/arc/arc_app_shortcuts_search_provider.h
index 4750538..f2689a5 100644
--- a/chrome/browser/ash/app_list/search/arc/arc_app_shortcuts_search_provider.h
+++ b/chrome/browser/ash/app_list/search/arc/arc_app_shortcuts_search_provider.h
@@ -10,7 +10,7 @@
 
 #include "ash/components/arc/mojom/app.mojom-forward.h"
 #include "base/memory/weak_ptr.h"
-#include "chrome/browser/ui/app_list/search/search_provider.h"
+#include "chrome/browser/ash/app_list/search/search_provider.h"
 
 class AppListControllerDelegate;
 class Profile;
diff --git a/chrome/browser/ash/app_list/search/arc/arc_app_shortcuts_search_provider_unittest.cc b/chrome/browser/ash/app_list/search/arc/arc_app_shortcuts_search_provider_unittest.cc
index d055cae..457c913 100644
--- a/chrome/browser/ash/app_list/search/arc/arc_app_shortcuts_search_provider_unittest.cc
+++ b/chrome/browser/ash/app_list/search/arc/arc_app_shortcuts_search_provider_unittest.cc
@@ -14,11 +14,11 @@
 #include "base/test/scoped_feature_list.h"
 #include "chrome/browser/ash/app_list/arc/arc_app_list_prefs.h"
 #include "chrome/browser/ash/app_list/arc/arc_app_test.h"
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
 #include "chrome/browser/ash/app_list/search/test/test_search_controller.h"
 #include "chrome/browser/ash/app_list/test/test_app_list_controller_delegate.h"
 #include "chrome/browser/chromeos/arc/icon_decode_request.h"
 #include "chrome/browser/ui/app_list/app_list_test_util.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace app_list::test {
diff --git a/chrome/browser/ash/app_list/search/arc/arc_playstore_search_provider.h b/chrome/browser/ash/app_list/search/arc/arc_playstore_search_provider.h
index 5fdd89d..c57d25af 100644
--- a/chrome/browser/ash/app_list/search/arc/arc_playstore_search_provider.h
+++ b/chrome/browser/ash/app_list/search/arc/arc_playstore_search_provider.h
@@ -9,7 +9,7 @@
 
 #include "ash/components/arc/mojom/app.mojom-forward.h"
 #include "base/memory/weak_ptr.h"
-#include "chrome/browser/ui/app_list/search/search_provider.h"
+#include "chrome/browser/ash/app_list/search/search_provider.h"
 
 class Profile;
 class AppListControllerDelegate;
diff --git a/chrome/browser/ash/app_list/search/arc/arc_playstore_search_provider_unittest.cc b/chrome/browser/ash/app_list/search/arc/arc_playstore_search_provider_unittest.cc
index 9376fd2..e11391d4 100644
--- a/chrome/browser/ash/app_list/search/arc/arc_playstore_search_provider_unittest.cc
+++ b/chrome/browser/ash/app_list/search/arc/arc_playstore_search_provider_unittest.cc
@@ -14,12 +14,12 @@
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/ash/app_list/arc/arc_app_test.h"
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
 #include "chrome/browser/ash/app_list/search/test/test_search_controller.h"
 #include "chrome/browser/ash/app_list/test/test_app_list_controller_delegate.h"
 #include "chrome/browser/chromeos/arc/icon_decode_request.h"
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/ui/app_list/app_list_test_util.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
 #include "chrome/test/base/testing_profile.h"
 #include "extensions/common/extension_builder.h"
 
diff --git a/chrome/browser/ash/app_list/search/arc/arc_playstore_search_result.h b/chrome/browser/ash/app_list/search/arc/arc_playstore_search_result.h
index ace56f2..a71b504 100644
--- a/chrome/browser/ash/app_list/search/arc/arc_playstore_search_result.h
+++ b/chrome/browser/ash/app_list/search/arc/arc_playstore_search_result.h
@@ -12,8 +12,8 @@
 #include "ash/components/arc/mojom/app.mojom.h"
 #include "ash/public/cpp/app_list/app_list_metrics.h"
 #include "base/memory/weak_ptr.h"
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
 #include "chrome/browser/ui/app_list/app_context_menu_delegate.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 class AppListControllerDelegate;
diff --git a/chrome/browser/ui/app_list/search/assistant_text_search_provider.cc b/chrome/browser/ash/app_list/search/assistant_text_search_provider.cc
similarity index 96%
rename from chrome/browser/ui/app_list/search/assistant_text_search_provider.cc
rename to chrome/browser/ash/app_list/search/assistant_text_search_provider.cc
index 0195348..ac548a3 100644
--- a/chrome/browser/ui/app_list/search/assistant_text_search_provider.cc
+++ b/chrome/browser/ash/app_list/search/assistant_text_search_provider.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/ui/app_list/search/assistant_text_search_provider.h"
+#include "chrome/browser/ash/app_list/search/assistant_text_search_provider.h"
 
 #include <memory>
 #include <string>
@@ -15,8 +15,8 @@
 #include "ash/public/cpp/assistant/controller/assistant_suggestions_controller.h"
 #include "ash/strings/grit/ash_strings.h"
 #include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
 #include "chrome/browser/ash/app_list/search/common/icon_constants.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
 #include "chromeos/ash/services/assistant/public/cpp/assistant_service.h"
 #include "chromeos/ui/vector_icons/vector_icons.h"
 #include "ui/base/l10n/l10n_util.h"
diff --git a/chrome/browser/ui/app_list/search/assistant_text_search_provider.h b/chrome/browser/ash/app_list/search/assistant_text_search_provider.h
similarity index 87%
rename from chrome/browser/ui/app_list/search/assistant_text_search_provider.h
rename to chrome/browser/ash/app_list/search/assistant_text_search_provider.h
index d6130ded..128cebda 100644
--- a/chrome/browser/ui/app_list/search/assistant_text_search_provider.h
+++ b/chrome/browser/ash/app_list/search/assistant_text_search_provider.h
@@ -2,14 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_UI_APP_LIST_SEARCH_ASSISTANT_TEXT_SEARCH_PROVIDER_H_
-#define CHROME_BROWSER_UI_APP_LIST_SEARCH_ASSISTANT_TEXT_SEARCH_PROVIDER_H_
+#ifndef CHROME_BROWSER_ASH_APP_LIST_SEARCH_ASSISTANT_TEXT_SEARCH_PROVIDER_H_
+#define CHROME_BROWSER_ASH_APP_LIST_SEARCH_ASSISTANT_TEXT_SEARCH_PROVIDER_H_
 
 #include "ash/public/cpp/assistant/assistant_state.h"
 #include "ash/public/cpp/assistant/controller/assistant_controller.h"
 #include "ash/public/cpp/assistant/controller/assistant_controller_observer.h"
 #include "base/scoped_observation.h"
-#include "chrome/browser/ui/app_list/search/search_provider.h"
+#include "chrome/browser/ash/app_list/search/search_provider.h"
 
 namespace app_list {
 
@@ -59,4 +59,4 @@
 
 }  // namespace app_list
 
-#endif  // CHROME_BROWSER_UI_APP_LIST_SEARCH_ASSISTANT_TEXT_SEARCH_PROVIDER_H_
+#endif  // CHROME_BROWSER_ASH_APP_LIST_SEARCH_ASSISTANT_TEXT_SEARCH_PROVIDER_H_
diff --git a/chrome/browser/ui/app_list/search/assistant_text_search_provider_unittest.cc b/chrome/browser/ash/app_list/search/assistant_text_search_provider_unittest.cc
similarity index 96%
rename from chrome/browser/ui/app_list/search/assistant_text_search_provider_unittest.cc
rename to chrome/browser/ash/app_list/search/assistant_text_search_provider_unittest.cc
index d1bbdd4..e308eba 100644
--- a/chrome/browser/ui/app_list/search/assistant_text_search_provider_unittest.cc
+++ b/chrome/browser/ash/app_list/search/assistant_text_search_provider_unittest.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/ui/app_list/search/assistant_text_search_provider.h"
+#include "chrome/browser/ash/app_list/search/assistant_text_search_provider.h"
 
 #include <string>
 
@@ -11,9 +11,9 @@
 #include "ash/public/cpp/assistant/test_support/mock_assistant_state.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/scoped_feature_list.h"
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
 #include "chrome/browser/ash/app_list/search/test/test_search_controller.h"
 #include "chrome/browser/ui/app_list/app_list_test_util.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "url/gurl.h"
 
diff --git a/chrome/browser/ui/app_list/search/burnin_controller.cc b/chrome/browser/ash/app_list/search/burnin_controller.cc
similarity index 94%
rename from chrome/browser/ui/app_list/search/burnin_controller.cc
rename to chrome/browser/ash/app_list/search/burnin_controller.cc
index f68191a..f397b0c3 100644
--- a/chrome/browser/ui/app_list/search/burnin_controller.cc
+++ b/chrome/browser/ash/app_list/search/burnin_controller.cc
@@ -2,10 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/ui/app_list/search/burnin_controller.h"
+#include "chrome/browser/ash/app_list/search/burnin_controller.h"
 
 #include "base/containers/flat_set.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
 
 namespace app_list {
 
diff --git a/chrome/browser/ui/app_list/search/burnin_controller.h b/chrome/browser/ash/app_list/search/burnin_controller.h
similarity index 91%
rename from chrome/browser/ui/app_list/search/burnin_controller.h
rename to chrome/browser/ash/app_list/search/burnin_controller.h
index 46d82c1f..b31d7ad 100644
--- a/chrome/browser/ui/app_list/search/burnin_controller.h
+++ b/chrome/browser/ash/app_list/search/burnin_controller.h
@@ -2,16 +2,16 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_UI_APP_LIST_SEARCH_BURNIN_CONTROLLER_H_
-#define CHROME_BROWSER_UI_APP_LIST_SEARCH_BURNIN_CONTROLLER_H_
+#ifndef CHROME_BROWSER_ASH_APP_LIST_SEARCH_BURNIN_CONTROLLER_H_
+#define CHROME_BROWSER_ASH_APP_LIST_SEARCH_BURNIN_CONTROLLER_H_
 
 #include "ash/public/cpp/app_list/app_list_types.h"
 #include "base/callback.h"
 #include "base/containers/flat_map.h"
 #include "base/time/time.h"
 #include "base/timer/timer.h"
-#include "chrome/browser/ui/app_list/search/search_controller.h"
-#include "chrome/browser/ui/app_list/search/types.h"
+#include "chrome/browser/ash/app_list/search/search_controller.h"
+#include "chrome/browser/ash/app_list/search/types.h"
 
 namespace app_list {
 
@@ -99,4 +99,4 @@
 
 }  // namespace app_list
 
-#endif  // CHROME_BROWSER_UI_APP_LIST_SEARCH_BURNIN_CONTROLLER_H_
+#endif  // CHROME_BROWSER_ASH_APP_LIST_SEARCH_BURNIN_CONTROLLER_H_
diff --git a/chrome/browser/ui/app_list/search/chrome_search_result.cc b/chrome/browser/ash/app_list/search/chrome_search_result.cc
similarity index 98%
rename from chrome/browser/ui/app_list/search/chrome_search_result.cc
rename to chrome/browser/ash/app_list/search/chrome_search_result.cc
index 24798986..622091f 100644
--- a/chrome/browser/ui/app_list/search/chrome_search_result.cc
+++ b/chrome/browser/ash/app_list/search/chrome_search_result.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/ui/app_list/search/chrome_search_result.h"
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
 
 #include <map>
 
diff --git a/chrome/browser/ui/app_list/search/chrome_search_result.h b/chrome/browser/ash/app_list/search/chrome_search_result.h
similarity index 97%
rename from chrome/browser/ui/app_list/search/chrome_search_result.h
rename to chrome/browser/ash/app_list/search/chrome_search_result.h
index 121beb5..81d6759 100644
--- a/chrome/browser/ui/app_list/search/chrome_search_result.h
+++ b/chrome/browser/ash/app_list/search/chrome_search_result.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_APP_LIST_SEARCH_CHROME_SEARCH_RESULT_H_
-#define CHROME_BROWSER_UI_APP_LIST_SEARCH_CHROME_SEARCH_RESULT_H_
+#ifndef CHROME_BROWSER_ASH_APP_LIST_SEARCH_CHROME_SEARCH_RESULT_H_
+#define CHROME_BROWSER_ASH_APP_LIST_SEARCH_CHROME_SEARCH_RESULT_H_
 
 #include <memory>
 #include <string>
@@ -12,8 +12,8 @@
 #include "ash/public/cpp/app_list/app_list_metrics.h"
 #include "ash/public/cpp/app_list/app_list_types.h"
 #include "base/memory/weak_ptr.h"
+#include "chrome/browser/ash/app_list/search/types.h"
 #include "chrome/browser/ui/app_list/app_list_model_updater.h"
-#include "chrome/browser/ui/app_list/search/types.h"
 #include "chromeos/crosapi/mojom/launcher_search.mojom.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "ui/base/models/simple_menu_model.h"
@@ -256,4 +256,4 @@
 ::std::ostream& operator<<(::std::ostream& os,
                            const ChromeSearchResult& result);
 
-#endif  // CHROME_BROWSER_UI_APP_LIST_SEARCH_CHROME_SEARCH_RESULT_H_
+#endif  // CHROME_BROWSER_ASH_APP_LIST_SEARCH_CHROME_SEARCH_RESULT_H_
diff --git a/chrome/browser/ui/app_list/search/chrome_search_result_unittest.cc b/chrome/browser/ash/app_list/search/chrome_search_result_unittest.cc
similarity index 98%
rename from chrome/browser/ui/app_list/search/chrome_search_result_unittest.cc
rename to chrome/browser/ash/app_list/search/chrome_search_result_unittest.cc
index e37b2ef1..039b0266 100644
--- a/chrome/browser/ui/app_list/search/chrome_search_result_unittest.cc
+++ b/chrome/browser/ash/app_list/search/chrome_search_result_unittest.cc
@@ -2,7 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
+
 #include <string>
 
 #include "ash/public/cpp/app_list/app_list_types.h"
diff --git a/chrome/browser/ash/app_list/search/files/drive_search_provider.h b/chrome/browser/ash/app_list/search/files/drive_search_provider.h
index a30b5fb..a10b271d 100644
--- a/chrome/browser/ash/app_list/search/files/drive_search_provider.h
+++ b/chrome/browser/ash/app_list/search/files/drive_search_provider.h
@@ -12,7 +12,7 @@
 #include "base/sequence_checker.h"
 #include "base/time/time.h"
 #include "chrome/browser/ash/app_list/search/files/file_result.h"
-#include "chrome/browser/ui/app_list/search/search_provider.h"
+#include "chrome/browser/ash/app_list/search/search_provider.h"
 #include "chromeos/ash/components/drivefs/mojom/drivefs.mojom.h"
 #include "chromeos/ash/components/string_matching/tokenized_string.h"
 #include "components/drive/file_errors.h"
diff --git a/chrome/browser/ash/app_list/search/files/file_result.h b/chrome/browser/ash/app_list/search/files/file_result.h
index 1b212cc..b8d66056 100644
--- a/chrome/browser/ash/app_list/search/files/file_result.h
+++ b/chrome/browser/ash/app_list/search/files/file_result.h
@@ -10,7 +10,7 @@
 #include "ash/public/cpp/style/color_mode_observer.h"
 #include "base/files/file.h"
 #include "base/files/file_path.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 class Profile;
diff --git a/chrome/browser/ash/app_list/search/files/file_search_provider.h b/chrome/browser/ash/app_list/search/files/file_search_provider.h
index 5d6f50f..924a6ec 100644
--- a/chrome/browser/ash/app_list/search/files/file_search_provider.h
+++ b/chrome/browser/ash/app_list/search/files/file_search_provider.h
@@ -12,7 +12,7 @@
 #include "base/memory/weak_ptr.h"
 #include "base/sequence_checker.h"
 #include "base/time/time.h"
-#include "chrome/browser/ui/app_list/search/search_provider.h"
+#include "chrome/browser/ash/app_list/search/search_provider.h"
 #include "chrome/browser/ui/ash/thumbnail_loader.h"
 #include "chromeos/ash/components/string_matching/tokenized_string.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
diff --git a/chrome/browser/ash/app_list/search/files/zero_state_drive_provider.cc b/chrome/browser/ash/app_list/search/files/zero_state_drive_provider.cc
index e57391d..5a1606f 100644
--- a/chrome/browser/ash/app_list/search/files/zero_state_drive_provider.cc
+++ b/chrome/browser/ash/app_list/search/files/zero_state_drive_provider.cc
@@ -14,9 +14,9 @@
 #include "chrome/browser/ash/app_list/search/files/file_suggest_keyed_service.h"
 #include "chrome/browser/ash/app_list/search/files/file_suggest_keyed_service_factory.h"
 #include "chrome/browser/ash/app_list/search/files/file_suggest_util.h"
+#include "chrome/browser/ash/app_list/search/search_controller.h"
 #include "chrome/browser/ash/drive/drive_integration_service.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/app_list/search/search_controller.h"
 #include "chromeos/dbus/power_manager/idle.pb.h"
 #include "content/public/browser/browser_context.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
diff --git a/chrome/browser/ash/app_list/search/files/zero_state_drive_provider.h b/chrome/browser/ash/app_list/search/files/zero_state_drive_provider.h
index 1d5eebf..2d55f98 100644
--- a/chrome/browser/ash/app_list/search/files/zero_state_drive_provider.h
+++ b/chrome/browser/ash/app_list/search/files/zero_state_drive_provider.h
@@ -16,8 +16,8 @@
 #include "chrome/browser/ash/app_list/search/files/file_result.h"
 #include "chrome/browser/ash/app_list/search/files/file_suggest_keyed_service.h"
 #include "chrome/browser/ash/app_list/search/files/item_suggest_cache.h"
+#include "chrome/browser/ash/app_list/search/search_provider.h"
 #include "chrome/browser/ash/drive/drive_integration_service.h"
-#include "chrome/browser/ui/app_list/search/search_provider.h"
 #include "chromeos/ash/components/drivefs/mojom/drivefs.mojom-forward.h"
 #include "chromeos/dbus/power/power_manager_client.h"
 #include "components/session_manager/core/session_manager.h"
diff --git a/chrome/browser/ash/app_list/search/files/zero_state_file_provider.h b/chrome/browser/ash/app_list/search/files/zero_state_file_provider.h
index 072bba52..ab7f184 100644
--- a/chrome/browser/ash/app_list/search/files/zero_state_file_provider.h
+++ b/chrome/browser/ash/app_list/search/files/zero_state_file_provider.h
@@ -17,9 +17,9 @@
 #include "base/task/sequenced_task_runner.h"
 #include "base/time/time.h"
 #include "chrome/browser/ash/app_list/search/files/file_suggest_keyed_service.h"
+#include "chrome/browser/ash/app_list/search/search_provider.h"
 #include "chrome/browser/ash/file_manager/file_tasks_notifier.h"
 #include "chrome/browser/ash/file_manager/file_tasks_observer.h"
-#include "chrome/browser/ui/app_list/search/search_provider.h"
 #include "chrome/browser/ui/ash/thumbnail_loader.h"
 
 class Profile;
diff --git a/chrome/browser/ash/app_list/search/games/game_provider.cc b/chrome/browser/ash/app_list/search/games/game_provider.cc
index 620273f..995960e8 100644
--- a/chrome/browser/ash/app_list/search/games/game_provider.cc
+++ b/chrome/browser/ash/app_list/search/games/game_provider.cc
@@ -20,9 +20,9 @@
 #include "chrome/browser/apps/app_discovery_service/game_extras.h"
 #include "chrome/browser/apps/app_discovery_service/result.h"
 #include "chrome/browser/ash/app_list/search/games/game_result.h"
+#include "chrome/browser/ash/app_list/search/search_features.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/app_list/app_list_controller_delegate.h"
-#include "chrome/browser/ui/app_list/search/search_features.h"
 #include "chromeos/ash/components/string_matching/fuzzy_tokenized_string_match.h"
 #include "chromeos/ash/components/string_matching/tokenized_string.h"
 #include "components/prefs/pref_service.h"
diff --git a/chrome/browser/ash/app_list/search/games/game_provider.h b/chrome/browser/ash/app_list/search/games/game_provider.h
index 6671128..ea30338 100644
--- a/chrome/browser/ash/app_list/search/games/game_provider.h
+++ b/chrome/browser/ash/app_list/search/games/game_provider.h
@@ -14,7 +14,7 @@
 #include "base/sequence_checker.h"
 #include "base/time/time.h"
 #include "chrome/browser/apps/app_discovery_service/result.h"
-#include "chrome/browser/ui/app_list/search/search_provider.h"
+#include "chrome/browser/ash/app_list/search/search_provider.h"
 #include "chrome/browser/ui/ash/thumbnail_loader.h"
 
 class AppListControllerDelegate;
diff --git a/chrome/browser/ash/app_list/search/games/game_provider_unittest.cc b/chrome/browser/ash/app_list/search/games/game_provider_unittest.cc
index a85b594..fd6c6e0 100644
--- a/chrome/browser/ash/app_list/search/games/game_provider_unittest.cc
+++ b/chrome/browser/ash/app_list/search/games/game_provider_unittest.cc
@@ -11,9 +11,9 @@
 #include "chrome/browser/apps/app_discovery_service/app_discovery_util.h"
 #include "chrome/browser/apps/app_discovery_service/game_extras.h"
 #include "chrome/browser/apps/app_discovery_service/result.h"
+#include "chrome/browser/ash/app_list/search/search_features.h"
 #include "chrome/browser/ash/app_list/search/test/test_search_controller.h"
 #include "chrome/browser/ash/app_list/test/test_app_list_controller_delegate.h"
-#include "chrome/browser/ui/app_list/search/search_features.h"
 #include "chrome/test/base/testing_profile.h"
 #include "components/prefs/pref_service.h"
 #include "content/public/test/browser_task_environment.h"
diff --git a/chrome/browser/ash/app_list/search/games/game_result.h b/chrome/browser/ash/app_list/search/games/game_result.h
index 3ee304ee..4b1a0f9 100644
--- a/chrome/browser/ash/app_list/search/games/game_result.h
+++ b/chrome/browser/ash/app_list/search/games/game_result.h
@@ -9,7 +9,7 @@
 #include "base/memory/weak_ptr.h"
 #include "chrome/browser/apps/app_discovery_service/app_discovery_util.h"
 #include "chrome/browser/apps/app_discovery_service/result.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
 #include "url/gurl.h"
 
 class AppListControllerDelegate;
diff --git a/chrome/browser/ui/app_list/search/help_app_provider.cc b/chrome/browser/ash/app_list/search/help_app_provider.cc
similarity index 98%
rename from chrome/browser/ui/app_list/search/help_app_provider.cc
rename to chrome/browser/ash/app_list/search/help_app_provider.cc
index d7be18f..1e920c0 100644
--- a/chrome/browser/ui/app_list/search/help_app_provider.cc
+++ b/chrome/browser/ash/app_list/search/help_app_provider.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/ui/app_list/search/help_app_provider.h"
+#include "chrome/browser/ash/app_list/search/help_app_provider.h"
 
 #include <memory>
 
diff --git a/chrome/browser/ui/app_list/search/help_app_provider.h b/chrome/browser/ash/app_list/search/help_app_provider.h
similarity index 90%
rename from chrome/browser/ui/app_list/search/help_app_provider.h
rename to chrome/browser/ash/app_list/search/help_app_provider.h
index 4380383..d7b9dc8 100644
--- a/chrome/browser/ui/app_list/search/help_app_provider.h
+++ b/chrome/browser/ash/app_list/search/help_app_provider.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_APP_LIST_SEARCH_HELP_APP_PROVIDER_H_
-#define CHROME_BROWSER_UI_APP_LIST_SEARCH_HELP_APP_PROVIDER_H_
+#ifndef CHROME_BROWSER_ASH_APP_LIST_SEARCH_HELP_APP_PROVIDER_H_
+#define CHROME_BROWSER_ASH_APP_LIST_SEARCH_HELP_APP_PROVIDER_H_
 
 #include <string>
 #include <vector>
@@ -12,8 +12,8 @@
 #include "base/memory/weak_ptr.h"
 #include "base/time/time.h"
 #include "chrome/browser/apps/app_service/app_service_proxy_forward.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
-#include "chrome/browser/ui/app_list/search/search_provider.h"
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
+#include "chrome/browser/ash/app_list/search/search_provider.h"
 #include "components/services/app_service/public/cpp/app_registry_cache.h"
 #include "components/services/app_service/public/cpp/icon_types.h"
 #include "mojo/public/cpp/bindings/receiver.h"
@@ -102,4 +102,4 @@
 
 }  // namespace app_list
 
-#endif  // CHROME_BROWSER_UI_APP_LIST_SEARCH_HELP_APP_PROVIDER_H_
+#endif  // CHROME_BROWSER_ASH_APP_LIST_SEARCH_HELP_APP_PROVIDER_H_
diff --git a/chrome/browser/ui/app_list/search/help_app_provider_unittest.cc b/chrome/browser/ash/app_list/search/help_app_provider_unittest.cc
similarity index 97%
rename from chrome/browser/ui/app_list/search/help_app_provider_unittest.cc
rename to chrome/browser/ash/app_list/search/help_app_provider_unittest.cc
index 833f6d8..72af884 100644
--- a/chrome/browser/ui/app_list/search/help_app_provider_unittest.cc
+++ b/chrome/browser/ash/app_list/search/help_app_provider_unittest.cc
@@ -2,9 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/ui/app_list/search/help_app_provider.h"
+#include "chrome/browser/ash/app_list/search/help_app_provider.h"
 
 #include <memory>
+
 #include "ash/constants/ash_features.h"
 #include "ash/webui/help_app_ui/search/search_handler.h"
 #include "ash/webui/help_app_ui/search/search_tag_registry.h"
@@ -13,10 +14,10 @@
 #include "base/test/scoped_feature_list.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/ash/app_list/search/chrome_search_result.h"
 #include "chrome/browser/ash/app_list/search/common/icon_constants.h"
 #include "chrome/browser/ash/app_list/search/test/test_search_controller.h"
 #include "chrome/browser/ui/app_list/app_list_test_util.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
 #include "chrome/browser/web_applications/web_app_id_constants.h"
 #include "components/services/app_service/public/cpp/stub_icon_loader.h"
 #include "content/public/test/browser_task_environment.h"
diff --git a/chrome/browser/ui/app_list/search/help_app_search_browsertest.cc b/chrome/browser/ash/app_list/search/help_app_search_browsertest.cc
similarity index 100%
rename from chrome/browser/ui/app_list/search/help_app_search_browsertest.cc
rename to chrome/browser/ash/app_list/search/help_app_search_browsertest.cc
diff --git a/chrome/browser/ui/app_list/search/help_app_zero_state_provider.cc b/chrome/browser/ash/app_list/search/help_app_zero_state_provider.cc
similarity index 98%
rename from chrome/browser/ui/app_list/search/help_app_zero_state_provider.cc
rename to chrome/browser/ash/app_list/search/help_app_zero_state_provider.cc
index 81391d5..60a4647 100644
--- a/chrome/browser/ui/app_list/search/help_app_zero_state_provider.cc
+++ b/chrome/browser/ash/app_list/search/help_app_zero_state_provider.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/ui/app_list/search/help_app_zero_state_provider.h"
+#include "chrome/browser/ash/app_list/search/help_app_zero_state_provider.h"
 
 #include <memory>
 
diff --git a/chrome/browser/ui/app_list/search/help_app_zero_state_provider.h b/chrome/browser/ash/app_list/search/help_app_zero_state_provider.h
similarity index 88%
rename from chrome/browser/ui/app_list/search/help_app_zero_state_provider.h
rename to chrome/browser/ash/app_list/search/help_app_zero_state_provider.h
index de2a9fa..8eb4e6c 100644
--- a/chrome/browser/ui/app_list/search/help_app_zero_state_provider.h
+++ b/chrome/browser/ash/app_list/search/help_app_zero_state_provider.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_APP_LIST_SEARCH_HELP_APP_ZERO_STATE_PROVIDER_H_
-#define CHROME_BROWSER_UI_APP_LIST_SEARCH_HELP_APP_ZERO_STATE_PROVIDER_H_
+#ifndef CHROME_BROWSER_ASH_APP_LIST_SEARCH_HELP_APP_ZERO_STATE_PROVIDER_H_
+#define CHROME_BROWSER_ASH_APP_LIST_SEARCH_HELP_APP_ZERO_STATE_PROVIDER_H_
 
 #include <string>
 #include <vector>
@@ -12,8 +12,8 @@
 #include "base/memory/weak_ptr.h"
 #include "base/time/time.h"
 #include "chrome/browser/apps/app_service/app_service_proxy_forward.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
-#include "chrome/browser/ui/app_list/search/search_provider.h"
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
+#include "chrome/browser/ash/app_list/search/search_provider.h"
 #include "components/services/app_service/public/cpp/app_registry_cache.h"
 #include "components/services/app_service/public/cpp/icon_types.h"
 
@@ -87,4 +87,4 @@
 
 }  // namespace app_list
 
-#endif  // CHROME_BROWSER_UI_APP_LIST_SEARCH_HELP_APP_ZERO_STATE_PROVIDER_H_
+#endif  // CHROME_BROWSER_ASH_APP_LIST_SEARCH_HELP_APP_ZERO_STATE_PROVIDER_H_
diff --git a/chrome/browser/ui/app_list/search/help_app_zero_state_provider_unittest.cc b/chrome/browser/ash/app_list/search/help_app_zero_state_provider_unittest.cc
similarity index 97%
rename from chrome/browser/ui/app_list/search/help_app_zero_state_provider_unittest.cc
rename to chrome/browser/ash/app_list/search/help_app_zero_state_provider_unittest.cc
index 3870604..d77d0fd6 100644
--- a/chrome/browser/ui/app_list/search/help_app_zero_state_provider_unittest.cc
+++ b/chrome/browser/ash/app_list/search/help_app_zero_state_provider_unittest.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/ui/app_list/search/help_app_zero_state_provider.h"
+#include "chrome/browser/ash/app_list/search/help_app_zero_state_provider.h"
 
 #include <memory>
 #include <string>
@@ -11,11 +11,11 @@
 #include "ash/public/cpp/app_list/app_list_types.h"
 #include "base/feature_list.h"
 #include "base/test/scoped_feature_list.h"
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
 #include "chrome/browser/ash/app_list/search/test/test_search_controller.h"
 #include "chrome/browser/ash/app_list/test/test_app_list_controller.h"
 #include "chrome/browser/ui/app_list/app_list_notifier_impl.h"
 #include "chrome/browser/ui/app_list/app_list_test_util.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/test/base/testing_profile.h"
 #include "chromeos/strings/grit/chromeos_strings.h"
diff --git a/chrome/browser/ui/app_list/search/keyboard_shortcut_data.cc b/chrome/browser/ash/app_list/search/keyboard_shortcut_data.cc
similarity index 95%
rename from chrome/browser/ui/app_list/search/keyboard_shortcut_data.cc
rename to chrome/browser/ash/app_list/search/keyboard_shortcut_data.cc
index 9abde4e..e4f2560 100644
--- a/chrome/browser/ui/app_list/search/keyboard_shortcut_data.cc
+++ b/chrome/browser/ash/app_list/search/keyboard_shortcut_data.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/ui/app_list/search/keyboard_shortcut_data.h"
+#include "chrome/browser/ash/app_list/search/keyboard_shortcut_data.h"
 
 #include "ash/shortcut_viewer/strings/grit/shortcut_viewer_strings.h"
 #include "ash/strings/grit/ash_strings.h"
diff --git a/chrome/browser/ui/app_list/search/keyboard_shortcut_data.h b/chrome/browser/ash/app_list/search/keyboard_shortcut_data.h
similarity index 89%
rename from chrome/browser/ui/app_list/search/keyboard_shortcut_data.h
rename to chrome/browser/ash/app_list/search/keyboard_shortcut_data.h
index d878d97..9dafeeb 100644
--- a/chrome/browser/ui/app_list/search/keyboard_shortcut_data.h
+++ b/chrome/browser/ash/app_list/search/keyboard_shortcut_data.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_APP_LIST_SEARCH_KEYBOARD_SHORTCUT_DATA_H_
-#define CHROME_BROWSER_UI_APP_LIST_SEARCH_KEYBOARD_SHORTCUT_DATA_H_
+#ifndef CHROME_BROWSER_ASH_APP_LIST_SEARCH_KEYBOARD_SHORTCUT_DATA_H_
+#define CHROME_BROWSER_ASH_APP_LIST_SEARCH_KEYBOARD_SHORTCUT_DATA_H_
 
 #include "ash/public/cpp/keyboard_shortcut_item.h"
 #include "ui/base/l10n/l10n_util.h"
@@ -50,4 +50,4 @@
 
 }  // namespace app_list
 
-#endif  // CHROME_BROWSER_UI_APP_LIST_SEARCH_KEYBOARD_SHORTCUT_DATA_H_
+#endif  // CHROME_BROWSER_ASH_APP_LIST_SEARCH_KEYBOARD_SHORTCUT_DATA_H_
diff --git a/chrome/browser/ui/app_list/search/keyboard_shortcut_provider.cc b/chrome/browser/ash/app_list/search/keyboard_shortcut_provider.cc
similarity index 93%
rename from chrome/browser/ui/app_list/search/keyboard_shortcut_provider.cc
rename to chrome/browser/ash/app_list/search/keyboard_shortcut_provider.cc
index 0ed73d7..2e4a787 100644
--- a/chrome/browser/ui/app_list/search/keyboard_shortcut_provider.cc
+++ b/chrome/browser/ash/app_list/search/keyboard_shortcut_provider.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/ui/app_list/search/keyboard_shortcut_provider.h"
+#include "chrome/browser/ash/app_list/search/keyboard_shortcut_provider.h"
 
 #include <algorithm>
 
@@ -11,9 +11,9 @@
 #include "ash/shortcut_viewer/keyboard_shortcut_viewer_metadata.h"
 #include "base/task/task_traits.h"
 #include "base/task/thread_pool.h"
+#include "chrome/browser/ash/app_list/search/keyboard_shortcut_result.h"
+#include "chrome/browser/ash/app_list/search/search_controller.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/app_list/search/keyboard_shortcut_result.h"
-#include "chrome/browser/ui/app_list/search/search_controller.h"
 #include "chromeos/ash/components/string_matching/tokenized_string.h"
 
 namespace app_list {
diff --git a/chrome/browser/ui/app_list/search/keyboard_shortcut_provider.h b/chrome/browser/ash/app_list/search/keyboard_shortcut_provider.h
similarity index 78%
rename from chrome/browser/ui/app_list/search/keyboard_shortcut_provider.h
rename to chrome/browser/ash/app_list/search/keyboard_shortcut_provider.h
index aeac7437..1934557 100644
--- a/chrome/browser/ui/app_list/search/keyboard_shortcut_provider.h
+++ b/chrome/browser/ash/app_list/search/keyboard_shortcut_provider.h
@@ -2,13 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_UI_APP_LIST_SEARCH_KEYBOARD_SHORTCUT_PROVIDER_H_
-#define CHROME_BROWSER_UI_APP_LIST_SEARCH_KEYBOARD_SHORTCUT_PROVIDER_H_
+#ifndef CHROME_BROWSER_ASH_APP_LIST_SEARCH_KEYBOARD_SHORTCUT_PROVIDER_H_
+#define CHROME_BROWSER_ASH_APP_LIST_SEARCH_KEYBOARD_SHORTCUT_PROVIDER_H_
 
 #include "base/memory/weak_ptr.h"
 #include "base/sequence_checker.h"
-#include "chrome/browser/ui/app_list/search/keyboard_shortcut_data.h"
-#include "chrome/browser/ui/app_list/search/search_provider.h"
+#include "chrome/browser/ash/app_list/search/keyboard_shortcut_data.h"
+#include "chrome/browser/ash/app_list/search/search_provider.h"
 
 class Profile;
 
@@ -49,4 +49,4 @@
 
 }  // namespace app_list
 
-#endif  // CHROME_BROWSER_UI_APP_LIST_SEARCH_KEYBOARD_SHORTCUT_PROVIDER_H_
+#endif  // CHROME_BROWSER_ASH_APP_LIST_SEARCH_KEYBOARD_SHORTCUT_PROVIDER_H_
diff --git a/chrome/browser/ui/app_list/search/keyboard_shortcut_provider_unittest.cc b/chrome/browser/ash/app_list/search/keyboard_shortcut_provider_unittest.cc
similarity index 97%
rename from chrome/browser/ui/app_list/search/keyboard_shortcut_provider_unittest.cc
rename to chrome/browser/ash/app_list/search/keyboard_shortcut_provider_unittest.cc
index 63213e56..dda3eeb 100644
--- a/chrome/browser/ui/app_list/search/keyboard_shortcut_provider_unittest.cc
+++ b/chrome/browser/ash/app_list/search/keyboard_shortcut_provider_unittest.cc
@@ -2,11 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/ui/app_list/search/keyboard_shortcut_provider.h"
+#include "chrome/browser/ash/app_list/search/keyboard_shortcut_provider.h"
 
 #include "base/test/scoped_feature_list.h"
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
 #include "chrome/browser/ash/app_list/search/test/test_search_controller.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
 #include "chrome/test/base/testing_profile.h"
 #include "content/public/test/browser_task_environment.h"
 #include "testing/gmock/include/gmock/gmock.h"
diff --git a/chrome/browser/ui/app_list/search/keyboard_shortcut_result.cc b/chrome/browser/ash/app_list/search/keyboard_shortcut_result.cc
similarity index 99%
rename from chrome/browser/ui/app_list/search/keyboard_shortcut_result.cc
rename to chrome/browser/ash/app_list/search/keyboard_shortcut_result.cc
index 9fb0708..09618ec7 100644
--- a/chrome/browser/ui/app_list/search/keyboard_shortcut_result.cc
+++ b/chrome/browser/ash/app_list/search/keyboard_shortcut_result.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/ui/app_list/search/keyboard_shortcut_result.h"
+#include "chrome/browser/ash/app_list/search/keyboard_shortcut_result.h"
 
 #include <string>
 
diff --git a/chrome/browser/ui/app_list/search/keyboard_shortcut_result.h b/chrome/browser/ash/app_list/search/keyboard_shortcut_result.h
similarity index 86%
rename from chrome/browser/ui/app_list/search/keyboard_shortcut_result.h
rename to chrome/browser/ash/app_list/search/keyboard_shortcut_result.h
index b096d65e..1fcd1ff 100644
--- a/chrome/browser/ui/app_list/search/keyboard_shortcut_result.h
+++ b/chrome/browser/ash/app_list/search/keyboard_shortcut_result.h
@@ -2,12 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_UI_APP_LIST_SEARCH_KEYBOARD_SHORTCUT_RESULT_H_
-#define CHROME_BROWSER_UI_APP_LIST_SEARCH_KEYBOARD_SHORTCUT_RESULT_H_
+#ifndef CHROME_BROWSER_ASH_APP_LIST_SEARCH_KEYBOARD_SHORTCUT_RESULT_H_
+#define CHROME_BROWSER_ASH_APP_LIST_SEARCH_KEYBOARD_SHORTCUT_RESULT_H_
 
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
+#include "chrome/browser/ash/app_list/search/keyboard_shortcut_data.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
-#include "chrome/browser/ui/app_list/search/keyboard_shortcut_data.h"
 
 namespace ash::string_matching {
 class TokenizedString;
@@ -67,4 +67,4 @@
 
 }  // namespace app_list
 
-#endif  // CHROME_BROWSER_UI_APP_LIST_SEARCH_KEYBOARD_SHORTCUT_RESULT_H_
+#endif  // CHROME_BROWSER_ASH_APP_LIST_SEARCH_KEYBOARD_SHORTCUT_RESULT_H_
diff --git a/chrome/browser/ui/app_list/search/keyboard_shortcut_result_unittest.cc b/chrome/browser/ash/app_list/search/keyboard_shortcut_result_unittest.cc
similarity index 97%
rename from chrome/browser/ui/app_list/search/keyboard_shortcut_result_unittest.cc
rename to chrome/browser/ash/app_list/search/keyboard_shortcut_result_unittest.cc
index dfa39c72..4f241b03 100644
--- a/chrome/browser/ui/app_list/search/keyboard_shortcut_result_unittest.cc
+++ b/chrome/browser/ash/app_list/search/keyboard_shortcut_result_unittest.cc
@@ -2,12 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/ui/app_list/search/keyboard_shortcut_result.h"
+#include "chrome/browser/ash/app_list/search/keyboard_shortcut_result.h"
 
 #include "ash/public/cpp/app_list/app_list_types.h"
 #include "ash/shortcut_viewer/keyboard_shortcut_viewer_metadata.h"
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
 #include "chromeos/ash/components/string_matching/tokenized_string.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
diff --git a/chrome/browser/ash/app_list/search/omnibox/omnibox_answer_result.h b/chrome/browser/ash/app_list/search/omnibox/omnibox_answer_result.h
index 5bf2373e..18b071d 100644
--- a/chrome/browser/ash/app_list/search/omnibox/omnibox_answer_result.h
+++ b/chrome/browser/ash/app_list/search/omnibox/omnibox_answer_result.h
@@ -7,8 +7,8 @@
 
 #include "ash/public/cpp/style/color_mode_observer.h"
 #include "base/memory/weak_ptr.h"
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
 #include "chrome/browser/bitmap_fetcher/bitmap_fetcher_delegate.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
 #include "chromeos/crosapi/mojom/launcher_search.mojom.h"
 
 class AppListControllerDelegate;
diff --git a/chrome/browser/ash/app_list/search/omnibox/omnibox_lacros_provider.h b/chrome/browser/ash/app_list/search/omnibox/omnibox_lacros_provider.h
index 8d5d49d0..1fd45fa 100644
--- a/chrome/browser/ash/app_list/search/omnibox/omnibox_lacros_provider.h
+++ b/chrome/browser/ash/app_list/search/omnibox/omnibox_lacros_provider.h
@@ -7,7 +7,7 @@
 
 #include "ash/public/cpp/app_list/app_list_types.h"
 #include "base/memory/weak_ptr.h"
-#include "chrome/browser/ui/app_list/search/search_provider.h"
+#include "chrome/browser/ash/app_list/search/search_provider.h"
 #include "chromeos/ash/components/string_matching/tokenized_string.h"
 #include "chromeos/crosapi/mojom/launcher_search.mojom.h"
 #include "components/omnibox/browser/autocomplete_input.h"
diff --git a/chrome/browser/ash/app_list/search/omnibox/omnibox_provider.h b/chrome/browser/ash/app_list/search/omnibox/omnibox_provider.h
index 5ed467b8..b132fc6 100644
--- a/chrome/browser/ash/app_list/search/omnibox/omnibox_provider.h
+++ b/chrome/browser/ash/app_list/search/omnibox/omnibox_provider.h
@@ -9,7 +9,7 @@
 #include <memory>
 
 #include "base/time/time.h"
-#include "chrome/browser/ui/app_list/search/search_provider.h"
+#include "chrome/browser/ash/app_list/search/search_provider.h"
 #include "chromeos/ash/components/string_matching/tokenized_string.h"
 #include "components/omnibox/browser/autocomplete_controller.h"
 #include "components/omnibox/browser/favicon_cache.h"
diff --git a/chrome/browser/ash/app_list/search/omnibox/omnibox_provider_unittest.cc b/chrome/browser/ash/app_list/search/omnibox/omnibox_provider_unittest.cc
index 1caa90b1..65b8f16 100644
--- a/chrome/browser/ash/app_list/search/omnibox/omnibox_provider_unittest.cc
+++ b/chrome/browser/ash/app_list/search/omnibox/omnibox_provider_unittest.cc
@@ -9,10 +9,10 @@
 #include <vector>
 
 #include "base/run_loop.h"
+#include "chrome/browser/ash/app_list/search/search_controller.h"
 #include "chrome/browser/ash/app_list/search/test/test_search_controller.h"
 #include "chrome/browser/ash/app_list/test/test_app_list_controller_delegate.h"
 #include "chrome/browser/search_engines/template_url_service_factory.h"
-#include "chrome/browser/ui/app_list/search/search_controller.h"
 #include "chrome/common/chrome_constants.h"
 #include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile_manager.h"
diff --git a/chrome/browser/ash/app_list/search/omnibox/omnibox_result.h b/chrome/browser/ash/app_list/search/omnibox/omnibox_result.h
index fe49506e..023fb05 100644
--- a/chrome/browser/ash/app_list/search/omnibox/omnibox_result.h
+++ b/chrome/browser/ash/app_list/search/omnibox/omnibox_result.h
@@ -11,8 +11,8 @@
 
 #include "ash/public/cpp/style/color_mode_observer.h"
 #include "base/memory/weak_ptr.h"
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
 #include "chrome/browser/bitmap_fetcher/bitmap_fetcher_delegate.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
 #include "chromeos/crosapi/mojom/launcher_search.mojom.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 
diff --git a/chrome/browser/ash/app_list/search/omnibox/omnibox_result_unittest.cc b/chrome/browser/ash/app_list/search/omnibox/omnibox_result_unittest.cc
index bc786d0..32a2f55 100644
--- a/chrome/browser/ash/app_list/search/omnibox/omnibox_result_unittest.cc
+++ b/chrome/browser/ash/app_list/search/omnibox/omnibox_result_unittest.cc
@@ -16,12 +16,12 @@
 #include "base/memory/scoped_refptr.h"
 #include "base/run_loop.h"
 #include "base/task/cancelable_task_tracker.h"
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
 #include "chrome/browser/ash/app_list/search/common/icon_constants.h"
 #include "chrome/browser/ash/app_list/test/test_app_list_controller_delegate.h"
 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
 #include "chrome/browser/chromeos/launcher_search/search_util.h"
 #include "chrome/browser/search_engines/template_url_service_factory.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
 #include "chrome/test/base/testing_profile.h"
 #include "components/bookmarks/browser/bookmark_model.h"
 #include "components/bookmarks/test/bookmark_test_helpers.h"
diff --git a/chrome/browser/ash/app_list/search/omnibox/open_tab_result.h b/chrome/browser/ash/app_list/search/omnibox/open_tab_result.h
index ab40c9a4e..084e974 100644
--- a/chrome/browser/ash/app_list/search/omnibox/open_tab_result.h
+++ b/chrome/browser/ash/app_list/search/omnibox/open_tab_result.h
@@ -9,7 +9,7 @@
 
 #include "ash/public/cpp/style/color_mode_observer.h"
 #include "base/memory/weak_ptr.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
 #include "chromeos/crosapi/mojom/launcher_search.mojom.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
diff --git a/chrome/browser/ui/app_list/search/os_settings_provider.cc b/chrome/browser/ash/app_list/search/os_settings_provider.cc
similarity index 99%
rename from chrome/browser/ui/app_list/search/os_settings_provider.cc
rename to chrome/browser/ash/app_list/search/os_settings_provider.cc
index f278286..a8d8249d 100644
--- a/chrome/browser/ui/app_list/search/os_settings_provider.cc
+++ b/chrome/browser/ash/app_list/search/os_settings_provider.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/ui/app_list/search/os_settings_provider.h"
+#include "chrome/browser/ash/app_list/search/os_settings_provider.h"
 
 #include <algorithm>
 #include <memory>
diff --git a/chrome/browser/ui/app_list/search/os_settings_provider.h b/chrome/browser/ash/app_list/search/os_settings_provider.h
similarity index 92%
rename from chrome/browser/ui/app_list/search/os_settings_provider.h
rename to chrome/browser/ash/app_list/search/os_settings_provider.h
index d05ccb2..3d68c735 100644
--- a/chrome/browser/ui/app_list/search/os_settings_provider.h
+++ b/chrome/browser/ash/app_list/search/os_settings_provider.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_APP_LIST_SEARCH_OS_SETTINGS_PROVIDER_H_
-#define CHROME_BROWSER_UI_APP_LIST_SEARCH_OS_SETTINGS_PROVIDER_H_
+#ifndef CHROME_BROWSER_ASH_APP_LIST_SEARCH_OS_SETTINGS_PROVIDER_H_
+#define CHROME_BROWSER_ASH_APP_LIST_SEARCH_OS_SETTINGS_PROVIDER_H_
 
 #include <string>
 #include <vector>
@@ -11,8 +11,8 @@
 #include "base/memory/weak_ptr.h"
 #include "base/time/time.h"
 #include "chrome/browser/apps/app_service/app_service_proxy_forward.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
-#include "chrome/browser/ui/app_list/search/search_provider.h"
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
+#include "chrome/browser/ash/app_list/search/search_provider.h"
 #include "chrome/browser/ui/webui/settings/ash/search/search.mojom.h"
 #include "components/services/app_service/public/cpp/app_registry_cache.h"
 #include "components/services/app_service/public/cpp/icon_types.h"
@@ -133,4 +133,4 @@
 
 }  // namespace app_list
 
-#endif  // CHROME_BROWSER_UI_APP_LIST_SEARCH_OS_SETTINGS_PROVIDER_H_
+#endif  // CHROME_BROWSER_ASH_APP_LIST_SEARCH_OS_SETTINGS_PROVIDER_H_
diff --git a/chrome/browser/ui/app_list/search/os_settings_provider_unittest.cc b/chrome/browser/ash/app_list/search/os_settings_provider_unittest.cc
similarity index 99%
rename from chrome/browser/ui/app_list/search/os_settings_provider_unittest.cc
rename to chrome/browser/ash/app_list/search/os_settings_provider_unittest.cc
index 073a4ca..b4cb001a 100644
--- a/chrome/browser/ui/app_list/search/os_settings_provider_unittest.cc
+++ b/chrome/browser/ash/app_list/search/os_settings_provider_unittest.cc
@@ -2,9 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/ui/app_list/search/os_settings_provider.h"
+#include "chrome/browser/ash/app_list/search/os_settings_provider.h"
 
 #include <memory>
+
 #include "chrome/browser/apps/app_service/app_service_proxy.h"
 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
 #include "chrome/browser/ash/app_list/search/test/test_search_controller.h"
diff --git a/chrome/browser/ui/app_list/search/personalization_provider.cc b/chrome/browser/ash/app_list/search/personalization_provider.cc
similarity index 98%
rename from chrome/browser/ui/app_list/search/personalization_provider.cc
rename to chrome/browser/ash/app_list/search/personalization_provider.cc
index 9854fdcb..75dbc83 100644
--- a/chrome/browser/ui/app_list/search/personalization_provider.cc
+++ b/chrome/browser/ash/app_list/search/personalization_provider.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/ui/app_list/search/personalization_provider.h"
+#include "chrome/browser/ash/app_list/search/personalization_provider.h"
 
 #include <string>
 #include <vector>
diff --git a/chrome/browser/ui/app_list/search/personalization_provider.h b/chrome/browser/ash/app_list/search/personalization_provider.h
similarity index 89%
rename from chrome/browser/ui/app_list/search/personalization_provider.h
rename to chrome/browser/ash/app_list/search/personalization_provider.h
index dabcd54..c85e739 100644
--- a/chrome/browser/ui/app_list/search/personalization_provider.h
+++ b/chrome/browser/ash/app_list/search/personalization_provider.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_APP_LIST_SEARCH_PERSONALIZATION_PROVIDER_H_
-#define CHROME_BROWSER_UI_APP_LIST_SEARCH_PERSONALIZATION_PROVIDER_H_
+#ifndef CHROME_BROWSER_ASH_APP_LIST_SEARCH_PERSONALIZATION_PROVIDER_H_
+#define CHROME_BROWSER_ASH_APP_LIST_SEARCH_PERSONALIZATION_PROVIDER_H_
 
 #include <string>
 #include <vector>
@@ -13,8 +13,8 @@
 #include "base/memory/weak_ptr.h"
 #include "base/time/time.h"
 #include "chrome/browser/apps/app_service/app_service_proxy_forward.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
-#include "chrome/browser/ui/app_list/search/search_provider.h"
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
+#include "chrome/browser/ash/app_list/search/search_provider.h"
 #include "components/services/app_service/public/cpp/app_registry_cache.h"
 #include "components/services/app_service/public/cpp/icon_types.h"
 #include "mojo/public/cpp/bindings/receiver.h"
@@ -101,4 +101,4 @@
 
 }  // namespace app_list
 
-#endif  // CHROME_BROWSER_UI_APP_LIST_SEARCH_PERSONALIZATION_PROVIDER_H_
+#endif  // CHROME_BROWSER_ASH_APP_LIST_SEARCH_PERSONALIZATION_PROVIDER_H_
diff --git a/chrome/browser/ui/app_list/search/personalization_provider_unittest.cc b/chrome/browser/ash/app_list/search/personalization_provider_unittest.cc
similarity index 98%
rename from chrome/browser/ui/app_list/search/personalization_provider_unittest.cc
rename to chrome/browser/ash/app_list/search/personalization_provider_unittest.cc
index c0dbe3e..639509ad 100644
--- a/chrome/browser/ui/app_list/search/personalization_provider_unittest.cc
+++ b/chrome/browser/ash/app_list/search/personalization_provider_unittest.cc
@@ -2,9 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/ui/app_list/search/personalization_provider.h"
+#include "chrome/browser/ash/app_list/search/personalization_provider.h"
 
 #include <memory>
+
 #include "ash/webui/personalization_app/personalization_app_url_constants.h"
 #include "ash/webui/personalization_app/search/search_handler.h"
 #include "chrome/browser/apps/app_service/app_service_proxy.h"
diff --git a/chrome/browser/ash/app_list/search/ranking/answer_ranker.cc b/chrome/browser/ash/app_list/search/ranking/answer_ranker.cc
index a014485d..6aa4abb 100644
--- a/chrome/browser/ash/app_list/search/ranking/answer_ranker.cc
+++ b/chrome/browser/ash/app_list/search/ranking/answer_ranker.cc
@@ -5,8 +5,8 @@
 #include "chrome/browser/ash/app_list/search/ranking/answer_ranker.h"
 
 #include "ash/public/cpp/app_list/app_list_types.h"
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
 #include "chrome/browser/ash/app_list/search/common/icon_constants.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
 
 namespace app_list {
 namespace {
diff --git a/chrome/browser/ash/app_list/search/ranking/answer_ranker.h b/chrome/browser/ash/app_list/search/ranking/answer_ranker.h
index d0c3339..16661e0 100644
--- a/chrome/browser/ash/app_list/search/ranking/answer_ranker.h
+++ b/chrome/browser/ash/app_list/search/ranking/answer_ranker.h
@@ -7,7 +7,7 @@
 
 #include "base/memory/weak_ptr.h"
 #include "chrome/browser/ash/app_list/search/ranking/ranker.h"
-#include "chrome/browser/ui/app_list/search/types.h"
+#include "chrome/browser/ash/app_list/search/types.h"
 
 namespace app_list {
 
diff --git a/chrome/browser/ash/app_list/search/ranking/answer_ranker_unittest.cc b/chrome/browser/ash/app_list/search/ranking/answer_ranker_unittest.cc
index 806c208..17f6e8e 100644
--- a/chrome/browser/ash/app_list/search/ranking/answer_ranker_unittest.cc
+++ b/chrome/browser/ash/app_list/search/ranking/answer_ranker_unittest.cc
@@ -4,10 +4,10 @@
 
 #include "chrome/browser/ash/app_list/search/ranking/answer_ranker.h"
 
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
 #include "chrome/browser/ash/app_list/search/common/icon_constants.h"
 #include "chrome/browser/ash/app_list/search/test/test_result.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
-#include "chrome/browser/ui/app_list/search/types.h"
+#include "chrome/browser/ash/app_list/search/types.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
diff --git a/chrome/browser/ash/app_list/search/ranking/best_match_ranker.cc b/chrome/browser/ash/app_list/search/ranking/best_match_ranker.cc
index afa6168..b0b5421 100644
--- a/chrome/browser/ash/app_list/search/ranking/best_match_ranker.cc
+++ b/chrome/browser/ash/app_list/search/ranking/best_match_ranker.cc
@@ -8,9 +8,9 @@
 #include <vector>
 
 #include "base/containers/flat_set.h"
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
 #include "chrome/browser/ash/app_list/search/ranking/constants.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
-#include "chrome/browser/ui/app_list/search/types.h"
+#include "chrome/browser/ash/app_list/search/types.h"
 
 namespace app_list {
 namespace {
diff --git a/chrome/browser/ash/app_list/search/ranking/best_match_ranker_unittest.cc b/chrome/browser/ash/app_list/search/ranking/best_match_ranker_unittest.cc
index e3495cc..cdaee14b 100644
--- a/chrome/browser/ash/app_list/search/ranking/best_match_ranker_unittest.cc
+++ b/chrome/browser/ash/app_list/search/ranking/best_match_ranker_unittest.cc
@@ -4,9 +4,9 @@
 
 #include "chrome/browser/ash/app_list/search/ranking/best_match_ranker.h"
 
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
 #include "chrome/browser/ash/app_list/search/test/test_result.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
-#include "chrome/browser/ui/app_list/search/types.h"
+#include "chrome/browser/ash/app_list/search/types.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
diff --git a/chrome/browser/ash/app_list/search/ranking/continue_ranker.cc b/chrome/browser/ash/app_list/search/ranking/continue_ranker.cc
index 401bbf54..fa64776 100644
--- a/chrome/browser/ash/app_list/search/ranking/continue_ranker.cc
+++ b/chrome/browser/ash/app_list/search/ranking/continue_ranker.cc
@@ -5,7 +5,7 @@
 #include "chrome/browser/ash/app_list/search/ranking/continue_ranker.h"
 
 #include "ash/public/cpp/app_list/app_list_types.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
 
 namespace app_list {
 
diff --git a/chrome/browser/ash/app_list/search/ranking/continue_ranker.h b/chrome/browser/ash/app_list/search/ranking/continue_ranker.h
index bc36c71a..6c825ab 100644
--- a/chrome/browser/ash/app_list/search/ranking/continue_ranker.h
+++ b/chrome/browser/ash/app_list/search/ranking/continue_ranker.h
@@ -6,7 +6,7 @@
 #define CHROME_BROWSER_ASH_APP_LIST_SEARCH_RANKING_CONTINUE_RANKER_H_
 
 #include "chrome/browser/ash/app_list/search/ranking/ranker.h"
-#include "chrome/browser/ui/app_list/search/types.h"
+#include "chrome/browser/ash/app_list/search/types.h"
 
 namespace app_list {
 
diff --git a/chrome/browser/ash/app_list/search/ranking/filtering_ranker.cc b/chrome/browser/ash/app_list/search/ranking/filtering_ranker.cc
index 5cd29df..bbc8010 100644
--- a/chrome/browser/ash/app_list/search/ranking/filtering_ranker.cc
+++ b/chrome/browser/ash/app_list/search/ranking/filtering_ranker.cc
@@ -6,9 +6,9 @@
 
 #include "ash/public/cpp/app_list/app_list_config.h"
 #include "base/containers/flat_set.h"
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
 #include "chrome/browser/ash/app_list/search/omnibox/omnibox_util.h"
 #include "chrome/browser/ash/app_list/search/ranking/constants.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
 #include "chromeos/crosapi/mojom/launcher_search.mojom.h"
 
 namespace app_list {
diff --git a/chrome/browser/ash/app_list/search/ranking/filtering_ranker_unittest.cc b/chrome/browser/ash/app_list/search/ranking/filtering_ranker_unittest.cc
index 50105fd..76c2755 100644
--- a/chrome/browser/ash/app_list/search/ranking/filtering_ranker_unittest.cc
+++ b/chrome/browser/ash/app_list/search/ranking/filtering_ranker_unittest.cc
@@ -4,11 +4,11 @@
 
 #include "chrome/browser/ash/app_list/search/ranking/filtering_ranker.h"
 
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
 #include "chrome/browser/ash/app_list/search/omnibox/omnibox_util.h"
+#include "chrome/browser/ash/app_list/search/search_controller.h"
 #include "chrome/browser/ash/app_list/search/test/test_result.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
-#include "chrome/browser/ui/app_list/search/search_controller.h"
-#include "chrome/browser/ui/app_list/search/types.h"
+#include "chrome/browser/ash/app_list/search/types.h"
 #include "chromeos/crosapi/mojom/launcher_search.mojom-forward.h"
 #include "chromeos/crosapi/mojom/launcher_search.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
diff --git a/chrome/browser/ash/app_list/search/ranking/ftrl_ranker.cc b/chrome/browser/ash/app_list/search/ranking/ftrl_ranker.cc
index d77c0b4..24dbe60 100644
--- a/chrome/browser/ash/app_list/search/ranking/ftrl_ranker.cc
+++ b/chrome/browser/ash/app_list/search/ranking/ftrl_ranker.cc
@@ -5,9 +5,9 @@
 #include "chrome/browser/ash/app_list/search/ranking/ftrl_ranker.h"
 
 #include "base/strings/string_number_conversions.h"
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
 #include "chrome/browser/ash/app_list/search/ranking/util.h"
 #include "chrome/browser/ash/app_list/search/util/ftrl_optimizer.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
 
 namespace app_list {
 
diff --git a/chrome/browser/ash/app_list/search/ranking/ftrl_ranker.h b/chrome/browser/ash/app_list/search/ranking/ftrl_ranker.h
index 931018b..7b145199 100644
--- a/chrome/browser/ash/app_list/search/ranking/ftrl_ranker.h
+++ b/chrome/browser/ash/app_list/search/ranking/ftrl_ranker.h
@@ -6,9 +6,9 @@
 #define CHROME_BROWSER_ASH_APP_LIST_SEARCH_RANKING_FTRL_RANKER_H_
 
 #include "chrome/browser/ash/app_list/search/ranking/ranker.h"
+#include "chrome/browser/ash/app_list/search/types.h"
 #include "chrome/browser/ash/app_list/search/util/ftrl_optimizer.h"
 #include "chrome/browser/ash/app_list/search/util/mrfu_cache.h"
-#include "chrome/browser/ui/app_list/search/types.h"
 
 namespace app_list {
 
diff --git a/chrome/browser/ash/app_list/search/ranking/ftrl_ranker_unittest.cc b/chrome/browser/ash/app_list/search/ranking/ftrl_ranker_unittest.cc
index e9f127b..ae4cc3e 100644
--- a/chrome/browser/ash/app_list/search/ranking/ftrl_ranker_unittest.cc
+++ b/chrome/browser/ash/app_list/search/ranking/ftrl_ranker_unittest.cc
@@ -4,11 +4,11 @@
 
 #include "chrome/browser/ash/app_list/search/ranking/ftrl_ranker.h"
 
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
 #include "chrome/browser/ash/app_list/search/ranking/ranker.h"
+#include "chrome/browser/ash/app_list/search/search_controller.h"
 #include "chrome/browser/ash/app_list/search/test/ranking_test_util.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
-#include "chrome/browser/ui/app_list/search/search_controller.h"
-#include "chrome/browser/ui/app_list/search/types.h"
+#include "chrome/browser/ash/app_list/search/types.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
diff --git a/chrome/browser/ash/app_list/search/ranking/mrfu_ranker.cc b/chrome/browser/ash/app_list/search/ranking/mrfu_ranker.cc
index 32b9f04..ce23194 100644
--- a/chrome/browser/ash/app_list/search/ranking/mrfu_ranker.cc
+++ b/chrome/browser/ash/app_list/search/ranking/mrfu_ranker.cc
@@ -4,10 +4,10 @@
 
 #include "chrome/browser/ash/app_list/search/ranking/mrfu_ranker.h"
 
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
 #include "chrome/browser/ash/app_list/search/ranking/util.h"
+#include "chrome/browser/ash/app_list/search/types.h"
 #include "chrome/browser/ash/app_list/search/util/ftrl_optimizer.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
-#include "chrome/browser/ui/app_list/search/types.h"
 
 namespace app_list {
 
diff --git a/chrome/browser/ash/app_list/search/ranking/mrfu_ranker.h b/chrome/browser/ash/app_list/search/ranking/mrfu_ranker.h
index 2846318..632118c 100644
--- a/chrome/browser/ash/app_list/search/ranking/mrfu_ranker.h
+++ b/chrome/browser/ash/app_list/search/ranking/mrfu_ranker.h
@@ -6,9 +6,9 @@
 #define CHROME_BROWSER_ASH_APP_LIST_SEARCH_RANKING_MRFU_RANKER_H_
 
 #include "chrome/browser/ash/app_list/search/ranking/ranker.h"
+#include "chrome/browser/ash/app_list/search/types.h"
 #include "chrome/browser/ash/app_list/search/util/ftrl_optimizer.h"
 #include "chrome/browser/ash/app_list/search/util/mrfu_cache.h"
-#include "chrome/browser/ui/app_list/search/types.h"
 
 namespace app_list {
 
diff --git a/chrome/browser/ash/app_list/search/ranking/mrfu_ranker_unittest.cc b/chrome/browser/ash/app_list/search/ranking/mrfu_ranker_unittest.cc
index 224b761..54a1f086 100644
--- a/chrome/browser/ash/app_list/search/ranking/mrfu_ranker_unittest.cc
+++ b/chrome/browser/ash/app_list/search/ranking/mrfu_ranker_unittest.cc
@@ -4,11 +4,11 @@
 
 #include "chrome/browser/ash/app_list/search/ranking/mrfu_ranker.h"
 
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
 #include "chrome/browser/ash/app_list/search/ranking/ranker.h"
+#include "chrome/browser/ash/app_list/search/search_controller.h"
 #include "chrome/browser/ash/app_list/search/test/ranking_test_util.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
-#include "chrome/browser/ui/app_list/search/search_controller.h"
-#include "chrome/browser/ui/app_list/search/types.h"
+#include "chrome/browser/ash/app_list/search/types.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
diff --git a/chrome/browser/ash/app_list/search/ranking/query_highlighter.cc b/chrome/browser/ash/app_list/search/ranking/query_highlighter.cc
index d794a7c..7918062 100644
--- a/chrome/browser/ash/app_list/search/ranking/query_highlighter.cc
+++ b/chrome/browser/ash/app_list/search/ranking/query_highlighter.cc
@@ -5,7 +5,7 @@
 #include "chrome/browser/ash/app_list/search/ranking/query_highlighter.h"
 
 #include "ash/public/cpp/app_list/app_list_types.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
 #include "chromeos/crosapi/mojom/launcher_search.mojom.h"
 #include "components/omnibox/browser/autocomplete_match.h"
 #include "components/omnibox/browser/autocomplete_match_classification.h"
diff --git a/chrome/browser/ash/app_list/search/ranking/query_highlighter.h b/chrome/browser/ash/app_list/search/ranking/query_highlighter.h
index fa279f7..8585fea 100644
--- a/chrome/browser/ash/app_list/search/ranking/query_highlighter.h
+++ b/chrome/browser/ash/app_list/search/ranking/query_highlighter.h
@@ -6,7 +6,7 @@
 #define CHROME_BROWSER_ASH_APP_LIST_SEARCH_RANKING_QUERY_HIGHLIGHTER_H_
 
 #include "chrome/browser/ash/app_list/search/ranking/ranker.h"
-#include "chrome/browser/ui/app_list/search/types.h"
+#include "chrome/browser/ash/app_list/search/types.h"
 
 namespace app_list {
 
diff --git a/chrome/browser/ash/app_list/search/ranking/query_highlighter_unittest.cc b/chrome/browser/ash/app_list/search/ranking/query_highlighter_unittest.cc
index 9df7b6b..ef4f123 100644
--- a/chrome/browser/ash/app_list/search/ranking/query_highlighter_unittest.cc
+++ b/chrome/browser/ash/app_list/search/ranking/query_highlighter_unittest.cc
@@ -4,9 +4,9 @@
 
 #include "chrome/browser/ash/app_list/search/ranking/query_highlighter.h"
 
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
 #include "chrome/browser/ash/app_list/search/test/test_result.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
-#include "chrome/browser/ui/app_list/search/types.h"
+#include "chrome/browser/ash/app_list/search/types.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
diff --git a/chrome/browser/ash/app_list/search/ranking/ranker.h b/chrome/browser/ash/app_list/search/ranking/ranker.h
index 144dc466..5dcc18d 100644
--- a/chrome/browser/ash/app_list/search/ranking/ranker.h
+++ b/chrome/browser/ash/app_list/search/ranking/ranker.h
@@ -6,8 +6,8 @@
 #define CHROME_BROWSER_ASH_APP_LIST_SEARCH_RANKING_RANKER_H_
 
 #include "chrome/browser/ash/app_list/search/ranking/launch_data.h"
-#include "chrome/browser/ui/app_list/search/search_controller.h"
-#include "chrome/browser/ui/app_list/search/types.h"
+#include "chrome/browser/ash/app_list/search/search_controller.h"
+#include "chrome/browser/ash/app_list/search/types.h"
 
 #include <string>
 
diff --git a/chrome/browser/ash/app_list/search/ranking/removed_results_ranker.cc b/chrome/browser/ash/app_list/search/ranking/removed_results_ranker.cc
index 551e122..7ef8e46 100644
--- a/chrome/browser/ash/app_list/search/ranking/removed_results_ranker.cc
+++ b/chrome/browser/ash/app_list/search/ranking/removed_results_ranker.cc
@@ -4,11 +4,11 @@
 
 #include "chrome/browser/ash/app_list/search/ranking/removed_results_ranker.h"
 
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
 #include "chrome/browser/ash/app_list/search/files/file_suggest_keyed_service.h"
 #include "chrome/browser/ash/app_list/search/files/file_suggest_keyed_service_factory.h"
+#include "chrome/browser/ash/app_list/search/types.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
-#include "chrome/browser/ui/app_list/search/types.h"
 
 namespace app_list {
 namespace {
diff --git a/chrome/browser/ash/app_list/search/ranking/removed_results_ranker_unittest.cc b/chrome/browser/ash/app_list/search/ranking/removed_results_ranker_unittest.cc
index 4e087b1..c171fe7 100644
--- a/chrome/browser/ash/app_list/search/ranking/removed_results_ranker_unittest.cc
+++ b/chrome/browser/ash/app_list/search/ranking/removed_results_ranker_unittest.cc
@@ -6,14 +6,14 @@
 
 #include "ash/public/cpp/app_list/app_list_types.h"
 #include "base/files/scoped_temp_dir.h"
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
 #include "chrome/browser/ash/app_list/search/files/file_result.h"
 #include "chrome/browser/ash/app_list/search/files/file_suggest_keyed_service_factory.h"
 #include "chrome/browser/ash/app_list/search/files/file_suggest_test_util.h"
 #include "chrome/browser/ash/app_list/search/files/mock_file_suggest_keyed_service.h"
+#include "chrome/browser/ash/app_list/search/search_controller.h"
 #include "chrome/browser/ash/app_list/search/test/test_result.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
-#include "chrome/browser/ui/app_list/search/search_controller.h"
-#include "chrome/browser/ui/app_list/search/types.h"
+#include "chrome/browser/ash/app_list/search/types.h"
 #include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile_manager.h"
 #include "content/public/test/browser_task_environment.h"
diff --git a/chrome/browser/ash/app_list/search/ranking/score_normalizing_ranker.cc b/chrome/browser/ash/app_list/search/ranking/score_normalizing_ranker.cc
index 1a8fe25b..8e920a9d 100644
--- a/chrome/browser/ash/app_list/search/ranking/score_normalizing_ranker.cc
+++ b/chrome/browser/ash/app_list/search/ranking/score_normalizing_ranker.cc
@@ -6,9 +6,9 @@
 
 #include "base/strings/strcat.h"
 #include "base/strings/string_number_conversions.h"
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
+#include "chrome/browser/ash/app_list/search/search_controller.h"
 #include "chrome/browser/ash/app_list/search/util/score_normalizer.pb.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
-#include "chrome/browser/ui/app_list/search/search_controller.h"
 
 namespace app_list {
 namespace {
diff --git a/chrome/browser/ash/app_list/search/ranking/score_normalizing_ranker_unittest.cc b/chrome/browser/ash/app_list/search/ranking/score_normalizing_ranker_unittest.cc
index 0bbf098a..f621b6b 100644
--- a/chrome/browser/ash/app_list/search/ranking/score_normalizing_ranker_unittest.cc
+++ b/chrome/browser/ash/app_list/search/ranking/score_normalizing_ranker_unittest.cc
@@ -7,10 +7,10 @@
 #include "base/files/scoped_temp_dir.h"
 #include "base/logging.h"
 #include "base/test/task_environment.h"
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
+#include "chrome/browser/ash/app_list/search/search_controller.h"
 #include "chrome/browser/ash/app_list/search/test/test_result.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
-#include "chrome/browser/ui/app_list/search/search_controller.h"
-#include "chrome/browser/ui/app_list/search/types.h"
+#include "chrome/browser/ash/app_list/search/types.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
diff --git a/chrome/browser/ash/app_list/search/ranking/scoring.h b/chrome/browser/ash/app_list/search/ranking/scoring.h
index eebe11b..dbb7708 100644
--- a/chrome/browser/ash/app_list/search/ranking/scoring.h
+++ b/chrome/browser/ash/app_list/search/ranking/scoring.h
@@ -5,8 +5,8 @@
 #ifndef CHROME_BROWSER_ASH_APP_LIST_SEARCH_RANKING_SCORING_H_
 #define CHROME_BROWSER_ASH_APP_LIST_SEARCH_RANKING_SCORING_H_
 
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
-#include "chrome/browser/ui/app_list/search/types.h"
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
+#include "chrome/browser/ash/app_list/search/types.h"
 
 namespace app_list {
 
diff --git a/chrome/browser/ash/app_list/search/ranking/util.h b/chrome/browser/ash/app_list/search/ranking/util.h
index 7f7897e..3373fe6 100644
--- a/chrome/browser/ash/app_list/search/ranking/util.h
+++ b/chrome/browser/ash/app_list/search/ranking/util.h
@@ -8,7 +8,7 @@
 #include <string>
 
 #include "base/files/file_path.h"
-#include "chrome/browser/ui/app_list/search/types.h"
+#include "chrome/browser/ash/app_list/search/types.h"
 
 class Profile;
 
diff --git a/chrome/browser/ui/app_list/search/search_controller.h b/chrome/browser/ash/app_list/search/search_controller.h
similarity index 94%
rename from chrome/browser/ui/app_list/search/search_controller.h
rename to chrome/browser/ash/app_list/search/search_controller.h
index 60147ce..84bf9d25 100644
--- a/chrome/browser/ui/app_list/search/search_controller.h
+++ b/chrome/browser/ash/app_list/search/search_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 CHROME_BROWSER_UI_APP_LIST_SEARCH_SEARCH_CONTROLLER_H_
-#define CHROME_BROWSER_UI_APP_LIST_SEARCH_SEARCH_CONTROLLER_H_
+#ifndef CHROME_BROWSER_ASH_APP_LIST_SEARCH_SEARCH_CONTROLLER_H_
+#define CHROME_BROWSER_ASH_APP_LIST_SEARCH_SEARCH_CONTROLLER_H_
 
 #include <stddef.h>
 
@@ -16,7 +16,7 @@
 #include "base/observer_list_types.h"
 #include "base/time/time.h"
 #include "chrome/browser/ash/app_list/search/ranking/launch_data.h"
-#include "chrome/browser/ui/app_list/search/types.h"
+#include "chrome/browser/ash/app_list/search/types.h"
 
 class ChromeSearchResult;
 
@@ -129,4 +129,4 @@
 
 }  // namespace app_list
 
-#endif  // CHROME_BROWSER_UI_APP_LIST_SEARCH_SEARCH_CONTROLLER_H_
+#endif  // CHROME_BROWSER_ASH_APP_LIST_SEARCH_SEARCH_CONTROLLER_H_
diff --git a/chrome/browser/ui/app_list/search/search_controller_factory.cc b/chrome/browser/ash/app_list/search/search_controller_factory.cc
similarity index 87%
rename from chrome/browser/ui/app_list/search/search_controller_factory.cc
rename to chrome/browser/ash/app_list/search/search_controller_factory.cc
index f40f0ec..a5196e2 100644
--- a/chrome/browser/ui/app_list/search/search_controller_factory.cc
+++ b/chrome/browser/ash/app_list/search/search_controller_factory.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/ui/app_list/search/search_controller_factory.h"
+#include "chrome/browser/ash/app_list/search/search_controller_factory.h"
 
 #include <stddef.h>
 
@@ -13,15 +13,26 @@
 #include "base/metrics/field_trial_params.h"
 #include "build/build_config.h"
 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
+#include "chrome/browser/ash/app_list/search/app_search_provider.h"
+#include "chrome/browser/ash/app_list/search/app_zero_state_provider.h"
 #include "chrome/browser/ash/app_list/search/arc/arc_app_shortcuts_search_provider.h"
 #include "chrome/browser/ash/app_list/search/arc/arc_playstore_search_provider.h"
+#include "chrome/browser/ash/app_list/search/assistant_text_search_provider.h"
 #include "chrome/browser/ash/app_list/search/files/drive_search_provider.h"
 #include "chrome/browser/ash/app_list/search/files/file_search_provider.h"
 #include "chrome/browser/ash/app_list/search/files/zero_state_drive_provider.h"
 #include "chrome/browser/ash/app_list/search/files/zero_state_file_provider.h"
 #include "chrome/browser/ash/app_list/search/games/game_provider.h"
+#include "chrome/browser/ash/app_list/search/help_app_provider.h"
+#include "chrome/browser/ash/app_list/search/help_app_zero_state_provider.h"
+#include "chrome/browser/ash/app_list/search/keyboard_shortcut_provider.h"
 #include "chrome/browser/ash/app_list/search/omnibox/omnibox_lacros_provider.h"
 #include "chrome/browser/ash/app_list/search/omnibox/omnibox_provider.h"
+#include "chrome/browser/ash/app_list/search/os_settings_provider.h"
+#include "chrome/browser/ash/app_list/search/personalization_provider.h"
+#include "chrome/browser/ash/app_list/search/search_controller.h"
+#include "chrome/browser/ash/app_list/search/search_controller_impl.h"
+#include "chrome/browser/ash/app_list/search/search_features.h"
 #include "chrome/browser/ash/arc/arc_util.h"
 #include "chrome/browser/ash/crosapi/browser_util.h"
 #include "chrome/browser/ash/crosapi/crosapi_manager.h"
@@ -30,17 +41,6 @@
 #include "chrome/browser/ash/web_applications/personalization_app/personalization_app_manager_factory.h"
 #include "chrome/browser/ash/web_applications/personalization_app/personalization_app_utils.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/app_list/search/app_search_provider.h"
-#include "chrome/browser/ui/app_list/search/app_zero_state_provider.h"
-#include "chrome/browser/ui/app_list/search/assistant_text_search_provider.h"
-#include "chrome/browser/ui/app_list/search/help_app_provider.h"
-#include "chrome/browser/ui/app_list/search/help_app_zero_state_provider.h"
-#include "chrome/browser/ui/app_list/search/keyboard_shortcut_provider.h"
-#include "chrome/browser/ui/app_list/search/os_settings_provider.h"
-#include "chrome/browser/ui/app_list/search/personalization_provider.h"
-#include "chrome/browser/ui/app_list/search/search_controller.h"
-#include "chrome/browser/ui/app_list/search/search_controller_impl.h"
-#include "chrome/browser/ui/app_list/search/search_features.h"
 #include "chrome/browser/ui/webui/settings/ash/os_settings_manager.h"
 #include "chrome/browser/ui/webui/settings/ash/os_settings_manager_factory.h"
 #include "components/session_manager/core/session_manager.h"
diff --git a/chrome/browser/ui/app_list/search/search_controller_factory.h b/chrome/browser/ash/app_list/search/search_controller_factory.h
similarity index 74%
rename from chrome/browser/ui/app_list/search/search_controller_factory.h
rename to chrome/browser/ash/app_list/search/search_controller_factory.h
index 9182963..67e71fe2 100644
--- a/chrome/browser/ui/app_list/search/search_controller_factory.h
+++ b/chrome/browser/ash/app_list/search/search_controller_factory.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_UI_APP_LIST_SEARCH_SEARCH_CONTROLLER_FACTORY_H_
-#define CHROME_BROWSER_UI_APP_LIST_SEARCH_SEARCH_CONTROLLER_FACTORY_H_
+#ifndef CHROME_BROWSER_ASH_APP_LIST_SEARCH_SEARCH_CONTROLLER_FACTORY_H_
+#define CHROME_BROWSER_ASH_APP_LIST_SEARCH_SEARCH_CONTROLLER_FACTORY_H_
 
 #include <memory>
 
@@ -29,4 +29,4 @@
 
 }  // namespace app_list
 
-#endif  // CHROME_BROWSER_UI_APP_LIST_SEARCH_SEARCH_CONTROLLER_FACTORY_H_
+#endif  // CHROME_BROWSER_ASH_APP_LIST_SEARCH_SEARCH_CONTROLLER_FACTORY_H_
diff --git a/chrome/browser/ui/app_list/search/search_controller_impl.cc b/chrome/browser/ash/app_list/search/search_controller_impl.cc
similarity index 97%
rename from chrome/browser/ui/app_list/search/search_controller_impl.cc
rename to chrome/browser/ash/app_list/search/search_controller_impl.cc
index 40ded5d5..c9cdb87 100644
--- a/chrome/browser/ui/app_list/search/search_controller_impl.cc
+++ b/chrome/browser/ash/app_list/search/search_controller_impl.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/ui/app_list/search/search_controller_impl.h"
+#include "chrome/browser/ash/app_list/search/search_controller_impl.h"
 
 #include <algorithm>
 
@@ -16,18 +16,18 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/time/default_clock.h"
 #include "base/time/time.h"
+#include "chrome/browser/ash/app_list/search/app_search_data_source.h"
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
 #include "chrome/browser/ash/app_list/search/common/string_util.h"
 #include "chrome/browser/ash/app_list/search/cros_action_history/cros_action_recorder.h"
 #include "chrome/browser/ash/app_list/search/ranking/ranker_manager.h"
 #include "chrome/browser/ash/app_list/search/ranking/scoring.h"
+#include "chrome/browser/ash/app_list/search/search_metrics_manager.h"
+#include "chrome/browser/ash/app_list/search/search_provider.h"
+#include "chrome/browser/ash/app_list/search/search_session_metrics_manager.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/app_list/app_list_controller_delegate.h"
 #include "chrome/browser/ui/app_list/app_list_model_updater.h"
-#include "chrome/browser/ui/app_list/search/app_search_data_source.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
-#include "chrome/browser/ui/app_list/search/search_metrics_manager.h"
-#include "chrome/browser/ui/app_list/search/search_provider.h"
-#include "chrome/browser/ui/app_list/search/search_session_metrics_manager.h"
 #include "components/metrics/structured/structured_events.h"
 #include "components/prefs/pref_service.h"
 #include "content/public/browser/browser_task_traits.h"
diff --git a/chrome/browser/ui/app_list/search/search_controller_impl.h b/chrome/browser/ash/app_list/search/search_controller_impl.h
similarity index 93%
rename from chrome/browser/ui/app_list/search/search_controller_impl.h
rename to chrome/browser/ash/app_list/search/search_controller_impl.h
index b35ef5b..6766a003 100644
--- a/chrome/browser/ui/app_list/search/search_controller_impl.h
+++ b/chrome/browser/ash/app_list/search/search_controller_impl.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_APP_LIST_SEARCH_SEARCH_CONTROLLER_IMPL_H_
-#define CHROME_BROWSER_UI_APP_LIST_SEARCH_SEARCH_CONTROLLER_IMPL_H_
+#ifndef CHROME_BROWSER_ASH_APP_LIST_SEARCH_SEARCH_CONTROLLER_IMPL_H_
+#define CHROME_BROWSER_ASH_APP_LIST_SEARCH_SEARCH_CONTROLLER_IMPL_H_
 
 #include <stddef.h>
 
@@ -16,10 +16,10 @@
 #include "base/observer_list.h"
 #include "base/time/time.h"
 #include "base/timer/timer.h"
+#include "chrome/browser/ash/app_list/search/burnin_controller.h"
 #include "chrome/browser/ash/app_list/search/ranking/launch_data.h"
 #include "chrome/browser/ash/app_list/search/ranking/ranker_manager.h"
-#include "chrome/browser/ui/app_list/search/burnin_controller.h"
-#include "chrome/browser/ui/app_list/search/search_controller.h"
+#include "chrome/browser/ash/app_list/search/search_controller.h"
 
 class AppListControllerDelegate;
 class AppListModelUpdater;
@@ -160,4 +160,4 @@
 
 }  // namespace app_list
 
-#endif  // CHROME_BROWSER_UI_APP_LIST_SEARCH_SEARCH_CONTROLLER_IMPL_H_
+#endif  // CHROME_BROWSER_ASH_APP_LIST_SEARCH_SEARCH_CONTROLLER_IMPL_H_
diff --git a/chrome/browser/ui/app_list/search/search_controller_impl_unittest.cc b/chrome/browser/ash/app_list/search/search_controller_impl_unittest.cc
similarity index 98%
rename from chrome/browser/ui/app_list/search/search_controller_impl_unittest.cc
rename to chrome/browser/ash/app_list/search/search_controller_impl_unittest.cc
index eead637..a846e952 100644
--- a/chrome/browser/ui/app_list/search/search_controller_impl_unittest.cc
+++ b/chrome/browser/ash/app_list/search/search_controller_impl_unittest.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/ui/app_list/search/search_controller_impl.h"
+#include "chrome/browser/ash/app_list/search/search_controller_impl.h"
 
 #include <memory>
 #include <vector>
@@ -10,16 +10,16 @@
 #include "ash/public/cpp/app_list/app_list_types.h"
 #include "base/test/bind.h"
 #include "base/time/time.h"
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
 #include "chrome/browser/ash/app_list/search/ranking/launch_data.h"
 #include "chrome/browser/ash/app_list/search/ranking/ranker_manager.h"
+#include "chrome/browser/ash/app_list/search/search_controller.h"
+#include "chrome/browser/ash/app_list/search/search_provider.h"
 #include "chrome/browser/ash/app_list/search/test/search_controller_test_util.h"
 #include "chrome/browser/ash/app_list/search/test/test_ranker_manager.h"
 #include "chrome/browser/ash/app_list/search/test/test_search_provider.h"
 #include "chrome/browser/ash/app_list/test/fake_app_list_model_updater.h"
 #include "chrome/browser/ash/app_list/test/test_app_list_controller_delegate.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
-#include "chrome/browser/ui/app_list/search/search_controller.h"
-#include "chrome/browser/ui/app_list/search/search_provider.h"
 #include "chrome/test/base/testing_profile.h"
 #include "content/public/test/browser_task_environment.h"
 #include "testing/gmock/include/gmock/gmock.h"
diff --git a/chrome/browser/ui/app_list/search/search_features.cc b/chrome/browser/ash/app_list/search/search_features.cc
similarity index 89%
rename from chrome/browser/ui/app_list/search/search_features.cc
rename to chrome/browser/ash/app_list/search/search_features.cc
index e90f00e..de507b6 100644
--- a/chrome/browser/ui/app_list/search/search_features.cc
+++ b/chrome/browser/ash/app_list/search/search_features.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/ui/app_list/search/search_features.h"
+#include "chrome/browser/ash/app_list/search/search_features.h"
 
 #include "base/feature_list.h"
 #include "chromeos/constants/chromeos_features.h"
diff --git a/chrome/browser/ui/app_list/search/search_features.h b/chrome/browser/ash/app_list/search/search_features.h
similarity index 66%
rename from chrome/browser/ui/app_list/search/search_features.h
rename to chrome/browser/ash/app_list/search/search_features.h
index 873ee63..ff0b8c27 100644
--- a/chrome/browser/ui/app_list/search/search_features.h
+++ b/chrome/browser/ash/app_list/search/search_features.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_APP_LIST_SEARCH_SEARCH_FEATURES_H_
-#define CHROME_BROWSER_UI_APP_LIST_SEARCH_SEARCH_FEATURES_H_
+#ifndef CHROME_BROWSER_ASH_APP_LIST_SEARCH_SEARCH_FEATURES_H_
+#define CHROME_BROWSER_ASH_APP_LIST_SEARCH_SEARCH_FEATURES_H_
 
 #include "base/feature_list.h"
 
@@ -16,4 +16,4 @@
 
 }  // namespace search_features
 
-#endif  // CHROME_BROWSER_UI_APP_LIST_SEARCH_SEARCH_FEATURES_H_
+#endif  // CHROME_BROWSER_ASH_APP_LIST_SEARCH_SEARCH_FEATURES_H_
diff --git a/chrome/browser/ui/app_list/search/search_metrics_manager.cc b/chrome/browser/ash/app_list/search/search_metrics_manager.cc
similarity index 97%
rename from chrome/browser/ui/app_list/search/search_metrics_manager.cc
rename to chrome/browser/ash/app_list/search/search_metrics_manager.cc
index 39e9b8a..f3af46b 100644
--- a/chrome/browser/ui/app_list/search/search_metrics_manager.cc
+++ b/chrome/browser/ash/app_list/search/search_metrics_manager.cc
@@ -2,14 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/ui/app_list/search/search_metrics_manager.h"
+#include "chrome/browser/ash/app_list/search/search_metrics_manager.h"
 
 #include "base/metrics/histogram_functions.h"
 #include "base/numerics/safe_conversions.h"
 #include "base/strings/strcat.h"
 #include "base/strings/string_util.h"
+#include "chrome/browser/ash/app_list/search/search_metrics_util.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/app_list/search/search_metrics_util.h"
 #include "components/drive/drive_pref_names.h"
 #include "components/prefs/pref_service.h"
 
diff --git a/chrome/browser/ui/app_list/search/search_metrics_manager.h b/chrome/browser/ash/app_list/search/search_metrics_manager.h
similarity index 90%
rename from chrome/browser/ui/app_list/search/search_metrics_manager.h
rename to chrome/browser/ash/app_list/search/search_metrics_manager.h
index 4d548eb..4a03550 100644
--- a/chrome/browser/ui/app_list/search/search_metrics_manager.h
+++ b/chrome/browser/ash/app_list/search/search_metrics_manager.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_UI_APP_LIST_SEARCH_SEARCH_METRICS_MANAGER_H_
-#define CHROME_BROWSER_UI_APP_LIST_SEARCH_SEARCH_METRICS_MANAGER_H_
+#ifndef CHROME_BROWSER_ASH_APP_LIST_SEARCH_SEARCH_METRICS_MANAGER_H_
+#define CHROME_BROWSER_ASH_APP_LIST_SEARCH_SEARCH_METRICS_MANAGER_H_
 
 #include <string>
 #include <vector>
@@ -62,4 +62,4 @@
 
 }  // namespace app_list
 
-#endif  // CHROME_BROWSER_UI_APP_LIST_SEARCH_SEARCH_METRICS_MANAGER_H_
+#endif  // CHROME_BROWSER_ASH_APP_LIST_SEARCH_SEARCH_METRICS_MANAGER_H_
diff --git a/chrome/browser/ui/app_list/search/search_metrics_manager_unittest.cc b/chrome/browser/ash/app_list/search/search_metrics_manager_unittest.cc
similarity index 98%
rename from chrome/browser/ui/app_list/search/search_metrics_manager_unittest.cc
rename to chrome/browser/ash/app_list/search/search_metrics_manager_unittest.cc
index 8cffa2d..3a28f61 100644
--- a/chrome/browser/ui/app_list/search/search_metrics_manager_unittest.cc
+++ b/chrome/browser/ash/app_list/search/search_metrics_manager_unittest.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/ui/app_list/search/search_metrics_manager.h"
+#include "chrome/browser/ash/app_list/search/search_metrics_manager.h"
 
 #include <memory>
 
@@ -10,7 +10,7 @@
 #include "ash/public/cpp/app_list/app_list_notifier.h"
 #include "base/strings/strcat.h"
 #include "base/test/metrics/histogram_tester.h"
-#include "chrome/browser/ui/app_list/search/search_metrics_util.h"
+#include "chrome/browser/ash/app_list/search/search_metrics_util.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace app_list::test {
diff --git a/chrome/browser/ui/app_list/search/search_metrics_util.cc b/chrome/browser/ash/app_list/search/search_metrics_util.cc
similarity index 85%
rename from chrome/browser/ui/app_list/search/search_metrics_util.cc
rename to chrome/browser/ash/app_list/search/search_metrics_util.cc
index 08d55e90..63a4738d8 100644
--- a/chrome/browser/ui/app_list/search/search_metrics_util.cc
+++ b/chrome/browser/ash/app_list/search/search_metrics_util.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/ui/app_list/search/search_metrics_util.h"
+#include "chrome/browser/ash/app_list/search/search_metrics_util.h"
 
 #include "base/metrics/histogram_functions.h"
 #include "base/strings/strcat.h"
diff --git a/chrome/browser/ui/app_list/search/search_metrics_util.h b/chrome/browser/ash/app_list/search/search_metrics_util.h
similarity index 75%
rename from chrome/browser/ui/app_list/search/search_metrics_util.h
rename to chrome/browser/ash/app_list/search/search_metrics_util.h
index 1c092f9..7161f74c 100644
--- a/chrome/browser/ui/app_list/search/search_metrics_util.h
+++ b/chrome/browser/ash/app_list/search/search_metrics_util.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_APP_LIST_SEARCH_SEARCH_METRICS_UTIL_H_
-#define CHROME_BROWSER_UI_APP_LIST_SEARCH_SEARCH_METRICS_UTIL_H_
+#ifndef CHROME_BROWSER_ASH_APP_LIST_SEARCH_SEARCH_METRICS_UTIL_H_
+#define CHROME_BROWSER_ASH_APP_LIST_SEARCH_SEARCH_METRICS_UTIL_H_
 
 namespace app_list {
 
@@ -24,4 +24,4 @@
 
 }  // namespace app_list
 
-#endif  // CHROME_BROWSER_UI_APP_LIST_SEARCH_SEARCH_METRICS_UTIL_H_
+#endif  // CHROME_BROWSER_ASH_APP_LIST_SEARCH_SEARCH_METRICS_UTIL_H_
diff --git a/chrome/browser/ui/app_list/search/search_provider.cc b/chrome/browser/ash/app_list/search/search_provider.cc
similarity index 70%
rename from chrome/browser/ui/app_list/search/search_provider.cc
rename to chrome/browser/ash/app_list/search/search_provider.cc
index c8dd531..3a556f4 100644
--- a/chrome/browser/ui/app_list/search/search_provider.cc
+++ b/chrome/browser/ash/app_list/search/search_provider.cc
@@ -2,12 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/ui/app_list/search/search_provider.h"
+#include "chrome/browser/ash/app_list/search/search_provider.h"
 
 #include <utility>
 
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
-#include "chrome/browser/ui/app_list/search/search_controller.h"
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
+#include "chrome/browser/ash/app_list/search/search_controller.h"
 
 namespace app_list {
 
diff --git a/chrome/browser/ui/app_list/search/search_provider.h b/chrome/browser/ash/app_list/search/search_provider.h
similarity index 91%
rename from chrome/browser/ui/app_list/search/search_provider.h
rename to chrome/browser/ash/app_list/search/search_provider.h
index 8e089f3d..e18f49d 100644
--- a/chrome/browser/ui/app_list/search/search_provider.h
+++ b/chrome/browser/ash/app_list/search/search_provider.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_APP_LIST_SEARCH_SEARCH_PROVIDER_H_
-#define CHROME_BROWSER_UI_APP_LIST_SEARCH_SEARCH_PROVIDER_H_
+#ifndef CHROME_BROWSER_ASH_APP_LIST_SEARCH_SEARCH_PROVIDER_H_
+#define CHROME_BROWSER_ASH_APP_LIST_SEARCH_SEARCH_PROVIDER_H_
 
 #include <memory>
 #include <string>
@@ -76,4 +76,4 @@
 
 }  // namespace app_list
 
-#endif  // CHROME_BROWSER_UI_APP_LIST_SEARCH_SEARCH_PROVIDER_H_
+#endif  // CHROME_BROWSER_ASH_APP_LIST_SEARCH_SEARCH_PROVIDER_H_
diff --git a/chrome/browser/ui/app_list/search/search_session_metrics_manager.cc b/chrome/browser/ash/app_list/search/search_session_metrics_manager.cc
similarity index 91%
rename from chrome/browser/ui/app_list/search/search_session_metrics_manager.cc
rename to chrome/browser/ash/app_list/search/search_session_metrics_manager.cc
index 9c7f5b5..9435f055 100644
--- a/chrome/browser/ui/app_list/search/search_session_metrics_manager.cc
+++ b/chrome/browser/ash/app_list/search/search_session_metrics_manager.cc
@@ -2,9 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/ui/app_list/search/search_session_metrics_manager.h"
+#include "chrome/browser/ash/app_list/search/search_session_metrics_manager.h"
 
-#include "chrome/browser/ui/app_list/search/search_metrics_util.h"
+#include "chrome/browser/ash/app_list/search/search_metrics_util.h"
 
 namespace app_list {
 namespace {
diff --git a/chrome/browser/ui/app_list/search/search_session_metrics_manager.h b/chrome/browser/ash/app_list/search/search_session_metrics_manager.h
similarity index 88%
rename from chrome/browser/ui/app_list/search/search_session_metrics_manager.h
rename to chrome/browser/ash/app_list/search/search_session_metrics_manager.h
index 72c2428..3565c857 100644
--- a/chrome/browser/ui/app_list/search/search_session_metrics_manager.h
+++ b/chrome/browser/ash/app_list/search/search_session_metrics_manager.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_UI_APP_LIST_SEARCH_SEARCH_SESSION_METRICS_MANAGER_H_
-#define CHROME_BROWSER_UI_APP_LIST_SEARCH_SEARCH_SESSION_METRICS_MANAGER_H_
+#ifndef CHROME_BROWSER_ASH_APP_LIST_SEARCH_SEARCH_SESSION_METRICS_MANAGER_H_
+#define CHROME_BROWSER_ASH_APP_LIST_SEARCH_SEARCH_SESSION_METRICS_MANAGER_H_
 
 #include <string>
 #include <vector>
@@ -55,4 +55,4 @@
 
 }  // namespace app_list
 
-#endif  // CHROME_BROWSER_UI_APP_LIST_SEARCH_SEARCH_SESSION_METRICS_MANAGER_H_
+#endif  // CHROME_BROWSER_ASH_APP_LIST_SEARCH_SEARCH_SESSION_METRICS_MANAGER_H_
diff --git a/chrome/browser/ash/app_list/search/test/app_list_search_test_helper.cc b/chrome/browser/ash/app_list/search/test/app_list_search_test_helper.cc
index 8b2bf9f..6938492 100644
--- a/chrome/browser/ash/app_list/search/test/app_list_search_test_helper.cc
+++ b/chrome/browser/ash/app_list/search/test/app_list_search_test_helper.cc
@@ -5,8 +5,8 @@
 #include "chrome/browser/ash/app_list/search/test/app_list_search_test_helper.h"
 
 #include "base/run_loop.h"
+#include "chrome/browser/ash/app_list/search/search_controller.h"
 #include "chrome/browser/ash/app_list/search/test/search_results_changed_waiter.h"
-#include "chrome/browser/ui/app_list/search/search_controller.h"
 #include "chrome/browser/ui/browser.h"
 
 namespace app_list {
diff --git a/chrome/browser/ash/app_list/search/test/app_list_search_test_helper.h b/chrome/browser/ash/app_list/search/test/app_list_search_test_helper.h
index ebf54d1b6..cba9c85 100644
--- a/chrome/browser/ash/app_list/search/test/app_list_search_test_helper.h
+++ b/chrome/browser/ash/app_list/search/test/app_list_search_test_helper.h
@@ -21,14 +21,14 @@
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/threading/thread_restrictions.h"
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
+#include "chrome/browser/ash/app_list/search/search_controller.h"
 #include "chrome/browser/ash/app_list/test/chrome_app_list_test_support.h"
 #include "chrome/browser/ash/file_manager/path_util.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/ui/app_list/app_list_client_impl.h"
 #include "chrome/browser/ui/app_list/app_list_model_updater.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
-#include "chrome/browser/ui/app_list/search/search_controller.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/common/pref_names.h"
diff --git a/chrome/browser/ash/app_list/search/test/ranking_test_util.h b/chrome/browser/ash/app_list/search/test/ranking_test_util.h
index 993a9838..1bc01c5b 100644
--- a/chrome/browser/ash/app_list/search/test/ranking_test_util.h
+++ b/chrome/browser/ash/app_list/search/test/ranking_test_util.h
@@ -9,7 +9,7 @@
 #include "base/files/scoped_temp_dir.h"
 #include "base/test/task_environment.h"
 #include "chrome/browser/ash/app_list/search/ranking/ranker.h"
-#include "chrome/browser/ui/app_list/search/types.h"
+#include "chrome/browser/ash/app_list/search/types.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
diff --git a/chrome/browser/ash/app_list/search/test/search_controller_test_util.cc b/chrome/browser/ash/app_list/search/test/search_controller_test_util.cc
index 711cfdb3..a743906d 100644
--- a/chrome/browser/ash/app_list/search/test/search_controller_test_util.cc
+++ b/chrome/browser/ash/app_list/search/test/search_controller_test_util.cc
@@ -9,10 +9,10 @@
 
 #include "ash/public/cpp/app_list/app_list_types.h"
 #include "base/time/time.h"
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
+#include "chrome/browser/ash/app_list/search/search_controller.h"
+#include "chrome/browser/ash/app_list/search/search_provider.h"
 #include "chrome/browser/ash/app_list/search/test/test_result.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
-#include "chrome/browser/ui/app_list/search/search_controller.h"
-#include "chrome/browser/ui/app_list/search/search_provider.h"
 #include "test_search_provider.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
diff --git a/chrome/browser/ash/app_list/search/test/search_controller_test_util.h b/chrome/browser/ash/app_list/search/test/search_controller_test_util.h
index ca8dd24..7815e3c1 100644
--- a/chrome/browser/ash/app_list/search/test/search_controller_test_util.h
+++ b/chrome/browser/ash/app_list/search/test/search_controller_test_util.h
@@ -9,9 +9,9 @@
 #include <vector>
 
 #include "ash/public/cpp/app_list/app_list_types.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
-#include "chrome/browser/ui/app_list/search/search_controller.h"
-#include "chrome/browser/ui/app_list/search/search_provider.h"
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
+#include "chrome/browser/ash/app_list/search/search_controller.h"
+#include "chrome/browser/ash/app_list/search/search_provider.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
 namespace app_list {
diff --git a/chrome/browser/ash/app_list/search/test/search_results_changed_waiter.cc b/chrome/browser/ash/app_list/search/test/search_results_changed_waiter.cc
index 615a2a45..497ee88d 100644
--- a/chrome/browser/ash/app_list/search/test/search_results_changed_waiter.cc
+++ b/chrome/browser/ash/app_list/search/test/search_results_changed_waiter.cc
@@ -6,7 +6,7 @@
 
 #include "base/bind.h"
 #include "base/callback_helpers.h"
-#include "chrome/browser/ui/app_list/search/search_controller.h"
+#include "chrome/browser/ash/app_list/search/search_controller.h"
 
 namespace app_list {
 
diff --git a/chrome/browser/ash/app_list/search/test/test_continue_files_search_provider.cc b/chrome/browser/ash/app_list/search/test/test_continue_files_search_provider.cc
index 2f63561b..beb8250 100644
--- a/chrome/browser/ash/app_list/search/test/test_continue_files_search_provider.cc
+++ b/chrome/browser/ash/app_list/search/test/test_continue_files_search_provider.cc
@@ -14,7 +14,7 @@
 #include "base/check.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
 
 namespace app_list {
 
diff --git a/chrome/browser/ash/app_list/search/test/test_continue_files_search_provider.h b/chrome/browser/ash/app_list/search/test/test_continue_files_search_provider.h
index 78b25f6..5dfa5c09 100644
--- a/chrome/browser/ash/app_list/search/test/test_continue_files_search_provider.h
+++ b/chrome/browser/ash/app_list/search/test/test_continue_files_search_provider.h
@@ -5,7 +5,7 @@
 #ifndef CHROME_BROWSER_ASH_APP_LIST_SEARCH_TEST_TEST_CONTINUE_FILES_SEARCH_PROVIDER_H_
 #define CHROME_BROWSER_ASH_APP_LIST_SEARCH_TEST_TEST_CONTINUE_FILES_SEARCH_PROVIDER_H_
 
-#include "chrome/browser/ui/app_list/search/search_provider.h"
+#include "chrome/browser/ash/app_list/search/search_provider.h"
 
 namespace app_list {
 
diff --git a/chrome/browser/ash/app_list/search/test/test_ranker_manager.cc b/chrome/browser/ash/app_list/search/test/test_ranker_manager.cc
index dbe622da..cb06281 100644
--- a/chrome/browser/ash/app_list/search/test/test_ranker_manager.cc
+++ b/chrome/browser/ash/app_list/search/test/test_ranker_manager.cc
@@ -8,7 +8,7 @@
 #include <vector>
 
 #include "ash/public/cpp/app_list/app_list_types.h"
-#include "chrome/browser/ui/app_list/search/search_controller.h"
+#include "chrome/browser/ash/app_list/search/search_controller.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
 namespace app_list {
diff --git a/chrome/browser/ash/app_list/search/test/test_ranker_manager.h b/chrome/browser/ash/app_list/search/test/test_ranker_manager.h
index 5b5f060..91e9ecb5 100644
--- a/chrome/browser/ash/app_list/search/test/test_ranker_manager.h
+++ b/chrome/browser/ash/app_list/search/test/test_ranker_manager.h
@@ -8,9 +8,9 @@
 #include <memory>
 #include <vector>
 
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
 #include "chrome/browser/ash/app_list/search/ranking/ranker_manager.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
-#include "chrome/browser/ui/app_list/search/search_controller.h"
+#include "chrome/browser/ash/app_list/search/search_controller.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
 namespace app_list {
diff --git a/chrome/browser/ash/app_list/search/test/test_result.h b/chrome/browser/ash/app_list/search/test/test_result.h
index 265052a..3411b386 100644
--- a/chrome/browser/ash/app_list/search/test/test_result.h
+++ b/chrome/browser/ash/app_list/search/test/test_result.h
@@ -6,7 +6,7 @@
 #define CHROME_BROWSER_ASH_APP_LIST_SEARCH_TEST_TEST_RESULT_H_
 
 #include "base/test/task_environment.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
 
 namespace app_list {
 
diff --git a/chrome/browser/ash/app_list/search/test/test_search_controller.cc b/chrome/browser/ash/app_list/search/test/test_search_controller.cc
index 4db89e10..516f6ca 100644
--- a/chrome/browser/ash/app_list/search/test/test_search_controller.cc
+++ b/chrome/browser/ash/app_list/search/test/test_search_controller.cc
@@ -6,7 +6,7 @@
 
 #include "ash/public/cpp/app_list/app_list_features.h"
 #include "ash/public/cpp/app_list/app_list_types.h"
-#include "chrome/browser/ui/app_list/search/search_provider.h"
+#include "chrome/browser/ash/app_list/search/search_provider.h"
 
 namespace app_list {
 
diff --git a/chrome/browser/ash/app_list/search/test/test_search_controller.h b/chrome/browser/ash/app_list/search/test/test_search_controller.h
index 2a67dfc..e15c558 100644
--- a/chrome/browser/ash/app_list/search/test/test_search_controller.h
+++ b/chrome/browser/ash/app_list/search/test/test_search_controller.h
@@ -5,8 +5,8 @@
 #ifndef CHROME_BROWSER_ASH_APP_LIST_SEARCH_TEST_TEST_SEARCH_CONTROLLER_H_
 #define CHROME_BROWSER_ASH_APP_LIST_SEARCH_TEST_TEST_SEARCH_CONTROLLER_H_
 
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
-#include "chrome/browser/ui/app_list/search/search_controller.h"
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
+#include "chrome/browser/ash/app_list/search/search_controller.h"
 
 class ChromeSearchResult;
 
diff --git a/chrome/browser/ash/app_list/search/test/test_search_provider.cc b/chrome/browser/ash/app_list/search/test/test_search_provider.cc
index 4272b7f0..13f1ab9 100644
--- a/chrome/browser/ash/app_list/search/test/test_search_provider.cc
+++ b/chrome/browser/ash/app_list/search/test/test_search_provider.cc
@@ -10,9 +10,9 @@
 #include "ash/public/cpp/app_list/app_list_types.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/time/time.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
-#include "chrome/browser/ui/app_list/search/search_controller.h"
-#include "chrome/browser/ui/app_list/search/search_provider.h"
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
+#include "chrome/browser/ash/app_list/search/search_controller.h"
+#include "chrome/browser/ash/app_list/search/search_provider.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
 namespace app_list {
diff --git a/chrome/browser/ash/app_list/search/test/test_search_provider.h b/chrome/browser/ash/app_list/search/test/test_search_provider.h
index f931dbf..bf973bd3 100644
--- a/chrome/browser/ash/app_list/search/test/test_search_provider.h
+++ b/chrome/browser/ash/app_list/search/test/test_search_provider.h
@@ -10,9 +10,9 @@
 
 #include "ash/public/cpp/app_list/app_list_types.h"
 #include "base/time/time.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
-#include "chrome/browser/ui/app_list/search/search_controller.h"
-#include "chrome/browser/ui/app_list/search/search_provider.h"
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
+#include "chrome/browser/ash/app_list/search/search_controller.h"
+#include "chrome/browser/ash/app_list/search/search_provider.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
 namespace app_list {
diff --git a/chrome/browser/ui/app_list/search/types.cc b/chrome/browser/ash/app_list/search/types.cc
similarity index 96%
rename from chrome/browser/ui/app_list/search/types.cc
rename to chrome/browser/ash/app_list/search/types.cc
index 5440087..5e7ac1c 100644
--- a/chrome/browser/ui/app_list/search/types.cc
+++ b/chrome/browser/ash/app_list/search/types.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/ui/app_list/search/types.h"
+#include "chrome/browser/ash/app_list/search/types.h"
 
 #include <stddef.h>
 
diff --git a/chrome/browser/ui/app_list/search/types.h b/chrome/browser/ash/app_list/search/types.h
similarity index 95%
rename from chrome/browser/ui/app_list/search/types.h
rename to chrome/browser/ash/app_list/search/types.h
index de666cf..abf2ec3 100644
--- a/chrome/browser/ui/app_list/search/types.h
+++ b/chrome/browser/ash/app_list/search/types.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_APP_LIST_SEARCH_TYPES_H_
-#define CHROME_BROWSER_UI_APP_LIST_SEARCH_TYPES_H_
+#ifndef CHROME_BROWSER_ASH_APP_LIST_SEARCH_TYPES_H_
+#define CHROME_BROWSER_ASH_APP_LIST_SEARCH_TYPES_H_
 
 #include "ash/public/cpp/app_list/app_list_types.h"
 #include "base/files/file_path.h"
@@ -91,4 +91,4 @@
 
 }  // namespace app_list
 
-#endif  // CHROME_BROWSER_UI_APP_LIST_SEARCH_TYPES_H_
+#endif  // CHROME_BROWSER_ASH_APP_LIST_SEARCH_TYPES_H_
diff --git a/chrome/browser/ash/app_list/test/fake_app_list_model_updater.h b/chrome/browser/ash/app_list/test/fake_app_list_model_updater.h
index 57d66a9..41126b8 100644
--- a/chrome/browser/ash/app_list/test/fake_app_list_model_updater.h
+++ b/chrome/browser/ash/app_list/test/fake_app_list_model_updater.h
@@ -11,8 +11,8 @@
 #include <vector>
 
 #include "base/observer_list.h"
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
 #include "chrome/browser/ui/app_list/app_list_model_updater.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
 
 class ChromeAppListItem;
 
diff --git a/chrome/browser/ash/app_mode/app_session_ash.cc b/chrome/browser/ash/app_mode/app_session_ash.cc
index cb1488c..ec50de3 100644
--- a/chrome/browser/ash/app_mode/app_session_ash.cc
+++ b/chrome/browser/ash/app_mode/app_session_ash.cc
@@ -16,6 +16,7 @@
 #include "chrome/browser/browser_process_platform_part.h"
 #include "chrome/common/pref_names.h"
 #include "components/prefs/pref_service.h"
+#include "extensions/common/manifest_handlers/offline_enabled_info.h"
 
 namespace ash {
 
@@ -29,13 +30,21 @@
     accessibility_controller->ShowFloatingMenuIfEnabled();
 }
 
+bool IsOfflineEnabledForApp(const std::string& app_id, Profile* profile) {
+  const extensions::Extension* primary_app =
+      extensions::ExtensionRegistry::Get(profile)->GetInstalledExtension(
+          app_id);
+  return extensions::OfflineEnabledInfo::IsOfflineEnabled(primary_app);
+}
+
 }  // namespace
 
 AppSessionAsh::AppSessionAsh(Profile* profile)
     : AppSession(profile),
       network_metrics_service_(
           std::make_unique<NetworkConnectivityMetricsService>()),
-      periodic_metrics_service_(std::make_unique<PeriodicMetricsService>()) {}
+      periodic_metrics_service_(std::make_unique<PeriodicMetricsService>(
+          g_browser_process->local_state())) {}
 
 AppSessionAsh::~AppSessionAsh() = default;
 
@@ -44,14 +53,21 @@
   StartFloatingAccessibilityMenu();
   InitKioskAppUpdateService(app_id);
   SetRebootAfterUpdateIfNecessary();
-  periodic_metrics_service_->StartRecordingPeriodicMetrics();
+
+  periodic_metrics_service_->RecordPreviousSessionMetrics();
+  periodic_metrics_service_->StartRecordingPeriodicMetrics(
+      IsOfflineEnabledForApp(app_id, profile()));
 }
 
 void AppSessionAsh::InitForWebKiosk(
     const absl::optional<std::string>& web_app_name) {
   chromeos::AppSession::InitForWebKiosk(web_app_name);
   StartFloatingAccessibilityMenu();
-  periodic_metrics_service_->StartRecordingPeriodicMetrics();
+
+  periodic_metrics_service_->RecordPreviousSessionMetrics();
+  // Web apps always support offline mode.
+  periodic_metrics_service_->StartRecordingPeriodicMetrics(
+      /*is_offline_enabled=*/true);
 }
 
 void AppSessionAsh::ShuttingDown() {
diff --git a/chrome/browser/ash/app_mode/kiosk_app_data.cc b/chrome/browser/ash/app_mode/kiosk_app_data.cc
index aec2dc88..e0a2e97 100644
--- a/chrome/browser/ash/app_mode/kiosk_app_data.cc
+++ b/chrome/browser/ash/app_mode/kiosk_app_data.cc
@@ -13,7 +13,6 @@
 #include "base/logging.h"
 #include "base/memory/ref_counted_memory.h"
 #include "base/task/thread_pool.h"
-#include "base/values.h"
 #include "chrome/browser/ash/app_mode/kiosk_app_data_delegate.h"
 #include "chrome/browser/ash/app_mode/kiosk_app_manager.h"
 #include "chrome/browser/browser_process.h"
@@ -57,7 +56,7 @@
       .value_or(false);
 }
 
-std::string ValueToString(const base::Value& value) {
+std::string ValueToString(base::ValueView value) {
   std::string json;
   base::JSONWriter::Write(value, &json);
   return json;
@@ -481,43 +480,40 @@
 
 void KioskAppData::OnWebstoreResponseParseSuccess(
     const std::string& extension_id,
-    std::unique_ptr<base::DictionaryValue> webstore_data) {
-  const std::string* id = webstore_data->GetDict().FindString(kIdKey);
+    const base::Value::Dict& webstore_data) {
+  const std::string* id = webstore_data.FindString(kIdKey);
   if (!id) {
     LOG(ERROR) << "Webstore response error (" << kIdKey
-               << "): " << ValueToString(*webstore_data.get());
+               << "): " << ValueToString(webstore_data);
     OnWebstoreResponseParseFailure(extension_id, kInvalidWebstoreResponseError);
     return;
   }
   if (extension_id != *id) {
     LOG(ERROR) << "Webstore response error (" << kIdKey
-               << "): " << ValueToString(*webstore_data.get());
+               << "): " << ValueToString(webstore_data);
     LOG(ERROR) << "Received extension id " << *id
                << " does not equal expected extension id " << extension_id;
     OnWebstoreResponseParseFailure(extension_id, kInvalidWebstoreResponseError);
     return;
   }
-  // Takes ownership of |webstore_data|.
   webstore_fetcher_.reset();
 
   std::string manifest;
-  if (!CheckResponseKeyValue(*id, webstore_data.get(), kManifestKey, &manifest))
+  if (!CheckResponseKeyValue(*id, webstore_data, kManifestKey, &manifest))
     return;
 
-  if (!CheckResponseKeyValue(*id, webstore_data.get(), kLocalizedNameKey,
-                             &name_))
+  if (!CheckResponseKeyValue(*id, webstore_data, kLocalizedNameKey, &name_))
     return;
 
   std::string icon_url_string;
-  if (!CheckResponseKeyValue(*id, webstore_data.get(), kIconUrlKey,
-                             &icon_url_string))
+  if (!CheckResponseKeyValue(*id, webstore_data, kIconUrlKey, &icon_url_string))
     return;
 
   GURL icon_url =
       extension_urls::GetWebstoreLaunchURL().Resolve(icon_url_string);
   if (!icon_url.is_valid()) {
     LOG(ERROR) << "Webstore response error (icon url): "
-               << ValueToString(*webstore_data);
+               << ValueToString(webstore_data);
     OnWebstoreResponseParseFailure(extension_id, kInvalidWebstoreResponseError);
     return;
   }
@@ -536,13 +532,13 @@
 }
 
 bool KioskAppData::CheckResponseKeyValue(const std::string& extension_id,
-                                         const base::DictionaryValue* response,
+                                         const base::Value::Dict& response,
                                          const char* key,
                                          std::string* value) {
-  const std::string* value_ptr = response->FindStringKey(key);
+  const std::string* value_ptr = response.FindString(key);
   if (!value_ptr) {
     LOG(ERROR) << "Webstore response error (" << key
-               << "): " << ValueToString(*response);
+               << "): " << ValueToString(response);
     OnWebstoreResponseParseFailure(extension_id, kInvalidWebstoreResponseError);
     return false;
   }
diff --git a/chrome/browser/ash/app_mode/kiosk_app_data.h b/chrome/browser/ash/app_mode/kiosk_app_data.h
index 2394a10f..d6caa559 100644
--- a/chrome/browser/ash/app_mode/kiosk_app_data.h
+++ b/chrome/browser/ash/app_mode/kiosk_app_data.h
@@ -10,6 +10,7 @@
 
 #include "base/files/file_path.h"
 #include "base/memory/weak_ptr.h"
+#include "base/values.h"
 #include "chrome/browser/ash/app_mode/kiosk_app_data_base.h"
 #include "chrome/browser/extensions/webstore_data_fetcher_delegate.h"
 #include "components/account_id/account_id.h"
@@ -129,7 +130,7 @@
   void OnWebstoreRequestFailure(const std::string& extension_id) override;
   void OnWebstoreResponseParseSuccess(
       const std::string& extension_id,
-      std::unique_ptr<base::DictionaryValue> webstore_data) override;
+      const base::Value::Dict& webstore_data) override;
   void OnWebstoreResponseParseFailure(const std::string& extension_id,
                                       const std::string& error) override;
 
@@ -137,7 +138,7 @@
   // |response|. Passes |key|'s content via |value| and returns
   // true when |key| is present.
   bool CheckResponseKeyValue(const std::string& extension_id,
-                             const base::DictionaryValue* response,
+                             const base::Value::Dict& response,
                              const char* key,
                              std::string* value);
 
diff --git a/chrome/browser/ash/app_mode/metrics/periodic_metrics_service.cc b/chrome/browser/ash/app_mode/metrics/periodic_metrics_service.cc
index f688193..e3d9b3f 100644
--- a/chrome/browser/ash/app_mode/metrics/periodic_metrics_service.cc
+++ b/chrome/browser/ash/app_mode/metrics/periodic_metrics_service.cc
@@ -11,6 +11,12 @@
 #include "base/process/process_metrics.h"
 #include "base/system/sys_info.h"
 #include "base/task/thread_pool.h"
+#include "chrome/common/pref_names.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 "components/prefs/pref_service.h"
+#include "components/prefs/scoped_user_pref_update.h"
 
 namespace ash {
 
@@ -28,12 +34,38 @@
   base::UmaHistogramPercentage(histogram_name, percents);
 }
 
+bool IsDeviceOnline() {
+  const NetworkState* default_network =
+      NetworkHandler::Get()->network_state_handler()->DefaultNetwork();
+  if (!default_network) {
+    // No connected network.
+    return false;
+  }
+  return default_network->IsOnline();
+}
+
+absl::optional<KioskInternetAccessInfo> GetSavedInternetAccessInfo(
+    const PrefService* prefs) {
+  const base::Value::Dict& metrics_dict = prefs->GetDict(prefs::kKioskMetrics);
+  const auto* internet_access_info_value =
+      metrics_dict.Find(kKioskInternetAccessInfo);
+  if (!internet_access_info_value)
+    return absl::nullopt;
+  auto internet_access_info = internet_access_info_value->GetIfInt();
+  if (!internet_access_info.has_value())
+    return absl::nullopt;
+  return static_cast<KioskInternetAccessInfo>(internet_access_info.value());
+}
+
 }  // namespace
 
 const char kKioskRamUsagePercentageHistogram[] = "Kiosk.RamUsagePercentage";
 const char kKioskSwapUsagePercentageHistogram[] = "Kiosk.SwapUsagePercentage";
 const char kKioskDiskUsagePercentageHistogram[] = "Kiosk.DiskUsagePercentage";
 const char kKioskChromeProcessCountHistogram[] = "Kiosk.ChromeProcessCount";
+const char kKioskSessionRestartInternetAccessHistogram[] =
+    "Kiosk.SessionRestart.InternetAccess";
+const char kKioskInternetAccessInfo[] = "internet-access";
 
 const base::TimeDelta kPeriodicMetricsInterval = base::Hours(1);
 
@@ -71,12 +103,19 @@
   base::WeakPtrFactory<DiskSpaceCalculator> weak_ptr_factory_{this};
 };
 
-PeriodicMetricsService::PeriodicMetricsService()
-    : disk_space_calculator_(std::make_unique<DiskSpaceCalculator>()) {}
+PeriodicMetricsService::PeriodicMetricsService(PrefService* prefs)
+    : prefs_(prefs),
+      disk_space_calculator_(std::make_unique<DiskSpaceCalculator>()) {}
 
 PeriodicMetricsService::~PeriodicMetricsService() = default;
 
-void PeriodicMetricsService::StartRecordingPeriodicMetrics() {
+void PeriodicMetricsService::RecordPreviousSessionMetrics() const {
+  RecordPreviousInternetAccessInfo();
+}
+
+void PeriodicMetricsService::StartRecordingPeriodicMetrics(
+    bool is_offline_enabled) {
+  is_offline_enabled_ = is_offline_enabled;
   // Record all periodic metrics at the beginning of the kiosk session and then
   // every `kPeriodicMetricsInterval`.
   RecordPeriodicMetrics();
@@ -89,6 +128,7 @@
   RecordSwapUsage();
   RecordDiskSpaceUsage();
   RecordChromeProcessCount();
+  SaveInternetAccessInfo();
 }
 
 void PeriodicMetricsService::RecordRamUsage() const {
@@ -122,4 +162,30 @@
   base::UmaHistogramCounts100(kKioskChromeProcessCountHistogram, process_count);
 }
 
+void PeriodicMetricsService::RecordPreviousInternetAccessInfo() const {
+  absl::optional<KioskInternetAccessInfo>
+      previous_session_internet_access_info =
+          GetSavedInternetAccessInfo(prefs_);
+
+  if (previous_session_internet_access_info.has_value()) {
+    base::UmaHistogramEnumeration(
+        kKioskSessionRestartInternetAccessHistogram,
+        previous_session_internet_access_info.value());
+  }
+}
+
+void PeriodicMetricsService::SaveInternetAccessInfo() const {
+  bool is_device_online = IsDeviceOnline();
+  KioskInternetAccessInfo network_access_info =
+      is_device_online
+          ? (is_offline_enabled_
+                 ? KioskInternetAccessInfo::kOnlineAndAppSupportsOffline
+                 : KioskInternetAccessInfo::kOnlineAndAppRequiresInternet)
+          : (is_offline_enabled_
+                 ? KioskInternetAccessInfo::kOfflineAndAppSupportsOffline
+                 : KioskInternetAccessInfo::kOfflineAndAppRequiresInternet);
+  ScopedDictPrefUpdate(prefs_, prefs::kKioskMetrics)
+      ->Set(kKioskInternetAccessInfo, static_cast<int>(network_access_info));
+}
+
 }  // namespace ash
diff --git a/chrome/browser/ash/app_mode/metrics/periodic_metrics_service.h b/chrome/browser/ash/app_mode/metrics/periodic_metrics_service.h
index 1f7b3fa..d62acfa 100644
--- a/chrome/browser/ash/app_mode/metrics/periodic_metrics_service.h
+++ b/chrome/browser/ash/app_mode/metrics/periodic_metrics_service.h
@@ -6,6 +6,7 @@
 #define CHROME_BROWSER_ASH_APP_MODE_METRICS_PERIODIC_METRICS_SERVICE_H_
 
 #include "base/timer/timer.h"
+#include "components/prefs/pref_service.h"
 
 namespace ash {
 
@@ -13,21 +14,38 @@
 extern const char kKioskSwapUsagePercentageHistogram[];
 extern const char kKioskDiskUsagePercentageHistogram[];
 extern const char kKioskChromeProcessCountHistogram[];
+extern const char kKioskSessionRestartInternetAccessHistogram[];
+extern const char kKioskInternetAccessInfo[];
 
 extern const base::TimeDelta kPeriodicMetricsInterval;
 
+// These values are used in UMA metrics. Entries should not be renumbered and
+// numeric values should never be reused. Keep in sync with respective enum in
+// tools/metrics/histograms/enums.xml
+enum class KioskInternetAccessInfo {
+  kOnlineAndAppRequiresInternet = 0,
+  kOnlineAndAppSupportsOffline = 1,
+  kOfflineAndAppRequiresInternet = 2,
+  kOfflineAndAppSupportsOffline = 3,
+  kMaxValue = kOfflineAndAppSupportsOffline,
+};
+
 class DiskSpaceCalculator;
 
 // This class save and record kiosk UMA metrics every
 // `kPeriodicMetricsInterval`.
 class PeriodicMetricsService {
  public:
-  PeriodicMetricsService();
+  explicit PeriodicMetricsService(PrefService* prefs);
   PeriodicMetricsService(const PeriodicMetricsService&) = delete;
   PeriodicMetricsService& operator=(const PeriodicMetricsService&) = delete;
   ~PeriodicMetricsService();
 
-  void StartRecordingPeriodicMetrics();
+  // Record metrics about the previous kiosk session. Should be called in the
+  // beginning of the kiosk session and before `StartRecordingPeriodicMetrics`.
+  void RecordPreviousSessionMetrics() const;
+
+  void StartRecordingPeriodicMetrics(bool is_offline_enabled);
 
  private:
   void RecordPeriodicMetrics();
@@ -40,10 +58,21 @@
 
   void RecordChromeProcessCount() const;
 
+  void RecordPreviousInternetAccessInfo() const;
+
+  // Save the Internet access info to record
+  // `kKioskSessionRestartInternetAccessHistogram` during the next kiosk session
+  // start.
+  void SaveInternetAccessInfo() const;
+
   // Invokes callback to record continuously monitored metrics. Starts when
   // the kiosk session is started.
   base::RepeatingTimer metrics_timer_;
 
+  bool is_offline_enabled_ = true;
+
+  raw_ptr<PrefService> prefs_;
+
   const std::unique_ptr<DiskSpaceCalculator> disk_space_calculator_;
 };
 
diff --git a/chrome/browser/ash/app_mode/metrics/periodic_metrics_service_unittest.cc b/chrome/browser/ash/app_mode/metrics/periodic_metrics_service_unittest.cc
index 02133a1..657b53d 100644
--- a/chrome/browser/ash/app_mode/metrics/periodic_metrics_service_unittest.cc
+++ b/chrome/browser/ash/app_mode/metrics/periodic_metrics_service_unittest.cc
@@ -5,37 +5,98 @@
 #include "chrome/browser/ash/app_mode/metrics/periodic_metrics_service.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/task_environment.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/test/base/scoped_testing_local_state.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "chromeos/ash/components/network/network_handler_test_helper.h"
+#include "chromeos/ash/components/sync_wifi/network_test_helper.h"
 #include "content/public/test/browser_task_environment.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace ash {
 
-class PeriodicMetricsServiceTest : public testing::Test {
+namespace {
+
+struct KioskSessionInternetAccessTestCase {
+  std::string test_name;
+  bool is_first_app_offline_enabled;
+  bool is_second_app_offline_enabled;
+  bool should_make_offline;
+  KioskInternetAccessInfo expected_metric;
+};
+
+}  // namespace
+
+class PeriodicMetricsServiceTest
+    : public ::testing::TestWithParam<KioskSessionInternetAccessTestCase> {
  public:
   PeriodicMetricsServiceTest()
       : task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME),
+        local_state_(std::make_unique<ScopedTestingLocalState>(
+            TestingBrowserProcess::GetGlobal())),
+        network_handler_test_helper_(
+            std::make_unique<NetworkHandlerTestHelper>()),
+        periodic_metrics_service_(local_state()),
         histogram_tester_(std::make_unique<base::HistogramTester>()) {}
 
   PeriodicMetricsServiceTest(const PeriodicMetricsServiceTest&) = delete;
   PeriodicMetricsServiceTest& operator=(const PeriodicMetricsServiceTest&) =
       delete;
 
+  void SetUp() override { network_handler_test_helper_->AddDefaultProfiles(); }
+
+  void TearDown() override {
+    network_handler_test_helper_.reset();
+    local_state()->RemoveUserPref(prefs::kKioskMetrics);
+  }
+
   base::test::TaskEnvironment* task_environment() { return &task_environment_; }
 
+  TestingPrefServiceSimple* local_state() { return local_state_->Get(); }
+
   base::HistogramTester* histogram_tester() { return histogram_tester_.get(); }
 
-  void StartRecordingPeriodicMetrics() {
-    periodic_metrics_service.StartRecordingPeriodicMetrics();
+  void RecordPreviousSessionMetrics() {
+    periodic_metrics_service_.RecordPreviousSessionMetrics();
+  }
+
+  void StartRecordingPeriodicMetrics(bool is_offline_enabled = true) {
+    periodic_metrics_service_.StartRecordingPeriodicMetrics(is_offline_enabled);
     // Some periodic metrics are calculated asynchronously.
     task_environment_.RunUntilIdle();
   }
 
+  void MakeOffline() {
+    network_handler_test_helper_->ResetDevicesAndServices();
+  }
+
+  void EmulateKioskRestart(bool is_first_app_offline_enabled,
+                           bool is_second_app_offline_enabled) {
+    // Emulate running a kiosk session.
+    RecordPreviousSessionMetrics();
+    StartRecordingPeriodicMetrics(is_first_app_offline_enabled);
+    // Nothing was saved in prefs. That means the kiosk session started the
+    // first time. So `kKioskSessionRestartInternetAccessHistogram` should be
+    // empty.
+    histogram_tester()->ExpectTotalCount(
+        kKioskSessionRestartInternetAccessHistogram, 0);
+
+    // Emulate kiosk session restart.
+    RecordPreviousSessionMetrics();
+    StartRecordingPeriodicMetrics(is_second_app_offline_enabled);
+  }
+
  private:
-  PeriodicMetricsService periodic_metrics_service;
   content::BrowserTaskEnvironment task_environment_;
+  sync_preferences::TestingPrefServiceSyncable user_prefs_;
+  std::unique_ptr<ScopedTestingLocalState> local_state_;
+  std::unique_ptr<NetworkHandlerTestHelper> network_handler_test_helper_;
+  PeriodicMetricsService periodic_metrics_service_;
   std::unique_ptr<base::HistogramTester> histogram_tester_;
 };
 
+using InternetPeriodicMetricsServiceTest = PeriodicMetricsServiceTest;
+
 TEST_F(PeriodicMetricsServiceTest, PeriodicMetrics) {
   const char* const kPeriodicMetrics[] = {
       kKioskRamUsagePercentageHistogram, kKioskSwapUsagePercentageHistogram,
@@ -64,4 +125,48 @@
   }
 }
 
+TEST_P(InternetPeriodicMetricsServiceTest, KioskInternetMetric) {
+  const KioskSessionInternetAccessTestCase& test_config = GetParam();
+  if (test_config.should_make_offline)
+    MakeOffline();
+
+  EmulateKioskRestart(test_config.is_first_app_offline_enabled,
+                      test_config.is_second_app_offline_enabled);
+  histogram_tester()->ExpectBucketCount(
+      kKioskSessionRestartInternetAccessHistogram, test_config.expected_metric,
+      1);
+  histogram_tester()->ExpectTotalCount(
+      kKioskSessionRestartInternetAccessHistogram, 1);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    InternetAccessInfos,
+    InternetPeriodicMetricsServiceTest,
+    testing::ValuesIn<KioskSessionInternetAccessTestCase>({
+        {/*test_name=*/"OnlineAndAppSupportsOffline",
+         /*is_first_app_offline_enabled=*/true,
+         /*is_second_app_offline_enabled=*/true, /*should_make_offline=*/false,
+         /*expected_metric=*/
+         KioskInternetAccessInfo::kOnlineAndAppSupportsOffline},
+        {/*test_name=*/"OfflineAndAppSupportsOffline",
+         /*is_first_app_offline_enabled=*/true,
+         /*is_second_app_offline_enabled=*/true, /*should_make_offline=*/true,
+         /*expected_metric=*/
+         KioskInternetAccessInfo::kOfflineAndAppSupportsOffline},
+        {/*test_name=*/"OfflineAndAppRequiresInternet",
+         /*is_first_app_offline_enabled=*/false,
+         /*is_second_app_offline_enabled=*/false, /*should_make_offline=*/true,
+         /*expected_metric=*/
+         KioskInternetAccessInfo::kOfflineAndAppRequiresInternet},
+        {/*test_name=*/"DifferentSupportOfflineMode",
+         /*is_first_app_offline_enabled=*/false,
+         /*is_second_app_offline_enabled=*/true, /*should_make_offline=*/false,
+         /*expected_metric=*/
+         KioskInternetAccessInfo::kOnlineAndAppRequiresInternet},
+    }),
+    [](const testing::TestParamInfo<
+        InternetPeriodicMetricsServiceTest::ParamType>& info) {
+      return info.param.test_name;
+    });
+
 }  // namespace ash
diff --git a/chrome/browser/ash/app_mode/web_app/web_kiosk_app_launcher_unittest.cc b/chrome/browser/ash/app_mode/web_app/web_kiosk_app_launcher_unittest.cc
index 7026068..fa531be 100644
--- a/chrome/browser/ash/app_mode/web_app/web_kiosk_app_launcher_unittest.cc
+++ b/chrome/browser/ash/app_mode/web_app/web_kiosk_app_launcher_unittest.cc
@@ -24,6 +24,7 @@
 #include "chrome/common/chrome_features.h"
 #include "chrome/test/base/browser_with_test_window_test.h"
 #include "chrome/test/base/testing_profile.h"
+#include "chromeos/ash/components/network/network_handler_test_helper.h"
 #include "components/exo/shell_surface_util.h"
 #include "components/exo/wm_helper_chromeos.h"
 #include "components/user_manager/scoped_user_manager.h"
@@ -124,6 +125,8 @@
 
   void SetUp() override {
     BrowserWithTestWindowTest::SetUp();
+    network_handler_test_helper_ = std::make_unique<NetworkHandlerTestHelper>();
+
     app_manager_ = std::make_unique<WebKioskAppManager>();
 
     ConstructLauncher(/*should_skip_install=*/false);
@@ -135,6 +138,7 @@
     closer_.reset();
     launcher_.reset();
     app_manager_.reset();
+    network_handler_test_helper_.reset();
     BrowserWithTestWindowTest::TearDown();
   }
 
@@ -198,6 +202,7 @@
 
  private:
   std::unique_ptr<WebKioskAppManager> app_manager_;
+  std::unique_ptr<NetworkHandlerTestHelper> network_handler_test_helper_;
 
   MockAppLauncherDelegate delegate_;
   std::unique_ptr<WebKioskAppLauncher> launcher_;
diff --git a/chrome/browser/ash/arc/app_shortcuts/arc_app_shortcuts_menu_builder.cc b/chrome/browser/ash/arc/app_shortcuts/arc_app_shortcuts_menu_builder.cc
index 1a1faec..9e8a88e4 100644
--- a/chrome/browser/ash/arc/app_shortcuts/arc_app_shortcuts_menu_builder.cc
+++ b/chrome/browser/ash/arc/app_shortcuts/arc_app_shortcuts_menu_builder.cc
@@ -12,10 +12,10 @@
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/ash/app_list/arc/arc_app_utils.h"
 #include "chrome/browser/ash/app_list/search/ranking/launch_data.h"
+#include "chrome/browser/ash/app_list/search/search_controller.h"
 #include "chrome/browser/ash/arc/app_shortcuts/arc_app_shortcuts_request.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/app_list/app_list_client_impl.h"
-#include "chrome/browser/ui/app_list/search/search_controller.h"
 #include "ui/base/models/image_model.h"
 #include "ui/base/models/simple_menu_model.h"
 
diff --git a/chrome/browser/ash/camera_mic/vm_camera_mic_manager_unittest.cc b/chrome/browser/ash/camera_mic/vm_camera_mic_manager_unittest.cc
index c8324d6..05e8d535 100644
--- a/chrome/browser/ash/camera_mic/vm_camera_mic_manager_unittest.cc
+++ b/chrome/browser/ash/camera_mic/vm_camera_mic_manager_unittest.cc
@@ -28,7 +28,6 @@
 #include "chrome/test/base/testing_profile.h"
 #include "components/account_id/account_id.h"
 #include "components/prefs/testing_pref_service.h"
-#include "components/services/app_service/public/cpp/features.h"
 #include "components/user_manager/scoped_user_manager.h"
 #include "content/public/test/browser_task_environment.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -274,10 +273,7 @@
  public:
   // VmCameraMicManagerTest:
   void SetUp() override {
-    scoped_feature_list_.InitWithFeatures(
-        {apps::kAppServiceCapabilityAccessWithoutMojom,
-         features::kPrivacyIndicators},
-        {});
+    scoped_feature_list_.InitWithFeatures({features::kPrivacyIndicators}, {});
 
     VmCameraMicManagerTest::SetUp();
   }
@@ -470,14 +466,11 @@
   // VmCameraMicManagerTest:
   void SetUp() override {
     if (IsPrivacyIndicatorsFeatureEnabled()) {
-      scoped_feature_list_.InitWithFeatures(
-          {apps::kAppServiceCapabilityAccessWithoutMojom,
-           ash::features::kPrivacyIndicators},
-          {});
+      scoped_feature_list_.InitAndEnableFeature(
+          ash::features::kPrivacyIndicators);
     } else {
-      scoped_feature_list_.InitWithFeatures(
-          {apps::kAppServiceCapabilityAccessWithoutMojom},
-          {ash::features::kPrivacyIndicators});
+      scoped_feature_list_.InitAndDisableFeature(
+          ash::features::kPrivacyIndicators);
     }
 
     VmCameraMicManagerTest::SetUp();
@@ -580,14 +573,11 @@
   // VmCameraMicManagerTest:
   void SetUp() override {
     if (IsPrivacyIndicatorsFeatureEnabled()) {
-      scoped_feature_list_.InitWithFeatures(
-          {apps::kAppServiceCapabilityAccessWithoutMojom,
-           ash::features::kPrivacyIndicators},
-          {});
+      scoped_feature_list_.InitAndEnableFeature(
+          ash::features::kPrivacyIndicators);
     } else {
-      scoped_feature_list_.InitWithFeatures(
-          {apps::kAppServiceCapabilityAccessWithoutMojom},
-          {ash::features::kPrivacyIndicators});
+      scoped_feature_list_.InitAndDisableFeature(
+          ash::features::kPrivacyIndicators);
     }
 
     VmCameraMicManagerTest::SetUp();
diff --git a/chrome/browser/ash/child_accounts/time_limits/app_time_test_utils.cc b/chrome/browser/ash/child_accounts/time_limits/app_time_test_utils.cc
index c068d97..5464ff4 100644
--- a/chrome/browser/ash/child_accounts/time_limits/app_time_test_utils.cc
+++ b/chrome/browser/ash/child_accounts/time_limits/app_time_test_utils.cc
@@ -40,11 +40,11 @@
     const std::string& name,
     const std::string& url,
     bool is_bookmark_app) {
-  base::Value manifest(base::Value::Type::DICTIONARY);
-  manifest.SetStringPath(extensions::manifest_keys::kName, name);
-  manifest.SetStringPath(extensions::manifest_keys::kVersion, "1");
-  manifest.SetIntPath(extensions::manifest_keys::kManifestVersion, 2);
-  manifest.SetStringPath(extensions::manifest_keys::kLaunchWebURL, url);
+  base::Value::Dict manifest;
+  manifest.Set(extensions::manifest_keys::kName, name);
+  manifest.Set(extensions::manifest_keys::kVersion, "1");
+  manifest.Set(extensions::manifest_keys::kManifestVersion, 2);
+  manifest.SetByDottedPath(extensions::manifest_keys::kLaunchWebURL, url);
 
   std::string error;
   extensions::Extension::InitFromValueFlags flags =
@@ -53,8 +53,7 @@
   scoped_refptr<extensions::Extension> extension =
       extensions::Extension::Create(
           base::FilePath(), extensions::mojom::ManifestLocation::kUnpacked,
-          static_cast<base::DictionaryValue&>(manifest), flags, extension_id,
-          &error);
+          manifest, flags, extension_id, &error);
   return extension;
 }
 
diff --git a/chrome/browser/ash/crosapi/browser_data_back_migrator.cc b/chrome/browser/ash/crosapi/browser_data_back_migrator.cc
index 63e1212..a776344b 100644
--- a/chrome/browser/ash/crosapi/browser_data_back_migrator.cc
+++ b/chrome/browser/ash/crosapi/browser_data_back_migrator.cc
@@ -1101,15 +1101,30 @@
     return false;
   }
 
+  crosapi::browser_util::LacrosDataBackwardMigrationMode migration_mode =
+      crosapi::browser_util::LacrosDataBackwardMigrationMode::kNone;
   if (policy_init_state ==
       crosapi::browser_util::PolicyInitState::kBeforeInit) {
     // TODO(b/244572632): Read cached flag.
   } else {
     DCHECK_EQ(policy_init_state,
               crosapi::browser_util::PolicyInitState::kAfterInit);
-    // TODO(b/244572632): Read policy value.
+    migration_mode =
+        crosapi::browser_util::GetCachedLacrosDataBackwardMigrationMode();
   }
 
+  // Backward migration can be explicitly enabled by using the
+  // LacrosDataBackwardMigrationMode policy.
+  if (migration_mode ==
+      crosapi::browser_util::LacrosDataBackwardMigrationMode::kKeepAll)
+    return true;
+
+  // Modes beside none do not go through backward migration.
+  // None is the default, fall back to the feature instead.
+  if (migration_mode !=
+      crosapi::browser_util::LacrosDataBackwardMigrationMode::kNone)
+    return false;
+
   return base::FeatureList::IsEnabled(
       ash::features::kLacrosProfileBackwardMigration);
 }
diff --git a/chrome/browser/ash/crosapi/browser_data_back_migrator.h b/chrome/browser/ash/crosapi/browser_data_back_migrator.h
index 484629f..c8bbd69 100644
--- a/chrome/browser/ash/crosapi/browser_data_back_migrator.h
+++ b/chrome/browser/ash/crosapi/browser_data_back_migrator.h
@@ -56,6 +56,8 @@
   // 1. The kForceBrowserDataBackwardMigration debug flag.
   // 2. The LacrosDataBackwardMigrationMode policy.
   // 3. The kLacrosProfileBackwardMigration feature flag.
+  // The policy value is cached at the beginning of the session and not
+  // updated.
   static bool IsBackMigrationEnabled(
       crosapi::browser_util::PolicyInitState policy_init_state);
 
diff --git a/chrome/browser/ash/crosapi/browser_data_back_migrator_unittest.cc b/chrome/browser/ash/crosapi/browser_data_back_migrator_unittest.cc
index b7505a8..a66c8b2 100644
--- a/chrome/browser/ash/crosapi/browser_data_back_migrator_unittest.cc
+++ b/chrome/browser/ash/crosapi/browser_data_back_migrator_unittest.cc
@@ -4,10 +4,15 @@
 
 #include "chrome/browser/ash/crosapi/browser_data_back_migrator.h"
 
+#include "ash/constants/ash_features.h"
 #include "base/files/file_enumerator.h"
 #include "base/files/file_util.h"
 #include "base/files/scoped_temp_dir.h"
+#include "base/test/scoped_feature_list.h"
 #include "chrome/browser/ash/crosapi/browser_data_migrator_util.h"
+#include "chrome/browser/ash/crosapi/browser_util.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/policy_constants.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace ash {
@@ -332,4 +337,82 @@
   }
 }
 
+namespace {
+// This implementation of RAII for LacrosDataBackwardMigrationMode is intended
+// to make it easy reset the state between runs.
+class ScopedLacrosDataBackwardMigrationModeCache {
+ public:
+  explicit ScopedLacrosDataBackwardMigrationModeCache(
+      crosapi::browser_util::LacrosDataBackwardMigrationMode mode) {
+    SetLacrosDataBackwardMigrationMode(mode);
+  }
+  ScopedLacrosDataBackwardMigrationModeCache(
+      const ScopedLacrosDataBackwardMigrationModeCache&) = delete;
+  ScopedLacrosDataBackwardMigrationModeCache& operator=(
+      const ScopedLacrosDataBackwardMigrationModeCache&) = delete;
+  ~ScopedLacrosDataBackwardMigrationModeCache() {
+    crosapi::browser_util::ClearLacrosDataBackwardMigrationModeCacheForTest();
+  }
+
+ private:
+  void SetLacrosDataBackwardMigrationMode(
+      crosapi::browser_util::LacrosDataBackwardMigrationMode mode) {
+    policy::PolicyMap policy;
+    policy.Set(policy::key::kLacrosDataBackwardMigrationMode,
+               policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER,
+               policy::POLICY_SOURCE_CLOUD,
+               base::Value(GetLacrosDataBackwardMigrationModeName(mode)),
+               /*external_data_fetcher=*/nullptr);
+    crosapi::browser_util::CacheLacrosDataBackwardMigrationMode(policy);
+  }
+};
+}  // namespace
+
+class BrowserDataBackMigratorTriggeringTest : public testing::Test {
+ public:
+  void SetUp() override {
+    scoped_disabled_feature.InitAndDisableFeature(
+        ash::features::kLacrosProfileBackwardMigration);
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_disabled_feature;
+};
+
+TEST_F(BrowserDataBackMigratorTriggeringTest, DefaultDisabledBeforeInit) {
+  EXPECT_FALSE(BrowserDataBackMigrator::IsBackMigrationEnabled(
+      crosapi::browser_util::PolicyInitState::kBeforeInit));
+}
+
+TEST_F(BrowserDataBackMigratorTriggeringTest, DefaultDisabledAfterInit) {
+  EXPECT_FALSE(BrowserDataBackMigrator::IsBackMigrationEnabled(
+      crosapi::browser_util::PolicyInitState::kAfterInit));
+}
+
+TEST_F(BrowserDataBackMigratorTriggeringTest, FeatureEnabledBeforeInit) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(
+      ash::features::kLacrosProfileBackwardMigration);
+
+  EXPECT_TRUE(BrowserDataBackMigrator::IsBackMigrationEnabled(
+      crosapi::browser_util::PolicyInitState::kAfterInit));
+}
+
+TEST_F(BrowserDataBackMigratorTriggeringTest, FeatureEnabledAfterInit) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(
+      ash::features::kLacrosProfileBackwardMigration);
+
+  EXPECT_TRUE(BrowserDataBackMigrator::IsBackMigrationEnabled(
+      crosapi::browser_util::PolicyInitState::kAfterInit));
+}
+
+TEST_F(BrowserDataBackMigratorTriggeringTest, PolicyEnabledAfterInit) {
+  ScopedLacrosDataBackwardMigrationModeCache scoped_policy(
+      crosapi::browser_util::LacrosDataBackwardMigrationMode::kKeepAll);
+
+  EXPECT_TRUE(BrowserDataBackMigrator::IsBackMigrationEnabled(
+      crosapi::browser_util::PolicyInitState::kAfterInit));
+}
+
 }  // namespace ash
diff --git a/chrome/browser/ash/crosapi/browser_util.cc b/chrome/browser/ash/crosapi/browser_util.cc
index 816974e..f1e7a51 100644
--- a/chrome/browser/ash/crosapi/browser_util.cc
+++ b/chrome/browser/ash/crosapi/browser_util.cc
@@ -55,6 +55,12 @@
 // result is stored in this variable which is used after that as a cache.
 absl::optional<LacrosAvailability> g_lacros_availability_cache;
 
+// At session start the value for LacrosDataBackwardMigrationMode logic is
+// applied and the result is stored in this variable which is used after that as
+// a cache.
+absl::optional<LacrosDataBackwardMigrationMode>
+    g_lacros_data_backward_migration_mode;
+
 // The rootfs lacros-chrome metadata keys.
 constexpr char kLacrosMetadataContentKey[] = "content";
 constexpr char kLacrosMetadataVersionKey[] = "version";
@@ -70,6 +76,20 @@
         {"lacros_only", LacrosAvailability::kLacrosOnly},
     });
 
+// The conversion map for LacrosDataBackwardMigrationMode policy data. The
+// values must match the ones from LacrosDataBackwardMigrationMode.yaml.
+constexpr auto kLacrosDataBackwardMigrationModeMap =
+    base::MakeFixedFlatMap<base::StringPiece, LacrosDataBackwardMigrationMode>({
+        {kLacrosDataBackwardMigrationModePolicyNone,
+         LacrosDataBackwardMigrationMode::kNone},
+        {kLacrosDataBackwardMigrationModePolicyKeepNone,
+         LacrosDataBackwardMigrationMode::kKeepNone},
+        {kLacrosDataBackwardMigrationModePolicyKeepSafeData,
+         LacrosDataBackwardMigrationMode::kKeepSafeData},
+        {kLacrosDataBackwardMigrationModePolicyKeepAll,
+         LacrosDataBackwardMigrationMode::kKeepAll},
+    });
+
 // Some account types require features that aren't yet supported by lacros.
 // See https://crbug.com/1080693
 bool IsUserTypeAllowed(const User* user) {
@@ -870,6 +890,20 @@
       value ? value->GetString() : base::StringPiece());
 }
 
+void CacheLacrosDataBackwardMigrationMode(const policy::PolicyMap& map) {
+  if (g_lacros_data_backward_migration_mode.has_value()) {
+    // Some browser tests might call this multiple times.
+    LOG(ERROR) << "Trying to cache LacrosDataBackwardMigrationMode and the "
+                  "value was set";
+    return;
+  }
+
+  const base::Value* value = map.GetValue(
+      policy::key::kLacrosDataBackwardMigrationMode, base::Value::Type::STRING);
+  g_lacros_data_backward_migration_mode = ParseLacrosDataBackwardMigrationMode(
+      value ? value->GetString() : base::StringPiece());
+}
+
 ComponentInfo GetLacrosComponentInfoForChannel(version_info::Channel channel) {
   // We default to the Dev component for UNKNOWN channels.
   static const auto kChannelToComponentInfoMap =
@@ -923,6 +957,15 @@
   return GetCachedLacrosAvailability();
 }
 
+// Returns the cached value of the LacrosDataBackwardMigrationMode policy.
+LacrosDataBackwardMigrationMode GetCachedLacrosDataBackwardMigrationMode() {
+  if (g_lacros_data_backward_migration_mode.has_value())
+    return g_lacros_data_backward_migration_mode.value();
+
+  // By default migration should be disabled.
+  return LacrosDataBackwardMigrationMode::kNone;
+}
+
 void SetLacrosLaunchSwitchSourceForTest(LacrosAvailability test_value) {
   g_lacros_availability_cache = test_value;
 }
@@ -931,6 +974,10 @@
   g_lacros_availability_cache.reset();
 }
 
+void ClearLacrosDataBackwardMigrationModeCacheForTest() {
+  g_lacros_data_backward_migration_mode.reset();
+}
+
 MigrationMode GetMigrationMode(const user_manager::User* user,
                                PolicyInitState policy_init_state) {
   if (base::FeatureList::IsEnabled(
@@ -1073,6 +1120,28 @@
   return base::StringPiece();
 }
 
+absl::optional<LacrosDataBackwardMigrationMode>
+ParseLacrosDataBackwardMigrationMode(base::StringPiece value) {
+  auto* it = kLacrosDataBackwardMigrationModeMap.find(value);
+  if (it != kLacrosDataBackwardMigrationModeMap.end())
+    return it->second;
+
+  LOG(ERROR) << "Unknown LacrosDataBackwardMigrationMode policy value: "
+             << value;
+  return absl::nullopt;
+}
+
+base::StringPiece GetLacrosDataBackwardMigrationModeName(
+    LacrosDataBackwardMigrationMode value) {
+  for (const auto& entry : kLacrosDataBackwardMigrationModeMap) {
+    if (entry.second == value)
+      return entry.first;
+  }
+
+  NOTREACHED();
+  return base::StringPiece();
+}
+
 bool IsAshBrowserSyncEnabled() {
   // Turn off sync from Ash if Lacros is enabled and Ash web browser is
   // disabled.
diff --git a/chrome/browser/ash/crosapi/browser_util.h b/chrome/browser/ash/crosapi/browser_util.h
index 04aa9d9..5113159 100644
--- a/chrome/browser/ash/crosapi/browser_util.h
+++ b/chrome/browser/ash/crosapi/browser_util.h
@@ -87,6 +87,21 @@
   kMaxValue = kStateful,
 };
 
+// Represents the values of the LacrosDataBackwardMigrationMode string enum
+// policy. It controls what happens when we switch from Lacros back to Ash.
+// The values shall be consistent with the policy description.
+enum class LacrosDataBackwardMigrationMode {
+  // Indicates data backward migration is not performed. The Lacros folder is
+  // removed and Ash uses its existing state.
+  kNone,
+  // Not yet implemented.
+  kKeepNone,
+  // Not yet implemented.
+  kKeepSafeData,
+  // All data is migrated back from Lacros to Ash.
+  kKeepAll,
+};
+
 struct ComponentInfo {
   // The client-side component name.
   const char* const name;
@@ -155,6 +170,28 @@
 extern const char kLacrosAvailabilityPolicyLacrosPrimary[];
 extern const char kLacrosAvailabilityPolicyLacrosOnly[];
 
+// The internal name in about_flags.cc for the `LacrosDataBackwardMigrationMode`
+// policy.
+inline constexpr const char
+    kLacrosDataBackwardMigrationModePolicyInternalName[] =
+        "lacros-data-backward-migration-policy";
+
+// The commandline flag name of `LacrosDataBackwardMigrationMode` policy.
+// The value should be the policy value as defined just below.
+inline constexpr const char kLacrosDataBackwardMigrationModePolicySwitch[] =
+    "lacros-data-backward-migration-policy";
+
+// The values for LacrosDataBackwardMigrationMode, they must match the ones
+// from LacrosDataBackwardMigrationMode.yaml.
+inline constexpr const char kLacrosDataBackwardMigrationModePolicyNone[] =
+    "none";
+inline constexpr const char kLacrosDataBackwardMigrationModePolicyKeepNone[] =
+    "keep_none";
+inline constexpr const char
+    kLacrosDataBackwardMigrationModePolicyKeepSafeData[] = "keep_safe_data";
+inline constexpr const char kLacrosDataBackwardMigrationModePolicyKeepAll[] =
+    "keep_all";
+
 // Boolean preference. Whether to launch lacros-chrome on login.
 extern const char kLaunchOnLoginPref[];
 
@@ -310,6 +347,10 @@
 // availability.
 void CacheLacrosAvailability(const policy::PolicyMap& map);
 
+// To be called at primary user login, to cache the policy value for the
+// LacrosDataBackwardMigrationMode policy.
+void CacheLacrosDataBackwardMigrationMode(const policy::PolicyMap& map);
+
 // Returns the lacros ComponentInfo for a given channel.
 ComponentInfo GetLacrosComponentInfoForChannel(version_info::Channel channel);
 
@@ -334,9 +375,16 @@
 // lacros-availability, modified by Finch flags and user flags as appropriate.
 LacrosAvailability GetCachedLacrosAvailabilityForTesting();
 
+// GetCachedLacrosDataBackwardMigrationMode returns the cached value of the
+// LacrosDataBackwardMigrationMode policy.
+LacrosDataBackwardMigrationMode GetCachedLacrosDataBackwardMigrationMode();
+
 // Clears the cached values for lacros availability policy.
 void ClearLacrosAvailabilityCacheForTest();
 
+// Clears the cached value for LacrosDataBackwardMigrationMode.
+void ClearLacrosDataBackwardMigrationModeCacheForTest();
+
 bool IsProfileMigrationEnabled(const AccountId& account_id);
 
 // Returns true if the profile migration can run, but not yet completed.
@@ -413,6 +461,15 @@
 // Returns the policy value name from the given value.
 base::StringPiece GetLacrosAvailabilityPolicyName(LacrosAvailability value);
 
+// Parses the string representation of LacrosDataBackwardMigrationMode policy
+// value into the enum value. Returns nullopt on unknown value.
+absl::optional<LacrosDataBackwardMigrationMode>
+ParseLacrosDataBackwardMigrationMode(base::StringPiece value);
+
+// Returns the policy string representation from the given enum value.
+base::StringPiece GetLacrosDataBackwardMigrationModeName(
+    LacrosDataBackwardMigrationMode value);
+
 // Stores that "Go to files button" on the migration error screen is clicked.
 void SetGotoFilesClicked(PrefService* local_state,
                          const std::string& user_id_hash);
diff --git a/chrome/browser/ash/crosapi/file_system_provider_service_ash.cc b/chrome/browser/ash/crosapi/file_system_provider_service_ash.cc
index 7a5d99a..85b9794 100644
--- a/chrome/browser/ash/crosapi/file_system_provider_service_ash.cc
+++ b/chrome/browser/ash/crosapi/file_system_provider_service_ash.cc
@@ -7,11 +7,14 @@
 #include "base/numerics/safe_conversions.h"
 #include "base/trace_event/trace_event.h"
 #include "base/types/expected.h"
+#include "chrome/browser/ash/file_system_provider/operation_request_manager.h"
 #include "chrome/browser/ash/file_system_provider/provided_file_system_info.h"
-#include "chrome/browser/ash/file_system_provider/request_manager.h"
+#include "chrome/browser/ash/file_system_provider/provider_interface.h"
+#include "chrome/browser/ash/file_system_provider/request_value.h"
 #include "chrome/browser/ash/file_system_provider/service.h"
 #include "chrome/browser/chromeos/extensions/file_system_provider/provider_function.h"
 #include "chrome/browser/profiles/profile_manager.h"
+#include "extensions/common/extension_id.h"
 
 using ash::file_system_provider::MountOptions;
 using ash::file_system_provider::OpenedFiles;
@@ -28,10 +31,32 @@
 
 constexpr char kDeserializationError[] = "deserialization error";
 
-// Either returns a valid request manager or else an error string.
+// Either returns a valid request manager for provider-level requests, or else
+// an error string.
 base::expected<ash::file_system_provider::RequestManager*, std::string>
-GetRequestManager(Profile* profile,
-                  const mojom::FileSystemIdPtr& file_system_id) {
+GetProviderRequestManager(Profile* profile,
+                          extensions::ExtensionId extension_id) {
+  Service* service = Service::Get(profile);
+  if (!service) {
+    return base::unexpected("File system provider service not found.");
+  }
+
+  ash::file_system_provider::ProviderInterface* provider =
+      service->GetProvider(ProviderId::CreateFromExtensionId(extension_id));
+  if (!provider) {
+    return base::unexpected(
+        extensions::FileErrorToString(base::File::FILE_ERROR_NOT_FOUND));
+  }
+
+  return provider->GetRequestManager();
+}
+
+// Either returns a valid request manager for file system level requests, or
+// else an error string.
+base::expected<ash::file_system_provider::OperationRequestManager*, std::string>
+GetProvidedFileSystemRequestManager(
+    Profile* profile,
+    const mojom::FileSystemIdPtr& file_system_id) {
   Service* service = Service::Get(profile);
   if (!service) {
     return base::unexpected("File system provider service not found.");
@@ -55,7 +80,7 @@
                                      std::unique_ptr<RequestValue> value,
                                      bool has_more,
                                      Profile* profile) {
-  auto manager = GetRequestManager(profile, file_system_id);
+  auto manager = GetProvidedFileSystemRequestManager(profile, file_system_id);
   if (!manager.has_value())
     return manager.error();
 
@@ -74,7 +99,7 @@
                                     std::unique_ptr<RequestValue> value,
                                     base::File::Error error,
                                     Profile* profile) {
-  auto manager = GetRequestManager(profile, file_system_id);
+  auto manager = GetProvidedFileSystemRequestManager(profile, file_system_id);
   if (!manager.has_value())
     return manager.error();
 
@@ -261,10 +286,19 @@
     int64_t request_id,
     base::Value::List args,
     OperationFinishedCallback callback) {
-  OperationFinishedWithProfile(
-      response, std::move(file_system_id), request_id,
-      std::move(base::Value(std::move(args)).GetList()), std::move(callback),
-      ProfileManager::GetPrimaryUserProfile());
+  OperationFinishedWithProfile(response, std::move(file_system_id), request_id,
+                               std::move(args), std::move(callback),
+                               ProfileManager::GetPrimaryUserProfile());
+}
+
+void FileSystemProviderServiceAsh::MountFinished(
+    const std::string& extension_id,
+    int64_t request_id,
+    base::Value::List args,
+    MountFinishedCallback callback) {
+  MountFinishedWithProfile(extension_id, request_id, std::move(args),
+                           std::move(callback),
+                           ProfileManager::GetPrimaryUserProfile());
 }
 
 void FileSystemProviderServiceAsh::ExtensionLoaded(
@@ -529,4 +563,40 @@
   std::move(callback).Run(std::move(error));
 }
 
+void FileSystemProviderServiceAsh::MountFinishedWithProfile(
+    const std::string& extension_id,
+    int64_t request_id,
+    base::Value::List args,
+    MountFinishedCallback callback,
+    Profile* profile) {
+  auto manager = GetProviderRequestManager(profile, extension_id);
+  if (!manager.has_value()) {
+    std::move(callback).Run(manager.error());
+    return;
+  }
+
+  using extensions::api::file_system_provider_internal::RespondToMountRequest::
+      Params;
+  std::unique_ptr<Params> params = Params::Create(std::move(args));
+  if (!params) {
+    std::move(callback).Run(kDeserializationError);
+    return;
+  }
+  base::File::Error mount_error =
+      extensions::ProviderErrorToFileError(params->error);
+  base::File::Error result =
+      mount_error == base::File::FILE_OK
+          ? manager.value()->FulfillRequest(
+                request_id, /*response=*/std::make_unique<RequestValue>(),
+                /*has_more=*/false)
+          : manager.value()->RejectRequest(
+                request_id, /*response=*/std::make_unique<RequestValue>(),
+                mount_error);
+
+  std::string error_str;
+  if (result != base::File::FILE_OK)
+    error_str = extensions::FileErrorToString(result);
+  std::move(callback).Run(error_str);
+}
+
 }  // namespace crosapi
diff --git a/chrome/browser/ash/crosapi/file_system_provider_service_ash.h b/chrome/browser/ash/crosapi/file_system_provider_service_ash.h
index b310c193..3a64dbf0 100644
--- a/chrome/browser/ash/crosapi/file_system_provider_service_ash.h
+++ b/chrome/browser/ash/crosapi/file_system_provider_service_ash.h
@@ -56,6 +56,10 @@
                          int64_t request_id,
                          base::Value::List args,
                          OperationFinishedCallback callback) override;
+  void MountFinished(const std::string& extension_id,
+                     int64_t request_id,
+                     base::Value::List args,
+                     MountFinishedCallback callback) override;
   void ExtensionLoaded(bool configurable,
                        bool watchable,
                        bool multiple_mounts,
@@ -92,6 +96,11 @@
                                     base::Value::List args,
                                     OperationFinishedCallback callback,
                                     Profile* profile);
+  void MountFinishedWithProfile(const std::string& extension_id,
+                                int64_t request_id,
+                                base::Value::List args,
+                                MountFinishedCallback callback,
+                                Profile* profile);
 
   // Exposed so that ash clients can work with Lacros file system providers.
   mojo::RemoteSet<mojom::FileSystemProvider>& remotes() { return remotes_; }
diff --git a/chrome/browser/ash/crosapi/geolocation_service_ash_unittest.cc b/chrome/browser/ash/crosapi/geolocation_service_ash_unittest.cc
index ad23e6d9..c709618 100644
--- a/chrome/browser/ash/crosapi/geolocation_service_ash_unittest.cc
+++ b/chrome/browser/ash/crosapi/geolocation_service_ash_unittest.cc
@@ -9,6 +9,7 @@
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/task_environment.h"
+#include "base/values.h"
 #include "chromeos/ash/components/dbus/shill/shill_clients.h"
 #include "chromeos/ash/components/dbus/shill/shill_manager_client.h"
 #include "chromeos/ash/components/network/geolocation_handler.h"
@@ -27,16 +28,14 @@
   void AddAccessPoints(int ssids, int aps_per_ssid) {
     for (int i = 0; i < ssids; ++i) {
       for (int j = 0; j < aps_per_ssid; ++j) {
-        base::DictionaryValue properties;
         std::string mac_address = base::StringPrintf(
             "%02X:%02X:%02X:%02X:%02X:%02X", i, j, 3, 4, 5, 6);
         std::string channel = base::NumberToString(i * 10 + j);
         std::string strength = base::NumberToString(i * 100 + j);
-        properties.SetKey(shill::kGeoMacAddressProperty,
-                          base::Value(mac_address));
-        properties.SetKey(shill::kGeoChannelProperty, base::Value(channel));
-        properties.SetKey(shill::kGeoSignalStrengthProperty,
-                          base::Value(strength));
+        base::Value::Dict properties;
+        properties.Set(shill::kGeoMacAddressProperty, mac_address);
+        properties.Set(shill::kGeoChannelProperty, channel);
+        properties.Set(shill::kGeoSignalStrengthProperty, strength);
         network_handler_test_helper_->manager_test()->AddGeoNetwork(
             shill::kGeoWifiAccessPointsProperty, properties);
       }
diff --git a/chrome/browser/ash/drive/drive_integration_service.cc b/chrome/browser/ash/drive/drive_integration_service.cc
index ec1a2fe..a4c7d6c2 100644
--- a/chrome/browser/ash/drive/drive_integration_service.cc
+++ b/chrome/browser/ash/drive/drive_integration_service.cc
@@ -1018,10 +1018,21 @@
         base::BindOnce(&DriveIntegrationService::OnEnableMirroringStatusUpdate,
                        weak_ptr_factory_.GetWeakPtr()));
   }
+
+  if (ash::features::IsDriveFsBulkPinningEnabled()) {
+    pin_manager_ = std::make_unique<drivefs::pinning::DriveFsPinManager>(
+        profile_->GetPrefs()->GetBoolean(prefs::kDriveFsBulkPinningEnabled),
+        profile_->GetPath(), GetDriveFsInterface());
+  }
 }
 
 void DriveIntegrationService::OnUnmounted(
     absl::optional<base::TimeDelta> remount_delay) {
+  if (ash::features::IsDriveFsBulkPinningEnabled() && pin_manager_) {
+    pin_manager_->Stop();
+    drivefs_holder_->drivefs_host()->RemoveObserver(pin_manager_.get());
+    pin_manager_.reset();
+  }
   UmaEmitUnmountOutcome(remount_delay ? DriveMountStatus::kTemporaryUnavailable
                                       : DriveMountStatus::kUnknownFailure);
   MaybeRemountFileSystem(remount_delay, false);
@@ -1435,25 +1446,23 @@
 
 void DriveIntegrationService::SetBulkPinningEnabled(bool enabled) {
   if (!ash::features::IsDriveFsBulkPinningEnabled() || !IsMounted() ||
-      !GetDriveFsInterface()) {
+      !GetDriveFsInterface() || !pin_manager_) {
     return;
   }
 
-  if (!pin_manager_) {
-    VLOG(1) << "Lazily creating the pin manager";
-    pin_manager_ = std::make_unique<drivefs::pinning::DriveFsPinManager>(
-        profile_->GetPrefs()->GetBoolean(prefs::kDriveFsBulkPinningEnabled),
-        profile_->GetPath(), GetDriveFsInterface());
-  }
-
   VLOG(1) << "Setting bulk pinning enabled: " << enabled;
   pin_manager_->SetBulkPinningEnabled(enabled);
 
   if (enabled) {
+    drivefs_holder_->drivefs_host()->AddObserver(pin_manager_.get());
     pin_manager_->Start(
         base::BindOnce(&DriveIntegrationService::OnBulkPinningFinished,
                        weak_ptr_factory_.GetWeakPtr()));
+    return;
   }
+
+  pin_manager_->Stop();
+  drivefs_holder_->drivefs_host()->RemoveObserver(pin_manager_.get());
 }
 
 void DriveIntegrationService::OnBulkPinningFinished(
diff --git a/chrome/browser/ash/eche_app/eche_app_manager_factory.cc b/chrome/browser/ash/eche_app/eche_app_manager_factory.cc
index 89a2598..683f82d219 100644
--- a/chrome/browser/ash/eche_app/eche_app_manager_factory.cc
+++ b/chrome/browser/ash/eche_app/eche_app_manager_factory.cc
@@ -67,6 +67,7 @@
                   const std::u16string& visible_name,
                   const absl::optional<int64_t>& user_id,
                   const gfx::Image& icon,
+                  const std::u16string& phone_name,
                   Profile* profile) {
   EcheAppManagerFactory::GetInstance()->SetLastLaunchedAppInfo(
       LaunchedAppInfo::Builder()
@@ -74,6 +75,7 @@
           .SetVisibleName(visible_name)
           .SetUserId(user_id)
           .SetIcon(icon)
+          .SetPhoneName(phone_name)
           .Build());
   std::u16string url;
   // Use hash mark(#) to send params to webui so we don't need to reload the
@@ -101,7 +103,7 @@
   }
   const auto gurl = GURL(url);
 
-  return LaunchBubble(gurl, icon, visible_name,
+  return LaunchBubble(gurl, icon, visible_name, phone_name,
                       base::BindOnce(&EnsureStreamClose, profile),
                       base::BindRepeating(&StreamGoBack, profile));
 }
@@ -112,7 +114,7 @@
   EcheAppManagerFactory::LaunchEcheApp(
       profile, absl::nullopt, last_launched_app_info->package_name(),
       last_launched_app_info->visible_name(), last_launched_app_info->user_id(),
-      last_launched_app_info->icon());
+      last_launched_app_info->icon(), last_launched_app_info->phone_name());
 }
 
 }  // namespace
@@ -121,11 +123,13 @@
 LaunchedAppInfo::LaunchedAppInfo(const std::string& package_name,
                                  const std::u16string& visible_name,
                                  const absl::optional<int64_t>& user_id,
-                                 const gfx::Image& icon) {
+                                 const gfx::Image& icon,
+                                 const std::u16string& phone_name) {
   package_name_ = package_name;
   visible_name_ = visible_name;
   user_id_ = user_id;
   icon_ = icon;
+  phone_name_ = phone_name;
 }
 
 LaunchedAppInfo::Builder::Builder() = default;
@@ -192,9 +196,10 @@
     const std::string& package_name,
     const std::u16string& visible_name,
     const absl::optional<int64_t>& user_id,
-    const gfx::Image& icon) {
+    const gfx::Image& icon,
+    const std::u16string& phone_name) {
   LaunchWebApp(package_name, notification_id, visible_name, user_id, icon,
-               profile);
+               phone_name, profile);
   EcheAppManagerFactory::GetInstance()
       ->CloseConnectionOrLaunchErrorNotifications();
 }
diff --git a/chrome/browser/ash/eche_app/eche_app_manager_factory.h b/chrome/browser/ash/eche_app/eche_app_manager_factory.h
index 6c8ce6d0..cc5811c 100644
--- a/chrome/browser/ash/eche_app/eche_app_manager_factory.h
+++ b/chrome/browser/ash/eche_app/eche_app_manager_factory.h
@@ -29,8 +29,8 @@
     ~Builder();
 
     std::unique_ptr<LaunchedAppInfo> Build() {
-      return base::WrapUnique(
-          new LaunchedAppInfo(package_name_, visible_name_, user_id_, icon_));
+      return base::WrapUnique(new LaunchedAppInfo(
+          package_name_, visible_name_, user_id_, icon_, phone_name_));
     }
     Builder& SetPackageName(const std::string& package_name) {
       package_name_ = package_name;
@@ -52,11 +52,17 @@
       return *this;
     }
 
+    Builder& SetPhoneName(const std::u16string& phone_name) {
+      phone_name_ = phone_name;
+      return *this;
+    }
+
    private:
     std::string package_name_;
     std::u16string visible_name_;
     absl::optional<int64_t> user_id_;
     gfx::Image icon_;
+    std::u16string phone_name_;
   };
 
   LaunchedAppInfo() = delete;
@@ -68,18 +74,21 @@
   std::u16string visible_name() const { return visible_name_; }
   absl::optional<int64_t> user_id() const { return user_id_; }
   gfx::Image icon() const { return icon_; }
+  std::u16string phone_name() const { return phone_name_; }
 
  protected:
   LaunchedAppInfo(const std::string& package_name,
                   const std::u16string& visible_name,
                   const absl::optional<int64_t>& user_id,
-                  const gfx::Image& icon);
+                  const gfx::Image& icon,
+                  const std::u16string& phone_name);
 
  private:
   std::string package_name_;
   std::u16string visible_name_;
   absl::optional<int64_t> user_id_;
   gfx::Image icon_;
+  std::u16string phone_name_;
 };
 
 // Factory to create a single EcheAppManager.
@@ -101,7 +110,8 @@
                             const std::string& package_name,
                             const std::u16string& visible_name,
                             const absl::optional<int64_t>& user_id,
-                            const gfx::Image& icon);
+                            const gfx::Image& icon,
+                            const std::u16string& phone_name);
 
   void SetLastLaunchedAppInfo(
       std::unique_ptr<LaunchedAppInfo> last_launched_app_info);
diff --git a/chrome/browser/ash/eche_app/eche_app_manager_factory_unittest.cc b/chrome/browser/ash/eche_app/eche_app_manager_factory_unittest.cc
index 0b76579..c7a3621 100644
--- a/chrome/browser/ash/eche_app/eche_app_manager_factory_unittest.cc
+++ b/chrome/browser/ash/eche_app/eche_app_manager_factory_unittest.cc
@@ -158,9 +158,10 @@
   const int64_t user_id = 1;
   const char16_t visible_name_1[] = u"Fake App 1";
   const char package_name_1[] = "com.fakeapp1";
+  const char16_t phone_name[] = u"your phone";
   EcheAppManagerFactory::LaunchEcheApp(
       GetProfile(), /*notification_id=*/absl::nullopt, package_name_1,
-      visible_name_1, user_id, gfx::Image());
+      visible_name_1, user_id, gfx::Image(), phone_name);
   // Wait for Eche Tray to load Eche Web to complete
   base::RunLoop().RunUntilIdle();
   // Eche icon should be visible after launch.
@@ -172,7 +173,7 @@
   const char package_name_2[] = "com.fakeapp2";
   EcheAppManagerFactory::LaunchEcheApp(
       GetProfile(), /*notification_id=*/absl::nullopt, package_name_2,
-      visible_name_2, user_id, gfx::Image());
+      visible_name_2, user_id, gfx::Image(), phone_name);
   // Wait for Eche Tray to load Eche Web to complete
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(widget, eche_tray()->GetBubbleWidget());
@@ -183,10 +184,11 @@
   const std::u16string visible_name = u"Fake App";
   const std::string package_name = "com.fakeapp";
   const gfx::Image icon = gfx::test::CreateImage(100, 100);
+  const std::u16string phone_name = u"your phone";
 
   EcheAppManagerFactory::LaunchEcheApp(
       GetProfile(), /*notification_id=*/absl::nullopt, package_name,
-      visible_name, user_id, icon);
+      visible_name, user_id, icon, phone_name);
 
   std::unique_ptr<LaunchedAppInfo> launched_app_info =
       EcheAppManagerFactory::GetInstance()->GetLastLaunchedAppInfo();
@@ -205,9 +207,10 @@
   const int64_t user_id = 1;
   const char16_t visible_name[] = u"Fake App";
   const char package_name[] = "com.fakeapp";
+  const char16_t phone_name[] = u"your phone";
   EcheAppManagerFactory::LaunchEcheApp(
       GetProfile(), /*notification_id=*/absl::nullopt, package_name,
-      visible_name, user_id, gfx::Image());
+      visible_name, user_id, gfx::Image(), phone_name);
   // Wait for Eche Tray to load Eche Web to complete
   base::RunLoop().RunUntilIdle();
   // Eche tray should be visible when streaming is active, not ative when
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 203e6e8..99e4aca 100644
--- a/chrome/browser/ash/extensions/autotest_private/autotest_private_api.cc
+++ b/chrome/browser/ash/extensions/autotest_private/autotest_private_api.cc
@@ -2284,7 +2284,7 @@
     if (app_id) {
       system_web_app.start_url =
           ash::SystemWebAppManager::GetWebAppProvider(profile)
-              ->registrar()
+              ->registrar_unsafe()
               .GetAppLaunchUrl(*app_id)
               .spec();
     }
diff --git a/chrome/browser/ash/extensions/autotest_private/autotest_private_apitest.cc b/chrome/browser/ash/extensions/autotest_private/autotest_private_apitest.cc
index 4eda1d7..b04c7177 100644
--- a/chrome/browser/ash/extensions/autotest_private/autotest_private_apitest.cc
+++ b/chrome/browser/ash/extensions/autotest_private/autotest_private_apitest.cc
@@ -29,6 +29,7 @@
 #include "base/values.h"
 #include "build/build_config.h"
 #include "chrome/browser/ash/app_list/arc/arc_app_list_prefs.h"
+#include "chrome/browser/ash/app_list/search/search_provider.h"
 #include "chrome/browser/ash/app_list/search/test/app_list_search_test_helper.h"
 #include "chrome/browser/ash/app_list/search/test/search_results_changed_waiter.h"
 #include "chrome/browser/ash/app_list/search/test/test_result.h"
@@ -40,7 +41,6 @@
 #include "chrome/browser/ash/settings/scoped_testing_cros_settings.h"
 #include "chrome/browser/ash/system_web_apps/test_support/test_system_web_app_installation.h"
 #include "chrome/browser/extensions/extension_apitest.h"
-#include "chrome/browser/ui/app_list/search/search_provider.h"
 #include "chrome/browser/ui/ash/shelf/chrome_shelf_prefs.h"
 #include "chrome/browser/ui/browser_window.h"
 #include "components/feature_engagement/public/feature_constants.h"
diff --git a/chrome/browser/ash/extensions/device_local_account_management_policy_provider_unittest.cc b/chrome/browser/ash/extensions/device_local_account_management_policy_provider_unittest.cc
index 9b67eb3..585e8e99 100644
--- a/chrome/browser/ash/extensions/device_local_account_management_policy_provider_unittest.cc
+++ b/chrome/browser/ash/extensions/device_local_account_management_policy_provider_unittest.cc
@@ -27,53 +27,49 @@
 scoped_refptr<const extensions::Extension> CreateExtensionFromValues(
     const std::string& id,
     ManifestLocation location,
-    base::DictionaryValue* values,
+    base::Value::Dict& values,
     int flags) {
-  values->SetStringKey(extensions::manifest_keys::kName, "test");
-  values->SetStringKey(extensions::manifest_keys::kVersion, "0.1");
-  values->SetIntKey(extensions::manifest_keys::kManifestVersion, 2);
+  values.Set(extensions::manifest_keys::kName, "test");
+  values.Set(extensions::manifest_keys::kVersion, "0.1");
+  values.Set(extensions::manifest_keys::kManifestVersion, 2);
   std::string error;
-  return extensions::Extension::Create(base::FilePath(),
-                                       location,
-                                       *values,
-                                       flags,
-                                       id,
-                                       &error);
+  return extensions::Extension::Create(base::FilePath(), location, values,
+                                       flags, id, &error);
 }
 
 scoped_refptr<const extensions::Extension> CreateRegularExtension(
     const std::string& id) {
-  base::DictionaryValue values;
-  return CreateExtensionFromValues(id, ManifestLocation::kInternal, &values,
+  base::Value::Dict values;
+  return CreateExtensionFromValues(id, ManifestLocation::kInternal, values,
                                    extensions::Extension::NO_FLAGS);
 }
 
 scoped_refptr<const extensions::Extension> CreateExternalComponentExtension() {
-  base::DictionaryValue values;
+  base::Value::Dict values;
   return CreateExtensionFromValues(std::string(),
-                                   ManifestLocation::kExternalComponent,
-                                   &values, extensions::Extension::NO_FLAGS);
+                                   ManifestLocation::kExternalComponent, values,
+                                   extensions::Extension::NO_FLAGS);
 }
 
 scoped_refptr<const extensions::Extension> CreateComponentExtension() {
-  base::DictionaryValue values;
+  base::Value::Dict values;
   return CreateExtensionFromValues(std::string(), ManifestLocation::kComponent,
-                                   &values, extensions::Extension::NO_FLAGS);
+                                   values, extensions::Extension::NO_FLAGS);
 }
 
 scoped_refptr<const extensions::Extension> CreatePlatformAppWithExtraValues(
-    const base::DictionaryValue* extra_values,
+    const base::Value::Dict& extra_values,
     ManifestLocation location,
     int flags) {
-  base::DictionaryValue values;
-  values.SetStringPath("app.background.page", "background.html");
-  values.MergeDictionary(extra_values);
-  return CreateExtensionFromValues(std::string(), location, &values, flags);
+  base::Value::Dict values;
+  values.SetByDottedPath("app.background.page", "background.html");
+  values.Merge(extra_values.Clone());
+  return CreateExtensionFromValues(std::string(), location, values, flags);
 }
 
 scoped_refptr<const extensions::Extension> CreatePlatformApp() {
-  base::DictionaryValue values;
-  return CreatePlatformAppWithExtraValues(&values, ManifestLocation::kInternal,
+  base::Value::Dict values;
+  return CreatePlatformAppWithExtraValues(values, ManifestLocation::kInternal,
                                           extensions::Extension::NO_FLAGS);
 }
 
@@ -113,9 +109,9 @@
   // Verify that a minimal platform app can be installed from location
   // EXTERNAL_POLICY.
   {
-    base::DictionaryValue values;
+    base::Value::Dict values;
     extension = CreatePlatformAppWithExtraValues(
-        &values, ManifestLocation::kExternalPolicy,
+        values, ManifestLocation::kExternalPolicy,
         extensions::Extension::NO_FLAGS);
     ASSERT_TRUE(extension);
 
@@ -127,9 +123,9 @@
   // Verify that a minimal platform app can be installed from location
   // EXTERNAL_POLICY_DOWNLOAD.
   {
-    base::DictionaryValue values;
+    base::Value::Dict values;
     extension = CreatePlatformAppWithExtraValues(
-        &values, ManifestLocation::kExternalPolicyDownload,
+        values, ManifestLocation::kExternalPolicyDownload,
         extensions::Extension::NO_FLAGS);
     ASSERT_TRUE(extension);
 
@@ -141,9 +137,9 @@
   // Verify that a minimal platform app cannot be installed from location
   // UNPACKED.
   {
-    base::DictionaryValue values;
+    base::Value::Dict values;
     extension = CreatePlatformAppWithExtraValues(
-        &values, ManifestLocation::kUnpacked, extensions::Extension::NO_FLAGS);
+        values, ManifestLocation::kUnpacked, extensions::Extension::NO_FLAGS);
     ASSERT_TRUE(extension);
 
     EXPECT_FALSE(provider.UserMayLoad(extension.get(), &error));
@@ -153,22 +149,20 @@
 
   // Verify that a platform app with all safe manifest entries can be installed.
   {
-    base::DictionaryValue values;
-    values.SetStringKey(extensions::manifest_keys::kDescription, "something");
-    values.SetStringKey(extensions::manifest_keys::kShortName,
-                        "something else");
-    base::ListValue permissions;
+    base::Value::Dict values;
+    values.Set(extensions::manifest_keys::kDescription, "something");
+    values.Set(extensions::manifest_keys::kShortName, "something else");
+    base::Value::List permissions;
     permissions.Append("alarms");
     permissions.Append("background");
-    values.SetKey(extensions::manifest_keys::kPermissions,
-                  std::move(permissions));
-    base::ListValue optional_permissions;
+    values.Set(extensions::manifest_keys::kPermissions, std::move(permissions));
+    base::Value::List optional_permissions;
     optional_permissions.Append("alarms");
     optional_permissions.Append("background");
-    values.SetKey(extensions::manifest_keys::kOptionalPermissions,
-                  std::move(optional_permissions));
+    values.Set(extensions::manifest_keys::kOptionalPermissions,
+               std::move(optional_permissions));
     extension = CreatePlatformAppWithExtraValues(
-        &values, ManifestLocation::kExternalPolicy,
+        values, ManifestLocation::kExternalPolicy,
         extensions::Extension::NO_FLAGS);
     ASSERT_TRUE(extension);
 
@@ -180,10 +174,10 @@
   // Verify that a platform app with a safe manifest entry under "app" can be
   // installed.
   {
-    base::DictionaryValue values;
-    values.SetStringPath("app.content_security_policy", "something2");
+    base::Value::Dict values;
+    values.SetByDottedPath("app.content_security_policy", "something2");
     extension = CreatePlatformAppWithExtraValues(
-        &values, ManifestLocation::kExternalPolicy,
+        values, ManifestLocation::kExternalPolicy,
         extensions::Extension::NO_FLAGS);
     ASSERT_TRUE(extension);
 
@@ -195,12 +189,13 @@
   // Verify that a hosted app with a safe manifest entry under "app" can be
   // installed.
   {
-    base::DictionaryValue values;
-    values.SetKey(extensions::manifest_keys::kApp, base::DictionaryValue());
-    values.SetPath(extensions::manifest_keys::kWebURLs, base::ListValue());
-    values.SetStringPath("app.content_security_policy", "something2");
+    base::Value::Dict values;
+    values.Set(extensions::manifest_keys::kApp, base::Value::Dict());
+    values.SetByDottedPath(extensions::manifest_keys::kWebURLs,
+                           base::Value::List());
+    values.SetByDottedPath("app.content_security_policy", "something2");
     extension = CreateExtensionFromValues(
-        std::string(), ManifestLocation::kExternalPolicy, &values,
+        std::string(), ManifestLocation::kExternalPolicy, values,
         extensions::Extension::NO_FLAGS);
     ASSERT_TRUE(extension);
 
@@ -212,14 +207,15 @@
   // Verify that a platform app with a url_handlers manifest entry and which is
   // installed through the web store can be installed.
   {
-    base::ListValue matches;
+    base::Value::List matches;
     matches.Append("https://example.com/*");
-    base::DictionaryValue values;
-    values.SetPath("url_handlers.example_com.matches", std::move(matches));
-    values.SetStringPath("url_handlers.example_com.title", "example title");
+    base::Value::Dict values;
+    values.SetByDottedPath("url_handlers.example_com.matches",
+                           std::move(matches));
+    values.SetByDottedPath("url_handlers.example_com.title", "example title");
 
     extension = CreatePlatformAppWithExtraValues(
-        &values, ManifestLocation::kExternalPolicy,
+        values, ManifestLocation::kExternalPolicy,
         extensions::Extension::FROM_WEBSTORE);
     ASSERT_TRUE(extension);
 
@@ -230,16 +226,15 @@
 
   // Verify that a platform app with remote URL permissions can be installed.
   {
-    base::ListValue permissions;
+    base::Value::List permissions;
     permissions.Append("https://example.com/");
     permissions.Append("http://example.com/");
     permissions.Append("ftp://example.com/");
-    base::DictionaryValue values;
-    values.SetKey(extensions::manifest_keys::kPermissions,
-                  std::move(permissions));
+    base::Value::Dict values;
+    values.Set(extensions::manifest_keys::kPermissions, std::move(permissions));
 
     extension = CreatePlatformAppWithExtraValues(
-        &values, ManifestLocation::kExternalPolicy,
+        values, ManifestLocation::kExternalPolicy,
         extensions::Extension::NO_FLAGS);
     ASSERT_TRUE(extension);
 
@@ -257,12 +252,11 @@
     socket.Set("socket", std::move(tcp_list));
     base::Value::List permissions;
     permissions.Append(std::move(socket));
-    base::DictionaryValue values;
-    values.SetKey(extensions::manifest_keys::kPermissions,
-                  base::Value(std::move(permissions)));
+    base::Value::Dict values;
+    values.Set(extensions::manifest_keys::kPermissions, std::move(permissions));
 
     extension = CreatePlatformAppWithExtraValues(
-        &values, ManifestLocation::kExternalPolicy,
+        values, ManifestLocation::kExternalPolicy,
         extensions::Extension::NO_FLAGS);
     ASSERT_TRUE(extension);
 
@@ -273,9 +267,9 @@
 
   // Verify that an extension can be installed.
   {
-    base::DictionaryValue values;
+    base::Value::Dict values;
     extension = CreateExtensionFromValues(
-        std::string(), ManifestLocation::kExternalPolicy, &values,
+        std::string(), ManifestLocation::kExternalPolicy, values,
         extensions::Extension::NO_FLAGS);
     ASSERT_TRUE(extension);
 
@@ -286,10 +280,10 @@
 
   // Verify that a shared_module can be installed.
   {
-    base::DictionaryValue values;
-    values.SetPath("export.whitelist", base::ListValue());  // nocheck
+    base::Value::Dict values;
+    values.SetByDottedPath("export.whitelist", base::Value::List());  // nocheck
     extension = CreateExtensionFromValues(
-        std::string(), ManifestLocation::kExternalPolicy, &values,
+        std::string(), ManifestLocation::kExternalPolicy, values,
         extensions::Extension::NO_FLAGS);
     ASSERT_TRUE(extension);
 
@@ -300,10 +294,10 @@
 
   // Verify that a theme can be installed.
   {
-    base::DictionaryValue values;
-    values.SetKey("theme", base::DictionaryValue());
+    base::Value::Dict values;
+    values.Set("theme", base::Value::Dict());
     extension = CreateExtensionFromValues(
-        std::string(), ManifestLocation::kExternalPolicy, &values,
+        std::string(), ManifestLocation::kExternalPolicy, values,
         extensions::Extension::NO_FLAGS);
     ASSERT_TRUE(extension);
 
diff --git a/chrome/browser/ash/extensions/file_manager/private_api_misc.cc b/chrome/browser/ash/extensions/file_manager/private_api_misc.cc
index a2af9761..504bead 100644
--- a/chrome/browser/ash/extensions/file_manager/private_api_misc.cc
+++ b/chrome/browser/ash/extensions/file_manager/private_api_misc.cc
@@ -740,8 +740,10 @@
   using ash::file_system_provider::Service;
   Service* const service = Service::Get(browser_context());
 
-  if (!service->RequestMount(ProviderId::FromString(params->provider_id)))
+  if (!service->RequestMount(ProviderId::FromString(params->provider_id),
+                             base::DoNothing())) {
     return RespondNow(Error("Failed to request a new mount."));
+  }
 
   return RespondNow(WithArguments());
 }
diff --git a/chrome/browser/ash/file_manager/file_manager_browsertest_base.cc b/chrome/browser/ash/file_manager/file_manager_browsertest_base.cc
index dde491b5..72fb93e7 100644
--- a/chrome/browser/ash/file_manager/file_manager_browsertest_base.cc
+++ b/chrome/browser/ash/file_manager/file_manager_browsertest_base.cc
@@ -53,6 +53,7 @@
 #include "chrome/browser/apps/app_service/app_service_proxy.h"
 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
 #include "chrome/browser/apps/app_service/browser_app_launcher.h"
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
 #include "chrome/browser/ash/arc/fileapi/arc_documents_provider_util.h"
 #include "chrome/browser/ash/arc/fileapi/arc_media_view_util.h"
 #include "chrome/browser/ash/base/locale_util.h"
@@ -65,6 +66,7 @@
 #include "chrome/browser/ash/extensions/file_manager/event_router_factory.h"
 #include "chrome/browser/ash/file_manager/app_id.h"
 #include "chrome/browser/ash/file_manager/file_manager_test_util.h"
+#include "chrome/browser/ash/file_manager/file_tasks.h"
 #include "chrome/browser/ash/file_manager/file_tasks_notifier.h"
 #include "chrome/browser/ash/file_manager/file_tasks_observer.h"
 #include "chrome/browser/ash/file_manager/mount_test_util.h"
@@ -87,7 +89,6 @@
 #include "chrome/browser/platform_util.h"
 #include "chrome/browser/sync_file_system/mock_remote_file_sync_service.h"
 #include "chrome/browser/sync_file_system/sync_file_system_service_factory.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
 #include "chrome/browser/ui/ash/system_web_apps/system_web_app_ui_utils.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_finder.h"
@@ -2841,6 +2842,13 @@
     return;
   }
 
+  if (name == "setOfficeSetupComplete") {
+    absl::optional<bool> complete = value.FindBool("complete");
+    ASSERT_TRUE(complete.has_value());
+    file_tasks::SetOfficeSetupComplete(profile(), complete.value());
+    return;
+  }
+
   if (name == "setDriveEnabled") {
     absl::optional<bool> enabled = value.FindBool("enabled");
     ASSERT_TRUE(enabled.has_value());
diff --git a/chrome/browser/ash/file_manager/file_manager_jstest.cc b/chrome/browser/ash/file_manager/file_manager_jstest.cc
index 4b1dfbd..13e4ae7 100644
--- a/chrome/browser/ash/file_manager/file_manager_jstest.cc
+++ b/chrome/browser/ash/file_manager/file_manager_jstest.cc
@@ -353,3 +353,7 @@
 IN_PROC_BROWSER_TEST_F(FileManagerJsTest, XfTreeItem) {
   RunTestURL("widgets/xf_tree_item_unittest.js");
 }
+
+IN_PROC_BROWSER_TEST_F(FileManagerJsTest, XfIcon) {
+  RunTestURL("widgets/xf_icon_unittest.js");
+}
diff --git a/chrome/browser/ash/file_manager/file_tasks.cc b/chrome/browser/ash/file_manager/file_tasks.cc
index f6488c92..566b1f8 100644
--- a/chrome/browser/ash/file_manager/file_tasks.cc
+++ b/chrome/browser/ash/file_manager/file_tasks.cc
@@ -416,32 +416,6 @@
   return num_opened > 0;
 }
 
-// Open a hosted MS Office file e.g. .docx, from a url hosted in DriveFS.
-void OpenHostedOfficeFile(Profile* profile,
-                          const TaskDescriptor& task,
-                          const std::vector<FileSystemURL>& file_urls,
-                          drive::FileError error,
-                          drivefs::mojom::FileMetadataPtr metadata) {
-  if (error != drive::FILE_ERROR_OK) {
-    UMA_HISTOGRAM_ENUMERATION(kDriveErrorMetricName,
-                              OfficeDriveErrors::NO_METADATA);
-    LOG(ERROR) << "Drive metadata error: " << error;
-    return;
-  }
-
-  GURL hosted_url(metadata->alternate_url);
-  bool opened = util::OpenNewTabForHostedOfficeFile(hosted_url);
-
-  if (opened) {
-    UMA_HISTOGRAM_ENUMERATION(kDriveTaskResultMetricName,
-                              OfficeTaskResult::OPENED);
-  } else {
-    GetUserFallbackChoice(
-        profile, task, file_urls,
-        ash::office_fallback::FallbackReason::kInvalidGoogleDocsURL);
-  }
-}
-
 bool ExecuteWebDriveOfficeTask(Profile* profile,
                                const TaskDescriptor& task,
                                const std::vector<FileSystemURL>& file_urls) {
@@ -460,23 +434,8 @@
       drive::DriveIntegrationServiceFactory::FindForProfile(profile);
   if (integration_service && integration_service->IsMounted() &&
       integration_service->GetDriveFsInterface()) {
-    base::FilePath relative_path;
-    base::FilePath first_file_path = file_urls.front().path();
-    if (integration_service->GetRelativeDrivePath(first_file_path,
-                                                  &relative_path)) {
-      // The file is on Drive already: Open the URL.
-      integration_service->GetDriveFsInterface()->GetMetadata(
-          relative_path,
-          base::BindOnce(&OpenHostedOfficeFile, profile, task, file_urls));
-      return true;
-    } else {
-      // We need to move the file to Drive first. This flow will eventually
-      // open the file in the browser, too.
-      // TODO(b/247038054) Add user preference to decide whether or not the
-      // dialog should be shown.
-      return ash::cloud_upload::UploadAndOpen(
-          profile, file_urls, ash::cloud_upload::CloudProvider::kGoogleDrive);
-    }
+    return ash::cloud_upload::OpenFilesWithCloudProvider(
+        profile, file_urls, ash::cloud_upload::CloudProvider::kGoogleDrive);
   } else {
     UMA_HISTOGRAM_ENUMERATION(kDriveErrorMetricName,
                               OfficeDriveErrors::DRIVEFS_INTERFACE);
@@ -491,49 +450,6 @@
 using ash::file_system_provider::ProviderId;
 using ash::file_system_provider::Service;
 
-bool FileIsOnODFS(const FileSystemURL& url, Profile* profile) {
-  ash::file_system_provider::util::FileSystemURLParser parser(url);
-  if (!parser.Parse()) {
-    LOG(ERROR) << "Path not in FSP";
-    return false;
-  }
-
-  ProviderId provider_id = ProviderId::CreateFromExtensionId(kODFSExtensionId);
-  if (parser.file_system()->GetFileSystemInfo().provider_id() != provider_id) {
-    LOG(ERROR) << "Path on another FSP";
-    return false;
-  }
-  return true;
-}
-
-// Pre-condition: |url| is for a file which is on ODFS already.
-void OpenODFSUrl(Profile* profile,
-                 const TaskDescriptor& task,
-                 const std::vector<FileSystemURL>& file_urls) {
-  const FileSystemURL& url = file_urls.front();
-  ash::file_system_provider::util::FileSystemURLParser parser(url);
-
-  if (!parser.Parse()) {
-    LOG(ERROR) << "Path not in FSP";
-    return;
-  }
-
-  parser.file_system()->ExecuteAction(
-      {parser.file_path()}, kActionIdOpenWeb,
-      base::BindOnce(
-          [](Profile* profile, const TaskDescriptor& task,
-             const std::vector<FileSystemURL>& file_urls,
-             base::File::Error result) {
-            if (result != base::File::Error::FILE_OK) {
-              LOG(ERROR) << "Error executing action: " << result;
-              GetUserFallbackChoice(
-                  profile, task, file_urls,
-                  ash::office_fallback::FallbackReason::kErrorOpeningWeb);
-            }
-          },
-          profile, task, file_urls));
-}
-
 bool ExecuteOpenInOfficeTask(Profile* profile,
                              const TaskDescriptor& task,
                              const std::vector<FileSystemURL>& file_urls) {
@@ -544,19 +460,8 @@
     // TODO(petermarshall): UMAs.
   }
 
-  if (FileIsOnODFS(file_urls.front(), profile)) {
-    OpenODFSUrl(profile, task, file_urls);
-    LOG(ERROR) << "File is on ODFS";
-    return true;
-  } else {
-    // We need to move the file to ODFS first. This flow will eventually open
-    // the file in the browser, too.
-    // TODO(b/247038054) Add user preference to decide whether or not the
-    // dialog should be shown.
-    LOG(ERROR) << "File can be moved to ODFS";
-    return ash::cloud_upload::UploadAndOpen(
-        profile, file_urls, ash::cloud_upload::CloudProvider::kOneDrive);
-  }
+  return ash::cloud_upload::OpenFilesWithCloudProvider(
+      profile, file_urls, ash::cloud_upload::CloudProvider::kOneDrive);
 }
 
 }  // namespace
diff --git a/chrome/browser/ash/file_manager/file_tasks_browsertest.cc b/chrome/browser/ash/file_manager/file_tasks_browsertest.cc
index 5325dc7..0ad73be 100644
--- a/chrome/browser/ash/file_manager/file_tasks_browsertest.cc
+++ b/chrome/browser/ash/file_manager/file_tasks_browsertest.cc
@@ -37,6 +37,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/web_applications/web_app_launch_manager.h"
+#include "chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_dialog.h"
 #include "chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_util.h"
 #include "chrome/browser/ui/webui/ash/office_fallback/office_fallback_ui.h"
 #include "chrome/browser/web_applications/test/profile_test_helper.h"
@@ -582,6 +583,15 @@
                         full_action_id);
 }
 
+const FileSystemURL CreateTestOfficeFile(Profile* profile) {
+  base::FilePath file =
+      util::GetMyFilesFolderForProfile(profile).AppendASCII("text.docx");
+  GURL url;
+  CHECK(util::ConvertAbsoluteFilePathToFileSystemUrl(
+      profile, file, util::GetFileManagerURL(), &url));
+  return FileSystemURL::CreateForTest(url);
+}
+
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
 // This test only runs with the is_chrome_branded GN flag set because otherwise
 // QuickOffice is not installed.
@@ -610,7 +620,7 @@
 #endif  // BUILDFLAG(GOOGLE_CHROME_BRANDING)
 
 // TODO(cassycc): move this class to a more appropriate spot.
-// Fake DriveFs specific to the `OfficeFallbackDriveTest`. Allows a test file to
+// Fake DriveFs specific to the `DriveTest`. Allows a test file to
 // be "added" to the DriveFs via `SetMetadata()`. The `alternate_url` of the
 // file can be retrieved via `GetMetadata()`. This a simplified version of
 // `FakeDriveFs` because the only condition for the file to be in the DriveFs is
@@ -649,7 +659,7 @@
 };
 
 // TODO(cassycc): move this class to a more appropriate spot
-// Fake DriveFs helper specific to the `OfficeFallbackDriveTest`. Implements the
+// Fake DriveFs helper specific to the `DriveTest`. Implements the
 // functions to create a `FakeSimpleDriveFs`.
 class FakeSimpleDriveFsHelper : public drive::FakeDriveFsHelper {
  public:
@@ -672,14 +682,13 @@
   FakeSimpleDriveFs fake_drivefs_;
 };
 
-
-
-
-// Tests the office fallback flow that occurs when a user fails to open an
-// office file from Drive.
-class OfficeFallbackDriveTest : public InProcessBrowserTest {
+// TODO(cassycc or petermarshall) share this class with other test files for
+// testing with a fake DriveFs.
+// Tests the office fallback flow that occurs when
+// a user fails to open an office file from Drive.
+class DriveTest : public InProcessBrowserTest {
  public:
-  OfficeFallbackDriveTest() {
+  DriveTest() {
     feature_list_.InitAndEnableFeature(ash::features::kUploadOfficeToCloud);
     EXPECT_TRUE(temp_dir_.CreateUniqueTempDir());
     drive_mount_point_ = temp_dir_.GetPath();
@@ -688,14 +697,13 @@
     relative_test_file_path = base::FilePath("/").AppendASCII(test_file_name_);
   }
 
-  OfficeFallbackDriveTest(const OfficeFallbackDriveTest&) = delete;
-  OfficeFallbackDriveTest& operator=(const OfficeFallbackDriveTest&) = delete;
+  DriveTest(const DriveTest&) = delete;
+  DriveTest& operator=(const DriveTest&) = delete;
 
   void SetUpInProcessBrowserTestFixture() override {
     // Setup drive integration service.
     create_drive_integration_service_ = base::BindRepeating(
-        &OfficeFallbackDriveTest::CreateDriveIntegrationService,
-        base::Unretained(this));
+        &DriveTest::CreateDriveIntegrationService, base::Unretained(this));
     service_factory_for_test_ = std::make_unique<
         drive::DriveIntegrationServiceFactory::ScopedFactoryForTest>(
         &create_drive_integration_service_);
@@ -741,7 +749,7 @@
   // Complete the set up of the fake DriveFs with a test file added.
   void SetUpTest() {
     // Install QuickOffice for the check in GetUserFallbackChoice() before
-    // the dialog can launched.
+    // the office fallback dialog can launched.
     test::AddDefaultComponentExtensionsOnMainThread(profile());
 
     // Create Drive root directory.
@@ -787,10 +795,14 @@
 // Test to check that the test file fails to open when the system is offline but
 // is successfully opened with a "try-again" dialog choice after the
 // systems comes online.
-IN_PROC_BROWSER_TEST_F(OfficeFallbackDriveTest, DriveTryAgain) {
+IN_PROC_BROWSER_TEST_F(DriveTest, OfficeFallbackTryAgain) {
   // Add test file to fake DriveFs.
   SetUpTest();
 
+  // Disable the setup flow for office files because we want the office
+  // fallback dialog to run instead.
+  SetOfficeSetupComplete(profile(), true);
+
   const TaskDescriptor web_drive_office_task = CreateWebDriveOfficeTask();
   std::vector<storage::FileSystemURL> file_urls{drive_test_file_url_};
 
@@ -800,14 +812,14 @@
       expected_dialog_URL);
   navigation_observer_dialog.StartWatchingNewWebContents();
 
-  // Fails as system is offline and thus will open dialog.
+  // Fails as system is offline and thus will open office fallback dialog.
   ExecuteFileTask(
       profile(), web_drive_office_task, file_urls,
       base::BindOnce(
           [](extensions::api::file_manager_private::TaskResult result,
              std::string error_message) {}));
 
-  // Wait for dialog to open.
+  // Wait for office fallback dialog to open.
   navigation_observer_dialog.Wait();
   ASSERT_TRUE(navigation_observer_dialog.last_navigation_succeeded());
 
@@ -829,10 +841,97 @@
   // Wait for file to open in web drive office.
   navigation_observer_office.Wait();
 }
+#endif  // BUILDFLAG(GOOGLE_CHROME_BRANDING)
+
+// Test that OpenOrMoveFiles() will open a DriveFs office file when the cloud
+// provider specified is Google Drive.
+IN_PROC_BROWSER_TEST_F(DriveTest, OpenFileInDrive) {
+  // Add test file to fake DriveFs.
+  SetUpTest();
+
+  std::vector<storage::FileSystemURL> file_urls{drive_test_file_url_};
+
+  // Start watching for the opening of `expected_web_drive_office_url`. The
+  // query parameter is concatenated to the URL as office files opened from
+  // drive have this query parameter added (https://crrev.com/c/3867338).
+  GURL expected_web_drive_office_url(alternate_url_ + "&cros_files=true");
+  content::TestNavigationObserver navigation_observer_office(
+      expected_web_drive_office_url);
+  navigation_observer_office.StartWatchingNewWebContents();
+
+  ash::cloud_upload::OpenOrMoveFiles(
+      profile(), file_urls, ash::cloud_upload::CloudProvider::kGoogleDrive);
+
+  // Wait for file to open in web drive office.
+  navigation_observer_office.Wait();
+}
+
+// Test that the setup flow for office files, that has never been run before,
+// will be run when a Web Drive Office task tries to open an office file
+// already in DriveFs.
+IN_PROC_BROWSER_TEST_F(DriveTest, FileInDriveOpensSetUpDialog) {
+  // Add test file to fake DriveFs.
+  SetUpTest();
+
+  SetConnectionOnline();
+
+  // Create a Web Drive Office task to open the file from DriveFs. The file is
+  // in the correct location for this task.
+  const TaskDescriptor web_drive_office_task = CreateWebDriveOfficeTask();
+  std::vector<storage::FileSystemURL> file_urls{drive_test_file_url_};
+
+  // Watch for dialog URL chrome://cloud-upload.
+  GURL expected_dialog_URL(chrome::kChromeUICloudUploadURL);
+  content::TestNavigationObserver navigation_observer_dialog(
+      expected_dialog_URL);
+  navigation_observer_dialog.StartWatchingNewWebContents();
+
+  // Triggers setup flow.
+  ExecuteFileTask(profile(), web_drive_office_task, file_urls,
+                  base::DoNothing());
+
+  // Wait for setup flow dialog to open.
+  navigation_observer_dialog.Wait();
+  ASSERT_TRUE(navigation_observer_dialog.last_navigation_succeeded());
+}
+
+// Test that the setup flow for office files, that has never been run before,
+// will be run when a Web Drive Office task tries to open an office file not
+// already in DriveFs.
+IN_PROC_BROWSER_TEST_F(DriveTest, FileNotInDriveOpensSetUpDialog) {
+  // Set up DriveFs.
+  SetUpTest();
+
+  SetConnectionOnline();
+
+  // Create a Web Drive Office task to open the file from DriveFs. The file is
+  // not in the correct location for this task and would have to be moved to
+  // DriveFs.
+  const TaskDescriptor web_drive_office_task = CreateWebDriveOfficeTask();
+  FileSystemURL file_outside_drive = CreateTestOfficeFile(profile());
+  std::vector<storage::FileSystemURL> file_urls{file_outside_drive};
+
+  // Watch for dialog URL chrome://cloud-upload.
+  GURL expected_dialog_URL(chrome::kChromeUICloudUploadURL);
+  content::TestNavigationObserver navigation_observer_dialog(
+      expected_dialog_URL);
+  navigation_observer_dialog.StartWatchingNewWebContents();
+
+  // Triggers setup flow.
+  ExecuteFileTask(
+      profile(), web_drive_office_task, file_urls,
+      base::BindOnce(
+          [](extensions::api::file_manager_private::TaskResult result,
+             std::string error_message) {}));
+
+  // Wait for setup flow dialog to open.
+  navigation_observer_dialog.Wait();
+  ASSERT_TRUE(navigation_observer_dialog.last_navigation_succeeded());
+}
 
 // TODO(cassycc): move this class to a more appropriate spot
 // Fake provided file system implementation specific to the
-// `OfficeFallbackOneDriveTest`. Notifies the `OfficeFallbackOneDriveTest` upon
+// `OneDriveTest`. Notifies the `OneDriveTest` upon
 // the "OPEN_WEB" action on the file system.
 class FakeProvidedFileSystemOneDrive
     : public ash::file_system_provider::FakeProvidedFileSystem {
@@ -848,7 +947,7 @@
       const std::string& action_id,
       storage::AsyncFileUtil::StatusCallback callback) override {
     // When the "OPEN_WEB" action is observed, notify the
-    // `OfficeFallbackOneDriveTest` via the `callback_`.
+    // `OneDriveTest` via the `callback_`.
     if (action_id == file_manager::file_tasks::kActionIdOpenWeb) {
       std::move(callback_).Run();
     }
@@ -857,12 +956,12 @@
   }
 
  protected:
-  // OfficeFallbackOneDriveTest::OpenWebAction.
+  // OneDriveTest::OpenWebAction.
   base::OnceClosure callback_;
 };
 
 // TODO(cassycc): move this class to a more appropriate spot
-// Fake extension provider specific to the `OfficeFallbackOneDriveTest`.
+// Fake extension provider specific to the `OneDriveTest`.
 // Implements the functions to create a `FakeProvidedFileSystemOneDrive` with a
 // test file added and passes along the appropriate `callback`.
 class FakeExtensionProviderOneDrive
@@ -910,26 +1009,27 @@
         relative_test_file_path_(relative_test_file_path),
         test_file_name_(test_file_name) {}
 
-  // OfficeFallbackOneDriveTest::OpenWebAction.
+  // OneDriveTest::OpenWebAction.
   base::OnceClosure callback_;
   const base::FilePath relative_test_file_path_;
   std::string test_file_name_;
 };
 
-// Tests the office fallback flow that occurs when a user fails to open an
-// office file from OneDrive.
-class OfficeFallbackOneDriveTest : public InProcessBrowserTest {
+// TODO(cassycc or petermarshall) share this class with other test files for
+// testing with a fake ODFS.
+// Tests the office fallback flow that occurs when a
+// user fails to open an office file from ODFS.
+class OneDriveTest : public InProcessBrowserTest {
  public:
-  OfficeFallbackOneDriveTest() {
+  OneDriveTest() {
     feature_list_.InitAndEnableFeature(ash::features::kUploadOfficeToCloud);
     test_file_name_ = "text.docx";
     relative_test_file_path_ = base::FilePath(test_file_name_);
     file_system_id_ = "odfs";
   }
 
-  OfficeFallbackOneDriveTest(const OfficeFallbackOneDriveTest&) = delete;
-  OfficeFallbackOneDriveTest& operator=(const OfficeFallbackOneDriveTest&) =
-      delete;
+  OneDriveTest(const OneDriveTest&) = delete;
+  OneDriveTest& operator=(const OneDriveTest&) = delete;
 
   void TearDown() override {
     InProcessBrowserTest::TearDown();
@@ -944,24 +1044,24 @@
   // added. Installs QuickOffice for the check in GetUserFallbackChoice() before
   // the dialog can launched.
   void SetUpTest() {
-    // Install QuickOffice.
+    // Install QuickOffice for the check in GetUserFallbackChoice() before
+    // the office fallback dialog can launched.
     test::AddDefaultComponentExtensionsOnMainThread(browser()->profile());
 
     service_ = ash::file_system_provider::Service::Get(profile());
-    // Set `OfficeFallbackOneDriveTest::OpenWebAction` as the callback for the
+    // Set `OneDriveTest::OpenWebAction` as the callback for the
     // `FakeProvidedFileSystemOneDrive`. The use of `base::Unretained()` is safe
     // because the class will exist for the duration of the test.
     service_->RegisterProvider(FakeExtensionProviderOneDrive::Create(
         kODFSExtensionId, relative_test_file_path_, test_file_name_,
-        base::BindOnce(&OfficeFallbackOneDriveTest::OpenWebAction,
-                       base::Unretained(this))));
+        base::BindOnce(&OneDriveTest::OpenWebAction, base::Unretained(this))));
     provider_id_ = ash::file_system_provider::ProviderId::CreateFromExtensionId(
         kODFSExtensionId);
     ash::file_system_provider::MountOptions options(file_system_id_, "ODFS");
     EXPECT_EQ(base::File::FILE_OK,
               service_->MountFileSystem(provider_id_, options));
 
-    // Get URL for test file in OneDrive.
+    // Get URL for test file in ODFS.
     one_drive_test_file_url_ = ash::cloud_upload::FilePathToFileSystemURL(
         profile(),
         file_manager::util::GetFileManagerFileSystemContext(profile()),
@@ -1010,34 +1110,49 @@
   std::string test_file_name_;
 };
 
+#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
 // Test to check that the test file fails to open when the system is offline but
 // is successfully opened with a "try-again" dialog choice after the
 // systems comes online.
-IN_PROC_BROWSER_TEST_F(OfficeFallbackOneDriveTest, OneDriveTryAgain) {
+IN_PROC_BROWSER_TEST_F(OneDriveTest, OfficeFallbackTryAgain) {
   // Creates a fake ODFS with a test file.
   SetUpTest();
 
-  const TaskDescriptor open_in_office_task = CreateWebDriveOfficeTask();
+  // Disable the setup flow for office files because we want the office
+  // fallback dialog to run instead.
+  SetOfficeSetupComplete(profile(), true);
+
+  const TaskDescriptor open_in_office_task = CreateOpenInOfficeTask();
   std::vector<storage::FileSystemURL> file_urls{one_drive_test_file_url_};
 
-  // This boolean only becomes `True` if the fake provided OneDrive file system
+  // Watch for dialog URL chrome://office-fallback.
+  GURL expected_dialog_URL(chrome::kChromeUIOfficeFallbackURL);
+  content::TestNavigationObserver navigation_observer_dialog(
+      expected_dialog_URL);
+  navigation_observer_dialog.StartWatchingNewWebContents();
+
+  // This boolean only becomes `True` if the fake provided ODFS
   // observes the test file being opened.
   file_opened_ = false;
 
-  // Fails as system is offline and thus will open dialog.
+  // Fails as system is offline and thus will open office fallback dialog.
   ExecuteFileTask(
       profile(), open_in_office_task, file_urls,
       base::BindOnce(
           [](extensions::api::file_manager_private::TaskResult result,
              std::string error_message) {}));
 
+  // Wait for office fallback dialog to open.
+  navigation_observer_dialog.Wait();
+  ASSERT_TRUE(navigation_observer_dialog.last_navigation_succeeded());
+
   ASSERT_FALSE(file_opened_);
 
   SetConnectionOnline();
 
   // Run dialog callback, simulate user choosing to "try-again". Will succeed
   // because system is online.
-  OnDialogChoiceReceived(profile(), CreateOpenInOfficeTask(), file_urls,
+  OnDialogChoiceReceived(profile(), open_in_office_task, file_urls,
                          ash::office_fallback::kDialogChoiceTryAgain);
 
   ASSERT_TRUE(file_opened_);
@@ -1046,40 +1161,149 @@
 // Test to check that the test file fails to open when the system is offline and
 // does not open from a "cancel" dialog choice even when the systems comes
 // online.
-IN_PROC_BROWSER_TEST_F(OfficeFallbackOneDriveTest, OneDriveCancel) {
+IN_PROC_BROWSER_TEST_F(OneDriveTest, OfficeFallbackCancel) {
   // Creates a fake ODFS with a test file.
   SetUpTest();
 
-  const TaskDescriptor open_in_office_task = CreateWebDriveOfficeTask();
+  // Disable the setup flow for office files because we want the office
+  // fallback dialog to run instead.
+  SetOfficeSetupComplete(profile(), true);
+
+  const TaskDescriptor open_in_office_task = CreateOpenInOfficeTask();
   std::vector<storage::FileSystemURL> file_urls{one_drive_test_file_url_};
 
-  // This boolean only becomes `True` if the fake provided OneDrive file system
+  // Watch for dialog URL chrome://office-fallback.
+  GURL expected_dialog_URL(chrome::kChromeUIOfficeFallbackURL);
+  content::TestNavigationObserver navigation_observer_dialog(
+      expected_dialog_URL);
+  navigation_observer_dialog.StartWatchingNewWebContents();
+
+  // This boolean only becomes `True` if the fake provided ODFS
   // observes the test file being opened.
   file_opened_ = false;
 
-  // Fails as system is offline and thus will open dialog.
+  // Fails as system is offline and thus will open office fallback dialog.
   ExecuteFileTask(
       profile(), open_in_office_task, file_urls,
       base::BindOnce(
           [](extensions::api::file_manager_private::TaskResult result,
              std::string error_message) {}));
 
+  // Wait for office fallback dialog to open.
+  navigation_observer_dialog.Wait();
+  ASSERT_TRUE(navigation_observer_dialog.last_navigation_succeeded());
+
   ASSERT_FALSE(file_opened_);
 
   SetConnectionOnline();
 
   // Run dialog callback, simulate user choosing to "cancel". The file will not
   // open.
-  OnDialogChoiceReceived(profile(), CreateOpenInOfficeTask(), file_urls,
+  OnDialogChoiceReceived(profile(), open_in_office_task, file_urls,
                          ash::office_fallback::kDialogChoiceCancel);
 
   ASSERT_FALSE(file_opened_);
 }
-
 #endif  // BUILDFLAG(GOOGLE_CHROME_BRANDING)
 
-// TODO(cassycc) figure out how to run all browser test types for
-// Office Fallback tests?
+// Test that OpenOrMoveFiles() will open a ODFS office file when the cloud
+// provider specified is OneDrive.
+IN_PROC_BROWSER_TEST_F(OneDriveTest, OpenFileInOneDrive) {
+  // Creates a fake ODFS with a test file.
+  SetUpTest();
+
+  std::vector<storage::FileSystemURL> file_urls{one_drive_test_file_url_};
+
+  // This boolean only becomes `True` if the fake provided ODFS
+  // observes the test file being opened.
+  file_opened_ = false;
+
+  ash::cloud_upload::OpenOrMoveFiles(
+      profile(), file_urls, ash::cloud_upload::CloudProvider::kOneDrive);
+
+  ASSERT_TRUE(file_opened_);
+}
+
+// Test that OpenOrMoveFiles() will open the Move Confirmation dialog when the
+// cloud provider specified is OneDrive but the office file to be opened needs
+// to be moved to OneDrive.
+IN_PROC_BROWSER_TEST_F(OneDriveTest, OpenFileNotInOneDrive) {
+  FileSystemURL file_outside_one_drive = CreateTestOfficeFile(profile());
+  std::vector<storage::FileSystemURL> file_urls{file_outside_one_drive};
+
+  // Watch for dialog URL chrome://cloud-upload.
+  GURL expected_dialog_URL(chrome::kChromeUICloudUploadURL);
+  content::TestNavigationObserver navigation_observer_dialog(
+      expected_dialog_URL);
+  navigation_observer_dialog.StartWatchingNewWebContents();
+
+  // Triggers Move Confirmation dialog.
+  ash::cloud_upload::OpenOrMoveFiles(
+      profile(), file_urls, ash::cloud_upload::CloudProvider::kOneDrive);
+
+  // Wait for setup flow dialog to open.
+  navigation_observer_dialog.Wait();
+  ASSERT_TRUE(navigation_observer_dialog.last_navigation_succeeded());
+}
+
+// Test that the setup flow for office files, that has never been run before,
+// will be run when an Open in Office task tries to open an office file
+// already in ODFS.
+IN_PROC_BROWSER_TEST_F(OneDriveTest, FileInOneDriveOpensSetUpDialog) {
+  // Creates a fake ODFS with a test file.
+  SetUpTest();
+
+  SetConnectionOnline();
+
+  // Create an Open in Office task to open the file from ODFS. The file is in
+  // the correct location for this task.
+  const TaskDescriptor open_in_office_task = CreateOpenInOfficeTask();
+  std::vector<storage::FileSystemURL> file_urls{one_drive_test_file_url_};
+
+  // Watch for dialog URL chrome://cloud-upload.
+  GURL expected_dialog_URL(chrome::kChromeUICloudUploadURL);
+  content::TestNavigationObserver navigation_observer_dialog(
+      expected_dialog_URL);
+  navigation_observer_dialog.StartWatchingNewWebContents();
+
+  // Triggers setup flow.
+  ExecuteFileTask(profile(), open_in_office_task, file_urls, base::DoNothing());
+
+  // Wait for setup flow dialog to open.
+  navigation_observer_dialog.Wait();
+  ASSERT_TRUE(navigation_observer_dialog.last_navigation_succeeded());
+}
+
+// Test that the setup flow for office files, that has never been run before,
+// will be run when an Open in Office task tries to open an office file not
+// already in ODFS.
+IN_PROC_BROWSER_TEST_F(OneDriveTest, FileNotInOneDriveOpensSetUpDialog) {
+  SetConnectionOnline();
+
+  // Create an Open in Office task to open the file from ODFS. The file is not
+  // in the correct location for this task and would have to be moved to ODFS.
+  const TaskDescriptor open_in_office_task = CreateOpenInOfficeTask();
+  FileSystemURL file_outside_one_drive = CreateTestOfficeFile(profile());
+  std::vector<storage::FileSystemURL> file_urls{file_outside_one_drive};
+
+  // Watch for dialog URL chrome://cloud-upload.
+  GURL expected_dialog_URL(chrome::kChromeUICloudUploadURL);
+  content::TestNavigationObserver navigation_observer_dialog(
+      expected_dialog_URL);
+  navigation_observer_dialog.StartWatchingNewWebContents();
+
+  // Triggers setup flow.
+  ExecuteFileTask(
+      profile(), open_in_office_task, file_urls,
+      base::BindOnce(
+          [](extensions::api::file_manager_private::TaskResult result,
+             std::string error_message) {}));
+
+  // Wait for setup flow dialog to open.
+  navigation_observer_dialog.Wait();
+  ASSERT_TRUE(navigation_observer_dialog.last_navigation_succeeded());
+}
+
 INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_ALL_PROFILE_TYPES_P(
     FileTasksBrowserTest);
 
diff --git a/chrome/browser/ash/file_manager/volume_manager.cc b/chrome/browser/ash/file_manager/volume_manager.cc
index 9513774..9519f9bb 100644
--- a/chrome/browser/ash/file_manager/volume_manager.cc
+++ b/chrome/browser/ash/file_manager/volume_manager.cc
@@ -710,15 +710,9 @@
   VLOG(1) << *this << "::Initialize";
 
   // If in the Sign in profile or the lock screen app profile or lock screen
-  // profile (i.e. if ash::ProfileHelper::IsUserProfile(etc) returns false),
-  // skip mounting and listening for mount events.
-  //
-  // Ditto (return early) for incognito profiles. We largely treat
-  // VolumeManager as a per-login singleton regarding its control-plane duties.
-  if (!ash::ProfileHelper::IsUserProfile(profile_) ||
-      profile_->IsIncognitoProfile()) {
-    VLOG(1) << *this << ": Not an incogntio or a user profile: "
-            << profile_->GetDebugName();
+  // profile, skip mounting and listening for mount events.
+  if (!ash::ProfileHelper::IsUserProfile(profile_)) {
+    VLOG(1) << *this << ": Not a user profile: " << profile_->GetDebugName();
     return;
   }
 
@@ -1330,12 +1324,14 @@
                                                  volume_context);
 
   // Register the fusebox FSP storage device with chrome::storage.
-  bool result = mount_points->RegisterFileSystem(
-      base::StrCat(
-          {util::kFuseBoxMountNamePrefix, util::kFuseBoxSubdirPrefixFSP, fsid}),
-      storage::kFileSystemTypeFuseBox, storage::FileSystemMountOption(),
-      volume_with_fusebox->mount_path());
-  DCHECK(result);
+  if (!profile_->IsIncognitoProfile()) {
+    bool result = mount_points->RegisterFileSystem(
+        base::StrCat({util::kFuseBoxMountNamePrefix,
+                      util::kFuseBoxSubdirPrefixFSP, fsid}),
+        storage::kFileSystemTypeFuseBox, storage::FileSystemMountOption(),
+        volume_with_fusebox->mount_path());
+    DCHECK(result);
+  }
 
   // Mount the fusebox FSP storage device in files app.
   DoMountEvent(std::move(volume_with_fusebox));
@@ -1532,10 +1528,12 @@
 
   // Register the MTP storage device with chrome::storage.
   auto* mount_points = storage::ExternalMountPoints::GetSystemInstance();
-  bool result_sans_fusebox = mount_points->RegisterFileSystem(
-      fsid, storage::kFileSystemTypeDeviceMediaAsFileStorage,
-      storage::FileSystemMountOption(), path);
-  DCHECK(result_sans_fusebox);
+  if (!profile_->IsIncognitoProfile()) {
+    bool result = mount_points->RegisterFileSystem(
+        fsid, storage::kFileSystemTypeDeviceMediaAsFileStorage,
+        storage::FileSystemMountOption(), path);
+    DCHECK(result);
+  }
 
   // Register the MTP storage device with the MTPDeviceMapService.
   content::GetIOThreadTaskRunner({})->PostTask(
@@ -1566,11 +1564,13 @@
       Volume::CreateForFuseBoxMTP(mount_path, label, read_only);
 
   // Register the fusebox MTP storage device with chrome::storage.
-  bool result_with_fusebox = mount_points->RegisterFileSystem(
-      base::StrCat({util::kFuseBoxMountNamePrefix, subdir}),
-      storage::kFileSystemTypeFuseBox, storage::FileSystemMountOption(),
-      volume_with_fusebox->mount_path());
-  DCHECK(result_with_fusebox);
+  if (!profile_->IsIncognitoProfile()) {
+    bool result = mount_points->RegisterFileSystem(
+        base::StrCat({util::kFuseBoxMountNamePrefix, subdir}),
+        storage::kFileSystemTypeFuseBox, storage::FileSystemMountOption(),
+        volume_with_fusebox->mount_path());
+    DCHECK(result);
+  }
 
   // Mount the fusebox MTP storage device in files app.
   DoMountEvent(std::move(volume_with_fusebox));
@@ -1653,11 +1653,13 @@
                                          summary, icon_url, read_only, subdir);
 
   // Register the fusebox ADP storage device with chrome::storage.
-  bool result = mount_points->RegisterFileSystem(
-      base::StrCat({util::kFuseBoxMountNamePrefix, subdir}),
-      storage::kFileSystemTypeFuseBox, storage::FileSystemMountOption(),
-      volume->mount_path());
-  DCHECK(result);
+  if (!profile_->IsIncognitoProfile()) {
+    bool result = mount_points->RegisterFileSystem(
+        base::StrCat({util::kFuseBoxMountNamePrefix, subdir}),
+        storage::kFileSystemTypeFuseBox, storage::FileSystemMountOption(),
+        volume->mount_path());
+    DCHECK(result);
+  }
 
   // Mount the fusebox ADP storage device in files app.
   DoMountEvent(std::move(volume));
diff --git a/chrome/browser/ash/file_system_provider/extension_provider.cc b/chrome/browser/ash/file_system_provider/extension_provider.cc
index 0500ae10..1442390c 100644
--- a/chrome/browser/ash/file_system_provider/extension_provider.cc
+++ b/chrome/browser/ash/file_system_provider/extension_provider.cc
@@ -12,6 +12,7 @@
 #include "chrome/browser/apps/app_service/app_icon/app_icon_source.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/ash/file_system_provider/mount_request_handler.h"
 #include "chrome/browser/ash/file_system_provider/provided_file_system.h"
 #include "chrome/browser/ash/file_system_provider/throttled_file_system.h"
 #include "chrome/browser/profiles/profile.h"
@@ -95,24 +96,28 @@
   return icon_set_;
 }
 
-bool ExtensionProvider::RequestMount(Profile* profile) {
+RequestManager* ExtensionProvider::GetRequestManager() {
+  return request_manager_.get();
+}
+
+bool ExtensionProvider::RequestMount(Profile* profile,
+                                     RequestMountCallback callback) {
   extensions::EventRouter* const event_router =
       extensions::EventRouter::Get(profile);
   DCHECK(event_router);
-
-  if (!event_router->ExtensionHasEventListener(
-          provider_id_.GetExtensionId(), extensions::api::file_system_provider::
-                                             OnMountRequested::kEventName)) {
+  // Create two callbacks of which only one will be called because
+  // RequestManager::CreateRequest() is guaranteed not to call |callback| if it
+  // signals an error (by returning request_id == 0).
+  auto split_callback = base::SplitOnceCallback(std::move(callback));
+  const int request_id = request_manager_->CreateRequest(
+      REQUEST_MOUNT,
+      std::make_unique<MountRequestHandler>(event_router, provider_id_,
+                                            std::move(split_callback.first)));
+  if (!request_id) {
+    std::move(split_callback.second).Run(base::File::FILE_ERROR_FAILED);
     return false;
   }
 
-  event_router->DispatchEventToExtension(
-      provider_id_.GetExtensionId(),
-      std::make_unique<extensions::Event>(
-          extensions::events::FILE_SYSTEM_PROVIDER_ON_MOUNT_REQUESTED,
-          extensions::api::file_system_provider::OnMountRequested::kEventName,
-          base::Value::List()));
-
   return true;
 }
 
@@ -120,7 +125,9 @@
     Profile* profile,
     const extensions::ExtensionId& extension_id,
     const ProvidingExtensionInfo& info)
-    : provider_id_(ProviderId::CreateFromExtensionId(extension_id)) {
+    : provider_id_(ProviderId::CreateFromExtensionId(extension_id)),
+      request_manager_(
+          new RequestManager(profile, /*notification_manager=*/nullptr)) {
   capabilities_.configurable = info.capabilities.configurable();
   capabilities_.watchable = info.capabilities.watchable();
   capabilities_.multiple_mounts = info.capabilities.multiple_mounts();
@@ -135,7 +142,9 @@
                                      std::string name)
     : provider_id_(std::move(id)),
       capabilities_(std::move(capabilities)),
-      name_(std::move(name)) {
+      name_(std::move(name)),
+      request_manager_(
+          new RequestManager(profile, /*notification_manager=*/nullptr)) {
   ObserveAppServiceForIcons(profile);
 }
 
diff --git a/chrome/browser/ash/file_system_provider/extension_provider.h b/chrome/browser/ash/file_system_provider/extension_provider.h
index fff8491..3636ed4 100644
--- a/chrome/browser/ash/file_system_provider/extension_provider.h
+++ b/chrome/browser/ash/file_system_provider/extension_provider.h
@@ -61,7 +61,8 @@
   const ProviderId& GetId() const override;
   const std::string& GetName() const override;
   const IconSet& GetIconSet() const override;
-  bool RequestMount(Profile* profile) override;
+  RequestManager* GetRequestManager() override;
+  bool RequestMount(Profile* profile, RequestMountCallback callback) override;
 
  private:
   // This method is only partially functional since non-app extensions are not
@@ -77,6 +78,7 @@
   Capabilities capabilities_;
   std::string name_;
   IconSet icon_set_;
+  std::unique_ptr<RequestManager> request_manager_;
 };
 
 }  // namespace file_system_provider
diff --git a/chrome/browser/ash/file_system_provider/fake_extension_provider.cc b/chrome/browser/ash/file_system_provider/fake_extension_provider.cc
index 9a13a404..f9853907 100644
--- a/chrome/browser/ash/file_system_provider/fake_extension_provider.cc
+++ b/chrome/browser/ash/file_system_provider/fake_extension_provider.cc
@@ -52,7 +52,14 @@
   return icon_set_;
 }
 
-bool FakeExtensionProvider::RequestMount(Profile* profile) {
+RequestManager* FakeExtensionProvider::GetRequestManager() {
+  NOTREACHED();
+  return nullptr;
+}
+
+bool FakeExtensionProvider::RequestMount(Profile* profile,
+                                         RequestMountCallback callback) {
+  std::move(callback).Run(base::File::Error::FILE_OK);
   return true;
 }
 
diff --git a/chrome/browser/ash/file_system_provider/fake_extension_provider.h b/chrome/browser/ash/file_system_provider/fake_extension_provider.h
index b3cf9f4..c76c29f 100644
--- a/chrome/browser/ash/file_system_provider/fake_extension_provider.h
+++ b/chrome/browser/ash/file_system_provider/fake_extension_provider.h
@@ -36,7 +36,8 @@
   const ProviderId& GetId() const override;
   const std::string& GetName() const override;
   const IconSet& GetIconSet() const override;
-  bool RequestMount(Profile* profile) override;
+  RequestManager* GetRequestManager() override;
+  bool RequestMount(Profile* profile, RequestMountCallback callback) override;
 
  protected:
   FakeExtensionProvider(const extensions::ExtensionId& extension_id,
diff --git a/chrome/browser/ash/file_system_provider/fake_provided_file_system.cc b/chrome/browser/ash/file_system_provider/fake_provided_file_system.cc
index 9016d5c..350c9a28 100644
--- a/chrome/browser/ash/file_system_provider/fake_provided_file_system.cc
+++ b/chrome/browser/ash/file_system_provider/fake_provided_file_system.cc
@@ -474,7 +474,7 @@
   return file_system_info_;
 }
 
-RequestManager* FakeProvidedFileSystem::GetRequestManager() {
+OperationRequestManager* FakeProvidedFileSystem::GetRequestManager() {
   NOTREACHED();
   return nullptr;
 }
diff --git a/chrome/browser/ash/file_system_provider/fake_provided_file_system.h b/chrome/browser/ash/file_system_provider/fake_provided_file_system.h
index cec5401..5cb5570 100644
--- a/chrome/browser/ash/file_system_provider/fake_provided_file_system.h
+++ b/chrome/browser/ash/file_system_provider/fake_provided_file_system.h
@@ -36,7 +36,7 @@
 
 namespace ash::file_system_provider {
 
-class RequestManager;
+class OperationRequestManager;
 
 // Path of a sample fake file, which is added to the fake file system by
 // default.
@@ -156,7 +156,7 @@
                      bool recursive,
                      storage::AsyncFileUtil::StatusCallback callback) override;
   const ProvidedFileSystemInfo& GetFileSystemInfo() const override;
-  RequestManager* GetRequestManager() override;
+  OperationRequestManager* GetRequestManager() override;
   Watchers* GetWatchers() override;
   const OpenedFiles& GetOpenedFiles() const override;
   void AddObserver(ProvidedFileSystemObserver* observer) override;
diff --git a/chrome/browser/ash/file_system_provider/mount_request_handler.cc b/chrome/browser/ash/file_system_provider/mount_request_handler.cc
new file mode 100644
index 0000000..9b915bcb
--- /dev/null
+++ b/chrome/browser/ash/file_system_provider/mount_request_handler.cc
@@ -0,0 +1,113 @@
+// 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/ash/file_system_provider/mount_request_handler.h"
+
+#include <utility>
+
+#include "chrome/browser/ash/crosapi/crosapi_ash.h"
+#include "chrome/browser/ash/crosapi/crosapi_manager.h"
+#include "chrome/browser/ash/crosapi/file_system_provider_service_ash.h"
+#include "chrome/browser/ash/file_system_provider/provided_file_system_info.h"
+#include "chrome/browser/ash/file_system_provider/service.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/common/extensions/api/file_system_provider.h"
+#include "chromeos/crosapi/mojom/file_system_provider.mojom.h"
+#include "extensions/browser/event_router.h"
+
+namespace ash::file_system_provider {
+namespace {
+
+// This method is only used when Lacros is enabled. It's a callback from Lacros
+// indicating whether the mount request was successfully forwarded.
+void OperationForwarded(ash::file_system_provider::ProviderId provider_id,
+                        int request_id,
+                        bool delivery_failure) {
+  using ash::file_system_provider::Service;
+  // Successful deliveries will go through the FileSystemProvider mojom path.
+  if (!delivery_failure)
+    return;
+  // When Lacros is enabled the primary profile is the only profile.
+  Service* const service =
+      Service::Get(ProfileManager::GetPrimaryUserProfile());
+  DCHECK(service);
+  ProviderInterface* const provider = service->GetProvider(provider_id);
+  if (!provider)
+    return;
+  provider->GetRequestManager()->RejectRequest(request_id,
+                                               std::make_unique<RequestValue>(),
+                                               base::File::FILE_ERROR_FAILED);
+}
+
+// Implementation for dispatching an event.
+bool DispatchEventImpl(extensions::EventRouter* event_router,
+                       ProviderId provider_id,
+                       int request_id) {
+  base::Value::List event_args;
+  event_args.reserve(1);
+  event_args.Append(base::Value(request_id));
+
+  const extensions::ExtensionId extension_id = provider_id.GetExtensionId();
+  extensions::events::HistogramValue histogram_value =
+      extensions::events::FILE_SYSTEM_PROVIDER_ON_MOUNT_REQUESTED;
+  const std::string event_name =
+      extensions::api::file_system_provider::OnMountRequested::kEventName;
+
+  // If ash has a matching extension, forward the event. This should not be
+  // needed once Lacros is the only browser on all devices.
+  if (event_router->ExtensionHasEventListener(extension_id, event_name)) {
+    event_router->DispatchEventToExtension(
+        extension_id, std::make_unique<extensions::Event>(
+                          histogram_value, event_name, std::move(event_args)));
+    return true;
+  }
+
+  // If there are any Lacros remotes, forward the message to the first one. This
+  // does not support multiple remotes.
+  auto& remotes = crosapi::CrosapiManager::Get()
+                      ->crosapi_ash()
+                      ->file_system_provider_service_ash()
+                      ->remotes();
+  if (!remotes.empty()) {
+    auto remote = remotes.begin();
+    auto callback =
+        base::BindOnce(&OperationForwarded, provider_id, request_id);
+    (*remote)->ForwardOperation(
+        extension_id, static_cast<int32_t>(histogram_value), event_name,
+        std::move(event_args), std::move(callback));
+  }
+  return !remotes.empty();
+}
+
+}  // namespace
+
+MountRequestHandler::MountRequestHandler(extensions::EventRouter* event_router,
+                                         ProviderId provider_id,
+                                         RequestMountCallback callback)
+    : dispatch_event_impl_(
+          base::BindRepeating(&DispatchEventImpl, event_router, provider_id)),
+      callback_(std::move(callback)) {}
+
+MountRequestHandler::~MountRequestHandler() = default;
+
+bool MountRequestHandler::Execute(int request_id) {
+  return dispatch_event_impl_.Run(request_id);
+}
+
+void MountRequestHandler::OnSuccess(int /* request_id */,
+                                    std::unique_ptr<RequestValue> /* result */,
+                                    bool has_more) {
+  // File handle is the same as request id of the OpenFile operation.
+  DCHECK(callback_);
+  std::move(callback_).Run(base::File::FILE_OK);
+}
+
+void MountRequestHandler::OnError(int /* request_id */,
+                                  std::unique_ptr<RequestValue> /* result */,
+                                  base::File::Error error) {
+  DCHECK(callback_);
+  std::move(callback_).Run(error);
+}
+
+}  // namespace ash::file_system_provider
diff --git a/chrome/browser/ash/file_system_provider/mount_request_handler.h b/chrome/browser/ash/file_system_provider/mount_request_handler.h
new file mode 100644
index 0000000..ff4d659
--- /dev/null
+++ b/chrome/browser/ash/file_system_provider/mount_request_handler.h
@@ -0,0 +1,48 @@
+// 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_ASH_FILE_SYSTEM_PROVIDER_MOUNT_REQUEST_HANDLER_H_
+#define CHROME_BROWSER_ASH_FILE_SYSTEM_PROVIDER_MOUNT_REQUEST_HANDLER_H_
+
+#include "chrome/browser/ash/file_system_provider/provided_file_system_info.h"
+#include "chrome/browser/ash/file_system_provider/provider_interface.h"
+#include "chrome/browser/ash/file_system_provider/request_manager.h"
+
+namespace extensions {
+class EventRouter;
+}  // namespace extensions
+
+namespace ash::file_system_provider {
+
+// Base class for operation bridges between fileapi and providing extensions.
+class MountRequestHandler : public RequestManager::HandlerInterface {
+ public:
+  MountRequestHandler(extensions::EventRouter* event_router,
+                      ProviderId provider_id,
+                      RequestMountCallback callback);
+
+  MountRequestHandler(const MountRequestHandler&) = delete;
+  MountRequestHandler& operator=(const MountRequestHandler&) = delete;
+
+  ~MountRequestHandler() override;
+
+  // RequestManager::HandlerInterface overrides.
+  bool Execute(int request_id) override;
+  void OnSuccess(int request_id,
+                 std::unique_ptr<RequestValue> result,
+                 bool has_more) override;
+  void OnError(int request_id,
+               std::unique_ptr<RequestValue> result,
+               base::File::Error error) override;
+
+ private:
+  using DispatchEventInternalCallback =
+      base::RepeatingCallback<bool(int request_id)>;
+  DispatchEventInternalCallback dispatch_event_impl_;
+  RequestMountCallback callback_;
+};
+
+}  // namespace ash::file_system_provider
+
+#endif  // CHROME_BROWSER_ASH_FILE_SYSTEM_PROVIDER_MOUNT_REQUEST_HANDLER_H_
diff --git a/chrome/browser/ash/file_system_provider/operation_request_manager.cc b/chrome/browser/ash/file_system_provider/operation_request_manager.cc
new file mode 100644
index 0000000..331dff89
--- /dev/null
+++ b/chrome/browser/ash/file_system_provider/operation_request_manager.cc
@@ -0,0 +1,88 @@
+// 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/ash/file_system_provider/operation_request_manager.h"
+
+#include "base/time/time.h"
+#include "chrome/browser/extensions/window_controller_list.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "extensions/browser/app_window/app_window_registry.h"
+#include "extensions/common/constants.h"
+
+namespace ash::file_system_provider {
+namespace {
+
+// Timeout in seconds, before a file system operation request is considered as
+// stale and hence aborted.
+const int kDefaultOperationTimeout = 10;
+
+}  // namespace
+
+OperationRequestManager::OperationRequestManager(
+    Profile* profile,
+    const std::string& provider_id,
+    NotificationManagerInterface* notification_manager)
+    : RequestManager(profile,
+                     notification_manager,
+                     base::Seconds(kDefaultOperationTimeout)),
+      provider_id_(provider_id) {}
+
+OperationRequestManager::~OperationRequestManager() = default;
+
+void OperationRequestManager::OnRequestTimeout(int request_id) {
+  for (auto& observer : observers_)
+    observer.OnRequestTimeouted(request_id);
+
+  if (!notification_manager_) {
+    RejectRequest(request_id, std::make_unique<RequestValue>(),
+                  base::File::FILE_ERROR_ABORT);
+    return;
+  }
+
+  if (!IsInteractingWithUser()) {
+    notification_manager_->ShowUnresponsiveNotification(
+        request_id,
+        base::BindOnce(
+            &OperationRequestManager::OnUnresponsiveNotificationResult,
+            weak_ptr_factory_.GetWeakPtr(), request_id));
+  } else {
+    ResetTimer(request_id);
+  }
+}
+
+bool OperationRequestManager::IsInteractingWithUser() const {
+  // First try for app windows. If not found, then fall back to browser windows
+  // and tabs.
+  const extensions::AppWindowRegistry* const registry =
+      extensions::AppWindowRegistry::Get(profile_);
+  DCHECK(registry);
+  if (registry->GetCurrentAppWindowForApp(provider_id_))
+    return true;
+
+  // This loop is heavy, but it's not called often. Only when a request timeouts
+  // which is at most once every 10 seconds per request (except tests).
+  const extensions::WindowControllerList::ControllerList& windows =
+      extensions::WindowControllerList::GetInstance()->windows();
+  for (auto* window : windows) {
+    const Browser* const browser = window->GetBrowser();
+    if (!browser)
+      continue;
+    const TabStripModel* const tabs = browser->tab_strip_model();
+    DCHECK(tabs);
+    for (int i = 0; i < tabs->count(); ++i) {
+      content::WebContents* const web_contents = tabs->GetWebContentsAt(i);
+      const GURL& url = web_contents->GetURL();
+      if (url.SchemeIs(extensions::kExtensionScheme) &&
+          url.host_piece() == provider_id_) {
+        return true;
+      }
+    }
+  }
+
+  return false;
+}
+
+}  // namespace ash::file_system_provider
diff --git a/chrome/browser/ash/file_system_provider/operation_request_manager.h b/chrome/browser/ash/file_system_provider/operation_request_manager.h
new file mode 100644
index 0000000..24bb505
--- /dev/null
+++ b/chrome/browser/ash/file_system_provider/operation_request_manager.h
@@ -0,0 +1,59 @@
+// 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_ASH_FILE_SYSTEM_PROVIDER_OPERATION_REQUEST_MANAGER_H_
+#define CHROME_BROWSER_ASH_FILE_SYSTEM_PROVIDER_OPERATION_REQUEST_MANAGER_H_
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/files/file.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/observer_list.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "chrome/browser/ash/file_system_provider/notification_manager_interface.h"
+#include "chrome/browser/ash/file_system_provider/request_manager.h"
+#include "chrome/browser/ash/file_system_provider/request_value.h"
+
+class Profile;
+
+namespace ash::file_system_provider {
+
+// Manages requests for provided file system operations by assigning them a
+// request id and timing out slow requests. Note: Those requests are all
+// requests that have a fileSystemId, which is all requests except
+// onMountRequested, handled by another instance of RequestManager.
+class OperationRequestManager : public RequestManager {
+ public:
+  // Creates a request manager for |profile| and |provider_id|. Note, that
+  // there is one instance per provided file system, thus potentially be
+  // multiple instances per provider.
+  OperationRequestManager(Profile* profile,
+                          const std::string& provider_id,
+                          NotificationManagerInterface* notification_manager);
+
+  explicit OperationRequestManager(const RequestManager&) = delete;
+  OperationRequestManager& operator=(const RequestManager&) = delete;
+
+  ~OperationRequestManager() override;
+
+ private:
+  // Called when a request with |request_id| times out.
+  void OnRequestTimeout(int request_id) override;
+
+  // Checks whether there is an ongoing interaction between the provider
+  // and user.
+  bool IsInteractingWithUser() const;
+
+  std::string provider_id_;
+};
+
+}  // namespace ash::file_system_provider
+
+#endif  // CHROME_BROWSER_ASH_FILE_SYSTEM_PROVIDER_OPERATION_REQUEST_MANAGER_H_
diff --git a/chrome/browser/ash/file_system_provider/request_manager_unittest.cc b/chrome/browser/ash/file_system_provider/operation_request_manager_unittest.cc
similarity index 98%
rename from chrome/browser/ash/file_system_provider/request_manager_unittest.cc
rename to chrome/browser/ash/file_system_provider/operation_request_manager_unittest.cc
index 187efd59..40ff9f9 100644
--- a/chrome/browser/ash/file_system_provider/request_manager_unittest.cc
+++ b/chrome/browser/ash/file_system_provider/operation_request_manager_unittest.cc
@@ -1,8 +1,8 @@
-// Copyright 2014 The Chromium Authors
+// 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/ash/file_system_provider/request_manager.h"
+#include "chrome/browser/ash/file_system_provider/operation_request_manager.h"
 
 #include <stddef.h>
 
@@ -332,7 +332,7 @@
   void SetUp() override {
     profile_ = std::make_unique<TestingProfile>();
     notification_manager_ = std::make_unique<FakeNotificationManager>();
-    request_manager_ = std::make_unique<RequestManager>(
+    request_manager_ = std::make_unique<OperationRequestManager>(
         profile_.get(), std::string() /* provider_id */,
         notification_manager_.get());
   }
@@ -695,8 +695,8 @@
   int request_id;
 
   {
-    RequestManager request_manager(profile_.get(),
-                                   std::string() /* provider_id */, nullptr);
+    OperationRequestManager request_manager(
+        profile_.get(), std::string() /* provider_id */, nullptr);
     request_manager.AddObserver(&observer);
 
     request_id = request_manager.CreateRequest(
diff --git a/chrome/browser/ash/file_system_provider/operations/operation.cc b/chrome/browser/ash/file_system_provider/operations/operation.cc
index f169d12a..8fb51ea 100644
--- a/chrome/browser/ash/file_system_provider/operations/operation.cc
+++ b/chrome/browser/ash/file_system_provider/operations/operation.cc
@@ -10,6 +10,7 @@
 #include "chrome/browser/ash/crosapi/crosapi_ash.h"
 #include "chrome/browser/ash/crosapi/crosapi_manager.h"
 #include "chrome/browser/ash/crosapi/file_system_provider_service_ash.h"
+#include "chrome/browser/ash/file_system_provider/operation_request_manager.h"
 #include "chrome/browser/ash/file_system_provider/provided_file_system_info.h"
 #include "chrome/browser/ash/file_system_provider/service.h"
 #include "chrome/browser/ash/guest_os/guest_os_terminal.h"
diff --git a/chrome/browser/ash/file_system_provider/provided_file_system.cc b/chrome/browser/ash/file_system_provider/provided_file_system.cc
index 09fc196d..cbe1106 100644
--- a/chrome/browser/ash/file_system_provider/provided_file_system.cc
+++ b/chrome/browser/ash/file_system_provider/provided_file_system.cc
@@ -14,6 +14,7 @@
 #include "base/memory/ptr_util.h"
 #include "base/trace_event/trace_event.h"
 #include "chrome/browser/ash/file_system_provider/notification_manager.h"
+#include "chrome/browser/ash/file_system_provider/operation_request_manager.h"
 #include "chrome/browser/ash/file_system_provider/operations/abort.h"
 #include "chrome/browser/ash/file_system_provider/operations/add_watcher.h"
 #include "chrome/browser/ash/file_system_provider/operations/close_file.h"
@@ -33,7 +34,6 @@
 #include "chrome/browser/ash/file_system_provider/operations/truncate.h"
 #include "chrome/browser/ash/file_system_provider/operations/unmount.h"
 #include "chrome/browser/ash/file_system_provider/operations/write_file.h"
-#include "chrome/browser/ash/file_system_provider/request_manager.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/common/extensions/api/file_system_provider.h"
 #include "extensions/browser/event_router.h"
@@ -138,10 +138,10 @@
       file_system_info_(file_system_info),
       notification_manager_(
           new NotificationManager(profile_, file_system_info_)),
-      request_manager_(
-          new RequestManager(profile,
-                             file_system_info.provider_id().GetExtensionId(),
-                             notification_manager_.get())),
+      request_manager_(new OperationRequestManager(
+          profile,
+          file_system_info.provider_id().GetExtensionId(),
+          notification_manager_.get())),
       watcher_queue_(1) {
   DCHECK_EQ(ProviderId::EXTENSION, file_system_info.provider_id().GetType());
 }
@@ -161,7 +161,7 @@
 void ProvidedFileSystem::SetNotificationManagerForTesting(
     std::unique_ptr<NotificationManagerInterface> notification_manager) {
   notification_manager_ = std::move(notification_manager);
-  request_manager_ = std::make_unique<RequestManager>(
+  request_manager_ = std::make_unique<OperationRequestManager>(
       profile_, file_system_info_.provider_id().GetExtensionId(),
       notification_manager_.get());
 }
@@ -494,7 +494,7 @@
   return file_system_info_;
 }
 
-RequestManager* ProvidedFileSystem::GetRequestManager() {
+OperationRequestManager* ProvidedFileSystem::GetRequestManager() {
   return request_manager_.get();
 }
 
diff --git a/chrome/browser/ash/file_system_provider/provided_file_system.h b/chrome/browser/ash/file_system_provider/provided_file_system.h
index 902819ca..222aaa6 100644
--- a/chrome/browser/ash/file_system_provider/provided_file_system.h
+++ b/chrome/browser/ash/file_system_provider/provided_file_system.h
@@ -15,11 +15,11 @@
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
 #include "chrome/browser/ash/file_system_provider/abort_callback.h"
+#include "chrome/browser/ash/file_system_provider/operation_request_manager.h"
 #include "chrome/browser/ash/file_system_provider/provided_file_system_info.h"
 #include "chrome/browser/ash/file_system_provider/provided_file_system_interface.h"
 #include "chrome/browser/ash/file_system_provider/provided_file_system_observer.h"
 #include "chrome/browser/ash/file_system_provider/queue.h"
-#include "chrome/browser/ash/file_system_provider/request_manager.h"
 #include "storage/browser/file_system/async_file_util.h"
 #include "storage/browser/file_system/watcher_manager.h"
 #include "url/gurl.h"
@@ -163,7 +163,7 @@
                      bool recursive,
                      storage::AsyncFileUtil::StatusCallback callback) override;
   const ProvidedFileSystemInfo& GetFileSystemInfo() const override;
-  RequestManager* GetRequestManager() override;
+  OperationRequestManager* GetRequestManager() override;
   Watchers* GetWatchers() override;
   const OpenedFiles& GetOpenedFiles() const override;
   void AddObserver(ProvidedFileSystemObserver* observer) override;
@@ -248,7 +248,7 @@
   extensions::EventRouter* event_router_;  // Not owned. May be NULL.
   ProvidedFileSystemInfo file_system_info_;
   std::unique_ptr<NotificationManagerInterface> notification_manager_;
-  std::unique_ptr<RequestManager> request_manager_;
+  std::unique_ptr<OperationRequestManager> request_manager_;
   Watchers watchers_;
   Queue watcher_queue_;
   OpenedFiles opened_files_;
diff --git a/chrome/browser/ash/file_system_provider/provided_file_system_interface.h b/chrome/browser/ash/file_system_provider/provided_file_system_interface.h
index 78f3a9d..4ccbd2a1 100644
--- a/chrome/browser/ash/file_system_provider/provided_file_system_interface.h
+++ b/chrome/browser/ash/file_system_provider/provided_file_system_interface.h
@@ -35,7 +35,7 @@
 namespace file_system_provider {
 
 class ProvidedFileSystemInfo;
-class RequestManager;
+class OperationRequestManager;
 
 // Represents metadata for either a file or a directory.
 struct EntryMetadata {
@@ -269,7 +269,7 @@
   virtual const OpenedFiles& GetOpenedFiles() const = 0;
 
   // Returns a request manager for the file system.
-  virtual RequestManager* GetRequestManager() = 0;
+  virtual OperationRequestManager* GetRequestManager() = 0;
 
   // Adds an observer on the file system.
   virtual void AddObserver(ProvidedFileSystemObserver* observer) = 0;
diff --git a/chrome/browser/ash/file_system_provider/provider_interface.h b/chrome/browser/ash/file_system_provider/provider_interface.h
index 966d20a..8a5514d0 100644
--- a/chrome/browser/ash/file_system_provider/provider_interface.h
+++ b/chrome/browser/ash/file_system_provider/provider_interface.h
@@ -8,8 +8,10 @@
 #include <memory>
 #include <string>
 
+#include "base/files/file.h"
 #include "chrome/browser/ash/file_system_provider/icon_set.h"
 #include "chrome/browser/ash/file_system_provider/provided_file_system_info.h"
+#include "chrome/browser/ash/file_system_provider/request_manager.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/common/extensions/api/file_system_provider_capabilities/file_system_provider_capabilities_handler.h"
 
@@ -21,6 +23,8 @@
 class ProvidedFileSystemInterface;
 class ProviderId;
 
+typedef base::OnceCallback<void(base::File::Error result)> RequestMountCallback;
+
 struct Capabilities {
   Capabilities(bool configurable,
                bool watchable,
@@ -68,9 +72,14 @@
   // Returns an icon URL set for the provider.
   virtual const IconSet& GetIconSet() const = 0;
 
+  // The returned request manager is registered per-provider to handle mount
+  // requests.
+  virtual RequestManager* GetRequestManager() = 0;
+
   // Requests mounting a new file system. Returns false if the request could not
   // be created, true otherwise.
-  virtual bool RequestMount(Profile* profile) = 0;
+  virtual bool RequestMount(Profile* profile,
+                            RequestMountCallback callback) = 0;
 };
 
 }  // namespace file_system_provider
diff --git a/chrome/browser/ash/file_system_provider/request_manager.cc b/chrome/browser/ash/file_system_provider/request_manager.cc
index 54af8df..9e2bca5 100644
--- a/chrome/browser/ash/file_system_provider/request_manager.cc
+++ b/chrome/browser/ash/file_system_provider/request_manager.cc
@@ -4,37 +4,36 @@
 
 #include "chrome/browser/ash/file_system_provider/request_manager.h"
 
-#include <utility>
-
-#include "base/bind.h"
 #include "base/files/file.h"
+#include "base/time/time.h"
 #include "base/trace_event/trace_event.h"
-#include "chrome/browser/extensions/window_controller_list.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/browser.h"
-#include "chrome/browser/ui/tabs/tab_strip_model.h"
-#include "extensions/browser/app_window/app_window_registry.h"
-#include "extensions/common/constants.h"
 
 namespace ash {
 namespace file_system_provider {
 namespace {
 
-// Timeout in seconds, before a request is considered as stale and hence
+// Timeout in minutes, before a request is considered as stale and hence
 // aborted.
-const int kDefaultTimeout = 10;
+const int kDefaultTimeout = 5;
 
 }  // namespace
 
 RequestManager::RequestManager(
     Profile* profile,
-    const std::string& provider_id,
-    NotificationManagerInterface* notification_manager)
+    raw_ptr<NotificationManagerInterface> notification_manager)
     : profile_(profile),
-      provider_id_(provider_id),
       notification_manager_(notification_manager),
       next_id_(1),
-      timeout_(base::Seconds(kDefaultTimeout)) {}
+      timeout_(base::Minutes(kDefaultTimeout)) {}
+
+RequestManager::RequestManager(
+    Profile* profile,
+    raw_ptr<NotificationManagerInterface> notification_manager,
+    base::TimeDelta timeout)
+    : profile_(profile),
+      notification_manager_(notification_manager),
+      next_id_(1),
+      timeout_(timeout) {}
 
 RequestManager::~RequestManager() {
   // Abort all of the active requests.
@@ -168,14 +167,10 @@
     return;
   }
 
-  if (!IsInteractingWithUser()) {
-    notification_manager_->ShowUnresponsiveNotification(
-        request_id,
-        base::BindOnce(&RequestManager::OnUnresponsiveNotificationResult,
-                       weak_ptr_factory_.GetWeakPtr(), request_id));
-  } else {
-    ResetTimer(request_id);
-  }
+  notification_manager_->ShowUnresponsiveNotification(
+      request_id,
+      base::BindOnce(&RequestManager::OnUnresponsiveNotificationResult,
+                     weak_ptr_factory_.GetWeakPtr(), request_id));
 }
 
 void RequestManager::OnUnresponsiveNotificationResult(
@@ -205,39 +200,6 @@
                      weak_ptr_factory_.GetWeakPtr(), request_id));
 }
 
-bool RequestManager::IsInteractingWithUser() const {
-  // First try for app windows. If not found, then fall back to browser windows
-  // and tabs.
-
-  const extensions::AppWindowRegistry* const registry =
-      extensions::AppWindowRegistry::Get(profile_);
-  DCHECK(registry);
-  if (registry->GetCurrentAppWindowForApp(provider_id_))
-    return true;
-
-  // This loop is heavy, but it's not called often. Only when a request timeouts
-  // which is at most once every 10 seconds per request (except tests).
-  const extensions::WindowControllerList::ControllerList& windows =
-      extensions::WindowControllerList::GetInstance()->windows();
-  for (auto* window : windows) {
-    const Browser* const browser = window->GetBrowser();
-    if (!browser)
-      continue;
-    const TabStripModel* const tabs = browser->tab_strip_model();
-    DCHECK(tabs);
-    for (int i = 0; i < tabs->count(); ++i) {
-      content::WebContents* const web_contents = tabs->GetWebContentsAt(i);
-      const GURL& url = web_contents->GetURL();
-      if (url.SchemeIs(extensions::kExtensionScheme) &&
-          url.host_piece() == provider_id_) {
-        return true;
-      }
-    }
-  }
-
-  return false;
-}
-
 void RequestManager::DestroyRequest(int request_id) {
   auto request_it = requests_.find(request_id);
   if (request_it == requests_.end())
diff --git a/chrome/browser/ash/file_system_provider/request_manager.h b/chrome/browser/ash/file_system_provider/request_manager.h
index f917cae..37ddb59 100644
--- a/chrome/browser/ash/file_system_provider/request_manager.h
+++ b/chrome/browser/ash/file_system_provider/request_manager.h
@@ -10,7 +10,6 @@
 #include <string>
 #include <vector>
 
-#include "base/callback.h"
 #include "base/files/file.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
@@ -27,6 +26,7 @@
 
 // Request type, passed to RequestManager::CreateRequest. For logging purposes.
 enum RequestType {
+  REQUEST_MOUNT,
   REQUEST_UNMOUNT,
   GET_METADATA,
   GET_ACTIONS,
@@ -106,11 +106,8 @@
     virtual void OnRequestTimeouted(int request_id) = 0;
   };
 
-  // Creates a request manager for |profile| and |provider_id|. Note, that
-  // there may be multiple instances of request managers per provider.
   RequestManager(Profile* profile,
-                 const std::string& provider_id,
-                 NotificationManagerInterface* notification_manager);
+                 raw_ptr<NotificationManagerInterface> notification_manager);
 
   RequestManager(const RequestManager&) = delete;
   RequestManager& operator=(const RequestManager&) = delete;
@@ -152,7 +149,7 @@
   // Destroys the request with the passed |request_id|.
   void DestroyRequest(int request_id);
 
- private:
+ protected:
   struct Request {
     Request();
 
@@ -168,8 +165,12 @@
     std::unique_ptr<HandlerInterface> handler;
   };
 
+  RequestManager(Profile* profile,
+                 raw_ptr<NotificationManagerInterface> notification_manager,
+                 base::TimeDelta timeout);
+
   // Called when a request with |request_id| timeouts.
-  void OnRequestTimeout(int request_id);
+  virtual void OnRequestTimeout(int request_id);
 
   // Called when an user either aborts the unresponsive request or lets it
   // continue.
@@ -180,12 +181,7 @@
   // Resets the timeout timer for the specified request.
   void ResetTimer(int request_id);
 
-  // Checks whether there is an ongoing interaction between the provider
-  // and user.
-  bool IsInteractingWithUser() const;
-
   raw_ptr<Profile> profile_;  // Not owned.
-  std::string provider_id_;
   std::map<int, std::unique_ptr<Request>> requests_;
   raw_ptr<NotificationManagerInterface> notification_manager_;  // Not owned.
   int next_id_;
diff --git a/chrome/browser/ash/file_system_provider/service.cc b/chrome/browser/ash/file_system_provider/service.cc
index bfc2f06..afa74b8 100644
--- a/chrome/browser/ash/file_system_provider/service.cc
+++ b/chrome/browser/ash/file_system_provider/service.cc
@@ -9,6 +9,7 @@
 #include <utility>
 
 #include "base/bind.h"
+#include "base/files/file.h"
 #include "base/files/file_path.h"
 #include "base/values.h"
 #include "chrome/browser/ash/file_system_provider/mount_path_util.h"
@@ -258,11 +259,16 @@
   return true;
 }
 
-bool Service::RequestMount(const ProviderId& provider_id) {
+bool Service::RequestMount(const ProviderId& provider_id,
+                           RequestMountCallback callback) {
   DCHECK(thread_checker_.CalledOnValidThread());
 
   ProviderInterface* const provider = GetProvider(provider_id);
-  return provider->RequestMount(profile_);
+  if (!provider) {
+    std::move(callback).Run(base::File::FILE_ERROR_FAILED);
+    return false;
+  }
+  return provider->RequestMount(profile_, std::move(callback));
 }
 
 std::vector<ProvidedFileSystemInfo> Service::GetProvidedFileSystemInfoList() {
diff --git a/chrome/browser/ash/file_system_provider/service.h b/chrome/browser/ash/file_system_provider/service.h
index f95d3dd..e1aaebb 100644
--- a/chrome/browser/ash/file_system_provider/service.h
+++ b/chrome/browser/ash/file_system_provider/service.h
@@ -12,6 +12,7 @@
 #include <vector>
 
 #include "base/files/file.h"
+#include "base/functional/callback_helpers.h"
 #include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
@@ -106,7 +107,8 @@
   // Requests mounting a new file system by the providing extension with
   // |provider_id|. Returns false if the request could not been created, true
   // otherwise.
-  bool RequestMount(const ProviderId& provider_id);
+  bool RequestMount(const ProviderId& provider_id,
+                    RequestMountCallback callback);
 
   // Returns a list of information of all currently provided file systems. All
   // items are copied.
@@ -117,6 +119,10 @@
   std::vector<ProvidedFileSystemInfo> GetProvidedFileSystemInfoList(
       const ProviderId& provider_id);
 
+  // Returns a file system provider for the passed |provider_id|. If not found
+  // then returns nullptr.
+  ProviderInterface* GetProvider(const ProviderId& provider_id);
+
   // Returns an immutable map of all registered providers.
   const ProviderMap& GetProviders() const;
 
@@ -198,10 +204,6 @@
   // for automagical remount in the future.
   void UnmountFileSystems(const ProviderId& provider_id, UnmountReason reason);
 
-  // Returns a file system provider for the passed |provider_id|. If not found
-  // then returns nullptr.
-  ProviderInterface* GetProvider(const ProviderId& provider_id);
-
   raw_ptr<Profile> profile_;
   raw_ptr<extensions::ExtensionRegistry> extension_registry_;  // Not owned.
   base::ObserverList<Observer>::Unchecked observers_;
diff --git a/chrome/browser/ash/file_system_provider/service_unittest.cc b/chrome/browser/ash/file_system_provider/service_unittest.cc
index 08216ea..8826d6ac 100644
--- a/chrome/browser/ash/file_system_provider/service_unittest.cc
+++ b/chrome/browser/ash/file_system_provider/service_unittest.cc
@@ -59,22 +59,22 @@
 // TODO(mtomasz): Use the extension builder.
 scoped_refptr<extensions::Extension> CreateFakeExtension(
     const extensions::ExtensionId& extension_id) {
-  base::DictionaryValue manifest;
+  base::Value::Dict manifest;
   std::string error;
-  manifest.SetKey(extensions::manifest_keys::kVersion, base::Value("1.0.0.0"));
-  manifest.SetKey(extensions::manifest_keys::kManifestVersion, base::Value(2));
-  manifest.SetKey(extensions::manifest_keys::kName, base::Value("unused"));
+  manifest.Set(extensions::manifest_keys::kVersion, "1.0.0.0");
+  manifest.Set(extensions::manifest_keys::kManifestVersion, 2);
+  manifest.Set(extensions::manifest_keys::kName, "unused");
 
-  base::ListValue permissions_list;
+  base::Value::List permissions_list;
   permissions_list.Append("fileSystemProvider");
-  manifest.SetKey(extensions::manifest_keys::kPermissions,
-                  std::move(permissions_list));
+  manifest.Set(extensions::manifest_keys::kPermissions,
+               std::move(permissions_list));
 
-  base::DictionaryValue capabilities;
-  capabilities.SetStringKey("source", "network");
-  capabilities.SetBoolKey("watchable", true);
-  manifest.SetKey(extensions::manifest_keys::kFileSystemProviderCapabilities,
-                  std::move(capabilities));
+  base::Value::Dict capabilities;
+  capabilities.Set("source", "network");
+  capabilities.Set("watchable", true);
+  manifest.Set(extensions::manifest_keys::kFileSystemProviderCapabilities,
+               std::move(capabilities));
 
   scoped_refptr<extensions::Extension> extension =
       extensions::Extension::Create(
diff --git a/chrome/browser/ash/file_system_provider/throttled_file_system.cc b/chrome/browser/ash/file_system_provider/throttled_file_system.cc
index a1b1eff..c8798015 100644
--- a/chrome/browser/ash/file_system_provider/throttled_file_system.cc
+++ b/chrome/browser/ash/file_system_provider/throttled_file_system.cc
@@ -173,7 +173,7 @@
   return file_system_->GetFileSystemInfo();
 }
 
-RequestManager* ThrottledFileSystem::GetRequestManager() {
+OperationRequestManager* ThrottledFileSystem::GetRequestManager() {
   return file_system_->GetRequestManager();
 }
 
diff --git a/chrome/browser/ash/file_system_provider/throttled_file_system.h b/chrome/browser/ash/file_system_provider/throttled_file_system.h
index 248eabf6..a9fcf31 100644
--- a/chrome/browser/ash/file_system_provider/throttled_file_system.h
+++ b/chrome/browser/ash/file_system_provider/throttled_file_system.h
@@ -33,7 +33,7 @@
 namespace file_system_provider {
 
 class Queue;
-class RequestManager;
+class OperationRequestManager;
 
 // Decorates ProvidedFileSystemInterface with throttling capabilities.
 class ThrottledFileSystem : public ProvidedFileSystemInterface {
@@ -113,7 +113,7 @@
                      bool recursive,
                      storage::AsyncFileUtil::StatusCallback callback) override;
   const ProvidedFileSystemInfo& GetFileSystemInfo() const override;
-  RequestManager* GetRequestManager() override;
+  OperationRequestManager* GetRequestManager() override;
   Watchers* GetWatchers() override;
   const OpenedFiles& GetOpenedFiles() const override;
   void AddObserver(ProvidedFileSystemObserver* observer) override;
diff --git a/chrome/browser/ash/fusebox/fusebox_server.cc b/chrome/browser/ash/fusebox/fusebox_server.cc
index d0b0e08..7b45e2b 100644
--- a/chrome/browser/ash/fusebox/fusebox_server.cc
+++ b/chrome/browser/ash/fusebox/fusebox_server.cc
@@ -1518,7 +1518,6 @@
   for (const auto& entry : entry_list) {
     bool is_directory = entry.type == filesystem::mojom::FsFileType::DIRECTORY;
     auto* proto = iter->second.response_.add_entries();
-    proto->set_is_directory(is_directory);
     proto->set_name(entry.name.value());
     proto->set_mode_bits(MakeModeBits(is_directory, read_only));
   }
diff --git a/chrome/browser/ash/input_method/component_extension_ime_manager_delegate_impl.cc b/chrome/browser/ash/input_method/component_extension_ime_manager_delegate_impl.cc
index f3b3b7a..04950a6 100644
--- a/chrome/browser/ash/input_method/component_extension_ime_manager_delegate_impl.cc
+++ b/chrome/browser/ash/input_method/component_extension_ime_manager_delegate_impl.cc
@@ -15,6 +15,7 @@
 #include "base/logging.h"
 #include "base/path_service.h"
 #include "base/strings/string_piece.h"
+#include "base/strings/string_piece_forward.h"
 #include "base/strings/string_util.h"
 #include "base/system/sys_info.h"
 #include "base/task/thread_pool.h"
@@ -36,6 +37,7 @@
 #include "extensions/browser/extension_system.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/manifest_constants.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "ui/base/ime/ash/extension_ime_util.h"
 #include "ui/base/resource/resource_bundle.h"
 
@@ -219,19 +221,22 @@
   return login_layout_set_.find(layout) != login_layout_set_.end();
 }
 
-std::unique_ptr<base::DictionaryValue>
-ComponentExtensionIMEManagerDelegateImpl::GetManifest(
-    const std::string& manifest_string) {
-  std::string error;
-  JSONStringValueDeserializer deserializer(manifest_string);
-  std::unique_ptr<base::Value> manifest =
-      deserializer.Deserialize(nullptr, &error);
-  if (!manifest.get())
-    LOG(ERROR) << "Failed at getting manifest";
-
-  std::unique_ptr<base::DictionaryValue> ret(
-      static_cast<base::DictionaryValue*>(manifest.release()));
-  return ret;
+absl::optional<base::Value::Dict>
+ComponentExtensionIMEManagerDelegateImpl::ParseManifest(
+    const base::StringPiece& manifest_string) {
+  base::JSONReader::Result result =
+      base::JSONReader::ReadAndReturnValueWithError(manifest_string);
+  if (!result.has_value()) {
+    LOG(ERROR) << "Failed to parse manifest: " << result.error().message
+               << " at line " << result.error().line << " column "
+               << result.error().column;
+    return absl::nullopt;
+  }
+  if (!result.value().is_dict()) {
+    LOG(ERROR) << "Failed to parse manifest: parsed value is not a dictionary";
+    return absl::nullopt;
+  }
+  return absl::make_optional(std::move(result.value()).TakeDict());
 }
 
 // static
@@ -247,28 +252,28 @@
 // static
 bool ComponentExtensionIMEManagerDelegateImpl::ReadEngineComponent(
     const ComponentExtensionIME& component_extension,
-    const base::DictionaryValue& dict,
+    const base::Value::Dict& dict,
     ComponentExtensionEngine* out) {
   DCHECK(out);
   const std::string* engine_id =
-      dict.FindStringKey(extensions::manifest_keys::kId);
+      dict.FindString(extensions::manifest_keys::kId);
   if (!engine_id)
     return false;
   out->engine_id = *engine_id;
 
   const std::string* display_name =
-      dict.FindStringKey(extensions::manifest_keys::kName);
+      dict.FindString(extensions::manifest_keys::kName);
   if (!display_name)
     return false;
   out->display_name = *display_name;
 
   const std::string* indicator =
-      dict.FindStringKey(extensions::manifest_keys::kIndicator);
+      dict.FindString(extensions::manifest_keys::kIndicator);
   out->indicator = indicator ? *indicator : "";
 
   std::set<std::string> languages;
   const base::Value* language_value =
-      dict.FindKey(extensions::manifest_keys::kLanguage);
+      dict.Find(extensions::manifest_keys::kLanguage);
   if (language_value) {
     if (language_value->is_string()) {
       languages.insert(language_value->GetString());
@@ -287,13 +292,13 @@
   // supports one layout per input method. Thus use the "first" layout if
   // specified, else default to "us". CrOS IME extension manifests should
   // specify one and only one layout per input method to avoid confusion.
-  const base::ListValue* layouts = nullptr;
-  if (!dict.GetList(extensions::manifest_keys::kLayouts, &layouts))
+  const base::Value::List* layouts =
+      dict.FindList(extensions::manifest_keys::kLayouts);
+  if (!layouts)
     return false;
 
-  const base::Value::List& layouts_list = layouts->GetList();
-  if (!layouts_list.empty() && layouts_list[0].is_string())
-    out->layout = layouts_list[0].GetString();
+  if (!layouts->empty() && layouts->front().is_string())
+    out->layout = layouts->front().GetString();
   else
     out->layout = "us";
 
@@ -309,7 +314,7 @@
   out->input_view_url = url;
 #else
   const std::string* input_view =
-      dict.FindStringKey(extensions::manifest_keys::kInputView);
+      dict.FindString(extensions::manifest_keys::kInputView);
   if (input_view) {
     url_string = *input_view;
     GURL url = extensions::Extension::GetResourceURL(
@@ -323,7 +328,7 @@
 #endif
 
   const std::string* option_page =
-      dict.FindStringKey(extensions::manifest_keys::kOptionsPage);
+      dict.FindString(extensions::manifest_keys::kOptionsPage);
   if (option_page) {
     url_string = *option_page;
     GURL options_page_url = extensions::Extension::GetResourceURL(
@@ -343,20 +348,20 @@
 
 // static
 bool ComponentExtensionIMEManagerDelegateImpl::ReadExtensionInfo(
-    const base::DictionaryValue& manifest,
+    const base::Value::Dict& manifest,
     const std::string& extension_id,
     ComponentExtensionIME* out) {
   const std::string* description =
-      manifest.FindStringKey(extensions::manifest_keys::kDescription);
+      manifest.FindString(extensions::manifest_keys::kDescription);
   if (!description)
     return false;
   out->description = *description;
 
-  const std::string* path = manifest.FindStringKey(kImePathKeyName);
+  const std::string* path = manifest.FindString(kImePathKeyName);
   if (path)
     out->path = base::FilePath(*path);
   const std::string* url_string =
-      manifest.FindStringKey(extensions::manifest_keys::kOptionsPage);
+      manifest.FindString(extensions::manifest_keys::kOptionsPage);
   if (url_string) {
     GURL url = extensions::Extension::GetResourceURL(
         extensions::Extension::GetBaseURLFromExtensionId(extension_id),
@@ -376,8 +381,9 @@
   for (auto& extension : allowlisted_component_extensions) {
     ComponentExtensionIME component_ime;
     ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
-    component_ime.manifest =
-        std::string(rb.GetRawDataResource(extension.manifest_resource_id));
+    const base::StringPiece& manifest_string =
+        rb.GetRawDataResource(extension.manifest_resource_id);
+    component_ime.manifest = std::string(manifest_string);
 
     if (component_ime.manifest.empty()) {
       LOG(ERROR) << "Couldn't get manifest from resource_id("
@@ -385,15 +391,16 @@
       continue;
     }
 
-    std::unique_ptr<base::DictionaryValue> manifest =
-        GetManifest(component_ime.manifest);
-    if (!manifest.get()) {
+    absl::optional<base::Value::Dict> maybe_manifest =
+        ParseManifest(manifest_string);
+    if (!maybe_manifest.has_value()) {
       LOG(ERROR) << "Failed to load invalid manifest: "
                  << component_ime.manifest;
       continue;
     }
+    const base::Value::Dict& manifest = maybe_manifest.value();
 
-    if (!ReadExtensionInfo(*manifest.get(), extension.id, &component_ime)) {
+    if (!ReadExtensionInfo(manifest, extension.id, &component_ime)) {
       LOG(ERROR) << "manifest doesn't have needed information for IME.";
       continue;
     }
@@ -407,19 +414,18 @@
       component_ime.path = resources_path.Append(component_ime.path);
     }
 
-    const base::ListValue* component_list;
-    if (!manifest->GetList(extensions::manifest_keys::kInputComponents,
-                           &component_list)) {
+    const base::Value::List* component_list =
+        manifest.FindList(extensions::manifest_keys::kInputComponents);
+    if (!component_list) {
       LOG(ERROR) << "No input_components is found in manifest.";
       continue;
     }
 
-    for (const base::Value& value : component_list->GetList()) {
+    for (const base::Value& value : *component_list) {
       if (!value.is_dict())
         continue;
 
-      const base::DictionaryValue& dictionary =
-          base::Value::AsDictionaryValue(value);
+      const base::Value::Dict& dictionary = value.GetDict();
       ComponentExtensionEngine engine;
       ReadEngineComponent(component_ime, dictionary, &engine);
 
diff --git a/chrome/browser/ash/input_method/component_extension_ime_manager_delegate_impl.h b/chrome/browser/ash/input_method/component_extension_ime_manager_delegate_impl.h
index 34a25c14..c923f2b8 100644
--- a/chrome/browser/ash/input_method/component_extension_ime_manager_delegate_impl.h
+++ b/chrome/browser/ash/input_method/component_extension_ime_manager_delegate_impl.h
@@ -12,6 +12,7 @@
 #include "base/task/sequenced_task_runner.h"
 #include "base/threading/thread_checker.h"
 #include "base/values.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "ui/base/ime/ash/component_extension_ime_manager.h"
 #include "ui/base/ime/ash/component_extension_ime_manager_delegate.h"
 
@@ -49,13 +50,13 @@
   static void ReadComponentExtensionsInfo(
       std::vector<ComponentExtensionIME>* out_imes);
 
-  // Parses manifest string to manifest json dictionary value.
-  static std::unique_ptr<base::DictionaryValue> GetManifest(
-      const std::string& manifest_string);
+  // Parses manifest string into dictionary value.
+  static absl::optional<base::Value::Dict> ParseManifest(
+      const base::StringPiece& manifest_string);
 
   // Reads extension information: description, option page. This function
   // returns true on success, otherwise returns false.
-  static bool ReadExtensionInfo(const base::DictionaryValue& manifest,
+  static bool ReadExtensionInfo(const base::Value::Dict& manifest,
                                 const std::string& extension_id,
                                 ComponentExtensionIME* out);
 
@@ -64,7 +65,7 @@
   // otherwise return false. This function must be called on FILE thread.
   static bool ReadEngineComponent(
       const ComponentExtensionIME& component_extension,
-      const base::DictionaryValue& dict,
+      const base::Value::Dict& dict,
       ComponentExtensionEngine* out);
 
   // The list of component extension IME.
diff --git a/chrome/browser/ash/input_method/native_input_method_engine_observer.cc b/chrome/browser/ash/input_method/native_input_method_engine_observer.cc
index 8e82e3a..cad7fc5 100644
--- a/chrome/browser/ash/input_method/native_input_method_engine_observer.cc
+++ b/chrome/browser/ash/input_method/native_input_method_engine_observer.cc
@@ -626,9 +626,10 @@
   if (!bound) {
     return;
   }
-  if (!base::FeatureList::IsEnabled(features::kSystemJapanesePhysicalTyping) &&
-      IsJapaneseSettingsMigrationComplete(*prefs_)) {
-    SetJapaneseSettingsMigrationComplete(*prefs_, false);
+  if (!base::FeatureList::IsEnabled(features::kSystemJapanesePhysicalTyping)) {
+    if (IsJapaneseSettingsMigrationComplete(*prefs_)) {
+      SetJapaneseSettingsMigrationComplete(*prefs_, false);
+    }
     return;
   }
   if (IsJapaneseSettingsMigrationComplete(*prefs_)) {
@@ -665,11 +666,13 @@
 
   // TODO(b/232341104): Add metrics to track how long this takes to init the
   // connection.
-  connection_factory_->ConnectToJapaneseDecoder(
-      japanese_decoder_.BindNewEndpointAndPassReceiver(),
-      base::BindOnce(
-          &NativeInputMethodEngineObserver::OnJapaneseDecoderConnected,
-          weak_ptr_factory_.GetWeakPtr()));
+  if (base::FeatureList::IsEnabled(features::kSystemJapanesePhysicalTyping)) {
+    connection_factory_->ConnectToJapaneseDecoder(
+        japanese_decoder_.BindNewEndpointAndPassReceiver(),
+        base::BindOnce(
+            &NativeInputMethodEngineObserver::OnJapaneseDecoderConnected,
+            weak_ptr_factory_.GetWeakPtr()));
+  }
   // If this is fast enough, maybe this code can block the ConnectToInputMethod
   // function on waiting for the migration if and only if the input_method
   // engine is JP.
diff --git a/chrome/browser/ash/lock_screen_apps/app_manager_impl.cc b/chrome/browser/ash/lock_screen_apps/app_manager_impl.cc
index a22fee19..178c3f2 100644
--- a/chrome/browser/ash/lock_screen_apps/app_manager_impl.cc
+++ b/chrome/browser/ash/lock_screen_apps/app_manager_impl.cc
@@ -423,10 +423,9 @@
 
   std::string error;
   scoped_refptr<extensions::Extension> lock_profile_app =
-      extensions::Extension::Create(
-          lock_profile_app_path, app->location(),
-          base::Value::AsDictionaryValue(app->manifest()->value()->Clone()),
-          app->creation_flags(), app->id(), &error);
+      extensions::Extension::Create(lock_profile_app_path, app->location(),
+                                    app->manifest()->value()->GetDict(),
+                                    app->creation_flags(), app->id(), &error);
 
   // While extension creation can fail in general, in this case the lock screen
   // profile extension creation arguments come from an app already installed in
@@ -567,10 +566,9 @@
 
   std::string error;
   scoped_refptr<extensions::Extension> lock_profile_app =
-      extensions::Extension::Create(
-          app->path(), app->location(),
-          base::Value::AsDictionaryValue(app->manifest()->value()->Clone()),
-          app->creation_flags(), app->id(), &error);
+      extensions::Extension::Create(app->path(), app->location(),
+                                    app->manifest()->value()->GetDict(),
+                                    app->creation_flags(), app->id(), &error);
 
   extensions::ExtensionService* extension_service =
       extensions::ExtensionSystem::Get(lock_screen_profile_)
diff --git a/chrome/browser/ash/login/quick_unlock/quick_unlock_factory.cc b/chrome/browser/ash/login/quick_unlock/quick_unlock_factory.cc
index 79562dea..98cec35 100644
--- a/chrome/browser/ash/login/quick_unlock/quick_unlock_factory.cc
+++ b/chrome/browser/ash/login/quick_unlock/quick_unlock_factory.cc
@@ -4,9 +4,12 @@
 
 #include "chrome/browser/ash/login/quick_unlock/quick_unlock_factory.h"
 
+#include "base/no_destructor.h"
+#include "chrome/browser/ash/login/quick_unlock/auth_token.h"
 #include "chrome/browser/ash/login/quick_unlock/quick_unlock_storage.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chromeos/ash/components/login/auth/public/user_context.h"
 #include "components/user_manager/user.h"
 #include "components/user_manager/user_manager.h"
 
@@ -45,6 +48,49 @@
   return base::Singleton<QuickUnlockFactory>::get();
 }
 
+ash::auth::QuickUnlockStorageDelegate& QuickUnlockFactory::GetDelegate() {
+  class Delegate : public auth::QuickUnlockStorageDelegate {
+    UserContext* GetUserContext(const ::user_manager::User* user,
+                                const std::string& token) override {
+      if (!user) {
+        LOG(ERROR) << "Invalid user";
+        return nullptr;
+      }
+      QuickUnlockStorage* storage = GetForUser(user);
+      if (!storage) {
+        LOG(ERROR) << "User does not have a QuickUnlockStorage";
+        return nullptr;
+      }
+      return storage->GetUserContext(token);
+    }
+
+    void SetUserContext(const ::user_manager::User* user,
+                        std::unique_ptr<UserContext> context) override {
+      if (!user) {
+        LOG(ERROR) << "Invalid user";
+        return;
+      }
+      QuickUnlockStorage* storage =
+          quick_unlock::QuickUnlockFactory::GetForUser(user);
+      if (!user) {
+        LOG(ERROR) << "User does not have a QuickUnlockStorage";
+        return;
+      }
+      quick_unlock::AuthToken* auth_token = storage->GetAuthToken();
+      if (auth_token == nullptr || auth_token->user_context() == nullptr) {
+        // If this happens, it means that the auth token expired. In this case,
+        // the user needs to reauthenticate, and a new context will be created.
+        return;
+      }
+
+      auth_token->ReplaceUserContext(std::move(context));
+    }
+  };
+
+  static base::NoDestructor<Delegate> delegate;
+  return *delegate;
+}
+
 QuickUnlockFactory::QuickUnlockFactory()
     : ProfileKeyedServiceFactory("QuickUnlockFactory") {}
 
diff --git a/chrome/browser/ash/login/quick_unlock/quick_unlock_factory.h b/chrome/browser/ash/login/quick_unlock/quick_unlock_factory.h
index a40001c..4aedbc9 100644
--- a/chrome/browser/ash/login/quick_unlock/quick_unlock_factory.h
+++ b/chrome/browser/ash/login/quick_unlock/quick_unlock_factory.h
@@ -7,6 +7,7 @@
 
 #include "base/memory/singleton.h"
 #include "chrome/browser/profiles/profile_keyed_service_factory.h"
+#include "chromeos/ash/services/auth_factor_config/quick_unlock_storage_delegate.h"
 #include "components/account_id/account_id.h"
 
 class Profile;
@@ -39,6 +40,9 @@
 
   static QuickUnlockFactory* GetInstance();
 
+  // Returns a delegate to QuickUnlockStorage.
+  static ash::auth::QuickUnlockStorageDelegate& GetDelegate();
+
   QuickUnlockFactory(const QuickUnlockFactory&) = delete;
   QuickUnlockFactory& operator=(const QuickUnlockFactory&) = delete;
 
diff --git a/chrome/browser/ash/login/quick_unlock/quick_unlock_storage.cc b/chrome/browser/ash/login/quick_unlock/quick_unlock_storage.cc
index 840576d9..321ca95d 100644
--- a/chrome/browser/ash/login/quick_unlock/quick_unlock_storage.cc
+++ b/chrome/browser/ash/login/quick_unlock/quick_unlock_storage.cc
@@ -86,8 +86,7 @@
   return auth_token_.get();
 }
 
-const UserContext* QuickUnlockStorage::GetUserContext(
-    const std::string& auth_token) {
+UserContext* QuickUnlockStorage::GetUserContext(const std::string& auth_token) {
   if (!auth_token_ || auth_token_->Identifier() != auth_token)
     return nullptr;
 
diff --git a/chrome/browser/ash/login/quick_unlock/quick_unlock_storage.h b/chrome/browser/ash/login/quick_unlock/quick_unlock_storage.h
index 2eedd82..019443e 100644
--- a/chrome/browser/ash/login/quick_unlock/quick_unlock_storage.h
+++ b/chrome/browser/ash/login/quick_unlock/quick_unlock_storage.h
@@ -74,7 +74,7 @@
   AuthToken* GetAuthToken();
 
   // Fetch the user context if `auth_token` is valid. May return null.
-  const UserContext* GetUserContext(const std::string& auth_token);
+  UserContext* GetUserContext(const std::string& auth_token);
 
   void ReplaceUserContext(const std::string& auth_token,
                           std::unique_ptr<UserContext>);
diff --git a/chrome/browser/ash/login/screens/choobe_screen.cc b/chrome/browser/ash/login/screens/choobe_screen.cc
new file mode 100644
index 0000000..f7c12b2
--- /dev/null
+++ b/chrome/browser/ash/login/screens/choobe_screen.cc
@@ -0,0 +1,94 @@
+// 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/ash/login/screens/choobe_screen.h"
+#include "chrome/browser/profiles/profile_manager.h"
+
+#include "chrome/browser/ash/login/choobe_flow_controller.h"
+#include "chrome/browser/ash/login/wizard_controller.h"
+#include "chrome/browser/ui/webui/ash/login/choobe_screen_handler.h"
+
+namespace ash {
+namespace {
+
+constexpr const char kUserActionSkip[] = "choobeSkip";
+constexpr const char kUserActionSelect[] = "choobeSelect";
+
+}  // namespace
+
+// static
+std::string ChoobeScreen::GetResultString(Result result) {
+  switch (result) {
+    case Result::SELECTED:
+      return "Selected";
+    case Result::SKIPPED:
+      return "Skipped";
+    case Result::NOT_APPLICABLE:
+      return BaseScreen::kNotApplicable;
+  }
+}
+
+ChoobeScreen::ChoobeScreen(base::WeakPtr<ChoobeScreenView> view,
+                           const ScreenExitCallback& exit_callback)
+    : BaseScreen(ChoobeScreenView::kScreenId, OobeScreenPriority::DEFAULT),
+      view_(std::move(view)),
+      exit_callback_(exit_callback) {}
+
+ChoobeScreen::~ChoobeScreen() = default;
+
+// to check with the ChoobeFlowController whether to skip
+bool ChoobeScreen::MaybeSkip(WizardContext& context) {
+  auto* choobe_controller_ =
+      WizardController::default_controller()->GetChoobeFlowController();
+  if (choobe_controller_ && choobe_controller_->IsChoobeFlowActive())
+    return false;
+
+  exit_callback_.Run(Result::NOT_APPLICABLE);
+  return true;
+}
+
+void ChoobeScreen::SkipScreen() {
+  WizardController::default_controller()->GetChoobeFlowController()->Stop(
+      *ProfileManager::GetActiveUserProfile()->GetPrefs());
+  exit_callback_.Run(Result::SKIPPED);
+}
+
+void ChoobeScreen::OnSelect(base::Value::List screens) {
+  WizardController::default_controller()
+      ->GetChoobeFlowController()
+      ->OnScreensSelected(*ProfileManager::GetActiveUserProfile()->GetPrefs(),
+                          std::move(screens));
+  exit_callback_.Run(Result::SELECTED);
+}
+
+void ChoobeScreen::ShowImpl() {
+  if (!view_)
+    return;
+
+  auto screens = WizardController::default_controller()
+                     ->GetChoobeFlowController()
+                     ->GetEligibleCHOOBEScreens();
+
+  view_->Show(std::move(screens));
+}
+
+void ChoobeScreen::HideImpl() {}
+
+void ChoobeScreen::OnUserAction(const base::Value::List& args) {
+  const std::string& action_id = args[0].GetString();
+  if (action_id == kUserActionSkip) {
+    SkipScreen();
+    return;
+  }
+
+  if (action_id == kUserActionSelect) {
+    CHECK_EQ(args.size(), 2u);
+    OnSelect(args[1].GetList().Clone());
+    return;
+  }
+
+  BaseScreen::OnUserAction(args);
+}
+
+}  // namespace ash
diff --git a/chrome/browser/ash/login/screens/choobe_screen.h b/chrome/browser/ash/login/screens/choobe_screen.h
new file mode 100644
index 0000000..51697fb6
--- /dev/null
+++ b/chrome/browser/ash/login/screens/choobe_screen.h
@@ -0,0 +1,69 @@
+// 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_ASH_LOGIN_SCREENS_CHOOBE_SCREEN_H_
+#define CHROME_BROWSER_ASH_LOGIN_SCREENS_CHOOBE_SCREEN_H_
+
+#include <memory>
+#include <string>
+
+#include "base/callback.h"
+#include "base/memory/weak_ptr.h"
+#include "chrome/browser/ash/login/choobe_flow_controller.h"
+#include "chrome/browser/ash/login/screens/base_screen.h"
+
+namespace base {
+class Value;
+}
+
+namespace ash {
+class ChoobeScreenView;
+
+// Controller for the CHOOBE screen.
+// Screen displays optional screens and allows
+// user to select which screens to be shown.
+class ChoobeScreen : public BaseScreen {
+ public:
+  using TView = ChoobeScreenView;
+
+  enum class Result { SELECTED, SKIPPED, NOT_APPLICABLE };
+
+  static std::string GetResultString(Result result);
+
+  using ScreenExitCallback = base::RepeatingCallback<void(Result result)>;
+
+  ChoobeScreen(base::WeakPtr<ChoobeScreenView> view,
+               const ScreenExitCallback& exit_callback);
+
+  ChoobeScreen(const ChoobeScreen&) = delete;
+  ChoobeScreen& operator=(const ChoobeScreen&) = delete;
+
+  ~ChoobeScreen() override;
+
+ private:
+  // BaseScreen:
+  bool MaybeSkip(WizardContext& context) override;
+  void ShowImpl() override;
+  void HideImpl() override;
+  void OnUserAction(const base::Value::List& args) override;
+
+  // Called when the user skips the CHOOBE screen.
+  void SkipScreen();
+
+  // Called when the user selects screens on the CHOOBE screen.
+  void OnSelect(base::Value::List screens);
+
+  base::WeakPtr<ChoobeScreenView> view_;
+  ScreenExitCallback exit_callback_;
+};
+
+}  // namespace ash
+
+// TODO(https://crbug.com/1164001): remove after the //chrome/browser/chromeos
+// source migration is finished.
+namespace chromeos {
+using ::ash ::ChoobeScreen;
+}
+
+#endif  // CHROME_BROWSER_ASH_LOGIN_SCREENS_CHOOBE_SCREEN_H_
diff --git a/chrome/browser/ash/login/screens/cryptohome_recovery_setup_screen.cc b/chrome/browser/ash/login/screens/cryptohome_recovery_setup_screen.cc
index 7db47d8f..53facc24 100644
--- a/chrome/browser/ash/login/screens/cryptohome_recovery_setup_screen.cc
+++ b/chrome/browser/ash/login/screens/cryptohome_recovery_setup_screen.cc
@@ -51,7 +51,9 @@
   CHECK(context()->extra_factors_auth_session);
   const std::string token = quick_unlock_storage->CreateAuthToken(
       *context()->extra_factors_auth_session);
-  auth::GetRecoveryFactorEditor().Configure(
+  auto& recovery_editor = auth::GetRecoveryFactorEditor(
+      quick_unlock::QuickUnlockFactory::GetDelegate());
+  recovery_editor.Configure(
       token, /*enabled=*/true,
       base::BindOnce(&CryptohomeRecoverySetupScreen::OnRecoveryConfigured,
                      weak_ptr_factory_.GetWeakPtr()));
diff --git a/chrome/browser/ash/login/screens/cryptohome_recovery_setup_screen_browsertest.cc b/chrome/browser/ash/login/screens/cryptohome_recovery_setup_screen_browsertest.cc
index b279f941..b4dc74b2 100644
--- a/chrome/browser/ash/login/screens/cryptohome_recovery_setup_screen_browsertest.cc
+++ b/chrome/browser/ash/login/screens/cryptohome_recovery_setup_screen_browsertest.cc
@@ -10,8 +10,10 @@
 #include "base/run_loop.h"
 #include "chrome/browser/ash/login/quick_unlock/quick_unlock_utils.h"
 #include "chrome/browser/ash/login/screen_manager.h"
+#include "chrome/browser/ash/login/test/cryptohome_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"
 #include "chrome/browser/ash/login/test/oobe_screen_waiter.h"
 #include "chrome/browser/ash/login/test/wizard_controller_screen_exit_waiter.h"
 #include "chrome/browser/ash/login/ui/login_display_host.h"
@@ -54,16 +56,23 @@
     context->defer_oobe_flow_finished_for_tests = true;
     login_manager_mixin_.LoginAsNewRegularUser();
     WizardControllerExitWaiter(UserCreationView::kScreenId).Wait();
-    // Wait for the recovery screen and copy the auth session configuration
-    // before the context is cleared.
+    // Wait for the recovery screen and copy the user context before it is
+    // cleared.
     WaitForScreenExit();
-    auto configuration =
-        context->extra_factors_auth_session->GetAuthFactorsConfiguration();
+    auto user_context =
+        std::make_unique<UserContext>(*context->extra_factors_auth_session);
+    cryptohome_.MarkUserAsExisting(user_context->GetAccountId());
     ContinueScreenExit();
+    // Wait until the OOBE flow finishes before we set new values on the wizard
+    // context.
+    OobeScreenExitWaiter(UserCreationView::kScreenId).Wait();
+
     // Set the values on the wizard context: the `extra_factors_auth_session`
-    // should be available for the test.
-    auto user_context = std::make_unique<UserContext>();
-    user_context->SetAuthFactorsConfiguration(configuration);
+    // is available after the previous screens have run regularly, and it holds
+    // an authenticated auth session.
+    user_context->ResetAuthSessionId();
+    user_context->SetAuthSessionId(cryptohome_.AddSession(
+        user_context->GetAccountId(), /*authenticated=*/true));
     context->extra_factors_auth_session = std::move(user_context);
     context->skip_post_login_screens_for_tests = false;
     // Clear the test state.
@@ -97,11 +106,14 @@
   }
 
   LoginManagerMixin login_manager_mixin_{&mixin_host_};
+  CryptohomeMixin cryptohome_{&mixin_host_};
   absl::optional<CryptohomeRecoverySetupScreen::Result> result_;
 
  private:
   void HandleScreenExit(CryptohomeRecoverySetupScreen::Result result) {
     result_ = result;
+    if (screen_exit_callback_)
+      std::move(screen_exit_callback_).Run();
   }
 
   base::test::ScopedFeatureList feature_list_;
diff --git a/chrome/browser/ash/login/screens/recovery_eligibility_screen_browsertest.cc b/chrome/browser/ash/login/screens/recovery_eligibility_screen_browsertest.cc
index 2dfc33b..c5710e5e 100644
--- a/chrome/browser/ash/login/screens/recovery_eligibility_screen_browsertest.cc
+++ b/chrome/browser/ash/login/screens/recovery_eligibility_screen_browsertest.cc
@@ -9,8 +9,10 @@
 #include "ash/public/cpp/test/shell_test_api.h"
 #include "base/run_loop.h"
 #include "chrome/browser/ash/login/screen_manager.h"
+#include "chrome/browser/ash/login/test/cryptohome_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"
 #include "chrome/browser/ash/login/test/oobe_screen_waiter.h"
 #include "chrome/browser/ash/login/test/wizard_controller_screen_exit_waiter.h"
 #include "chrome/browser/ash/login/ui/login_display_host.h"
@@ -49,22 +51,30 @@
   }
 
   void LoginAsRegularUser() {
-    LoginDisplayHost::default_host()
-        ->GetWizardContextForTesting()
-        ->defer_oobe_flow_finished_for_tests = true;
+    // Login, and skip the post login screens.
+    auto* context =
+        LoginDisplayHost::default_host()->GetWizardContextForTesting();
+    context->skip_post_login_screens_for_tests = true;
+    context->defer_oobe_flow_finished_for_tests = true;
     login_manager_mixin_.LoginAsNewRegularUser();
     WizardControllerExitWaiter(UserCreationView::kScreenId).Wait();
     WaitForScreenExit();
-    auto configuration =
-        LoginDisplayHost::default_host()
-            ->GetWizardContextForTesting()
-            ->extra_factors_auth_session->GetAuthFactorsConfiguration();
+    auto user_context =
+        std::make_unique<UserContext>(*context->extra_factors_auth_session);
+    cryptohome_.MarkUserAsExisting(user_context->GetAccountId());
     ContinueScreenExit();
-    auto context = std::make_unique<UserContext>();
-    context->SetAuthFactorsConfiguration(configuration);
-    LoginDisplayHost::default_host()
-        ->GetWizardContextForTesting()
-        ->extra_factors_auth_session = std::move(context);
+    // Wait until the OOBE flow finishes before we set new values on the wizard
+    // context.
+    OobeScreenExitWaiter(UserCreationView::kScreenId).Wait();
+
+    // Set the values on the wizard context: the `extra_factors_auth_session`
+    // is available after the previous screens have run regularly, and it holds
+    // an authenticated auth session.
+    user_context->ResetAuthSessionId();
+    user_context->SetAuthSessionId(cryptohome_.AddSession(
+        user_context->GetAccountId(), /*authenticated=*/true));
+    context->extra_factors_auth_session = std::move(user_context);
+    context->skip_post_login_screens_for_tests = false;
     result_ = absl::nullopt;
   }
 
@@ -96,6 +106,7 @@
   }
 
   LoginManagerMixin login_manager_mixin_{&mixin_host_};
+  CryptohomeMixin cryptohome_{&mixin_host_};
   absl::optional<RecoveryEligibilityScreen::Result> result_;
 
  private:
diff --git a/chrome/browser/ash/login/screens/theme_selection_screen.cc b/chrome/browser/ash/login/screens/theme_selection_screen.cc
index 938537d..79275fc9 100644
--- a/chrome/browser/ash/login/screens/theme_selection_screen.cc
+++ b/chrome/browser/ash/login/screens/theme_selection_screen.cc
@@ -10,6 +10,7 @@
 #include "ash/system/scheduled_feature/scheduled_feature.h"
 #include "chrome/browser/ash/login/screens/base_screen.h"
 #include "chrome/browser/ash/login/wizard_context.h"
+#include "chrome/browser/ash/login/wizard_controller.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/ui/webui/ash/login/theme_selection_screen_handler.h"
 #include "components/prefs/pref_service.h"
@@ -60,6 +61,13 @@
   if (context.skip_post_login_screens_for_tests)
     return true;
 
+  if (features::IsOobeChoobeEnabled() &&
+      WizardController::default_controller()
+          ->GetChoobeFlowController()
+          ->ShouldScreenBeSkipped(ThemeSelectionScreenView::kScreenId)) {
+    return true;
+  }
+
   const PrefService::Preference* pref =
       ProfileManager::GetActiveUserProfile()->GetPrefs()->FindPreference(
           prefs::kDarkModeScheduleType);
diff --git a/chrome/browser/ash/login/test/cryptohome_mixin.cc b/chrome/browser/ash/login/test/cryptohome_mixin.cc
index 96dcbc93..c99139b 100644
--- a/chrome/browser/ash/login/test/cryptohome_mixin.cc
+++ b/chrome/browser/ash/login/test/cryptohome_mixin.cc
@@ -28,6 +28,13 @@
       std::move(account_id));
 }
 
+std::string CryptohomeMixin::AddSession(const AccountId& user,
+                                        bool authenticated) {
+  auto account_id = cryptohome::CreateAccountIdentifierFromAccountId(user);
+  return FakeUserDataAuthClient::TestApi::Get()->AddSession(
+      std::move(account_id), authenticated);
+}
+
 void CryptohomeMixin::AddGaiaPassword(const AccountId& user,
                                       std::string password) {
   auto account_identifier =
@@ -53,4 +60,14 @@
       cryptohome::CreateAccountIdentifierFromAccountId(user));
 }
 
+void CryptohomeMixin::AddRecoveryFactor(const AccountId& user) {
+  return TestApi::AddRecoveryFactor(
+      cryptohome::CreateAccountIdentifierFromAccountId(user));
+}
+
+bool CryptohomeMixin::HasRecoveryFactor(const AccountId& user) {
+  return TestApi::HasRecoveryFactor(
+      cryptohome::CreateAccountIdentifierFromAccountId(user));
+}
+
 }  // namespace ash
diff --git a/chrome/browser/ash/login/test/cryptohome_mixin.h b/chrome/browser/ash/login/test/cryptohome_mixin.h
index 7ecc3da4..2ab1cb29 100644
--- a/chrome/browser/ash/login/test/cryptohome_mixin.h
+++ b/chrome/browser/ash/login/test/cryptohome_mixin.h
@@ -26,8 +26,11 @@
   ~CryptohomeMixin() override;
 
   void MarkUserAsExisting(const AccountId& user);
+  std::string AddSession(const AccountId& user, bool authenticated);
   void AddGaiaPassword(const AccountId& user, std::string password);
   bool HasPinFactor(const AccountId& user);
+  void AddRecoveryFactor(const AccountId& user);
+  bool HasRecoveryFactor(const AccountId& user);
 };
 
 }  // namespace ash
diff --git a/chrome/browser/ash/notifications/idle_app_name_notification_view_unittest.cc b/chrome/browser/ash/notifications/idle_app_name_notification_view_unittest.cc
index c3e9d35..4e9f5842 100644
--- a/chrome/browser/ash/notifications/idle_app_name_notification_view_unittest.cc
+++ b/chrome/browser/ash/notifications/idle_app_name_notification_view_unittest.cc
@@ -42,21 +42,21 @@
 
     BrowserWithTestWindowTest::SetUp();
 
-    base::DictionaryValue manifest;
-    manifest.SetStringKey(extensions::manifest_keys::kName, "Test");
-    manifest.SetStringKey(extensions::manifest_keys::kVersion, "1");
-    manifest.SetIntKey(extensions::manifest_keys::kManifestVersion, 2);
-    manifest.SetStringKey(extensions::manifest_keys::kDescription, "Test app");
-    manifest.SetStringPath("author.email", "Someone");
+    base::Value::Dict manifest;
+    manifest.Set(extensions::manifest_keys::kName, "Test");
+    manifest.Set(extensions::manifest_keys::kVersion, "1");
+    manifest.Set(extensions::manifest_keys::kManifestVersion, 2);
+    manifest.Set(extensions::manifest_keys::kDescription, "Test app");
+    manifest.SetByDottedPath("author.email", "Someone");
 
     std::string error;
     correct_extension_ = extensions::Extension::Create(
         base::FilePath(), extensions::mojom::ManifestLocation::kUnpacked,
         manifest, extensions::Extension::NO_FLAGS, kTestAppName, &error);
-    base::DictionaryValue manifest2;
-    manifest2.SetStringKey(extensions::manifest_keys::kName, "Test");
-    manifest2.SetStringKey(extensions::manifest_keys::kVersion, "1");
-    manifest2.SetStringKey(extensions::manifest_keys::kDescription, "Test app");
+    base::Value::Dict manifest2;
+    manifest2.Set(extensions::manifest_keys::kName, "Test");
+    manifest2.Set(extensions::manifest_keys::kVersion, "1");
+    manifest2.Set(extensions::manifest_keys::kDescription, "Test app");
 
     incorrect_extension_ = extensions::Extension::Create(
         base::FilePath(), extensions::mojom::ManifestLocation::kUnpacked,
diff --git a/chrome/browser/ash/os_feedback/os_feedback_screenshot_manager.h b/chrome/browser/ash/os_feedback/os_feedback_screenshot_manager.h
index 89ba8b3..d3bfbe55 100644
--- a/chrome/browser/ash/os_feedback/os_feedback_screenshot_manager.h
+++ b/chrome/browser/ash/os_feedback/os_feedback_screenshot_manager.h
@@ -8,14 +8,11 @@
 #include <vector>
 
 #include "base/callback.h"
+#include "base/memory/ref_counted_memory.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/memory/singleton.h"
 #include "base/memory/weak_ptr.h"
 
-namespace base {
-class RefCountedMemory;
-}  // namespace base
-
 namespace ash {
 
 // This is a singleton class used to manage screenshot taking and cleanup.
diff --git a/chrome/browser/ash/policy/core/browser_policy_connector_ash.cc b/chrome/browser/ash/policy/core/browser_policy_connector_ash.cc
index 6c2da77..1ac5b47 100644
--- a/chrome/browser/ash/policy/core/browser_policy_connector_ash.cc
+++ b/chrome/browser/ash/policy/core/browser_policy_connector_ash.cc
@@ -533,12 +533,6 @@
   }
 }
 
-void BrowserPolicyConnectorAsh::OnDeviceCloudPolicyManagerDisconnected() {
-  DCHECK(!device_cloud_policy_initializer_);
-
-  RestartDeviceCloudPolicyInitializer();
-}
-
 void BrowserPolicyConnectorAsh::OnDeviceCloudPolicyManagerGotRegistry() {
   // Do nothing.
 }
diff --git a/chrome/browser/ash/policy/core/browser_policy_connector_ash.h b/chrome/browser/ash/policy/core/browser_policy_connector_ash.h
index f2e7fab..f07cf49 100644
--- a/chrome/browser/ash/policy/core/browser_policy_connector_ash.h
+++ b/chrome/browser/ash/policy/core/browser_policy_connector_ash.h
@@ -237,7 +237,6 @@
 
   // DeviceCloudPolicyManagerAsh::Observer:
   void OnDeviceCloudPolicyManagerConnected() override;
-  void OnDeviceCloudPolicyManagerDisconnected() override;
   void OnDeviceCloudPolicyManagerGotRegistry() override;
 
   // TODO(crbug.com/1187628): Combine the following two functions into one to
diff --git a/chrome/browser/ash/policy/core/device_cloud_policy_manager_ash.cc b/chrome/browser/ash/policy/core/device_cloud_policy_manager_ash.cc
index cd7732f..fb14f35 100644
--- a/chrome/browser/ash/policy/core/device_cloud_policy_manager_ash.cc
+++ b/chrome/browser/ash/policy/core/device_cloud_policy_manager_ash.cc
@@ -273,15 +273,6 @@
   CreateManagedSessionServiceAndReporters();
 }
 
-void DeviceCloudPolicyManagerAsh::Disconnect() {
-  status_uploader_.reset();
-  syslog_uploader_.reset();
-  heartbeat_scheduler_.reset();
-  core()->Disconnect();
-
-  NotifyDisconnected();
-}
-
 void DeviceCloudPolicyManagerAsh::SetSigninProfileSchemaRegistry(
     SchemaRegistry* schema_registry) {
   DCHECK(!signin_profile_forwarding_schema_registry_);
@@ -302,11 +293,6 @@
     observer.OnDeviceCloudPolicyManagerConnected();
 }
 
-void DeviceCloudPolicyManagerAsh::NotifyDisconnected() {
-  for (auto& observer : observers_)
-    observer.OnDeviceCloudPolicyManagerDisconnected();
-}
-
 void DeviceCloudPolicyManagerAsh::NotifyGotRegistry() {
   for (auto& observer : observers_)
     observer.OnDeviceCloudPolicyManagerGotRegistry();
diff --git a/chrome/browser/ash/policy/core/device_cloud_policy_manager_ash.h b/chrome/browser/ash/policy/core/device_cloud_policy_manager_ash.h
index 29f40571..18330c2 100644
--- a/chrome/browser/ash/policy/core/device_cloud_policy_manager_ash.h
+++ b/chrome/browser/ash/policy/core/device_cloud_policy_manager_ash.h
@@ -65,8 +65,6 @@
    public:
     // Invoked when the device cloud policy manager connects.
     virtual void OnDeviceCloudPolicyManagerConnected() = 0;
-    // Invoked when the device cloud policy manager disconnects.
-    virtual void OnDeviceCloudPolicyManagerDisconnected() = 0;
     // Invoked when the device cloud policy manager obtains schema registry.
     virtual void OnDeviceCloudPolicyManagerGotRegistry() = 0;
   };
@@ -115,9 +113,6 @@
   // Called when policy store is ready.
   void OnPolicyStoreReady(ash::InstallAttributes* install_attributes);
 
-  // Disconnects the manager.
-  virtual void Disconnect();
-
   bool IsConnected() const { return core()->service() != nullptr; }
 
   bool HasSchemaRegistry() const {
diff --git a/chrome/browser/ash/policy/core/device_cloud_policy_manager_ash_unittest.cc b/chrome/browser/ash/policy/core/device_cloud_policy_manager_ash_unittest.cc
index c14bcd6..1f57ddb 100644
--- a/chrome/browser/ash/policy/core/device_cloud_policy_manager_ash_unittest.cc
+++ b/chrome/browser/ash/policy/core/device_cloud_policy_manager_ash_unittest.cc
@@ -540,11 +540,10 @@
 
   // DeviceCloudPolicyManagerAsh::Observer:
   MOCK_METHOD0(OnDeviceCloudPolicyManagerConnected, void());
-  MOCK_METHOD0(OnDeviceCloudPolicyManagerDisconnected, void());
   MOCK_METHOD0(OnDeviceCloudPolicyManagerGotRegistry, void());
 };
 
-TEST_F(DeviceCloudPolicyManagerAshObserverTest, ConnectAndDisconnect) {
+TEST_F(DeviceCloudPolicyManagerAshObserverTest, Connect) {
   LockDevice();
   device_settings_service_->LoadImmediately();
   FlushDeviceSettings();
@@ -560,11 +559,6 @@
   Mock::VerifyAndClearExpectations(&device_management_service_);
   Mock::VerifyAndClearExpectations(this);
   EXPECT_TRUE(manager_->IsConnected());
-
-  // Disconnect the manager.
-  EXPECT_CALL(*this, OnDeviceCloudPolicyManagerDisconnected());
-  manager_->Disconnect();
-  EXPECT_FALSE(manager_->IsConnected());
 }
 
 TEST_F(DeviceCloudPolicyManagerAshObserverTest, GetSchemaRegistry) {
diff --git a/chrome/browser/ash/policy/core/device_policy_decoder.cc b/chrome/browser/ash/policy/core/device_policy_decoder.cc
index 3d68478..c8500428 100644
--- a/chrome/browser/ash/policy/core/device_policy_decoder.cc
+++ b/chrome/browser/ash/policy/core/device_policy_decoder.cc
@@ -1436,19 +1436,6 @@
     }
   }
 
-  if (policy.has_keyboard_brightness()) {
-    const em::KeyboardBrightnessProto& container(policy.keyboard_brightness());
-    if (container.has_percentage()) {
-      // This policy is interpreted as "Recommended".
-      // See the comment at the definition of the
-      // ash::prefs::kPersonalizationKeyboardBrightness pref (to which this
-      // policy will be mapped) for more details.
-      policies->Set(key::kDeviceKeyboardBrightness, POLICY_LEVEL_RECOMMENDED,
-                    POLICY_SCOPE_MACHINE, POLICY_SOURCE_CLOUD,
-                    base::Value(container.percentage()), nullptr);
-    }
-  }
-
   if (policy.has_allow_redeem_offers()) {
     const em::AllowRedeemChromeOsRegistrationOffersProto& container(
         policy.allow_redeem_offers());
diff --git a/chrome/browser/ash/policy/core/fake_device_cloud_policy_manager.cc b/chrome/browser/ash/policy/core/fake_device_cloud_policy_manager.cc
index 3905732..0093fd4f 100644
--- a/chrome/browser/ash/policy/core/fake_device_cloud_policy_manager.cc
+++ b/chrome/browser/ash/policy/core/fake_device_cloud_policy_manager.cc
@@ -25,6 +25,4 @@
   Shutdown();
 }
 
-void FakeDeviceCloudPolicyManager::Disconnect() {}
-
 }  // namespace policy
diff --git a/chrome/browser/ash/policy/core/fake_device_cloud_policy_manager.h b/chrome/browser/ash/policy/core/fake_device_cloud_policy_manager.h
index 55068df..4d079f7 100644
--- a/chrome/browser/ash/policy/core/fake_device_cloud_policy_manager.h
+++ b/chrome/browser/ash/policy/core/fake_device_cloud_policy_manager.h
@@ -29,9 +29,6 @@
       delete;
 
   ~FakeDeviceCloudPolicyManager() override;
-
-  // DeviceCloudPolicyManagerAsh:
-  void Disconnect() override;
 };
 
 }  // namespace policy
diff --git a/chrome/browser/ash/policy/enrollment/device_cloud_policy_initializer.cc b/chrome/browser/ash/policy/enrollment/device_cloud_policy_initializer.cc
index cfe4bf3..759b2afe 100644
--- a/chrome/browser/ash/policy/enrollment/device_cloud_policy_initializer.cc
+++ b/chrome/browser/ash/policy/enrollment/device_cloud_policy_initializer.cc
@@ -81,9 +81,6 @@
 void DeviceCloudPolicyInitializer::OnDeviceCloudPolicyManagerConnected() {
   // Do nothing.
 }
-void DeviceCloudPolicyInitializer::OnDeviceCloudPolicyManagerDisconnected() {
-  // Do nothing.
-}
 void DeviceCloudPolicyInitializer::OnDeviceCloudPolicyManagerGotRegistry() {
   // `policy_manager_->HasSchemaRegistry()` is one of requirements for
   // StartConnection. Make another attempt when `policy_manager_` gets its
diff --git a/chrome/browser/ash/policy/enrollment/device_cloud_policy_initializer.h b/chrome/browser/ash/policy/enrollment/device_cloud_policy_initializer.h
index 28b2b87db..67535c2 100644
--- a/chrome/browser/ash/policy/enrollment/device_cloud_policy_initializer.h
+++ b/chrome/browser/ash/policy/enrollment/device_cloud_policy_initializer.h
@@ -68,7 +68,6 @@
 
   // DeviceCloudPolicyManagerAsh::Observer
   void OnDeviceCloudPolicyManagerConnected() override;
-  void OnDeviceCloudPolicyManagerDisconnected() override;
   void OnDeviceCloudPolicyManagerGotRegistry() override;
 
   void SetSystemURLLoaderFactoryForTesting(
diff --git a/chrome/browser/ash/policy/remote_commands/crd_host_delegate_unittest.cc b/chrome/browser/ash/policy/remote_commands/crd_host_delegate_unittest.cc
index 1e2968f..eaf90ec 100644
--- a/chrome/browser/ash/policy/remote_commands/crd_host_delegate_unittest.cc
+++ b/chrome/browser/ash/policy/remote_commands/crd_host_delegate_unittest.cc
@@ -25,6 +25,8 @@
     DeviceCommandStartCrdSessionJob::Delegate::SessionParameters;
 using StartSupportSessionCallback =
     crosapi::mojom::Remoting::StartSupportSessionCallback;
+
+using ResultCode = DeviceCommandStartCrdSessionJob::ResultCode;
 using remoting::mojom::StartSupportSessionResponse;
 using remoting::mojom::StartSupportSessionResponsePtr;
 using remoting::mojom::SupportHostObserver;
@@ -118,7 +120,7 @@
     return Response(access_code);
   }
 
-  static Response Error(DeviceCommandStartCrdSessionJob::ResultCode error_code,
+  static Response Error(ResultCode error_code,
                         const std::string& error_message) {
     return Response(error_code, error_message);
   }
@@ -134,9 +136,8 @@
     return error_message_.value_or("<no error received>");
   }
 
-  DeviceCommandStartCrdSessionJob::ResultCode error_code() const {
-    return error_code_.value_or(
-        DeviceCommandStartCrdSessionJob::ResultCode::SUCCESS);
+  ResultCode error_code() const {
+    return error_code_.value_or(ResultCode::SUCCESS);
   }
 
   std::string access_code() const {
@@ -146,12 +147,11 @@
  private:
   explicit Response(const std::string& access_code)
       : access_code_(access_code) {}
-  Response(DeviceCommandStartCrdSessionJob::ResultCode error_code,
-           const std::string& error_message)
+  Response(ResultCode error_code, const std::string& error_message)
       : error_code_(error_code), error_message_(error_message) {}
 
   absl::optional<std::string> access_code_;
-  absl::optional<DeviceCommandStartCrdSessionJob::ResultCode> error_code_;
+  absl::optional<ResultCode> error_code_;
   absl::optional<std::string> error_message_;
 };
 
@@ -178,8 +178,7 @@
 
   auto error_callback() {
     return base::BindOnce(
-        [](base::OnceCallback<void(Response)> setter,
-           DeviceCommandStartCrdSessionJob::ResultCode error_code,
+        [](base::OnceCallback<void(Response)> setter, ResultCode error_code,
            const std::string& error_message) {
           std::move(setter).Run(Response::Error(error_code, error_message));
         },
@@ -361,8 +360,7 @@
 
   Response response = WaitForResponse();
   ASSERT_TRUE(response.HasError());
-  EXPECT_EQ(DeviceCommandStartCrdSessionJob::FAILURE_CRD_HOST_ERROR,
-            response.error_code());
+  EXPECT_EQ(ResultCode::FAILURE_CRD_HOST_ERROR, response.error_code());
 }
 
 TEST_F(CrdHostDelegateTest, ShouldReturnAccessCode) {
@@ -384,8 +382,7 @@
   Response response = WaitForResponse();
   ASSERT_TRUE(response.HasError());
   EXPECT_EQ("host disconnected", response.error_message());
-  EXPECT_EQ(DeviceCommandStartCrdSessionJob::FAILURE_CRD_HOST_ERROR,
-            response.error_code());
+  EXPECT_EQ(ResultCode::FAILURE_CRD_HOST_ERROR, response.error_code());
 }
 
 TEST_F(CrdHostDelegateTest,
@@ -397,8 +394,7 @@
   Response response = WaitForResponse();
   ASSERT_TRUE(response.HasError());
   EXPECT_EQ("policy error", response.error_message());
-  EXPECT_EQ(DeviceCommandStartCrdSessionJob::FAILURE_CRD_HOST_ERROR,
-            response.error_code());
+  EXPECT_EQ(ResultCode::FAILURE_CRD_HOST_ERROR, response.error_code());
 }
 
 TEST_F(CrdHostDelegateTest,
@@ -410,8 +406,7 @@
   Response response = WaitForResponse();
   ASSERT_TRUE(response.HasError());
   EXPECT_EQ("invalid domain error", response.error_message());
-  EXPECT_EQ(DeviceCommandStartCrdSessionJob::FAILURE_CRD_HOST_ERROR,
-            response.error_code());
+  EXPECT_EQ(ResultCode::FAILURE_CRD_HOST_ERROR, response.error_code());
 }
 
 TEST_F(CrdHostDelegateTest,
@@ -423,8 +418,7 @@
   Response response = WaitForResponse();
   ASSERT_TRUE(response.HasError());
   EXPECT_EQ("host state error", response.error_message());
-  EXPECT_EQ(DeviceCommandStartCrdSessionJob::FAILURE_CRD_HOST_ERROR,
-            response.error_code());
+  EXPECT_EQ(ResultCode::FAILURE_CRD_HOST_ERROR, response.error_code());
 }
 
 TEST_F(CrdHostDelegateTest, HasActiveSessionShouldBeTrueWhenASessionIsStarted) {
diff --git a/chrome/browser/ash/policy/remote_commands/crd_remote_command_utils.cc b/chrome/browser/ash/policy/remote_commands/crd_remote_command_utils.cc
new file mode 100644
index 0000000..dfddd84
--- /dev/null
+++ b/chrome/browser/ash/policy/remote_commands/crd_remote_command_utils.cc
@@ -0,0 +1,204 @@
+// 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/ash/policy/remote_commands/crd_remote_command_utils.h"
+
+#include "base/check_deref.h"
+#include "base/functional/bind.h"
+#include "base/notreached.h"
+#include "base/time/time.h"
+#include "chrome/browser/ash/app_mode/arc/arc_kiosk_app_manager.h"
+#include "chrome/browser/ash/app_mode/kiosk_app_manager.h"
+#include "chrome/browser/ash/app_mode/web_app/web_kiosk_app_manager.h"
+#include "chrome/browser/ash/policy/remote_commands/crd_logging.h"
+#include "chromeos/services/network_config/in_process_instance.h"
+#include "components/user_manager/user_manager.h"
+#include "ui/base/user_activity/user_activity_detector.h"
+
+namespace policy {
+
+namespace {
+
+using chromeos::network_config::mojom::CrosNetworkConfig;
+using chromeos::network_config::mojom::FilterType;
+using chromeos::network_config::mojom::kNoLimit;
+using chromeos::network_config::mojom::NetworkFilter;
+using chromeos::network_config::mojom::NetworkStateProperties;
+using chromeos::network_config::mojom::NetworkStatePropertiesPtr;
+using chromeos::network_config::mojom::NetworkType;
+using chromeos::network_config::mojom::OncSource;
+
+const ash::KioskAppManagerBase* GetKioskAppManager(
+    const user_manager::UserManager& user_manager) {
+  if (user_manager.IsLoggedInAsKioskApp())
+    return ash::KioskAppManager::Get();
+  if (user_manager.IsLoggedInAsArcKioskApp())
+    return ash::ArcKioskAppManager::Get();
+  if (user_manager.IsLoggedInAsWebKioskApp())
+    return ash::WebKioskAppManager::Get();
+
+  // This method should only be invoked when we know we're in a kiosk
+  // environment, so one of these app managers must exist.
+  NOTREACHED();
+  return nullptr;
+}
+
+bool IsRunningAutoLaunchedKiosk(const user_manager::UserManager& user_manager) {
+  const auto& kiosk_app_manager = CHECK_DEREF(GetKioskAppManager(user_manager));
+  return kiosk_app_manager.current_app_was_auto_launched_with_zero_delay();
+}
+
+// Helper method that DVLOGs all the given networks.
+void LogNetworks(const std::vector<NetworkStatePropertiesPtr>& networks,
+                 const char* type) {
+  CRD_DVLOG(3) << "Found " << networks.size() << " " << type << " networks:";
+  for (const auto& network : networks) {
+    CRD_DVLOG(3) << "   --> " << network->name << " (" << network->guid << "): "
+                 << " ONC source: " << network->source
+                 << ", Type: " << network->type;
+  }
+}
+
+bool IsNetworkManagedByPolicy(
+    const NetworkStatePropertiesPtr& network_properties) {
+  return network_properties->source == OncSource::kDevicePolicy ||
+         network_properties->source == OncSource::kUserPolicy;
+}
+
+template <typename Lambda>
+void EraseIf(std::vector<NetworkStatePropertiesPtr>& networks,
+             Lambda predicate) {
+  networks.erase(std::remove_if(networks.begin(), networks.end(), predicate),
+                 networks.end());
+}
+
+// Returns if the ChromeOS device is in a managed environment or not.
+bool IsInManagedEnvironment(std::vector<NetworkStatePropertiesPtr> networks) {
+  LogNetworks(networks, "active");
+
+  // Filter out the unmanaged networks.
+  EraseIf(networks, [](const auto& network) {
+    return !IsNetworkManagedByPolicy(network);
+  });
+
+  // Filter out cellular networks, as managed cellular networks might
+  // be found even at the user's home.
+  EraseIf(networks, [](const auto& network) {
+    return network->type == NetworkType::kCellular;
+  });
+
+  LogNetworks(networks, "managed");
+
+  // Now if any networks remain we are in a managed environment.
+  return !networks.empty();
+}
+
+void BindMojomService(mojo::Remote<CrosNetworkConfig>& network_service) {
+  ash::network_config::BindToInProcessInstance(
+      network_service.BindNewPipeAndPassReceiver());
+}
+
+void CloseMojomConnection(
+    std::unique_ptr<mojo::Remote<CrosNetworkConfig>> network_service) {
+  // By simply dropping the remote we will close the mojom connection.
+  // See `CalculateIsInManagedEnvironmentAsync` where this method is used.
+}
+
+}  // namespace
+
+base::TimeDelta GetDeviceIdleTime() {
+  return base::TimeTicks::Now() -
+         ui::UserActivityDetector::Get()->last_activity_time();
+}
+
+UserSessionType GetCurrentUserSessionType() {
+  const auto& user_manager = CHECK_DEREF(user_manager::UserManager::Get());
+
+  if (!user_manager.IsUserLoggedIn())
+    return UserSessionType::kNoUser;
+
+  if (user_manager.IsLoggedInAsAnyKioskApp()) {
+    if (IsRunningAutoLaunchedKiosk(user_manager))
+      return UserSessionType::kAutoLaunchedKiosk;
+    else
+      return UserSessionType::kManuallyLaunchedKiosk;
+  }
+
+  if (user_manager.IsLoggedInAsPublicAccount())
+    return UserSessionType::kManagedGuestSession;
+
+  if (user_manager.IsLoggedInAsGuest())
+    return UserSessionType::kOther;
+
+  if (user_manager.GetActiveUser()->IsAffiliated())
+    return UserSessionType::kAffiliatedUser;
+
+  return UserSessionType::kOther;
+}
+
+bool UserSessionSupportsRemoteAccess(UserSessionType user_session) {
+  // Remote access is currently only supported while no user is logged in
+  // (and the device sits at the login screen).
+  switch (user_session) {
+    case UserSessionType::kNoUser:
+      return true;
+
+    case UserSessionType::kAutoLaunchedKiosk:
+    case UserSessionType::kManuallyLaunchedKiosk:
+    case UserSessionType::kAffiliatedUser:
+    case UserSessionType::kManagedGuestSession:
+    case UserSessionType::kOther:
+      return false;
+  }
+}
+bool UserSessionSupportsRemoteSupport(UserSessionType user_session) {
+  switch (user_session) {
+    case UserSessionType::kAutoLaunchedKiosk:
+    case UserSessionType::kManuallyLaunchedKiosk:
+    case UserSessionType::kAffiliatedUser:
+    case UserSessionType::kManagedGuestSession:
+      return true;
+
+    case UserSessionType::kNoUser:
+    case UserSessionType::kOther:
+      return false;
+  }
+}
+
+const char* UserSessionTypeToString(UserSessionType value) {
+#define CASE(type_)            \
+  case UserSessionType::type_: \
+    return #type_;
+
+  switch (value) {
+    CASE(kAutoLaunchedKiosk);
+    CASE(kManuallyLaunchedKiosk);
+    CASE(kNoUser);
+    CASE(kAffiliatedUser);
+    CASE(kOther);
+    CASE(kManagedGuestSession);
+  }
+
+#undef CASE
+}
+
+void CalculateIsInManagedEnvironmentAsync(
+    ManagedEnvironmentResultCallback result_callback) {
+  auto network_service = std::make_unique<mojo::Remote<CrosNetworkConfig>>();
+  BindMojomService(*network_service);
+
+  // Store a raw pointer as we're moving the unique pointer before using the
+  // service.
+  CrosNetworkConfig* network_service_ptr = network_service.get()->get();
+
+  network_service_ptr->GetNetworkStateList(
+      NetworkFilter::New(FilterType::kActive, NetworkType::kAll, kNoLimit),
+      base::BindOnce(IsInManagedEnvironment)
+          .Then(std::move(result_callback))
+          // Keep the mojom connection alive until the callback is invoked.
+          .Then(base::BindOnce(CloseMojomConnection,
+                               std::move(network_service))));
+}
+
+}  // namespace policy
diff --git a/chrome/browser/ash/policy/remote_commands/crd_remote_command_utils.h b/chrome/browser/ash/policy/remote_commands/crd_remote_command_utils.h
new file mode 100644
index 0000000..3cba722
--- /dev/null
+++ b/chrome/browser/ash/policy/remote_commands/crd_remote_command_utils.h
@@ -0,0 +1,56 @@
+// 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.
+//
+// This file contains some utilities that are used by both CRD related
+// remote commands (`FETCH_CRD_AVAILABILITY_INFO` and
+// `DEVICE_START_CRD_SESSION`).
+
+#ifndef CHROME_BROWSER_ASH_POLICY_REMOTE_COMMANDS_CRD_REMOTE_COMMAND_UTILS_H_
+#define CHROME_BROWSER_ASH_POLICY_REMOTE_COMMANDS_CRD_REMOTE_COMMAND_UTILS_H_
+
+#include "base/functional/callback_forward.h"
+#include "base/time/time.h"
+
+namespace policy {
+
+enum class UserSessionType {
+  kAutoLaunchedKiosk,
+  kManuallyLaunchedKiosk,
+  kNoUser,
+  kAffiliatedUser,
+  kManagedGuestSession,
+  kOther,
+};
+
+// Returns the time since the last user activity on this device.
+base::TimeDelta GetDeviceIdleTime();
+
+// Returns the type of the currently active user session.
+UserSessionType GetCurrentUserSessionType();
+
+// Returns if a remote admin is allowed to start a 'CRD remote support' session
+// when an user session of the given type is active.
+bool UserSessionSupportsRemoteSupport(UserSessionType user_session);
+// Returns if a remote admin is allowed to start a 'CRD remote access' session
+// when an user session of the given type is active.
+bool UserSessionSupportsRemoteAccess(UserSessionType user_session);
+
+const char* UserSessionTypeToString(UserSessionType value);
+
+// Returns asynchronously if the ChromeOS device is in a managed environment.
+// We consider the device's environment to be managed if there is a
+//      * active (connected) network
+//      * with a policy ONC source set (meaning the network is managed)
+//      * which is not cellular
+//
+// The reasoning is that these conditions will only be met if the device is in
+// an office building or a store, and these conditions will not be met if the
+// device is in a private setting like an user's home.
+using ManagedEnvironmentResultCallback = base::OnceCallback<void(bool)>;
+void CalculateIsInManagedEnvironmentAsync(
+    ManagedEnvironmentResultCallback result_callback);
+
+}  // namespace policy
+
+#endif  // CHROME_BROWSER_ASH_POLICY_REMOTE_COMMANDS_CRD_REMOTE_COMMAND_UTILS_H_
diff --git a/chrome/browser/ash/policy/remote_commands/device_command_run_routine_job.cc b/chrome/browser/ash/policy/remote_commands/device_command_run_routine_job.cc
index 7820110..2517b93f 100644
--- a/chrome/browser/ash/policy/remote_commands/device_command_run_routine_job.cc
+++ b/chrome/browser/ash/policy/remote_commands/device_command_run_routine_job.cc
@@ -172,9 +172,33 @@
     }
     case ash::cros_healthd::mojom::DiagnosticRoutineEnum::kSmartctlCheck: {
       diagnostics_service->RunSmartctlCheckRoutine(
+          ash::cros_healthd::mojom::NullableUint32Ptr(),
           std::move(response_callback));
       break;
     }
+    case ash::cros_healthd::mojom::DiagnosticRoutineEnum::
+        kSmartctlCheckWithPercentageUsed: {
+      constexpr char kPercentageUsedThresholdFieldName[] =
+          "percentageUsedThreshold";
+      absl::optional<int> percentage_used_threshold =
+          params_dict_.FindIntKey(kPercentageUsedThresholdFieldName);
+      ash::cros_healthd::mojom::NullableUint32Ptr input_threshold;
+      // The smartctl check routine expects one optional integer >= 0.
+      if (percentage_used_threshold.has_value()) {
+        // If the optional integer parameter is specified, it must be [0, 255].
+        int value = percentage_used_threshold.value();
+        if (value < 0 || value > 255) {
+          SYSLOG(ERROR) << "Invalid parameters for smartctl check routine.";
+          base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+              FROM_HERE, std::move(invalid_parameters_callback));
+          break;
+        }
+        input_threshold = ash::cros_healthd::mojom::NullableUint32::New(value);
+      }
+      diagnostics_service->RunSmartctlCheckRoutine(
+          std::move(input_threshold), std::move(response_callback));
+      break;
+    }
     case ash::cros_healthd::mojom::DiagnosticRoutineEnum::kAcPower: {
       constexpr char kExpectedStatusFieldName[] = "expectedStatus";
       // Note that expectedPowerType is an optional parameter.
diff --git a/chrome/browser/ash/policy/remote_commands/device_command_run_routine_job_unittest.cc b/chrome/browser/ash/policy/remote_commands/device_command_run_routine_job_unittest.cc
index db28720..e1e7e24f 100644
--- a/chrome/browser/ash/policy/remote_commands/device_command_run_routine_job_unittest.cc
+++ b/chrome/browser/ash/policy/remote_commands/device_command_run_routine_job_unittest.cc
@@ -69,6 +69,10 @@
 // routine.
 constexpr char kPrivacyScreenTargetStateFieldName[] = "targetState";
 
+// String constants identifying the parameter fields for the smartctl check
+// routine.
+constexpr char kPercentageUsedThresholdFieldName[] = "percentageUsedThreshold";
+
 constexpr uint32_t kId = 11;
 constexpr auto kStatus =
     ash::cros_healthd::mojom::DiagnosticRoutineStatusEnum::kRunning;
@@ -77,6 +81,7 @@
 
 constexpr int kPositiveInt = 8789;
 constexpr int kNegativeInt = -231;
+constexpr int kValidSmartctlCheckPercentageUsedValue = 100;
 constexpr auto kValidAcPowerStatusEnum =
     ash::cros_healthd::mojom::AcPowerStatusEnum::kConnected;
 constexpr char kValidExpectedAcPowerType[] = "power_type";
@@ -352,8 +357,8 @@
              })));
 }
 
-// Note that the smartctl check routine has no parameters, so we only need to
-// test that it can be run successfully.
+// Note that the smartctl check routine (without percentage_used) has no
+// parameters, so we only need to test that it can be run successfully.
 TEST_F(DeviceCommandRunRoutineJobTest, RunSmartctlCheckRoutineSuccess) {
   auto run_routine_response =
       ash::cros_healthd::mojom::RunRoutineResponse::New(kId, kStatus);
@@ -371,6 +376,68 @@
              })));
 }
 
+// Test that the smartctl check routine (with percentage_used) handles the
+// optional percentage_used_threshold parameter being persent.
+TEST_F(DeviceCommandRunRoutineJobTest,
+       RunSmartctlCheckRoutineWithPercentageUsedSuccess) {
+  auto run_routine_response =
+      ash::cros_healthd::mojom::RunRoutineResponse::New(kId, kStatus);
+  ash::cros_healthd::FakeCrosHealthd::Get()->SetRunRoutineResponseForTesting(
+      run_routine_response);
+  base::Value params_dict(base::Value::Type::DICTIONARY);
+  params_dict.SetIntKey(kPercentageUsedThresholdFieldName,
+                        kValidSmartctlCheckPercentageUsedValue);
+  EXPECT_TRUE(RunJob(ash::cros_healthd::mojom::DiagnosticRoutineEnum::
+                         kSmartctlCheckWithPercentageUsed,
+                     std::move(params_dict),
+                     base::BindLambdaForTesting([](RemoteCommandJob* job) {
+                       EXPECT_EQ(job->status(), RemoteCommandJob::SUCCEEDED);
+                       std::unique_ptr<std::string> payload =
+                           job->GetResultPayload();
+                       EXPECT_TRUE(payload);
+                       EXPECT_EQ(CreateSuccessPayload(kId, kStatus), *payload);
+                     })));
+}
+
+// Test that the smartctl check routine (with percentage_used) handles the
+// optional percentage_used_threshold parameter being missing.
+TEST_F(DeviceCommandRunRoutineJobTest,
+       RunSmartctlCheckRoutineWithPercentageUsedMissingParam) {
+  auto run_routine_response =
+      ash::cros_healthd::mojom::RunRoutineResponse::New(kId, kStatus);
+  ash::cros_healthd::FakeCrosHealthd::Get()->SetRunRoutineResponseForTesting(
+      run_routine_response);
+  base::Value params_dict(base::Value::Type::DICTIONARY);
+  EXPECT_TRUE(RunJob(ash::cros_healthd::mojom::DiagnosticRoutineEnum::
+                         kSmartctlCheckWithPercentageUsed,
+                     std::move(params_dict),
+                     base::BindLambdaForTesting([](RemoteCommandJob* job) {
+                       EXPECT_EQ(job->status(), RemoteCommandJob::SUCCEEDED);
+                       std::unique_ptr<std::string> payload =
+                           job->GetResultPayload();
+                       EXPECT_TRUE(payload);
+                       EXPECT_EQ(CreateSuccessPayload(kId, kStatus), *payload);
+                     })));
+}
+
+// Test that a negative percentage_used_threshold parameter causes the smartctl
+// check routine (with percentage_used) to fail.
+TEST_F(DeviceCommandRunRoutineJobTest,
+       RunSmartctlCheckRoutineWithPercentageUsedInvalidParam) {
+  base::Value params_dict(base::Value::Type::DICTIONARY);
+  params_dict.SetIntKey(kPercentageUsedThresholdFieldName, kNegativeInt);
+  EXPECT_TRUE(
+      RunJob(ash::cros_healthd::mojom::DiagnosticRoutineEnum::
+                 kSmartctlCheckWithPercentageUsed,
+             std::move(params_dict),
+             base::BindLambdaForTesting([](RemoteCommandJob* job) {
+               EXPECT_EQ(job->status(), RemoteCommandJob::FAILED);
+               std::unique_ptr<std::string> payload = job->GetResultPayload();
+               EXPECT_TRUE(payload);
+               EXPECT_EQ(CreateInvalidParametersFailurePayload(), *payload);
+             })));
+}
+
 // Test that the AC power routine succeeds with all parameters specified.
 TEST_F(DeviceCommandRunRoutineJobTest, RunAcPowerRoutineSuccess) {
   auto run_routine_response =
diff --git a/chrome/browser/ash/policy/remote_commands/device_command_start_crd_session_job.cc b/chrome/browser/ash/policy/remote_commands/device_command_start_crd_session_job.cc
index 0ff2ead..18a979a3 100644
--- a/chrome/browser/ash/policy/remote_commands/device_command_start_crd_session_job.cc
+++ b/chrome/browser/ash/policy/remote_commands/device_command_start_crd_session_job.cc
@@ -4,40 +4,36 @@
 
 #include "chrome/browser/ash/policy/remote_commands/device_command_start_crd_session_job.h"
 
+#include <iomanip>
 #include <memory>
 #include <utility>
 
 #include "base/check_deref.h"
 #include "base/feature_list.h"
 #include "base/functional/bind.h"
+#include "base/functional/callback.h"
 #include "base/json/json_reader.h"
 #include "base/memory/weak_ptr.h"
 #include "base/metrics/histogram_functions.h"
+#include "base/notreached.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/values.h"
 #include "build/build_config.h"
-#include "chrome/browser/ash/app_mode/arc/arc_kiosk_app_manager.h"
-#include "chrome/browser/ash/app_mode/kiosk_app_manager.h"
-#include "chrome/browser/ash/app_mode/web_app/web_kiosk_app_manager.h"
 #include "chrome/browser/ash/policy/remote_commands/crd_logging.h"
+#include "chrome/browser/ash/policy/remote_commands/crd_remote_command_utils.h"
 #include "chrome/browser/device_identity/device_oauth2_token_service.h"
 #include "chrome/browser/device_identity/device_oauth2_token_service_factory.h"
-#include "chromeos/services/network_config/in_process_instance.h"
 #include "components/policy/proto/device_management_backend.pb.h"
-#include "components/user_manager/user_manager.h"
 #include "extensions/common/value_builder.h"
 #include "google_apis/gaia/gaia_constants.h"
 #include "google_apis/gaia/oauth2_access_token_manager.h"
 #include "remoting/host/chromeos/features.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
-#include "ui/base/user_activity/user_activity_detector.h"
 
 namespace policy {
 
 namespace {
 
-namespace mojom = chromeos::network_config::mojom;
-
 using ResultCode = DeviceCommandStartCrdSessionJob::ResultCode;
 using extensions::DictionaryBuilder;
 
@@ -80,34 +76,7 @@
 // FAILURE_NOT_IDLE result code.
 const char kResultLastActivityFieldName[] = "lastActivitySec";
 
-// Helper method that DVLOGs all the given networks.
-void LogNetworks(
-    std::vector<mojom::NetworkStatePropertiesPtr>::const_iterator begin,
-    std::vector<mojom::NetworkStatePropertiesPtr>::const_iterator end,
-    const char* type) {
-  CRD_DVLOG(3) << "Found " << (end - begin) << " " << type << " networks:";
-  for (auto it = begin; it < end; it++) {
-    const mojom::NetworkStateProperties& network = *it->get();
-    CRD_DVLOG(3) << "   --> " << network.name << " (" << network.guid << "): "
-                 << " ONC source: " << network.source
-                 << ", Type: " << network.type;
-  }
-}
-
-ash::KioskAppManagerBase* GetKioskAppManagerIfKioskAppIsRunning(
-    const user_manager::UserManager* user_manager) {
-  if (user_manager->IsLoggedInAsKioskApp())
-    return ash::KioskAppManager::Get();
-  if (user_manager->IsLoggedInAsArcKioskApp())
-    return ash::ArcKioskAppManager::Get();
-  if (user_manager->IsLoggedInAsWebKioskApp())
-    return ash::WebKioskAppManager::Get();
-
-  return nullptr;
-}
-
-void SendResultCodeToUma(
-    DeviceCommandStartCrdSessionJob::ResultCode result_code) {
+void SendResultCodeToUma(ResultCode result_code) {
   base::UmaHistogramEnumeration("Enterprise.DeviceRemoteCommand.Crd.Result",
                                 result_code);
 }
@@ -118,56 +87,16 @@
       "Enterprise.DeviceRemoteCommand.Crd.SessionType", session_type);
 }
 
-bool IsNetworkManagedByPolicy(
-    const mojom::NetworkStatePropertiesPtr& network_properties) {
-  return network_properties->source == mojom::OncSource::kDevicePolicy ||
-         network_properties->source == mojom::OncSource::kUserPolicy;
-}
-
-// Returns if the ChromeOS device is in a managed environment or not.
-// We consider our environment to be managed if there is a
-//      * active (connected) network
-//      * with an ONC source set (meaning the network is managed)
-//      * which is not cellular
-//
-// The reasoning is that these conditions will only be met if the device is in
-// an office building or store, and these conditions will not be met if the
-// device is in a private setting like an user's home.
-bool IsInManagedEnvironment(
-    std::vector<mojom::NetworkStatePropertiesPtr> active_networks) {
-  const auto begin = active_networks.begin();
-  auto end = active_networks.end();
-
-  LogNetworks(begin, end, "active");
-
-  // Filter out the unmanaged networks.
-  end = std::remove_if(begin, end, [](const auto& network) {
-    return !IsNetworkManagedByPolicy(network);
-  });
-
-  // Filter out cellular networks, as managed cellular networks might be found
-  // even at the user's home.
-  end = std::remove_if(begin, end, [](const auto& network) {
-    return network->type == mojom::NetworkType::kCellular;
-  });
-
-  LogNetworks(begin, end, "managed");
-
-  // Now if any networks remain we are in a managed environment.
-  bool is_empty = (begin == end);
-  return !is_empty;
-}
-
 std::string CreateSuccessPayload(const std::string& access_code) {
   return DictionaryBuilder()
-      .Set(kResultCodeFieldName, ResultCode::SUCCESS)
+      .Set(kResultCodeFieldName, static_cast<int>(ResultCode::SUCCESS))
       .Set(kResultAccessCodeFieldName, access_code)
       .ToJSON();
 }
 
 std::string CreateNonIdlePayload(const base::TimeDelta& time_delta) {
   return DictionaryBuilder()
-      .Set(kResultCodeFieldName, ResultCode::FAILURE_NOT_IDLE)
+      .Set(kResultCodeFieldName, static_cast<int>(ResultCode::FAILURE_NOT_IDLE))
       .Set(kResultLastActivityFieldName,
            static_cast<int>(time_delta.InSeconds()))
       .ToJSON();
@@ -179,7 +108,7 @@
   DCHECK(result_code != ResultCode::FAILURE_NOT_IDLE);
 
   DictionaryBuilder builder;
-  builder.Set(kResultCodeFieldName, result_code);
+  builder.Set(kResultCodeFieldName, static_cast<int>(result_code));
   if (!error_message.empty()) {
     builder.Set(kResultMessageFieldName, error_message);
   }
@@ -193,71 +122,6 @@
 }  // namespace
 
 ////////////////////////////////////////////////////////////////////////////////
-// ManagedNetworkChecker
-////////////////////////////////////////////////////////////////////////////////
-
-// Helper class that asynchronously fetches the list of active (connected)
-// networks, and uses that information to decide if the admin is allowed to
-// start a curtained session.
-//
-// See `IsInManagedEnvironment()` for a more detailed description on the
-// algorithm used.
-class DeviceCommandStartCrdSessionJob::ManagedNetworkChecker {
- public:
-  using SuccessCallback = base::OnceClosure;
-
-  ManagedNetworkChecker(SuccessCallback success_callback,
-                        ErrorCallback error_callback)
-      : success_callback_(std::move(success_callback)),
-        error_callback_(std::move(error_callback)) {}
-  ManagedNetworkChecker(const ManagedNetworkChecker&) = delete;
-  ManagedNetworkChecker& operator=(const ManagedNetworkChecker&) = delete;
-  ~ManagedNetworkChecker() = default;
-
-  void Start() {
-    BindMojomService();
-    GetActiveNetworksAsync(
-        base::BindOnce(&ManagedNetworkChecker::CheckIsInManagedEnvironment,
-                       weak_factory_.GetWeakPtr()));
-  }
-
- private:
-  void BindMojomService() {
-    ash::network_config::BindToInProcessInstance(
-        network_service_.BindNewPipeAndPassReceiver());
-  }
-
-  void GetActiveNetworksAsync(
-      mojom::CrosNetworkConfig::GetNetworkStateListCallback on_success) {
-    CRD_DVLOG(1) << "Fetching active networks";
-    network_service_->GetNetworkStateList(
-        mojom::NetworkFilter::New(mojom::FilterType::kActive,  //
-                                  mojom::NetworkType::kAll,    //
-                                  mojom::kNoLimit),
-        std::move(on_success));
-  }
-
-  void CheckIsInManagedEnvironment(
-      std::vector<mojom::NetworkStatePropertiesPtr> networks) {
-    CRD_DVLOG(1) << "Received active networks";
-
-    if (IsInManagedEnvironment(std::move(networks))) {
-      std::move(success_callback_).Run();
-    } else {
-      std::move(error_callback_)
-          .Run(ResultCode::FAILURE_UNMANAGED_ENVIRONMENT, "");
-    }
-  }
-
-  mojo::Remote<mojom::CrosNetworkConfig> network_service_;
-
-  SuccessCallback success_callback_;
-  ErrorCallback error_callback_;
-
-  base::WeakPtrFactory<ManagedNetworkChecker> weak_factory_{this};
-};
-
-////////////////////////////////////////////////////////////////////////////////
 // OAuthTokenFetcher
 ////////////////////////////////////////////////////////////////////////////////
 
@@ -308,8 +172,7 @@
     CRD_DVLOG(1) << "Failed to get OAuth access token: " << error.ToString();
     oauth_request_.reset();
     std::move(error_callback_)
-        .Run(DeviceCommandStartCrdSessionJob::FAILURE_NO_OAUTH_TOKEN,
-             error.ToString());
+        .Run(ResultCode::FAILURE_NO_OAUTH_TOKEN, error.ToString());
   }
 
   DeviceOAuth2TokenService& oauth_service_;
@@ -335,7 +198,7 @@
 
 enterprise_management::RemoteCommand_Type
 DeviceCommandStartCrdSessionJob::GetType() const {
-  return enterprise_management::RemoteCommand_Type_DEVICE_START_CRD_SESSION;
+  return enterprise_management::RemoteCommand::DEVICE_START_CRD_SESSION;
 }
 
 void DeviceCommandStartCrdSessionJob::SetOAuthTokenForTest(
@@ -346,19 +209,22 @@
 bool DeviceCommandStartCrdSessionJob::ParseCommandPayload(
     const std::string& command_payload) {
   absl::optional<base::Value> root(base::JSONReader::Read(command_payload));
-  if (!root)
+  if (!root || !root->is_dict()) {
+    LOG(WARNING) << "Rejecting remote command with invalid payload: "
+                 << std::quoted(command_payload);
     return false;
-  if (!root->is_dict())
-    return false;
+  }
+
+  const base::Value::Dict& root_dict = root->GetDict();
 
   idleness_cutoff_ =
-      base::Seconds(root->FindIntKey(kIdlenessCutoffFieldName).value_or(0));
+      base::Seconds(root_dict.FindInt(kIdlenessCutoffFieldName).value_or(0));
 
   acked_user_presence_ =
-      root->FindBoolKey(kAckedUserPresenceFieldName).value_or(false);
+      root_dict.FindBool(kAckedUserPresenceFieldName).value_or(false);
 
   curtain_local_user_session_ =
-      root->FindBoolKey(kCurtainLocalUserSession).value_or(false);
+      root_dict.FindBool(kCurtainLocalUserSession).value_or(false);
 
   if (base::FeatureList::IsEnabled(
           remoting::features::kForceCrdAdminRemoteAccess)) {
@@ -422,8 +288,6 @@
 
 void DeviceCommandStartCrdSessionJob::CheckManagedNetworkASync(
     base::OnceClosure on_success) {
-  DCHECK_EQ(managed_network_checker_, nullptr);
-
   if (!curtain_local_user_session_) {
     // No need to check for managed networks if we are not going to curtain
     // off the local session.
@@ -431,11 +295,17 @@
     return;
   }
 
-  managed_network_checker_ = std::make_unique<ManagedNetworkChecker>(
-      /*on_success=*/std::move(on_success),
-      /*on_error=*/GetErrorCallback());
-
-  managed_network_checker_->Start();
+  CalculateIsInManagedEnvironmentAsync(base::BindOnce(
+      [](base::OnceClosure on_success, ErrorCallback on_error,
+         bool is_in_managed_environment) {
+        if (is_in_managed_environment) {
+          std::move(on_success).Run();
+        } else {
+          std::move(on_error).Run(ResultCode::FAILURE_UNMANAGED_ENVIRONMENT,
+                                  /*error_messages=*/"");
+        }
+      },
+      std::move(on_success), GetErrorCallback()));
 }
 
 void DeviceCommandStartCrdSessionJob::FetchOAuthTokenASync(
@@ -483,7 +353,8 @@
     const std::string& message) {
   DCHECK(result_code != ResultCode::SUCCESS);
   CRD_LOG(INFO) << "Not starting CRD session because of error (code "
-                << result_code << ", message '" << message << "')";
+                << static_cast<int>(result_code) << ", message '" << message
+                << "')";
   if (!failed_callback_)
     return;  // Task was terminated.
 
@@ -500,60 +371,24 @@
 
   SendResultCodeToUma(ResultCode::FAILURE_NOT_IDLE);
   base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
-      FROM_HERE,
-      base::BindOnce(std::move(failed_callback_),
-                     CreateNonIdlePayload(GetDeviceIdlenessPeriod())));
+      FROM_HERE, base::BindOnce(std::move(failed_callback_),
+                                CreateNonIdlePayload(GetDeviceIdleTime())));
 }
 
 bool DeviceCommandStartCrdSessionJob::UserTypeSupportsCrd() const {
-  const UserSessionType current_user_type = GetUserSessionType();
-
-  CRD_DVLOG(2) << "User is of type " << UserTypeToString(current_user_type);
+  CRD_DVLOG(2) << "User is of type "
+               << UserSessionTypeToString(GetCurrentUserSessionType());
 
   if (curtain_local_user_session_) {
-    return current_user_type == UserSessionType::kNoUser;
+    return UserSessionSupportsRemoteAccess(GetCurrentUserSessionType());
+  } else {
+    return UserSessionSupportsRemoteSupport(GetCurrentUserSessionType());
   }
-
-  switch (current_user_type) {
-    case UserSessionType::kAffiliatedUser:
-    case UserSessionType::kAutoLaunchedKiosk:
-    case UserSessionType::kManagedGuestSession:
-    case UserSessionType::kManuallyLaunchedKiosk:
-      return true;
-    case UserSessionType::kNoUser:
-    case UserSessionType::kOther:
-      return false;
-  }
-  NOTREACHED();
-  return false;
-}
-
-DeviceCommandStartCrdSessionJob::UserSessionType
-DeviceCommandStartCrdSessionJob::GetUserSessionType() const {
-  const auto* user_manager = user_manager::UserManager::Get();
-
-  if (!user_manager->IsUserLoggedIn())
-    return UserSessionType::kNoUser;
-
-  if (user_manager->IsLoggedInAsAnyKioskApp()) {
-    if (IsRunningAutoLaunchedKiosk())
-      return UserSessionType::kAutoLaunchedKiosk;
-    else
-      return UserSessionType::kManuallyLaunchedKiosk;
-  }
-
-  if (user_manager->IsLoggedInAsPublicAccount())
-    return UserSessionType::kManagedGuestSession;
-
-  if (user_manager->GetActiveUser()->IsAffiliated())
-    return UserSessionType::kAffiliatedUser;
-
-  return UserSessionType::kOther;
 }
 
 DeviceCommandStartCrdSessionJob::UmaSessionType
 DeviceCommandStartCrdSessionJob::GetUmaSessionType() const {
-  switch (GetUserSessionType()) {
+  switch (GetCurrentUserSessionType()) {
     case UserSessionType::kAutoLaunchedKiosk:
       return UmaSessionType::kAutoLaunchedKiosk;
     case UserSessionType::kAffiliatedUser:
@@ -571,24 +406,8 @@
   }
 }
 
-bool DeviceCommandStartCrdSessionJob::IsRunningAutoLaunchedKiosk() const {
-  const auto* user_manager = user_manager::UserManager::Get();
-  const auto* kiosk_app_manager =
-      GetKioskAppManagerIfKioskAppIsRunning(user_manager);
-
-  if (!kiosk_app_manager)
-    return false;
-  return kiosk_app_manager->current_app_was_auto_launched_with_zero_delay();
-}
-
 bool DeviceCommandStartCrdSessionJob::IsDeviceIdle() const {
-  return GetDeviceIdlenessPeriod() >= idleness_cutoff_;
-}
-
-base::TimeDelta DeviceCommandStartCrdSessionJob::GetDeviceIdlenessPeriod()
-    const {
-  return base::TimeTicks::Now() -
-         ui::UserActivityDetector::Get()->last_activity_time();
+  return GetDeviceIdleTime() >= idleness_cutoff_;
 }
 
 std::string DeviceCommandStartCrdSessionJob::GetRobotAccountUserName() const {
@@ -600,25 +419,24 @@
 }
 
 bool DeviceCommandStartCrdSessionJob::ShouldShowConfirmationDialog() const {
-  switch (GetUserSessionType()) {
-    case UserSessionType::kAffiliatedUser:
-    case UserSessionType::kManagedGuestSession:
-      return true;
+  switch (GetCurrentUserSessionType()) {
     case UserSessionType::kAutoLaunchedKiosk:
     case UserSessionType::kManuallyLaunchedKiosk:
     case UserSessionType::kNoUser:
-    case UserSessionType::kOther:
       return false;
+
+    case UserSessionType::kAffiliatedUser:
+    case UserSessionType::kManagedGuestSession:
+    case UserSessionType::kOther:
+      return true;
   }
-  NOTREACHED();
-  return false;
 }
 
 bool DeviceCommandStartCrdSessionJob::ShouldTerminateUponInput() const {
   if (curtain_local_user_session_)
     return false;
 
-  switch (GetUserSessionType()) {
+  switch (GetCurrentUserSessionType()) {
     case UserSessionType::kAffiliatedUser:
     case UserSessionType::kManagedGuestSession:
       // We never terminate upon input for the user-session scenarios, because:
@@ -629,18 +447,15 @@
       //      as pressing the button to accept the connection request counts as
       //      user input.
       return false;
+
     case UserSessionType::kAutoLaunchedKiosk:
     case UserSessionType::kManuallyLaunchedKiosk:
       return !acked_user_presence_;
+
     case UserSessionType::kNoUser:
     case UserSessionType::kOther:
-      // This method will only be called for user types for which we support
-      // CRD sessions.
-      NOTREACHED();
-      return false;
+      return true;
   }
-  NOTREACHED();
-  return false;
 }
 
 DeviceCommandStartCrdSessionJob::ErrorCallback
@@ -656,24 +471,4 @@
   delegate_->TerminateSession(base::OnceClosure());
 }
 
-const char* DeviceCommandStartCrdSessionJob::UserTypeToString(
-    UserSessionType value) const {
-  switch (value) {
-    case UserSessionType::kAutoLaunchedKiosk:
-      return "kAutoLaunchedKiosk";
-    case UserSessionType::kManuallyLaunchedKiosk:
-      return "kManuallyLaunchedKiosk";
-    case UserSessionType::kNoUser:
-      return "kNoUser";
-    case UserSessionType::kAffiliatedUser:
-      return "kAffiliatedUser";
-    case UserSessionType::kManagedGuestSession:
-      return "kManagedGuestSession";
-    case UserSessionType::kOther:
-      return "kOther";
-  }
-  NOTREACHED();
-  return "<invalid user type>";
-}
-
 }  // namespace policy
diff --git a/chrome/browser/ash/policy/remote_commands/device_command_start_crd_session_job.h b/chrome/browser/ash/policy/remote_commands/device_command_start_crd_session_job.h
index 3bc8ad5f..8a3e05e 100644
--- a/chrome/browser/ash/policy/remote_commands/device_command_start_crd_session_job.h
+++ b/chrome/browser/ash/policy/remote_commands/device_command_start_crd_session_job.h
@@ -24,7 +24,7 @@
   // This enum can't be renumbered because it's logged to UMA and because its
   // values must match the values in the server side
   // `DeviceCommandUtil::ClientResultCode` enum.
-  enum ResultCode {
+  enum class ResultCode {
     // Successfully obtained access code.
     SUCCESS = 0,
 
@@ -116,20 +116,8 @@
   void TerminateImpl() override;
 
  private:
-  class ManagedNetworkChecker;
   class OAuthTokenFetcher;
 
-  enum class UserSessionType {
-    kAutoLaunchedKiosk,
-    kManuallyLaunchedKiosk,
-    kNoUser,
-    kAffiliatedUser,
-    kManagedGuestSession,
-    kOther,
-  };
-
-  const char* UserTypeToString(UserSessionType value) const;
-
   void CheckManagedNetworkASync(base::OnceClosure on_success);
   void FetchOAuthTokenASync(OAuthTokenCallback on_success);
   void StartCrdHostAndGetCode(const std::string& token);
@@ -139,11 +127,8 @@
   void FinishWithNotIdleError();
 
   bool UserTypeSupportsCrd() const;
-  UserSessionType GetUserSessionType() const;
   UmaSessionType GetUmaSessionType() const;
-  bool IsRunningAutoLaunchedKiosk() const;
   bool IsDeviceIdle() const;
-  base::TimeDelta GetDeviceIdlenessPeriod() const;
 
   std::string GetRobotAccountUserName() const;
   bool ShouldShowConfirmationDialog() const;
@@ -152,7 +137,6 @@
   ErrorCallback GetErrorCallback();
 
   std::unique_ptr<OAuthTokenFetcher> oauth_token_fetcher_;
-  std::unique_ptr<ManagedNetworkChecker> managed_network_checker_;
 
   // The callback that will be called when the access code was successfully
   // obtained.
diff --git a/chrome/browser/ash/policy/remote_commands/device_command_start_crd_session_unittest.cc b/chrome/browser/ash/policy/remote_commands/device_command_start_crd_session_unittest.cc
index 37d8c13..4eceb5a 100644
--- a/chrome/browser/ash/policy/remote_commands/device_command_start_crd_session_unittest.cc
+++ b/chrome/browser/ash/policy/remote_commands/device_command_start_crd_session_unittest.cc
@@ -151,8 +151,7 @@
     std::move(success_callback).Run(kTestAccessCode);
   } else {
     std::move(error_callback)
-        .Run(DeviceCommandStartCrdSessionJob::FAILURE_CRD_HOST_ERROR,
-             std::string());
+        .Run(ResultCode::FAILURE_CRD_HOST_ERROR, std::string());
   }
 }
 
@@ -484,7 +483,7 @@
 std::string DeviceCommandStartCrdSessionJobTest::CreateSuccessPayload(
     const std::string& access_code) {
   return DictionaryBuilder()
-      .Set(kResultCodeFieldName, DeviceCommandStartCrdSessionJob::SUCCESS)
+      .Set(kResultCodeFieldName, static_cast<int>(ResultCode::SUCCESS))
       .Set(kResultAccessCodeFieldName, access_code)
       .ToJSON();
 }
@@ -493,7 +492,7 @@
     ResultCode result_code,
     const std::string& error_message = "") {
   DictionaryBuilder builder;
-  builder.Set(kResultCodeFieldName, result_code);
+  builder.Set(kResultCodeFieldName, static_cast<int>(result_code));
   if (!error_message.empty())
     builder.Set(kResultMessageFieldName, error_message);
   return builder.ToJSON();
@@ -502,8 +501,7 @@
 std::string DeviceCommandStartCrdSessionJobTest::CreateNotIdlePayload(
     int idle_time_in_sec) {
   return DictionaryBuilder()
-      .Set(kResultCodeFieldName,
-           DeviceCommandStartCrdSessionJob::FAILURE_NOT_IDLE)
+      .Set(kResultCodeFieldName, static_cast<int>(ResultCode::FAILURE_NOT_IDLE))
       .Set(kResultLastActivityFieldName, idle_time_in_sec)
       .ToJSON();
 }
@@ -531,14 +529,14 @@
   LogInAsGuestUser();
 
   EXPECT_ERROR(RunJobAndWaitForResult(),
-               DeviceCommandStartCrdSessionJob::FAILURE_UNSUPPORTED_USER_TYPE);
+               ResultCode::FAILURE_UNSUPPORTED_USER_TYPE);
 }
 
 TEST_F(DeviceCommandStartCrdSessionJobTest, ShouldFailForRegularUser) {
   LogInAsRegularUser();
 
   EXPECT_ERROR(RunJobAndWaitForResult(),
-               DeviceCommandStartCrdSessionJob::FAILURE_UNSUPPORTED_USER_TYPE);
+               ResultCode::FAILURE_UNSUPPORTED_USER_TYPE);
 }
 
 TEST_F(DeviceCommandStartCrdSessionJobTest,
@@ -640,7 +638,7 @@
 
   EXPECT_ERROR(RunJobAndWaitForResult(
                    Payload().Set("idlenessCutoffSec", idleness_cutoff_in_sec)),
-               DeviceCommandStartCrdSessionJob::FAILURE_UNSUPPORTED_USER_TYPE);
+               ResultCode::FAILURE_UNSUPPORTED_USER_TYPE);
 }
 
 TEST_F(DeviceCommandStartCrdSessionJobTest,
@@ -648,8 +646,7 @@
   LogInAsAutoLaunchedKioskAppUser();
   ClearOAuthToken();
 
-  EXPECT_ERROR(RunJobAndWaitForResult(),
-               DeviceCommandStartCrdSessionJob::FAILURE_NO_OAUTH_TOKEN,
+  EXPECT_ERROR(RunJobAndWaitForResult(), ResultCode::FAILURE_NO_OAUTH_TOKEN,
                kTestNoOAuthTokenReason);
 }
 
@@ -658,8 +655,7 @@
 
   crd_host_delegate().MakeAccessCodeFetchFail();
 
-  EXPECT_ERROR(RunJobAndWaitForResult(),
-               DeviceCommandStartCrdSessionJob::FAILURE_CRD_HOST_ERROR);
+  EXPECT_ERROR(RunJobAndWaitForResult(), ResultCode::FAILURE_CRD_HOST_ERROR);
 }
 
 TEST_F(DeviceCommandStartCrdSessionJobTest, ShouldPassOAuthTokenToDelegate) {
@@ -743,7 +739,7 @@
 
 TEST_F(DeviceCommandStartCrdSessionJobTest, ShouldFailIfNoUserIsLoggedIn) {
   EXPECT_ERROR(RunJobAndWaitForResult(),
-               DeviceCommandStartCrdSessionJob::FAILURE_UNSUPPORTED_USER_TYPE);
+               ResultCode::FAILURE_UNSUPPORTED_USER_TYPE);
 }
 
 TEST_F(DeviceCommandStartCrdSessionJobTest, ShouldSucceedForManagedGuestUser) {
@@ -975,7 +971,7 @@
 
   EXPECT_ERROR(
       RunJobAndWaitForResult(Payload().Set("curtainLocalUserSession", true)),
-      DeviceCommandStartCrdSessionJob::FAILURE_UNSUPPORTED_USER_TYPE);
+      ResultCode::FAILURE_UNSUPPORTED_USER_TYPE);
 }
 
 TEST_F(DeviceCommandStartCrdSessionJobCurtainSessionTest,
@@ -986,7 +982,7 @@
 
   EXPECT_ERROR(
       RunJobAndWaitForResult(Payload().Set("curtainLocalUserSession", true)),
-      DeviceCommandStartCrdSessionJob::FAILURE_UNSUPPORTED_USER_TYPE);
+      ResultCode::FAILURE_UNSUPPORTED_USER_TYPE);
 }
 
 TEST_F(DeviceCommandStartCrdSessionJobCurtainSessionTest,
@@ -997,7 +993,7 @@
 
   EXPECT_ERROR(
       RunJobAndWaitForResult(Payload().Set("curtainLocalUserSession", true)),
-      DeviceCommandStartCrdSessionJob::FAILURE_UNSUPPORTED_USER_TYPE);
+      ResultCode::FAILURE_UNSUPPORTED_USER_TYPE);
 }
 
 TEST_F(DeviceCommandStartCrdSessionJobCurtainSessionTest,
@@ -1008,7 +1004,7 @@
 
   EXPECT_ERROR(
       RunJobAndWaitForResult(Payload().Set("curtainLocalUserSession", true)),
-      DeviceCommandStartCrdSessionJob::FAILURE_UNSUPPORTED_USER_TYPE);
+      ResultCode::FAILURE_UNSUPPORTED_USER_TYPE);
 }
 
 TEST_F(DeviceCommandStartCrdSessionJobCurtainSessionTest,
@@ -1019,7 +1015,7 @@
 
   EXPECT_ERROR(
       RunJobAndWaitForResult(Payload().Set("curtainLocalUserSession", true)),
-      DeviceCommandStartCrdSessionJob::FAILURE_UNSUPPORTED_USER_TYPE);
+      ResultCode::FAILURE_UNSUPPORTED_USER_TYPE);
 }
 
 TEST_F(DeviceCommandStartCrdSessionJobCurtainSessionTest,
@@ -1030,7 +1026,7 @@
 
   EXPECT_ERROR(
       RunJobAndWaitForResult(Payload().Set("curtainLocalUserSession", true)),
-      DeviceCommandStartCrdSessionJob::FAILURE_UNSUPPORTED_USER_TYPE);
+      ResultCode::FAILURE_UNSUPPORTED_USER_TYPE);
 }
 
 TEST_F(DeviceCommandStartCrdSessionJobCurtainSessionTest,
@@ -1092,7 +1088,7 @@
 
   EXPECT_ERROR(
       RunJobAndWaitForResult(Payload().Set("curtainLocalUserSession", true)),
-      DeviceCommandStartCrdSessionJob::FAILURE_UNMANAGED_ENVIRONMENT);
+      ResultCode::FAILURE_UNMANAGED_ENVIRONMENT);
 }
 
 TEST_F(DeviceCommandStartCrdSessionJobCurtainSessionTest,
@@ -1106,7 +1102,7 @@
 
   EXPECT_ERROR(
       RunJobAndWaitForResult(Payload().Set("curtainLocalUserSession", true)),
-      DeviceCommandStartCrdSessionJob::FAILURE_UNMANAGED_ENVIRONMENT);
+      ResultCode::FAILURE_UNMANAGED_ENVIRONMENT);
 }
 
 TEST_F(DeviceCommandStartCrdSessionJobCurtainSessionTest,
@@ -1118,7 +1114,7 @@
 
   EXPECT_ERROR(
       RunJobAndWaitForResult(Payload().Set("curtainLocalUserSession", true)),
-      DeviceCommandStartCrdSessionJob::FAILURE_UNMANAGED_ENVIRONMENT);
+      ResultCode::FAILURE_UNMANAGED_ENVIRONMENT);
 }
 
 TEST_F(DeviceCommandStartCrdSessionJobCurtainSessionTest,
diff --git a/chrome/browser/ash/smb_client/smb_file_system.cc b/chrome/browser/ash/smb_client/smb_file_system.cc
index 20b57ac..4b9435d 100644
--- a/chrome/browser/ash/smb_client/smb_file_system.cc
+++ b/chrome/browser/ash/smb_client/smb_file_system.cc
@@ -167,7 +167,8 @@
   return file_system_info_;
 }
 
-file_system_provider::RequestManager* SmbFileSystem::GetRequestManager() {
+file_system_provider::OperationRequestManager*
+SmbFileSystem::GetRequestManager() {
   NOTREACHED();
   return nullptr;
 }
diff --git a/chrome/browser/ash/smb_client/smb_file_system.h b/chrome/browser/ash/smb_client/smb_file_system.h
index d63e1d09..dc1dc2e 100644
--- a/chrome/browser/ash/smb_client/smb_file_system.h
+++ b/chrome/browser/ash/smb_client/smb_file_system.h
@@ -31,7 +31,7 @@
 namespace ash {
 
 namespace file_system_manager {
-class RequestManager;
+class OperationRequestManager;
 }  // namespace file_system_manager
 
 namespace smb_client {
@@ -140,7 +140,7 @@
   const file_system_provider::ProvidedFileSystemInfo& GetFileSystemInfo()
       const override;
 
-  file_system_provider::RequestManager* GetRequestManager() override;
+  file_system_provider::OperationRequestManager* GetRequestManager() override;
 
   file_system_provider::Watchers* GetWatchers() override;
 
diff --git a/chrome/browser/ash/smb_client/smb_provider.cc b/chrome/browser/ash/smb_client/smb_provider.cc
index c7cf225..d0d905a 100644
--- a/chrome/browser/ash/smb_client/smb_provider.cc
+++ b/chrome/browser/ash/smb_client/smb_provider.cc
@@ -55,7 +55,17 @@
   return icon_set_;
 }
 
-bool SmbProvider::RequestMount(Profile* profile) {
+file_system_provider::RequestManager* SmbProvider::GetRequestManager() {
+  NOTREACHED();
+  return nullptr;
+}
+
+bool SmbProvider::RequestMount(
+    Profile* profile,
+    ash::file_system_provider::RequestMountCallback callback) {
+  // Mount requests for SMB are handled by the SMB dialog. The callback
+  // isn't expected to be used.
+  std::move(callback).Run(base::File::Error::FILE_OK);
   smb_dialog::SmbShareDialog::Show();
   return true;
 }
diff --git a/chrome/browser/ash/smb_client/smb_provider.h b/chrome/browser/ash/smb_client/smb_provider.h
index ff9a4a0..46baa920c 100644
--- a/chrome/browser/ash/smb_client/smb_provider.h
+++ b/chrome/browser/ash/smb_client/smb_provider.h
@@ -12,6 +12,7 @@
 #include "chrome/browser/ash/file_system_provider/provided_file_system_info.h"
 #include "chrome/browser/ash/file_system_provider/provided_file_system_interface.h"
 #include "chrome/browser/ash/file_system_provider/provider_interface.h"
+#include "chrome/browser/ash/file_system_provider/request_manager.h"
 #include "chrome/browser/ash/smb_client/smb_file_system.h"
 
 class Profile;
@@ -35,7 +36,10 @@
   const file_system_provider::ProviderId& GetId() const override;
   const std::string& GetName() const override;
   const file_system_provider::IconSet& GetIconSet() const override;
-  bool RequestMount(Profile* profile) override;
+  file_system_provider::RequestManager* GetRequestManager() override;
+  bool RequestMount(
+      Profile* profile,
+      ash::file_system_provider::RequestMountCallback callback) override;
 
  private:
   file_system_provider::ProviderId provider_id_;
diff --git a/chrome/browser/ash/telemetry_extension/diagnostics_service_ash.cc b/chrome/browser/ash/telemetry_extension/diagnostics_service_ash.cc
index 5c12cf3..f6b2087 100644
--- a/chrome/browser/ash/telemetry_extension/diagnostics_service_ash.cc
+++ b/chrome/browser/ash/telemetry_extension/diagnostics_service_ash.cc
@@ -15,6 +15,7 @@
 #include "chromeos/ash/services/cros_healthd/public/mojom/cros_healthd_diagnostics.mojom.h"
 #include "chromeos/ash/services/cros_healthd/public/mojom/nullable_primitives.mojom.h"
 #include "chromeos/crosapi/mojom/diagnostics_service.mojom.h"
+#include "chromeos/crosapi/mojom/nullable_primitives.mojom.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/receiver_set.h"
 #include "mojo/public/cpp/bindings/remote.h"
@@ -373,15 +374,18 @@
 }
 
 void DiagnosticsServiceAsh::RunSmartctlCheckRoutine(
+    crosapi::mojom::UInt32ValuePtr percentage_used_threshold,
     RunSmartctlCheckRoutineCallback callback) {
-  GetService()->RunSmartctlCheckRoutine(base::BindOnce(
-      [](crosapi::mojom::DiagnosticsService::RunSmartctlCheckRoutineCallback
-             callback,
-         cros_healthd::mojom::RunRoutineResponsePtr ptr) {
-        std::move(callback).Run(
-            converters::ConvertDiagnosticsPtr(std::move(ptr)));
-      },
-      std::move(callback)));
+  GetService()->RunSmartctlCheckRoutine(
+      converters::ConvertDiagnosticsPtr(std::move(percentage_used_threshold)),
+      base::BindOnce(
+          [](crosapi::mojom::DiagnosticsService::RunSmartctlCheckRoutineCallback
+                 callback,
+             cros_healthd::mojom::RunRoutineResponsePtr ptr) {
+            std::move(callback).Run(
+                converters::ConvertDiagnosticsPtr(std::move(ptr)));
+          },
+          std::move(callback)));
 }
 
 }  // namespace ash
diff --git a/chrome/browser/ash/telemetry_extension/diagnostics_service_ash.h b/chrome/browser/ash/telemetry_extension/diagnostics_service_ash.h
index 9640dfd..82971b4 100644
--- a/chrome/browser/ash/telemetry_extension/diagnostics_service_ash.h
+++ b/chrome/browser/ash/telemetry_extension/diagnostics_service_ash.h
@@ -9,6 +9,7 @@
 
 #include "chromeos/ash/services/cros_healthd/public/mojom/cros_healthd.mojom.h"
 #include "chromeos/crosapi/mojom/diagnostics_service.mojom.h"
+#include "chromeos/crosapi/mojom/nullable_primitives.mojom.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/receiver_set.h"
 #include "mojo/public/cpp/bindings/remote.h"
@@ -106,6 +107,7 @@
   void RunSignalStrengthRoutine(
       RunSignalStrengthRoutineCallback callback) override;
   void RunSmartctlCheckRoutine(
+      crosapi::mojom::UInt32ValuePtr percentage_used_threshold,
       RunSmartctlCheckRoutineCallback callback) override;
 
   // Pointer to real implementation.
diff --git a/chrome/browser/ash/telemetry_extension/diagnostics_service_ash_unittest.cc b/chrome/browser/ash/telemetry_extension/diagnostics_service_ash_unittest.cc
index 3451dd2..4474d6a9b 100644
--- a/chrome/browser/ash/telemetry_extension/diagnostics_service_ash_unittest.cc
+++ b/chrome/browser/ash/telemetry_extension/diagnostics_service_ash_unittest.cc
@@ -14,6 +14,7 @@
 #include "chromeos/ash/services/cros_healthd/public/cpp/fake_cros_healthd.h"
 #include "chromeos/ash/services/cros_healthd/public/mojom/cros_healthd_diagnostics.mojom.h"
 #include "chromeos/crosapi/mojom/diagnostics_service.mojom.h"
+#include "chromeos/crosapi/mojom/nullable_primitives.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -82,6 +83,8 @@
       cros_healthd::mojom::DiagnosticRoutineEnum::kSensitiveSensor,
       cros_healthd::mojom::DiagnosticRoutineEnum::kNvmeSelfTest,
       cros_healthd::mojom::DiagnosticRoutineEnum::kFingerprintAlive,
+      cros_healthd::mojom::DiagnosticRoutineEnum::
+          kSmartctlCheckWithPercentageUsed,
   });
 
   base::test::TestFuture<
@@ -114,7 +117,9 @@
           crosapi::mojom::DiagnosticsRoutineEnum::kSmartctlCheck,
           crosapi::mojom::DiagnosticsRoutineEnum::kSensitiveSensor,
           crosapi::mojom::DiagnosticsRoutineEnum::kNvmeSelfTest,
-          crosapi::mojom::DiagnosticsRoutineEnum::kFingerprintAlive));
+          crosapi::mojom::DiagnosticsRoutineEnum::kFingerprintAlive,
+          crosapi::mojom::DiagnosticsRoutineEnum::
+              kSmartctlCheckWithPercentageUsed));
 }
 
 TEST_F(DiagnosticsServiceAshTest, GetRoutineUpdateSuccess) {
@@ -455,12 +460,27 @@
 
   base::test::TestFuture<crosapi::mojom::DiagnosticsRunRoutineResponsePtr>
       future;
-  diagnostics_service()->RunSmartctlCheckRoutine(future.GetCallback());
+  diagnostics_service()->RunSmartctlCheckRoutine(nullptr, future.GetCallback());
 
   ASSERT_TRUE(future.Wait());
   const auto& result = future.Get();
-  ValidateResponse(result,
-                   cros_healthd::mojom::DiagnosticRoutineEnum::kSmartctlCheck);
+  ValidateResponse(result, cros_healthd::mojom::DiagnosticRoutineEnum::
+                               kSmartctlCheckWithPercentageUsed);
+}
+
+TEST_F(DiagnosticsServiceAshTest, RunSmartctlCheckRoutineWithParameterSuccess) {
+  // Configure FakeCrosHealthd.
+  SetSuccessfulRoutineResponse();
+
+  base::test::TestFuture<crosapi::mojom::DiagnosticsRunRoutineResponsePtr>
+      future;
+  diagnostics_service()->RunSmartctlCheckRoutine(
+      crosapi::mojom::UInt32Value::New(42), future.GetCallback());
+
+  ASSERT_TRUE(future.Wait());
+  const auto& result = future.Get();
+  ValidateResponse(result, cros_healthd::mojom::DiagnosticRoutineEnum::
+                               kSmartctlCheckWithPercentageUsed);
 }
 
 }  // namespace ash
diff --git a/chrome/browser/ash/telemetry_extension/diagnostics_service_converters.cc b/chrome/browser/ash/telemetry_extension/diagnostics_service_converters.cc
index a77382d..6a054dd 100644
--- a/chrome/browser/ash/telemetry_extension/diagnostics_service_converters.cc
+++ b/chrome/browser/ash/telemetry_extension/diagnostics_service_converters.cc
@@ -8,6 +8,7 @@
 #include "base/strings/string_piece.h"
 #include "chrome/browser/ash/wilco_dtc_supportd/mojo_utils.h"
 #include "chromeos/ash/services/cros_healthd/public/mojom/cros_healthd_diagnostics.mojom.h"
+#include "chromeos/ash/services/cros_healthd/public/mojom/nullable_primitives.mojom.h"
 #include "chromeos/crosapi/mojom/diagnostics_service.mojom.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
@@ -66,6 +67,10 @@
       input->id, Convert(input->status));
 }
 
+cros_healthd::mojom::NullableUint32Ptr UncheckedConvertPtr(
+    crosapi::mojom::UInt32ValuePtr value) {
+  return cros_healthd::mojom::NullableUint32::New(value->value);
+}
 }  // namespace unchecked
 
 absl::optional<crosapi::mojom::DiagnosticsRoutineEnum> Convert(
@@ -115,6 +120,10 @@
       return crosapi::mojom::DiagnosticsRoutineEnum::kSensitiveSensor;
     case cros_healthd::mojom::DiagnosticRoutineEnum::kFingerprintAlive:
       return crosapi::mojom::DiagnosticsRoutineEnum::kFingerprintAlive;
+    case cros_healthd::mojom::DiagnosticRoutineEnum::
+        kSmartctlCheckWithPercentageUsed:
+      return crosapi::mojom::DiagnosticsRoutineEnum::
+          kSmartctlCheckWithPercentageUsed;
     default:
       return absl::nullopt;
   }
diff --git a/chrome/browser/ash/telemetry_extension/diagnostics_service_converters.h b/chrome/browser/ash/telemetry_extension/diagnostics_service_converters.h
index 15475f9f..31f03e6 100644
--- a/chrome/browser/ash/telemetry_extension/diagnostics_service_converters.h
+++ b/chrome/browser/ash/telemetry_extension/diagnostics_service_converters.h
@@ -10,7 +10,9 @@
 #include <vector>
 
 #include "chromeos/ash/services/cros_healthd/public/mojom/cros_healthd_diagnostics.mojom.h"
+#include "chromeos/ash/services/cros_healthd/public/mojom/nullable_primitives.mojom.h"
 #include "chromeos/crosapi/mojom/diagnostics_service.mojom-forward.h"
+#include "chromeos/crosapi/mojom/nullable_primitives.mojom.h"
 #include "mojo/public/cpp/system/handle.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
@@ -36,6 +38,9 @@
 crosapi::mojom::DiagnosticsRunRoutineResponsePtr UncheckedConvertPtr(
     cros_healthd::mojom::RunRoutineResponsePtr input);
 
+cros_healthd::mojom::NullableUint32Ptr UncheckedConvertPtr(
+    crosapi::mojom::UInt32ValuePtr value);
+
 }  // namespace unchecked
 
 absl::optional<crosapi::mojom::DiagnosticsRoutineEnum> Convert(
diff --git a/chrome/browser/ash/telemetry_extension/diagnostics_service_converters_unittest.cc b/chrome/browser/ash/telemetry_extension/diagnostics_service_converters_unittest.cc
index 5cfd3a9..a86a86cd 100644
--- a/chrome/browser/ash/telemetry_extension/diagnostics_service_converters_unittest.cc
+++ b/chrome/browser/ash/telemetry_extension/diagnostics_service_converters_unittest.cc
@@ -8,7 +8,9 @@
 #include <vector>
 
 #include "chromeos/ash/services/cros_healthd/public/mojom/cros_healthd_diagnostics.mojom.h"
+#include "chromeos/ash/services/cros_healthd/public/mojom/nullable_primitives.mojom.h"
 #include "chromeos/crosapi/mojom/diagnostics_service.mojom.h"
+#include "chromeos/crosapi/mojom/nullable_primitives.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -72,6 +74,9 @@
             crosapi::DiagnosticsRoutineEnum::kSensitiveSensor);
   EXPECT_EQ(Convert(cros_healthd::DiagnosticRoutineEnum::kFingerprintAlive),
             crosapi::DiagnosticsRoutineEnum::kFingerprintAlive);
+  EXPECT_EQ(Convert(cros_healthd::DiagnosticRoutineEnum::
+                        kSmartctlCheckWithPercentageUsed),
+            crosapi::DiagnosticsRoutineEnum::kSmartctlCheckWithPercentageUsed);
 
   EXPECT_EQ(Convert(cros_healthd::DiagnosticRoutineEnum::kArcHttp),
             absl::nullopt);
@@ -205,5 +210,15 @@
             cros_healthd::DiskReadRoutineTypeEnum::kRandomRead);
 }
 
+TEST(DiagnosticsServiceConvertersTest, ConvertUInt32ValuePtr) {
+  namespace cros_healthd = cros_healthd::mojom;
+  namespace crosapi = ::crosapi::mojom;
+
+  EXPECT_EQ(ConvertDiagnosticsPtr(crosapi::UInt32ValuePtr()),
+            cros_healthd::NullableUint32Ptr());
+  EXPECT_EQ(ConvertDiagnosticsPtr(crosapi::UInt32Value::New(42)),
+            cros_healthd::NullableUint32::New(42));
+}
+
 }  // namespace converters
 }  // namespace ash
diff --git a/chrome/browser/ash/web_applications/help_app/help_app_integration_browsertest.cc b/chrome/browser/ash/web_applications/help_app/help_app_integration_browsertest.cc
index b5998868..446aaa07 100644
--- a/chrome/browser/ash/web_applications/help_app/help_app_integration_browsertest.cc
+++ b/chrome/browser/ash/web_applications/help_app/help_app_integration_browsertest.cc
@@ -205,7 +205,7 @@
   web_app::AppId app_id =
       *GetManager().GetAppIdForSystemApp(SystemWebAppType::HELP);
   web_app::WebAppRegistrar& registrar =
-      web_app::WebAppProvider::GetForTest(profile())->registrar();
+      web_app::WebAppProvider::GetForTest(profile())->registrar_unsafe();
 
   EXPECT_EQ(registrar.GetAppThemeColor(app_id), SK_ColorWHITE);
   EXPECT_EQ(registrar.GetAppBackgroundColor(app_id), SK_ColorWHITE);
@@ -220,7 +220,7 @@
   web_app::AppId app_id =
       *GetManager().GetAppIdForSystemApp(SystemWebAppType::HELP);
   web_app::WebAppRegistrar& registrar =
-      web_app::WebAppProvider::GetForTest(profile())->registrar();
+      web_app::WebAppProvider::GetForTest(profile())->registrar_unsafe();
 
   EXPECT_EQ(registrar.GetAppThemeColor(app_id), SK_ColorWHITE);
   EXPECT_EQ(registrar.GetAppBackgroundColor(app_id), SK_ColorWHITE);
diff --git a/chrome/browser/ash/web_applications/media_app/media_app_integration_browsertest.cc b/chrome/browser/ash/web_applications/media_app/media_app_integration_browsertest.cc
index a10d658..8376f40 100644
--- a/chrome/browser/ash/web_applications/media_app/media_app_integration_browsertest.cc
+++ b/chrome/browser/ash/web_applications/media_app/media_app_integration_browsertest.cc
@@ -1138,7 +1138,7 @@
   web_app::AppId app_id = MediaAppAppId();
 
   web_app::WebAppRegistrar& registrar =
-      web_app::WebAppProvider::GetForTest(profile())->registrar();
+      web_app::WebAppProvider::GetForTest(profile())->registrar_unsafe();
 
   EXPECT_EQ(registrar.GetAppThemeColor(app_id), SK_ColorWHITE);
   EXPECT_EQ(registrar.GetAppBackgroundColor(app_id), SK_ColorWHITE);
@@ -1152,7 +1152,7 @@
   web_app::AppId app_id = MediaAppAppId();
 
   web_app::WebAppRegistrar& registrar =
-      web_app::WebAppProvider::GetForTest(profile())->registrar();
+      web_app::WebAppProvider::GetForTest(profile())->registrar_unsafe();
 
   EXPECT_EQ(registrar.GetAppThemeColor(app_id), gfx::kGoogleGrey900);
   EXPECT_EQ(registrar.GetAppBackgroundColor(app_id), gfx::kGoogleGrey800);
diff --git a/chrome/browser/ash/web_applications/personalization_app/personalization_app_theme_provider_impl.cc b/chrome/browser/ash/web_applications/personalization_app/personalization_app_theme_provider_impl.cc
index 1b25481..19e1a1b 100644
--- a/chrome/browser/ash/web_applications/personalization_app/personalization_app_theme_provider_impl.cc
+++ b/chrome/browser/ash/web_applications/personalization_app/personalization_app_theme_provider_impl.cc
@@ -54,8 +54,12 @@
                                 NotifyColorModeAutoScheduleChanged,
                             base::Unretained(this)));
   }
-  // Call it once to get the status of auto mode.
+  // Call once to get the initial status.
   NotifyColorModeAutoScheduleChanged();
+  if (ash::features::IsJellyEnabled()) {
+    // TODO(b/261505637): Observe changes to the static color pref.
+    OnStaticColorChanged(color_palette_controller_->static_color());
+  }
 }
 
 void PersonalizationAppThemeProviderImpl::SetColorModePref(
@@ -97,6 +101,12 @@
   theme_observer_remote_->OnColorModeChanged(dark_mode_enabled);
 }
 
+void PersonalizationAppThemeProviderImpl::OnStaticColorChanged(
+    absl::optional<SkColor> color) {
+  DCHECK(theme_observer_remote_.is_bound());
+  theme_observer_remote_->OnStaticColorChanged(color);
+}
+
 bool PersonalizationAppThemeProviderImpl::IsColorModeAutoScheduleEnabled() {
   PrefService* pref_service = profile_->GetPrefs();
   DCHECK(pref_service);
@@ -128,5 +138,16 @@
     return;
   }
   color_palette_controller_->SetStaticColor(static_color, base::DoNothing());
+  OnStaticColorChanged(static_color);
+}
+
+void PersonalizationAppThemeProviderImpl::GetStaticColor(
+    GetStaticColorCallback callback) {
+  if (!ash::features::IsJellyEnabled()) {
+    theme_receiver_.ReportBadMessage(
+        "Cannot call GetStaticColor without Jelly enabled.");
+    return;
+  }
+  std::move(callback).Run(color_palette_controller_->static_color());
 }
 }  // namespace ash::personalization_app
diff --git a/chrome/browser/ash/web_applications/personalization_app/personalization_app_theme_provider_impl.h b/chrome/browser/ash/web_applications/personalization_app/personalization_app_theme_provider_impl.h
index 2d5bc53..5ff9139 100644
--- a/chrome/browser/ash/web_applications/personalization_app/personalization_app_theme_provider_impl.h
+++ b/chrome/browser/ash/web_applications/personalization_app/personalization_app_theme_provider_impl.h
@@ -14,6 +14,7 @@
 #include "components/prefs/pref_change_registrar.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/remote.h"
+#include "third_party/skia/include/core/SkColor.h"
 
 class Profile;
 
@@ -62,12 +63,16 @@
   // ash::ColorModeObserver:
   void OnColorModeChanged(bool dark_mode_enabled) override;
 
+  void GetStaticColor(GetStaticColorCallback callback) override;
+
  private:
   bool IsColorModeAutoScheduleEnabled();
 
   // Notify webUI the current state of color mode auto scheduler.
   void NotifyColorModeAutoScheduleChanged();
 
+  void OnStaticColorChanged(absl::optional<SkColor> color);
+
   // Pointer to profile of user that opened personalization SWA. Not owned.
   raw_ptr<Profile> const profile_ = nullptr;
 
diff --git a/chrome/browser/ash/web_applications/personalization_app/personalization_app_theme_provider_impl_unittest.cc b/chrome/browser/ash/web_applications/personalization_app/personalization_app_theme_provider_impl_unittest.cc
index 129323f..128f036c 100644
--- a/chrome/browser/ash/web_applications/personalization_app/personalization_app_theme_provider_impl_unittest.cc
+++ b/chrome/browser/ash/web_applications/personalization_app/personalization_app_theme_provider_impl_unittest.cc
@@ -20,6 +20,7 @@
 #include "content/public/browser/web_contents.h"
 #include "content/public/test/test_web_ui.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/skia/include/core/SkColor.h"
 
 namespace ash::personalization_app {
 
@@ -38,6 +39,10 @@
     color_mode_auto_schedule_enabled_ = enabled;
   }
 
+  void OnStaticColorChanged(absl::optional<::SkColor> static_color) override {
+    static_color_ = static_color;
+  }
+
   mojo::PendingRemote<ash::personalization_app::mojom::ThemeObserver>
   pending_remote() {
     if (theme_observer_receiver_.is_bound()) {
@@ -60,12 +65,20 @@
     return color_mode_auto_schedule_enabled_;
   }
 
+  absl::optional<SkColor> get_static_color() {
+    if (!theme_observer_receiver_.is_bound())
+      return absl::nullopt;
+    theme_observer_receiver_.FlushForTesting();
+    return static_color_;
+  }
+
  private:
   mojo::Receiver<ash::personalization_app::mojom::ThemeObserver>
       theme_observer_receiver_{this};
 
   bool dark_mode_enabled_ = false;
   bool color_mode_auto_schedule_enabled_ = false;
+  absl::optional<::SkColor> static_color_ = absl::nullopt;
 };
 
 }  // namespace
@@ -137,6 +150,12 @@
     return test_theme_observer_.is_color_mode_auto_schedule_enabled();
   }
 
+  absl::optional<SkColor> get_static_color() {
+    if (theme_provider_remote_.is_bound())
+      theme_provider_remote_.FlushForTesting();
+    return test_theme_observer_.get_static_color();
+  }
+
   const base::HistogramTester& histogram_tester() { return histogram_tester_; }
 
  private:
@@ -191,4 +210,31 @@
       kPersonalizationThemeColorModeHistogramName, ColorMode::kAuto, 1);
 }
 
+class PersonalizationAppThemeProviderImplJellyTest
+    : public PersonalizationAppThemeProviderImplTest {
+ public:
+  PersonalizationAppThemeProviderImplJellyTest() {
+    scoped_feature_list_.InitAndEnableFeature(features::kJelly);
+  }
+
+  PersonalizationAppThemeProviderImplJellyTest(
+      const PersonalizationAppThemeProviderImplJellyTest&) = delete;
+  PersonalizationAppThemeProviderImplJellyTest& operator=(
+      const PersonalizationAppThemeProviderImplJellyTest&) = delete;
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+TEST_F(PersonalizationAppThemeProviderImplJellyTest, SetStaticColor) {
+  SetThemeObserver();
+  theme_provider_remote()->FlushForTesting();
+  SkColor color = SK_ColorMAGENTA;
+  EXPECT_NE(color, get_static_color());
+
+  theme_provider()->SetStaticColor(color);
+
+  EXPECT_EQ(color, get_static_color());
+}
+
 }  // namespace ash::personalization_app
diff --git a/chrome/browser/banners/app_banner_manager_desktop.cc b/chrome/browser/banners/app_banner_manager_desktop.cc
index 57d0f9ce..872cf7b 100644
--- a/chrome/browser/banners/app_banner_manager_desktop.cc
+++ b/chrome/browser/banners/app_banner_manager_desktop.cc
@@ -164,7 +164,7 @@
   auto* provider = web_app::WebAppProvider::GetForWebApps(
       Profile::FromBrowserContext(web_contents()->GetBrowserContext()));
   DCHECK(provider);
-  return provider->registrar();
+  return provider->registrar_unsafe();
 }
 
 bool AppBannerManagerDesktop::ShouldAllowWebAppReplacementInstall() {
diff --git a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc
index 844fd0b..1cc356a 100644
--- a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc
+++ b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc
@@ -1547,23 +1547,27 @@
                                                       GURL("http://some.url"));
   auto last_launch_time = base::Time() + base::Seconds(10);
   provider->sync_bridge().SetAppLastLaunchTime(web_app_id, last_launch_time);
-  EXPECT_EQ(provider->registrar().GetAppById(web_app_id)->last_launch_time(),
-            last_launch_time);
+  EXPECT_EQ(
+      provider->registrar_unsafe().GetAppById(web_app_id)->last_launch_time(),
+      last_launch_time);
   auto last_badging_time = base::Time() + base::Seconds(20);
   provider->sync_bridge().SetAppLastBadgingTime(web_app_id, last_badging_time);
-  EXPECT_EQ(provider->registrar().GetAppById(web_app_id)->last_badging_time(),
-            last_badging_time);
+  EXPECT_EQ(
+      provider->registrar_unsafe().GetAppById(web_app_id)->last_badging_time(),
+      last_badging_time);
 
   // Run RemoveEmbedderData, and wait for it to complete.
   BlockUntilBrowsingDataRemoved(base::Time(), base::Time::Max(),
                                 constants::DATA_TYPE_HISTORY, false);
 
   // Verify that web app's last launch time is cleared.
-  EXPECT_EQ(provider->registrar().GetAppById(web_app_id)->last_launch_time(),
-            base::Time());
+  EXPECT_EQ(
+      provider->registrar_unsafe().GetAppById(web_app_id)->last_launch_time(),
+      base::Time());
   // Verify that web app's last badging time is cleared.
-  EXPECT_EQ(provider->registrar().GetAppById(web_app_id)->last_badging_time(),
-            base::Time());
+  EXPECT_EQ(
+      provider->registrar_unsafe().GetAppById(web_app_id)->last_badging_time(),
+      base::Time());
 
   EXPECT_EQ(constants::DATA_TYPE_HISTORY, GetRemovalMask());
 }
diff --git a/chrome/browser/chrome_browser_main_win.cc b/chrome/browser/chrome_browser_main_win.cc
index 9eac64d..3f60005f 100644
--- a/chrome/browser/chrome_browser_main_win.cc
+++ b/chrome/browser/chrome_browser_main_win.cc
@@ -415,7 +415,7 @@
   auto* provider = web_app::WebAppProvider::GetForWebApps(profile);
   if (!provider)
     return;
-  web_app::WebAppRegistrar& registrar = provider->registrar();
+  web_app::WebAppRegistrar& registrar = provider->registrar_unsafe();
 
   // Create a vector of all PWA-launcher paths in |profile_dir|.
   std::vector<base::FilePath> pwa_launcher_paths;
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index ab6c9e4..b00cdd97 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -2266,7 +2266,7 @@
 
   Profile* profile = Profile::FromBrowserContext(browser_context);
   auto& registrar =
-      web_app::WebAppProvider::GetForWebApps(profile)->registrar();
+      web_app::WebAppProvider::GetForWebApps(profile)->registrar_unsafe();
   std::vector<web_app::AppId> app_ids_for_origin =
       registrar.FindAppsInScope(app_origin.GetURL());
   if (app_ids_for_origin.empty()) {
@@ -3976,7 +3976,7 @@
             web_app::WebAppProvider::GetForLocalAppsUnchecked(profile);
         const web_app::AppId& app_id = browser->app_controller()->app_id();
         const web_app::WebAppRegistrar& registrar =
-            web_app_provider->registrar();
+            web_app_provider->registrar_unsafe();
         if (registrar.IsLocallyInstalled(app_id))
           web_prefs->web_app_scope = registrar.GetAppScope(app_id);
 
diff --git a/chrome/browser/chrome_web_platform_security_metrics_browsertest.cc b/chrome/browser/chrome_web_platform_security_metrics_browsertest.cc
index e16bc42..1b135503 100644
--- a/chrome/browser/chrome_web_platform_security_metrics_browsertest.cc
+++ b/chrome/browser/chrome_web_platform_security_metrics_browsertest.cc
@@ -1796,6 +1796,8 @@
     document.body.appendChild(iframe);
   )"));
   CheckCounter(WebFeature::kAnonymousIframe, 0);
+  CheckHistogramCount("Navigation.AnonymousIframeIsSandboxed", false, 0);
+  CheckHistogramCount("Navigation.AnonymousIframeIsSandboxed", true, 0);
 }
 
 IN_PROC_BROWSER_TEST_F(ChromeWebPlatformSecurityMetricsBrowserTest,
@@ -1808,6 +1810,8 @@
     document.body.appendChild(iframe);
   )"));
   CheckCounter(WebFeature::kAnonymousIframe, 1);
+  CheckHistogramCount("Navigation.AnonymousIframeIsSandboxed", false, 1);
+  CheckHistogramCount("Navigation.AnonymousIframeIsSandboxed", true, 0);
 }
 
 IN_PROC_BROWSER_TEST_F(ChromeWebPlatformSecurityMetricsBrowserTest,
@@ -1824,6 +1828,8 @@
     });
   )"));
   CheckCounter(WebFeature::kAnonymousIframe, 0);
+  CheckHistogramCount("Navigation.AnonymousIframeIsSandboxed", false, 0);
+  CheckHistogramCount("Navigation.AnonymousIframeIsSandboxed", true, 0);
 }
 
 IN_PROC_BROWSER_TEST_F(ChromeWebPlatformSecurityMetricsBrowserTest,
@@ -1840,6 +1846,8 @@
     });
   )"));
   CheckCounter(WebFeature::kAnonymousIframe, 1);
+  CheckHistogramCount("Navigation.AnonymousIframeIsSandboxed", false, 1);
+  CheckHistogramCount("Navigation.AnonymousIframeIsSandboxed", true, 0);
 }
 
 IN_PROC_BROWSER_TEST_F(ChromeWebPlatformSecurityMetricsBrowserTest,
@@ -1854,6 +1862,7 @@
       document.body.appendChild(iframe);
     });
   )"));
+  CheckCounter(WebFeature::kAnonymousIframe, 0);
   CheckHistogramCount("Navigation.AnonymousIframeIsSandboxed", false, 0);
   CheckHistogramCount("Navigation.AnonymousIframeIsSandboxed", true, 0);
 }
@@ -1880,6 +1889,7 @@
       createIframe(true),
     ]);
   )"));
+  CheckCounter(WebFeature::kAnonymousIframe, 1);
   CheckHistogramCount("Navigation.AnonymousIframeIsSandboxed", false, 3);
   CheckHistogramCount("Navigation.AnonymousIframeIsSandboxed", true, 2);
 }
diff --git a/chrome/browser/chromeos/extensions/file_system_provider/file_system_provider_api.cc b/chrome/browser/chromeos/extensions/file_system_provider/file_system_provider_api.cc
index 7b7fc16..48c7964f 100644
--- a/chrome/browser/chromeos/extensions/file_system_provider/file_system_provider_api.cc
+++ b/chrome/browser/chromeos/extensions/file_system_provider/file_system_provider_api.cc
@@ -10,9 +10,11 @@
 #include <vector>
 
 #include "base/bind.h"
+#include "base/files/file.h"
 #include "base/memory/ptr_util.h"
 #include "base/trace_event/trace_event.h"
 #include "base/values.h"
+#include "chrome/browser/chromeos/extensions/file_system_provider/provider_function.h"
 #include "chrome/browser/extensions/chrome_extension_function_details.h"
 #include "chrome/common/extensions/api/file_system_provider.h"
 #include "chrome/common/extensions/api/file_system_provider_internal.h"
@@ -128,7 +130,15 @@
 }
 
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
-bool FileSystemProviderBase::InterfaceAvailable() {
+bool FileSystemProviderBase::MountFinishedInterfaceAvailable() {
+  auto* service = chromeos::LacrosService::Get();
+  return service->GetInterfaceVersion(
+             crosapi::mojom::FileSystemProviderService::Uuid_) >=
+         int{crosapi::mojom::FileSystemProviderService::MethodMinVersions::
+                 kMountFinishedMinVersion};
+}
+
+bool FileSystemProviderBase::OperationFinishedInterfaceAvailable() {
   auto* service = chromeos::LacrosService::Get();
   return service->GetInterfaceVersion(
              crosapi::mojom::FileSystemProviderService::Uuid_) >=
@@ -193,7 +203,7 @@
       ->MountWithProfile(std::move(metadata), persistent, std::move(callback),
                          Profile::FromBrowserContext(browser_context()));
 #else
-  if (!InterfaceAvailable())
+  if (!OperationFinishedInterfaceAvailable())
     return RespondNow(Error(kInterfaceUnavailable));
   GetRemote()->Mount(std::move(metadata), persistent, std::move(callback));
 
@@ -218,7 +228,7 @@
       ->UnmountWithProfile(std::move(id), std::move(callback),
                            Profile::FromBrowserContext(browser_context()));
 #else
-  if (!InterfaceAvailable())
+  if (!OperationFinishedInterfaceAvailable())
     return RespondNow(Error(kInterfaceUnavailable));
   GetRemote()->Unmount(std::move(id), std::move(callback));
 #endif
@@ -235,7 +245,7 @@
       ->GetAllWithProfile(GetProviderId(), std::move(callback),
                           Profile::FromBrowserContext(browser_context()));
 #else
-  if (!InterfaceAvailable())
+  if (!OperationFinishedInterfaceAvailable())
     return RespondNow(Error(kInterfaceUnavailable));
   GetRemote()->GetAll(GetProviderId(), std::move(callback));
 #endif
@@ -270,7 +280,7 @@
       ->GetWithProfile(std::move(id), std::move(callback),
                        Profile::FromBrowserContext(browser_context()));
 #else
-  if (!InterfaceAvailable())
+  if (!OperationFinishedInterfaceAvailable())
     return RespondNow(Error(kInterfaceUnavailable));
   GetRemote()->Get(std::move(id), std::move(callback));
 #endif
@@ -320,7 +330,7 @@
                           std::move(changes), std::move(callback),
                           Profile::FromBrowserContext(browser_context()));
 #else
-  if (!InterfaceAvailable())
+  if (!OperationFinishedInterfaceAvailable())
     return RespondNow(Error(kInterfaceUnavailable));
   GetRemote()->Notify(std::move(id), std::move(watcher), type,
                       std::move(changes), std::move(callback));
@@ -338,6 +348,40 @@
   Respond(NoArguments());
 }
 
+bool FileSystemProviderInternal::ForwardMountResult(int64_t request_id,
+                                                    base::Value::List& args) {
+  auto callback =
+      base::BindOnce(&FileSystemProviderInternal::RespondWithError, this);
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  crosapi::CrosapiManager::Get()
+      ->crosapi_ash()
+      ->file_system_provider_service_ash()
+      ->MountFinishedWithProfile(
+          extension_id(), request_id, std::move(args), std::move(callback),
+          Profile::FromBrowserContext(browser_context()));
+  return true;
+#else
+  if (!MountFinishedInterfaceAvailable())
+    return false;
+  GetRemote()->MountFinished(extension_id(), request_id, std::move(args),
+                             std::move(callback));
+  return true;
+#endif
+}
+
+ExtensionFunction::ResponseAction
+FileSystemProviderInternalRespondToMountRequestFunction::Run() {
+  using api::file_system_provider_internal::RespondToMountRequest::Params;
+  std::unique_ptr<Params> params(Params::Create(args()));
+  EXTENSION_FUNCTION_VALIDATE(params);
+
+  int64_t request_id = params->request_id;
+  bool result = ForwardMountResult(request_id, mutable_args());
+  if (!result)
+    Respond(Error(kInterfaceUnavailable));
+  return RespondLater();
+}
+
 ExtensionFunction::ResponseAction
 FileSystemProviderInternalUnmountRequestedSuccessFunction::Run() {
   using api::file_system_provider_internal::UnmountRequestedSuccess::Params;
diff --git a/chrome/browser/chromeos/extensions/file_system_provider/file_system_provider_api.h b/chrome/browser/chromeos/extensions/file_system_provider/file_system_provider_api.h
index a29f2a0..26d26fb 100644
--- a/chrome/browser/chromeos/extensions/file_system_provider/file_system_provider_api.h
+++ b/chrome/browser/chromeos/extensions/file_system_provider/file_system_provider_api.h
@@ -7,7 +7,6 @@
 
 #include "base/values.h"
 #include "build/chromeos_buildflags.h"
-#include "chrome/browser/chromeos/extensions/file_system_provider/provider_function.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chromeos/crosapi/mojom/file_system_provider.mojom.h"
 #include "extensions/browser/extension_function.h"
@@ -32,11 +31,16 @@
   void RespondWithError(const std::string& error);
 
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
-  // Whether ash supports the FileSystemProviderService interface.
-  bool InterfaceAvailable();
+  // Whether ash supports the FileSystemProviderService interface, and in
+  // particular its `MountFinished` method..
+  bool MountFinishedInterfaceAvailable();
+
+  // Whether ash supports the FileSystemProviderService interface, and in
+  // particular its `OperationFinished` method..
+  bool OperationFinishedInterfaceAvailable();
 
   // A helper function that returns a reference to a functional remote. Should
-  // only be called if InterfaceAvailable is true.
+  // only be called if the needed interface method is supported.
   mojo::Remote<crosapi::mojom::FileSystemProviderService>& GetRemote();
 #endif
 };
@@ -112,6 +116,10 @@
     *request_id = params->request_id;
   }
 
+  // Forwards the result of the mount request to the file system provider
+  // service. Returns false if the forwarding failed.
+  bool ForwardMountResult(int64_t request_id, base::Value::List& args);
+
   // Forwards the result of the operation to the file system provider service.
   // Returns false if the forwarding failed.
   template <typename Params>
@@ -133,7 +141,7 @@
             Profile::FromBrowserContext(browser_context()));
     return true;
 #else
-    if (!InterfaceAvailable())
+    if (!OperationFinishedInterfaceAvailable())
       return false;
     GetRemote()->OperationFinished(response, std::move(file_system_id),
                                    request_id, std::move(args),
@@ -143,6 +151,17 @@
   }
 };
 
+class FileSystemProviderInternalRespondToMountRequestFunction
+    : public FileSystemProviderInternal {
+ public:
+  DECLARE_EXTENSION_FUNCTION("fileSystemProviderInternal.respondToMountRequest",
+                             FILESYSTEMPROVIDERINTERNAL_RESPONDTOMOUNTREQUEST)
+
+ protected:
+  ~FileSystemProviderInternalRespondToMountRequestFunction() override = default;
+  ResponseAction Run() override;
+};
+
 class FileSystemProviderInternalUnmountRequestedSuccessFunction
     : public FileSystemProviderInternal {
  public:
diff --git a/chrome/browser/chromeos/extensions/file_system_provider/file_system_provider_apitest.cc b/chrome/browser/chromeos/extensions/file_system_provider/file_system_provider_apitest.cc
index 1e61509e..6217198d 100644
--- a/chrome/browser/chromeos/extensions/file_system_provider/file_system_provider_apitest.cc
+++ b/chrome/browser/chromeos/extensions/file_system_provider/file_system_provider_apitest.cc
@@ -10,6 +10,7 @@
 #include "base/task/single_thread_task_runner.h"
 #include "base/time/time.h"
 #include "chrome/browser/ash/file_system_provider/observer.h"
+#include "chrome/browser/ash/file_system_provider/operation_request_manager.h"
 #include "chrome/browser/ash/file_system_provider/provided_file_system_info.h"
 #include "chrome/browser/ash/file_system_provider/provided_file_system_interface.h"
 #include "chrome/browser/ash/file_system_provider/request_manager.h"
diff --git a/chrome/browser/chromeos/extensions/telemetry/api/base_telemetry_extension_api_guard_function.cc b/chrome/browser/chromeos/extensions/telemetry/api/base_telemetry_extension_api_guard_function.cc
index f76eaeae..b4f7fd0 100644
--- a/chrome/browser/chromeos/extensions/telemetry/api/base_telemetry_extension_api_guard_function.cc
+++ b/chrome/browser/chromeos/extensions/telemetry/api/base_telemetry_extension_api_guard_function.cc
@@ -41,7 +41,7 @@
 
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
   if (!IsCrosApiAvailable()) {
-    error = "Not supported by ash browser";
+    error = "Not implemented.";
     Respond(Error(
         base::StringPrintf("API chrome.%s failed. %s", name(), error.c_str())));
     return;
diff --git a/chrome/browser/chromeos/extensions/telemetry/api/diagnostics_api.cc b/chrome/browser/chromeos/extensions/telemetry/api/diagnostics_api.cc
index 50799ff..d136e95d 100644
--- a/chrome/browser/chromeos/extensions/telemetry/api/diagnostics_api.cc
+++ b/chrome/browser/chromeos/extensions/telemetry/api/diagnostics_api.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/chromeos/extensions/telemetry/api/diagnostics_api.h"
 
+#include <cstddef>
 #include <memory>
 #include <string>
 #include <utility>
@@ -15,9 +16,19 @@
 #include "chrome/browser/chromeos/extensions/telemetry/api/remote_diagnostics_service_strategy.h"
 #include "chrome/common/chromeos/extensions/api/diagnostics.h"
 #include "chromeos/crosapi/mojom/diagnostics_service.mojom.h"
+#include "chromeos/crosapi/mojom/nullable_primitives.mojom.h"
+
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+#include "base/strings/stringprintf.h"
+#include "chromeos/lacros/lacros_service.h"
+#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
 
 namespace chromeos {
 
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+static constexpr char kNotSupportedByAshBrowserError[] = "Not implemented.";
+#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
+
 namespace diag = api::os_diagnostics;
 
 // DiagnosticsApiFunctionBase --------------------------------------------------
@@ -346,7 +357,38 @@
 // OsDiagnosticsRunSmartctlCheckRoutineFunction --------------------------------
 
 void OsDiagnosticsRunSmartctlCheckRoutineFunction::RunIfAllowed() {
-  GetRemoteService()->RunSmartctlCheckRoutine(GetOnResult());
+  std::unique_ptr<api::os_diagnostics::RunSmartctlCheckRoutine::Params> params(
+      api::os_diagnostics::RunSmartctlCheckRoutine::Params::Create(args()));
+
+  crosapi::mojom::UInt32ValuePtr percentage_used;
+  if (params && params->request && params->request->percentage_used_threshold) {
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+    // TODO(b/261181600): Remove this code as soon as version skew is no issue
+    // anymore.
+
+    auto* lacros_service = LacrosService::Get();
+    // Check if ash chrome supports calling the routine with a parameter.
+    if (!lacros_service || lacros_service->GetInterfaceVersion(
+                               crosapi::mojom::DiagnosticsService::Uuid_) < 1) {
+      // TODO(b/261181443): Move this to a super class.
+      Respond(Error(base::StringPrintf("API chrome.%s failed. %s", name(),
+                                       kNotSupportedByAshBrowserError)));
+      return;
+    }
+#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
+
+    percentage_used = crosapi::mojom::UInt32Value::New(
+        params->request->percentage_used_threshold.value());
+  }
+
+  auto cb =
+      base::BindOnce(&DiagnosticsApiRunRoutineFunctionBase::OnResult, this);
+
+  // Backwards compatibility: Calling the routine with an null parameter
+  // results in the same behaviour as the former `RunSmartctlCheckRoutine`
+  // without any parameters.
+  GetRemoteService()->RunSmartctlCheckRoutine(std::move(percentage_used),
+                                              std::move(cb));
 }
 
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/extensions/telemetry/api/diagnostics_api_browsertest.cc b/chrome/browser/chromeos/extensions/telemetry/api/diagnostics_api_browsertest.cc
index 77cde2f..6067ac3 100644
--- a/chrome/browser/chromeos/extensions/telemetry/api/diagnostics_api_browsertest.cc
+++ b/chrome/browser/chromeos/extensions/telemetry/api/diagnostics_api_browsertest.cc
@@ -21,6 +21,7 @@
 
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
 #include "chromeos/lacros/lacros_service.h"
+#include "chromeos/startup/browser_init_params.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
 
@@ -54,6 +55,16 @@
   }
 #endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
 
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+  template <typename Interface>
+  bool InterfaceVersionHigherOrEqual(int version) {
+    auto* lacros_service = chromeos::LacrosService::Get();
+
+    return lacros_service &&
+           lacros_service->GetInterfaceVersion(Interface::Uuid_) >= version;
+  }
+#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
+
   void SetServiceForTesting(
       std::unique_ptr<FakeDiagnosticsService> fake_diagnostics_service_impl) {
 #if BUILDFLAG(IS_CHROMEOS_ASH)
@@ -387,6 +398,8 @@
         crosapi::mojom::DiagnosticsRoutineEnum::kSensitiveSensor,
         crosapi::mojom::DiagnosticsRoutineEnum::kNvmeSelfTest,
         crosapi::mojom::DiagnosticsRoutineEnum::kFingerprintAlive,
+        crosapi::mojom::DiagnosticsRoutineEnum::
+            kSmartctlCheckWithPercentageUsed,
     });
 
     SetServiceForTesting(std::move(fake_service_impl));
@@ -420,7 +433,8 @@
               "smartctl_check",
               "sensitive_sensor",
               "nvme_self_test",
-              "fingerprint_alive"
+              "fingerprint_alive",
+              "smartctl_check_with_percentage_used"
             ]
           }, response);
         chrome.test.succeed();
@@ -1531,4 +1545,90 @@
   )");
 }
 
+IN_PROC_BROWSER_TEST_F(TelemetryExtensionDiagnosticsApiBrowserTest,
+                       RunSmartctlCheckRoutineWithPercentageUsedSuccess) {
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+  // Only run this tests when Ash does support the new parameter for
+  // SmartctlCheck. The parameter is supported from version 1 onwards.
+  if (!InterfaceVersionHigherOrEqual<crosapi::mojom::DiagnosticsService>(1)) {
+    return;
+  }
+#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
+
+  // Configure FakeDiagnosticsService.
+  {
+    auto expected_response =
+        crosapi::mojom::DiagnosticsRunRoutineResponse::New();
+    expected_response->id = 0;
+    expected_response->status =
+        crosapi::mojom::DiagnosticsRoutineStatusEnum::kReady;
+
+    // Set the return value for a call to RunSmartctlCheckRoutine.
+    auto fake_service_impl = std::make_unique<FakeDiagnosticsService>();
+    fake_service_impl->SetRunRoutineResponse(std::move(expected_response));
+
+    base::Value::Dict expected_result;
+    expected_result.Set("percentage_used_threshold", 42);
+
+    fake_service_impl->SetExpectedLastPassedParameters(
+        std::move(expected_result));
+
+    // Set the expected called routine.
+    fake_service_impl->SetExpectedLastCalledRoutine(
+        crosapi::mojom::DiagnosticsRoutineEnum::
+            kSmartctlCheckWithPercentageUsed);
+
+    SetServiceForTesting(std::move(fake_service_impl));
+  }
+
+  CreateExtensionAndRunServiceWorker(R"(
+    chrome.test.runTests([
+      async function runSmartctlCheckRoutine() {
+        const response =
+          await chrome.os.diagnostics.runSmartctlCheckRoutine(
+            {
+              percentage_used_threshold: 42
+            }
+          );
+        chrome.test.assertEq({id: 0, status: "ready"}, response);
+        chrome.test.succeed();
+      }
+    ]);
+  )");
+}
+
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+// TODO(b/261181600): Remove this code as soon as version skew is no issue
+// anymore. We only do version skew testing in Lacros.
+IN_PROC_BROWSER_TEST_F(
+    TelemetryExtensionDiagnosticsApiBrowserTest,
+    RunSmartctlCheckRoutineWithPercentageUsedVersionSkewSuccess) {
+  // Set the interface version to a version that does not support the parameter.
+  crosapi::mojom::BrowserInitParamsPtr init_params =
+      BrowserInitParams::GetForTests()->Clone();
+  init_params->interface_versions
+      .value()[crosapi::mojom::DiagnosticsService::Uuid_] = 0;
+  BrowserInitParams::SetInitParamsForTests(std::move(init_params));
+
+  // Expect an error message if Ash does not support the SmartctlCheck
+  // interface with a parameter.
+  CreateExtensionAndRunServiceWorker(R"(
+    chrome.test.runTests([
+      async function runSmartctlCheckRoutine() {
+        await chrome.test.assertPromiseRejects(
+            chrome.os.diagnostics.runSmartctlCheckRoutine(
+              {
+                percentage_used_threshold: 42
+              }
+            ),
+            'Error: API chrome.os.diagnostics.runSmartctlCheckRoutine ' +
+            'failed. Not implemented.'
+        );
+        chrome.test.succeed();
+      },
+    ]);
+  )");
+}
+#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
+
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/extensions/telemetry/api/diagnostics_api_converters.cc b/chrome/browser/chromeos/extensions/telemetry/api/diagnostics_api_converters.cc
index edab3e9..4d4e674 100644
--- a/chrome/browser/chromeos/extensions/telemetry/api/diagnostics_api_converters.cc
+++ b/chrome/browser/chromeos/extensions/telemetry/api/diagnostics_api_converters.cc
@@ -102,6 +102,9 @@
     case MojoRoutineType::kFingerprintAlive:
       *out = RoutineType::ROUTINE_TYPE_FINGERPRINT_ALIVE;
       return true;
+    case MojoRoutineType::kSmartctlCheckWithPercentageUsed:
+      *out = RoutineType::ROUTINE_TYPE_SMARTCTL_CHECK_WITH_PERCENTAGE_USED;
+      return true;
     default:
       return false;
   }
diff --git a/chrome/browser/chromeos/extensions/telemetry/api/diagnostics_api_converters_unittest.cc b/chrome/browser/chromeos/extensions/telemetry/api/diagnostics_api_converters_unittest.cc
index a921a688..4c86f3bc1 100644
--- a/chrome/browser/chromeos/extensions/telemetry/api/diagnostics_api_converters_unittest.cc
+++ b/chrome/browser/chromeos/extensions/telemetry/api/diagnostics_api_converters_unittest.cc
@@ -136,6 +136,13 @@
   }
   {
     RoutineType out = RoutineType::ROUTINE_TYPE_NONE;
+    EXPECT_TRUE(ConvertMojoRoutine(
+        MojoRoutineType::kSmartctlCheckWithPercentageUsed, &out));
+    EXPECT_EQ(out,
+              RoutineType::ROUTINE_TYPE_SMARTCTL_CHECK_WITH_PERCENTAGE_USED);
+  }
+  {
+    RoutineType out = RoutineType::ROUTINE_TYPE_NONE;
     EXPECT_TRUE(ConvertMojoRoutine(MojoRoutineType::kSmartctlCheck, &out));
     EXPECT_EQ(out, RoutineType::ROUTINE_TYPE_SMARTCTL_CHECK);
   }
diff --git a/chrome/browser/chromeos/extensions/telemetry/api/fake_diagnostics_service.cc b/chrome/browser/chromeos/extensions/telemetry/api/fake_diagnostics_service.cc
index ce468057..590b5741 100644
--- a/chrome/browser/chromeos/extensions/telemetry/api/fake_diagnostics_service.cc
+++ b/chrome/browser/chromeos/extensions/telemetry/api/fake_diagnostics_service.cc
@@ -321,10 +321,21 @@
 }
 
 void FakeDiagnosticsService::RunSmartctlCheckRoutine(
+    crosapi::mojom::UInt32ValuePtr percentage_used_threshold,
     RunSmartctlCheckRoutineCallback callback) {
   actual_passed_parameters_.clear();
-  actual_called_routine_ =
-      crosapi::mojom::DiagnosticsRoutineEnum::kSmartctlCheck;
+
+  if (percentage_used_threshold) {
+    actual_passed_parameters_.Set(
+        "percentage_used_threshold",
+        static_cast<int32_t>(percentage_used_threshold->value));
+    actual_called_routine_ = crosapi::mojom::DiagnosticsRoutineEnum::
+        kSmartctlCheckWithPercentageUsed;
+  } else {
+    actual_called_routine_ =
+        crosapi::mojom::DiagnosticsRoutineEnum::kSmartctlCheck;
+  }
+
   base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE,
       base::BindOnce(std::move(callback), run_routine_response_->Clone()));
diff --git a/chrome/browser/chromeos/extensions/telemetry/api/fake_diagnostics_service.h b/chrome/browser/chromeos/extensions/telemetry/api/fake_diagnostics_service.h
index 10f51abd..5133f9e 100644
--- a/chrome/browser/chromeos/extensions/telemetry/api/fake_diagnostics_service.h
+++ b/chrome/browser/chromeos/extensions/telemetry/api/fake_diagnostics_service.h
@@ -10,6 +10,7 @@
 
 #include "base/values.h"
 #include "chromeos/crosapi/mojom/diagnostics_service.mojom.h"
+#include "chromeos/crosapi/mojom/nullable_primitives.mojom.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -84,6 +85,7 @@
   void RunSignalStrengthRoutine(
       RunSignalStrengthRoutineCallback callback) override;
   void RunSmartctlCheckRoutine(
+      crosapi::mojom::UInt32ValuePtr percentage_used_threshold,
       RunSmartctlCheckRoutineCallback callback) override;
 
   // Sets the return value for |Run*Routine|.
diff --git a/chrome/browser/creator/android/BUILD.gn b/chrome/browser/creator/android/BUILD.gn
index e4811b1..47b141f 100644
--- a/chrome/browser/creator/android/BUILD.gn
+++ b/chrome/browser/creator/android/BUILD.gn
@@ -12,26 +12,42 @@
     "java/src/org/chromium/chrome/browser/creator/CreatorProfileView.java",
     "java/src/org/chromium/chrome/browser/creator/CreatorProfileViewBinder.java",
     "java/src/org/chromium/chrome/browser/creator/CreatorProperties.java",
+    "java/src/org/chromium/chrome/browser/creator/CreatorTabMediator.java",
+    "java/src/org/chromium/chrome/browser/creator/CreatorTabSheetContent.java",
     "java/src/org/chromium/chrome/browser/creator/CreatorToolbarView.java",
     "java/src/org/chromium/chrome/browser/creator/CreatorToolbarViewBinder.java",
+    "java/src/org/chromium/chrome/browser/creator/NewTabCreator.java",
+    "java/src/org/chromium/chrome/browser/creator/WebContentsCreator.java",
   ]
   deps = [
     ":creator_java_resources",
     "//base:base_java",
     "//base:jni_java",
     "//build/android:build_java",
+    "//chrome/browser/android/customtabs/branding:java",
     "//chrome/browser/feed/android:feed_java_resources",
     "//chrome/browser/feed/android:java",
     "//chrome/browser/feedback/android:java",
     "//chrome/browser/profiles/android:java",
     "//chrome/browser/share:java",
+    "//chrome/browser/ui/android/favicon:java",
     "//chrome/browser/ui/messages/android:java",
     "//chrome/browser/xsurface:java",
     "//components/browser_ui/bottomsheet/android:factory_java",
     "//components/browser_ui/bottomsheet/android:java",
     "//components/browser_ui/styles/android:java",
     "//components/browser_ui/widget/android:java",
+    "//components/embedder_support/android:content_view_java",
+    "//components/embedder_support/android:util_java",
+    "//components/embedder_support/android:web_contents_delegate_java",
     "//components/feed/core/v2:feedv2_core_java",
+    "//components/security_state/content/android:java",
+    "//components/security_state/core:security_state_enums_java",
+    "//components/thin_webview:factory_java",
+    "//components/thin_webview:java",
+    "//components/url_formatter/android:url_formatter_java",
+    "//components/version_info/android:version_constants_java",
+    "//content/public/android:content_full_java",
     "//content/public/android:content_main_dex_java",
     "//third_party/androidx:androidx_annotation_annotation_java",
     "//third_party/androidx:androidx_appcompat_appcompat_java",
@@ -52,6 +68,7 @@
     "java/res/drawable/following_checkmark.xml",
     "java/res/drawable/profile_background.xml",
     "java/res/layout/creator_activity.xml",
+    "java/res/layout/creator_bottomsheet_toolbar.xml",
     "java/res/layout/creator_layout.xml",
     "java/res/layout/creator_profile.xml",
     "java/res/values/dimens.xml",
@@ -85,6 +102,7 @@
     "//chrome/browser/feed/android:feed_java_resources",
     "//chrome/browser/feed/android:java",
     "//chrome/browser/profiles/android:java",
+    "//chrome/browser/share:java",
     "//chrome/browser/ui/messages/android:java",
     "//third_party/androidx:androidx_test_core_java",
     "//third_party/androidx:androidx_test_ext_junit_java",
diff --git a/chrome/browser/creator/android/DEPS b/chrome/browser/creator/android/DEPS
index 840b66c..c7b6430a 100644
--- a/chrome/browser/creator/android/DEPS
+++ b/chrome/browser/creator/android/DEPS
@@ -1,3 +1,4 @@
 include_rules = [
-  "+components/browser_ui/share/android"
+  "+components/browser_ui/share/android",
+  "+components/thin_webview",
 ]
diff --git a/chrome/browser/creator/android/java/res/drawable/following_checkmark.xml b/chrome/browser/creator/android/java/res/drawable/following_checkmark.xml
index 77f33ef..26f4103c 100644
--- a/chrome/browser/creator/android/java/res/drawable/following_checkmark.xml
+++ b/chrome/browser/creator/android/java/res/drawable/following_checkmark.xml
@@ -9,7 +9,7 @@
     android:height="24dp"
     android:viewportWidth="24"
     android:viewportHeight="24"
-    android:tint="@macro/default_text_color_secondary">
+    android:tint="@macro/default_text_color_accent1">
   <path
       android:fillColor="@android:color/white"
       android:pathData="M9.55,17.65 L4.225,12.325 5.275,11.25 9.55,15.525 18.725,6.35 19.775,7.425Z"/>
diff --git a/chrome/browser/creator/android/java/res/drawable/profile_background.xml b/chrome/browser/creator/android/java/res/drawable/profile_background.xml
index 770e2b3e0..72baae3 100644
--- a/chrome/browser/creator/android/java/res/drawable/profile_background.xml
+++ b/chrome/browser/creator/android/java/res/drawable/profile_background.xml
@@ -4,14 +4,15 @@
      found in the LICENSE file.
 -->
 
-<shape
+<org.chromium.components.browser_ui.widget.SurfaceColorDrawable
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="rectangle" >
-  <solid android:color="?attr/colorSurfaceVariant" />
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:shape="rectangle"
+    app:surfaceElevation="@dimen/default_elevation_1" >
   <corners android:radius="@dimen/card_rounded_corner_radius" />
   <padding
       android:top="10dp"
       android:bottom="10dp"
       android:left="20dp"
       android:right="20dp"/>
-</shape>
\ No newline at end of file
+</org.chromium.components.browser_ui.widget.SurfaceColorDrawable>
diff --git a/chrome/browser/creator/android/java/res/layout/creator_bottomsheet_toolbar.xml b/chrome/browser/creator/android/java/res/layout/creator_bottomsheet_toolbar.xml
new file mode 100644
index 0000000..8e967994
--- /dev/null
+++ b/chrome/browser/creator/android/java/res/layout/creator_bottomsheet_toolbar.xml
@@ -0,0 +1,125 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2022 The Chromium Authors
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<!-- A toolbar layout designed for bottom-sheet based components -->
+<FrameLayout
+    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_width="match_parent"
+    android:layout_height="@dimen/creator_tab_toolbar_height">
+  <LinearLayout
+      android:id="@+id/toolbar"
+      android:layout_width="match_parent"
+      android:layout_height="@dimen/creator_tab_toolbar_height"
+      android:orientation="vertical">
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="4dp" />
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="62dp"
+        android:paddingStart="16dp"
+        android:paddingEnd="16dp"
+        android:orientation="horizontal">
+      <org.chromium.ui.widget.ChromeImageView
+          android:id="@+id/favicon"
+          android:layout_width="24dp"
+          android:layout_height="24dp"
+          android:layout_alignParentBottom="true"
+          android:layout_marginEnd="16dp"
+          android:layout_marginBottom="14dp"
+          android:scaleType="fitCenter"
+          tools:ignore="ContentDescription" />
+      <ImageView
+          android:id="@+id/drag_handlebar"
+          android:layout_width="wrap_content"
+          android:layout_height="wrap_content"
+          android:layout_gravity="center_horizontal|top"
+          android:layout_marginTop="4dp"
+          android:layout_centerHorizontal="true"
+          android:importantForAccessibility="no"
+          android:src="@drawable/drag_handlebar" />
+      <org.chromium.ui.widget.ChromeImageView
+          android:id="@+id/close"
+          android:layout_width="40dp"
+          android:layout_height="40dp"
+          android:paddingTop="8dp"
+          android:paddingBottom="8dp"
+          android:paddingStart="8dp"
+          android:paddingEnd="8dp"
+          android:layout_alignParentEnd="true"
+          android:layout_alignParentBottom="true"
+          android:layout_marginBottom="6dp"
+          android:src="@drawable/btn_close"
+          android:contentDescription="@string/close"
+          app:tint="@macro/default_icon_color" />
+      <org.chromium.ui.widget.ChromeImageView
+          android:id="@+id/open_in_new_tab"
+          android:layout_width="40dp"
+          android:layout_height="40dp"
+          android:paddingTop="8dp"
+          android:paddingBottom="8dp"
+          android:paddingStart="8dp"
+          android:paddingEnd="8dp"
+          android:layout_toStartOf="@+id/close"
+          android:layout_alignParentBottom="true"
+          android:layout_marginEnd="8dp"
+          android:layout_marginBottom="6dp"
+          android:src="@drawable/open_in_new_tab"
+          android:contentDescription="@string/contextmenu_open_in_new_tab"
+          android:visibility="gone"
+          app:tint="@macro/default_icon_color" />
+      <TextView
+          android:id="@+id/title"
+          android:layout_width="match_parent"
+          android:layout_height="wrap_content"
+          android:layout_marginTop="16dp"
+          android:layout_marginEnd="16dp"
+          android:layout_toStartOf="@id/open_in_new_tab"
+          android:layout_toEndOf="@id/favicon"
+          android:textAlignment="viewStart"
+          android:ellipsize="end"
+          android:singleLine="true"
+          android:textAppearance="@style/TextAppearance.TextMedium.Primary" />
+      <org.chromium.ui.widget.ChromeImageView
+          android:id="@+id/security_icon"
+          android:layout_width="16dp"
+          android:layout_height="16dp"
+          android:layout_alignStart="@id/title"
+          android:layout_below="@id/title"
+          android:layout_marginTop="3dp"
+          app:tint="@macro/default_icon_color" />
+      <TextView
+          android:id="@+id/origin"
+          android:layout_width="match_parent"
+          android:layout_height="wrap_content"
+          android:layout_toStartOf="@id/open_in_new_tab"
+          android:layout_toEndOf="@id/security_icon"
+          android:layout_below="@id/title"
+          android:layout_marginStart="4dp"
+          android:textAlignment="viewStart"
+          android:ellipsize="start"
+          android:singleLine="true"
+          android:textAppearance="@style/TextAppearance.TextMedium.Secondary" />
+    </RelativeLayout>
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="2dp" />
+    <ProgressBar
+        android:id="@+id/progress_bar"
+        style="?android:attr/progressBarStyleHorizontal"
+        android:layout_width="match_parent"
+        android:layout_height="2dp"
+        android:max="100" />
+  </LinearLayout>
+  <org.chromium.components.browser_ui.widget.FadingShadowView
+      android:id="@+id/shadow"
+      android:layout_width="match_parent"
+      android:layout_height="@dimen/action_bar_shadow_height"
+      android:layout_marginTop="@dimen/creator_tab_toolbar_height"/>
+</FrameLayout>
diff --git a/chrome/browser/creator/android/java/res/values/dimens.xml b/chrome/browser/creator/android/java/res/values/dimens.xml
index c94fbaf..1fd30c5 100644
--- a/chrome/browser/creator/android/java/res/values/dimens.xml
+++ b/chrome/browser/creator/android/java/res/values/dimens.xml
@@ -8,4 +8,7 @@
 <resources xmlns:tools="http://schemas.android.com/tools">
     <!-- Content Previews dimensions. -->
     <dimen name="content_previews_padding">16dp</dimen>
+
+    <!-- Sheet tab toolbar dimensions -->
+    <dimen name="creator_tab_toolbar_height">70dp</dimen>
 </resources>
diff --git a/chrome/browser/creator/android/java/res/values/styles.xml b/chrome/browser/creator/android/java/res/values/styles.xml
index a5babaf..70d2004 100644
--- a/chrome/browser/creator/android/java/res/values/styles.xml
+++ b/chrome/browser/creator/android/java/res/values/styles.xml
@@ -8,6 +8,7 @@
 <resources xmlns:tools="http://schemas.android.com/tools">
     <style name="CreatorActivityNoActionBar">
         <item name="windowActionBar">false</item>
+        <item name="titleTextAppearance">@style/TextAppearance.Headline.Primary</item>
     </style>
 
     <style name="CreatorFollowFilledButton" parent="ButtonCompatBase">
@@ -27,9 +28,11 @@
         <item name="android:drawablePadding">5dp</item>
         <item name="android:layout_marginEnd">5dp</item>
         <item name="android:textAppearance">@style/TextAppearance.TextAccentMediumThick</item>
-        <item name="buttonTextColor">@macro/default_text_color_secondary</item>
-        <item name="buttonColor">@android:color/white</item>
+        <item name="buttonTextColor">@macro/default_text_color_accent1</item>
+        <item name="buttonColor">@android:color/transparent</item>
         <item name="rippleColor">@color/text_button_ripple_color_list</item>
+        <item name="borderWidth">@dimen/button_outlined_border_width</item>
+        <item name="borderColor">?attr/globalOutlinedButtonBorderColor</item>
         <item name="buttonRaised">true</item>
     </style>
 
@@ -38,21 +41,23 @@
         <item name="android:paddingEnd">14dp</item>
         <item name="android:drawablePadding">10dp</item>
         <item name="android:layout_marginEnd">16dp</item>
-        <item name="android:textAppearance">@style/TextAppearance.TextSmall.Primary</item>
+        <item name="android:textAppearance">@style/TextAppearance.TextAccentMediumThick</item>
         <item name="buttonTextColor">?attr/globalFilledButtonTextColor</item>
         <item name="buttonColor">?attr/globalFilledButtonBgColor</item>
         <item name="rippleColor">@color/filled_button_ripple_color</item>
         <item name="buttonRaised">true</item>
     </style>
     <style name="CreatorFollowReverseButtonToolbar" parent="ButtonCompatBase">
-        <item name="android:paddingStart">12dp</item>
-        <item name="android:paddingEnd">10dp</item>
+        <item name="android:paddingStart">6dp</item>
+        <item name="android:paddingEnd">14dp</item>
         <item name="android:drawablePadding">5dp</item>
         <item name="android:layout_marginEnd">16dp</item>
-        <item name="android:textAppearance">@style/TextAppearance.TextSmall.Primary</item>
-        <item name="buttonTextColor">@macro/default_text_color_secondary</item>
-        <item name="buttonColor">@android:color/white</item>
+        <item name="android:textAppearance">@style/TextAppearance.TextAccentMediumThick</item>
+        <item name="buttonTextColor">@macro/default_text_color_accent1</item>
+        <item name="buttonColor">@android:color/transparent</item>
         <item name="rippleColor">@color/text_button_ripple_color_list</item>
+        <item name="borderWidth">@dimen/button_outlined_border_width</item>
+        <item name="borderColor">?attr/globalOutlinedButtonBorderColor</item>
         <item name="buttonRaised">true</item>
     </style>
 </resources>
diff --git a/chrome/browser/creator/android/java/src/org/chromium/chrome/browser/creator/CreatorCoordinator.java b/chrome/browser/creator/android/java/src/org/chromium/chrome/browser/creator/CreatorCoordinator.java
index 57c00a9..1cf9325c 100644
--- a/chrome/browser/creator/android/java/src/org/chromium/chrome/browser/creator/CreatorCoordinator.java
+++ b/chrome/browser/creator/android/java/src/org/chromium/chrome/browser/creator/CreatorCoordinator.java
@@ -5,6 +5,8 @@
 package org.chromium.chrome.browser.creator;
 
 import android.app.Activity;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -16,6 +18,7 @@
 
 import org.chromium.base.Callback;
 import org.chromium.base.supplier.Supplier;
+import org.chromium.base.supplier.UnownedUserDataSupplier;
 import org.chromium.chrome.browser.feed.FeedActionDelegate;
 import org.chromium.chrome.browser.feed.FeedAutoplaySettingsDelegate;
 import org.chromium.chrome.browser.feed.FeedContentFirstLoadWatcher;
@@ -32,21 +35,35 @@
 import org.chromium.chrome.browser.feedback.HelpAndFeedbackLauncher;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.share.ShareDelegate;
+import org.chromium.chrome.browser.ui.favicon.FaviconHelper;
+import org.chromium.chrome.browser.ui.favicon.FaviconUtils;
 import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
 import org.chromium.chrome.browser.xsurface.FeedLaunchReliabilityLogger;
 import org.chromium.chrome.browser.xsurface.HybridListRenderer;
 import org.chromium.chrome.browser.xsurface.ProcessScope;
 import org.chromium.chrome.browser.xsurface.SurfaceScope;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetContent;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetController.StateChangeReason;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetControllerFactory;
+import org.chromium.components.browser_ui.bottomsheet.EmptyBottomSheetObserver;
 import org.chromium.components.browser_ui.styles.SemanticColorUtils;
+import org.chromium.components.browser_ui.widget.RoundedIconGenerator;
 import org.chromium.components.browser_ui.widget.scrim.ScrimCoordinator;
+import org.chromium.components.embedder_support.view.ContentView;
+import org.chromium.components.version_info.VersionInfo;
+import org.chromium.content_public.browser.LoadUrlParams;
+import org.chromium.content_public.browser.WebContents;
 import org.chromium.ui.KeyboardVisibilityDelegate;
+import org.chromium.ui.UiUtils;
+import org.chromium.ui.base.IntentRequestTracker;
+import org.chromium.ui.base.ViewAndroidDelegate;
 import org.chromium.ui.base.WindowAndroid;
 import org.chromium.ui.modelutil.PropertyKey;
 import org.chromium.ui.modelutil.PropertyModel;
 import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
 import org.chromium.ui.util.ColorUtils;
+import org.chromium.url.GURL;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -55,10 +72,12 @@
  * Sets up the Coordinator for Cormorant Creator surface.  It is based on the doc at
  * https://chromium.googlesource.com/chromium/src/+/HEAD/docs/ui/android/mvc_simple_list_tutorial.md
  */
-public class CreatorCoordinator
-        implements FeedAutoplaySettingsDelegate, FeedContentFirstLoadWatcher {
+public class CreatorCoordinator implements FeedAutoplaySettingsDelegate,
+                                           FeedContentFirstLoadWatcher,
+                                           View.OnLayoutChangeListener {
     private final ViewGroup mCreatorViewGroup;
     private CreatorMediator mMediator;
+    private CreatorTabMediator mTabMediator;
     private Activity mActivity;
     private NtpListContentManager mContentManager;
     private RecyclerView mRecyclerView;
@@ -87,10 +106,37 @@
     private String mUrl;
     private int mHeaderCount;
 
+    private EmptyBottomSheetObserver mSheetObserver;
+    private ContentView mContentView;
+    private WebContents mWebContents;
+    private int mCurrentMaxViewHeight;
+    private CreatorTabSheetContent mSheetContent;
+    private boolean mPeeked;
+    private boolean mFullyOpened;
+    private WebContentsCreator mCreatorWebContents;
+    private NewTabCreator mCreatorOpenTab;
+    private final UnownedUserDataSupplier<ShareDelegate> mBottomsheetShareDelegateSupplier;
+
     private static final String CREATOR_PROFILE_ID = "CreatorProfileView";
 
+    /**
+     * The constructor for the CreatorCoordinator.
+     * @param activity The Creator Activity this is a part of.
+     * @param webFeedId The ID that is is used to create the feed.
+     * @param snackbarManager the snackbarManager that is used for the feed.
+     * @param windowAndroid the window needed by the feed
+     * @param profile The Profile of the user.
+     * @param title The title used by the creator profile.
+     * @param url the url used by the creator profile.
+     * @param creatorWebContents the interface to generate webcontents for the bottomsheet.
+     * @param creatorOpenTab the interface to open urls in a new tab, used by the bottomsheet.
+     * @param bottomsheetShareDelegateSupplier an empty share delegate supplier, used by the
+     *         bottomsheet.
+     */
     public CreatorCoordinator(Activity activity, byte[] webFeedId, SnackbarManager snackbarManager,
-            WindowAndroid windowAndroid, Profile profile, String title, String url) {
+            WindowAndroid windowAndroid, Profile profile, String title, String url,
+            WebContentsCreator creatorWebContents, NewTabCreator creatorOpenTab,
+            UnownedUserDataSupplier<ShareDelegate> bottomsheetShareDelegateSupplier) {
         mActivity = activity;
         mWebFeedId = webFeedId;
         mProfile = profile;
@@ -99,6 +145,9 @@
         mTitle = title;
         mUrl = url;
         mRecyclerView = setUpView();
+        mCreatorWebContents = creatorWebContents;
+        mCreatorOpenTab = creatorOpenTab;
+        mBottomsheetShareDelegateSupplier = bottomsheetShareDelegateSupplier;
 
         mProfileView =
                 (View) LayoutInflater.from(mActivity).inflate(R.layout.creator_profile, null);
@@ -260,4 +309,184 @@
     public void launchAutoplaySettings() {}
     @Override
     public void nonNativeContentLoaded(@StreamKind int kind) {}
+
+    /**
+     * Entry point for preview tab flow. This will create an creator tab and show it in the
+     * bottom sheet.
+     * @param url The URL to be shown.
+     */
+    public void requestOpenSheet(GURL url) {
+        if (mTabMediator == null) {
+            float topControlsHeight =
+                    mActivity.getResources().getDimensionPixelSize(R.dimen.toolbar_height_no_shadow)
+                    / mWindowAndroid.getDisplay().getDipScale();
+            mTabMediator = new CreatorTabMediator(
+                    mBottomSheetController, new FaviconLoader(mActivity), (int) topControlsHeight);
+        }
+        if (mWebContents == null) {
+            assert mSheetContent == null;
+            createWebContents();
+            mSheetObserver = new EmptyBottomSheetObserver() {
+                @Override
+                public void onSheetContentChanged(BottomSheetContent newContent) {
+                    if (newContent != mSheetContent) {
+                        mPeeked = false;
+                        destroyWebContents();
+                    }
+                }
+
+                @Override
+                public void onSheetStateChanged(int newState, int reason) {
+                    if (mSheetContent == null) return;
+                    switch (newState) {
+                        case BottomSheetController.SheetState.PEEK:
+                            if (!mPeeked) {
+                                mPeeked = true;
+                            }
+                            break;
+                        case BottomSheetController.SheetState.FULL:
+                            if (!mFullyOpened) {
+                                mFullyOpened = true;
+                            }
+                            break;
+                    }
+                }
+
+                @Override
+                public void onSheetOffsetChanged(float heightFraction, float offsetPx) {
+                    if (mSheetContent == null) return;
+                    mSheetContent.showOpenInNewTabButton(heightFraction);
+                }
+            };
+            mBottomSheetController.addObserver(mSheetObserver);
+            IntentRequestTracker intentRequestTracker = mWindowAndroid.getIntentRequestTracker();
+            assert intentRequestTracker
+                    != null : "ActivityWindowAndroid must have a IntentRequestTracker.";
+            mSheetContent = new CreatorTabSheetContent(mActivity, this::openInNewTab,
+                    this::onToolbarClick, this::close, getMaxViewHeight(), intentRequestTracker,
+                    mBottomsheetShareDelegateSupplier);
+            mTabMediator.init(mWebContents, mContentView, mSheetContent, mProfile);
+            mLayoutView.addOnLayoutChangeListener(this);
+        }
+
+        mPeeked = false;
+        mFullyOpened = false;
+        mTabMediator.requestShowContent(url, mTitle);
+    }
+
+    @Override
+    public void onLayoutChange(View view, int left, int top, int right, int bottom, int oldLeft,
+            int oldTop, int oldRight, int oldBottom) {
+        if (mSheetContent == null) return;
+
+        // It may not be possible to update the content height when the actual height changes
+        // due to the current tab not being ready yet. Try it later again when the tab
+        // (hence MaxViewHeight) becomes valid.
+        int maxViewHeight = getMaxViewHeight();
+        if (maxViewHeight == 0 || mCurrentMaxViewHeight == maxViewHeight) return;
+        mSheetContent.updateContentHeight(maxViewHeight);
+        mCurrentMaxViewHeight = maxViewHeight;
+    }
+
+    /** @return The maximum base view height for sheet content view. */
+    private int getMaxViewHeight() {
+        return mCreatorViewGroup.getHeight();
+    }
+
+    /**
+     * Close the bottomsheet tab.
+     */
+    public void close() {
+        mBottomSheetController.hideContent(mSheetContent, /* animate= */ true);
+    }
+
+    private void openInNewTab() {
+        mBottomSheetController.hideContent(
+                mSheetContent, /* animate= */ true, StateChangeReason.PROMOTE_TAB);
+        mCreatorOpenTab.createNewTab(new LoadUrlParams(mUrl));
+    }
+
+    private void onToolbarClick() {
+        int state = mBottomSheetController.getSheetState();
+        if (state == BottomSheetController.SheetState.PEEK) {
+            mBottomSheetController.expandSheet();
+        } else if (state == BottomSheetController.SheetState.FULL) {
+            mBottomSheetController.collapseSheet(true);
+        }
+    }
+    private void createWebContents() {
+        assert mWebContents == null;
+
+        // Creates an initially hidden WebContents which gets shown when the panel is opened.
+        mWebContents = mCreatorWebContents.createWebContents();
+
+        mContentView = ContentView.createContentView(
+                mActivity, null /* eventOffsetHandler */, mWebContents);
+
+        mWebContents.initialize(VersionInfo.getProductVersion(),
+                ViewAndroidDelegate.createBasicDelegate(mContentView), mContentView, mWindowAndroid,
+                WebContents.createDefaultInternalsHolder());
+    }
+
+    private void destroyWebContents() {
+        mSheetContent = null; // Will be destroyed by BottomSheet controller.
+
+        mPeeked = false;
+        mFullyOpened = false;
+
+        if (mWebContents != null) {
+            mWebContents.destroy();
+            mWebContents = null;
+            mContentView = null;
+        }
+
+        if (mMediator != null) mTabMediator.destroyContent();
+
+        mLayoutView.removeOnLayoutChangeListener(this);
+        if (mSheetObserver != null) mBottomSheetController.removeObserver(mSheetObserver);
+    }
+    /**
+     * Helper class to generate a favicon for a given URL and resize it to the desired dimensions
+     * for displaying it on the image view.
+     */
+    static class FaviconLoader {
+        private final Context mContext;
+        private final FaviconHelper mFaviconHelper;
+        private final RoundedIconGenerator mIconGenerator;
+        private final int mFaviconSize;
+
+        /** Constructor. */
+        public FaviconLoader(Context context) {
+            mContext = context;
+            mFaviconHelper = new FaviconHelper();
+            mIconGenerator = FaviconUtils.createCircularIconGenerator(mContext);
+            mFaviconSize =
+                    mContext.getResources().getDimensionPixelSize(R.dimen.preview_tab_favicon_size);
+        }
+
+        /**
+         * Generates a favicon for a given URL. If no favicon was could be found or generated from
+         * the URL, a default favicon will be shown.
+         * @param url The URL for which favicon is to be generated.
+         * @param callback The callback to be invoked to display the final image.
+         * @param profile The profile for which favicon service is used.
+         */
+        public void loadFavicon(final GURL url, Callback<Drawable> callback, Profile profile) {
+            assert profile != null;
+            FaviconHelper.FaviconImageCallback imageCallback = (bitmap, iconUrl) -> {
+                Drawable drawable;
+                if (bitmap != null) {
+                    drawable = FaviconUtils.createRoundedBitmapDrawable(
+                            mContext.getResources(), bitmap);
+                } else {
+                    drawable = UiUtils.getTintedDrawable(mContext, R.drawable.ic_globe_24dp,
+                            R.color.default_icon_color_tint_list);
+                }
+
+                callback.onResult(drawable);
+            };
+
+            mFaviconHelper.getLocalFaviconImageForURL(profile, url, mFaviconSize, imageCallback);
+        }
+    }
 }
diff --git a/chrome/browser/creator/android/java/src/org/chromium/chrome/browser/creator/CreatorCoordinatorTest.java b/chrome/browser/creator/android/java/src/org/chromium/chrome/browser/creator/CreatorCoordinatorTest.java
index daf0f5d..51ee0d3 100644
--- a/chrome/browser/creator/android/java/src/org/chromium/chrome/browser/creator/CreatorCoordinatorTest.java
+++ b/chrome/browser/creator/android/java/src/org/chromium/chrome/browser/creator/CreatorCoordinatorTest.java
@@ -18,6 +18,7 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import org.chromium.base.supplier.UnownedUserDataSupplier;
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.base.test.util.JniMocker;
 import org.chromium.chrome.browser.creator.test.R;
@@ -28,6 +29,7 @@
 import org.chromium.chrome.browser.feed.FeedStreamJni;
 import org.chromium.chrome.browser.feed.webfeed.WebFeedBridge;
 import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.browser.share.ShareDelegate;
 import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
 import org.chromium.ui.base.TestActivity;
 import org.chromium.ui.base.WindowAndroid;
@@ -53,6 +55,12 @@
     private SnackbarManager mSnackbarManager;
     @Mock
     private Profile mProfile;
+    @Mock
+    private WebContentsCreator mCreatorWebContents;
+    @Mock
+    private NewTabCreator mCreatorOpenTab;
+    @Mock
+    private UnownedUserDataSupplier<ShareDelegate> mShareDelegateSupplier;
     private final String mTitle = "Example";
     private final String mUrl = "example.com";
 
@@ -78,8 +86,9 @@
 
         mActivityScenarioRule.getScenario().onActivity(activity -> mActivity = activity);
 
-        mCreatorCoordinator = new CreatorCoordinator(
-                mActivity, sWebFeedId, mSnackbarManager, mWindowAndroid, mProfile, mTitle, mUrl);
+        mCreatorCoordinator = new CreatorCoordinator(mActivity, sWebFeedId, mSnackbarManager,
+                mWindowAndroid, mProfile, mTitle, mUrl, mCreatorWebContents, mCreatorOpenTab,
+                mShareDelegateSupplier);
     }
 
     @Test
diff --git a/chrome/browser/creator/android/java/src/org/chromium/chrome/browser/creator/CreatorMediatorTest.java b/chrome/browser/creator/android/java/src/org/chromium/chrome/browser/creator/CreatorMediatorTest.java
index 6e98933..e1bff95 100644
--- a/chrome/browser/creator/android/java/src/org/chromium/chrome/browser/creator/CreatorMediatorTest.java
+++ b/chrome/browser/creator/android/java/src/org/chromium/chrome/browser/creator/CreatorMediatorTest.java
@@ -15,6 +15,7 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import org.chromium.base.supplier.UnownedUserDataSupplier;
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.base.test.util.JniMocker;
 import org.chromium.chrome.browser.feed.FeedReliabilityLoggingBridge;
@@ -24,6 +25,7 @@
 import org.chromium.chrome.browser.feed.FeedStreamJni;
 import org.chromium.chrome.browser.feed.webfeed.WebFeedBridge;
 import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.browser.share.ShareDelegate;
 import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
 import org.chromium.ui.base.TestActivity;
 import org.chromium.ui.base.WindowAndroid;
@@ -50,6 +52,13 @@
     private SnackbarManager mSnackbarManager;
     @Mock
     private Profile mProfile;
+    @Mock
+    private WebContentsCreator mCreatorWebContents;
+    @Mock
+    private NewTabCreator mCreatorOpenTab;
+    @Mock
+    private UnownedUserDataSupplier<ShareDelegate> mShareDelegateSupplier;
+
     private final String mTitle = "Example";
     private final String mUrl = "example.com";
 
@@ -76,8 +85,9 @@
                 mFeedReliabilityLoggingBridgeJniMock);
 
         mActivityScenarioRule.getScenario().onActivity(activity -> mActivity = activity);
-        mCreatorCoordinator = new CreatorCoordinator(
-                mActivity, sWebFeedId, mSnackbarManager, mWindowAndroid, mProfile, mTitle, mUrl);
+        mCreatorCoordinator = new CreatorCoordinator(mActivity, sWebFeedId, mSnackbarManager,
+                mWindowAndroid, mProfile, mTitle, mUrl, mCreatorWebContents, mCreatorOpenTab,
+                mShareDelegateSupplier);
         mCreatorModel = mCreatorCoordinator.getCreatorModel();
 
         mCreatorMediator = new CreatorMediator(mActivity, mCreatorModel);
diff --git a/chrome/browser/creator/android/java/src/org/chromium/chrome/browser/creator/CreatorTabMediator.java b/chrome/browser/creator/android/java/src/org/chromium/chrome/browser/creator/CreatorTabMediator.java
new file mode 100644
index 0000000..3c49be3
--- /dev/null
+++ b/chrome/browser/creator/android/java/src/org/chromium/chrome/browser/creator/CreatorTabMediator.java
@@ -0,0 +1,232 @@
+// 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.creator;
+
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+
+import androidx.annotation.DrawableRes;
+
+import org.chromium.base.ContextUtils;
+import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
+import org.chromium.components.embedder_support.delegate.WebContentsDelegateAndroid;
+import org.chromium.components.embedder_support.util.UrlUtilities;
+import org.chromium.components.embedder_support.view.ContentView;
+import org.chromium.components.security_state.ConnectionSecurityLevel;
+import org.chromium.components.security_state.SecurityStateModel;
+import org.chromium.content_public.browser.LoadUrlParams;
+import org.chromium.content_public.browser.NavigationHandle;
+import org.chromium.content_public.browser.WebContents;
+import org.chromium.content_public.browser.WebContentsObserver;
+import org.chromium.content_public.common.ResourceRequestBody;
+import org.chromium.ui.widget.Toast;
+import org.chromium.url.GURL;
+
+/**
+ * Mediator class for preview tab, responsible for communicating with other objects.
+ * This is based on the implementation of bottombar/ephemeraltab/EphemeralTabMediator.java.
+ */
+public class CreatorTabMediator {
+    /** The delay (four video frames) after which the hide progress will be hidden. */
+    private static final long HIDE_PROGRESS_BAR_DELAY_MS = (1000 / 60) * 4;
+
+    private final BottomSheetController mBottomSheetController;
+    private final CreatorCoordinator.FaviconLoader mFaviconLoader;
+    private final int mTopControlsHeightDp;
+
+    private WebContents mWebContents;
+    private CreatorTabSheetContent mSheetContent;
+    private WebContentsObserver mWebContentsObserver;
+    private WebContentsDelegateAndroid mWebContentsDelegate;
+    private Profile mProfile;
+
+    /**
+     * Constructor.
+     */
+    public CreatorTabMediator(BottomSheetController bottomSheetController,
+            CreatorCoordinator.FaviconLoader faviconLoader, int topControlsHeightDp) {
+        mBottomSheetController = bottomSheetController;
+        mFaviconLoader = faviconLoader;
+        mTopControlsHeightDp = topControlsHeightDp;
+    }
+
+    /**
+     * Initializes various objects for a new tab.
+     */
+    void init(WebContents webContents, ContentView contentView, CreatorTabSheetContent sheetContent,
+            Profile profile) {
+        // Ensures that initialization is performed only when a new tab is opened.
+        assert mProfile == null && mWebContentsObserver == null && mWebContentsDelegate == null;
+
+        mProfile = profile;
+        mWebContents = webContents;
+        mSheetContent = sheetContent;
+        createWebContentsObserver();
+        createWebContentsDelegate();
+        mSheetContent.attachWebContents(mWebContents, contentView, mWebContentsDelegate);
+    }
+
+    /**
+     * Loads a new URL into the tab and makes it visible.
+     */
+    void requestShowContent(GURL url, String title) {
+        loadUrl(url);
+        mSheetContent.updateTitle(title);
+        mBottomSheetController.requestShowContent(mSheetContent, true);
+    }
+
+    private void loadUrl(GURL url) {
+        mWebContents.getNavigationController().loadUrl(new LoadUrlParams(url.getSpec()));
+    }
+
+    private void createWebContentsObserver() {
+        assert mWebContentsObserver == null;
+        mWebContentsObserver = new WebContentsObserver(mWebContents) {
+            /** Whether the currently loaded page is an error (interstitial) page. */
+            private boolean mIsOnErrorPage;
+
+            private GURL mCurrentUrl;
+
+            @Override
+            public void loadProgressChanged(float progress) {
+                if (mSheetContent != null) mSheetContent.setProgress(progress);
+            }
+
+            @Override
+            public void didStartNavigationInPrimaryMainFrame(NavigationHandle navigation) {
+                if (!navigation.isSameDocument()) {
+                    GURL url = navigation.getUrl();
+                    if (url.equals(mCurrentUrl)) return;
+
+                    // The link Back to Safety on the interstitial page will go to the previous
+                    // page. If there is no previous page, i.e. previous page is NTP, the preview
+                    // tab will be closed.
+                    if (mIsOnErrorPage && UrlUtilities.isNTPUrl(url)) {
+                        mBottomSheetController.hideContent(mSheetContent, /* animate= */ true);
+                        mCurrentUrl = null;
+                        return;
+                    }
+
+                    mCurrentUrl = url;
+                    mFaviconLoader.loadFavicon(
+                            url, (drawable) -> onFaviconAvailable(drawable), mProfile);
+                }
+            }
+
+            @Override
+            public void didStartNavigationNoop(NavigationHandle navigation) {
+                if (!navigation.isInPrimaryMainFrame()) return;
+            }
+
+            @Override
+            public void titleWasSet(String title) {
+                mSheetContent.updateTitle(title);
+            }
+
+            @Override
+            public void didFinishNavigationInPrimaryMainFrame(NavigationHandle navigation) {
+                if (navigation.hasCommitted()) {
+                    mIsOnErrorPage = navigation.isErrorPage();
+                    mSheetContent.updateURL(mWebContents.get().getVisibleUrl());
+                } else {
+                    // Not viewable contents such as download. Show a toast and close the tab.
+                    Toast.makeText(ContextUtils.getApplicationContext(),
+                                 "test" /*R.string.creator_tab_sheet_not_viewable*/,
+                                 Toast.LENGTH_SHORT)
+                            .show();
+                    mBottomSheetController.hideContent(mSheetContent, /* animate= */ true);
+                }
+            }
+
+            @Override
+            public void didFinishNavigationNoop(NavigationHandle navigation) {
+                if (navigation.isInPrimaryMainFrame()) return;
+            }
+        };
+    }
+
+    private void onFaviconAvailable(Drawable drawable) {
+        if (mSheetContent != null) mSheetContent.startFaviconAnimation(drawable);
+    }
+
+    private void createWebContentsDelegate() {
+        assert mWebContentsDelegate == null;
+        mWebContentsDelegate = new WebContentsDelegateAndroid() {
+            @Override
+            public void visibleSSLStateChanged() {
+                if (mSheetContent == null) return;
+                int securityLevel = SecurityStateModel.getSecurityLevelForWebContents(mWebContents);
+                mSheetContent.setSecurityIcon(getSecurityIconResource(securityLevel));
+                mSheetContent.updateURL(mWebContents.getVisibleUrl());
+            }
+
+            @Override
+            public void openNewTab(GURL url, String extraHeaders, ResourceRequestBody postData,
+                    int disposition, boolean isRendererInitiated) {
+                // We never open a separate tab when navigating in a preview tab.
+                loadUrl(url);
+            }
+
+            @Override
+            public boolean shouldCreateWebContents(GURL targetUrl) {
+                loadUrl(targetUrl);
+                return false;
+            }
+
+            @Override
+            public void loadingStateChanged(boolean shouldShowLoadingUI) {
+                boolean isLoading = mWebContents != null && mWebContents.isLoading();
+                if (isLoading) {
+                    if (mSheetContent == null) return;
+                    mSheetContent.setProgress(0);
+                    mSheetContent.setProgressVisible(true);
+                } else {
+                    // Hides the Progress Bar after a delay to make sure it is rendered for at least
+                    // a few frames, otherwise its completion won't be visually noticeable.
+                    new Handler().postDelayed(() -> {
+                        if (mSheetContent != null) mSheetContent.setProgressVisible(false);
+                    }, HIDE_PROGRESS_BAR_DELAY_MS);
+                }
+            }
+
+            @Override
+            public int getTopControlsHeight() {
+                return mTopControlsHeightDp;
+            }
+        };
+    }
+
+    @DrawableRes
+    private static int getSecurityIconResource(@ConnectionSecurityLevel int securityLevel) {
+        switch (securityLevel) {
+            case ConnectionSecurityLevel.NONE:
+            case ConnectionSecurityLevel.WARNING:
+                return R.drawable.omnibox_info;
+            case ConnectionSecurityLevel.DANGEROUS:
+                return R.drawable.omnibox_not_secure_warning;
+            case ConnectionSecurityLevel.SECURE_WITH_POLICY_INSTALLED_CERT:
+            case ConnectionSecurityLevel.SECURE:
+                return R.drawable.omnibox_https_valid;
+            default:
+                assert false;
+        }
+        return 0;
+    }
+
+    /**
+     * Destroys the objects used for the current preview tab.
+     */
+    void destroyContent() {
+        if (mWebContentsObserver != null) {
+            mWebContentsObserver.destroy();
+            mWebContentsObserver = null;
+        }
+        mWebContentsDelegate = null;
+        mWebContents = null;
+        mSheetContent = null;
+        mProfile = null;
+    }
+}
diff --git a/chrome/browser/creator/android/java/src/org/chromium/chrome/browser/creator/CreatorTabSheetContent.java b/chrome/browser/creator/android/java/src/org/chromium/chrome/browser/creator/CreatorTabSheetContent.java
new file mode 100644
index 0000000..f0fb3ed
--- /dev/null
+++ b/chrome/browser/creator/android/java/src/org/chromium/chrome/browser/creator/CreatorTabSheetContent.java
@@ -0,0 +1,326 @@
+// 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.creator;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import androidx.annotation.DrawableRes;
+import androidx.annotation.Nullable;
+
+import org.chromium.base.supplier.ObservableSupplierImpl;
+import org.chromium.base.supplier.UnownedUserDataSupplier;
+import org.chromium.chrome.browser.share.ShareDelegate;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetContent;
+import org.chromium.components.browser_ui.widget.ChromeTransitionDrawable;
+import org.chromium.components.browser_ui.widget.FadingShadow;
+import org.chromium.components.browser_ui.widget.FadingShadowView;
+import org.chromium.components.embedder_support.delegate.WebContentsDelegateAndroid;
+import org.chromium.components.embedder_support.view.ContentView;
+import org.chromium.components.thinwebview.ThinWebView;
+import org.chromium.components.thinwebview.ThinWebViewConstraints;
+import org.chromium.components.thinwebview.ThinWebViewFactory;
+import org.chromium.components.url_formatter.SchemeDisplay;
+import org.chromium.components.url_formatter.UrlFormatter;
+import org.chromium.content_public.browser.RenderCoordinates;
+import org.chromium.content_public.browser.WebContents;
+import org.chromium.ui.base.IntentRequestTracker;
+import org.chromium.ui.base.ViewUtils;
+import org.chromium.ui.base.WindowAndroid;
+import org.chromium.url.GURL;
+
+/**
+ * Represents creator tab content and the toolbar, which can be included inside the bottom sheet.
+ * This is based on the implementation of bottombar/ephemeraltab/EphemeralTabSheetContent.java.
+ */
+public class CreatorTabSheetContent implements BottomSheetContent {
+    /**
+     * The base duration of the settling animation of the sheet. 218 ms is a spec for material
+     * design (this is the minimum time a user is guaranteed to pay attention to something).
+     */
+    private static final int BASE_ANIMATION_DURATION_MS = 218;
+
+    private static final float PEEK_TOOLBAR_HEIGHT_MULTIPLE = 2.f;
+
+    /** Ratio of the height when in full mode. Used in half-open variation. */
+    private static final float FULL_HEIGHT_RATIO = 0.9f;
+
+    private final Context mContext;
+    private final Runnable mOpenNewTabCallback;
+    private final Runnable mToolbarClickCallback;
+    private final Runnable mCloseButtonCallback;
+    private final int mToolbarHeightPx;
+    private final UnownedUserDataSupplier<ShareDelegate> mShareDelegateSupplier;
+    private final ObservableSupplierImpl<Boolean> mBackPressStateChangedSupplier =
+            new ObservableSupplierImpl<>();
+
+    private ViewGroup mToolbarView;
+    private ViewGroup mSheetContentView;
+
+    private WebContents mWebContents;
+    private ContentView mWebContentView;
+    private ThinWebView mThinWebView;
+    private FadingShadowView mShadow;
+    private Drawable mCurrentFavicon;
+    private ImageView mFaviconView;
+
+    /**
+     * Constructor.
+     * @param context An Android context.
+     * @param openNewTabCallback Callback invoked to open a new tab.
+     * @param toolbarClickCallback Callback invoked when user clicks on the toolbar.
+     * @param closeButtonCallback Callback invoked when user clicks on the close button.
+     * @param maxViewHeight The height of the sheet in full height position.
+     * @param intentRequestTracker The {@link IntentRequestTracker} of the current activity.
+     */
+    public CreatorTabSheetContent(Context context, Runnable openNewTabCallback,
+            Runnable toolbarClickCallback, Runnable closeButtonCallback, int maxViewHeight,
+            IntentRequestTracker intentRequestTracker,
+            UnownedUserDataSupplier<ShareDelegate> shareDelegateSupplier) {
+        mContext = context;
+        mOpenNewTabCallback = openNewTabCallback;
+        mToolbarClickCallback = toolbarClickCallback;
+        mCloseButtonCallback = closeButtonCallback;
+        mToolbarHeightPx =
+                mContext.getResources().getDimensionPixelSize(R.dimen.creator_tab_toolbar_height);
+
+        createThinWebView((int) (maxViewHeight * FULL_HEIGHT_RATIO), intentRequestTracker);
+        createToolbarView();
+
+        mBackPressStateChangedSupplier.set(true);
+        mShareDelegateSupplier = shareDelegateSupplier;
+    }
+
+    /**
+     * Add web contents to the sheet.
+     * @param webContents The {@link WebContents} to be displayed.
+     * @param contentView The {@link ContentView} associated with the web contents.
+     * @param delegate The {@link WebContentsDelegateAndroid} that handles requests on WebContents.
+     */
+    public void attachWebContents(
+            WebContents webContents, ContentView contentView, WebContentsDelegateAndroid delegate) {
+        mWebContents = webContents;
+        mWebContentView = contentView;
+        if (mWebContentView.getParent() != null) {
+            ((ViewGroup) mWebContentView.getParent()).removeView(mWebContentView);
+        }
+        mThinWebView.attachWebContents(mWebContents, mWebContentView, delegate);
+
+        // Initialize the supplier of {@link ShareDelegate} for the WindowAndroid used by
+        // ThinWebView.  The {@link ShareDelegate} itself is not set by design in order to leave
+        // the share feature disabled on Preview Tab.
+        WindowAndroid window = mWebContents.getTopLevelNativeWindow();
+        assert window != null;
+        mShareDelegateSupplier.attach(window.getUnownedUserDataHost());
+    }
+
+    /**
+     * Create a ThinWebView, add it to the view hierarchy, which represents the contents of the
+     * bottom sheet.
+     */
+    private void createThinWebView(int maxSheetHeight, IntentRequestTracker intentRequestTracker) {
+        mThinWebView = ThinWebViewFactory.create(
+                mContext, new ThinWebViewConstraints(), intentRequestTracker);
+
+        mSheetContentView = new FrameLayout(mContext);
+        mThinWebView.getView().setLayoutParams(new FrameLayout.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT, maxSheetHeight - mToolbarHeightPx));
+        mSheetContentView.addView(mThinWebView.getView());
+
+        mSheetContentView.setPadding(0, mToolbarHeightPx, 0, 0);
+    }
+
+    private void createToolbarView() {
+        mToolbarView = (ViewGroup) LayoutInflater.from(mContext).inflate(
+                R.layout.creator_bottomsheet_toolbar, null);
+        mShadow = mToolbarView.findViewById(R.id.shadow);
+        mShadow.init(mContext.getColor(R.color.toolbar_shadow_color), FadingShadow.POSITION_TOP);
+        ImageView openInNewTabButton = mToolbarView.findViewById(R.id.open_in_new_tab);
+        openInNewTabButton.setOnClickListener(view -> mOpenNewTabCallback.run());
+
+        View toolbar = mToolbarView.findViewById(R.id.toolbar);
+        toolbar.setOnClickListener(view -> mToolbarClickCallback.run());
+
+        View closeButton = mToolbarView.findViewById(R.id.close);
+        closeButton.setOnClickListener(view -> mCloseButtonCallback.run());
+
+        mFaviconView = mToolbarView.findViewById(R.id.favicon);
+        mCurrentFavicon = mFaviconView.getDrawable();
+    }
+
+    /**
+     * Resizes the thin webview as per the given new max height.
+     * @param maxViewHeight The maximum height of the view.
+     */
+    void updateContentHeight(int maxViewHeight) {
+        if (maxViewHeight == 0) return;
+        ViewGroup.LayoutParams layoutParams = mThinWebView.getView().getLayoutParams();
+
+        // This should never be more than the tab height for it to function correctly.
+        // We scale it by |FULL_HEIGHT_RATIO| to make the size equal to that of
+        // ThinWebView and so it can leave a portion of the page below it visible.
+        layoutParams.height = (int) (maxViewHeight * FULL_HEIGHT_RATIO) - mToolbarHeightPx;
+        ViewUtils.requestLayout(mSheetContentView, "CreatorTabSheetContent.updateContentHeight");
+    }
+
+    /** Method to be called to start the favicon anmiation. */
+    public void startFaviconAnimation(Drawable favicon) {
+        if (favicon == null) {
+            mCurrentFavicon = null;
+            mFaviconView.setImageDrawable(null);
+            return;
+        }
+
+        Drawable presentedDrawable = favicon;
+        if (mCurrentFavicon != null && !(mCurrentFavicon instanceof ChromeTransitionDrawable)) {
+            ChromeTransitionDrawable transitionDrawable =
+                    new ChromeTransitionDrawable(mCurrentFavicon, favicon);
+            transitionDrawable.setCrossFadeEnabled(true);
+            transitionDrawable.startTransition().setDuration(BASE_ANIMATION_DURATION_MS);
+            presentedDrawable = transitionDrawable;
+        }
+
+        mFaviconView.setImageDrawable(presentedDrawable);
+        mCurrentFavicon = favicon;
+    }
+
+    /** Sets the creator tab title text. */
+    public void updateTitle(String title) {
+        TextView toolbarText = mToolbarView.findViewById(R.id.title);
+        toolbarText.setText(title);
+    }
+
+    /** Sets the creator tab URL. */
+    public void updateURL(GURL url) {
+        TextView originView = mToolbarView.findViewById(R.id.origin);
+        originView.setText(
+                UrlFormatter.formatUrlForSecurityDisplay(url, SchemeDisplay.OMIT_HTTP_AND_HTTPS));
+    }
+
+    /** Sets the security icon. */
+    public void setSecurityIcon(@DrawableRes int resId) {
+        ImageView securityIcon = mToolbarView.findViewById(R.id.security_icon);
+        securityIcon.setImageResource(resId);
+    }
+
+    /** Sets the progress on the progress bar. */
+    public void setProgress(float progress) {
+        ProgressBar progressBar = mToolbarView.findViewById(R.id.progress_bar);
+        progressBar.setProgress(Math.round(progress * 100));
+    }
+
+    /** Called to show or hide the progress bar. */
+    public void setProgressVisible(boolean visible) {
+        ProgressBar progressBar = mToolbarView.findViewById(R.id.progress_bar);
+        progressBar.setVisibility(visible ? View.VISIBLE : View.GONE);
+    }
+
+    /**
+     * Called to show (with alpha) or hide the open in new tab button.
+     * @param fraction Alpha for the button when visible.
+     */
+    public void showOpenInNewTabButton(float fraction) {
+        View button = mToolbarView.findViewById(R.id.open_in_new_tab);
+        // Start showing the button about halfway toward the full state.
+        if (fraction <= 0.5f) {
+            if (button.getVisibility() != View.GONE) button.setVisibility(View.GONE);
+        } else {
+            if (button.getVisibility() != View.VISIBLE) button.setVisibility(View.VISIBLE);
+            button.setAlpha((fraction - 0.5f) * 2.0f);
+        }
+    }
+
+    @Override
+    public View getContentView() {
+        return mSheetContentView;
+    }
+
+    @Nullable
+    @Override
+    public View getToolbarView() {
+        return mToolbarView;
+    }
+
+    @Override
+    public int getVerticalScrollOffset() {
+        return mWebContents == null
+                ? 0
+                : RenderCoordinates.fromWebContents(mWebContents).getScrollYPixInt();
+    }
+
+    @Override
+    public void destroy() {
+        mThinWebView.destroy();
+        mShareDelegateSupplier.destroy();
+    }
+
+    @Override
+    public int getPriority() {
+        return BottomSheetContent.ContentPriority.HIGH;
+    }
+
+    @Override
+    public boolean swipeToDismissEnabled() {
+        return true;
+    }
+
+    @Override
+    public int getPeekHeight() {
+        return HeightMode.DISABLED;
+    }
+
+    @Override
+    public float getHalfHeightRatio() {
+        return HeightMode.DEFAULT;
+    }
+
+    @Override
+    public float getFullHeightRatio() {
+        return HeightMode.WRAP_CONTENT;
+    }
+
+    @Override
+    public boolean handleBackPress() {
+        mCloseButtonCallback.run();
+        return true;
+    }
+
+    @Override
+    public ObservableSupplierImpl<Boolean> getBackPressStateChangedSupplier() {
+        return mBackPressStateChangedSupplier;
+    }
+
+    @Override
+    public void onBackPressed() {
+        mCloseButtonCallback.run();
+    }
+
+    @Override
+    public int getSheetContentDescriptionStringId() {
+        return R.string.ephemeral_tab_sheet_description;
+    }
+
+    @Override
+    public int getSheetHalfHeightAccessibilityStringId() {
+        return R.string.ephemeral_tab_sheet_opened_half;
+    }
+
+    @Override
+    public int getSheetFullHeightAccessibilityStringId() {
+        return R.string.ephemeral_tab_sheet_opened_full;
+    }
+
+    @Override
+    public int getSheetClosedAccessibilityStringId() {
+        return R.string.ephemeral_tab_sheet_closed;
+    }
+}
diff --git a/chrome/browser/creator/android/java/src/org/chromium/chrome/browser/creator/NewTabCreator.java b/chrome/browser/creator/android/java/src/org/chromium/chrome/browser/creator/NewTabCreator.java
new file mode 100644
index 0000000..f533c68
--- /dev/null
+++ b/chrome/browser/creator/android/java/src/org/chromium/chrome/browser/creator/NewTabCreator.java
@@ -0,0 +1,16 @@
+// 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.creator;
+
+import org.chromium.content_public.browser.LoadUrlParams;
+
+/** Interface for opening URLS in a new tab.*/
+public interface NewTabCreator {
+    /**
+     * Creates a new tab with the given params.
+     * @param params the URL parameters needed to open the tab.
+     */
+    void createNewTab(LoadUrlParams params);
+}
diff --git a/chrome/browser/creator/android/java/src/org/chromium/chrome/browser/creator/WebContentsCreator.java b/chrome/browser/creator/android/java/src/org/chromium/chrome/browser/creator/WebContentsCreator.java
new file mode 100644
index 0000000..f319d0e
--- /dev/null
+++ b/chrome/browser/creator/android/java/src/org/chromium/chrome/browser/creator/WebContentsCreator.java
@@ -0,0 +1,13 @@
+// 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.creator;
+
+import org.chromium.content_public.browser.WebContents;
+
+/** Interface to create a web contents*/
+public interface WebContentsCreator {
+    // create a web contents;
+    WebContents createWebContents();
+}
diff --git a/chrome/browser/enterprise/connectors/device_trust/device_trust_browsertest.cc b/chrome/browser/enterprise/connectors/device_trust/device_trust_browsertest.cc
index 815c71d..e1a8332 100644
--- a/chrome/browser/enterprise/connectors/device_trust/device_trust_browsertest.cc
+++ b/chrome/browser/enterprise/connectors/device_trust/device_trust_browsertest.cc
@@ -356,17 +356,7 @@
   DeviceTrustAshBrowserTest() {
     auto mock_challenge_key =
         std::make_unique<ash::attestation::MockTpmChallengeKey>();
-    if (use_v2_header()) {
-      mock_challenge_key->EnableFake();
-    } else {
-      // It is not possible to test the real TPM during browser test, which we
-      // are dependent on to decide, whether we can build a response from the
-      // challenge or not. Thus, we are purposely failing here for tests with
-      // the old VA header
-      mock_challenge_key->EnableFakeError(
-          ash::attestation::TpmChallengeKeyResultCode::
-              kChallengeBadBase64Error);
-    }
+    mock_challenge_key->EnableFake();
     ash::attestation::TpmChallengeKeyFactory::SetForTesting(
         std::move(mock_challenge_key));
 
diff --git a/chrome/browser/extensions/api/downloads/downloads_api.cc b/chrome/browser/extensions/api/downloads/downloads_api.cc
index 2801288..be730d8 100644
--- a/chrome/browser/extensions/api/downloads/downloads_api.cc
+++ b/chrome/browser/extensions/api/downloads/downloads_api.cc
@@ -65,11 +65,13 @@
 #include "extensions/browser/extension_function_dispatcher.h"
 #include "extensions/browser/extension_prefs.h"
 #include "extensions/browser/warning_service.h"
+#include "extensions/common/mojom/event_dispatcher.mojom-forward.h"
 #include "extensions/common/permissions/permissions_data.h"
 #include "net/base/filename_util.h"
 #include "net/base/load_flags.h"
 #include "net/http/http_util.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 #include "ui/base/webui/web_ui_util.h"
 #include "ui/gfx/image/image_skia.h"
@@ -1004,8 +1006,8 @@
     Feature::Context target_context,
     const Extension* extension,
     const base::Value::Dict* listener_filter,
-    std::unique_ptr<base::Value::List>* event_args_out,
-    mojom::EventFilteringInfoPtr* event_filtering_info_out) {
+    absl::optional<base::Value::List>& event_args_out,
+    mojom::EventFilteringInfoPtr& event_filtering_info_out) {
   *any_determiners = true;
   base::Time installed =
       ExtensionPrefs::Get(browser_context)->GetLastUpdateTime(extension->id());
diff --git a/chrome/browser/extensions/api/mdns/mdns_api.cc b/chrome/browser/extensions/api/mdns/mdns_api.cc
index a565a31..407e556 100644
--- a/chrome/browser/extensions/api/mdns/mdns_api.cc
+++ b/chrome/browser/extensions/api/mdns/mdns_api.cc
@@ -216,7 +216,7 @@
     std::set<std::string>* extension_ids,
     ServiceTypeCounts* service_type_counts) {
   for (const auto& listener : GetEventListeners()) {
-    base::Value::Dict* filter = listener->filter();
+    const base::Value::Dict* filter = listener->filter();
 
     const std::string* service_type =
         filter->FindString(kEventFilterServiceTypeKey);
diff --git a/chrome/browser/extensions/api/mdns/mdns_api_unittest.cc b/chrome/browser/extensions/api/mdns/mdns_api_unittest.cc
index f1c442b..1642f9e 100644
--- a/chrome/browser/extensions/api/mdns/mdns_api_unittest.cc
+++ b/chrome/browser/extensions/api/mdns/mdns_api_unittest.cc
@@ -47,8 +47,8 @@
     const std::string& extension_id,
     const std::string& service_type,
     extensions::EventListenerMap::ListenerList* listener_list) {
-  auto filter = std::make_unique<base::Value::Dict>();
-  filter->Set(kEventFilterServiceTypeKey, service_type);
+  base::Value::Dict filter;
+  filter.Set(kEventFilterServiceTypeKey, service_type);
   listener_list->push_back(EventListener::ForExtension(
       kEventFilterServiceTypeKey, extension_id, nullptr, std::move(filter)));
 }
@@ -214,15 +214,16 @@
       std::string name,
       bool is_platform_app,
       std::string extension_id) {
-    base::DictionaryValue manifest;
-    manifest.SetStringKey(extensions::manifest_keys::kVersion, "1.0.0.0");
-    manifest.SetStringKey(extensions::manifest_keys::kName, name);
-    manifest.SetIntKey(extensions::manifest_keys::kManifestVersion, 2);
+    base::Value::Dict manifest;
+    manifest.Set(extensions::manifest_keys::kVersion, "1.0.0.0");
+    manifest.Set(extensions::manifest_keys::kName, name);
+    manifest.Set(extensions::manifest_keys::kManifestVersion, 2);
     if (is_platform_app) {
       // Setting app.background.page = "background.html" is sufficient to make
       // the extension type TYPE_PLATFORM_APP.
-      manifest.Set(extensions::manifest_keys::kPlatformAppBackgroundPage,
-                   std::make_unique<base::Value>("background.html"));
+      manifest.SetByDottedPath(
+          extensions::manifest_keys::kPlatformAppBackgroundPage,
+          "background.html");
     }
 
     std::string error;
diff --git a/chrome/browser/extensions/api/messaging/native_message_echo_host.cc b/chrome/browser/extensions/api/messaging/native_message_echo_host.cc
index 8851b86..58c9aff4 100644
--- a/chrome/browser/extensions/api/messaging/native_message_echo_host.cc
+++ b/chrome/browser/extensions/api/messaging/native_message_echo_host.cc
@@ -51,7 +51,7 @@
   } else if (request_string.find("bigMessageTest") != std::string::npos) {
     client_->CloseChannel(kHostInputOutputError);
   } else {
-    ProcessEcho(base::Value::AsDictionaryValue(request_value.value()));
+    ProcessEcho(request_value->GetDict());
   }
 }
 
@@ -60,11 +60,11 @@
   return base::SingleThreadTaskRunner::GetCurrentDefault();
 }
 
-void NativeMessageEchoHost::ProcessEcho(const base::DictionaryValue& request) {
-  base::DictionaryValue response;
-  response.SetIntKey("id", ++message_number_);
-  response.SetKey("echo", request.Clone());
-  response.SetStringKey("caller_url", kOrigins[0]);
+void NativeMessageEchoHost::ProcessEcho(const base::Value::Dict& request) {
+  base::Value::Dict response;
+  response.Set("id", ++message_number_);
+  response.Set("echo", request.Clone());
+  response.Set("caller_url", kOrigins[0]);
   std::string response_string;
   base::JSONWriter::Write(response, &response_string);
   client_->PostMessageFromNativeHost(response_string);
diff --git a/chrome/browser/extensions/api/messaging/native_message_echo_host.h b/chrome/browser/extensions/api/messaging/native_message_echo_host.h
index 8f8d101..6ea94c3 100644
--- a/chrome/browser/extensions/api/messaging/native_message_echo_host.h
+++ b/chrome/browser/extensions/api/messaging/native_message_echo_host.h
@@ -9,13 +9,9 @@
 #include <string>
 
 #include "base/memory/raw_ptr.h"
+#include "base/values.h"
 #include "extensions/browser/api/messaging/native_message_host.h"
 
-namespace base {
-class DictionaryValue;
-class SingleThreadTaskRunner;
-}  // namespace base
-
 namespace {
 class BrowserContext;
 }
@@ -47,7 +43,7 @@
   scoped_refptr<base::SingleThreadTaskRunner> task_runner() const override;
 
  private:
-  void ProcessEcho(const base::DictionaryValue& request);
+  void ProcessEcho(const base::Value::Dict& request);
 
   // Counter used to ensure message uniqueness for testing.
   int message_number_ = 0;
diff --git a/chrome/browser/extensions/api/passwords_private/passwords_private_delegate_impl.cc b/chrome/browser/extensions/api/passwords_private/passwords_private_delegate_impl.cc
index 0038bee..a55f27c 100644
--- a/chrome/browser/extensions/api/passwords_private/passwords_private_delegate_impl.cc
+++ b/chrome/browser/extensions/api/passwords_private/passwords_private_delegate_impl.cc
@@ -922,10 +922,24 @@
 #endif
 }
 
-extensions::api::passwords_private::PasswordUiEntry
+api::passwords_private::PasswordUiEntry
 PasswordsPrivateDelegateImpl::CreatePasswordUiEntryFromCredentialUiEntry(
     CredentialUIEntry credential) {
-  extensions::api::passwords_private::PasswordUiEntry entry;
+  api::passwords_private::PasswordUiEntry entry;
+  if (base::FeatureList::IsEnabled(
+          password_manager::features::kPasswordsGrouping)) {
+    entry.affiliated_domains =
+        std::vector<api::passwords_private::DomainInfo>();
+    base::ranges::transform(
+        credential.GetAffiliatedDomains(),
+        std::back_inserter(entry.affiliated_domains.value()),
+        [](const CredentialUIEntry::DomainInfo& domain) {
+          api::passwords_private::DomainInfo domainInfo;
+          domainInfo.name = domain.name;
+          domainInfo.url = domain.url.spec();
+          return domainInfo;
+        });
+  }
   entry.urls = extensions::CreateUrlCollectionFromCredential(credential);
   entry.username = base::UTF16ToUTF8(credential.username);
   entry.stored_in = extensions::StoreSetFromCredential(credential);
diff --git a/chrome/browser/extensions/api/printing/print_job_submitter.cc b/chrome/browser/extensions/api/printing/print_job_submitter.cc
index e4ab961..c36bb56 100644
--- a/chrome/browser/extensions/api/printing/print_job_submitter.cc
+++ b/chrome/browser/extensions/api/printing/print_job_submitter.cc
@@ -20,7 +20,6 @@
 #include "chrome/browser/printing/printing_service.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/extensions/extensions_dialogs.h"
-#include "chrome/browser/ui/native_window_tracker.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/services/printing/public/mojom/pdf_flattener.mojom.h"
 #include "chrome/services/printing/public/mojom/printing_service.mojom.h"
@@ -39,6 +38,7 @@
 #include "printing/printing_utils.h"
 #include "ui/gfx/image/image.h"
 #include "ui/gfx/image/image_skia.h"
+#include "ui/views/native_window_tracker.h"
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "chrome/browser/ash/crosapi/local_printer_ash.h"
@@ -111,7 +111,7 @@
       callback_(std::move(callback)) {
   DCHECK(extension);
   if (native_window)
-    native_window_tracker_ = NativeWindowTracker::Create(native_window);
+    native_window_tracker_ = views::NativeWindowTracker::Create(native_window);
 }
 
 PrintJobSubmitter::~PrintJobSubmitter() {
diff --git a/chrome/browser/extensions/api/printing/print_job_submitter.h b/chrome/browser/extensions/api/printing/print_job_submitter.h
index 2704948..4ef59567 100644
--- a/chrome/browser/extensions/api/printing/print_job_submitter.h
+++ b/chrome/browser/extensions/api/printing/print_job_submitter.h
@@ -34,6 +34,10 @@
 class Image;
 }
 
+namespace views {
+class NativeWindowTracker;
+}
+
 namespace printing {
 namespace mojom {
 class PdfFlattener;
@@ -42,8 +46,6 @@
 class PrintSettings;
 }  // namespace printing
 
-class NativeWindowTracker;
-
 namespace extensions {
 
 class Extension;
@@ -121,7 +123,7 @@
   const raw_ptr<content::BrowserContext> browser_context_;
 
   // Tracks whether |native_window_| got destroyed.
-  std::unique_ptr<NativeWindowTracker> native_window_tracker_;
+  std::unique_ptr<views::NativeWindowTracker> native_window_tracker_;
 
   // These objects are owned by PrintingAPIHandler.
   const raw_ptr<PrintJobController> print_job_controller_;
diff --git a/chrome/browser/extensions/api/tabs/tabs_event_router.cc b/chrome/browser/extensions/api/tabs/tabs_event_router.cc
index 0c8f79a..acbe01e 100644
--- a/chrome/browser/extensions/api/tabs/tabs_event_router.cc
+++ b/chrome/browser/extensions/api/tabs/tabs_event_router.cc
@@ -28,6 +28,8 @@
 #include "content/public/browser/navigation_entry.h"
 #include "content/public/browser/web_contents.h"
 #include "extensions/common/features/feature.h"
+#include "extensions/common/mojom/event_dispatcher.mojom-forward.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/common/page/page_zoom.h"
 
 using base::Value;
@@ -45,8 +47,8 @@
     Feature::Context target_context,
     const Extension* extension,
     const base::Value::Dict* listener_filter,
-    std::unique_ptr<base::Value::List>* event_args_out,
-    mojom::EventFilteringInfoPtr* event_filtering_info_out) {
+    absl::optional<base::Value::List>& event_args_out,
+    mojom::EventFilteringInfoPtr& event_filtering_info_out) {
   ExtensionTabUtil::ScrubTabBehavior scrub_tab_behavior =
       ExtensionTabUtil::GetScrubTabBehavior(extension, target_context,
                                             contents);
@@ -61,10 +63,10 @@
       changed_properties.Set(property, value->Clone());
   }
 
-  *event_args_out = std::make_unique<base::Value::List>();
-  (*event_args_out)->Append(ExtensionTabUtil::GetTabId(contents));
-  (*event_args_out)->Append(std::move(changed_properties));
-  (*event_args_out)->Append(std::move(tab_value));
+  event_args_out.emplace();
+  event_args_out->Append(ExtensionTabUtil::GetTabId(contents));
+  event_args_out->Append(std::move(changed_properties));
+  event_args_out->Append(std::move(tab_value));
   return true;
 }
 
@@ -75,8 +77,8 @@
     Feature::Context target_context,
     const Extension* extension,
     const base::Value::Dict* listener_filter,
-    std::unique_ptr<base::Value::List>* event_args_out,
-    mojom::EventFilteringInfoPtr* event_filtering_info_out) {
+    absl::optional<base::Value::List>& event_args_out,
+    mojom::EventFilteringInfoPtr& event_filtering_info_out) {
   ExtensionTabUtil::ScrubTabBehavior scrub_tab_behavior =
       ExtensionTabUtil::GetScrubTabBehavior(extension, target_context,
                                             contents);
@@ -86,8 +88,8 @@
   tab_value.Set(tabs_constants::kSelectedKey, active);
   tab_value.Set(tabs_constants::kActiveKey, active);
 
-  *event_args_out = std::make_unique<base::Value::List>();
-  (*event_args_out)->Append(std::move(tab_value));
+  event_args_out.emplace();
+  event_args_out->Append(std::move(tab_value));
   return true;
 }
 
diff --git a/chrome/browser/extensions/api/tabs/windows_event_router.cc b/chrome/browser/extensions/api/tabs/windows_event_router.cc
index 7f5c1c4..3e7498c 100644
--- a/chrome/browser/extensions/api/tabs/windows_event_router.cc
+++ b/chrome/browser/extensions/api/tabs/windows_event_router.cc
@@ -25,6 +25,7 @@
 #include "extensions/browser/extension_util.h"
 #include "extensions/common/constants.h"
 #include "extensions/common/mojom/event_dispatcher.mojom.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 using content::BrowserContext;
 
@@ -65,8 +66,8 @@
     Feature::Context target_context,
     const Extension* extension,
     const base::Value::Dict* listener_filter,
-    std::unique_ptr<base::Value::List>* event_args_out,
-    mojom::EventFilteringInfoPtr* event_filtering_info_out) {
+    absl::optional<base::Value::List>& event_args_out,
+    mojom::EventFilteringInfoPtr& event_filtering_info_out) {
   bool has_filter =
       listener_filter &&
       listener_filter->contains(extensions::tabs_constants::kWindowTypesKey);
@@ -77,15 +78,15 @@
     return false;
   }
 
-  *event_filtering_info_out = mojom::EventFilteringInfo::New();
+  event_filtering_info_out = mojom::EventFilteringInfo::New();
   // Only set the window type if the listener has set a filter.
   // Otherwise we set the window visibility relative to the extension.
   if (has_filter) {
-    (*event_filtering_info_out)->window_type =
+    event_filtering_info_out->window_type =
         window_controller->GetWindowTypeText();
   } else {
-    (*event_filtering_info_out)->has_window_exposed_by_default = true;
-    (*event_filtering_info_out)->window_exposed_by_default = true;
+    event_filtering_info_out->has_window_exposed_by_default = true;
+    event_filtering_info_out->window_exposed_by_default = true;
   }
   return true;
 }
@@ -96,8 +97,8 @@
     Feature::Context target_context,
     const Extension* extension,
     const base::Value::Dict* listener_filter,
-    std::unique_ptr<base::Value::List>* event_args_out,
-    mojom::EventFilteringInfoPtr* event_filtering_info_out) {
+    absl::optional<base::Value::List>& event_args_out,
+    mojom::EventFilteringInfoPtr& event_filtering_info_out) {
   int window_id = extension_misc::kUnknownWindowId;
   Profile* new_active_context = nullptr;
   bool has_filter =
@@ -111,18 +112,18 @@
     new_active_context = window_controller->profile();
   }
 
-  *event_filtering_info_out = mojom::EventFilteringInfo::New();
+  event_filtering_info_out = mojom::EventFilteringInfo::New();
   // Only set the window type if the listener has set a filter,
   // otherwise set the visibility to true (if the window is not
   // supposed to be visible by the extension, we will clear out the
   // window id later).
   if (has_filter) {
-    (*event_filtering_info_out)->window_type =
+    event_filtering_info_out->window_type =
         window_controller ? window_controller->GetWindowTypeText()
                           : extensions::tabs_constants::kWindowTypeValueNormal;
   } else {
-    (*event_filtering_info_out)->has_window_exposed_by_default = true;
-    (*event_filtering_info_out)->window_exposed_by_default = true;
+    event_filtering_info_out->has_window_exposed_by_default = true;
+    event_filtering_info_out->window_exposed_by_default = true;
   }
 
   // When switching between windows in the default and incognito profiles,
@@ -137,11 +138,11 @@
   bool visible_to_listener = ControllerVisibleToListener(
       window_controller, extension, listener_filter);
 
-  *event_args_out = std::make_unique<base::Value::List>();
+  event_args_out.emplace();
   if (cant_cross_incognito || !visible_to_listener) {
-    (*event_args_out)->Append(extension_misc::kUnknownWindowId);
+    event_args_out->Append(extension_misc::kUnknownWindowId);
   } else {
-    (*event_args_out)->Append(window_id);
+    event_args_out->Append(window_id);
   }
   return true;
 }
diff --git a/chrome/browser/extensions/component_loader.cc b/chrome/browser/extensions/component_loader.cc
index b87c268..b1ee681 100644
--- a/chrome/browser/extensions/component_loader.cc
+++ b/chrome/browser/extensions/component_loader.cc
@@ -6,7 +6,6 @@
 
 #include <string>
 
-#include "ash/constants/ash_pref_names.h"
 #include "base/bind.h"
 #include "base/callback.h"
 #include "base/callback_helpers.h"
diff --git a/chrome/browser/extensions/extension_install_prompt_show_params.cc b/chrome/browser/extensions/extension_install_prompt_show_params.cc
index 419450a..d5c3447 100644
--- a/chrome/browser/extensions/extension_install_prompt_show_params.cc
+++ b/chrome/browser/extensions/extension_install_prompt_show_params.cc
@@ -9,8 +9,8 @@
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/native_window_tracker.h"
 #include "content/public/browser/web_contents.h"
+#include "ui/views/native_window_tracker.h"
 
 namespace {
 
@@ -31,7 +31,7 @@
       parent_web_contents_(contents ? contents->GetWeakPtr() : nullptr),
       parent_window_(NativeWindowForWebContents(contents)) {
   if (parent_window_)
-    native_window_tracker_ = NativeWindowTracker::Create(parent_window_);
+    native_window_tracker_ = views::NativeWindowTracker::Create(parent_window_);
 }
 
 ExtensionInstallPromptShowParams::ExtensionInstallPromptShowParams(
@@ -41,7 +41,7 @@
       parent_web_contents_(nullptr),
       parent_window_(parent_window) {
   if (parent_window_)
-    native_window_tracker_ = NativeWindowTracker::Create(parent_window_);
+    native_window_tracker_ = views::NativeWindowTracker::Create(parent_window_);
 }
 
 ExtensionInstallPromptShowParams::~ExtensionInstallPromptShowParams() = default;
diff --git a/chrome/browser/extensions/extension_install_prompt_show_params.h b/chrome/browser/extensions/extension_install_prompt_show_params.h
index 7352a5a..5909d5d6 100644
--- a/chrome/browser/extensions/extension_install_prompt_show_params.h
+++ b/chrome/browser/extensions/extension_install_prompt_show_params.h
@@ -11,13 +11,16 @@
 #include "base/memory/weak_ptr.h"
 #include "ui/gfx/native_widget_types.h"
 
-class NativeWindowTracker;
 class Profile;
 
 namespace content {
 class WebContents;
 }
 
+namespace views {
+class NativeWindowTracker;
+}
+
 // Parameters to show an install prompt dialog. The parameters control:
 // - The dialog's parent window
 // - The browser window to use to open a new tab if a user clicks a link in the
@@ -59,7 +62,7 @@
   base::WeakPtr<content::WebContents> parent_web_contents_;
 
   gfx::NativeWindow parent_window_;
-  std::unique_ptr<NativeWindowTracker> native_window_tracker_;
+  std::unique_ptr<views::NativeWindowTracker> native_window_tracker_;
 };
 
 #endif  // CHROME_BROWSER_EXTENSIONS_EXTENSION_INSTALL_PROMPT_SHOW_PARAMS_H_
diff --git a/chrome/browser/extensions/extension_uninstall_dialog.cc b/chrome/browser/extensions/extension_uninstall_dialog.cc
index 13047847..d179333 100644
--- a/chrome/browser/extensions/extension_uninstall_dialog.cc
+++ b/chrome/browser/extensions/extension_uninstall_dialog.cc
@@ -17,7 +17,6 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser_navigator.h"
 #include "chrome/browser/ui/browser_navigator_params.h"
-#include "chrome/browser/ui/native_window_tracker.h"
 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
 #include "chrome/grit/chromium_strings.h"
 #include "chrome/grit/generated_resources.h"
@@ -38,6 +37,7 @@
 #include "ui/display/screen.h"
 #include "ui/gfx/image/image.h"
 #include "ui/gfx/image/image_skia.h"
+#include "ui/views/native_window_tracker.h"
 #include "url/origin.h"
 
 namespace extensions {
@@ -75,7 +75,7 @@
     : profile_(profile), parent_(parent), delegate_(delegate) {
   DCHECK(delegate_);
   if (parent)
-    parent_window_tracker_ = NativeWindowTracker::Create(parent);
+    parent_window_tracker_ = views::NativeWindowTracker::Create(parent);
   profile_observation_.Observe(profile_.get());
 }
 
diff --git a/chrome/browser/extensions/extension_uninstall_dialog.h b/chrome/browser/extensions/extension_uninstall_dialog.h
index 9778d99..46aa379 100644
--- a/chrome/browser/extensions/extension_uninstall_dialog.h
+++ b/chrome/browser/extensions/extension_uninstall_dialog.h
@@ -23,7 +23,9 @@
 #include "ui/gfx/native_widget_types.h"
 #include "url/gurl.h"
 
+namespace views {
 class NativeWindowTracker;
+}
 
 namespace extensions {
 class Extension;
@@ -174,7 +176,7 @@
   std::unique_ptr<ChromeAppIcon> icon_;
 
   // Tracks whether |parent_| got destroyed.
-  std::unique_ptr<NativeWindowTracker> parent_window_tracker_;
+  std::unique_ptr<views::NativeWindowTracker> parent_window_tracker_;
 
   // Indicates that dialog was shown.
   bool dialog_shown_ = false;
diff --git a/chrome/browser/extensions/external_install_error.cc b/chrome/browser/extensions/external_install_error.cc
index d618e5e..3e3b3752 100644
--- a/chrome/browser/extensions/external_install_error.cc
+++ b/chrome/browser/extensions/external_install_error.cc
@@ -305,11 +305,11 @@
 // static
 ExternalInstallError::DefaultDialogButtonSetting
 ExternalInstallError::GetDefaultDialogButton(
-    const base::Value& webstore_response) {
-  const base::Value* value = webstore_response.FindKeyOfType(
-      kExternalInstallDefaultButtonKey, base::Value::Type::STRING);
-  if (value) {
-    return MapDefaultButtonStringToSetting(value->GetString());
+    const base::Value::Dict& webstore_response) {
+  const std::string* value_str =
+      webstore_response.FindString(kExternalInstallDefaultButtonKey);
+  if (value_str) {
+    return MapDefaultButtonStringToSetting(*value_str);
   }
 
   if (base::FeatureList::IsEnabled(
@@ -431,12 +431,11 @@
 
 void ExternalInstallError::OnWebstoreResponseParseSuccess(
     const std::string& extension_id,
-    std::unique_ptr<base::DictionaryValue> webstore_data) {
+    const base::Value::Dict& webstore_data) {
   absl::optional<double> average_rating =
-      webstore_data->FindDoubleKey(kAverageRatingKey);
-  absl::optional<int> rating_count = webstore_data->FindIntKey(kRatingCountKey);
-  const std::string* localized_user_count =
-      webstore_data->GetDict().FindString(kUsersKey);
+      webstore_data.FindDouble(kAverageRatingKey);
+  absl::optional<int> rating_count = webstore_data.FindInt(kRatingCountKey);
+  const std::string* localized_user_count = webstore_data.FindString(kUsersKey);
   if (!localized_user_count || !average_rating || !rating_count) {
     // If we don't get a valid webstore response, short circuit, and continue
     // to show a prompt without webstore data.
@@ -444,10 +443,10 @@
     return;
   }
 
-  default_dialog_button_setting_ = GetDefaultDialogButton(*webstore_data.get());
+  default_dialog_button_setting_ = GetDefaultDialogButton(webstore_data);
 
   absl::optional<bool> show_user_count =
-      webstore_data->FindBoolKey(kShowUserCountKey);
+      webstore_data.FindBool(kShowUserCountKey);
 
   prompt_->SetWebstoreData(*localized_user_count,
                            show_user_count.value_or(true), *average_rating,
diff --git a/chrome/browser/extensions/external_install_error.h b/chrome/browser/extensions/external_install_error.h
index 04f2ec1b..c8bec92 100644
--- a/chrome/browser/extensions/external_install_error.h
+++ b/chrome/browser/extensions/external_install_error.h
@@ -10,6 +10,7 @@
 
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
+#include "base/values.h"
 #include "chrome/browser/extensions/extension_install_prompt.h"
 #include "chrome/browser/extensions/webstore_data_fetcher_delegate.h"
 
@@ -83,7 +84,7 @@
   //    |kExternalInstallDefaultButtonKey| value.
   // If not specified by either optional source, returns |NOT_SPECIFIED|.
   static DefaultDialogButtonSetting GetDefaultDialogButton(
-      const base::Value& webstore_response);
+      const base::Value::Dict& webstore_response);
 
   DefaultDialogButtonSetting default_dialog_button_setting() const {
     return default_dialog_button_setting_;
@@ -94,7 +95,7 @@
   void OnWebstoreRequestFailure(const std::string& extension_id) override;
   void OnWebstoreResponseParseSuccess(
       const std::string& extension_id,
-      std::unique_ptr<base::DictionaryValue> webstore_data) override;
+      const base::Value::Dict& webstore_data) override;
   void OnWebstoreResponseParseFailure(const std::string& extension_id,
                                       const std::string& error) override;
 
diff --git a/chrome/browser/extensions/external_install_error_unittest.cc b/chrome/browser/extensions/external_install_error_unittest.cc
index ae43509a..7f0f56f 100644
--- a/chrome/browser/extensions/external_install_error_unittest.cc
+++ b/chrome/browser/extensions/external_install_error_unittest.cc
@@ -3,7 +3,9 @@
 // found in the LICENSE file.
 
 #include "chrome/browser/extensions/external_install_error.h"
+
 #include "base/test/scoped_feature_list.h"
+#include "base/values.h"
 #include "chrome/browser/extensions/external_install_error_constants.h"
 #include "chrome/common/chrome_features.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -18,8 +20,7 @@
         kDefaultDialogButtonSettingOk}});
 
   EXPECT_EQ(ExternalInstallError::DIALOG_BUTTON_OK,
-            ExternalInstallError::GetDefaultDialogButton(
-                base::Value(base::Value::Type::DICTIONARY)));
+            ExternalInstallError::GetDefaultDialogButton({}));
 }
 
 TEST(ExternalInstallErrorTest, DefaultButtonFromWebstoreResponse) {
@@ -31,10 +32,10 @@
       {{WebstoreDataFetcherDelegate::kExternalInstallDefaultButtonKey,
         kDefaultDialogButtonSettingOk}});
 
-  base::Value webstore_data(base::Value::Type::DICTIONARY);
-  webstore_data.SetKey(
+  base::Value::Dict webstore_data;
+  webstore_data.Set(
       WebstoreDataFetcherDelegate::kExternalInstallDefaultButtonKey,
-      base::Value(kDefaultDialogButtonSettingCancel));
+      kDefaultDialogButtonSettingCancel);
 
   EXPECT_EQ(ExternalInstallError::DIALOG_BUTTON_CANCEL,
             ExternalInstallError::GetDefaultDialogButton(webstore_data));
@@ -42,8 +43,7 @@
 
 TEST(ExternalInstallErrorTest, DefaultButtonFallback) {
   EXPECT_EQ(ExternalInstallError::NOT_SPECIFIED,
-            ExternalInstallError::GetDefaultDialogButton(
-                base::Value(base::Value::Type::DICTIONARY)));
+            ExternalInstallError::GetDefaultDialogButton({}));
 }
 
 }  // namespace extensions
diff --git a/chrome/browser/extensions/external_provider_impl.cc b/chrome/browser/extensions/external_provider_impl.cc
index a7068e2..d1f471b 100644
--- a/chrome/browser/extensions/external_provider_impl.cc
+++ b/chrome/browser/extensions/external_provider_impl.cc
@@ -71,7 +71,6 @@
 #include "chrome/browser/ash/policy/core/device_local_account_policy_service.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/chromeos/extensions/device_local_account_external_policy_loader.h"
-#include "extensions/common/constants.h"
 #else
 #include "chrome/browser/extensions/preinstalled_apps.h"
 #endif
diff --git a/chrome/browser/extensions/menu_manager_unittest.cc b/chrome/browser/extensions/menu_manager_unittest.cc
index 571ffbd2c..354ee56 100644
--- a/chrome/browser/extensions/menu_manager_unittest.cc
+++ b/chrome/browser/extensions/menu_manager_unittest.cc
@@ -17,7 +17,6 @@
 #include "base/values.h"
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/extensions/extension_system_factory.h"
-#include "chrome/browser/extensions/menu_manager.h"
 #include "chrome/browser/extensions/menu_manager_test_observer.h"
 #include "chrome/browser/extensions/test_extension_prefs.h"
 #include "chrome/browser/extensions/test_extension_system.h"
diff --git a/chrome/browser/extensions/standard_management_policy_provider.cc b/chrome/browser/extensions/standard_management_policy_provider.cc
index f20685a..0c5ea42 100644
--- a/chrome/browser/extensions/standard_management_policy_provider.cc
+++ b/chrome/browser/extensions/standard_management_policy_provider.cc
@@ -128,6 +128,15 @@
     return ReturnLoadError(extension, error);
   }
 
+  if (!settings_->IsAllowedManifestVersion(extension)) {
+    if (error) {
+      *error = l10n_util::GetStringFUTF16(
+          IDS_EXTENSION_MANIFEST_VERSION_NOT_SUPPORTED,
+          base::UTF8ToUTF16(extension->name()));
+    }
+    return false;
+  }
+
   return true;
 }
 
diff --git a/chrome/browser/extensions/standard_management_policy_provider_unittest.cc b/chrome/browser/extensions/standard_management_policy_provider_unittest.cc
index 38acfcf..2874aa8 100644
--- a/chrome/browser/extensions/standard_management_policy_provider_unittest.cc
+++ b/chrome/browser/extensions/standard_management_policy_provider_unittest.cc
@@ -10,10 +10,12 @@
 #include "base/values.h"
 #include "chrome/browser/extensions/blocklist.h"
 #include "chrome/browser/extensions/extension_management.h"
+#include "chrome/browser/extensions/extension_management_internal.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/test/base/testing_profile.h"
 #include "components/sync_preferences/testing_pref_service_syncable.h"
 #include "content/public/test/browser_task_environment.h"
+#include "extensions/browser/pref_names.h"
 #include "extensions/common/constants.h"
 #include "extensions/common/extension_builder.h"
 #include "extensions/common/manifest.h"
@@ -173,4 +175,28 @@
   EXPECT_TRUE(provider_.UserMayLoad(extension.get(), &error16));
 }
 
+// Tests the behavior of the ManagementPolicy provider methods for an extension
+// which manifest version is controlled by policy.
+TEST_F(StandardManagementPolicyProviderTest, ManifestVersion) {
+  auto extension = ExtensionBuilder("testManifestVersion")
+                       .SetLocation(ManifestLocation::kExternalPolicyDownload)
+                       .SetManifestVersion(2)
+                       .Build();
+
+  std::u16string error16;
+  EXPECT_TRUE(provider_.UserMayLoad(extension.get(), &error16));
+  EXPECT_TRUE(error16.empty());
+
+  profile_.GetTestingPrefService()->SetManagedPref(
+      pref_names::kManifestV2Availability,
+      std::make_unique<base::Value>(static_cast<int>(
+          internal::GlobalSettings::ManifestV2Setting::kDisabled)));
+
+  EXPECT_FALSE(provider_.UserMayLoad(extension.get(), &error16));
+  EXPECT_EQ(
+      u"The administrator of this machine requires testManifestVersion "
+      "to have a minimum manifest version of 3.",
+      error16);
+}
+
 }  // namespace extensions
diff --git a/chrome/browser/extensions/webstore_data_fetcher.cc b/chrome/browser/extensions/webstore_data_fetcher.cc
index 1cb1b9d7..1ad7703 100644
--- a/chrome/browser/extensions/webstore_data_fetcher.cc
+++ b/chrome/browser/extensions/webstore_data_fetcher.cc
@@ -129,9 +129,7 @@
     return;
   }
 
-  delegate_->OnWebstoreResponseParseSuccess(
-      id_, base::DictionaryValue::From(
-               base::Value::ToUniquePtrValue(std::move(*result))));
+  delegate_->OnWebstoreResponseParseSuccess(id_, result->GetDict());
 }
 
 void WebstoreDataFetcher::OnSimpleLoaderComplete(
diff --git a/chrome/browser/extensions/webstore_data_fetcher_delegate.h b/chrome/browser/extensions/webstore_data_fetcher_delegate.h
index f2b69b28..5ac7285 100644
--- a/chrome/browser/extensions/webstore_data_fetcher_delegate.h
+++ b/chrome/browser/extensions/webstore_data_fetcher_delegate.h
@@ -8,10 +8,7 @@
 #include <memory>
 #include <string>
 
-
-namespace base {
-class DictionaryValue;
-}
+#include "base/values.h"
 
 namespace extensions {
 
@@ -20,11 +17,10 @@
   // Invoked when the web store data request failed.
   virtual void OnWebstoreRequestFailure(const std::string& extension_id) = 0;
 
-  // Invoked when the web store response parsing is successful. Delegate takes
-  // ownership of |webstore_data|.
+  // Invoked when the web store response parsing is successful.
   virtual void OnWebstoreResponseParseSuccess(
       const std::string& extension_id,
-      std::unique_ptr<base::DictionaryValue> webstore_data) = 0;
+      const base::Value::Dict& webstore_data) = 0;
 
   // Invoked when the web store response parsing is failed.
   virtual void OnWebstoreResponseParseFailure(const std::string& extension_id,
diff --git a/chrome/browser/extensions/webstore_install_with_prompt.cc b/chrome/browser/extensions/webstore_install_with_prompt.cc
index d5c1bf29..3e1e853 100644
--- a/chrome/browser/extensions/webstore_install_with_prompt.cc
+++ b/chrome/browser/extensions/webstore_install_with_prompt.cc
@@ -41,7 +41,7 @@
           WebContents::Create(WebContents::CreateParams(profile))),
       parent_window_(parent_window) {
   if (parent_window_)
-    parent_window_tracker_ = NativeWindowTracker::Create(parent_window);
+    parent_window_tracker_ = views::NativeWindowTracker::Create(parent_window);
   set_install_source(WebstoreInstaller::INSTALL_SOURCE_OTHER);
 }
 
diff --git a/chrome/browser/extensions/webstore_install_with_prompt.h b/chrome/browser/extensions/webstore_install_with_prompt.h
index f9a63ca..a9fd2c1 100644
--- a/chrome/browser/extensions/webstore_install_with_prompt.h
+++ b/chrome/browser/extensions/webstore_install_with_prompt.h
@@ -8,8 +8,8 @@
 #include <memory>
 
 #include "chrome/browser/extensions/webstore_standalone_installer.h"
-#include "chrome/browser/ui/native_window_tracker.h"
 #include "ui/gfx/native_widget_types.h"
+#include "ui/views/native_window_tracker.h"
 #include "url/gurl.h"
 
 namespace content {
@@ -69,7 +69,7 @@
   std::unique_ptr<content::WebContents> dummy_web_contents_;
 
   gfx::NativeWindow parent_window_;
-  std::unique_ptr<NativeWindowTracker> parent_window_tracker_;
+  std::unique_ptr<views::NativeWindowTracker> parent_window_tracker_;
 };
 
 }  // namespace extensions
diff --git a/chrome/browser/extensions/webstore_standalone_installer.cc b/chrome/browser/extensions/webstore_standalone_installer.cc
index a804161..a33c2d3c 100644
--- a/chrome/browser/extensions/webstore_standalone_installer.cc
+++ b/chrome/browser/extensions/webstore_standalone_installer.cc
@@ -235,8 +235,7 @@
 
 void WebstoreStandaloneInstaller::OnWebstoreResponseParseSuccess(
     const std::string& extension_id,
-    std::unique_ptr<base::DictionaryValue> webstore_data_val) {
-  base::Value::Dict webstore_data = std::move(*webstore_data_val).TakeDict();
+    const base::Value::Dict& webstore_data) {
   OnWebStoreDataFetcherDone();
 
   if (!CheckRequestorAlive()) {
diff --git a/chrome/browser/extensions/webstore_standalone_installer.h b/chrome/browser/extensions/webstore_standalone_installer.h
index a70733a..6ffd9db 100644
--- a/chrome/browser/extensions/webstore_standalone_installer.h
+++ b/chrome/browser/extensions/webstore_standalone_installer.h
@@ -179,7 +179,7 @@
 
   void OnWebstoreResponseParseSuccess(
       const std::string& extension_id,
-      std::unique_ptr<base::DictionaryValue> webstore_data) override;
+      const base::Value::Dict& webstore_data) override;
 
   void OnWebstoreResponseParseFailure(const std::string& extension_id,
                                       const std::string& error) override;
diff --git a/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/FeedSwipeRefreshLayout.java b/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/FeedSwipeRefreshLayout.java
index cf5fd87f..2262c50 100644
--- a/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/FeedSwipeRefreshLayout.java
+++ b/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/FeedSwipeRefreshLayout.java
@@ -40,6 +40,9 @@
     private static final int SPINNER_START_OFFSET = 16;
     // Offset in dips from the top of the view to where the progress spinner should stop.
     private static final int SPINNER_END_OFFSET = 80;
+    // Offset in dips from the bottom of the view to where the progress spinner should be shown when
+    // switched to a "bottom" spinner (non-pull refresh).
+    private static final int SPINNER_OFFSET_FROM_BOTTOM = 100;
 
     private final Activity mActivity;
     @IdRes
@@ -171,6 +174,20 @@
         mRefreshListeners.removeObserver(listener);
     }
 
+    /**
+     * Starts a refreshing spinner at the bottom of the view. Should only be used for non-swipe
+     * refreshes.
+     */
+    public void startRefreshingAtTheBottom() {
+        final DisplayMetrics metrics = mActivity.getResources().getDisplayMetrics();
+        // The offset will limited to show the spiiner as high as the vertical middle of the view.
+        int offset = Math.max(metrics.heightPixels / 2,
+                metrics.heightPixels - ((int) (SPINNER_OFFSET_FROM_BOTTOM * metrics.density)));
+        setProgressViewEndTarget(false, offset);
+        setRefreshing(true);
+        setProgressViewEndTarget(false, (int) (SPINNER_END_OFFSET * metrics.density));
+    }
+
     private void ensureTarget() {
         if (mTarget == null) {
             for (int i = 0; i < getChildCount(); i++) {
diff --git a/chrome/browser/file_system_access/chrome_file_system_access_permission_context.cc b/chrome/browser/file_system_access/chrome_file_system_access_permission_context.cc
index e0064cf..6a71cea 100644
--- a/chrome/browser/file_system_access/chrome_file_system_access_permission_context.cc
+++ b/chrome/browser/file_system_access/chrome_file_system_access_permission_context.cc
@@ -1743,10 +1743,11 @@
   if (!web_app_provider)
     return false;
 
-  auto app_id =
-      web_app_provider->registrar().FindAppWithUrlInScope(origin.GetURL());
+  auto app_id = web_app_provider->registrar_unsafe().FindAppWithUrlInScope(
+      origin.GetURL());
   return app_id.has_value() &&
-         web_app_provider->registrar().IsActivelyInstalled(app_id.value());
+         web_app_provider->registrar_unsafe().IsActivelyInstalled(
+             app_id.value());
 #endif  // BUILDFLAG(IS_ANDROID)
 }
 
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 46c185e..560f36b 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -761,6 +761,14 @@
     "expiry_milestone": 112
   },
   {
+    "name": "calendar-jelly",
+    "owners": [
+      "newcomer",
+      "cros-status-area-eng@google.com"
+    ],
+    "expiry_milestone": 118
+  },
+  {
     "name": "calendar-view",
     "owners": ["jiamingc", "cros-status-area-eng@google.com"],
     "expiry_milestone" : 106
@@ -771,11 +779,6 @@
     "expiry_milestone": 113
   },
   {
-    "name": "camera-app-doc-scan-dlc",
-    "owners": [ "wtlee" ],
-    "expiry_milestone": 110
-  },
-  {
     "name": "camera-app-low-storage-warning",
     "owners": [ "kamchonlathorn" ],
     "expiry_milestone": 114
@@ -833,7 +836,7 @@
   {
     "name": "cct-real-time-engagement-signals",
     "owners": ["sinansahin@google.com", "jinsukkim"],
-    "expiry_milestone": 110
+    "expiry_milestone": 115
   },
   {
     "name": "cct-resizable-90-maximum-height",
@@ -1138,6 +1141,11 @@
     "expiry_milestone": 110
   },
   {
+    "name": "cros-labs-overview-desk-navigation",
+    "owners": [ "richui", "janetmac" ],
+    "expiry_milestone": 122
+  },
+  {
     "name": "cros-labs-window-cycle-shortcut",
     "owners": [ "andp", "benbecker", "nupurjain" ],
     "expiry_milestone": 120
@@ -1667,11 +1675,6 @@
     "expiry_milestone": 115
   },
   {
-    "name": "enable-app-discovery-for-oobe",
-    "owners": [ "melzhang", "tsergeant", "chromeos-apps-foundation-team" ],
-    "expiry_milestone": 110
-  },
-  {
     "name": "enable-app-service-in-kiosk",
     "owners": ["yixie", "chromeos-kiosk-eng@google.com"],
     "expiry_milestone": 130
@@ -2040,7 +2043,7 @@
   {
     "name": "enable-delegated-compositing",
     "owners": [ "petermcneeley" ],
-    "expiry_milestone": 110
+    "expiry_milestone": 120
   },
   {
     "name": "enable-desks-close-all",
@@ -2587,11 +2590,6 @@
     "expiry_milestone": 120
   },
   {
-    "name": "enable-lens-instruction-chip-improvements",
-    "owners": [ "schechter@google.com", "juanmojica@google.com" ],
-    "expiry_milestone": 112
-  },
-  {
     "name": "enable-lens-region-search-static-page",
     "owners": ["juanmojica@google.com", "stanfield@google.com", "lens-chrome@google.com"],
     "expiry_milestone": 112
@@ -2735,7 +2733,7 @@
   {
     "name": "enable-oauth-ipp",
     "owners": [ "batrapranav", "pawliczek" ],
-    "expiry_milestone": 110
+    "expiry_milestone": 114
   },
   {
     "name": "enable-oop-print-drivers",
@@ -4060,6 +4058,11 @@
     "expiry_milestone": 115
   },
   {
+    "name": "history-journeys-include-synced-visits",
+    "owners": [ "tommycli", "chrome-journeys@google.com" ],
+    "expiry_milestone": 115
+  },
+  {
     "name": "history-journeys-labels",
     "owners": [ "tommycli", "chrome-journeys@google.com" ],
     "expiry_milestone": 115
@@ -4280,7 +4283,7 @@
   {
     "name": "ios-media-permissions-control",
     "owners": ["ginnyhuang","bling-flags@google.com"],
-    "expiry_milestone": 110
+    "expiry_milestone": 112
   },
   {
     "name": "ios-new-post-restore-experience",
@@ -4654,7 +4657,7 @@
   {
     "name": "messages-for-android-stacking-animation",
     "owners": [ "lazzzis", "aishwaryarj"],
-    "expiry_milestone": 110
+    "expiry_milestone": 118
   },
   {
     "name": "messages-for-android-update-password",
@@ -5298,7 +5301,7 @@
   {
     "name": "omnibox-zero-suggest-in-memory-caching",
     "owners": [ "khalidpeer", "mahmadi", "chrome-omnibox-team@google.com" ],
-    "expiry_milestone": 110
+    "expiry_milestone": 120
   },
   {
     "name": "omnibox-zero-suggest-prefetching",
@@ -5333,27 +5336,27 @@
   {
     "name": "optimization-guide-debug-logs",
     "owners": [ "rajendrant", "robertogden", "chrome-intelligence-core@google.com" ],
-    "expiry_milestone": 110
+    "expiry_milestone": 115
   },
   {
     "name": "optimization-guide-install-wide-model-store",
     "owners": [ "rajendrant", "sophiechang", "chrome-intelligence-core@google.com" ],
-    "expiry_milestone": 113
+    "expiry_milestone": 115
   },
   {
     "name": "optimization-guide-model-downloading",
     "owners": [ "sophiechang", "chrome-intelligence-core@google.com" ],
-    "expiry_milestone": 110
+    "expiry_milestone": 115
   },
   {
     "name": "optimization-guide-push-notifications",
     "owners": [ "robertogden", "chrome-intelligence-core@google.com" ],
-    "expiry_milestone": 110
+    "expiry_milestone": 115
   },
   {
     "name": "optimization-target-prediction",
     "owners": [ "sophiechang", "chrome-intelligence-core@google.com" ],
-    "expiry_milestone": 110
+    "expiry_milestone": 115
   },
   {
     "name": "organic-repeatable-queries",
@@ -5414,12 +5417,12 @@
   {
     "name": "page-content-annotations",
     "owners": [ "sophiechang", "mcrouse", "chrome-intelligence-core@google.com"],
-    "expiry_milestone": 110
+    "expiry_milestone": 115
   },
   {
     "name": "page-entities-page-content-annotations",
     "owners": [ "sophiechang", "mcrouse", "chrome-intelligence-core@google.com"],
-    "expiry_milestone": 110
+    "expiry_milestone": 115
   },
   {
     "name": "page-info-about-this-page-description-placeholder",
@@ -5466,7 +5469,7 @@
   {
     "name": "page-visibility-page-content-annotations",
     "owners": [ "sophiechang", "mcrouse", "chrome-intelligence-core@google.com"],
-    "expiry_milestone": 110
+    "expiry_milestone": 115
   },
   {
     "name": "paint-preview-demo",
@@ -5481,7 +5484,7 @@
   {
     "name": "partitioned-cookies",
     "owners": [ "dylancutler@google.com" ],
-    "expiry_milestone": 110
+    "expiry_milestone": 118
   },
   {
     "name": "password-change-account-store-users",
@@ -6067,6 +6070,11 @@
     "expiry_milestone": 110
   },
   {
+    "name": "screen-saver-preview",
+    "owners": [ "safarli", "assistive-eng@google.com" ],
+    "expiry_milestone": 116
+  },
+  {
     "name": "screen-time-integration-ios",
     "owners": [ "edchin", "bling-flags@google.com" ],
     "expiry_milestone": 92
@@ -6079,7 +6087,7 @@
   {
     "name": "screentime",
     "owners": [ "lgrey", "chrome-mac-dev@google.com" ],
-    "expiry_milestone": 110
+    "expiry_milestone": 120
   },
   {
     "name": "scroll-unification",
@@ -6972,7 +6980,7 @@
   {
     "name": "view-transition",
     "owners": [ "khushalsagar", "vmpstr", "chrishtr" ],
-    "expiry_milestone" : 110
+    "expiry_milestone" : 118
   },
   {
     "name": "view-transition-on-navigation",
@@ -7072,7 +7080,7 @@
   {
     "name": "web-otp-backend",
     "owners": [ "yigu" ],
-    "expiry_milestone": 110
+    "expiry_milestone": 114
   },
   {
     "name": "web-payment-api-csp",
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index c62b12d..6cce9bfd 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -1252,12 +1252,6 @@
     "Enables Lens image and region search to learn about the visual content "
     "you see while you browse and shop on the web.";
 
-const char kEnableLensInstructionChipImprovementsName[] =
-    "Enable improvements to the Lens instruction chip.";
-const char kEnableLensInstructionChipImprovementsDescription[] =
-    "Enables improvements to the Lens instruction chip when using the region "
-    "search feature.";
-
 const char kEnableRegionSearchOnPdfViewerName[] =
     "Enable Region Search on PDF viewer.";
 const char kEnableRegionSearchOnPdfViewerDescription[] =
@@ -1766,6 +1760,11 @@
 const char kJourneysVisitDedupingDescription[] =
     "Enables variations for how visits are deduped";
 
+const char kJourneysIncludeSyncedVisitsName[] =
+    "History Journeys Include SyncedVisits";
+const char kJourneysIncludeSyncedVisitsDescription[] =
+    "Enabled synced visits to be included in History Journeys clusters.";
+
 const char kExtractRelatedSearchesFromPrefetchedZPSResponseName[] =
     "Extract Related Searches from Prefetched ZPS Response";
 const char kExtractRelatedSearchesFromPrefetchedZPSResponseDescription[] =
@@ -2233,6 +2232,12 @@
 const char kOverviewButtonDescription[] =
     "If enabled, always show the overview button at the status area.";
 
+const char kOverviewDeskNavigationName[] =
+    "CrOS Labs: Overview Desk Navigation";
+const char kOverviewDeskNavigationDescription[] =
+    "Stay in overview when navigating between desks using a swipe gesture or "
+    "keyboard shortcut.";
+
 const char kPageContentAnnotationsName[] = "Page content annotations";
 const char kPageContentAnnotationsDescription[] =
     "Enables page content to be annotated on-device.";
@@ -2577,6 +2582,11 @@
     "behavior. Please file bugs at https://bugs.chromium.org/p/chromium/issues/"
     "entry?labels=StoragePartitioning-trial-bugs&components=Blink%3EStorage.";
 
+const char kScreenSaverPreviewName[] = "Screen saver preview";
+const char kScreenSaverPreviewDescription[] =
+    "Enables the screen saver preview feature which allows the users to "
+    "preview current screen saver.";
+
 const char kScrollableTabStripFlagId[] = "scrollable-tabstrip";
 const char kScrollableTabStripName[] = "Tab Scrolling";
 const char kScrollableTabStripDescription[] =
@@ -4873,6 +4883,10 @@
     "Enable address resolution offloading to Bluetooth Controller if "
     "supported. Modifying this flag will cause Bluetooth Controller to reset.";
 
+const char kCalendarJellyName[] = "Enable Calendar Jelly features";
+const char kCalendarJellyDescription[] =
+    "Enables Jelly changes for the sys tray Calendar views.";
+
 const char kCalendarViewName[] =
     "Productivity experiment: Monthly Calendar View";
 const char kCalendarViewDescription[] =
@@ -5015,11 +5029,6 @@
     "Enable this flag to migrate a Bruschetta installed during the alpha. "
     "Requires the bruschetta flag to be enabled.";
 
-const char kCameraAppDocScanDlcName[] = "Camera App Doc Scan DLC";
-const char kCameraAppDocScanDlcDescription[] =
-    "Enables this flag to allow downloading document scanning feature via DLC "
-    "and use it in the camera app";
-
 extern const char kCameraAppLowStorageWarningName[] =
     "Camera App Low Storage Warning";
 extern const char kCameraAppLowStorageWarningDescription[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index f53b12a..4525738 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -675,9 +675,6 @@
 extern const char kEnableLensStandaloneName[];
 extern const char kEnableLensStandaloneDescription[];
 
-extern const char kEnableLensInstructionChipImprovementsName[];
-extern const char kEnableLensInstructionChipImprovementsDescription[];
-
 extern const char kEnableRegionSearchOnPdfViewerName[];
 extern const char kEnableRegionSearchOnPdfViewerDescription[];
 
@@ -996,6 +993,9 @@
 extern const char kJourneysVisitDedupingName[];
 extern const char kJourneysVisitDedupingDescription[];
 
+extern const char kJourneysIncludeSyncedVisitsName[];
+extern const char kJourneysIncludeSyncedVisitsDescription[];
+
 extern const char kExtractRelatedSearchesFromPrefetchedZPSResponseName[];
 extern const char kExtractRelatedSearchesFromPrefetchedZPSResponseDescription[];
 
@@ -1236,6 +1236,9 @@
 extern const char kOverviewButtonName[];
 extern const char kOverviewButtonDescription[];
 
+extern const char kOverviewDeskNavigationName[];
+extern const char kOverviewDeskNavigationDescription[];
+
 extern const char kOverlayScrollbarsName[];
 extern const char kOverlayScrollbarsDescription[];
 
@@ -1447,6 +1450,9 @@
 extern const char kThirdPartyStoragePartitioningName[];
 extern const char kThirdPartyStoragePartitioningDescription[];
 
+extern const char kScreenSaverPreviewName[];
+extern const char kScreenSaverPreviewDescription[];
+
 extern const char kScrollableTabStripFlagId[];
 extern const char kScrollableTabStripName[];
 extern const char kScrollableTabStripDescription[];
@@ -2702,9 +2708,6 @@
 extern const char kApnRevampName[];
 extern const char kApnRevampDescription[];
 
-extern const char kAppDiscoveryForOobeName[];
-extern const char kAppDiscoveryForOobeDescription[];
-
 extern const char kArcCustomTabsExperimentName[];
 extern const char kArcCustomTabsExperimentDescription[];
 
@@ -2804,6 +2807,9 @@
 extern const char kBluetoothUseLLPrivacyName[];
 extern const char kBluetoothUseLLPrivacyDescription[];
 
+extern const char kCalendarJellyName[];
+extern const char kCalendarJellyDescription[];
+
 extern const char kCalendarViewName[];
 extern const char kCalendarViewDescription[];
 
@@ -2891,9 +2897,6 @@
 extern const char kBruschettaAlphaMigrateName[];
 extern const char kBruschettaAlphaMigrateDescription[];
 
-extern const char kCameraAppDocScanDlcName[];
-extern const char kCameraAppDocScanDlcDescription[];
-
 extern const char kCameraAppLowStorageWarningName[];
 extern const char kCameraAppLowStorageWarningDescription[];
 
diff --git a/chrome/browser/history/history_tab_helper.cc b/chrome/browser/history/history_tab_helper.cc
index cb9d570..32748be 100644
--- a/chrome/browser/history/history_tab_helper.cc
+++ b/chrome/browser/history/history_tab_helper.cc
@@ -357,8 +357,9 @@
   // the WebContents' URL getter does.
   NavigationEntry* last_committed =
       web_contents()->GetController().GetLastCommittedEntry();
+  base::Time timestamp = last_committed->GetTimestamp();
   history::HistoryAddPageArgs add_page_args = CreateHistoryAddPageArgs(
-      web_contents()->GetLastCommittedURL(), last_committed->GetTimestamp(),
+      web_contents()->GetLastCommittedURL(), timestamp,
       last_committed->GetUniqueID(), navigation_handle);
 
   if (!IsEligibleTab(add_page_args))
@@ -369,7 +370,7 @@
   if (HistoryClustersTabHelper* clusters_tab_helper =
           HistoryClustersTabHelper::FromWebContents(web_contents())) {
     clusters_tab_helper->OnUpdatedHistoryForNavigation(
-        navigation_handle->GetNavigationId(), add_page_args.url);
+        navigation_handle->GetNavigationId(), timestamp, add_page_args.url);
   }
 }
 
diff --git a/chrome/browser/history_clusters/history_clusters_tab_helper.cc b/chrome/browser/history_clusters/history_clusters_tab_helper.cc
index 5155323..2d2f5f3a 100644
--- a/chrome/browser/history_clusters/history_clusters_tab_helper.cc
+++ b/chrome/browser/history_clusters/history_clusters_tab_helper.cc
@@ -60,10 +60,8 @@
   }
 
   void DoneRunOnMainThread() override {
-    if (!visits_.empty()) {
-      DCHECK_LE(visits_.size(), 2u);
-      std::move(callback_).Run(url_row_, visits_);
-    }
+    DCHECK_LE(visits_.size(), 2u);
+    std::move(callback_).Run(url_row_, visits_);
   }
 
  private:
@@ -158,6 +156,7 @@
 
 void HistoryClustersTabHelper::OnUpdatedHistoryForNavigation(
     int64_t navigation_id,
+    base::Time timestamp,
     const GURL& url) {
   auto* history_clusters_service = GetHistoryClustersService();
   if (!history_clusters_service)
@@ -174,7 +173,7 @@
       .is_existing_bookmark = IsPageBookmarked(web_contents(), url);
 
   if (auto* history_service = GetHistoryService()) {
-    // This `GetMostRecentVisitsToUrl` task will find at least 1 visit since
+    // This `GetMostRecentVisitsToUrl` task should find at least 1 visit since
     // `HistoryTabHelper::UpdateHistoryForNavigation()`, invoked prior to
     // `OnUpdatedHistoryForNavigation()`, will have posted a task to add the
     // visit associated to `incomplete_visit_context_annotations`.
@@ -186,15 +185,36 @@
                 [](HistoryClustersTabHelper* history_clusters_tab_helper,
                    history_clusters::HistoryClustersService*
                        history_clusters_service,
-                   int64_t navigation_id,
+                   int64_t navigation_id, base::Time timestamp,
                    history_clusters::IncompleteVisitContextAnnotations&
                        incomplete_visit_context_annotations,
                    history::URLRow url_row, history::VisitVector visits) {
                   DCHECK(history_clusters_tab_helper);
                   DCHECK(history_clusters_service);
+                  // This can happen for navigations that don't result in a
+                  // visit being added to the DB, e.g. navigations to
+                  // "chrome://" URLs.
+                  if (visits.empty()) {
+                    return;
+                  }
                   DCHECK(url_row.id());
                   DCHECK(visits[0].visit_id);
                   DCHECK_EQ(url_row.id(), visits[0].url_id);
+                  // Make sure the visit we got actually corresponds to the
+                  // navigation by comparing the timestamps.
+                  if (visits[0].visit_time != timestamp) {
+                    return;
+                  }
+                  // Make sure the latest visit (the first one in the array) is
+                  // a local one. That should almost always be the case, since
+                  // this gets called just after a local visit happened, but in
+                  // some rare cases it might not be, e.g. if another device
+                  // sent us a visit "from the future". If this turns out to be
+                  // a problem, consider implementing a
+                  // GetMostRecent*Local*VisitsForURL().
+                  if (!visits[0].originator_cache_guid.empty()) {
+                    return;
+                  }
                   incomplete_visit_context_annotations.url_row = url_row;
                   incomplete_visit_context_annotations.visit_row = visits[0];
                   if (visits.size() > 1) {
@@ -214,7 +234,7 @@
                         navigation_id);
                   }
                 },
-                this, history_clusters_service, navigation_id,
+                this, history_clusters_service, navigation_id, timestamp,
                 std::ref(incomplete_visit_context_annotations))),
         &task_tracker_);
   }
diff --git a/chrome/browser/history_clusters/history_clusters_tab_helper.h b/chrome/browser/history_clusters/history_clusters_tab_helper.h
index 6d73026..6733260 100644
--- a/chrome/browser/history_clusters/history_clusters_tab_helper.h
+++ b/chrome/browser/history_clusters/history_clusters_tab_helper.h
@@ -36,7 +36,9 @@
   // Called by `HistoryTabHelper` right after submitting a new navigation for
   // `web_contents()` to HistoryService. We need close coordination with
   // History's conception of the visit lifetime.
-  void OnUpdatedHistoryForNavigation(int64_t navigation_id, const GURL& url);
+  void OnUpdatedHistoryForNavigation(int64_t navigation_id,
+                                     base::Time timestamp,
+                                     const GURL& url);
 
   // Invoked for navigations that are tracked by UKM. Specifically, same-app
   // navigations aren't tracked individually in UKM and therefore won't receive
diff --git a/chrome/browser/history_clusters/history_clusters_tab_helper_unittest.cc b/chrome/browser/history_clusters/history_clusters_tab_helper_unittest.cc
index 84b1010..1a881dd 100644
--- a/chrome/browser/history_clusters/history_clusters_tab_helper_unittest.cc
+++ b/chrome/browser/history_clusters/history_clusters_tab_helper_unittest.cc
@@ -99,12 +99,15 @@
     return history_clusters_service_test_api_->GetVisits();
   }
 
-  void AddToHistory(const GURL& url, int time_seconds) {
+  base::Time MakeTime(int time_seconds) {
+    return base::Time::FromDeltaSinceWindowsEpoch(base::Seconds(time_seconds));
+  }
+
+  void AddToHistory(const GURL& url, base::Time timestamp) {
     history::HistoryAddPageArgs add_page_args;
     add_page_args.url = url;
     add_page_args.title = u"Fake Title";
-    add_page_args.time =
-        base::Time::FromDeltaSinceWindowsEpoch(base::Seconds(time_seconds));
+    add_page_args.time = timestamp;
     history_service_->AddPage(add_page_args);
   }
 
@@ -153,8 +156,9 @@
 // 2) `WebContentsDestroyed()` is invoked.
 // Then: 0 context annotations should be committed.
 TEST_F(HistoryClustersTabHelperTest, NavigationWith0HistoryVisits) {
-  AddToHistory(GURL{"https://other.com"}, 1);
-  helper_->OnUpdatedHistoryForNavigation(0, GURL{"https://github.com"});
+  AddToHistory(GURL{"https://other.com"}, MakeTime(2));
+  helper_->OnUpdatedHistoryForNavigation(0, MakeTime(1),
+                                         GURL{"https://github.com"});
   history::BlockUntilHistoryProcessesPendingRequests(history_service_);
   helper_->OnOmniboxUrlCopied();
   DeleteContents();
@@ -172,8 +176,9 @@
 // 2) `WebContentsDestroyed()` is invoked.
 // Then: 1 context annotation should be committed.
 TEST_F(HistoryClustersTabHelperTest, NavigationWith1HistoryVisits) {
-  AddToHistory(GURL{"https://github.com"}, 1);
-  helper_->OnUpdatedHistoryForNavigation(0, GURL{"https://github.com"});
+  AddToHistory(GURL{"https://github.com"}, MakeTime(1));
+  helper_->OnUpdatedHistoryForNavigation(0, MakeTime(1),
+                                         GURL{"https://github.com"});
   history::BlockUntilHistoryProcessesPendingRequests(history_service_);
   helper_->OnOmniboxUrlCopied();
   EXPECT_EQ(GetVisits().size(), 1u);
@@ -191,6 +196,33 @@
   EXPECT_TRUE(visits[0].context_annotations.omnibox_url_copied);
 }
 
+// History (w/ 1 *mismatched* visit) -> destroy
+// When:
+// 1) `OnUpdatedHistoryForNavigation()` is invoked and 1 history visit is
+//    fetched, but it doesn't match the navigation timestamp.
+// 2) `WebContentsDestroyed()` is invoked.
+// Then: 0 context annotations should be committed.
+TEST_F(HistoryClustersTabHelperTest, NavigationWith1MismatchedHistoryVisit) {
+  // One pre-existing visit to the URL exists in the DB.
+  AddToHistory(GURL{"https://github.com"}, MakeTime(1));
+  // A new visit to the same URL happens, but this one is *not* in the DB.
+  helper_->OnUpdatedHistoryForNavigation(0, MakeTime(100),
+                                         GURL{"https://github.com"});
+  history::BlockUntilHistoryProcessesPendingRequests(history_service_);
+  helper_->OnOmniboxUrlCopied();
+  ASSERT_EQ(GetVisits().size(), 1u);
+
+  DeleteContents();
+
+  auto visits = GetVisits();
+  ASSERT_EQ(visits.size(), 1u);
+  ASSERT_EQ(visits[0].url_row.url(), GURL{"https://github.com"});
+  EXPECT_NE(visits[0].visit_row.visit_time, MakeTime(100));
+  // Since the timestamps didn't match, the visit should *not* have gotten any
+  // annotations.
+  EXPECT_FALSE(visits[0].context_annotations.omnibox_url_copied);
+}
+
 // History (w/ 2 visits) -> destroy
 // When:
 // 1) `OnUpdatedHistoryForNavigation()` is invoked and 2 history visits are
@@ -198,9 +230,10 @@
 // 2) `WebContentsDestroyed()` is invoked.
 // Then: 1 context annotation should be committed.
 TEST_F(HistoryClustersTabHelperTest, NavigationWith2HistoryVisits) {
-  AddToHistory(GURL{"https://github.com"}, 19);
-  AddToHistory(GURL{"https://github.com"}, 23);
-  helper_->OnUpdatedHistoryForNavigation(0, GURL{"https://github.com"});
+  AddToHistory(GURL{"https://github.com"}, MakeTime(19));
+  AddToHistory(GURL{"https://github.com"}, MakeTime(23));
+  helper_->OnUpdatedHistoryForNavigation(0, MakeTime(23),
+                                         GURL{"https://github.com"});
   history::BlockUntilHistoryProcessesPendingRequests(history_service_);
   auto visits = GetVisits();
   // Two visits are there but context annotations are initially unavailable.
@@ -230,8 +263,10 @@
 // 3) `WebContentsDestroyed()` is invoked.
 // Then: 0 visits should be committed.
 TEST_F(HistoryClustersTabHelperTest, TwoNavigationsWith0HistoryVisits) {
-  helper_->OnUpdatedHistoryForNavigation(0, GURL{"https://github.com"});
-  helper_->OnUpdatedHistoryForNavigation(1, GURL{"https://google.com"});
+  helper_->OnUpdatedHistoryForNavigation(0, MakeTime(1),
+                                         GURL{"https://github.com"});
+  helper_->OnUpdatedHistoryForNavigation(1, MakeTime(2),
+                                         GURL{"https://google.com"});
   history::BlockUntilHistoryProcessesPendingRequests(history_service_);
   EXPECT_TRUE(GetVisits().empty());
 }
@@ -245,11 +280,12 @@
 // 3) `WebContentsDestroyed()` is invoked.
 // Then: 2 context annotations should be committed.
 TEST_F(HistoryClustersTabHelperTest, TwoNavigationsWith2HistoryVisits) {
-  AddToHistory(GURL{"https://github.com"}, 1);
-  AddToHistory(GURL{"https://github.com"}, 5);
-  AddToHistory(GURL{"https://google.com"}, 10);
-  AddToHistory(GURL{"https://google.com"}, 18);
-  helper_->OnUpdatedHistoryForNavigation(0, GURL{"https://github.com"});
+  AddToHistory(GURL{"https://github.com"}, MakeTime(1));
+  AddToHistory(GURL{"https://github.com"}, MakeTime(5));
+  AddToHistory(GURL{"https://google.com"}, MakeTime(10));
+  AddToHistory(GURL{"https://google.com"}, MakeTime(18));
+  helper_->OnUpdatedHistoryForNavigation(0, MakeTime(5),
+                                         GURL{"https://github.com"});
   history::BlockUntilHistoryProcessesPendingRequests(history_service_);
   // 4 visits are in History, but they don't have context annotations.
   auto visits = GetVisits();
@@ -267,7 +303,8 @@
   EXPECT_EQ(visits[3].context_annotations.duration_since_last_visit.InSeconds(),
             -1);
 
-  helper_->OnUpdatedHistoryForNavigation(1, GURL{"https://google.com"});
+  helper_->OnUpdatedHistoryForNavigation(1, MakeTime(18),
+                                         GURL{"https://google.com"});
   history::BlockUntilHistoryProcessesPendingRequests(history_service_);
   visits = GetVisits();
   ASSERT_EQ(visits.size(), 4u);
@@ -302,8 +339,9 @@
 //    resolved.
 // Then: 0 context annotations should be committed.
 TEST_F(HistoryClustersTabHelperTest, HistoryResolvedAfterDestroy) {
-  AddToHistory(GURL{"https://github.com"}, 1);
-  helper_->OnUpdatedHistoryForNavigation(0, GURL{"https://github.com"});
+  AddToHistory(GURL{"https://github.com"}, MakeTime(1));
+  helper_->OnUpdatedHistoryForNavigation(0, MakeTime(1),
+                                         GURL{"https://github.com"});
   helper_->OnOmniboxUrlCopied();
   DeleteContents();
   history::BlockUntilHistoryProcessesPendingRequests(history_service_);
@@ -322,10 +360,12 @@
 // 3) `WebContentsDestroyed()` is invoked.
 // Then: 2 context annotations should be committed.
 TEST_F(HistoryClustersTabHelperTest, HistoryResolvedAfter2ndNavigation) {
-  AddToHistory(GURL{"https://google.com"}, 1);
-  AddToHistory(GURL{"https://github.com"}, 2);
-  helper_->OnUpdatedHistoryForNavigation(0, GURL{"https://google.com"});
-  helper_->OnUpdatedHistoryForNavigation(1, GURL{"https://github.com"});
+  AddToHistory(GURL{"https://google.com"}, MakeTime(1));
+  AddToHistory(GURL{"https://github.com"}, MakeTime(2));
+  helper_->OnUpdatedHistoryForNavigation(0, MakeTime(1),
+                                         GURL{"https://google.com"});
+  helper_->OnUpdatedHistoryForNavigation(1, MakeTime(2),
+                                         GURL{"https://github.com"});
 
   // Bookmarked after navigation ends, but before its history request resolved.
   AddBookmark(GURL{"https://google.com"});
@@ -360,10 +400,11 @@
 // Then: 3 context annotations should be committed; the 1st and 3rd should have
 //       `omnibox_url_copied` true.
 TEST_F(HistoryClustersTabHelperTest, UrlsCopied) {
-  AddToHistory(GURL{"https://github.com"}, 1);
-  AddToHistory(GURL{"https://google.com"}, 2);
-  AddToHistory(GURL{"https://gmail.com"}, 3);
-  helper_->OnUpdatedHistoryForNavigation(0, GURL{"https://github.com"});
+  AddToHistory(GURL{"https://github.com"}, MakeTime(1));
+  AddToHistory(GURL{"https://google.com"}, MakeTime(2));
+  AddToHistory(GURL{"https://gmail.com"}, MakeTime(3));
+  helper_->OnUpdatedHistoryForNavigation(0, MakeTime(1),
+                                         GURL{"https://github.com"});
   helper_->OnOmniboxUrlCopied();
   history::BlockUntilHistoryProcessesPendingRequests(history_service_);
   auto visits = GetVisits();
@@ -375,7 +416,8 @@
   EXPECT_EQ(visits[2].url_row.url(), GURL{"https://github.com"});
   EXPECT_FALSE(visits[2].context_annotations.omnibox_url_copied);
 
-  helper_->OnUpdatedHistoryForNavigation(1, GURL{"https://google.com"});
+  helper_->OnUpdatedHistoryForNavigation(1, MakeTime(2),
+                                         GURL{"https://google.com"});
   history::BlockUntilHistoryProcessesPendingRequests(history_service_);
   visits = GetVisits();
   ASSERT_EQ(visits.size(), 3u);
@@ -386,7 +428,8 @@
   EXPECT_EQ(visits[2].url_row.url(), GURL{"https://github.com"});
   EXPECT_TRUE(visits[2].context_annotations.omnibox_url_copied);
 
-  helper_->OnUpdatedHistoryForNavigation(2, GURL{"https://gmail.com"});
+  helper_->OnUpdatedHistoryForNavigation(2, MakeTime(3),
+                                         GURL{"https://gmail.com"});
   history::BlockUntilHistoryProcessesPendingRequests(history_service_);
   helper_->OnOmniboxUrlCopied();
   visits = GetVisits();
@@ -417,8 +460,9 @@
 // 4) `WebContentsDestroyed()` is invoked.
 // Then: 1 context annotations committed after step 3 w/ a `page_end_reason`.
 TEST_F(HistoryClustersTabHelperTest, NavigationWithUkmBeforeDestroy) {
-  AddToHistory(GURL{"https://github.com"}, 1);
-  helper_->OnUpdatedHistoryForNavigation(0, GURL{"https://github.com"});
+  AddToHistory(GURL{"https://github.com"}, MakeTime(1));
+  helper_->OnUpdatedHistoryForNavigation(0, MakeTime(1),
+                                         GURL{"https://github.com"});
   history::BlockUntilHistoryProcessesPendingRequests(history_service_);
   helper_->TagNavigationAsExpectingUkmNavigationComplete(0);
   ASSERT_EQ(GetVisits().size(), 1u);
@@ -446,8 +490,9 @@
 // Then: 1 context annotation should be committed after step 4 w/ a
 //       `page_end_reason`.
 TEST_F(HistoryClustersTabHelperTest, NavigationWithUkmAfterDestroy) {
-  AddToHistory(GURL{"https://github.com"}, 1);
-  helper_->OnUpdatedHistoryForNavigation(0, GURL{"https://github.com"});
+  AddToHistory(GURL{"https://github.com"}, MakeTime(1));
+  helper_->OnUpdatedHistoryForNavigation(0, MakeTime(1),
+                                         GURL{"https://github.com"});
   history::BlockUntilHistoryProcessesPendingRequests(history_service_);
   helper_->TagNavigationAsExpectingUkmNavigationComplete(0);
 
@@ -484,9 +529,10 @@
 //       `page_end_reason`.
 TEST_F(HistoryClustersTabHelperTest,
        NavigationAfterUkmExpectAndWithUkmBeforeDestroy) {
-  AddToHistory(GURL{"https://github.com"}, 1);
+  AddToHistory(GURL{"https://github.com"}, MakeTime(1));
   helper_->TagNavigationAsExpectingUkmNavigationComplete(0);
-  helper_->OnUpdatedHistoryForNavigation(0, GURL{"https://github.com"});
+  helper_->OnUpdatedHistoryForNavigation(0, MakeTime(1),
+                                         GURL{"https://github.com"});
   history::BlockUntilHistoryProcessesPendingRequests(history_service_);
   ASSERT_EQ(GetVisits().size(), 1u);
   EXPECT_EQ(GetVisits()[0].url_row.url(), GURL{"https://github.com"});
@@ -513,8 +559,9 @@
 // `page_end_reason`.
 TEST_F(HistoryClustersTabHelperTest,
        NavigationWithUkmBeforeDestroyAndHistoryResolvedAfterDestroy) {
-  AddToHistory(GURL{"https://github.com"}, 1);
-  helper_->OnUpdatedHistoryForNavigation(0, GURL{"https://github.com"});
+  AddToHistory(GURL{"https://github.com"}, MakeTime(1));
+  helper_->OnUpdatedHistoryForNavigation(0, MakeTime(1),
+                                         GURL{"https://github.com"});
   helper_->TagNavigationAsExpectingUkmNavigationComplete(0);
   AddBookmark(GURL{"https://github.com"});
   helper_->OnUkmNavigationComplete(0, base::Seconds(20),
@@ -559,9 +606,10 @@
 //.      should have a `page_end_reason`.
 TEST_F(HistoryClustersTabHelperTest,
        TwoNavigationsWith1stUkmBefore2ndNavigation) {
-  AddToHistory(GURL{"https://google.com"}, 1);
-  AddToHistory(GURL{"https://github.com"}, 2);
-  helper_->OnUpdatedHistoryForNavigation(0, GURL{"https://github.com"});
+  AddToHistory(GURL{"https://google.com"}, MakeTime(1));
+  AddToHistory(GURL{"https://github.com"}, MakeTime(2));
+  helper_->OnUpdatedHistoryForNavigation(0, MakeTime(2),
+                                         GURL{"https://github.com"});
   history::BlockUntilHistoryProcessesPendingRequests(history_service_);
   helper_->TagNavigationAsExpectingUkmNavigationComplete(0);
 
@@ -574,7 +622,8 @@
   helper_->OnUkmNavigationComplete(0, base::Seconds(20),
                                    page_load_metrics::PageEndReason::END_OTHER);
 
-  helper_->OnUpdatedHistoryForNavigation(1, GURL{"https://google.com"});
+  helper_->OnUpdatedHistoryForNavigation(1, MakeTime(1),
+                                         GURL{"https://google.com"});
   history::BlockUntilHistoryProcessesPendingRequests(history_service_);
 
   DeleteContents();
@@ -601,12 +650,14 @@
 //.      should have a `page_end_reason`.
 TEST_F(HistoryClustersTabHelperTest,
        TwoNavigationsWith1stUkmAfter2ndNavigation) {
-  AddToHistory(GURL{"https://google.com"}, 1);
-  AddToHistory(GURL{"https://github.com"}, 2);
-  helper_->OnUpdatedHistoryForNavigation(0, GURL{"https://github.com"});
+  AddToHistory(GURL{"https://google.com"}, MakeTime(1));
+  AddToHistory(GURL{"https://github.com"}, MakeTime(2));
+  helper_->OnUpdatedHistoryForNavigation(0, MakeTime(2),
+                                         GURL{"https://github.com"});
   history::BlockUntilHistoryProcessesPendingRequests(history_service_);
   helper_->TagNavigationAsExpectingUkmNavigationComplete(0);
-  helper_->OnUpdatedHistoryForNavigation(1, GURL{"https://google.com"});
+  helper_->OnUpdatedHistoryForNavigation(1, MakeTime(1),
+                                         GURL{"https://google.com"});
   history::BlockUntilHistoryProcessesPendingRequests(history_service_);
   auto visits = GetVisits();
   ASSERT_EQ(visits.size(), 2u);
@@ -654,9 +705,10 @@
 // Then: 2 context annotations should be committed after steps 2 and 5; the 2nd
 //.      should have a `page_end_reason`.
 TEST_F(HistoryClustersTabHelperTest, TwoNavigations2ndUkmBefore2ndNavigation) {
-  AddToHistory(GURL{"https://google.com"}, 1);
-  AddToHistory(GURL{"https://github.com"}, 2);
-  helper_->OnUpdatedHistoryForNavigation(0, GURL{"https://github.com"});
+  AddToHistory(GURL{"https://google.com"}, MakeTime(1));
+  AddToHistory(GURL{"https://github.com"}, MakeTime(2));
+  helper_->OnUpdatedHistoryForNavigation(0, MakeTime(2),
+                                         GURL{"https://github.com"});
   history::BlockUntilHistoryProcessesPendingRequests(history_service_);
   auto visits = GetVisits();
   ASSERT_EQ(visits.size(), 2u);
@@ -672,7 +724,8 @@
   helper_->TagNavigationAsExpectingUkmNavigationComplete(1);
   EXPECT_EQ(GetVisits().size(), 2u);
 
-  helper_->OnUpdatedHistoryForNavigation(1, GURL{"https://google.com"});
+  helper_->OnUpdatedHistoryForNavigation(1, MakeTime(1),
+                                         GURL{"https://google.com"});
   history::BlockUntilHistoryProcessesPendingRequests(history_service_);
 
   // Invoke `OnUkmNavigationComplete()` after `WebContentsDestroyed()` is
diff --git a/chrome/browser/installable/installable_utils.cc b/chrome/browser/installable/installable_utils.cc
index 3e2c7b19..45168f62 100644
--- a/chrome/browser/installable/installable_utils.cc
+++ b/chrome/browser/installable/installable_utils.cc
@@ -29,7 +29,7 @@
   // that WebAppProvider is started.
   if (!provider || !provider->on_registry_ready().is_signaled())
     return false;
-  return provider->registrar().DoesScopeContainAnyApp(origin);
+  return provider->registrar_unsafe().DoesScopeContainAnyApp(origin);
 #endif
 }
 
@@ -44,7 +44,7 @@
   // that WebAppProvider is started.
   if (!provider || !provider->on_registry_ready().is_signaled())
     return std::set<GURL>();
-  const web_app::WebAppRegistrar& registrar = provider->registrar();
+  const web_app::WebAppRegistrar& registrar = provider->registrar_unsafe();
   auto app_ids = registrar.GetAppIds();
   std::set<GURL> installed_origins;
   for (auto& app_id : app_ids) {
diff --git a/chrome/browser/lacros/browser_launcher_browsertest.cc b/chrome/browser/lacros/browser_launcher_browsertest.cc
index e7b67e3..df8bf5ce 100644
--- a/chrome/browser/lacros/browser_launcher_browsertest.cc
+++ b/chrome/browser/lacros/browser_launcher_browsertest.cc
@@ -169,7 +169,7 @@
         profile_manager->GetPrimaryUserProfilePath());
 
     web_app::WebAppRegistrar& registrar =
-        web_app::WebAppProvider::GetForTest(profile)->registrar();
+        web_app::WebAppProvider::GetForTest(profile)->registrar_unsafe();
     for (auto& app_id : registrar.GetAppIds()) {
       web_app::test::UninstallWebApp(profile, app_id);
     }
diff --git a/chrome/browser/lookalikes/lookalike_url_navigation_throttle_unittest.cc b/chrome/browser/lookalikes/lookalike_url_navigation_throttle_unittest.cc
index bdb6c6e..a14daec 100644
--- a/chrome/browser/lookalikes/lookalike_url_navigation_throttle_unittest.cc
+++ b/chrome/browser/lookalikes/lookalike_url_navigation_throttle_unittest.cc
@@ -4,19 +4,44 @@
 
 #include "chrome/browser/lookalikes/lookalike_url_navigation_throttle.h"
 
+#include "base/test/scoped_feature_list.h"
 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
 #include "components/reputation/core/safety_tip_test_utils.h"
 #include "components/url_formatter/spoof_checks/idn_spoof_checker.h"
 #include "components/url_formatter/url_formatter.h"
 #include "content/public/test/mock_navigation_handle.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "url/url_features.h"
 
 namespace lookalikes {
 
-using LookalikeThrottleTest = ChromeRenderViewHostTestHarness;
+// IDNA mode to use in tests.
+enum class IDNAMode { kTransitional, kNonTransitional };
+
+class LookalikeThrottleTest : public testing::WithParamInterface<IDNAMode>,
+                              public ChromeRenderViewHostTestHarness {
+ public:
+  LookalikeThrottleTest() {
+    if (GetParam() == IDNAMode::kNonTransitional) {
+      scoped_feature_list_.InitAndEnableFeature(
+          url::kUseIDNA2008NonTransitional);
+    } else {
+      scoped_feature_list_.InitAndDisableFeature(
+          url::kUseIDNA2008NonTransitional);
+    }
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+INSTANTIATE_TEST_SUITE_P(All,
+                         LookalikeThrottleTest,
+                         ::testing::Values(IDNAMode::kTransitional,
+                                           IDNAMode::kNonTransitional));
 
 // Tests that spoofy hostnames are properly handled in the throttle.
-TEST_F(LookalikeThrottleTest, SpoofsBlocked) {
+TEST_P(LookalikeThrottleTest, SpoofsBlocked) {
   reputation::InitializeSafetyTipConfig();
 
   const struct TestCase {
@@ -55,9 +80,12 @@
       {"xn--vi8h.com", false,
        url_formatter::IDNSpoofChecker::Result::kICUSpoofChecks},
       // sparkasse-gießen.de, has a deviation character (ß). This is in punycode
-      // because GURL canonicalizes ß to ss.
+      // because GURL canonicalizes ß to ss. Safe in IDNA Non-Transitional mode,
+      // unsafe otherwise.
       {"xn--sparkasse-gieen-2ib.de", false,
-       url_formatter::IDNSpoofChecker::Result::kDeviationCharacters},
+       GetParam() == IDNAMode::kNonTransitional
+           ? url_formatter::IDNSpoofChecker::Result::kSafe
+           : url_formatter::IDNSpoofChecker::Result::kDeviationCharacters},
   };
 
   for (const TestCase& test_case : kTestCases) {
diff --git a/chrome/browser/media/encrypted_media_supported_types_browsertest.cc b/chrome/browser/media/encrypted_media_supported_types_browsertest.cc
index 90cf6c0..f897371b 100644
--- a/chrome/browser/media/encrypted_media_supported_types_browsertest.cc
+++ b/chrome/browser/media/encrypted_media_supported_types_browsertest.cc
@@ -137,13 +137,13 @@
 class EncryptedMediaSupportedTypesTest : public InProcessBrowserTest {
  protected:
   EncryptedMediaSupportedTypesTest() {
-    // TODO(crbug.com/1243903): WhatsNewUI might be causing timeouts.
-    disabled_features_.push_back(features::kChromeWhatsNewUI);
-
 #if BUILDFLAG(ENABLE_PLATFORM_HEVC)
     EnableFeature(media::kPlatformHEVCDecoderSupport);
 #endif  // BUILDFLAG(ENABLE_PLATFORM_HEVC)
 
+    // TODO(crbug.com/1243903): WhatsNewUI might be causing timeouts.
+    DisableFeature(features::kChromeWhatsNewUI);
+
     audio_webm_codecs_.push_back("vorbis");
 
     video_webm_codecs_.push_back("vp8");
@@ -245,6 +245,11 @@
     enabled_features_.push_back({feature, {}});
   }
 
+  // Disable a feature.
+  void DisableFeature(const base::Feature& feature) {
+    disabled_features_.push_back(feature);
+  }
+
   void SetUpDefaultCommandLine(base::CommandLine* command_line) override {
 #if BUILDFLAG(ENABLE_LIBRARY_CDMS)
     base::CommandLine default_command_line(base::CommandLine::NO_PROGRAM);
@@ -579,6 +584,7 @@
 #if BUILDFLAG(ENABLE_PLATFORM_ENCRYPTED_DOLBY_VISION)
     EnableFeature(media::kPlatformEncryptedDolbyVision);
 #endif
+    DisableFeature(media::kHardwareSecureDecryptionExperiment);
   }
 
   void SetUpCommandLine(base::CommandLine* command_line) override {
diff --git a/chrome/browser/media/router/discovery/access_code/access_code_cast_sink_service.cc b/chrome/browser/media/router/discovery/access_code/access_code_cast_sink_service.cc
index 7185a61..4803c20d 100644
--- a/chrome/browser/media/router/discovery/access_code/access_code_cast_sink_service.cc
+++ b/chrome/browser/media/router/discovery/access_code/access_code_cast_sink_service.cc
@@ -365,6 +365,15 @@
       // information already in the media router.
       StoreSinkInPrefs(&sink);
       SetExpirationTimer(&sink);
+
+      // Get the existing sink to we can update its info.
+      cast_media_sink_service_impl_->task_runner()->PostTaskAndReplyWithResult(
+          FROM_HERE,
+          base::BindOnce(&CastMediaSinkServiceImpl::GetSinkById,
+                         base::Unretained(cast_media_sink_service_impl_),
+                         sink.id()),
+          base::BindOnce(&AccessCodeCastSinkService::UpdateExistingSink,
+                         weak_ptr_factory_.GetWeakPtr(), sink));
     }
 
     std::move(add_sink_callback).Run(AddSinkResultCode::OK, sink.id());
@@ -742,6 +751,25 @@
       kExpirationDelay);
 }
 
+void AccessCodeCastSinkService::UpdateExistingSink(
+    const MediaSinkInternal& new_sink,
+    const MediaSinkInternal* existing_sink) {
+  // AddOrUpdate takes time, so avoid calling it if we can
+  if (existing_sink->sink().name() == new_sink.sink().name() &&
+      existing_sink->cast_data().capabilities ==
+          new_sink.cast_data().capabilities)
+    return;
+
+  MediaSinkInternal existing_sink_copy = *existing_sink;
+  existing_sink_copy.sink().set_name(new_sink.sink().name());
+  auto capabilities = new_sink.cast_data().capabilities;
+  existing_sink_copy.cast_data().capabilities = capabilities;
+  cast_media_sink_service_impl_->task_runner()->PostTask(
+      FROM_HERE, base::BindOnce(&CastMediaSinkServiceImpl::AddOrUpdateSink,
+                                base::Unretained(cast_media_sink_service_impl_),
+                                existing_sink_copy));
+}
+
 void AccessCodeCastSinkService::RemoveSinkIdFromAllEntries(
     const MediaSink::Id& sink_id) {
   pref_updater_->RemoveSinkIdFromDevicesDict(sink_id);
diff --git a/chrome/browser/media/router/discovery/access_code/access_code_cast_sink_service.h b/chrome/browser/media/router/discovery/access_code/access_code_cast_sink_service.h
index 034be6b0..bcee02ad 100644
--- a/chrome/browser/media/router/discovery/access_code/access_code_cast_sink_service.h
+++ b/chrome/browser/media/router/discovery/access_code/access_code_cast_sink_service.h
@@ -193,6 +193,8 @@
   FRIEND_TEST_ALL_PREFIXES(AccessCodeCastSinkServiceTest,
                            RefreshStoredDeviceInfo);
   FRIEND_TEST_ALL_PREFIXES(AccessCodeCastSinkServiceTest,
+                           RefreshExistingDeviceName);
+  FRIEND_TEST_ALL_PREFIXES(AccessCodeCastSinkServiceTest,
                            RefreshStoredDeviceTimer);
   FRIEND_TEST_ALL_PREFIXES(AccessCodeCastSinkServiceTest,
                            HandleMediaRouteAdded);
@@ -283,6 +285,9 @@
 
   void RemoveAndDisconnectExistingSinksOnNetwork();
 
+  void UpdateExistingSink(const MediaSinkInternal& new_sink,
+                          const MediaSinkInternal* exisitng_sink);
+
   // DiscoveryNetworkMonitor::Observer implementation
   void OnNetworksChanged(const std::string& network_id) override;
 
diff --git a/chrome/browser/media/router/discovery/access_code/access_code_cast_sink_service_unittest.cc b/chrome/browser/media/router/discovery/access_code/access_code_cast_sink_service_unittest.cc
index 05a9751..d8ef4fa8be 100644
--- a/chrome/browser/media/router/discovery/access_code/access_code_cast_sink_service_unittest.cc
+++ b/chrome/browser/media/router/discovery/access_code/access_code_cast_sink_service_unittest.cc
@@ -70,7 +70,7 @@
         message_handler_(mock_cast_socket_service_.get()),
         cast_media_sink_service_impl_(
             std::make_unique<MockCastMediaSinkServiceImpl>(
-                OnSinksDiscoveredCallback(),
+                mock_sink_discovered_cb_.Get(),
                 mock_cast_socket_service_.get(),
                 discovery_network_monitor_.get(),
                 &dual_media_sink_service_)) {
@@ -1319,6 +1319,43 @@
             CreateValueDictFromMediaSinkInternal(existing_sink_2));
 }
 
+TEST_F(AccessCodeCastSinkServiceTest, RefreshExistingDeviceName) {
+  SetDeviceDurationPrefForTest(base::Seconds(100));
+
+  // Create fake sinks for test. Importantly, new_sink_1 has the same id as
+  // existing_sink_1, but they have different names.
+  MediaSinkInternal existing_sink_1 = CreateCastSink(1);
+  MediaSinkInternal new_sink_1 = CreateCastSink(1);
+  const std::string fake_new_sink_name = "Fake new sink";
+  new_sink_1.sink().set_name(fake_new_sink_name);
+  EXPECT_EQ(existing_sink_1.id(), new_sink_1.id());
+  EXPECT_NE(existing_sink_1.sink().name(), new_sink_1.sink().name());
+
+  existing_sink_1.cast_data().discovery_type =
+      CastDiscoveryType::kAccessCodeManualEntry;
+  new_sink_1.cast_data().discovery_type =
+      CastDiscoveryType::kAccessCodeManualEntry;
+
+  // Add existing sinks to the mock media router and stored prefs. Since the
+  // CastMediaSinkService is mocked out, just manually set everything instead.
+  mock_cast_media_sink_service_impl()->AddSinkForTest(existing_sink_1);
+
+  mock_time_task_runner()->FastForwardUntilNoTasksRemain();
+
+  // Try to add new_sink_1, which has an id that matches an existing sink.
+  MockAddSinkResultCallback mock_callback;
+  access_code_cast_sink_service_->OpenChannelIfNecessary(
+      new_sink_1, mock_callback.Get(), /*has_sink=*/true);
+  FastForwardUiAndIoTasks();
+  mock_time_task_runner()->FastForwardUntilNoTasksRemain();
+
+  EXPECT_EQ(mock_cast_media_sink_service_impl()
+                ->GetSinkById(existing_sink_1.id())
+                ->sink()
+                .name(),
+            new_sink_1.sink().name());
+}
+
 TEST_F(AccessCodeCastSinkServiceTest, RefreshStoredDeviceTimer) {
   // If a device is already stored, then its timer is reset.
   SetDeviceDurationPrefForTest(base::Seconds(100));
diff --git a/chrome/browser/metrics/power/battery_discharge_reporter.cc b/chrome/browser/metrics/power/battery_discharge_reporter.cc
index 61bd158..74e7975 100644
--- a/chrome/browser/metrics/power/battery_discharge_reporter.cc
+++ b/chrome/browser/metrics/power/battery_discharge_reporter.cc
@@ -7,6 +7,7 @@
 #include <utility>
 
 #include "base/bind.h"
+#include "base/metrics/histogram_functions.h"
 #include "chrome/browser/metrics/power/power_metrics.h"
 #include "chrome/browser/metrics/power/process_metrics_recorder_util.h"
 #include "chrome/browser/metrics/power/usage_scenario.h"
@@ -73,6 +74,30 @@
     battery_discharge.mode = BatteryDischargeMode::kInvalidInterval;
   }
 
+#if BUILDFLAG(IS_WIN)
+  if (battery_discharge.mode == BatteryDischargeMode::kDischarging) {
+    base::UmaHistogramBoolean(
+        "Power.BatteryDischargeGranularityAvailable",
+        battery_state->battery_discharge_granularity.has_value());
+
+    if (battery_state->battery_discharge_granularity.has_value()) {
+      base::UmaHistogramCustomCounts(
+          "Power.BatteryDischargeGranularityMilliwattHours",
+          battery_state->battery_discharge_granularity.value(),
+          /*min=*/0, /*exclusive_max=*/20000,
+          /*buckets=*/50);
+
+      uint32_t granularity_relative =
+          battery_state->battery_discharge_granularity.value() * 10000 /
+          battery_state->full_charged_capacity.value();
+      base::UmaHistogramCustomCounts(
+          "Power.BatteryDischargeGranularityRelative", granularity_relative,
+          /*min=*/0, /*exclusive_max=*/20000,
+          /*buckets=*/50);
+    }
+  }
+#endif
+
   auto interval_data = battery_usage_scenario_data_store_->ResetIntervalData();
 
   // Get scenario data.
diff --git a/chrome/browser/metrics/power/battery_discharge_reporter_unittest.cc b/chrome/browser/metrics/power/battery_discharge_reporter_unittest.cc
index 7aa296a9..b9fcb78 100644
--- a/chrome/browser/metrics/power/battery_discharge_reporter_unittest.cc
+++ b/chrome/browser/metrics/power/battery_discharge_reporter_unittest.cc
@@ -466,3 +466,39 @@
       },
       BatteryDischargeMode::kBatteryLevelIncreased);
 }
+
+#if BUILDFLAG(IS_WIN)
+TEST_F(BatteryDischargeReporterTest, BatteryDischargeGranularity) {
+  TestUsageScenarioDataStoreImpl usage_scenario_data_store;
+
+  base::BatteryStateSampler battery_state_sampler(
+      std::make_unique<NoopSamplingEventSource>(),
+      std::make_unique<NoopBatteryLevelProvider>());
+  BatteryDischargeReporter battery_discharge_reporter(
+      &battery_state_sampler, &usage_scenario_data_store);
+
+  const int64_t kGranularityMilliwattHours = 10;
+  // Since the full charged capacity is 1000, a granularity of 10 is equal to
+  // one percent, or 100 hundredths of a percent.
+  const int64_t kGranularityRelative = 100;
+
+  const auto kBatteryState = base::BatteryLevelProvider::BatteryState{
+      .battery_count = 1,
+      .is_external_power_connected = false,
+      .current_capacity = 500,
+      .full_charged_capacity = 1000,
+      .charge_unit = base::BatteryLevelProvider::BatteryLevelUnit::kMWh,
+      .battery_discharge_granularity = kGranularityMilliwattHours,
+  };
+
+  battery_discharge_reporter.OnBatteryStateSampled(kBatteryState);
+  task_environment_.FastForwardBy(base::Minutes(1));
+  battery_discharge_reporter.OnBatteryStateSampled(kBatteryState);
+
+  histogram_tester_.ExpectUniqueSample(
+      "Power.BatteryDischargeGranularityMilliwattHours",
+      kGranularityMilliwattHours, 1);
+  histogram_tester_.ExpectUniqueSample(
+      "Power.BatteryDischargeGranularityRelative", kGranularityRelative, 1);
+}
+#endif  // BUILDFLAG(IS_WIN)
diff --git a/chrome/browser/metrics/power/power_metrics_reporter.cc b/chrome/browser/metrics/power/power_metrics_reporter.cc
index 38af8cedf..79f70a5 100644
--- a/chrome/browser/metrics/power/power_metrics_reporter.cc
+++ b/chrome/browser/metrics/power/power_metrics_reporter.cc
@@ -233,32 +233,6 @@
   auto battery_discharge = GetBatteryDischargeDuringInterval(
       previous_battery_state, new_battery_state, interval_duration);
 
-#if BUILDFLAG(IS_WIN)
-  if (new_battery_state.has_value() &&
-      new_battery_state->charge_unit.has_value() &&
-      new_battery_state->charge_unit.value() ==
-          base::BatteryLevelProvider::BatteryLevelUnit::kMWh) {
-    base::UmaHistogramBoolean(
-        "Power.BatteryDischargeGranularityAvailable",
-        new_battery_state->battery_discharge_granularity.has_value());
-
-    DCHECK_EQ(new_battery_state->battery_discharge_granularity.has_value(),
-              new_battery_state->max_battery_discharge_granularity.has_value());
-    if (new_battery_state->battery_discharge_granularity.has_value()) {
-      base::UmaHistogramCustomCounts(
-          "Power.BatteryDischargeGranularity",
-          new_battery_state->battery_discharge_granularity.value(),
-          /*min=*/0, /*exclusive_max=*/20000,
-          /*buckets=*/50);
-      base::UmaHistogramCustomCounts(
-          "Power.MaxBatteryDischargeGranularity",
-          new_battery_state->max_battery_discharge_granularity.value(),
-          /*min=*/0, /*exclusive_max=*/20000,
-          /*buckets=*/50);
-    }
-  }
-#endif
-
   // Get usage scenario data.
   auto long_interval_data =
       long_usage_scenario_data_store_->ResetIntervalData();
diff --git a/chrome/browser/metrics/power/power_metrics_reporter_unittest.cc b/chrome/browser/metrics/power/power_metrics_reporter_unittest.cc
index 709a7a36..90b96ad0 100644
--- a/chrome/browser/metrics/power/power_metrics_reporter_unittest.cc
+++ b/chrome/browser/metrics/power/power_metrics_reporter_unittest.cc
@@ -751,22 +751,3 @@
       "PerformanceMonitor.ResourceCoalition.CPUTime2_10sec", 6000, 1);
 }
 #endif  // BUILDFLAG(IS_MAC)
-
-#if BUILDFLAG(IS_WIN)
-TEST_F(PowerMetricsReporterUnitTest, BatteryDischargeGranularity) {
-  const uint32_t kGranularity = 20;
-  const uint32_t kMaxGranularity = 30;
-
-  battery_states_.push(base::BatteryLevelProvider::BatteryState{
-      .full_charged_capacity = 100,
-      .charge_unit = base::BatteryLevelProvider::BatteryLevelUnit::kMWh,
-      .battery_discharge_granularity = kGranularity,
-      .max_battery_discharge_granularity = kMaxGranularity});
-  task_environment_.FastForwardBy(kLongPowerMetricsIntervalDuration);
-
-  histogram_tester_.ExpectUniqueSample("Power.BatteryDischargeGranularity",
-                                       kGranularity, 1);
-  histogram_tester_.ExpectUniqueSample("Power.MaxBatteryDischargeGranularity",
-                                       kMaxGranularity, 1);
-}
-#endif  // BUILDFLAG(IS_WIN)
diff --git a/chrome/browser/nearby_sharing/nearby_sharing_service_impl.cc b/chrome/browser/nearby_sharing/nearby_sharing_service_impl.cc
index 0c314f15..d4d2539 100644
--- a/chrome/browser/nearby_sharing/nearby_sharing_service_impl.cc
+++ b/chrome/browser/nearby_sharing/nearby_sharing_service_impl.cc
@@ -1764,7 +1764,15 @@
     NS_LOG(INFO)
         << __func__
         << ": Shutdown Process timer fired, releasing process reference";
-    process_reference_.reset();
+
+    // Manually firing this callback will handle destroying
+    // |process_reference_|.
+    //
+    // The NearbyProcessManager would ordinarily be responsible for firing this
+    // callback, but it assumes that is unnecessary if the owner destroys the
+    // process reference, so we're responsible for calling it to ensure that
+    // downstream listeners are notified.
+    OnNearbyProcessStopped(NearbyProcessShutdownReason::kNormal);
   }
 }
 
diff --git a/chrome/browser/new_tab_page/chrome_colors/chrome_colors_service.h b/chrome/browser/new_tab_page/chrome_colors/chrome_colors_service.h
index 07e59fc..b8bddf34 100644
--- a/chrome/browser/new_tab_page/chrome_colors/chrome_colors_service.h
+++ b/chrome/browser/new_tab_page/chrome_colors/chrome_colors_service.h
@@ -44,8 +44,9 @@
 
   // Applies a theme that can be reverted by saving the previous theme state and
   // the |tab| that changes are made from.
-  void ApplyDefaultTheme(content::WebContents* tab);
-  void ApplyAutogeneratedTheme(SkColor color, content::WebContents* tab);
+  virtual void ApplyDefaultTheme(content::WebContents* tab);
+  virtual void ApplyAutogeneratedTheme(SkColor color,
+                                       content::WebContents* tab);
 
   // Reverts to the previous theme state before first Apply* was used. Called
   // because user action on colors menu.
@@ -57,7 +58,7 @@
 
   // Confirms current theme changes. Since the theme is already installed by
   // Apply*, this only clears the previously saved state.
-  void ConfirmThemeChanges();
+  virtual void ConfirmThemeChanges();
 
  private:
   friend class ::TestChromeColorsService;
diff --git a/chrome/browser/notifications/android/java/src/org/chromium/chrome/browser/notifications/NotificationIntentInterceptor.java b/chrome/browser/notifications/android/java/src/org/chromium/chrome/browser/notifications/NotificationIntentInterceptor.java
index 9ac76f1..6e393ce 100644
--- a/chrome/browser/notifications/android/java/src/org/chromium/chrome/browser/notifications/NotificationIntentInterceptor.java
+++ b/chrome/browser/notifications/android/java/src/org/chromium/chrome/browser/notifications/NotificationIntentInterceptor.java
@@ -155,6 +155,12 @@
             intent.putExtra(EXTRA_ACTION_TYPE, intentId);
         }
 
+        // This flag ensures the TrampolineActivity won't trigger ChromeActivity's auto enter
+        // picture-in-picture.
+        if (!shouldUseBroadcast && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+            intent.addFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION);
+        }
+
         // This flag ensures the broadcast is delivered with foreground priority to speed up the
         // broadcast delivery.
         if (shouldUseBroadcast && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
diff --git a/chrome/browser/notifications/platform_notification_service_impl.cc b/chrome/browser/notifications/platform_notification_service_impl.cc
index cd97395..a3bc7c2 100644
--- a/chrome/browser/notifications/platform_notification_service_impl.cc
+++ b/chrome/browser/notifications/platform_notification_service_impl.cc
@@ -556,13 +556,14 @@
       web_app::WebAppProvider::GetForLocalAppsUnchecked(profile_);
   if (web_app_provider) {
     const absl::optional<web_app::AppId> app_id =
-        web_app_provider->registrar().FindAppWithUrlInScope(web_app_hint_url);
+        web_app_provider->registrar_unsafe().FindAppWithUrlInScope(
+            web_app_hint_url);
     if (app_id) {
       absl::optional<WebAppIconAndTitle> icon_and_title;
       icon_and_title.emplace();
 
       icon_and_title->title = base::UTF8ToUTF16(
-          web_app_provider->registrar().GetAppShortName(*app_id));
+          web_app_provider->registrar_unsafe().GetAppShortName(*app_id));
       icon_and_title->icon =
           web_app_provider->icon_manager().GetMonochromeFavicon(*app_id);
       return icon_and_title;
diff --git a/chrome/browser/optimization_guide/page_content_annotations_service_browsertest.cc b/chrome/browser/optimization_guide/page_content_annotations_service_browsertest.cc
index 5ba1ebc..10ecdc8b 100644
--- a/chrome/browser/optimization_guide/page_content_annotations_service_browsertest.cc
+++ b/chrome/browser/optimization_guide/page_content_annotations_service_browsertest.cc
@@ -640,77 +640,6 @@
   }
 }
 
-// Flaky timeout in debug builds (crbug.com/1338408).
-// TODO(crbug.com/1365619): Flaky on several OSes in non-debug.
-IN_PROC_BROWSER_TEST_F(PageContentAnnotationsServiceBrowserTest,
-                       DISABLED_OgImagePresent) {
-  base::HistogramTester histogram_tester;
-  ukm::TestAutoSetUkmRecorder ukm_recorder;
-
-  GURL url(embedded_test_server()->GetURL("a.com", "/og_image.html"));
-  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
-
-  // Value taken from SalientImageAvailability enum.
-  static const int kAvailableFromOgImage = 3;
-
-  RetryForHistogramUntilCountReached(
-      &histogram_tester,
-      "OptimizationGuide.PageContentAnnotations.SalientImageAvailability", 1);
-
-  histogram_tester.ExpectBucketCount(
-      "OptimizationGuide.PageContentAnnotations.SalientImageAvailability",
-      kAvailableFromOgImage, 1);
-
-  std::vector<const ukm::mojom::UkmEntry*> entries =
-      ukm_recorder.GetEntriesByName(
-          ukm::builders::SalientImageAvailability::kEntryName);
-  ASSERT_EQ(1u, entries.size());
-
-  ASSERT_EQ(1u, entries[0]->metrics.size());
-  EXPECT_EQ(kAvailableFromOgImage, entries[0]->metrics.begin()->second);
-}
-
-// Flaky timeout in debug builds (crbug.com/1338408).
-// TODO(crbug.com/1365619): Flaky on several OSes in non-debug.
-IN_PROC_BROWSER_TEST_F(PageContentAnnotationsServiceBrowserTest,
-                       DISABLED_OgImagePresentButMalformed) {
-  base::HistogramTester histogram_tester;
-
-  GURL url(embedded_test_server()->GetURL("a.com", "/og_image_malformed.html"));
-  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
-
-  RetryForHistogramUntilCountReached(
-      &histogram_tester,
-      "OptimizationGuide.PageContentAnnotations.SalientImageAvailability", 1);
-
-  // Malformed URL is also reported as og image unavailable.
-  histogram_tester.ExpectBucketCount(
-      "OptimizationGuide.PageContentAnnotations.SalientImageAvailability", 1,
-      1);
-}
-
-#if !defined(NDEBUG)
-// Flaky timeout in debug builds (crbug.com/1338408).
-#define MAYBE_OgImageNotPresent DISABLED_OgImageNotPresent
-#else
-#define MAYBE_OgImageNotPresent OgImageNotPresent
-#endif
-IN_PROC_BROWSER_TEST_F(PageContentAnnotationsServiceBrowserTest,
-                       MAYBE_OgImageNotPresent) {
-  base::HistogramTester histogram_tester;
-
-  GURL url(embedded_test_server()->GetURL("a.com", "/no_og_image.html"));
-  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
-
-  RetryForHistogramUntilCountReached(
-      &histogram_tester,
-      "OptimizationGuide.PageContentAnnotations.SalientImageAvailability", 1);
-
-  histogram_tester.ExpectBucketCount(
-      "OptimizationGuide.PageContentAnnotations.SalientImageAvailability", 1,
-      1);
-}
-
 class PageContentAnnotationsServiceRemoteMetadataBrowserTest
     : public PageContentAnnotationsServiceBrowserTest {
  public:
diff --git a/chrome/browser/page_load_metrics/integration_tests/interaction_to_next_paint_browsertest.cc b/chrome/browser/page_load_metrics/integration_tests/interaction_to_next_paint_browsertest.cc
new file mode 100644
index 0000000..72ff9c1
--- /dev/null
+++ b/chrome/browser/page_load_metrics/integration_tests/interaction_to_next_paint_browsertest.cc
@@ -0,0 +1,257 @@
+// 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/page_load_metrics/integration_tests/metric_integration_test.h"
+
+#include "base/test/trace_event_analyzer.h"
+#include "build/build_config.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "components/page_load_metrics/browser/page_load_metrics_test_waiter.h"
+#include "components/page_load_metrics/browser/page_load_metrics_util.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/hit_test_region_observer.h"
+#include "services/metrics/public/cpp/ukm_builders.h"
+
+using absl::optional;
+using base::Bucket;
+using base::Value;
+using trace_analyzer::Query;
+using trace_analyzer::TraceAnalyzer;
+using trace_analyzer::TraceEventVector;
+using ukm::builders::PageLoad;
+
+class InteractionToNextPaintTest : public MetricIntegrationTest {
+ protected:
+  // This function will extract the target UKM value from ukm_recorder
+  // by the given metric_name in PageLoad.
+  bool ExtractUKMPageLoadMetric(const ukm::TestUkmRecorder& ukm_recorder,
+                                base::StringPiece metric_name,
+                                int64_t* extracted_value);
+
+  // This function extract the maximum duration for EventTiming from
+  // trace data.
+  int ExtractMaxInteractionDurationFromTrace(TraceEventVector events);
+
+  // This function will extract and compare the INP values in TraceAnalyzer
+  // and UKM.
+  bool VerifyUKMAndTraceData(TraceAnalyzer& analyzer);
+
+  // Create Performance Observers to observe first input and events in the
+  // program. We are leveraging the Performance Observer to ensure we
+  // received the input in renderer.
+  void InjectWaitJavaScript();
+};
+
+bool InteractionToNextPaintTest::ExtractUKMPageLoadMetric(
+    const ukm::TestUkmRecorder& ukm_recorder,
+    base::StringPiece metric_name,
+    int64_t* extracted_value) {
+  std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries =
+      ukm_recorder.GetMergedEntriesByName(PageLoad::kEntryName);
+  const auto& kv = merged_entries.begin();
+  auto* metric_value =
+      ukm::TestUkmRecorder::GetEntryMetric(kv->second.get(), metric_name);
+  if (!metric_value)
+    return false;
+  *extracted_value = *metric_value;
+  return true;
+}
+
+int InteractionToNextPaintTest::ExtractMaxInteractionDurationFromTrace(
+    TraceEventVector events) {
+  int max_duration = 0;
+  int sizeOfEvents = (int)events.size();
+  for (int i = 0; i < sizeOfEvents; i++) {
+    auto* traceEvent = events[i];
+
+    // If the traceEvent doesn't contain args data, it is not
+    // one of pointerdown, pointerup and click.
+    if (traceEvent->HasDictArg("data")) {
+      Value::Dict data = traceEvent->GetKnownArgAsDict("data");
+
+      // INP only consider the events with interactionID greater than 0.
+      std::string* event_name = data.FindString("type");
+      if ((*event_name == "pointerdown" || *event_name == "pointerup" ||
+           *event_name == "click") &&
+          data.FindInt("interactionId").value_or(-1) > 0) {
+        int duration = (int)*(data.FindDouble("duration"));
+
+        // Ensure the max_duration carries the largest duration out of
+        // pointerdown, pointerup and click.
+        max_duration = fmax(max_duration, duration);
+      }
+    }
+  }
+  return max_duration;
+}
+
+bool InteractionToNextPaintTest::VerifyUKMAndTraceData(
+    TraceAnalyzer& analyzer) {
+  TraceEventVector events;
+
+  // Extract the events by name EventTiming.
+  analyzer.FindEvents(Query::EventNameIs("EventTiming"), &events);
+
+  // max_duration is used to record the maximum duration out of
+  // pointerdown, pointerup and click.
+  int max_duration = ExtractMaxInteractionDurationFromTrace(events);
+
+  // Extract the UKM INP values from ukm_recorder.
+  int64_t INP_numOfInteraction_value;
+  int64_t INP_100th_value;
+  int64_t INP_98th_value;
+
+  bool extract_num_of_interaction = ExtractUKMPageLoadMetric(
+      ukm_recorder(),
+      ukm::builders::PageLoad::kInteractiveTiming_NumInteractionsName,
+      &INP_numOfInteraction_value);
+  bool extract_worst_interaction = ExtractUKMPageLoadMetric(
+      ukm_recorder(),
+      ukm::builders::PageLoad::
+          kInteractiveTiming_WorstUserInteractionLatency_MaxEventDurationName,
+      &INP_100th_value);
+  bool extract_98th_interaction = ExtractUKMPageLoadMetric(
+      ukm_recorder(),
+      ukm::builders::PageLoad::
+          kInteractiveTiming_UserInteractionLatency_HighPercentile2_MaxEventDurationName,
+      &INP_98th_value);
+
+  // Ensure the UKM contains all three values.
+  if (!extract_num_of_interaction || !extract_worst_interaction ||
+      !extract_98th_interaction) {
+    return false;
+  }
+
+  // Since the INP value takes 98th percentile all interactions,
+  // the 98th percentile and 100th percentile should be the same when
+  // we have less than 50 interactions.
+  EXPECT_EQ(INP_98th_value, INP_100th_value);
+
+  // The duration value in trace data is rounded to 8md
+  // which means the value before rounding should be in the
+  // range of plus and minus 8ms of the rounded value.
+  EXPECT_GE(max_duration, INP_98th_value - 8);
+  EXPECT_LE(max_duration, INP_98th_value + 8);
+
+  // Ensure that we only have one interaction in UKM.
+  EXPECT_EQ(INP_numOfInteraction_value, 1);
+  return true;
+}
+
+void InteractionToNextPaintTest::InjectWaitJavaScript() {
+  EXPECT_TRUE(ExecJs(web_contents(), R"(
+   waitForClick = async () => {
+      const observePromise = new Promise(resolve => {
+        new PerformanceObserver(e => {
+          e.getEntries().forEach(entry => {
+            resolve(true);
+          })
+        }).observe({type: 'first-input', buffered: true});
+      });
+      return await observePromise;
+    };
+
+    let eventCounts = {mouseup: 0, pointerup: 0, click: 0};
+    let eventPromise;
+    waitForEvents = () => {
+      eventPromise = new Promise(resolve => {
+        for (let evt in eventCounts) {
+          window.addEventListener(evt, function(e) {
+            eventCounts[e.type]++;
+            if (eventCounts.click == 1 &&
+                eventCounts.pointerup == 1 &&
+                eventCounts.mouseup == 1) {
+              resolve(true);
+            }
+          });
+        }
+      });
+      return true;
+    };
+    runwaitForEvents = async () => {
+      return await eventPromise;
+    };
+  )"));
+}
+
+IN_PROC_BROWSER_TEST_F(InteractionToNextPaintTest, INP_ClickOnPage) {
+  // Add waiter to wait for the interaction is arrived in browser.
+  auto waiter = std::make_unique<page_load_metrics::PageLoadMetricsTestWaiter>(
+      web_contents());
+  waiter->AddNumInteractionsExpectation(1);
+
+  // Start tracing to record tracing data.
+  StartTracing({"devtools.timeline"});
+  LoadHTML(R"HTML(
+  <p>Sample website</p>
+  )HTML");
+
+  // Create Performance Observers to observe first input and events in the
+  // program.
+  InjectWaitJavaScript();
+  ASSERT_TRUE(EvalJs(web_contents(), "waitForEvents()").ExtractBool());
+
+  // We should wait for the main frame's hit-test data to be ready before
+  // sending the click event below to avoid flakiness.
+  content::WaitForHitTestData(web_contents()->GetPrimaryMainFrame());
+  // Ensure the compositor thread is aware of the mouse events.
+  content::MainThreadFrameObserver frame_observer(GetRenderWidgetHost());
+  frame_observer.Wait();
+
+  // Simulate a click on div which has no renderer actions as our interaction.
+  content::SimulateMouseClick(web_contents(), 0,
+                              blink::WebMouseEvent::Button::kLeft);
+
+  // Start the waitForClick Performance Observer.
+  ASSERT_TRUE(EvalJs(web_contents(), "waitForClick()").ExtractBool());
+  ASSERT_TRUE(EvalJs(web_contents(), "runwaitForEvents()").ExtractBool());
+  waiter->Wait();
+
+  // Navigate to blank page to ensure the data gets flushed from renderer to
+  // browser.
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank")));
+
+  auto analyzer = StopTracingAndAnalyze();
+  ASSERT_TRUE(VerifyUKMAndTraceData(*analyzer));
+}
+
+IN_PROC_BROWSER_TEST_F(InteractionToNextPaintTest, INP_ClickOnButton) {
+  // Add waiter to wait for the interaction is arrived in browser.
+  auto waiter = std::make_unique<page_load_metrics::PageLoadMetricsTestWaiter>(
+      web_contents());
+  waiter->AddNumInteractionsExpectation(1);
+
+  // Start tracing to record tracing data.
+  StartTracing({"devtools.timeline"});
+  LoadHTML(R"HTML(
+    <button id="button">Click me</button>
+  )HTML");
+
+  // Create Performance Observers to observe first input and events in the
+  // program.
+  InjectWaitJavaScript();
+
+  ASSERT_TRUE(EvalJs(web_contents(), "waitForEvents()").ExtractBool());
+  // We should wait for the main frame's hit-test data to be ready before
+  // sending the click event below to avoid flakiness.
+  content::WaitForHitTestData(web_contents()->GetPrimaryMainFrame());
+  // Ensure the compositor thread is aware of the mouse events.
+  content::MainThreadFrameObserver frame_observer(GetRenderWidgetHost());
+  frame_observer.Wait();
+
+  // Simulate a click on button which has default browser-driven presentation.
+  content::SimulateMouseClickOrTapElementWithId(web_contents(), "button");
+
+  // Start the waitForClick Performance Observer.
+  ASSERT_TRUE(EvalJs(web_contents(), "waitForClick()").ExtractBool());
+  ASSERT_TRUE(EvalJs(web_contents(), "runwaitForEvents()").ExtractBool());
+  waiter->Wait();
+
+  // Navigate to blank page to ensure the data gets flushed from renderer to
+  // browser.
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank")));
+
+  auto analyzer = StopTracingAndAnalyze();
+  ASSERT_TRUE(VerifyUKMAndTraceData(*analyzer));
+}
diff --git a/chrome/browser/page_load_metrics/integration_tests/sources.gni b/chrome/browser/page_load_metrics/integration_tests/sources.gni
index 6be8d24b..7743216 100644
--- a/chrome/browser/page_load_metrics/integration_tests/sources.gni
+++ b/chrome/browser/page_load_metrics/integration_tests/sources.gni
@@ -7,6 +7,7 @@
   "//chrome/browser/page_load_metrics/integration_tests/event_counts_browsertest.cc",
   "//chrome/browser/page_load_metrics/integration_tests/first_input_delay_browsertest.cc",
   "//chrome/browser/page_load_metrics/integration_tests/first_scroll_delay_browsertest.cc",
+  "//chrome/browser/page_load_metrics/integration_tests/interaction_to_next_paint_browsertest.cc",
   "//chrome/browser/page_load_metrics/integration_tests/largest_contentful_paint_browsertest.cc",
   "//chrome/browser/page_load_metrics/integration_tests/layout_instability_browsertest.cc",
   "//chrome/browser/page_load_metrics/integration_tests/metric_integration_test.cc",
diff --git a/chrome/browser/password_manager/android/BUILD.gn b/chrome/browser/password_manager/android/BUILD.gn
index a0036a91..4342ccc 100644
--- a/chrome/browser/password_manager/android/BUILD.gn
+++ b/chrome/browser/password_manager/android/BUILD.gn
@@ -55,8 +55,14 @@
     "password_store_android_backend.h",
     "password_store_android_backend_api_error_codes.h",
     "password_store_android_backend_bridge.h",
+    "password_store_android_backend_bridge_helper.h",
+    "password_store_android_backend_bridge_helper_impl.cc",
+    "password_store_android_backend_bridge_helper_impl.h",
     "password_store_android_backend_bridge_impl.cc",
     "password_store_android_backend_bridge_impl.h",
+    "password_store_android_backend_consumer_bridge.h",
+    "password_store_android_backend_consumer_bridge_impl.cc",
+    "password_store_android_backend_consumer_bridge_impl.h",
     "password_store_operation_target.h",
     "password_sync_controller_delegate_android.cc",
     "password_sync_controller_delegate_android.h",
@@ -139,6 +145,7 @@
     "java/src/org/chromium/chrome/browser/password_manager/PasswordSettingsUpdaterBridge.java",
     "java/src/org/chromium/chrome/browser/password_manager/PasswordSettingsUpdaterMetricsRecorder.java",
     "java/src/org/chromium/chrome/browser/password_manager/PasswordStoreAndroidBackendBridgeImpl.java",
+    "java/src/org/chromium/chrome/browser/password_manager/PasswordStoreAndroidBackendConsumerBridgeImpl.java",
     "java/src/org/chromium/chrome/browser/password_manager/PasswordStoreBridge.java",
     "java/src/org/chromium/chrome/browser/password_manager/PasswordStoreCredential.java",
     "java/src/org/chromium/chrome/browser/password_manager/PasswordSyncControllerDelegateBridgeImpl.java",
@@ -165,6 +172,7 @@
     "java/src/org/chromium/chrome/browser/password_manager/PasswordScriptsFetcherBridge.java",
     "java/src/org/chromium/chrome/browser/password_manager/PasswordSettingsUpdaterBridge.java",
     "java/src/org/chromium/chrome/browser/password_manager/PasswordStoreAndroidBackendBridgeImpl.java",
+    "java/src/org/chromium/chrome/browser/password_manager/PasswordStoreAndroidBackendConsumerBridgeImpl.java",
     "java/src/org/chromium/chrome/browser/password_manager/PasswordStoreBridge.java",
     "java/src/org/chromium/chrome/browser/password_manager/PasswordStoreCredential.java",
     "java/src/org/chromium/chrome/browser/password_manager/PasswordSyncControllerDelegateBridgeImpl.java",
@@ -187,6 +195,7 @@
     "junit/src/org/chromium/chrome/browser/password_manager/PasswordSettingsAccessorFactoryTest.java",
     "junit/src/org/chromium/chrome/browser/password_manager/PasswordSettingsUpdaterBridgeTest.java",
     "junit/src/org/chromium/chrome/browser/password_manager/PasswordStoreAndroidBackendBridgeTest.java",
+    "junit/src/org/chromium/chrome/browser/password_manager/PasswordStoreAndroidBackendConsumerBridgeTest.java",
     "junit/src/org/chromium/chrome/browser/password_manager/PasswordSyncControllerDelegateBridgeTest.java",
     "junit/src/org/chromium/chrome/browser/password_manager/settings/PasswordReauthenticationFragmentTest.java",
     "junit/src/org/chromium/chrome/browser/password_manager/settings/ReauthenticationManagerTest.java",
@@ -416,6 +425,7 @@
     "password_generation_controller_impl_unittest.cc",
     "password_manager_error_message_delegate_unittest.cc",
     "password_manager_settings_service_android_impl_unittest.cc",
+    "password_store_android_backend_bridge_helper_impl_unittest.cc",
     "password_store_android_backend_unittest.cc",
     "password_sync_controller_delegate_android_unittest.cc",
   ]
diff --git a/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/PasswordStoreAndroidBackendBridgeImpl.java b/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/PasswordStoreAndroidBackendBridgeImpl.java
index f09ab20..154fb49 100644
--- a/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/PasswordStoreAndroidBackendBridgeImpl.java
+++ b/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/PasswordStoreAndroidBackendBridgeImpl.java
@@ -9,51 +9,45 @@
 
 import org.chromium.base.ContextUtils;
 import org.chromium.base.annotations.CalledByNative;
-import org.chromium.base.annotations.NativeMethods;
+import org.chromium.base.annotations.JNINamespace;
+import org.chromium.base.task.PostTask;
 import org.chromium.chrome.browser.notifications.NotificationConstants;
 import org.chromium.chrome.browser.notifications.NotificationUmaTracker;
 import org.chromium.chrome.browser.notifications.NotificationWrapperBuilderFactory;
 import org.chromium.chrome.browser.notifications.channels.ChromeChannelDefinitions.ChannelId;
+import org.chromium.chrome.browser.password_manager.PasswordStoreAndroidBackendConsumerBridgeImpl.JobId;
 import org.chromium.components.browser_ui.notifications.NotificationManagerProxy;
 import org.chromium.components.browser_ui.notifications.NotificationManagerProxyImpl;
 import org.chromium.components.browser_ui.notifications.NotificationMetadata;
 import org.chromium.components.browser_ui.notifications.NotificationWrapper;
 import org.chromium.components.browser_ui.notifications.NotificationWrapperBuilder;
 import org.chromium.components.signin.AccountUtils;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
 import java.util.Optional;
 
 /**
  * Java-counterpart of the native PasswordStoreAndroidBackendBridgeImpl. It's part of the password
  * store backend that forwards password store operations to a downstream implementation.
  */
+@JNINamespace("password_manager")
 class PasswordStoreAndroidBackendBridgeImpl {
-    /**
-     * Each operation sent to the passwords API will be assigned a JobId. The native side uses
-     * this ID to map an API response to the job that invoked it.
-     */
-    @Target(ElementType.TYPE_USE)
-    @Retention(RetentionPolicy.SOURCE)
-    @interface JobId {}
-
     private final PasswordStoreAndroidBackend mBackend;
-    private long mNativeBackendBridge;
+    private final PasswordStoreAndroidBackendConsumerBridgeImpl mConsumerBridge;
 
     PasswordStoreAndroidBackendBridgeImpl(
-            long nativeBackendBridge, PasswordStoreAndroidBackend backend) {
-        mNativeBackendBridge = nativeBackendBridge;
+            PasswordStoreAndroidBackendConsumerBridgeImpl consumerBridge,
+            PasswordStoreAndroidBackend backend) {
+        mConsumerBridge = consumerBridge;
         mBackend = backend;
         assert mBackend != null;
     }
 
     @CalledByNative
-    static PasswordStoreAndroidBackendBridgeImpl create(long nativeBackendBridge) {
-        return new PasswordStoreAndroidBackendBridgeImpl(nativeBackendBridge,
-                PasswordStoreAndroidBackendFactory.getInstance().createBackend());
+    static PasswordStoreAndroidBackendBridgeImpl create(
+            PasswordStoreAndroidBackendConsumerBridgeImpl consumerBridge) {
+        return new PasswordStoreAndroidBackendBridgeImpl(
+                consumerBridge, PasswordStoreAndroidBackendFactory.getInstance().createBackend());
     }
 
     @CalledByNative
@@ -63,70 +57,59 @@
 
     @CalledByNative
     void getAllLogins(@JobId int jobId, String syncingAccount) {
-        mBackend.getAllLogins(getAccount(syncingAccount), passwords -> {
-            if (mNativeBackendBridge == 0) return;
-            PasswordStoreAndroidBackendBridgeImplJni.get().onCompleteWithLogins(
-                    mNativeBackendBridge, jobId, passwords);
-        }, exception -> handleAndroidBackendException(jobId, exception));
+        mBackend.getAllLogins(getAccount(syncingAccount),
+                passwords
+                -> mConsumerBridge.onCompleteWithLogins(jobId, passwords),
+                exception -> handleAndroidBackendExceptionOnUiThread(jobId, exception));
     }
 
     @CalledByNative
     void getAutofillableLogins(@JobId int jobId, String syncingAccount) {
-        mBackend.getAutofillableLogins(getAccount(syncingAccount), passwords -> {
-            if (mNativeBackendBridge == 0) return;
-            PasswordStoreAndroidBackendBridgeImplJni.get().onCompleteWithLogins(
-                    mNativeBackendBridge, jobId, passwords);
-        }, exception -> handleAndroidBackendException(jobId, exception));
+        mBackend.getAutofillableLogins(getAccount(syncingAccount),
+                passwords
+                -> mConsumerBridge.onCompleteWithLogins(jobId, passwords),
+                exception -> handleAndroidBackendExceptionOnUiThread(jobId, exception));
     }
 
     @CalledByNative
     void getLoginsForSignonRealm(@JobId int jobId, String signonRealm, String syncingAccount) {
-        mBackend.getLoginsForSignonRealm(signonRealm, getAccount(syncingAccount), passwords -> {
-            if (mNativeBackendBridge == 0) return;
-            PasswordStoreAndroidBackendBridgeImplJni.get().onCompleteWithLogins(
-                    mNativeBackendBridge, jobId, passwords);
-        }, exception -> handleAndroidBackendException(jobId, exception));
+        mBackend.getLoginsForSignonRealm(signonRealm, getAccount(syncingAccount),
+                passwords
+                -> mConsumerBridge.onCompleteWithLogins(jobId, passwords),
+                exception -> handleAndroidBackendExceptionOnUiThread(jobId, exception));
     }
 
     @CalledByNative
     void addLogin(@JobId int jobId, byte[] pwdWithLocalData, String syncingAccount) {
-        mBackend.addLogin(pwdWithLocalData, getAccount(syncingAccount), () -> {
-            if (mNativeBackendBridge == 0) return;
-            PasswordStoreAndroidBackendBridgeImplJni.get().onLoginChanged(
-                    mNativeBackendBridge, jobId);
-        }, exception -> handleAndroidBackendException(jobId, exception));
+        mBackend.addLogin(pwdWithLocalData, getAccount(syncingAccount),
+                ()
+                        -> mConsumerBridge.onLoginChanged(jobId),
+                exception -> handleAndroidBackendExceptionOnUiThread(jobId, exception));
     }
 
     @CalledByNative
     void updateLogin(@JobId int jobId, byte[] pwdWithLocalData, String syncingAccount) {
-        mBackend.updateLogin(pwdWithLocalData, getAccount(syncingAccount), () -> {
-            if (mNativeBackendBridge == 0) return;
-            PasswordStoreAndroidBackendBridgeImplJni.get().onLoginChanged(
-                    mNativeBackendBridge, jobId);
-        }, exception -> handleAndroidBackendException(jobId, exception));
+        mBackend.updateLogin(pwdWithLocalData, getAccount(syncingAccount),
+                ()
+                        -> mConsumerBridge.onLoginChanged(jobId),
+                exception -> handleAndroidBackendExceptionOnUiThread(jobId, exception));
     }
 
     @CalledByNative
     void removeLogin(@JobId int jobId, byte[] pwdSpecificsData, String syncingAccount) {
-        mBackend.removeLogin(pwdSpecificsData, getAccount(syncingAccount), () -> {
-            if (mNativeBackendBridge == 0) return;
-            PasswordStoreAndroidBackendBridgeImplJni.get().onLoginChanged(
-                    mNativeBackendBridge, jobId);
-        }, exception -> handleAndroidBackendException(jobId, exception));
+        mBackend.removeLogin(pwdSpecificsData, getAccount(syncingAccount),
+                ()
+                        -> mConsumerBridge.onLoginChanged(jobId),
+                exception -> handleAndroidBackendExceptionOnUiThread(jobId, exception));
     }
 
-    private void handleAndroidBackendException(@JobId int jobId, Exception exception) {
-        if (mNativeBackendBridge == 0) return;
-
-        @AndroidBackendErrorType
-        int error = PasswordManagerAndroidBackendUtil.getBackendError(exception);
-        int apiErrorCode = PasswordManagerAndroidBackendUtil.getApiErrorCode(exception);
-        Integer connectionResultCode =
-                PasswordManagerAndroidBackendUtil.getConnectionResultCode(exception);
-
-        PasswordStoreAndroidBackendBridgeImplJni.get().onError(mNativeBackendBridge, jobId, error,
-                apiErrorCode, connectionResultCode != null,
-                connectionResultCode == null ? -1 : connectionResultCode.intValue());
+    private void handleAndroidBackendExceptionOnUiThread(@JobId int jobId, Exception exception) {
+        // Error callback could be either triggered
+        // - by the GMS Core on the UI thread
+        // - by the password store downstream backend on the operation thread
+        // |runOrPostTask| ensures callback will always be executed on the UI thread.
+        PostTask.runOrPostTask(UiThreadTaskTraits.DEFAULT,
+                () -> mConsumerBridge.handleAndroidBackendException(jobId, exception));
     }
 
     private Optional<Account> getAccount(String syncingAccount) {
@@ -134,8 +117,15 @@
         return Optional.of(AccountUtils.createAccountFromName(syncingAccount));
     }
 
+    // This method interacts with the UI and should be executed on the UI thread. Native bridge
+    // however does not have JNIEnv for UI thread and calls this method on background thread.
+    // Operation is reposted on the default UI sequence for execution.
     @CalledByNative
     private void showErrorUi() {
+        PostTask.postTask(UiThreadTaskTraits.USER_VISIBLE, () -> showErrorUiOnMainThread());
+    }
+
+    private void showErrorUiOnMainThread() {
         Context context = ContextUtils.getApplicationContext();
         // The context can sometimes be null in tests.
         if (context == null) return;
@@ -159,19 +149,4 @@
                 notificationWrapperBuilder.buildWithBigTextStyle(contents);
         notificationManager.notify(notification);
     }
-
-    @CalledByNative
-    private void destroy() {
-        mNativeBackendBridge = 0;
-    }
-
-    @NativeMethods
-    interface Natives {
-        void onCompleteWithLogins(long nativePasswordStoreAndroidBackendBridgeImpl,
-                @JobId int jobId, byte[] passwords);
-        void onLoginChanged(long nativePasswordStoreAndroidBackendBridgeImpl, @JobId int jobId);
-        void onError(long nativePasswordStoreAndroidBackendBridgeImpl, @JobId int jobId,
-                int errorType, int apiErrorCode, boolean hasConnectionResult,
-                int connectionResultStatusCode);
-    }
 }
diff --git a/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/PasswordStoreAndroidBackendConsumerBridgeImpl.java b/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/PasswordStoreAndroidBackendConsumerBridgeImpl.java
new file mode 100644
index 0000000..eb48415
--- /dev/null
+++ b/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/PasswordStoreAndroidBackendConsumerBridgeImpl.java
@@ -0,0 +1,89 @@
+// 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.password_manager;
+
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.JNINamespace;
+import org.chromium.base.annotations.NativeMethods;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Java-counterpart of the native PasswordStoreAndroidBackendConsumerBridgeImpl. It's part of the
+ * password store backend that forwards operation callbacks to the native password manager.
+ */
+@JNINamespace("password_manager")
+class PasswordStoreAndroidBackendConsumerBridgeImpl {
+    /**
+     * Each operation sent to the passwords API will be assigned a JobId. The native side uses
+     * this ID to map an API response to the job that invoked it.
+     */
+    @Target(ElementType.TYPE_USE)
+    @Retention(RetentionPolicy.SOURCE)
+    @interface JobId {}
+
+    private long mNativeConsumerBridge;
+
+    PasswordStoreAndroidBackendConsumerBridgeImpl(long nativeConsumerBridge) {
+        mNativeConsumerBridge = nativeConsumerBridge;
+    }
+
+    @CalledByNative
+    static PasswordStoreAndroidBackendConsumerBridgeImpl create(long nativeConsumerBridge) {
+        return new PasswordStoreAndroidBackendConsumerBridgeImpl(nativeConsumerBridge);
+    }
+
+    void handleAndroidBackendException(@JobId int jobId, Exception exception) {
+        if (mNativeConsumerBridge == 0) return;
+
+        @AndroidBackendErrorType
+        int error = PasswordManagerAndroidBackendUtil.getBackendError(exception);
+        int apiErrorCode = PasswordManagerAndroidBackendUtil.getApiErrorCode(exception);
+        Integer connectionResultCode =
+                PasswordManagerAndroidBackendUtil.getConnectionResultCode(exception);
+
+        PasswordStoreAndroidBackendConsumerBridgeImplJni.get().onError(mNativeConsumerBridge, jobId,
+                error, apiErrorCode, connectionResultCode != null,
+                connectionResultCode == null ? -1 : connectionResultCode.intValue());
+    }
+
+    void onCompleteWithLogins(@JobId int jobId, byte[] passwords) {
+        if (mNativeConsumerBridge == 0) return;
+        PasswordStoreAndroidBackendConsumerBridgeImplJni.get().onCompleteWithLogins(
+                mNativeConsumerBridge, jobId, passwords);
+    }
+
+    void onLoginChanged(@JobId int jobId) {
+        if (mNativeConsumerBridge == 0) return;
+        PasswordStoreAndroidBackendConsumerBridgeImplJni.get().onLoginChanged(
+                mNativeConsumerBridge, jobId);
+    }
+
+    void onError(@JobId int jobId, int errorType, int apiErrorCode, boolean hasConnectionResult,
+            int connectionResultStatusCode) {
+        if (mNativeConsumerBridge == 0) return;
+        PasswordStoreAndroidBackendConsumerBridgeImplJni.get().onError(mNativeConsumerBridge, jobId,
+                errorType, apiErrorCode, hasConnectionResult, connectionResultStatusCode);
+    }
+
+    @CalledByNative
+    private void destroy() {
+        mNativeConsumerBridge = 0;
+    }
+
+    @NativeMethods
+    interface Natives {
+        void onCompleteWithLogins(long nativePasswordStoreAndroidBackendConsumerBridgeImpl,
+                @JobId int jobId, byte[] passwords);
+        void onLoginChanged(
+                long nativePasswordStoreAndroidBackendConsumerBridgeImpl, @JobId int jobId);
+        void onError(long nativePasswordStoreAndroidBackendConsumerBridgeImpl, @JobId int jobId,
+                int errorType, int apiErrorCode, boolean hasConnectionResult,
+                int connectionResultStatusCode);
+    }
+}
diff --git a/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/PasswordStoreAndroidBackendFactory.java b/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/PasswordStoreAndroidBackendFactory.java
index ac24b78..b0ecd449 100644
--- a/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/PasswordStoreAndroidBackendFactory.java
+++ b/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/PasswordStoreAndroidBackendFactory.java
@@ -4,8 +4,6 @@
 
 package org.chromium.chrome.browser.password_manager;
 
-import static org.chromium.base.ThreadUtils.assertOnUiThread;
-
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
@@ -25,7 +23,8 @@
      * @return The shared {@link PasswordStoreAndroidBackendFactory} instance.
      */
     public static PasswordStoreAndroidBackendFactory getInstance() {
-        assertOnUiThread();
+        // TODO(crbug.com/1394715): assert running on background thread once CanCreateBackend is
+        // updated to use non-UI thread.
         if (sInstance == null) sInstance = new PasswordStoreAndroidBackendFactoryImpl();
         return sInstance;
     }
diff --git a/chrome/browser/password_manager/android/junit/src/org/chromium/chrome/browser/password_manager/PasswordStoreAndroidBackendBridgeTest.java b/chrome/browser/password_manager/android/junit/src/org/chromium/chrome/browser/password_manager/PasswordStoreAndroidBackendBridgeTest.java
index ecbb720..a6085d8 100644
--- a/chrome/browser/password_manager/android/junit/src/org/chromium/chrome/browser/password_manager/PasswordStoreAndroidBackendBridgeTest.java
+++ b/chrome/browser/password_manager/android/junit/src/org/chromium/chrome/browser/password_manager/PasswordStoreAndroidBackendBridgeTest.java
@@ -33,7 +33,6 @@
 import org.chromium.base.Callback;
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.base.test.util.Batch;
-import org.chromium.base.test.util.JniMocker;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.test.util.browser.Features;
 import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
@@ -67,16 +66,12 @@
             PasswordWithLocalData.newBuilder().setPasswordSpecificsData(sTestProfile);
     private static final ListPasswordsResult.Builder sTestLogins =
             ListPasswordsResult.newBuilder().addPasswordData(sTestPwdWithLocalData);
-    private static final long sDummyNativePointer = 4;
     private static final String sTestAccountEmail = "test@email.com";
     private static final Optional<Account> sTestAccount =
             Optional.of(AccountUtils.createAccountFromName(sTestAccountEmail));
 
-    @Rule
-    public JniMocker mJniMocker = new JniMocker();
-
     @Mock
-    private PasswordStoreAndroidBackendBridgeImpl.Natives mBridgeJniMock;
+    private PasswordStoreAndroidBackendConsumerBridgeImpl mConsumerBridgeMock;
     @Mock
     private PasswordStoreAndroidBackend mBackendMock;
 
@@ -85,9 +80,8 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mJniMocker.mock(PasswordStoreAndroidBackendBridgeImplJni.TEST_HOOKS, mBridgeJniMock);
         mBackendBridge =
-                new PasswordStoreAndroidBackendBridgeImpl(sDummyNativePointer, mBackendMock);
+                new PasswordStoreAndroidBackendBridgeImpl(mConsumerBridgeMock, mBackendMock);
     }
 
     @Test
@@ -102,8 +96,7 @@
 
         byte[] kExpectedList = sTestLogins.build().toByteArray();
         successCallback.getValue().onResult(kExpectedList);
-        verify(mBridgeJniMock)
-                .onCompleteWithLogins(sDummyNativePointer, kTestTaskId, kExpectedList);
+        verify(mConsumerBridgeMock).onCompleteWithLogins(kTestTaskId, kExpectedList);
     }
 
     @Test
@@ -119,9 +112,7 @@
 
         Exception kExpectedException = new Exception("Sample failure");
         failureCallback.getValue().onResult(kExpectedException);
-        verify(mBridgeJniMock)
-                .onError(sDummyNativePointer, kTestTaskId, AndroidBackendErrorType.UNCATEGORIZED, 0,
-                        false, -1);
+        verify(mConsumerBridgeMock).handleAndroidBackendException(kTestTaskId, kExpectedException);
     }
 
     @Test
@@ -138,9 +129,7 @@
         Exception kExpectedException = new PasswordStoreAndroidBackend.BackendException(
                 "Sample failure", AndroidBackendErrorType.NO_ACCOUNT);
         failureCallback.getValue().onResult(kExpectedException);
-        verify(mBridgeJniMock)
-                .onError(sDummyNativePointer, kTestTaskId, AndroidBackendErrorType.NO_ACCOUNT, 0,
-                        false, -1);
+        verify(mConsumerBridgeMock).handleAndroidBackendException(kTestTaskId, kExpectedException);
     }
 
     @Test
@@ -157,10 +146,7 @@
         Exception kExpectedException = new ApiException(
                 new Status(new ConnectionResult(ConnectionResult.API_UNAVAILABLE), ""));
         failureCallback.getValue().onResult(kExpectedException);
-        verify(mBridgeJniMock)
-                .onError(sDummyNativePointer, kTestTaskId, AndroidBackendErrorType.EXTERNAL_ERROR,
-                        CommonStatusCodes.API_NOT_CONNECTED, true,
-                        ConnectionResult.API_UNAVAILABLE);
+        verify(mConsumerBridgeMock).handleAndroidBackendException(kTestTaskId, kExpectedException);
     }
 
     @Test
@@ -179,9 +165,7 @@
                 new Status(CommonStatusCodes.RESOLUTION_REQUIRED, "", pendingIntentMock));
         failureCallback.getValue().onResult(kExpectedException);
         verify(pendingIntentMock, never()).send();
-        verify(mBridgeJniMock)
-                .onError(sDummyNativePointer, kTestTaskId, AndroidBackendErrorType.EXTERNAL_ERROR,
-                        CommonStatusCodes.RESOLUTION_REQUIRED, false, -1);
+        verify(mConsumerBridgeMock).handleAndroidBackendException(kTestTaskId, kExpectedException);
     }
 
     @Test
@@ -197,8 +181,7 @@
 
         byte[] kExpectedList = sTestLogins.build().toByteArray();
         successCallback.getValue().onResult(kExpectedList);
-        verify(mBridgeJniMock)
-                .onCompleteWithLogins(sDummyNativePointer, kTestTaskId, kExpectedList);
+        verify(mConsumerBridgeMock).onCompleteWithLogins(kTestTaskId, kExpectedList);
     }
 
     @Test
@@ -215,9 +198,7 @@
 
         Exception kExpectedException = new Exception("Sample failure");
         failureCallback.getValue().onResult(kExpectedException);
-        verify(mBridgeJniMock)
-                .onError(sDummyNativePointer, kTestTaskId, AndroidBackendErrorType.UNCATEGORIZED, 0,
-                        false, -1);
+        verify(mConsumerBridgeMock).handleAndroidBackendException(kTestTaskId, kExpectedException);
     }
 
     @Test
@@ -234,8 +215,7 @@
 
         byte[] kExpectedList = sTestLogins.build().toByteArray();
         successCallback.getValue().onResult(kExpectedList);
-        verify(mBridgeJniMock)
-                .onCompleteWithLogins(sDummyNativePointer, kTestTaskId, kExpectedList);
+        verify(mConsumerBridgeMock).onCompleteWithLogins(kTestTaskId, kExpectedList);
     }
 
     @Test
@@ -253,9 +233,7 @@
 
         Exception kExpectedException = new Exception("Sample failure");
         failureCallback.getValue().onResult(kExpectedException);
-        verify(mBridgeJniMock)
-                .onError(sDummyNativePointer, kTestTaskId, AndroidBackendErrorType.UNCATEGORIZED, 0,
-                        false, -1);
+        verify(mConsumerBridgeMock).handleAndroidBackendException(kTestTaskId, kExpectedException);
     }
 
     @Test
@@ -271,7 +249,7 @@
         assertNotNull(successCallback.getValue());
 
         successCallback.getValue().run();
-        verify(mBridgeJniMock).onLoginChanged(sDummyNativePointer, kTestTaskId);
+        verify(mConsumerBridgeMock).onLoginChanged(kTestTaskId);
     }
 
     @Test
@@ -288,9 +266,7 @@
 
         Exception kExpectedException = new Exception("Sample failure");
         failureCallback.getValue().onResult(kExpectedException);
-        verify(mBridgeJniMock)
-                .onError(sDummyNativePointer, kTestTaskId, AndroidBackendErrorType.UNCATEGORIZED, 0,
-                        false, -1);
+        verify(mConsumerBridgeMock).handleAndroidBackendException(kTestTaskId, kExpectedException);
     }
 
     @Test
@@ -306,7 +282,7 @@
         assertNotNull(successCallback.getValue());
 
         successCallback.getValue().run();
-        verify(mBridgeJniMock).onLoginChanged(sDummyNativePointer, kTestTaskId);
+        verify(mConsumerBridgeMock).onLoginChanged(kTestTaskId);
     }
 
     @Test
@@ -323,9 +299,7 @@
 
         Exception kExpectedException = new Exception("Sample failure");
         failureCallback.getValue().onResult(kExpectedException);
-        verify(mBridgeJniMock)
-                .onError(sDummyNativePointer, kTestTaskId, AndroidBackendErrorType.UNCATEGORIZED, 0,
-                        false, -1);
+        verify(mConsumerBridgeMock).handleAndroidBackendException(kTestTaskId, kExpectedException);
     }
 
     @Test
@@ -341,7 +315,7 @@
         assertNotNull(successCallback.getValue());
 
         successCallback.getValue().run();
-        verify(mBridgeJniMock).onLoginChanged(sDummyNativePointer, kTestTaskId);
+        verify(mConsumerBridgeMock).onLoginChanged(kTestTaskId);
     }
 
     @Test
@@ -358,8 +332,6 @@
 
         Exception kExpectedException = new Exception("Sample failure");
         failureCallback.getValue().onResult(kExpectedException);
-        verify(mBridgeJniMock)
-                .onError(sDummyNativePointer, kTestTaskId, AndroidBackendErrorType.UNCATEGORIZED, 0,
-                        false, -1);
+        verify(mConsumerBridgeMock).handleAndroidBackendException(kTestTaskId, kExpectedException);
     }
 }
diff --git a/chrome/browser/password_manager/android/junit/src/org/chromium/chrome/browser/password_manager/PasswordStoreAndroidBackendConsumerBridgeTest.java b/chrome/browser/password_manager/android/junit/src/org/chromium/chrome/browser/password_manager/PasswordStoreAndroidBackendConsumerBridgeTest.java
new file mode 100644
index 0000000..e199423
--- /dev/null
+++ b/chrome/browser/password_manager/android/junit/src/org/chromium/chrome/browser/password_manager/PasswordStoreAndroidBackendConsumerBridgeTest.java
@@ -0,0 +1,119 @@
+// 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.password_manager;
+
+import static org.mockito.Mockito.verify;
+
+import com.google.android.gms.common.ConnectionResult;
+import com.google.android.gms.common.api.ApiException;
+import com.google.android.gms.common.api.CommonStatusCodes;
+import com.google.android.gms.common.api.Status;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.base.test.util.Batch;
+import org.chromium.base.test.util.JniMocker;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
+import org.chromium.chrome.test.util.browser.Features;
+import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
+import org.chromium.components.password_manager.core.browser.proto.ListPasswordsResult;
+import org.chromium.components.password_manager.core.browser.proto.PasswordWithLocalData;
+import org.chromium.components.sync.protocol.PasswordSpecificsData;
+
+/**
+ * Tests that backend consumer bridge calls for operation callbacks reach native backend.
+ */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+@Batch(Batch.PER_CLASS)
+@EnableFeatures(ChromeFeatureList.UNIFIED_PASSWORD_MANAGER_ANDROID)
+public class PasswordStoreAndroidBackendConsumerBridgeTest {
+    @Rule
+    public TestRule mProcessor = new Features.JUnitProcessor();
+
+    private static final PasswordSpecificsData.Builder sTestProfile =
+            PasswordSpecificsData.newBuilder()
+                    .setUsernameValue("Todd Tester")
+                    .setUsernameElement("name")
+                    .setPasswordElement("pwd")
+                    .setOrigin("https://www.google.com/")
+                    .setSignonRealm("https://accounts.google.com/signin")
+                    .setPasswordValue("S3cr3t");
+    private static final PasswordWithLocalData.Builder sTestPwdWithLocalData =
+            PasswordWithLocalData.newBuilder().setPasswordSpecificsData(sTestProfile);
+    private static final ListPasswordsResult.Builder sTestLogins =
+            ListPasswordsResult.newBuilder().addPasswordData(sTestPwdWithLocalData);
+    private static final long sDummyNativePointer = 4;
+    private static final int sTestJobId = 1337;
+
+    @Rule
+    public JniMocker mJniMocker = new JniMocker();
+
+    @Mock
+    private PasswordStoreAndroidBackendConsumerBridgeImpl.Natives mConsumerBridgeJniMock;
+
+    private PasswordStoreAndroidBackendConsumerBridgeImpl mConsumerBridge;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mJniMocker.mock(PasswordStoreAndroidBackendConsumerBridgeImplJni.TEST_HOOKS,
+                mConsumerBridgeJniMock);
+        mConsumerBridge = new PasswordStoreAndroidBackendConsumerBridgeImpl(sDummyNativePointer);
+    }
+
+    @Test
+    public void testOnCompleteWithLoginsCallsBridge() {
+        final byte[] kExpectedList = sTestLogins.build().toByteArray();
+
+        mConsumerBridge.onCompleteWithLogins(sTestJobId, kExpectedList);
+        verify(mConsumerBridgeJniMock)
+                .onCompleteWithLogins(sDummyNativePointer, sTestJobId, kExpectedList);
+    }
+
+    @Test
+    public void testOnApiExceptionCallsBridgeOnError() {
+        Exception kExpectedException = new ApiException(new Status(
+                new ConnectionResult(ConnectionResult.API_UNAVAILABLE), "Test API error"));
+        mConsumerBridge.handleAndroidBackendException(sTestJobId, kExpectedException);
+        verify(mConsumerBridgeJniMock)
+                .onError(sDummyNativePointer, sTestJobId, AndroidBackendErrorType.EXTERNAL_ERROR,
+                        CommonStatusCodes.API_NOT_CONNECTED, true,
+                        ConnectionResult.API_UNAVAILABLE);
+    }
+
+    @Test
+    public void testOnBackendExceptionCallsBridgeOnError() {
+        Exception kExpectedException = new PasswordStoreAndroidBackend.BackendException(
+                "Test backend error", AndroidBackendErrorType.NO_ACCOUNT);
+        mConsumerBridge.handleAndroidBackendException(sTestJobId, kExpectedException);
+        verify(mConsumerBridgeJniMock)
+                .onError(sDummyNativePointer, sTestJobId, AndroidBackendErrorType.NO_ACCOUNT, 0,
+                        false, -1);
+    }
+
+    @Test
+    public void testOnUnknownExceptionCallsBridgeOnError() {
+        Exception kExpectedException = new Exception("Test error");
+        mConsumerBridge.handleAndroidBackendException(sTestJobId, kExpectedException);
+        verify(mConsumerBridgeJniMock)
+                .onError(sDummyNativePointer, sTestJobId, AndroidBackendErrorType.UNCATEGORIZED, 0,
+                        false, -1);
+    }
+
+    @Test
+    public void testOnLoginChangedCallsBridge() {
+        mConsumerBridge.onLoginChanged(sTestJobId);
+        verify(mConsumerBridgeJniMock).onLoginChanged(sDummyNativePointer, sTestJobId);
+    }
+}
diff --git a/chrome/browser/password_manager/android/password_store_android_backend.cc b/chrome/browser/password_manager/android/password_store_android_backend.cc
index 1b7cab6..95da4632 100644
--- a/chrome/browser/password_manager/android/password_store_android_backend.cc
+++ b/chrome/browser/password_manager/android/password_store_android_backend.cc
@@ -27,7 +27,7 @@
 #include "base/time/time.h"
 #include "chrome/browser/password_manager/android/password_manager_lifecycle_helper_impl.h"
 #include "chrome/browser/password_manager/android/password_store_android_backend_api_error_codes.h"
-#include "chrome/browser/password_manager/android/password_store_android_backend_bridge.h"
+#include "chrome/browser/password_manager/android/password_store_android_backend_bridge_helper.h"
 #include "chrome/browser/password_manager/android/password_store_operation_target.h"
 #include "chrome/browser/password_manager/android/password_sync_controller_delegate_android.h"
 #include "chrome/browser/password_manager/android/password_sync_controller_delegate_bridge_impl.h"
@@ -70,7 +70,7 @@
 using password_manager::GetRegexForPSLMatching;
 using sync_util::GetSyncingAccount;
 
-using JobId = PasswordStoreAndroidBackendBridge::JobId;
+using JobId = PasswordStoreAndroidBackendConsumerBridge::JobId;
 using SuccessStatus = PasswordStoreBackendMetricsRecorder::SuccessStatus;
 
 std::vector<std::unique_ptr<PasswordForm>> WrapPasswordsIntoPointers(
@@ -591,13 +591,13 @@
 
 PasswordStoreAndroidBackend::PasswordStoreAndroidBackend(PrefService* prefs)
     : lifecycle_helper_(std::make_unique<PasswordManagerLifecycleHelperImpl>()),
-      bridge_(PasswordStoreAndroidBackendBridge::Create()) {
+      bridge_helper_(PasswordStoreAndroidBackendBridgeHelper::Create()) {
   DCHECK(base::FeatureList::IsEnabled(
       password_manager::features::kUnifiedPasswordManagerAndroid));
-  DCHECK(bridge_);
+  DCHECK(bridge_helper_);
   prefs_ = prefs;
   DCHECK(prefs_);
-  bridge_->SetConsumer(weak_ptr_factory_.GetWeakPtr());
+  bridge_helper_->SetConsumer(weak_ptr_factory_.GetWeakPtr());
   sync_controller_delegate_ =
       std::make_unique<PasswordSyncControllerDelegateAndroid>(
           std::make_unique<PasswordSyncControllerDelegateBridgeImpl>(),
@@ -607,18 +607,18 @@
 
 PasswordStoreAndroidBackend::PasswordStoreAndroidBackend(
     base::PassKey<class PasswordStoreAndroidBackendTest>,
-    std::unique_ptr<PasswordStoreAndroidBackendBridge> bridge,
+    std::unique_ptr<PasswordStoreAndroidBackendBridgeHelper> bridge_helper,
     std::unique_ptr<PasswordManagerLifecycleHelper> lifecycle_helper,
     std::unique_ptr<PasswordSyncControllerDelegateAndroid>
         sync_controller_delegate,
     PrefService* prefs)
     : lifecycle_helper_(std::move(lifecycle_helper)),
-      bridge_(std::move(bridge)),
+      bridge_helper_(std::move(bridge_helper)),
       sync_controller_delegate_(std::move(sync_controller_delegate)) {
-  DCHECK(bridge_);
+  DCHECK(bridge_helper_);
   prefs_ = prefs;
   DCHECK(prefs_);
-  bridge_->SetConsumer(weak_ptr_factory_.GetWeakPtr());
+  bridge_helper_->SetConsumer(weak_ptr_factory_.GetWeakPtr());
 }
 
 PasswordStoreAndroidBackend::~PasswordStoreAndroidBackend() = default;
@@ -704,8 +704,8 @@
     PasswordChangesOrErrorReply callback) {
   DCHECK(!form.blocked_by_user ||
          (form.username_value.empty() && form.password_value.empty()));
-  JobId job_id =
-      bridge_->AddLogin(form, GetAccount(GetSyncingAccount(sync_service_)));
+  JobId job_id = bridge_helper_->AddLogin(
+      form, GetAccount(GetSyncingAccount(sync_service_)));
   QueueNewJob(job_id, std::move(callback), MetricInfix("AddLoginAsync"),
               PasswordStoreOperation::kAddLoginAsync,
               /*delay=*/base::Seconds(0));
@@ -716,8 +716,8 @@
     PasswordChangesOrErrorReply callback) {
   DCHECK(!form.blocked_by_user ||
          (form.username_value.empty() && form.password_value.empty()));
-  JobId job_id =
-      bridge_->UpdateLogin(form, GetAccount(GetSyncingAccount(sync_service_)));
+  JobId job_id = bridge_helper_->UpdateLogin(
+      form, GetAccount(GetSyncingAccount(sync_service_)));
   QueueNewJob(job_id, std::move(callback), MetricInfix("UpdateLoginAsync"),
               PasswordStoreOperation::kUpdateLoginAsync,
               /*delay=*/base::Seconds(0));
@@ -931,7 +931,7 @@
     LoginsOrErrorReply callback,
     PasswordStoreOperation operation,
     base::TimeDelta delay) {
-  JobId job_id = bridge_->GetAutofillableLogins(
+  JobId job_id = bridge_helper_->GetAutofillableLogins(
       GetAccount(GetSyncingAccount(sync_service_)));
   QueueNewJob(job_id, std::move(callback),
               MetricInfix("GetAutofillableLoginsAsync"),
@@ -943,7 +943,7 @@
     LoginsOrErrorReply callback,
     PasswordStoreOperation operation,
     base::TimeDelta delay) {
-  JobId job_id = bridge_->GetAllLogins(std::move(account));
+  JobId job_id = bridge_helper_->GetAllLogins(std::move(account));
   QueueNewJob(job_id, std::move(callback), MetricInfix("GetAllLoginsAsync"),
               operation, delay);
 }
@@ -954,7 +954,7 @@
     PasswordChangesOrErrorReply callback,
     PasswordStoreOperation operation,
     base::TimeDelta delay) {
-  JobId job_id = bridge_->RemoveLogin(form, std::move(account));
+  JobId job_id = bridge_helper_->RemoveLogin(form, std::move(account));
   QueueNewJob(job_id, std::move(callback), MetricInfix("RemoveLoginAsync"),
               operation, delay);
 }
@@ -1083,7 +1083,7 @@
       if (!password_manager_upm_eviction::IsCurrentUserEvicted(prefs_)) {
         if (base::FeatureList::IsEnabled(
                 password_manager::features::kShowUPMErrorNotification)) {
-          bridge_->ShowErrorNotification();
+          bridge_helper_->ShowErrorNotification();
         }
         password_manager_upm_eviction::EvictCurrentUser(api_error, prefs_);
       }
@@ -1146,7 +1146,7 @@
     bool include_psl,
     LoginsOrErrorReply callback,
     PasswordStoreOperation operation) {
-  JobId job_id = bridge_->GetLoginsForSignonRealm(
+  JobId job_id = bridge_helper_->GetLoginsForSignonRealm(
       FormToSignonRealmQuery(form, include_psl),
       GetAccount(GetSyncingAccount(sync_service_)));
   QueueNewJob(job_id,
diff --git a/chrome/browser/password_manager/android/password_store_android_backend.h b/chrome/browser/password_manager/android/password_store_android_backend.h
index b4719e6..19de683 100644
--- a/chrome/browser/password_manager/android/password_store_android_backend.h
+++ b/chrome/browser/password_manager/android/password_store_android_backend.h
@@ -18,6 +18,7 @@
 #include "base/types/strong_alias.h"
 #include "chrome/browser/password_manager/android/password_manager_lifecycle_helper.h"
 #include "chrome/browser/password_manager/android/password_store_android_backend_bridge.h"
+#include "chrome/browser/password_manager/android/password_store_android_backend_bridge_helper.h"
 #include "chrome/browser/password_manager/android/password_sync_controller_delegate_android.h"
 #include "components/password_manager/core/browser/password_store_backend.h"
 #include "components/password_manager/core/browser/password_store_backend_metrics_recorder.h"
@@ -80,12 +81,12 @@
 // required the job since JNI itself can't preserve the callbacks.
 class PasswordStoreAndroidBackend
     : public PasswordStoreBackend,
-      public PasswordStoreAndroidBackendBridge::Consumer {
+      public PasswordStoreAndroidBackendConsumerBridge::Consumer {
  public:
   explicit PasswordStoreAndroidBackend(PrefService* prefs);
   PasswordStoreAndroidBackend(
       base::PassKey<class PasswordStoreAndroidBackendTest>,
-      std::unique_ptr<PasswordStoreAndroidBackendBridge> bridge,
+      std::unique_ptr<PasswordStoreAndroidBackendBridgeHelper> bridge_helper,
       std::unique_ptr<PasswordManagerLifecycleHelper> lifecycle_helper,
       std::unique_ptr<PasswordSyncControllerDelegateAndroid>
           sync_controller_delegate,
@@ -331,8 +332,12 @@
   // called via JNI directly.
   JobMap request_for_job_ GUARDED_BY_CONTEXT(main_sequence_checker_);
 
+  // This object is the proxy to the JNI bridge that handles API callbacks from
+  // the Java side.
+  std::unique_ptr<PasswordStoreAndroidBackendConsumerBridge> consumer_bridge_;
+
   // This object is the proxy to the JNI bridge that performs the API requests.
-  std::unique_ptr<PasswordStoreAndroidBackendBridge> bridge_;
+  std::unique_ptr<PasswordStoreAndroidBackendBridgeHelper> bridge_helper_;
 
   raw_ptr<const syncer::SyncService> sync_service_ = nullptr;
 
diff --git a/chrome/browser/password_manager/android/password_store_android_backend_bridge.h b/chrome/browser/password_manager/android/password_store_android_backend_bridge.h
index a199a4f..8935524 100644
--- a/chrome/browser/password_manager/android/password_store_android_backend_bridge.h
+++ b/chrome/browser/password_manager/android/password_store_android_backend_bridge.h
@@ -7,12 +7,8 @@
 
 #include <vector>
 
-#include "base/types/strong_alias.h"
-#include "chrome/browser/password_manager/android/password_store_operation_target.h"
-#include "components/password_manager/core/browser/android_backend_error.h"
+#include "chrome/browser/password_manager/android/password_store_android_backend_consumer_bridge.h"
 #include "components/password_manager/core/browser/password_form.h"
-#include "components/password_manager/core/browser/password_store_backend.h"
-#include "components/password_manager/core/browser/password_store_change.h"
 
 namespace password_manager {
 
@@ -21,97 +17,85 @@
 // that performs API call on the Java side to Google Mobile for each operation.
 // Any logic beyond data conversion should either live in
 // `PasswordStoreAndroidBackend` or a component that is used by the java-side of
-// this bridge.
+// this bridge. Consumer bridge is defined separately in
+// `PasswordStoreAndroidBackendConsumerBridge`.
+// All methods should be called on the same single threaded sequence bound to
+// the background thread. Instance could be constructed and destroyed on any
+// thread.
 class PasswordStoreAndroidBackendBridge {
  public:
-  using JobId = base::StrongAlias<struct JobIdTag, int>;
   using SyncingAccount =
-      base::StrongAlias<struct SyncingAccountTag, std::string>;
-  using Account = absl::variant<PasswordStoreOperationTarget, SyncingAccount>;
-
-  // Each bridge is created with a consumer that will be called when a job is
-  // completed. In order to identify which request the response belongs to, the
-  // bridge needs to pass a `JobId` back that was returned by the initial
-  // request and is unique per bridge.
-  class Consumer {
-   public:
-    virtual ~Consumer() = default;
-
-    // Asynchronous response called with the `job_id` which was passed to the
-    // corresponding call to `PasswordStoreAndroidBackendBridge`, and with the
-    // requested `passwords`.
-    // Used in response to `GetAllLogins`.
-    virtual void OnCompleteWithLogins(JobId job_id,
-                                      std::vector<PasswordForm> passwords) = 0;
-
-    // Asynchronous response called with the `job_id` which was passed to the
-    // corresponding call to `PasswordStoreAndroidBackendBridge`, and with the
-    // PasswordChanges.
-    // Used in response to 'AddLogin', 'UpdateLogin' and `RemoveLogin`.
-    virtual void OnLoginsChanged(JobId job_id, PasswordChanges changes) = 0;
-
-    // Asynchronous response called with the `job_id` which was passed to the
-    // corresponding call to `PasswordStoreAndroidBackendBridge`.
-    virtual void OnError(JobId job_id, AndroidBackendError error) = 0;
-  };
+      PasswordStoreAndroidBackendConsumerBridge::SyncingAccount;
+  using Account = PasswordStoreAndroidBackendConsumerBridge::Account;
+  using JobId = PasswordStoreAndroidBackendConsumerBridge::JobId;
 
   virtual ~PasswordStoreAndroidBackendBridge() = default;
 
-  // Sets the `consumer` that is notified on job completion.
-  virtual void SetConsumer(base::WeakPtr<Consumer> consumer) = 0;
+  // Perform bridge and Java counterpart initialization. This method should be
+  // executed on the same thread where all operations will run.
+  // `consumer_bridge` will be set to handle callbacks from the Java side and
+  // should outlive this object.
+  virtual void Init(
+      const PasswordStoreAndroidBackendConsumerBridge& consumer_bridge) = 0;
 
   // Triggers an asynchronous request to retrieve all stored passwords. The
   // registered `Consumer` is notified with `OnCompleteWithLogins` when the
-  // job with the returned JobId succeeds. `syncing_account` is used to decide
+  // job with the given JobId succeeds. `syncing_account` is used to decide
   // which storage to use. If `syncing_account` is absl::nullopt local storage
   // will be used.
-  [[nodiscard]] virtual JobId GetAllLogins(Account account) = 0;
+  virtual void GetAllLogins(JobId job_id, Account account) = 0;
 
   // Triggers an asynchronous request to retrieve all autofillable
   // (non-blocklisted) passwords. The registered `Consumer` is notified with
-  // `OnCompleteWithLogins` when the job with the returned JobId succeeds.
+  // `OnCompleteWithLogins` when the job with the given JobId succeeds.
   // `syncing_account` is used to decide which storage to use. If
   // `syncing_account` is absl::nullopt local storage will be used.
-  [[nodiscard]] virtual JobId GetAutofillableLogins(Account account) = 0;
+  virtual void GetAutofillableLogins(JobId job_id, Account account) = 0;
 
   // Triggers an asynchronous request to retrieve stored passwords with
   // matching |signon_realm|. The returned results must be validated (e.g
   // matching "sample.com" also returns logins for "not-sample.com").
   // The registered `Consumer` is notified with `OnCompleteWithLogins` when the
-  // job with the returned JobId succeeds. `syncing_account` is used to decide
+  // job with the given JobId succeeds. `syncing_account` is used to decide
   // which storage to use. If `syncing_account` is absl::nullopt local storage
   // will be used.
-  [[nodiscard]] virtual JobId GetLoginsForSignonRealm(
-      const std::string& signon_realm,
-      Account account) = 0;
+  virtual void GetLoginsForSignonRealm(JobId job_id,
+                                       const std::string& signon_realm,
+                                       Account account) = 0;
 
   // Triggers an asynchronous request to add |form| to store. The
   // registered `Consumer` is notified with `OnLoginsChanged` when the
-  // job with the returned JobId succeeds. `syncing_account` is used to decide
+  // job with the given JobId succeeds. `syncing_account` is used to decide
   // which storage to use. If `syncing_account` is absl::nullopt local storage
   // will be used.
-  [[nodiscard]] virtual JobId AddLogin(const PasswordForm& form,
-                                       Account account) = 0;
+  virtual void AddLogin(JobId job_id,
+                        const PasswordForm& form,
+                        Account account) = 0;
 
   // Triggers an asynchronous request to update |form| in store. The
   // registered `Consumer` is notified with `OnLoginsChanged` when the
-  // job with the returned JobId succeeds. `syncing_account` is used to decide
+  // job with the given JobId succeeds. `syncing_account` is used to decide
   // which storage to use. If `syncing_account` is absl::nullopt local storage
   // will be used.
-  [[nodiscard]] virtual JobId UpdateLogin(const PasswordForm& form,
-                                          Account account) = 0;
+  virtual void UpdateLogin(JobId job_id,
+                           const PasswordForm& form,
+                           Account account) = 0;
 
   // Triggers an asynchronous request to remove |form| from store. The
   // registered `Consumer` is notified with `OnLoginsChanged` when the
-  // job with the returned JobId succeeds. `syncing_account` is used to decide
+  // job with the given JobId succeeds. `syncing_account` is used to decide
   // which storage to use. If `syncing_account` is absl::nullopt local storage
   // will be used.
-  [[nodiscard]] virtual JobId RemoveLogin(const PasswordForm& form,
-                                          Account account) = 0;
+  virtual void RemoveLogin(JobId job_id,
+                           const PasswordForm& form,
+                           Account account) = 0;
 
   // Displays a notification when a store backend request finishes with an
   // unrecoverable error. TODO(crbug.com/1344576) Remove when not required
   // anymore.
+  // This method interacts with the UI but should also be called on the
+  // background thread as native bridge does not have JNIEnv for the UI thread.
+  // Operation will be actually be executed on the UI thread by the Java bridge.
   virtual void ShowErrorNotification() = 0;
 
   // Factory function for creating the bridge. Implementation is pulled in by
diff --git a/chrome/browser/password_manager/android/password_store_android_backend_bridge_helper.h b/chrome/browser/password_manager/android/password_store_android_backend_bridge_helper.h
new file mode 100644
index 0000000..64cb0707
--- /dev/null
+++ b/chrome/browser/password_manager/android/password_store_android_backend_bridge_helper.h
@@ -0,0 +1,64 @@
+// 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_PASSWORD_MANAGER_ANDROID_PASSWORD_STORE_ANDROID_BACKEND_BRIDGE_HELPER_H_
+#define CHROME_BROWSER_PASSWORD_MANAGER_ANDROID_PASSWORD_STORE_ANDROID_BACKEND_BRIDGE_HELPER_H_
+
+#include "base/functional/callback_forward.h"
+#include "chrome/browser/password_manager/android/password_store_android_backend_bridge.h"
+#include "chrome/browser/password_manager/android/password_store_android_backend_consumer_bridge.h"
+
+namespace password_manager {
+
+// Interface for the native side of the JNI bridge. Simplifies mocking in tests.
+// This helper simplifies bridge usage by managing unique job ids and executing
+// operations on a proper sequence. All methods should be called on the default
+// sequence of the UI thread.
+class PasswordStoreAndroidBackendBridgeHelper {
+ public:
+  using JobId = PasswordStoreAndroidBackendConsumerBridge::JobId;
+  using Account = PasswordStoreAndroidBackendConsumerBridge::Account;
+  using Consumer = PasswordStoreAndroidBackendConsumerBridge::Consumer;
+
+  virtual ~PasswordStoreAndroidBackendBridgeHelper() = default;
+
+  // Factory function for creating the helper. Implementation is pulled in by
+  // including an implementation or by defining it explicitly in tests.
+  // Ensure `CanCreateBackend` returns true before calling this method.
+  static std::unique_ptr<PasswordStoreAndroidBackendBridgeHelper> Create();
+
+  // Method that checks whether a backend can be created or whether `Create`
+  // would fail. It returns true if all nontransient prerequisistes are
+  // fulfilled. E.g. if the backend requires a minimum GMS version this method
+  // would return false.
+  static bool CanCreateBackend();
+
+  // Sets the `consumer` that is notified on job completion as defined in
+  // `PasswordStoreAndroidBackendConsumerBridge::Consumer`.
+  virtual void SetConsumer(base::WeakPtr<Consumer> consumer) = 0;
+
+  // Password store backend bridge operations. Each operation is executed
+  // asynchronously and could be uniquely identified within the bridge helper
+  // instance using the returned JobId.
+  [[nodiscard]] virtual JobId GetAllLogins(Account account) = 0;
+  [[nodiscard]] virtual JobId GetAutofillableLogins(Account account) = 0;
+  [[nodiscard]] virtual JobId GetLoginsForSignonRealm(
+      const std::string& signon_realm,
+      Account account) = 0;
+  [[nodiscard]] virtual JobId AddLogin(
+      const password_manager::PasswordForm& form,
+      Account account) = 0;
+  [[nodiscard]] virtual JobId UpdateLogin(
+      const password_manager::PasswordForm& form,
+      Account account) = 0;
+  [[nodiscard]] virtual JobId RemoveLogin(
+      const password_manager::PasswordForm& form,
+      Account account) = 0;
+
+  virtual void ShowErrorNotification() = 0;
+};
+
+}  // namespace password_manager
+
+#endif  // CHROME_BROWSER_PASSWORD_MANAGER_ANDROID_PASSWORD_STORE_ANDROID_BACKEND_BRIDGE_HELPER_H_
diff --git a/chrome/browser/password_manager/android/password_store_android_backend_bridge_helper_impl.cc b/chrome/browser/password_manager/android/password_store_android_backend_bridge_helper_impl.cc
new file mode 100644
index 0000000..0d16c42
--- /dev/null
+++ b/chrome/browser/password_manager/android/password_store_android_backend_bridge_helper_impl.cc
@@ -0,0 +1,180 @@
+// 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/password_manager/android/password_store_android_backend_bridge_helper_impl.h"
+
+#include <cstdint>
+#include <memory>
+
+#include "base/functional/bind.h"
+#include "base/location.h"
+#include "base/task/bind_post_task.h"
+#include "base/task/task_traits.h"
+#include "base/task/thread_pool.h"
+#include "chrome/browser/password_manager/android/password_store_android_backend_bridge.h"
+#include "chrome/browser/password_manager/android/password_store_android_backend_consumer_bridge.h"
+#include "components/password_manager/core/browser/password_form.h"
+
+namespace password_manager {
+
+namespace {
+
+using JobId = PasswordStoreAndroidBackendBridgeHelper::JobId;
+
+}
+
+std::unique_ptr<PasswordStoreAndroidBackendBridgeHelper>
+PasswordStoreAndroidBackendBridgeHelper::Create() {
+  return std::make_unique<PasswordStoreAndroidBackendBridgeHelperImpl>();
+}
+
+bool PasswordStoreAndroidBackendBridgeHelper::CanCreateBackend() {
+  // TODO(crbug.com/1394715): Either move this call to the background thread or
+  // use cached GMS Core version from `BuildInfo.gmsVersionCode`.
+  return PasswordStoreAndroidBackendBridge::CanCreateBackend();
+}
+
+PasswordStoreAndroidBackendBridgeHelperImpl::
+    PasswordStoreAndroidBackendBridgeHelperImpl()
+    : consumer_bridge_(PasswordStoreAndroidBackendConsumerBridge::Create()),
+      bridge_(PasswordStoreAndroidBackendBridge::Create()),
+      background_task_runner_(base::ThreadPool::CreateSingleThreadTaskRunner(
+          {base::TaskPriority::USER_VISIBLE})) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_checker_);
+  // Bridge is manually shut down on the sequence where all operations are
+  // executed. It's safe to use `base::Unretained(bridge_)` for binding.
+  background_task_runner_->PostTask(
+      FROM_HERE, base::BindOnce(&PasswordStoreAndroidBackendBridge::Init,
+                                base::Unretained(bridge_.get()),
+                                std::ref(*consumer_bridge_)));
+}
+
+PasswordStoreAndroidBackendBridgeHelperImpl::
+    PasswordStoreAndroidBackendBridgeHelperImpl(
+        base::PassKey<class PasswordStoreAndroidBackendBridgeHelperImplTest>,
+        std::unique_ptr<PasswordStoreAndroidBackendConsumerBridge>
+            consumer_bridge,
+        std::unique_ptr<PasswordStoreAndroidBackendBridge> bridge)
+    : consumer_bridge_(std::move(consumer_bridge)),
+      bridge_(std::move(bridge)),
+      background_task_runner_(base::ThreadPool::CreateSingleThreadTaskRunner(
+          {base::TaskPriority::USER_VISIBLE})) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_checker_);
+  background_task_runner_->PostTask(
+      FROM_HERE, base::BindOnce(&PasswordStoreAndroidBackendBridge::Init,
+                                base::Unretained(bridge_.get()),
+                                std::ref(*consumer_bridge_)));
+}
+
+PasswordStoreAndroidBackendBridgeHelperImpl::
+    ~PasswordStoreAndroidBackendBridgeHelperImpl() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_checker_);
+  // Delete bridge on the background thread where it lives.
+  bool will_delete =
+      background_task_runner_->DeleteSoon(FROM_HERE, std::move(bridge_));
+  DCHECK(will_delete);
+}
+
+void PasswordStoreAndroidBackendBridgeHelperImpl::SetConsumer(
+    base::WeakPtr<Consumer> consumer) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_checker_);
+  DCHECK(consumer_bridge_);
+  consumer_bridge_->SetConsumer(consumer);
+}
+
+JobId PasswordStoreAndroidBackendBridgeHelperImpl::GetAllLogins(
+    Account account) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_checker_);
+  DCHECK(bridge_);
+  JobId job_id = GetNextJobId();
+  background_task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(&PasswordStoreAndroidBackendBridge::GetAllLogins,
+                     base::Unretained(bridge_.get()), job_id,
+                     std::move(account)));
+  return job_id;
+}
+
+JobId PasswordStoreAndroidBackendBridgeHelperImpl::GetAutofillableLogins(
+    Account account) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_checker_);
+  DCHECK(bridge_);
+  JobId job_id = GetNextJobId();
+  background_task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(&PasswordStoreAndroidBackendBridge::GetAutofillableLogins,
+                     base::Unretained(bridge_.get()), job_id,
+                     std::move(account)));
+  return job_id;
+}
+
+JobId PasswordStoreAndroidBackendBridgeHelperImpl::GetLoginsForSignonRealm(
+    const std::string& signon_realm,
+    Account account) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_checker_);
+  DCHECK(bridge_);
+  JobId job_id = GetNextJobId();
+  background_task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(
+          &PasswordStoreAndroidBackendBridge::GetLoginsForSignonRealm,
+          base::Unretained(bridge_.get()), job_id, signon_realm,
+          std::move(account)));
+  return job_id;
+}
+
+JobId PasswordStoreAndroidBackendBridgeHelperImpl::AddLogin(
+    const password_manager::PasswordForm& form,
+    Account account) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_checker_);
+  DCHECK(bridge_);
+  JobId job_id = GetNextJobId();
+  background_task_runner_->PostTask(
+      FROM_HERE, base::BindOnce(&PasswordStoreAndroidBackendBridge::AddLogin,
+                                base::Unretained(bridge_.get()), job_id, form,
+                                std::move(account)));
+  return job_id;
+}
+
+JobId PasswordStoreAndroidBackendBridgeHelperImpl::UpdateLogin(
+    const password_manager::PasswordForm& form,
+    Account account) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_checker_);
+  DCHECK(bridge_);
+  JobId job_id = GetNextJobId();
+  background_task_runner_->PostTask(
+      FROM_HERE, base::BindOnce(&PasswordStoreAndroidBackendBridge::UpdateLogin,
+                                base::Unretained(bridge_.get()), job_id, form,
+                                std::move(account)));
+  return job_id;
+}
+
+JobId PasswordStoreAndroidBackendBridgeHelperImpl::RemoveLogin(
+    const password_manager::PasswordForm& form,
+    Account account) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_checker_);
+  DCHECK(bridge_);
+  JobId job_id = GetNextJobId();
+  background_task_runner_->PostTask(
+      FROM_HERE, base::BindOnce(&PasswordStoreAndroidBackendBridge::RemoveLogin,
+                                base::Unretained(bridge_.get()), job_id, form,
+                                std::move(account)));
+  return job_id;
+}
+
+JobId PasswordStoreAndroidBackendBridgeHelperImpl::GetNextJobId() {
+  last_job_id_ = JobId(last_job_id_.value() + 1);
+  return last_job_id_;
+}
+
+void PasswordStoreAndroidBackendBridgeHelperImpl::ShowErrorNotification() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_checker_);
+  DCHECK(bridge_);
+  background_task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(&PasswordStoreAndroidBackendBridge::ShowErrorNotification,
+                     base::Unretained(bridge_.get())));
+}
+
+}  // namespace password_manager
diff --git a/chrome/browser/password_manager/android/password_store_android_backend_bridge_helper_impl.h b/chrome/browser/password_manager/android/password_store_android_backend_bridge_helper_impl.h
new file mode 100644
index 0000000..f3ec6ce
--- /dev/null
+++ b/chrome/browser/password_manager/android/password_store_android_backend_bridge_helper_impl.h
@@ -0,0 +1,76 @@
+// 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_PASSWORD_MANAGER_ANDROID_PASSWORD_STORE_ANDROID_BACKEND_BRIDGE_HELPER_IMPL_H_
+#define CHROME_BROWSER_PASSWORD_MANAGER_ANDROID_PASSWORD_STORE_ANDROID_BACKEND_BRIDGE_HELPER_IMPL_H_
+
+#include "base/task/sequenced_task_runner.h"
+#include "chrome/browser/password_manager/android/password_store_android_backend_bridge_helper.h"
+
+namespace password_manager {
+
+// Helper class that executes password store backend bridge operations on the
+// background thread. All operations are executed sequntally on the same
+// physical thread as JNIEnv can not be shared between threads.
+// Class does not implement bridge interface as it also handles job id for async
+// operations. This class methods should be called from the main thread.
+class PasswordStoreAndroidBackendBridgeHelperImpl
+    : public PasswordStoreAndroidBackendBridgeHelper {
+ public:
+  PasswordStoreAndroidBackendBridgeHelperImpl();
+  PasswordStoreAndroidBackendBridgeHelperImpl(
+      base::PassKey<class PasswordStoreAndroidBackendBridgeHelperImplTest>,
+      std::unique_ptr<PasswordStoreAndroidBackendConsumerBridge>
+          consumer_bridge,
+      std::unique_ptr<PasswordStoreAndroidBackendBridge> bridge);
+
+  PasswordStoreAndroidBackendBridgeHelperImpl(
+      PasswordStoreAndroidBackendBridgeHelperImpl&&) = delete;
+  PasswordStoreAndroidBackendBridgeHelperImpl(
+      const PasswordStoreAndroidBackendBridgeHelperImpl&) = delete;
+  PasswordStoreAndroidBackendBridgeHelperImpl& operator=(
+      PasswordStoreAndroidBackendBridgeHelperImpl&&) = delete;
+  PasswordStoreAndroidBackendBridgeHelperImpl& operator=(
+      const PasswordStoreAndroidBackendBridgeHelperImpl&) = delete;
+  ~PasswordStoreAndroidBackendBridgeHelperImpl() override;
+
+  // PasswordStoreAndroidBackendBridgeHelper implementation
+  void SetConsumer(base::WeakPtr<Consumer> consumer) override;
+  [[nodiscard]] JobId GetAllLogins(Account account) override;
+  [[nodiscard]] JobId GetAutofillableLogins(Account account) override;
+  [[nodiscard]] JobId GetLoginsForSignonRealm(const std::string& signon_realm,
+                                              Account account) override;
+  [[nodiscard]] JobId AddLogin(const password_manager::PasswordForm& form,
+                               Account account) override;
+  [[nodiscard]] JobId UpdateLogin(const password_manager::PasswordForm& form,
+                                  Account account) override;
+  [[nodiscard]] JobId RemoveLogin(const password_manager::PasswordForm& form,
+                                  Account account) override;
+
+  void ShowErrorNotification() override;
+
+ private:
+  JobId GetNextJobId();
+
+  // This object is the proxy to the JNI bridge that handles API callvacks.
+  std::unique_ptr<PasswordStoreAndroidBackendConsumerBridge> consumer_bridge_;
+
+  // This object is the proxy to the JNI bridge that performs the API requests.
+  std::unique_ptr<PasswordStoreAndroidBackendBridge> bridge_;
+
+  // Background thread pool task runner. Used to execute all backend operations
+  // including JNI and/or GMS Core interaction. Limited to a single thread as
+  // JNIEnv only suitable for use on a single thread.
+  scoped_refptr<base::SingleThreadTaskRunner> background_task_runner_;
+
+  // This member stores the unique ID last used for an API request.
+  JobId last_job_id_{0};
+
+  // All methods should be called on the main thread.
+  SEQUENCE_CHECKER(main_sequence_checker_);
+};
+
+}  // namespace password_manager
+
+#endif  // CHROME_BROWSER_PASSWORD_MANAGER_ANDROID_PASSWORD_STORE_ANDROID_BACKEND_BRIDGE_HELPER_IMPL_H_
diff --git a/chrome/browser/password_manager/android/password_store_android_backend_bridge_helper_impl_unittest.cc b/chrome/browser/password_manager/android/password_store_android_backend_bridge_helper_impl_unittest.cc
new file mode 100644
index 0000000..1283cc9
--- /dev/null
+++ b/chrome/browser/password_manager/android/password_store_android_backend_bridge_helper_impl_unittest.cc
@@ -0,0 +1,223 @@
+// 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/password_manager/android/password_store_android_backend_bridge_helper_impl.h"
+
+#include <memory>
+#include <vector>
+
+#include "base/memory/raw_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/test/task_environment.h"
+#include "chrome/browser/password_manager/android/password_store_android_backend_bridge.h"
+#include "chrome/browser/password_manager/android/password_store_android_backend_bridge_helper.h"
+#include "chrome/browser/password_manager/android/password_store_android_backend_consumer_bridge.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace password_manager {
+namespace {
+
+using testing::_;
+using testing::ElementsAre;
+using testing::Eq;
+using testing::NiceMock;
+using testing::Optional;
+using testing::Return;
+using testing::StrictMock;
+using testing::VariantWith;
+using testing::WithArg;
+using JobId = PasswordStoreAndroidBackendBridge::JobId;
+
+constexpr char kTestAccount[] = "test@gmail.com";
+const std::u16string kTestUsername(u"Todd Tester");
+const std::u16string kTestPassword(u"S3cr3t");
+constexpr char kTestUrl[] = "https://example.com";
+constexpr base::Time kTestDateCreated = base::Time::FromTimeT(1500);
+
+PasswordForm CreateTestLogin() {
+  PasswordForm form;
+  form.username_value = kTestUsername;
+  form.password_value = kTestPassword;
+  form.url = GURL(kTestUrl);
+  form.signon_realm = kTestUrl;
+  form.date_created = kTestDateCreated;
+  return form;
+}
+
+MATCHER_P(ExpectSyncingAccount, expectation, "") {
+  return absl::holds_alternative<
+             PasswordStoreAndroidBackendBridge::SyncingAccount>(arg) &&
+         expectation ==
+             absl::get<PasswordStoreAndroidBackendBridge::SyncingAccount>(arg)
+                 .value();
+}
+
+class MockBackendConsumer
+    : public PasswordStoreAndroidBackendConsumerBridge::Consumer {
+  MOCK_METHOD(void,
+              OnCompleteWithLogins,
+              (JobId, std::vector<PasswordForm>),
+              (override));
+  MOCK_METHOD(void, OnLoginsChanged, (JobId, PasswordChanges), (override));
+  MOCK_METHOD(void, OnError, (JobId, AndroidBackendError), (override));
+};
+
+class MockPasswordStoreAndroidBackendConsumerBridge
+    : public PasswordStoreAndroidBackendConsumerBridge {
+ public:
+  MOCK_METHOD(void, SetConsumer, (base::WeakPtr<Consumer>), (override));
+  MOCK_METHOD(base::android::ScopedJavaGlobalRef<jobject>,
+              GetJavaBridge,
+              (),
+              (const, override));
+};
+
+class MockPasswordStoreAndroidBackendBridge
+    : public PasswordStoreAndroidBackendBridge {
+ public:
+  MOCK_METHOD(void,
+              Init,
+              (const PasswordStoreAndroidBackendConsumerBridge&),
+              (override));
+  MOCK_METHOD(void, GetAllLogins, (JobId, Account), (override));
+  MOCK_METHOD(void, GetAutofillableLogins, (JobId, Account), (override));
+  MOCK_METHOD(void,
+              GetLoginsForSignonRealm,
+              (JobId, const std::string&, Account),
+              (override));
+  MOCK_METHOD(void,
+              AddLogin,
+              (JobId, const PasswordForm&, Account),
+              (override));
+  MOCK_METHOD(void,
+              UpdateLogin,
+              (JobId, const PasswordForm&, Account),
+              (override));
+  MOCK_METHOD(void,
+              RemoveLogin,
+              (JobId, const PasswordForm&, Account),
+              (override));
+  MOCK_METHOD(void, ShowErrorNotification, (), (override));
+};
+
+}  // namespace
+
+class PasswordStoreAndroidBackendBridgeHelperImplTest : public testing::Test {
+ protected:
+  PasswordStoreAndroidBackendBridgeHelperImplTest()
+      : helper_(base::PassKey<
+                    class PasswordStoreAndroidBackendBridgeHelperImplTest>(),
+                CreateMockConsumerBridge(),
+                CreateMockBridge()) {
+    EXPECT_CALL(*bridge(), Init);
+    EXPECT_CALL(*consumer_bridge(), SetConsumer);
+    helper_.SetConsumer(consumer_weak_factory_.GetWeakPtr());
+    RunUntilIdle();
+  }
+
+  ~PasswordStoreAndroidBackendBridgeHelperImplTest() override {
+    RunUntilIdle();
+    testing::Mock::VerifyAndClearExpectations(consumer_bridge_);
+    testing::Mock::VerifyAndClearExpectations(bridge_);
+    testing::Mock::VerifyAndClearExpectations(&consumer_);
+  }
+
+  MockPasswordStoreAndroidBackendConsumerBridge* consumer_bridge() {
+    return consumer_bridge_;
+  }
+  MockPasswordStoreAndroidBackendBridge* bridge() { return bridge_; }
+  PasswordStoreAndroidBackendBridgeHelperImpl* helper() { return &helper_; }
+
+  void RunUntilIdle() { task_environment_.RunUntilIdle(); }
+
+  base::test::TaskEnvironment task_environment_{
+      base::test::TaskEnvironment::MainThreadType::UI,
+      base::test::TaskEnvironment::TimeSource::MOCK_TIME,
+      base::test::TaskEnvironment::ThreadPoolExecutionMode::QUEUED};
+
+ private:
+  std::unique_ptr<MockPasswordStoreAndroidBackendConsumerBridge>
+  CreateMockConsumerBridge() {
+    auto unique_consumer_bridge = std::make_unique<
+        StrictMock<MockPasswordStoreAndroidBackendConsumerBridge>>();
+    consumer_bridge_ = unique_consumer_bridge.get();
+
+    return unique_consumer_bridge;
+  }
+
+  std::unique_ptr<PasswordStoreAndroidBackendBridge> CreateMockBridge() {
+    auto unique_bridge =
+        std::make_unique<StrictMock<MockPasswordStoreAndroidBackendBridge>>();
+    bridge_ = unique_bridge.get();
+    return unique_bridge;
+  }
+
+  raw_ptr<StrictMock<MockPasswordStoreAndroidBackendConsumerBridge>>
+      consumer_bridge_;
+  raw_ptr<StrictMock<MockPasswordStoreAndroidBackendBridge>> bridge_;
+  PasswordStoreAndroidBackendBridgeHelperImpl helper_;
+  StrictMock<MockBackendConsumer> consumer_;
+  base::WeakPtrFactory<MockBackendConsumer> consumer_weak_factory_{&consumer_};
+};
+
+TEST_F(PasswordStoreAndroidBackendBridgeHelperImplTest,
+       GetAllLoginsCallsBridge) {
+  JobId job_id = helper()->GetAllLogins(
+      PasswordStoreAndroidBackendBridge::SyncingAccount(kTestAccount));
+  EXPECT_CALL(*bridge(),
+              GetAllLogins(job_id, ExpectSyncingAccount(kTestAccount)));
+  RunUntilIdle();
+}
+
+TEST_F(PasswordStoreAndroidBackendBridgeHelperImplTest,
+       GetAutofillableLoginsCallsBridge) {
+  JobId job_id = helper()->GetAutofillableLogins(
+      PasswordStoreAndroidBackendBridge::SyncingAccount(kTestAccount));
+  EXPECT_CALL(*bridge(), GetAutofillableLogins(
+                             job_id, ExpectSyncingAccount(kTestAccount)));
+  RunUntilIdle();
+}
+
+TEST_F(PasswordStoreAndroidBackendBridgeHelperImplTest,
+       GetLoginsForSignonRealmCallsBridge) {
+  JobId job_id = helper()->GetLoginsForSignonRealm(
+      kTestUrl,
+      PasswordStoreAndroidBackendBridge::SyncingAccount(kTestAccount));
+  EXPECT_CALL(*bridge(),
+              GetLoginsForSignonRealm(job_id, kTestUrl,
+                                      ExpectSyncingAccount(kTestAccount)));
+  RunUntilIdle();
+}
+
+TEST_F(PasswordStoreAndroidBackendBridgeHelperImplTest, AddLoginCallsBridge) {
+  auto form = CreateTestLogin();
+  JobId job_id = helper()->AddLogin(
+      form, PasswordStoreAndroidBackendBridge::SyncingAccount(kTestAccount));
+  EXPECT_CALL(*bridge(),
+              AddLogin(job_id, Eq(form), ExpectSyncingAccount(kTestAccount)));
+  RunUntilIdle();
+}
+
+TEST_F(PasswordStoreAndroidBackendBridgeHelperImplTest,
+       UpdateLoginCallsBridge) {
+  auto form = CreateTestLogin();
+  JobId job_id = helper()->UpdateLogin(
+      form, PasswordStoreAndroidBackendBridge::SyncingAccount(kTestAccount));
+  EXPECT_CALL(*bridge(), UpdateLogin(job_id, Eq(form),
+                                     ExpectSyncingAccount(kTestAccount)));
+  RunUntilIdle();
+}
+
+TEST_F(PasswordStoreAndroidBackendBridgeHelperImplTest,
+       RemoveLoginCallsBridge) {
+  auto form = CreateTestLogin();
+  JobId job_id = helper()->RemoveLogin(
+      form, PasswordStoreAndroidBackendBridge::SyncingAccount(kTestAccount));
+  EXPECT_CALL(*bridge(), RemoveLogin(job_id, Eq(form),
+                                     ExpectSyncingAccount(kTestAccount)));
+  RunUntilIdle();
+}
+
+}  // namespace password_manager
diff --git a/chrome/browser/password_manager/android/password_store_android_backend_bridge_impl.cc b/chrome/browser/password_manager/android/password_store_android_backend_bridge_impl.cc
index d45424e..c44924f 100644
--- a/chrome/browser/password_manager/android/password_store_android_backend_bridge_impl.cc
+++ b/chrome/browser/password_manager/android/password_store_android_backend_bridge_impl.cc
@@ -10,46 +10,24 @@
 #include "base/android/jni_android.h"
 #include "base/android/jni_array.h"
 #include "base/android/jni_string.h"
-#include "base/feature_list.h"
-#include "base/task/sequenced_task_runner.h"
 #include "chrome/browser/password_manager/android/jni_headers/PasswordStoreAndroidBackendBridgeImpl_jni.h"
-#include "chrome/browser/password_manager/android/password_store_android_backend_api_error_codes.h"
-#include "components/password_manager/core/browser/android_backend_error.h"
 #include "components/password_manager/core/browser/password_form.h"
 #include "components/password_manager/core/browser/protos/list_passwords_result.pb.h"
 #include "components/password_manager/core/browser/protos/password_with_local_data.pb.h"
 #include "components/password_manager/core/browser/sync/password_proto_utils.h"
 #include "components/password_manager/core/browser/unified_password_manager_proto_utils.h"
-#include "components/password_manager/core/common/password_manager_features.h"
 
-using JobId = PasswordStoreAndroidBackendBridgeImpl::JobId;
+namespace password_manager {
 
 namespace {
 
-std::vector<password_manager::PasswordForm> CreateFormsVector(
-    const base::android::JavaRef<jbyteArray>& passwords) {
-  std::vector<uint8_t> serialized_result;
-  base::android::JavaByteArrayToByteVector(base::android::AttachCurrentThread(),
-                                           passwords, &serialized_result);
-  password_manager::ListPasswordsResult list_passwords_result;
-  bool parsing_succeeds = list_passwords_result.ParseFromArray(
-      serialized_result.data(), serialized_result.size());
-  DCHECK(parsing_succeeds);
-  auto forms =
-      password_manager::PasswordVectorFromListResult(list_passwords_result);
-  for (auto& form : forms) {
-    // Set proper in_store value for GMS Core storage.
-    form.in_store = password_manager::PasswordForm::Store::kProfileStore;
-  }
-  return forms;
-}
+using JobId = PasswordStoreAndroidBackendBridge::JobId;
 
 base::android::ScopedJavaLocalRef<jstring> GetJavaStringFromAccount(
     PasswordStoreAndroidBackendBridgeImpl::Account account) {
-  if (absl::holds_alternative<password_manager::PasswordStoreOperationTarget>(
-          account)) {
-    DCHECK(password_manager::PasswordStoreOperationTarget::kLocalStorage ==
-           absl::get<password_manager::PasswordStoreOperationTarget>(account));
+  if (absl::holds_alternative<PasswordStoreOperationTarget>(account)) {
+    DCHECK(PasswordStoreOperationTarget::kLocalStorage ==
+           absl::get<PasswordStoreOperationTarget>(account));
     return nullptr;
   }
   return base::android::ConvertUTF8ToJavaString(
@@ -60,8 +38,6 @@
 
 }  // namespace
 
-namespace password_manager {
-
 std::unique_ptr<PasswordStoreAndroidBackendBridge>
 PasswordStoreAndroidBackendBridge::Create() {
   return std::make_unique<PasswordStoreAndroidBackendBridgeImpl>();
@@ -72,91 +48,56 @@
       base::android::AttachCurrentThread());
 }
 
-}  // namespace password_manager
-
 PasswordStoreAndroidBackendBridgeImpl::PasswordStoreAndroidBackendBridgeImpl() {
-  java_object_ = Java_PasswordStoreAndroidBackendBridgeImpl_create(
-      base::android::AttachCurrentThread(), reinterpret_cast<intptr_t>(this));
+  DETACH_FROM_THREAD(thread_checker_);
 }
 
 PasswordStoreAndroidBackendBridgeImpl::
     ~PasswordStoreAndroidBackendBridgeImpl() {
-  Java_PasswordStoreAndroidBackendBridgeImpl_destroy(
-      base::android::AttachCurrentThread(), java_object_);
-}
-void PasswordStoreAndroidBackendBridgeImpl::SetConsumer(
-    base::WeakPtr<Consumer> consumer) {
-  consumer_ = consumer;
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
 }
 
-void PasswordStoreAndroidBackendBridgeImpl::OnCompleteWithLogins(
-    JNIEnv* env,
-    jint job_id,
-    const base::android::JavaParamRef<jbyteArray>& passwords) {
-  DCHECK(consumer_);
-  consumer_->OnCompleteWithLogins(JobId(job_id), CreateFormsVector(passwords));
+void PasswordStoreAndroidBackendBridgeImpl::Init(
+    const PasswordStoreAndroidBackendConsumerBridge& consumer_bridge) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  java_object_ = Java_PasswordStoreAndroidBackendBridgeImpl_create(
+      base::android::AttachCurrentThread(), consumer_bridge.GetJavaBridge());
 }
 
-void PasswordStoreAndroidBackendBridgeImpl::OnError(
-    JNIEnv* env,
-    jint job_id,
-    jint error_type,
-    jint api_error_code,
-    jboolean has_connection_result,
-    jint connection_result_code) {
-  DCHECK(consumer_);
-  // Posting the tasks to the same sequence prevents that synchronous responses
-  // try to finish tasks before their registration was completed.
-  password_manager::AndroidBackendError error{
-      static_cast<password_manager::AndroidBackendErrorType>(error_type)};
-
-  if (error.type == password_manager::AndroidBackendErrorType::kExternalError) {
-    error.api_error_code = static_cast<int>(api_error_code);
-  }
-
-  if (has_connection_result) {
-    error.connection_result_code = static_cast<int>(connection_result_code);
-  }
-
-  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
-      FROM_HERE,
-      base::BindOnce(&PasswordStoreAndroidBackendBridge::Consumer::OnError,
-                     consumer_, JobId(job_id), std::move(error)));
-}
-
-JobId PasswordStoreAndroidBackendBridgeImpl::GetAllLogins(Account account) {
-  JobId job_id = GetNextJobId();
+void PasswordStoreAndroidBackendBridgeImpl::GetAllLogins(JobId job_id,
+                                                         Account account) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   Java_PasswordStoreAndroidBackendBridgeImpl_getAllLogins(
       base::android::AttachCurrentThread(), java_object_, job_id.value(),
       GetJavaStringFromAccount(std::move(account)));
-  return job_id;
 }
 
-JobId PasswordStoreAndroidBackendBridgeImpl::GetAutofillableLogins(
+void PasswordStoreAndroidBackendBridgeImpl::GetAutofillableLogins(
+    JobId job_id,
     Account account) {
-  JobId job_id = GetNextJobId();
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   Java_PasswordStoreAndroidBackendBridgeImpl_getAutofillableLogins(
       base::android::AttachCurrentThread(), java_object_, job_id.value(),
       GetJavaStringFromAccount(std::move(account)));
-  return job_id;
 }
 
-JobId PasswordStoreAndroidBackendBridgeImpl::GetLoginsForSignonRealm(
+void PasswordStoreAndroidBackendBridgeImpl::GetLoginsForSignonRealm(
+    JobId job_id,
     const std::string& signon_realm,
     Account account) {
-  JobId job_id = GetNextJobId();
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   Java_PasswordStoreAndroidBackendBridgeImpl_getLoginsForSignonRealm(
       base::android::AttachCurrentThread(), java_object_, job_id.value(),
       base::android::ConvertUTF8ToJavaString(
           base::android::AttachCurrentThread(), signon_realm),
       GetJavaStringFromAccount(std::move(account)));
-  return job_id;
 }
 
-JobId PasswordStoreAndroidBackendBridgeImpl::AddLogin(
+void PasswordStoreAndroidBackendBridgeImpl::AddLogin(
+    JobId job_id,
     const password_manager::PasswordForm& form,
     Account account) {
-  JobId job_id = GetNextJobId();
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   password_manager::PasswordWithLocalData data =
       PasswordWithLocalDataFromPassword(form);
   Java_PasswordStoreAndroidBackendBridgeImpl_addLogin(
@@ -164,13 +105,13 @@
       base::android::ToJavaByteArray(base::android::AttachCurrentThread(),
                                      data.SerializeAsString()),
       GetJavaStringFromAccount(std::move(account)));
-  return job_id;
 }
 
-JobId PasswordStoreAndroidBackendBridgeImpl::UpdateLogin(
+void PasswordStoreAndroidBackendBridgeImpl::UpdateLogin(
+    JobId job_id,
     const password_manager::PasswordForm& form,
     Account account) {
-  JobId job_id = GetNextJobId();
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   password_manager::PasswordWithLocalData data =
       PasswordWithLocalDataFromPassword(form);
   Java_PasswordStoreAndroidBackendBridgeImpl_updateLogin(
@@ -178,13 +119,13 @@
       base::android::ToJavaByteArray(base::android::AttachCurrentThread(),
                                      data.SerializeAsString()),
       GetJavaStringFromAccount(std::move(account)));
-  return job_id;
 }
 
-JobId PasswordStoreAndroidBackendBridgeImpl::RemoveLogin(
+void PasswordStoreAndroidBackendBridgeImpl::RemoveLogin(
+    JobId job_id,
     const password_manager::PasswordForm& form,
     Account account) {
-  JobId job_id = GetNextJobId();
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   sync_pb::PasswordSpecificsData data =
       SpecificsDataFromPassword(form, /*base_password_data=*/{});
   Java_PasswordStoreAndroidBackendBridgeImpl_removeLogin(
@@ -192,23 +133,12 @@
       base::android::ToJavaByteArray(base::android::AttachCurrentThread(),
                                      data.SerializeAsString()),
       GetJavaStringFromAccount(std::move(account)));
-  return job_id;
-}
-
-void PasswordStoreAndroidBackendBridgeImpl::OnLoginChanged(JNIEnv* env,
-                                                           jint job_id) {
-  DCHECK(consumer_);
-  // Notifying that a login changed without providing a changelist prompts the
-  // caller to explicitly check the remaining logins.
-  consumer_->OnLoginsChanged(JobId(job_id), absl::nullopt);
-}
-
-JobId PasswordStoreAndroidBackendBridgeImpl::GetNextJobId() {
-  last_job_id_ = JobId(last_job_id_.value() + 1);
-  return last_job_id_;
 }
 
 void PasswordStoreAndroidBackendBridgeImpl::ShowErrorNotification() {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   Java_PasswordStoreAndroidBackendBridgeImpl_showErrorUi(
       base::android::AttachCurrentThread(), java_object_);
 }
+
+}  // namespace password_manager
diff --git a/chrome/browser/password_manager/android/password_store_android_backend_bridge_impl.h b/chrome/browser/password_manager/android/password_store_android_backend_bridge_impl.h
index a1d3499..e8e172b 100644
--- a/chrome/browser/password_manager/android/password_store_android_backend_bridge_impl.h
+++ b/chrome/browser/password_manager/android/password_store_android_backend_bridge_impl.h
@@ -6,15 +6,23 @@
 #define CHROME_BROWSER_PASSWORD_MANAGER_ANDROID_PASSWORD_STORE_ANDROID_BACKEND_BRIDGE_IMPL_H_
 
 #include "base/android/scoped_java_ref.h"
+#include "base/thread_annotations.h"
+#include "base/threading/thread_checker.h"
 #include "chrome/browser/password_manager/android/password_store_android_backend_bridge.h"
-#include "components/password_manager/core/browser/password_store_backend.h"
+
+namespace password_manager {
 
 // Native side of the JNI bridge to forward password store requests to Java.
 // JNI code is expensive to test. Therefore, any logic beyond data conversion
 // should either live in `PasswordStoreAndroidBackend` or a component that is
 // used by the java-side of this bridge.
+// Class could be instantiated and deleted on any thread. All methods should be
+// called on a single threaded sequence bound to a single background thread.
+// This class instance itself could be created and destroyed on any thread.
+// Thread affinity come from the JNIEnv which could only be used from a single
+// physical thread where JNIEnv was created.
 class PasswordStoreAndroidBackendBridgeImpl
-    : public password_manager::PasswordStoreAndroidBackendBridge {
+    : public PasswordStoreAndroidBackendBridge {
  public:
   PasswordStoreAndroidBackendBridgeImpl();
   PasswordStoreAndroidBackendBridgeImpl(
@@ -27,54 +35,40 @@
       const PasswordStoreAndroidBackendBridgeImpl&) = delete;
   ~PasswordStoreAndroidBackendBridgeImpl() override;
 
-  // Called via JNI. Called when the api call with `job_id` finished and
-  // provides the resulting `passwords`.
-  void OnCompleteWithLogins(
-      JNIEnv* env,
-      jint job_id,
-      const base::android::JavaParamRef<jbyteArray>& passwords);
-
-  // Called via JNI. Called when the api call with `job_id` finished that could
-  // have added, modified or deleted a login.
-  void OnLoginChanged(JNIEnv* env, jint job_id);
-
-  // Called via JNI. Called when the api call with `job_id` finished with
-  // an exception.
-  void OnError(JNIEnv* env,
-               jint job_id,
-               jint error_type,
-               jint api_error_code,
-               jboolean has_connection_result,
-               jint connection_result_code);
+  void Init(const PasswordStoreAndroidBackendConsumerBridge& consumer_bridge)
+      override;
 
  private:
   // Implements PasswordStoreAndroidBackendBridge interface.
-  void SetConsumer(base::WeakPtr<Consumer> consumer) override;
-  [[nodiscard]] JobId GetAllLogins(Account account) override;
-  [[nodiscard]] JobId GetAutofillableLogins(Account account) override;
-  [[nodiscard]] JobId GetLoginsForSignonRealm(const std::string& signon_realm,
-                                              Account account) override;
-  [[nodiscard]] JobId AddLogin(const password_manager::PasswordForm& form,
+  void GetAllLogins(JobId job_id, Account account) override;
+  void GetAutofillableLogins(JobId job_id, Account account) override;
+  void GetLoginsForSignonRealm(JobId job_id,
+                               const std::string& signon_realm,
                                Account account) override;
-  [[nodiscard]] JobId UpdateLogin(const password_manager::PasswordForm& form,
-                                  Account account) override;
-  [[nodiscard]] JobId RemoveLogin(const password_manager::PasswordForm& form,
-                                  Account account) override;
-
-  [[nodiscard]] JobId GetNextJobId();
+  void AddLogin(JobId job_id,
+                const password_manager::PasswordForm& form,
+                Account account) override;
+  void UpdateLogin(JobId job_id,
+                   const password_manager::PasswordForm& form,
+                   Account account) override;
+  void RemoveLogin(JobId job_id,
+                   const password_manager::PasswordForm& form,
+                   Account account) override;
 
   void ShowErrorNotification() override;
 
   // This member stores the unique ID last used for an API request.
   JobId last_job_id_{0};
 
-  // Weak reference to the `Consumer` that is notified when a job completes. It
-  // outlives this bridge but tasks may be posted to it.
-  base::WeakPtr<Consumer> consumer_ = nullptr;
-
   // This object is an instance of PasswordStoreAndroidBackendBridgeImpl, i.e.
   // the Java counterpart to this class.
-  base::android::ScopedJavaGlobalRef<jobject> java_object_;
+  base::android::ScopedJavaGlobalRef<jobject> java_object_
+      GUARDED_BY_CONTEXT(thread_checker_);
+
+  // All operations should be called on the same background thread.
+  THREAD_CHECKER(thread_checker_);
 };
 
+}  // namespace password_manager
+
 #endif  // CHROME_BROWSER_PASSWORD_MANAGER_ANDROID_PASSWORD_STORE_ANDROID_BACKEND_BRIDGE_IMPL_H_
diff --git a/chrome/browser/password_manager/android/password_store_android_backend_consumer_bridge.h b/chrome/browser/password_manager/android/password_store_android_backend_consumer_bridge.h
new file mode 100644
index 0000000..358e5ee
--- /dev/null
+++ b/chrome/browser/password_manager/android/password_store_android_backend_consumer_bridge.h
@@ -0,0 +1,75 @@
+// 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_PASSWORD_MANAGER_ANDROID_PASSWORD_STORE_ANDROID_BACKEND_CONSUMER_BRIDGE_H_
+#define CHROME_BROWSER_PASSWORD_MANAGER_ANDROID_PASSWORD_STORE_ANDROID_BACKEND_CONSUMER_BRIDGE_H_
+
+#include <vector>
+
+#include "base/android/scoped_java_ref.h"
+#include "base/types/strong_alias.h"
+#include "chrome/browser/password_manager/android/password_store_operation_target.h"
+#include "components/password_manager/core/browser/android_backend_error.h"
+#include "components/password_manager/core/browser/password_form.h"
+#include "components/password_manager/core/browser/password_store_backend.h"
+
+namespace password_manager {
+
+// Interface for the native side of the consumer JNI bridge. Defines callbacks
+// for backend operations to be called by the GMSCore via the bridge.
+// Simplifies mocking in tests. Implementers of this class are expected to be a
+// minimal layer of glue code that handles API callbacks for each operation.
+// Any logic beyond data conversion should either live in
+// `PasswordStoreAndroidBackend` or a component that is used by the java-side of
+// this bridge.
+class PasswordStoreAndroidBackendConsumerBridge {
+ public:
+  using SyncingAccount =
+      base::StrongAlias<struct SyncingAccountTag, std::string>;
+  using Account = absl::variant<PasswordStoreOperationTarget, SyncingAccount>;
+  using JobId = base::StrongAlias<struct JobIdTag, int>;
+
+  // Each bridge is created with a consumer that will be called when a job is
+  // completed. In order to identify which request the response belongs to, the
+  // bridge needs to pass a `JobId` back that was returned by the initial
+  // request and is unique per bridge.
+  class Consumer {
+   public:
+    virtual ~Consumer() = default;
+
+    // Asynchronous response called with the `job_id` which was passed to the
+    // corresponding call to `PasswordStoreAndroidBackendBridge`, and with the
+    // requested `passwords`.
+    // Used in response to `GetAllLogins`.
+    virtual void OnCompleteWithLogins(JobId job_id,
+                                      std::vector<PasswordForm> passwords) = 0;
+
+    // Asynchronous response called with the `job_id` which was passed to the
+    // corresponding call to `PasswordStoreAndroidBackendBridge`, and with the
+    // PasswordChanges.
+    // Used in response to 'AddLogin', 'UpdateLogin' and `RemoveLogin`.
+    virtual void OnLoginsChanged(JobId job_id, PasswordChanges changes) = 0;
+
+    // Asynchronous response called with the `job_id` which was passed to the
+    // corresponding call to `PasswordStoreAndroidBackendBridge`.
+    virtual void OnError(JobId job_id, AndroidBackendError error) = 0;
+  };
+
+  virtual ~PasswordStoreAndroidBackendConsumerBridge() = default;
+
+  // Sets the `consumer` that is notified on job completion.
+  virtual void SetConsumer(base::WeakPtr<Consumer> consumer) = 0;
+
+  // Returns reference to the Java JNI bridge object which is Java counterpart
+  // of this class.
+  virtual base::android::ScopedJavaGlobalRef<jobject> GetJavaBridge() const = 0;
+
+  // Factory function for creating the bridge. Implementation is pulled in by
+  // including an implementation or by defining it explicitly in tests.
+  static std::unique_ptr<PasswordStoreAndroidBackendConsumerBridge> Create();
+};
+
+}  // namespace password_manager
+
+#endif  // CHROME_BROWSER_PASSWORD_MANAGER_ANDROID_PASSWORD_STORE_ANDROID_BACKEND_CONSUMER_BRIDGE_H_
diff --git a/chrome/browser/password_manager/android/password_store_android_backend_consumer_bridge_impl.cc b/chrome/browser/password_manager/android/password_store_android_backend_consumer_bridge_impl.cc
new file mode 100644
index 0000000..be941531
--- /dev/null
+++ b/chrome/browser/password_manager/android/password_store_android_backend_consumer_bridge_impl.cc
@@ -0,0 +1,123 @@
+// 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/password_manager/android/password_store_android_backend_consumer_bridge_impl.h"
+
+#include <jni.h>
+#include <cstdint>
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_array.h"
+#include "base/sequence_checker.h"
+#include "base/task/sequenced_task_runner.h"
+#include "chrome/browser/password_manager/android/jni_headers/PasswordStoreAndroidBackendConsumerBridgeImpl_jni.h"
+#include "components/password_manager/core/browser/android_backend_error.h"
+#include "components/password_manager/core/browser/password_form.h"
+#include "components/password_manager/core/browser/protos/list_passwords_result.pb.h"
+#include "components/password_manager/core/browser/protos/password_with_local_data.pb.h"
+#include "components/password_manager/core/browser/unified_password_manager_proto_utils.h"
+
+namespace password_manager {
+
+namespace {
+
+using JobId = PasswordStoreAndroidBackendConsumerBridge::JobId;
+
+std::vector<PasswordForm> CreateFormsVector(
+    const base::android::JavaRef<jbyteArray>& passwords) {
+  std::vector<uint8_t> serialized_result;
+  base::android::JavaByteArrayToByteVector(base::android::AttachCurrentThread(),
+                                           passwords, &serialized_result);
+  ListPasswordsResult list_passwords_result;
+  bool parsing_succeeds = list_passwords_result.ParseFromArray(
+      serialized_result.data(), serialized_result.size());
+  DCHECK(parsing_succeeds);
+  auto forms = PasswordVectorFromListResult(list_passwords_result);
+  for (auto& form : forms) {
+    // Set proper in_store value for GMS Core storage.
+    form.in_store = PasswordForm::Store::kProfileStore;
+  }
+  return forms;
+}
+
+}  // namespace
+
+std::unique_ptr<PasswordStoreAndroidBackendConsumerBridge>
+PasswordStoreAndroidBackendConsumerBridge::Create() {
+  return std::make_unique<PasswordStoreAndroidBackendConsumerBridgeImpl>();
+}
+
+PasswordStoreAndroidBackendConsumerBridgeImpl::
+    PasswordStoreAndroidBackendConsumerBridgeImpl() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_checker_);
+  java_object_ = Java_PasswordStoreAndroidBackendConsumerBridgeImpl_create(
+      base::android::AttachCurrentThread(), reinterpret_cast<intptr_t>(this));
+}
+
+PasswordStoreAndroidBackendConsumerBridgeImpl::
+    ~PasswordStoreAndroidBackendConsumerBridgeImpl() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_checker_);
+  Java_PasswordStoreAndroidBackendConsumerBridgeImpl_destroy(
+      base::android::AttachCurrentThread(), java_object_);
+}
+
+base::android::ScopedJavaGlobalRef<jobject>
+PasswordStoreAndroidBackendConsumerBridgeImpl::GetJavaBridge() const {
+  return java_object_;
+}
+
+void PasswordStoreAndroidBackendConsumerBridgeImpl::SetConsumer(
+    base::WeakPtr<Consumer> consumer) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_checker_);
+  consumer_ = consumer;
+}
+
+void PasswordStoreAndroidBackendConsumerBridgeImpl::OnCompleteWithLogins(
+    JNIEnv* env,
+    jint job_id,
+    const base::android::JavaParamRef<jbyteArray>& passwords) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_checker_);
+  DCHECK(consumer_);
+  consumer_->OnCompleteWithLogins(JobId(job_id), CreateFormsVector(passwords));
+}
+
+void PasswordStoreAndroidBackendConsumerBridgeImpl::OnError(
+    JNIEnv* env,
+    jint job_id,
+    jint error_type,
+    jint api_error_code,
+    jboolean has_connection_result,
+    jint connection_result_code) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_checker_);
+  DCHECK(consumer_);
+  // Posting the tasks to the same sequence prevents that synchronous responses
+  // try to finish tasks before their registration was completed.
+  AndroidBackendError error{static_cast<AndroidBackendErrorType>(error_type)};
+
+  if (error.type == AndroidBackendErrorType::kExternalError) {
+    error.api_error_code = static_cast<int>(api_error_code);
+  }
+
+  if (has_connection_result) {
+    error.connection_result_code = static_cast<int>(connection_result_code);
+  }
+
+  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE,
+      base::BindOnce(
+          &PasswordStoreAndroidBackendConsumerBridge::Consumer::OnError,
+          consumer_, JobId(job_id), std::move(error)));
+}
+
+void PasswordStoreAndroidBackendConsumerBridgeImpl::OnLoginChanged(
+    JNIEnv* env,
+    jint job_id) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_checker_);
+  DCHECK(consumer_);
+  // Notifying that a login changed without providing a changelist prompts the
+  // caller to explicitly check the remaining logins.
+  consumer_->OnLoginsChanged(JobId(job_id), absl::nullopt);
+}
+
+}  // namespace password_manager
diff --git a/chrome/browser/password_manager/android/password_store_android_backend_consumer_bridge_impl.h b/chrome/browser/password_manager/android/password_store_android_backend_consumer_bridge_impl.h
new file mode 100644
index 0000000..a1554fd
--- /dev/null
+++ b/chrome/browser/password_manager/android/password_store_android_backend_consumer_bridge_impl.h
@@ -0,0 +1,73 @@
+// 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_PASSWORD_MANAGER_ANDROID_PASSWORD_STORE_ANDROID_BACKEND_CONSUMER_BRIDGE_IMPL_H_
+#define CHROME_BROWSER_PASSWORD_MANAGER_ANDROID_PASSWORD_STORE_ANDROID_BACKEND_CONSUMER_BRIDGE_IMPL_H_
+
+#include "base/android/scoped_java_ref.h"
+#include "chrome/browser/password_manager/android/password_store_android_backend_consumer_bridge.h"
+
+namespace password_manager {
+
+// Native side of the JNI bridge to handle password store callbacks from Java.
+// JNI code is expensive to test. Therefore, any logic beyond data conversion
+// should either live in `PasswordStoreAndroidBackend` or a component that is
+// used by the java-side of this bridge.
+class PasswordStoreAndroidBackendConsumerBridgeImpl
+    : public password_manager::PasswordStoreAndroidBackendConsumerBridge {
+ public:
+  PasswordStoreAndroidBackendConsumerBridgeImpl();
+  PasswordStoreAndroidBackendConsumerBridgeImpl(
+      PasswordStoreAndroidBackendConsumerBridgeImpl&&) = delete;
+  PasswordStoreAndroidBackendConsumerBridgeImpl(
+      const PasswordStoreAndroidBackendConsumerBridgeImpl&) = delete;
+  PasswordStoreAndroidBackendConsumerBridgeImpl& operator=(
+      PasswordStoreAndroidBackendConsumerBridgeImpl&&) = delete;
+  PasswordStoreAndroidBackendConsumerBridgeImpl& operator=(
+      const PasswordStoreAndroidBackendConsumerBridgeImpl&) = delete;
+  ~PasswordStoreAndroidBackendConsumerBridgeImpl() override;
+
+  base::android::ScopedJavaGlobalRef<jobject> GetJavaBridge() const override;
+
+  // Implements consumer interface
+  // Called via JNI. Called when the api call with `job_id` finished and
+  // provides the resulting `passwords`.
+  void OnCompleteWithLogins(
+      JNIEnv* env,
+      jint job_id,
+      const base::android::JavaParamRef<jbyteArray>& passwords);
+
+  // Called via JNI. Called when the api call with `job_id` finished that could
+  // have added, modified or deleted a login.
+  void OnLoginChanged(JNIEnv* env, jint job_id);
+
+  // Called via JNI. Called when the api call with `job_id` finished with
+  // an exception.
+  void OnError(JNIEnv* env,
+               jint job_id,
+               jint error_type,
+               jint api_error_code,
+               jboolean has_connection_result,
+               jint connection_result_code);
+
+ private:
+  // Implements PasswordStoreAndroidBackendBridge interface.
+  void SetConsumer(base::WeakPtr<Consumer> consumer) override;
+
+  // Weak reference to the `Consumer` that is notified when a job completes. It
+  // outlives this bridge but tasks may be posted to it.
+  base::WeakPtr<Consumer> consumer_ = nullptr;
+
+  // This object is an instance of
+  // `PasswordStoreAndroidBackendConsumerBridgeImpl`, i.e. the Java counterpart
+  // to this class.
+  base::android::ScopedJavaGlobalRef<jobject> java_object_;
+
+  // All callbacks should be called on the same background thread.
+  SEQUENCE_CHECKER(main_sequence_checker_);
+};
+
+}  // namespace password_manager
+
+#endif  // CHROME_BROWSER_PASSWORD_MANAGER_ANDROID_PASSWORD_STORE_ANDROID_BACKEND_CONSUMER_BRIDGE_IMPL_H_
diff --git a/chrome/browser/password_manager/android/password_store_android_backend_unittest.cc b/chrome/browser/password_manager/android/password_store_android_backend_unittest.cc
index 461b7fc..ae63cb71 100644
--- a/chrome/browser/password_manager/android/password_store_android_backend_unittest.cc
+++ b/chrome/browser/password_manager/android/password_store_android_backend_unittest.cc
@@ -7,6 +7,7 @@
 #include <memory>
 #include <vector>
 
+#include "base/android/scoped_java_ref.h"
 #include "base/callback_forward.h"
 #include "base/callback_helpers.h"
 #include "base/functional/callback_helpers.h"
@@ -23,6 +24,7 @@
 #include "chrome/browser/password_manager/android/password_manager_lifecycle_helper.h"
 #include "chrome/browser/password_manager/android/password_store_android_backend_api_error_codes.h"
 #include "chrome/browser/password_manager/android/password_store_android_backend_bridge.h"
+#include "chrome/browser/password_manager/android/password_store_android_backend_consumer_bridge.h"
 #include "chrome/browser/password_manager/android/password_sync_controller_delegate_android.h"
 #include "chrome/browser/password_manager/android/password_sync_controller_delegate_bridge_impl.h"
 #include "components/password_manager/core/browser/android_backend_error.h"
@@ -161,8 +163,8 @@
          ".APIError";
 }
 
-class MockPasswordStoreAndroidBackendBridge
-    : public PasswordStoreAndroidBackendBridge {
+class MockPasswordStoreAndroidBackendBridgeHelper
+    : public PasswordStoreAndroidBackendBridgeHelper {
  public:
   MOCK_METHOD(void, SetConsumer, (base::WeakPtr<Consumer>), (override));
   MOCK_METHOD(JobId, GetAllLogins, (Account), (override));
@@ -199,19 +201,23 @@
 
     backend_ = std::make_unique<PasswordStoreAndroidBackend>(
         base::PassKey<class PasswordStoreAndroidBackendTest>(),
-        CreateMockBridge(), CreateFakeLifecycleHelper(),
+        CreateMockBridgeHelper(), CreateFakeLifecycleHelper(),
         CreatePasswordSyncControllerDelegate(), &prefs_);
   }
 
   ~PasswordStoreAndroidBackendTest() override {
     lifecycle_helper_->UnregisterObserver();
     lifecycle_helper_ = nullptr;
-    testing::Mock::VerifyAndClearExpectations(bridge_);
+    testing::Mock::VerifyAndClearExpectations(bridge_helper_);
   }
 
   PasswordStoreBackend& backend() { return *backend_; }
-  PasswordStoreAndroidBackendBridge::Consumer& consumer() { return *backend_; }
-  MockPasswordStoreAndroidBackendBridge* bridge() { return bridge_; }
+  PasswordStoreAndroidBackendConsumerBridge::Consumer& consumer() {
+    return *backend_;
+  }
+  MockPasswordStoreAndroidBackendBridgeHelper* bridge_helper() {
+    return bridge_helper_;
+  }
   FakePasswordManagerLifecycleHelper* lifecycle_helper() {
     return lifecycle_helper_;
   }
@@ -240,12 +246,13 @@
       base::test::TaskEnvironment::TimeSource::MOCK_TIME};
 
  private:
-  std::unique_ptr<PasswordStoreAndroidBackendBridge> CreateMockBridge() {
-    auto unique_bridge =
-        std::make_unique<StrictMock<MockPasswordStoreAndroidBackendBridge>>();
-    bridge_ = unique_bridge.get();
-    EXPECT_CALL(*bridge_, SetConsumer);
-    return unique_bridge;
+  std::unique_ptr<PasswordStoreAndroidBackendBridgeHelper>
+  CreateMockBridgeHelper() {
+    auto unique_bridge_helper = std::make_unique<
+        StrictMock<MockPasswordStoreAndroidBackendBridgeHelper>>();
+    bridge_helper_ = unique_bridge_helper.get();
+    EXPECT_CALL(*bridge_helper_, SetConsumer);
+    return unique_bridge_helper;
   }
 
   std::unique_ptr<PasswordManagerLifecycleHelper> CreateFakeLifecycleHelper() {
@@ -265,7 +272,8 @@
   }
 
   std::unique_ptr<PasswordStoreAndroidBackend> backend_;
-  raw_ptr<StrictMock<MockPasswordStoreAndroidBackendBridge>> bridge_;
+  raw_ptr<StrictMock<MockPasswordStoreAndroidBackendBridgeHelper>>
+      bridge_helper_;
   raw_ptr<FakePasswordManagerLifecycleHelper> lifecycle_helper_;
   raw_ptr<PasswordSyncControllerDelegateAndroid> sync_controller_delegate_;
   syncer::TestSyncService sync_service_;
@@ -288,7 +296,8 @@
   backend().OnSyncServiceInitialized(sync_service());
 
   base::MockCallback<LoginsOrErrorReply> mock_reply;
-  EXPECT_CALL(*bridge(), GetAllLogins(ExpectSyncingAccount(kTestAccount)))
+  EXPECT_CALL(*bridge_helper(),
+              GetAllLogins(ExpectSyncingAccount(kTestAccount)))
       .WillOnce(Return(kJobId));
   backend().GetAllLoginsAsync(mock_reply.Get());
 
@@ -306,7 +315,8 @@
   base::MockCallback<LoginsOrErrorReply> mock_reply;
 
   const JobId kFirstJobId{1337};
-  EXPECT_CALL(*bridge(), GetLoginsForSignonRealm).WillOnce(Return(kFirstJobId));
+  EXPECT_CALL(*bridge_helper(), GetLoginsForSignonRealm)
+      .WillOnce(Return(kFirstJobId));
 
   std::string TestURL1("https://firstexample.com");
   std::string TestURL2("https://secondexample.com");
@@ -330,7 +340,7 @@
                       "https://mobile.secondexample.com/", kTestDateCreated);
 
   const JobId kSecondJobId{1338};
-  EXPECT_CALL(*bridge(), GetLoginsForSignonRealm)
+  EXPECT_CALL(*bridge_helper(), GetLoginsForSignonRealm)
       .WillOnce(Return(kSecondJobId));
   // Logins will be retrieved for forms from |forms| in a backwards order.
   consumer().OnCompleteWithLogins(kFirstJobId,
@@ -359,7 +369,8 @@
   base::MockCallback<LoginsOrErrorReply> mock_reply;
 
   const JobId kFirstJobId{1337};
-  EXPECT_CALL(*bridge(), GetLoginsForSignonRealm).WillOnce(Return(kFirstJobId));
+  EXPECT_CALL(*bridge_helper(), GetLoginsForSignonRealm)
+      .WillOnce(Return(kFirstJobId));
 
   std::string TestURL1("https://firstexample.com");
   std::string TestURL2("https://secondexample.com");
@@ -384,7 +395,7 @@
       "federation://mobile.secondexample.com/google.com/", kTestDateCreated);
 
   const JobId kSecondJobId{1338};
-  EXPECT_CALL(*bridge(), GetLoginsForSignonRealm)
+  EXPECT_CALL(*bridge_helper(), GetLoginsForSignonRealm)
       .WillOnce(Return(kSecondJobId));
   // Logins will be retrieved for forms from |forms| in a backwards order.
   consumer().OnCompleteWithLogins(kFirstJobId, {psl_matching_federated});
@@ -411,7 +422,8 @@
   base::MockCallback<LoginsOrErrorReply> mock_reply;
 
   const JobId kFirstJobId{1337};
-  EXPECT_CALL(*bridge(), GetLoginsForSignonRealm).WillOnce(Return(kFirstJobId));
+  EXPECT_CALL(*bridge_helper(), GetLoginsForSignonRealm)
+      .WillOnce(Return(kFirstJobId));
 
   std::string TestURL1("https://google.com");
 
@@ -441,7 +453,7 @@
   backend().InitBackend(PasswordStoreAndroidBackend::RemoteChangesReceived(),
                         base::RepeatingClosure(), base::DoNothing());
   base::MockCallback<LoginsOrErrorReply> mock_reply;
-  EXPECT_CALL(*bridge(), GetAutofillableLogins).WillOnce(Return(kJobId));
+  EXPECT_CALL(*bridge_helper(), GetAutofillableLogins).WillOnce(Return(kJobId));
   backend().GetAutofillableLoginsAsync(mock_reply.Get());
 
   std::vector<std::unique_ptr<PasswordForm>> expected_logins =
@@ -455,7 +467,7 @@
   backend().InitBackend(PasswordStoreAndroidBackend::RemoteChangesReceived(),
                         base::RepeatingClosure(), base::DoNothing());
   base::MockCallback<LoginsOrErrorReply> mock_reply;
-  EXPECT_CALL(*bridge(), GetAllLogins).WillOnce(Return(kJobId));
+  EXPECT_CALL(*bridge_helper(), GetAllLogins).WillOnce(Return(kJobId));
   absl::optional<std::string> account = "mytestemail@gmail.com";
   backend().GetAllLoginsForAccountAsync(account, mock_reply.Get());
 
@@ -475,7 +487,7 @@
 
   PasswordForm form =
       CreateTestLogin(kTestUsername, kTestPassword, kTestUrl, kTestDateCreated);
-  EXPECT_CALL(*bridge(), RemoveLogin(form, ExpectLocalAccount()))
+  EXPECT_CALL(*bridge_helper(), RemoveLogin(form, ExpectLocalAccount()))
       .WillOnce(Return(kRemoveLoginJobId));
   backend().RemoveLoginAsync(form, mock_reply.Get());
 
@@ -506,7 +518,7 @@
   // Check that calling RemoveLoginsByURLAndTime triggers logins retrieval
   // first.
   const JobId kGetLoginsJobId{13387};
-  EXPECT_CALL(*bridge(), GetAllLogins).WillOnce(Return(kGetLoginsJobId));
+  EXPECT_CALL(*bridge_helper(), GetAllLogins).WillOnce(Return(kGetLoginsJobId));
   backend().RemoveLoginsByURLAndTimeAsync(url_filter, delete_begin, delete_end,
                                           base::OnceCallback<void(bool)>(),
                                           mock_deletion_reply.Get());
@@ -514,7 +526,8 @@
   // Imitate login retrieval and check that it triggers the removal of matching
   // forms.
   const JobId kRemoveLoginJobId{13388};
-  EXPECT_CALL(*bridge(), RemoveLogin).WillOnce(Return(kRemoveLoginJobId));
+  EXPECT_CALL(*bridge_helper(), RemoveLogin)
+      .WillOnce(Return(kRemoveLoginJobId));
   PasswordForm form_to_delete = CreateTestLogin(
       kTestUsername, kTestPassword, kTestUrl, base::Time::FromTimeT(1500));
   PasswordForm form_to_keep =
@@ -555,14 +568,15 @@
   // Check that calling RemoveLoginsCreatedBetween triggers logins retrieval
   // first.
   const JobId kGetLoginsJobId{13387};
-  EXPECT_CALL(*bridge(), GetAllLogins).WillOnce(Return(kGetLoginsJobId));
+  EXPECT_CALL(*bridge_helper(), GetAllLogins).WillOnce(Return(kGetLoginsJobId));
   backend().RemoveLoginsCreatedBetweenAsync(delete_begin, delete_end,
                                             mock_deletion_reply.Get());
 
   // Imitate login retrieval and check that it triggers the removal of matching
   // forms.
   const JobId kRemoveLoginJobId{13388};
-  EXPECT_CALL(*bridge(), RemoveLogin).WillOnce(Return(kRemoveLoginJobId));
+  EXPECT_CALL(*bridge_helper(), RemoveLogin)
+      .WillOnce(Return(kRemoveLoginJobId));
   PasswordForm form_to_delete = CreateTestLogin(
       kTestUsername, kTestPassword, kTestUrl, base::Time::FromTimeT(1500));
   PasswordForm form_to_keep = CreateTestLogin(
@@ -596,7 +610,8 @@
   base::MockCallback<PasswordChangesOrErrorReply> mock_reply;
   PasswordForm form =
       CreateTestLogin(kTestUsername, kTestPassword, kTestUrl, kTestDateCreated);
-  EXPECT_CALL(*bridge(), AddLogin(form, ExpectSyncingAccount(kTestAccount)))
+  EXPECT_CALL(*bridge_helper(),
+              AddLogin(form, ExpectSyncingAccount(kTestAccount)))
       .WillOnce(Return(kAddLoginJobId));
   backend().AddLoginAsync(form, mock_reply.Get());
 
@@ -617,7 +632,7 @@
   base::MockCallback<PasswordChangesOrErrorReply> mock_reply;
   PasswordForm form =
       CreateTestLogin(kTestUsername, kTestPassword, kTestUrl, kTestDateCreated);
-  EXPECT_CALL(*bridge(), UpdateLogin(form, ExpectLocalAccount()))
+  EXPECT_CALL(*bridge_helper(), UpdateLogin(form, ExpectLocalAccount()))
       .WillOnce(Return(kUpdateLoginJobId));
   backend().UpdateLoginAsync(form, mock_reply.Get());
 
@@ -640,7 +655,7 @@
   backend().OnSyncServiceInitialized(sync_service());
 
   base::MockCallback<LoginsOrErrorReply> mock_reply;
-  EXPECT_CALL(*bridge(), GetAllLogins).WillOnce(Return(kJobId));
+  EXPECT_CALL(*bridge_helper(), GetAllLogins).WillOnce(Return(kJobId));
   backend().GetAllLoginsAsync(mock_reply.Get());
   EXPECT_CALL(
       mock_reply,
@@ -693,7 +708,7 @@
   backend().OnSyncServiceInitialized(sync_service());
 
   base::MockCallback<LoginsOrErrorReply> mock_reply;
-  EXPECT_CALL(*bridge(), GetAllLogins).WillOnce(Return(kJobId));
+  EXPECT_CALL(*bridge_helper(), GetAllLogins).WillOnce(Return(kJobId));
   backend().GetAllLoginsAsync(mock_reply.Get());
   EXPECT_CALL(
       mock_reply,
@@ -737,7 +752,7 @@
   backend().OnSyncServiceInitialized(sync_service());
 
   base::MockCallback<LoginsOrErrorReply> mock_reply;
-  EXPECT_CALL(*bridge(), GetAllLogins).WillOnce(Return(kJobId));
+  EXPECT_CALL(*bridge_helper(), GetAllLogins).WillOnce(Return(kJobId));
   backend().GetAllLoginsAsync(mock_reply.Get());
   EXPECT_CALL(
       mock_reply,
@@ -781,7 +796,7 @@
   backend().OnSyncServiceInitialized(sync_service());
 
   base::MockCallback<LoginsOrErrorReply> mock_reply;
-  EXPECT_CALL(*bridge(), GetAllLogins).WillOnce(Return(kJobId));
+  EXPECT_CALL(*bridge_helper(), GetAllLogins).WillOnce(Return(kJobId));
   backend().GetAllLoginsAsync(mock_reply.Get());
   EXPECT_CALL(
       mock_reply,
@@ -835,7 +850,7 @@
   backend().OnSyncServiceInitialized(sync_service());
 
   base::MockCallback<PasswordChangesOrErrorReply> mock_reply;
-  EXPECT_CALL(*bridge(), AddLogin).WillOnce(Return(kJobId));
+  EXPECT_CALL(*bridge_helper(), AddLogin).WillOnce(Return(kJobId));
   PasswordForm form =
       CreateTestLogin(kTestUsername, kTestPassword, kTestUrl, kTestDateCreated);
 
@@ -908,7 +923,9 @@
   backend().OnSyncServiceInitialized(sync_service());
 
   base::MockCallback<LoginsOrErrorReply> mock_reply;
-  EXPECT_CALL(*bridge(), GetAllLogins).Times(6).WillRepeatedly(Return(kJobId));
+  EXPECT_CALL(*bridge_helper(), GetAllLogins)
+      .Times(6)
+      .WillRepeatedly(Return(kJobId));
 
   // Initiating the first call.
   backend().GetAllLoginsAsync(mock_reply.Get());
@@ -1001,7 +1018,7 @@
   // repeating.
   const JobId kFailedJobId{1};
   const JobId kSucceedJobId{2};
-  EXPECT_CALL(*bridge(), GetAllLogins)
+  EXPECT_CALL(*bridge_helper(), GetAllLogins)
       .WillOnce(Return(kFailedJobId))
       .WillOnce(Return(kSucceedJobId));
 
@@ -1062,7 +1079,7 @@
   EXPECT_FALSE(prefs()->GetBoolean(prefs::kSavePasswordsSuspendedByError));
 
   base::MockCallback<LoginsOrErrorReply> mock_reply;
-  EXPECT_CALL(*bridge(), GetAllLogins).WillOnce(Return(kJobId));
+  EXPECT_CALL(*bridge_helper(), GetAllLogins).WillOnce(Return(kJobId));
   backend().GetAllLoginsAsync(mock_reply.Get());
 
   EXPECT_CALL(
@@ -1097,7 +1114,7 @@
   EXPECT_FALSE(prefs()->GetBoolean(prefs::kSavePasswordsSuspendedByError));
 
   base::MockCallback<LoginsOrErrorReply> mock_reply;
-  EXPECT_CALL(*bridge(), GetAllLogins).WillOnce(Return(kJobId));
+  EXPECT_CALL(*bridge_helper(), GetAllLogins).WillOnce(Return(kJobId));
   backend().GetAllLoginsAsync(mock_reply.Get());
   EXPECT_CALL(
       mock_reply,
@@ -1131,7 +1148,7 @@
   EXPECT_FALSE(prefs()->GetBoolean(prefs::kSavePasswordsSuspendedByError));
 
   base::MockCallback<LoginsOrErrorReply> mock_reply;
-  EXPECT_CALL(*bridge(), GetAllLogins).WillOnce(Return(kJobId));
+  EXPECT_CALL(*bridge_helper(), GetAllLogins).WillOnce(Return(kJobId));
   backend().GetAllLoginsAsync(mock_reply.Get());
   EXPECT_CALL(
       mock_reply,
@@ -1161,7 +1178,7 @@
 
   // Simulate a successful logins call.
   base::MockCallback<LoginsOrErrorReply> mock_reply;
-  EXPECT_CALL(*bridge(), GetAllLogins).WillOnce(Return(kJobId));
+  EXPECT_CALL(*bridge_helper(), GetAllLogins).WillOnce(Return(kJobId));
   backend().GetAllLoginsAsync(mock_reply.Get());
   EXPECT_CALL(mock_reply, Run(_));
   task_environment_.FastForwardBy(kTestLatencyDelta);
@@ -1180,7 +1197,7 @@
   backend().OnSyncServiceInitialized(sync_service());
 
   base::MockCallback<LoginsOrErrorReply> mock_reply;
-  EXPECT_CALL(*bridge(), GetAllLogins).WillOnce(Return(kJobId));
+  EXPECT_CALL(*bridge_helper(), GetAllLogins).WillOnce(Return(kJobId));
   backend().GetAllLoginsAsync(mock_reply.Get());
   EXPECT_CALL(
       mock_reply,
@@ -1228,7 +1245,7 @@
   ASSERT_FALSE(sync_service()->GetAuthError().IsPersistentError());
 
   base::MockCallback<LoginsOrErrorReply> mock_reply;
-  EXPECT_CALL(*bridge(), GetAllLogins).WillOnce(Return(kJobId));
+  EXPECT_CALL(*bridge_helper(), GetAllLogins).WillOnce(Return(kJobId));
   backend().GetAllLoginsAsync(mock_reply.Get());
   EXPECT_CALL(
       mock_reply,
@@ -1259,7 +1276,7 @@
   sync_service()->SetTransientAuthError();
 
   base::MockCallback<LoginsOrErrorReply> mock_reply;
-  EXPECT_CALL(*bridge(), GetAllLogins).WillOnce(Return(kJobId));
+  EXPECT_CALL(*bridge_helper(), GetAllLogins).WillOnce(Return(kJobId));
   backend().GetAllLoginsAsync(mock_reply.Get());
   EXPECT_CALL(
       mock_reply,
@@ -1290,7 +1307,7 @@
   sync_service()->SetPersistentAuthErrorOtherThanWebSignout();
 
   base::MockCallback<LoginsOrErrorReply> mock_reply;
-  EXPECT_CALL(*bridge(), GetAllLogins).WillOnce(Return(kJobId));
+  EXPECT_CALL(*bridge_helper(), GetAllLogins).WillOnce(Return(kJobId));
   backend().GetAllLoginsAsync(mock_reply.Get());
   EXPECT_CALL(
       mock_reply,
@@ -1320,7 +1337,7 @@
   backend().OnSyncServiceInitialized(sync_service());
 
   base::MockCallback<LoginsOrErrorReply> mock_reply;
-  EXPECT_CALL(*bridge(), GetAllLogins).WillOnce(Return(kJobId));
+  EXPECT_CALL(*bridge_helper(), GetAllLogins).WillOnce(Return(kJobId));
   backend().GetAllLoginsAsync(mock_reply.Get());
   EXPECT_CALL(
       mock_reply,
@@ -1332,7 +1349,7 @@
       static_cast<int>(AndroidBackendAPIErrorCode::kInternalError);
   error.api_error_code = absl::optional<int>(kInternalErrorCode);
 
-  EXPECT_CALL(*bridge(), ShowErrorNotification);
+  EXPECT_CALL(*bridge_helper(), ShowErrorNotification);
   consumer().OnError(kJobId, std::move(error));
 
   RunUntilIdle();
@@ -1348,7 +1365,7 @@
   backend().OnSyncServiceInitialized(sync_service());
 
   base::MockCallback<LoginsOrErrorReply> mock_reply;
-  EXPECT_CALL(*bridge(), GetAllLogins).WillOnce(Return(kJobId));
+  EXPECT_CALL(*bridge_helper(), GetAllLogins).WillOnce(Return(kJobId));
   backend().GetAllLoginsAsync(mock_reply.Get());
   EXPECT_CALL(
       mock_reply,
@@ -1360,7 +1377,7 @@
       static_cast<int>(AndroidBackendAPIErrorCode::kInternalError);
   error.api_error_code = absl::optional<int>(kInternalErrorCode);
 
-  EXPECT_CALL(*bridge(), ShowErrorNotification).Times(0);
+  EXPECT_CALL(*bridge_helper(), ShowErrorNotification).Times(0);
   consumer().OnError(kJobId, std::move(error));
 
   RunUntilIdle();
@@ -1377,7 +1394,7 @@
   // Check that calling DisableAutoSignInForOrigins triggers logins retrieval
   // first.
   const JobId kGetLoginsJobId{13387};
-  EXPECT_CALL(*bridge(), GetAllLogins).WillOnce(Return(kGetLoginsJobId));
+  EXPECT_CALL(*bridge_helper(), GetAllLogins).WillOnce(Return(kGetLoginsJobId));
 
   base::RepeatingCallback<bool(const GURL&)> origin_filter =
       base::BindRepeating(
@@ -1399,7 +1416,7 @@
 
   const JobId kUpdateJobId1{13388};
   // Forms are updated in reverse order.
-  EXPECT_CALL(*bridge(),
+  EXPECT_CALL(*bridge_helper(),
               UpdateLogin(FormWithDisabledAutoSignIn(form_to_update2),
                           ExpectSyncingAccount(kTestAccount)))
       .WillOnce(Return(kUpdateJobId1));
@@ -1420,7 +1437,7 @@
       PasswordStoreChange(PasswordStoreChange::UPDATE,
                           FormWithDisabledAutoSignIn(form_to_update2)));
   const JobId kUpdateJobId2{13389};
-  EXPECT_CALL(*bridge(),
+  EXPECT_CALL(*bridge_helper(),
               UpdateLogin(FormWithDisabledAutoSignIn(form_to_update1),
                           ExpectSyncingAccount(kTestAccount)))
       .WillOnce(Return(kUpdateJobId2));
@@ -1453,7 +1470,7 @@
                         base::RepeatingClosure(), base::DoNothing());
 
   const JobId kGetLoginsJobId{13387};
-  EXPECT_CALL(*bridge(), GetAllLogins(ExpectLocalAccount()))
+  EXPECT_CALL(*bridge_helper(), GetAllLogins(ExpectLocalAccount()))
       .WillOnce(Return(kGetLoginsJobId));
   backend().ClearAllLocalPasswords();
 
@@ -1461,7 +1478,8 @@
   const JobId kRemoveLoginJobId{13388};
   PasswordForm form_to_delete = CreateTestLogin(
       kTestUsername, kTestPassword, kTestUrl, base::Time::FromTimeT(1500));
-  EXPECT_CALL(*bridge(), RemoveLogin(form_to_delete, ExpectLocalAccount()))
+  EXPECT_CALL(*bridge_helper(),
+              RemoveLogin(form_to_delete, ExpectLocalAccount()))
       .WillOnce(Return(kRemoveLoginJobId));
 
   task_environment_.FastForwardBy(kTestLatencyDelta);
@@ -1488,7 +1506,7 @@
   backend().OnSyncServiceInitialized(sync_service());
 
   const JobId kGetLoginsJobId{13387};
-  EXPECT_CALL(*bridge(), GetAllLogins(ExpectLocalAccount()))
+  EXPECT_CALL(*bridge_helper(), GetAllLogins(ExpectLocalAccount()))
       .WillOnce(Return(kGetLoginsJobId));
   backend().ClearAllLocalPasswords();
 
@@ -1496,21 +1514,23 @@
   const JobId kRemoveLoginJobId1{13388}, kRemoveLoginJobId2{13389};
   PasswordForm form = CreateTestLogin(kTestUsername, kTestPassword, kTestUrl,
                                       base::Time::FromTimeT(1500));
-  EXPECT_CALL(*bridge(), RemoveLogin).WillOnce(Return(kRemoveLoginJobId1));
+  EXPECT_CALL(*bridge_helper(), RemoveLogin)
+      .WillOnce(Return(kRemoveLoginJobId1));
 
   // Simulate GetLoginAsync returns two identical logins
   consumer().OnCompleteWithLogins(kGetLoginsJobId, {form, form});
   RunUntilIdle();
-  testing::Mock::VerifyAndClearExpectations(bridge());
+  testing::Mock::VerifyAndClearExpectations(bridge_helper());
 
   PasswordStoreChangeList changes = {
       PasswordStoreChange(PasswordStoreChange::REMOVE, form)};
   // Receiving callback after removing the first login should trigger
   // removal of the second login.
-  EXPECT_CALL(*bridge(), RemoveLogin).WillOnce(Return(kRemoveLoginJobId2));
+  EXPECT_CALL(*bridge_helper(), RemoveLogin)
+      .WillOnce(Return(kRemoveLoginJobId2));
   consumer().OnLoginsChanged(kRemoveLoginJobId1, changes);
   RunUntilIdle();
-  testing::Mock::VerifyAndClearExpectations(bridge());
+  testing::Mock::VerifyAndClearExpectations(bridge_helper());
 
   // Simulate that the second removal failed.
   consumer().OnError(
@@ -1564,7 +1584,7 @@
                         /*completion=*/base::DoNothing());
 
   base::MockCallback<PasswordChangesOrErrorReply> mock_reply;
-  EXPECT_CALL(*bridge(), AddLogin).WillOnce(Return(kJobId));
+  EXPECT_CALL(*bridge_helper(), AddLogin).WillOnce(Return(kJobId));
   // Since tasks are cleaned up too early, the reply should never be called.
   EXPECT_CALL(mock_reply, Run).Times(0);
 
@@ -1610,7 +1630,7 @@
                         /*completion=*/base::DoNothing());
 
   base::MockCallback<PasswordChangesOrErrorReply> mock_reply;
-  EXPECT_CALL(*bridge(), AddLogin).WillOnce(Return(kJobId));
+  EXPECT_CALL(*bridge_helper(), AddLogin).WillOnce(Return(kJobId));
   // Since tasks are never run, the reply should never be called.
   EXPECT_CALL(mock_reply, Run).Times(0);
 
@@ -1684,7 +1704,7 @@
   backend().OnSyncServiceInitialized(sync_service());
 
   base::MockCallback<LoginsOrErrorReply> mock_reply;
-  EXPECT_CALL(*bridge(), GetAllLogins).WillOnce(Return(kJobId));
+  EXPECT_CALL(*bridge_helper(), GetAllLogins).WillOnce(Return(kJobId));
   backend().GetAllLoginsAsync(mock_reply.Get());
   AndroidBackendError error{AndroidBackendErrorType::kExternalError};
   // Simulate receiving AUTH_ERROR_RESOLVABLE code.
@@ -1722,7 +1742,7 @@
   backend().OnSyncServiceInitialized(sync_service());
 
   base::MockCallback<LoginsOrErrorReply> mock_reply;
-  EXPECT_CALL(*bridge(), GetAllLogins).WillOnce(Return(kJobId));
+  EXPECT_CALL(*bridge_helper(), GetAllLogins).WillOnce(Return(kJobId));
   backend().GetAllLoginsAsync(mock_reply.Get());
   AndroidBackendError error{AndroidBackendErrorType::kExternalError};
   // Simulate receiving AUTH_ERROR_RESOLVABLE code.
@@ -1759,7 +1779,7 @@
   backend().OnSyncServiceInitialized(sync_service());
 
   base::MockCallback<LoginsOrErrorReply> mock_reply;
-  EXPECT_CALL(*bridge(), GetAllLogins).WillOnce(Return(kJobId));
+  EXPECT_CALL(*bridge_helper(), GetAllLogins).WillOnce(Return(kJobId));
   backend().GetAllLoginsAsync(mock_reply.Get());
   // Pretend 2 prompts were already shown.
   prefs()->SetInteger(prefs::kTimesUPMAuthErrorShown, 2);
@@ -1797,7 +1817,7 @@
   base::MockCallback<PasswordChangesOrErrorReply> mock_reply;
   PasswordForm form =
       CreateTestLogin(kTestUsername, kTestPassword, kTestUrl, kTestDateCreated);
-  EXPECT_CALL(*bridge(), RemoveLogin).WillOnce(Return(kJobId));
+  EXPECT_CALL(*bridge_helper(), RemoveLogin).WillOnce(Return(kJobId));
   backend().RemoveLoginAsync(form, mock_reply.Get());
   PasswordStoreChangeList expected_changes;
   expected_changes.emplace_back(PasswordStoreChange::REMOVE, form);
@@ -1837,7 +1857,7 @@
       ApiErrorMetricName(kGetAllLoginsMethodName);
 
   base::MockCallback<LoginsOrErrorReply> mock_reply;
-  EXPECT_CALL(*bridge(), GetAllLogins).WillOnce(Return(kJobId));
+  EXPECT_CALL(*bridge_helper(), GetAllLogins).WillOnce(Return(kJobId));
   backend().GetAllLoginsAsync(mock_reply.Get());
   EXPECT_CALL(mock_reply, Run(_)).Times(1);
   task_environment_.FastForwardBy(kTestLatencyDelta);
@@ -1881,7 +1901,7 @@
   const std::string kApiErrorMetric = ApiErrorMetricName(kAddLoginMethodName);
 
   base::MockCallback<PasswordChangesOrErrorReply> mock_reply;
-  EXPECT_CALL(*bridge(), AddLogin).WillOnce(Return(kJobId));
+  EXPECT_CALL(*bridge_helper(), AddLogin).WillOnce(Return(kJobId));
   PasswordForm form =
       CreateTestLogin(kTestUsername, kTestPassword, kTestUrl, kTestDateCreated);
   backend().AddLoginAsync(form, mock_reply.Get());
@@ -1929,7 +1949,7 @@
       ApiErrorMetricName(kUpdateLoginMethodName);
 
   base::MockCallback<PasswordChangesOrErrorReply> mock_reply;
-  EXPECT_CALL(*bridge(), UpdateLogin).WillOnce(Return(kJobId));
+  EXPECT_CALL(*bridge_helper(), UpdateLogin).WillOnce(Return(kJobId));
   PasswordForm form =
       CreateTestLogin(kTestUsername, kTestPassword, kTestUrl, kTestDateCreated);
   backend().UpdateLoginAsync(form, mock_reply.Get());
@@ -1977,7 +1997,7 @@
       ApiErrorMetricName(kRemoveLoginMethodName);
 
   base::MockCallback<PasswordChangesOrErrorReply> mock_reply;
-  EXPECT_CALL(*bridge(), RemoveLogin).WillOnce(Return(kJobId));
+  EXPECT_CALL(*bridge_helper(), RemoveLogin).WillOnce(Return(kJobId));
   PasswordForm form =
       CreateTestLogin(kTestUsername, kTestPassword, kTestUrl, kTestDateCreated);
   backend().RemoveLoginAsync(form, mock_reply.Get());
@@ -2026,7 +2046,7 @@
       ApiErrorMetricName(kGetAutofillableLoginsMethodName);
 
   base::MockCallback<LoginsOrErrorReply> mock_reply;
-  EXPECT_CALL(*bridge(), GetAutofillableLogins).WillOnce(Return(kJobId));
+  EXPECT_CALL(*bridge_helper(), GetAutofillableLogins).WillOnce(Return(kJobId));
   backend().GetAutofillableLoginsAsync(mock_reply.Get());
   EXPECT_CALL(mock_reply, Run(_)).Times(1);
   task_environment_.FastForwardBy(kTestLatencyDelta);
diff --git a/chrome/browser/password_manager/chrome_webauthn_credentials_delegate.cc b/chrome/browser/password_manager/chrome_webauthn_credentials_delegate.cc
index a8bca78f..a6fe2a6 100644
--- a/chrome/browser/password_manager/chrome_webauthn_credentials_delegate.cc
+++ b/chrome/browser/password_manager/chrome_webauthn_credentials_delegate.cc
@@ -23,7 +23,7 @@
 #endif  // !BUILDFLAG(IS_ANDROID)
 
 #if BUILDFLAG(IS_ANDROID)
-#include "chrome/browser/webauthn/android/conditional_ui_delegate_android.h"
+#include "chrome/browser/webauthn/android/webauthn_request_delegate_android.h"
 #endif
 
 ChromeWebAuthnCredentialsDelegate::ChromeWebAuthnCredentialsDelegate(
@@ -57,12 +57,12 @@
   DCHECK(selected_credential_id);
 
 #if BUILDFLAG(IS_ANDROID)
-  auto* credentials_delegate =
-      ConditionalUiDelegateAndroid::GetConditionalUiDelegate(web_contents_);
-  if (!credentials_delegate) {
+  auto* request_delegate =
+      WebAuthnRequestDelegateAndroid::GetRequestDelegate(web_contents_);
+  if (!request_delegate) {
     return;
   }
-  credentials_delegate->OnWebAuthnAccountSelected(*selected_credential_id);
+  request_delegate->OnWebAuthnAccountSelected(*selected_credential_id);
 #else
   ChromeAuthenticatorRequestDelegate* authenticator_delegate =
       AuthenticatorRequestScheduler::GetRequestDelegate(web_contents_);
diff --git a/chrome/browser/password_manager/chrome_webauthn_credentials_delegate_unittest.cc b/chrome/browser/password_manager/chrome_webauthn_credentials_delegate_unittest.cc
index eb4deb7..6a8473a9 100644
--- a/chrome/browser/password_manager/chrome_webauthn_credentials_delegate_unittest.cc
+++ b/chrome/browser/password_manager/chrome_webauthn_credentials_delegate_unittest.cc
@@ -35,7 +35,7 @@
 #endif  // !BUILDFLAG(IS_ANDROID)
 
 #if BUILDFLAG(IS_ANDROID)
-#include "chrome/browser/webauthn/android/conditional_ui_delegate_android.h"
+#include "chrome/browser/webauthn/android/webauthn_request_delegate_android.h"
 #endif
 
 namespace {
@@ -95,7 +95,7 @@
     authenticator_request_delegate_->SetRelyingPartyId("rpId");
 #else
     delegate_ =
-        ConditionalUiDelegateAndroid::GetConditionalUiDelegate(web_contents());
+        WebAuthnRequestDelegateAndroid::GetRequestDelegate(web_contents());
 #endif
 
     content::WebContentsTester::For(web_contents())
@@ -151,7 +151,7 @@
   std::unique_ptr<ChromeAuthenticatorRequestDelegate>
       authenticator_request_delegate_;
 #else
-  raw_ptr<ConditionalUiDelegateAndroid> delegate_;
+  raw_ptr<WebAuthnRequestDelegateAndroid> delegate_;
   absl::optional<std::vector<uint8_t>> selected_id_;
 #endif
 };
diff --git a/chrome/browser/password_manager/password_store_backend_factory.cc b/chrome/browser/password_manager/password_store_backend_factory.cc
index 5ccfb7d..e19ca0bd 100644
--- a/chrome/browser/password_manager/password_store_backend_factory.cc
+++ b/chrome/browser/password_manager/password_store_backend_factory.cc
@@ -32,7 +32,7 @@
   return std::make_unique<PasswordStoreBuiltInBackend>(
       CreateLoginDatabaseForProfileStorage(login_db_path));
 #else  // BUILDFLAG(IS_ANDROID) && !USE_LEGACY_PASSWORD_STORE_BACKEND
-  if (PasswordStoreAndroidBackendBridge::CanCreateBackend() &&
+  if (PasswordStoreAndroidBackendBridgeHelper::CanCreateBackend() &&
       base::FeatureList::IsEnabled(
           password_manager::features::kUnifiedPasswordManagerAndroid)) {
     // Re-enrollment happens before the initial migration and any possible
diff --git a/chrome/browser/pdf/pdf_extension_test.cc b/chrome/browser/pdf/pdf_extension_test.cc
index fce09322..cba7d77 100644
--- a/chrome/browser/pdf/pdf_extension_test.cc
+++ b/chrome/browser/pdf/pdf_extension_test.cc
@@ -4495,20 +4495,17 @@
 #define MAYBE_SubmitForm SubmitForm
 #endif
 IN_PROC_BROWSER_TEST_F(PDFExtensionSubmitFormTest, MAYBE_SubmitForm) {
-  MimeHandlerViewGuest* guest = LoadPdfGetMimeHandlerView(
+  WebContents* guest_contents = LoadPdfGetGuestContents(
       embedded_test_server()->GetURL("/pdf/submit_form.pdf"));
-  ASSERT_TRUE(guest);
-
-  content::RenderFrameHost* guest_mainframe = guest->GetGuestMainFrame();
-  ASSERT_TRUE(guest_mainframe);
+  ASSERT_TRUE(guest_contents);
 
   std::unique_ptr<base::RunLoop> run_loop = CreateFormSubmissionRunLoop();
 
   // Click on the "Submit Form" button.
   content::SimulateMouseClickAt(
-      GetActiveWebContents(), blink::WebInputEvent::kNoModifiers,
+      guest_contents, blink::WebInputEvent::kNoModifiers,
       blink::WebMouseEvent::Button::kLeft,
-      ConvertPageCoordToScreenCoord(guest_mainframe, {200, 200}));
+      ConvertPageCoordToScreenCoord(guest_contents, {200, 200}));
 
   run_loop->Run();
 }
diff --git a/chrome/browser/policy/configuration_policy_handler_list_factory.cc b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
index 764b60e..1de919b8 100644
--- a/chrome/browser/policy/configuration_policy_handler_list_factory.cc
+++ b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
@@ -1031,13 +1031,6 @@
   { key::kDeviceKeyboardBacklightColor,
     ash::prefs::kPersonalizationKeyboardBacklightColor,
     base::Value::Type::INTEGER },
-  // Note that this pref exists in both user PrefStore and local_state
-  // PrefStore, and it is intended that the device policy is mapped to
-  // both. See the comment at the definition of
-  // ash::prefs::kPersonalizationKeyboardBrightness for details.
-  { key::kDeviceKeyboardBrightness,
-    ash::prefs::kPersonalizationKeyboardBrightness,
-    base::Value::Type::INTEGER },
   { key::kRebootAfterUpdate,
     prefs::kRebootAfterUpdate,
     base::Value::Type::BOOLEAN },
diff --git a/chrome/browser/privacy_guide/android/java/src/org/chromium/chrome/browser/privacy_guide/PrivacyGuideMetricsDelegate.java b/chrome/browser/privacy_guide/android/java/src/org/chromium/chrome/browser/privacy_guide/PrivacyGuideMetricsDelegate.java
index f888463..1694fea 100644
--- a/chrome/browser/privacy_guide/android/java/src/org/chromium/chrome/browser/privacy_guide/PrivacyGuideMetricsDelegate.java
+++ b/chrome/browser/privacy_guide/android/java/src/org/chromium/chrome/browser/privacy_guide/PrivacyGuideMetricsDelegate.java
@@ -89,4 +89,16 @@
     static void recordMetricsForDoneButton() {
         RecordUserAction.record("Settings.PrivacyGuide.NextClickCompletion");
     }
+
+    /**
+     * A method to record metrics on the History Sync toggle change of the Privacy Guide's {@link
+     * SyncFragment}.
+     */
+    static void recordMetricsOnSyncChange(boolean isHistorySyncOn) {
+        if (isHistorySyncOn) {
+            RecordUserAction.record("Settings.PrivacyGuide.ChangeHistorySyncOn");
+        } else {
+            RecordUserAction.record("Settings.PrivacyGuide.ChangeHistorySyncOff");
+        }
+    }
 }
diff --git a/chrome/browser/privacy_guide/android/java/src/org/chromium/chrome/browser/privacy_guide/SyncFragment.java b/chrome/browser/privacy_guide/android/java/src/org/chromium/chrome/browser/privacy_guide/SyncFragment.java
index 337d4dfb..a358ea0 100644
--- a/chrome/browser/privacy_guide/android/java/src/org/chromium/chrome/browser/privacy_guide/SyncFragment.java
+++ b/chrome/browser/privacy_guide/android/java/src/org/chromium/chrome/browser/privacy_guide/SyncFragment.java
@@ -46,6 +46,8 @@
 
     @Override
     public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+        PrivacyGuideMetricsDelegate.recordMetricsOnSyncChange(isChecked);
+
         boolean keepEverythingSynced = isChecked && mInitialKeepEverythingSynced;
 
         Set<Integer> syncTypes = mSyncService.getSelectedTypes();
diff --git a/chrome/browser/privacy_guide/android/junit/src/org/chromium/chrome/browser/privacy_guide/PrivacyGuideMetricsDelegateTest.java b/chrome/browser/privacy_guide/android/junit/src/org/chromium/chrome/browser/privacy_guide/PrivacyGuideMetricsDelegateTest.java
index 54f9944..a77f55f 100644
--- a/chrome/browser/privacy_guide/android/junit/src/org/chromium/chrome/browser/privacy_guide/PrivacyGuideMetricsDelegateTest.java
+++ b/chrome/browser/privacy_guide/android/junit/src/org/chromium/chrome/browser/privacy_guide/PrivacyGuideMetricsDelegateTest.java
@@ -123,4 +123,20 @@
         triggerMSBBMetricsOnNext();
         assertTrue(mActionTester.getActions().contains("Settings.PrivacyGuide.NextClickMSBB"));
     }
+
+    @Test
+    @SmallTest
+    public void testSync_changeHistorySyncOnUserAction() {
+        PrivacyGuideMetricsDelegate.recordMetricsOnSyncChange(true);
+        assertTrue(
+                mActionTester.getActions().contains("Settings.PrivacyGuide.ChangeHistorySyncOn"));
+    }
+
+    @Test
+    @SmallTest
+    public void testSync_changeHistorySyncOffUserAction() {
+        PrivacyGuideMetricsDelegate.recordMetricsOnSyncChange(false);
+        assertTrue(
+                mActionTester.getActions().contains("Settings.PrivacyGuide.ChangeHistorySyncOff"));
+    }
 }
diff --git a/chrome/browser/privacy_guide/android/junit/src/org/chromium/chrome/browser/privacy_guide/SyncFragmentTest.java b/chrome/browser/privacy_guide/android/junit/src/org/chromium/chrome/browser/privacy_guide/SyncFragmentTest.java
index 9c97ae1..a4d7bbd92 100644
--- a/chrome/browser/privacy_guide/android/junit/src/org/chromium/chrome/browser/privacy_guide/SyncFragmentTest.java
+++ b/chrome/browser/privacy_guide/android/junit/src/org/chromium/chrome/browser/privacy_guide/SyncFragmentTest.java
@@ -28,6 +28,7 @@
 import org.mockito.junit.MockitoRule;
 
 import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.base.test.util.UserActionTester;
 import org.chromium.chrome.browser.sync.SyncService;
 import org.chromium.components.sync.UserSelectableType;
 
@@ -39,6 +40,11 @@
  */
 @RunWith(BaseRobolectricTestRunner.class)
 public class SyncFragmentTest {
+    private static final String CHANGE_HISTORY_SYNC_ON_USER_ACTION =
+            "Settings.PrivacyGuide.ChangeHistorySyncOn";
+    private static final String CHANGE_HISTORY_SYNC_OFF_USER_ACTION =
+            "Settings.PrivacyGuide.ChangeHistorySyncOff";
+
     @Rule
     public MockitoRule mMockitoRule = MockitoJUnit.rule();
 
@@ -50,6 +56,7 @@
 
     private FragmentScenario mScenario;
     private SwitchCompat mHistorySyncButton;
+    private final UserActionTester mActionTester = new UserActionTester();
 
     @Before
     public void setUp() {
@@ -137,4 +144,43 @@
         verify(mSyncService, times(2)).setSelectedTypes(eq(false), mSetCaptor.capture());
         assertTrue(mSetCaptor.getAllValues().get(1).contains(UserSelectableType.HISTORY));
     }
+
+    @Test
+    public void testTurnHistorySyncOn_changeHistorySyncOnUserAction() {
+        initFragmentWithSyncState(false, false);
+        mHistorySyncButton.performClick();
+        assertTrue(mActionTester.getActions().contains(CHANGE_HISTORY_SYNC_ON_USER_ACTION));
+    }
+
+    @Test
+    public void testTurnHistorySyncOffWhenSyncAllOn_changeHistorySyncOffUserAction() {
+        initFragmentWithSyncState(true, true);
+        mHistorySyncButton.performClick();
+        assertTrue(mActionTester.getActions().contains(CHANGE_HISTORY_SYNC_OFF_USER_ACTION));
+    }
+
+    @Test
+    public void testTurnHistorySyncOffWhenSyncAllOff_changeHistorySyncOffUserAction() {
+        initFragmentWithSyncState(false, true);
+        mHistorySyncButton.performClick();
+        assertTrue(mActionTester.getActions().contains(CHANGE_HISTORY_SYNC_OFF_USER_ACTION));
+    }
+
+    @Test
+    public void testTurnHistorySyncOffThenOnWhenSyncAllOn_changeHistorySyncOffOnUserAction() {
+        initFragmentWithSyncState(true, true);
+        mHistorySyncButton.performClick();
+        assertTrue(mActionTester.getActions().contains(CHANGE_HISTORY_SYNC_OFF_USER_ACTION));
+        mHistorySyncButton.performClick();
+        assertTrue(mActionTester.getActions().contains(CHANGE_HISTORY_SYNC_ON_USER_ACTION));
+    }
+
+    @Test
+    public void testTurnHistorySyncOffThenOnWhenSyncAllOff_changeHistorySyncOffOnUserAction() {
+        initFragmentWithSyncState(false, true);
+        mHistorySyncButton.performClick();
+        assertTrue(mActionTester.getActions().contains(CHANGE_HISTORY_SYNC_OFF_USER_ACTION));
+        mHistorySyncButton.performClick();
+        assertTrue(mActionTester.getActions().contains(CHANGE_HISTORY_SYNC_ON_USER_ACTION));
+    }
 }
diff --git a/chrome/browser/profiles/avatar_menu_browsertest.cc b/chrome/browser/profiles/avatar_menu_browsertest.cc
index d6bf2bf..e80184ef 100644
--- a/chrome/browser/profiles/avatar_menu_browsertest.cc
+++ b/chrome/browser/profiles/avatar_menu_browsertest.cc
@@ -178,6 +178,9 @@
   profiles::SwitchToGuestProfile();
   Browser* guest_browser = ui_test_utils::WaitForBrowserToOpen();
   ASSERT_TRUE(guest_browser);
+
+  BrowserList::SetLastActive(guest_browser);
+
   ASSERT_TRUE(guest_browser->profile()->IsGuestSession());
   // This should not crash.
   EXPECT_FALSE(menu()->ShouldShowEditProfileLink());
diff --git a/chrome/browser/profiles/profile_impl.cc b/chrome/browser/profiles/profile_impl.cc
index e0c4e9b..0e17f238 100644
--- a/chrome/browser/profiles/profile_impl.cc
+++ b/chrome/browser/profiles/profile_impl.cc
@@ -656,6 +656,7 @@
     auto& map = profile_policy_connector_->policy_service()->GetPolicies(
         policy::PolicyNamespace(policy::POLICY_DOMAIN_CHROME, std::string()));
     crosapi::browser_util::CacheLacrosAvailability(map);
+    crosapi::browser_util::CacheLacrosDataBackwardMigrationMode(map);
   }
 #endif
 }
@@ -1181,6 +1182,7 @@
       auto& map = profile_policy_connector_->policy_service()->GetPolicies(
           policy::PolicyNamespace(policy::POLICY_DOMAIN_CHROME, std::string()));
       crosapi::browser_util::CacheLacrosAvailability(map);
+      crosapi::browser_util::CacheLacrosDataBackwardMigrationMode(map);
     }
 
     ash::UserSessionManager::GetInstance()->RespectLocalePreferenceWrapper(
diff --git a/chrome/browser/renderer_context_menu/render_view_context_menu.cc b/chrome/browser/renderer_context_menu/render_view_context_menu.cc
index e8535d64..1907eb7b 100644
--- a/chrome/browser/renderer_context_menu/render_view_context_menu.cc
+++ b/chrome/browser/renderer_context_menu/render_view_context_menu.cc
@@ -1185,12 +1185,8 @@
 std::u16string RenderViewContextMenu::GetImageSearchProviderName(
     const TemplateURL* provider) const {
   if (search::DefaultSearchProviderIsGoogle(GetProfile())) {
-    // Check if string should use `Google Lens` as visual search provider or
-    // `Google`.
-    if (!base::FeatureList::IsEnabled(lens::features::kLensStandalone) ||
-        lens::features::UseGoogleAsVisualSearchProvider()) {
-      return provider->short_name();
-    }
+    // The image search branding label should always be 'Google'.
+    return provider->short_name();
   }
   // image_search_branding_label() returns the provider short name if no
   // image_search_branding_label is set.
@@ -2207,15 +2203,8 @@
 void RenderViewContextMenu::AppendRegionSearchItem() {
   int resource_id = IDS_CONTENT_CONTEXT_LENS_REGION_SEARCH;
 
-  if (lens::features::UseRegionSearchMenuItemAltText1()) {
-    resource_id = IDS_CONTENT_CONTEXT_LENS_REGION_SEARCH_ALT1;
-  } else if (lens::features::UseRegionSearchMenuItemAltText2()) {
-    resource_id = IDS_CONTENT_CONTEXT_LENS_REGION_SEARCH_ALT2;
-  } else if (lens::features::UseRegionSearchMenuItemAltText3()) {
-    resource_id = IDS_CONTENT_CONTEXT_LENS_REGION_SEARCH_ALT3;
-  } else if (lens::features::IsLensFullscreenSearchEnabled()) {
-    // Default text for fullscreen search when enabled. This is the same string
-    // as the third alternative text option.
+  if (lens::features::IsLensFullscreenSearchEnabled()) {
+    // Default text for fullscreen search when enabled.
     resource_id = IDS_CONTENT_CONTEXT_LENS_REGION_SEARCH_ALT1;
   }
 
diff --git a/chrome/browser/resources/BUILD.gn b/chrome/browser/resources/BUILD.gn
index 03f5bb8..22bca9b1 100644
--- a/chrome/browser/resources/BUILD.gn
+++ b/chrome/browser/resources/BUILD.gn
@@ -141,7 +141,6 @@
     if (is_android) {
       deps += [
         "explore_sites_internals:closure_compile",
-        "internals/lens:closure_compile",
         "internals/notifications:closure_compile",
         "video_tutorials:closure_compile",
       ]
diff --git a/chrome/browser/resources/bookmarks/BUILD.gn b/chrome/browser/resources/bookmarks/BUILD.gn
index cc5ee55e..08be7188 100644
--- a/chrome/browser/resources/bookmarks/BUILD.gn
+++ b/chrome/browser/resources/bookmarks/BUILD.gn
@@ -25,6 +25,7 @@
     "actions.ts",
     "bookmarks.ts",
     "api_listener.ts",
+    "bookmarks_api_proxy.ts",
     "browser_proxy.ts",
     "constants.ts",
     "debouncer.ts",
diff --git a/chrome/browser/resources/bookmarks/app.ts b/chrome/browser/resources/bookmarks/app.ts
index 429c7b7..1317674f 100644
--- a/chrome/browser/resources/bookmarks/app.ts
+++ b/chrome/browser/resources/bookmarks/app.ts
@@ -26,6 +26,7 @@
 import {setSearchResults} from './actions.js';
 import {destroy as destroyApiListener, init as initApiListener} from './api_listener.js';
 import {getTemplate} from './app.html.js';
+import {BookmarksApiProxyImpl} from './bookmarks_api_proxy.js';
 import {LOCAL_STORAGE_FOLDER_STATE_KEY, LOCAL_STORAGE_TREE_WIDTH_KEY, ROOT_NODE_ID} from './constants.js';
 import {DndManager} from './dnd_manager.js';
 import {MouseFocusMixin, MouseFocusMixinInterface} from './mouse_focus_behavior.js';
@@ -114,7 +115,7 @@
       return state.folderOpenState;
     });
 
-    chrome.bookmarks.getTree((results) => {
+    BookmarksApiProxyImpl.getInstance().getTree().then((results) => {
       const nodeMap = normalizeNodes(results[0]!);
       const initialState = createEmptyState();
       initialState.nodes = nodeMap;
@@ -190,21 +191,21 @@
       return;
     }
 
-    chrome.bookmarks.search(this.searchTerm_, (results) => {
-      const ids = results.map(function(node) {
-        return node.id;
-      });
-      this.dispatch(setSearchResults(ids));
-      this.dispatchEvent(new CustomEvent('iron-announce', {
-        bubbles: true,
-        composed: true,
-        detail: {
-          text: ids.length > 0 ?
-              loadTimeData.getStringF('searchResults', this.searchTerm_) :
-              loadTimeData.getString('noSearchResults'),
-        },
-      }));
-    });
+    BookmarksApiProxyImpl.getInstance()
+        .search(this.searchTerm_)
+        .then(results => {
+          const ids = results.map(node => node.id);
+          this.dispatch(setSearchResults(ids));
+          this.dispatchEvent(new CustomEvent('iron-announce', {
+            bubbles: true,
+            composed: true,
+            detail: {
+              text: ids.length > 0 ?
+                  loadTimeData.getStringF('searchResults', this.searchTerm_) :
+                  loadTimeData.getString('noSearchResults'),
+            },
+          }));
+        });
   }
 
   private folderOpenStateChanged_(): void {
diff --git a/chrome/browser/resources/bookmarks/bookmarks.ts b/chrome/browser/resources/bookmarks/bookmarks.ts
index e18d2f42..77a1b55 100644
--- a/chrome/browser/resources/bookmarks/bookmarks.ts
+++ b/chrome/browser/resources/bookmarks/bookmarks.ts
@@ -6,6 +6,7 @@
 
 export {changeFolderOpen, clearSearch, createBookmark, deselectItems, editBookmark, moveBookmark, removeBookmark, reorderChildren, selectFolder, SelectFolderAction, selectItem, SelectItemsAction, setSearchResults, setSearchTerm, updateAnchor} from './actions.js';
 export {BookmarksAppElement} from './app.js';
+export {BookmarksApiProxy, BookmarksApiProxyImpl, Query} from './bookmarks_api_proxy.js';
 export {BrowserProxy, BrowserProxyImpl} from './browser_proxy.js';
 export {BookmarksCommandManagerElement} from './command_manager.js';
 export {Command, DropPosition, IncognitoAvailability, LOCAL_STORAGE_FOLDER_STATE_KEY, LOCAL_STORAGE_TREE_WIDTH_KEY, MenuSource, ROOT_NODE_ID} from './constants.js';
diff --git a/chrome/browser/resources/bookmarks/bookmarks_api_proxy.ts b/chrome/browser/resources/bookmarks/bookmarks_api_proxy.ts
new file mode 100644
index 0000000..d6fa28f
--- /dev/null
+++ b/chrome/browser/resources/bookmarks/bookmarks_api_proxy.ts
@@ -0,0 +1,54 @@
+// 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.
+
+export type Query = string|{
+  query?: string,
+  url?: string,
+  title?: string,
+};
+
+export interface BookmarksApiProxy {
+  getTree(): Promise<chrome.bookmarks.BookmarkTreeNode[]>;
+  search(query: Query): Promise<chrome.bookmarks.BookmarkTreeNode[]>;
+  update(id: string, changes: {title?: string, url?: string}):
+      Promise<chrome.bookmarks.BookmarkTreeNode>;
+  create(bookmark: chrome.bookmarks.CreateDetails):
+      Promise<chrome.bookmarks.BookmarkTreeNode>;
+}
+
+export class BookmarksApiProxyImpl implements BookmarksApiProxy {
+  getTree() {
+    return new Promise<chrome.bookmarks.BookmarkTreeNode[]>(resolve => {
+      chrome.bookmarks.getTree(resolve);
+    });
+  }
+
+  search(query: Query) {
+    return new Promise<chrome.bookmarks.BookmarkTreeNode[]>(resolve => {
+      chrome.bookmarks.search(query, resolve);
+    });
+  }
+
+  update(id: string, changes: {title?: string, url?: string}) {
+    return new Promise<chrome.bookmarks.BookmarkTreeNode>(resolve => {
+      chrome.bookmarks.update(id, changes, resolve);
+    });
+  }
+
+  create(bookmark: chrome.bookmarks.CreateDetails) {
+    return new Promise<chrome.bookmarks.BookmarkTreeNode>(resolve => {
+      chrome.bookmarks.create(bookmark, resolve);
+    });
+  }
+
+  static getInstance(): BookmarksApiProxy {
+    return instance || (instance = new BookmarksApiProxyImpl());
+  }
+
+  static setInstance(obj: BookmarksApiProxy) {
+    instance = obj;
+  }
+}
+
+let instance: BookmarksApiProxy|null = null;
diff --git a/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/dictation.js b/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/dictation.js
index f0b7bcd..6ac1bc5 100644
--- a/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/dictation.js
+++ b/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/dictation.js
@@ -553,6 +553,11 @@
     this.prevMacro_ = newMacro;
     return newMacro;
   }
+
+  /** @private */
+  disablePumpkinForTesting_() {
+    this.speechParser_.disablePumpkinForTesting();
+  }
 }
 
 /**
diff --git a/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/dictation_test_base.js b/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/dictation_test_base.js
index 331a926..ff59d5e8 100644
--- a/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/dictation_test_base.js
+++ b/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/dictation_test_base.js
@@ -10,6 +10,34 @@
 GEN_INCLUDE(['../../common/testing/mock_speech_recognition_private.js']);
 
 /**
+ * @typedef {{
+ *   name: (string|undefined),
+ *   repeat: (number|undefined),
+ *   smart: (boolean|undefined),
+ * }}
+ */
+let ParseTestExpectations;
+
+/** A class that represents a test case for parsing text. */
+class ParseTestCase {
+  /**
+   * @param {string} text The text to be parsed
+   * @param {!ParseTestExpectations} expectations
+   * @constructor
+   */
+  constructor(text, expectations) {
+    /** @type {string} */
+    this.text = text;
+    /** @type {string|undefined} */
+    this.expectedName = expectations.name;
+    /** @type {number|undefined} */
+    this.expectedRepeat = expectations.repeat;
+    /** @type {boolean|undefined} */
+    this.expectedSmart = expectations.smart;
+  }
+}
+
+/**
  * Base class for tests for Dictation feature using accessibility common
  * extension browser tests.
  */
@@ -82,6 +110,9 @@
       chrome.accessibilityFeatures.dictation.set({value: true}, resolve);
     });
     await this.setPref(Dictation.DICTATION_LOCALE_PREF, 'en-US');
+
+    // By default, Dictation JS tests should use regex parsing.
+    accessibilityCommon.dictation_.disablePumpkinForTesting_();
   }
 
   /** @override */
@@ -396,6 +427,59 @@
   alwaysEnableCommands() {
     LocaleInfo.alwaysEnableCommandsForTesting = true;
   }
+
+  /**
+   * @param {!ParseTestCase} testCase
+   * @return {!Promise}
+   */
+  async runInputTextParseTestCase(testCase) {
+    const macro = await this.getInputTextStrategy().parse(testCase.text);
+    this.runParseTestCaseAssertions(testCase, macro);
+  }
+
+  /**
+   * @param {!ParseTestCase} testCase
+   * @return {!Promise}
+   */
+  async runSimpleParseTestCase(testCase) {
+    const macro = await this.getSimpleParseStrategy().parse(testCase.text);
+    this.runParseTestCaseAssertions(testCase, macro);
+  }
+
+  /**
+   * @param {!ParseTestCase} testCase
+   * @return {!Promise}
+   */
+  async runPumpkinParseTestCase(testCase) {
+    const macro = await this.getPumpkinParseStrategy().parse(testCase.text);
+    this.runParseTestCaseAssertions(testCase, macro);
+  }
+
+  /**
+   * @param {!ParseTestCase} testCase
+   * @param {?Macro} macro
+   */
+  runParseTestCaseAssertions(testCase, macro) {
+    const expectedName = testCase.expectedName;
+    const expectedRepeat = testCase.expectedRepeat;
+    const expectedSmart = testCase.expectedSmart;
+    if (!macro) {
+      assertEquals(undefined, expectedName);
+      assertEquals(undefined, expectedRepeat);
+      assertEquals(undefined, expectedSmart);
+      return;
+    }
+
+    if (expectedName) {
+      assertEquals(expectedName, macro.getMacroNameString());
+    }
+    if (expectedRepeat) {
+      assertEquals(expectedRepeat, macro.repeat_);
+    }
+    if (expectedSmart) {
+      assertEquals(expectedSmart, macro.isSmart());
+    }
+  }
 };
 
 /** A Dictation test class that fails on console warnings and errors. */
diff --git a/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/parse/dictation_parse_test.js b/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/parse/dictation_parse_test.js
index 14693d4a..0525fee 100644
--- a/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/parse/dictation_parse_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/parse/dictation_parse_test.js
@@ -18,122 +18,79 @@
 // Tests that the InputTextStrategy always returns an InputTextViewMacro,
 // regardless of the speech input.
 AX_TEST_F('DictationParseTest', 'InputTextStrategy', async function() {
-  const strategy = this.getInputTextStrategy();
-  assertNotNullNorUndefined(strategy);
-  let macro = await strategy.parse('Hello world');
-  assertEquals('INPUT_TEXT_VIEW', macro.getMacroNameString());
-  macro = await strategy.parse('delete two characters');
-  assertEquals('INPUT_TEXT_VIEW', macro.getMacroNameString());
-  macro = await strategy.parse('select all');
-  assertEquals('INPUT_TEXT_VIEW', macro.getMacroNameString());
+  /** @type {!Array<!ParseTestCase>} */
+  const testCases = [
+    new ParseTestCase('Hello world', {name: 'INPUT_TEXT_VIEW'}),
+    new ParseTestCase('delete two characters', {name: 'INPUT_TEXT_VIEW'}),
+    new ParseTestCase('select all', {name: 'INPUT_TEXT_VIEW'}),
+  ];
+
+  for (const test of testCases) {
+    await this.runInputTextParseTestCase(test);
+  }
 });
 
 // Tests that the SimpleParseStrategy returns the correct type of Macro given
 // speech input.
 AX_TEST_F('DictationParseTest', 'SimpleParseStrategy', async function() {
-  const strategy = this.getSimpleParseStrategy();
-  assertNotNullNorUndefined(strategy);
-  let macro = await strategy.parse('Hello world');
-  assertEquals('INPUT_TEXT_VIEW', macro.getMacroNameString());
-  assertFalse(macro.isSmart());
-  macro = await strategy.parse('type delete');
-  assertEquals('INPUT_TEXT_VIEW', macro.getMacroNameString());
-  assertFalse(macro.isSmart());
-  macro = await strategy.parse('delete');
-  assertEquals('DELETE_PREV_CHAR', macro.getMacroNameString());
-  assertFalse(macro.isSmart());
-  macro = await strategy.parse('move to the previous character');
-  assertEquals('NAV_PREV_CHAR', macro.getMacroNameString());
-  assertFalse(macro.isSmart());
-  macro = await strategy.parse('move to the next character');
-  assertEquals('NAV_NEXT_CHAR', macro.getMacroNameString());
-  assertFalse(macro.isSmart());
-  macro = await strategy.parse('move to the previous line');
-  assertEquals('NAV_PREV_LINE', macro.getMacroNameString());
-  assertFalse(macro.isSmart());
-  macro = await strategy.parse('move to the next line');
-  assertEquals('NAV_NEXT_LINE', macro.getMacroNameString());
-  assertFalse(macro.isSmart());
-  macro = await strategy.parse('copy');
-  assertEquals('COPY_SELECTED_TEXT', macro.getMacroNameString());
-  assertFalse(macro.isSmart());
-  macro = await strategy.parse('paste');
-  assertEquals('PASTE_TEXT', macro.getMacroNameString());
-  assertFalse(macro.isSmart());
-  macro = await strategy.parse('cut');
-  assertEquals('CUT_SELECTED_TEXT', macro.getMacroNameString());
-  assertFalse(macro.isSmart());
-  macro = await strategy.parse('undo');
-  assertEquals('UNDO_TEXT_EDIT', macro.getMacroNameString());
-  assertFalse(macro.isSmart());
-  macro = await strategy.parse('redo');
-  assertEquals('REDO_ACTION', macro.getMacroNameString());
-  assertFalse(macro.isSmart());
-  macro = await strategy.parse('select all');
-  assertEquals('SELECT_ALL_TEXT', macro.getMacroNameString());
-  assertFalse(macro.isSmart());
-  macro = await strategy.parse('unselect');
-  assertEquals('UNSELECT_TEXT', macro.getMacroNameString());
-  assertFalse(macro.isSmart());
-  macro = await strategy.parse('help');
-  assertEquals('LIST_COMMANDS', macro.getMacroNameString());
-  assertFalse(macro.isSmart());
-  macro = await strategy.parse('new line');
-  assertEquals('NEW_LINE', macro.getMacroNameString());
-  assertFalse(macro.isSmart());
-  macro = await strategy.parse('cancel');
-  assertEquals('STOP_LISTENING', macro.getMacroNameString());
-  assertFalse(macro.isSmart());
-  macro = await strategy.parse('delete the previous word');
-  assertEquals('DELETE_PREV_WORD', macro.getMacroNameString());
-  assertFalse(macro.isSmart());
-  macro = await strategy.parse('move to the next word');
-  assertEquals('NAV_NEXT_WORD', macro.getMacroNameString());
-  assertFalse(macro.isSmart());
-  macro = await strategy.parse('move to the previous word');
-  assertEquals('NAV_PREV_WORD', macro.getMacroNameString());
-  assertFalse(macro.isSmart());
+  /** @type {!Array<!ParseTestCase>} */
+  const testCases = [
+    new ParseTestCase('Hello world', {name: 'INPUT_TEXT_VIEW'}),
+    new ParseTestCase('type delete', {name: 'INPUT_TEXT_VIEW'}),
+    new ParseTestCase('highlight the next word', {name: 'INPUT_TEXT_VIEW'}),
+    new ParseTestCase('repeat', {name: 'INPUT_TEXT_VIEW'}),
+    new ParseTestCase('delete', {name: 'DELETE_PREV_CHAR'}),
+    new ParseTestCase(
+        'move to the previous character', {name: 'NAV_PREV_CHAR'}),
+    new ParseTestCase('move to the next character', {name: 'NAV_NEXT_CHAR'}),
+    new ParseTestCase('move to the previous line', {name: 'NAV_PREV_LINE'}),
+    new ParseTestCase('move to the next line', {name: 'NAV_NEXT_LINE'}),
+    new ParseTestCase('copy', {name: 'COPY_SELECTED_TEXT'}),
+    new ParseTestCase('paste', {name: 'PASTE_TEXT'}),
+    new ParseTestCase('cut', {name: 'CUT_SELECTED_TEXT'}),
+    new ParseTestCase('undo', {name: 'UNDO_TEXT_EDIT'}),
+    new ParseTestCase('redo', {name: 'REDO_ACTION'}),
+    new ParseTestCase('select all', {name: 'SELECT_ALL_TEXT'}),
+    new ParseTestCase('unselect', {name: 'UNSELECT_TEXT'}),
+    new ParseTestCase('help', {name: 'LIST_COMMANDS'}),
+    new ParseTestCase('new line', {name: 'NEW_LINE'}),
+    new ParseTestCase('cancel', {name: 'STOP_LISTENING'}),
+    new ParseTestCase('delete the previous word', {name: 'DELETE_PREV_WORD'}),
+    new ParseTestCase('move to the next word', {name: 'NAV_NEXT_WORD'}),
+    new ParseTestCase('move to the previous word', {name: 'NAV_PREV_WORD'}),
+    new ParseTestCase(
+        'delete the previous sentence',
+        {name: 'DELETE_PREV_SENT', smart: true}),
+    new ParseTestCase(
+        'delete hello world', {name: 'SMART_DELETE_PHRASE', smart: true}),
+    new ParseTestCase(
+        'replace hello world with goodnight world',
+        {name: 'SMART_REPLACE_PHRASE', smart: true}),
+    new ParseTestCase(
+        'insert hello world before goodnight world',
+        {name: 'SMART_INSERT_BEFORE', smart: true}),
+    new ParseTestCase(
+        'select from hello world to goodnight world',
+        {name: 'SMART_SELECT_BTWN_INCL', smart: true}),
+    new ParseTestCase(
+        'move to the next sentence', {name: 'NAV_NEXT_SENT', smart: true}),
+    new ParseTestCase(
+        'move to the previous sentence', {name: 'NAV_PREV_SENT', smart: true}),
+  ];
 
-  // Smart macros.
-  macro = await strategy.parse('delete the previous sentence');
-  assertEquals('DELETE_PREV_SENT', macro.getMacroNameString());
-  assertTrue(macro.isSmart());
-  macro = await strategy.parse('delete hello world');
-  assertEquals('SMART_DELETE_PHRASE', macro.getMacroNameString());
-  assertTrue(macro.isSmart());
-  macro = await strategy.parse('replace hello world with goodnight world');
-  assertEquals('SMART_REPLACE_PHRASE', macro.getMacroNameString());
-  assertTrue(macro.isSmart());
-  macro = await strategy.parse('insert hello world before goodnight world');
-  assertEquals('SMART_INSERT_BEFORE', macro.getMacroNameString());
-  assertTrue(macro.isSmart());
-  macro = await strategy.parse('select from hello world to goodnight world');
-  assertEquals('SMART_SELECT_BTWN_INCL', macro.getMacroNameString());
-  assertTrue(macro.isSmart());
-  macro = await strategy.parse('move to the next sentence');
-  assertEquals('NAV_NEXT_SENT', macro.getMacroNameString());
-  assertTrue(macro.isSmart());
-  macro = await strategy.parse('move to the previous sentence');
-  assertEquals('NAV_PREV_SENT', macro.getMacroNameString());
-  assertTrue(macro.isSmart());
-
-  // Unsupported commands.
-  macro = await strategy.parse('highlight the next word');
-  assertEquals('INPUT_TEXT_VIEW', macro.getMacroNameString());
-  macro = await strategy.parse('repeat');
-  assertEquals('INPUT_TEXT_VIEW', macro.getMacroNameString());
+  for (const test of testCases) {
+    await this.runSimpleParseTestCase(test);
+  }
 });
 
 AX_TEST_F('DictationParseTest', 'NoSmartMacrosForRTLLocales', async function() {
-  const strategy = this.getSimpleParseStrategy();
-  assertNotNullNorUndefined(strategy);
   await this.setPref(Dictation.DICTATION_LOCALE_PREF, 'en-US');
   await this.getPref(Dictation.DICTATION_LOCALE_PREF);
 
-  let macro = await strategy.parse('insert hello world before goodnight world');
-  assertNotNullNorUndefined(macro);
-  assertTrue(macro.isSmart());
-  assertEquals('SMART_INSERT_BEFORE', macro.getMacroNameString());
+  // Add is smart here and below.
+  await this.runSimpleParseTestCase(new ParseTestCase(
+      'insert hello world before goodnight world',
+      {name: 'SMART_INSERT_BEFORE', smart: true}));
 
   // Change Dictation locale to a right-to-left locale.
   await this.setPref(Dictation.DICTATION_LOCALE_PREF, 'ar-LB');
@@ -141,8 +98,7 @@
 
   // Smart macros are not supported in right-to-left locales. In these cases,
   // we fall back to INPUT_TEXT_VIEW macros.
-  macro = await strategy.parse('insert hello world before goodnight world');
-  assertNotNullNorUndefined(macro);
-  assertFalse(macro.isSmart());
-  assertEquals('INPUT_TEXT_VIEW', macro.getMacroNameString());
+  await this.runSimpleParseTestCase(new ParseTestCase(
+      'insert hello world before goodnight world',
+      {name: 'INPUT_TEXT_VIEW', smart: false}));
 });
diff --git a/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/parse/dictation_pumpkin_parse_test.js b/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/parse/dictation_pumpkin_parse_test.js
index 24df1c5..d981923 100644
--- a/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/parse/dictation_pumpkin_parse_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/parse/dictation_pumpkin_parse_test.js
@@ -4,26 +4,6 @@
 
 GEN_INCLUDE(['../dictation_test_base.js']);
 
-/** A class that represents a test case for parsing text. */
-class ParseTestCase {
-  /**
-   * @param {string} text The text to be parsed
-   * @param {string|undefined} expectedMacroName The expected name of the
-   *     resulting macro.
-   * @param {number|undefined} expectedRepeat The expected repeat value of the
-   *     resulting macro.
-   * @constructor
-   */
-  constructor(text, expectedMacroName, expectedRepeat) {
-    /** @type {string} */
-    this.text = text;
-    /** @type {string|undefined} */
-    this.expectedMacroName = expectedMacroName;
-    /** @type {number|undefined} */
-    this.expectedRepeat = expectedRepeat;
-  }
-}
-
 /**
  * Dictation tests for speech parsing with Pumpkin. These tests do not use the
  * live Pumpkin DLC, but instead use a local tar archive that mirrors the DLC.
@@ -38,10 +18,6 @@
 DictationPumpkinParseTest = class extends DictationE2ETestAllowConsole {
   /** @override */
   async setUpDeferred() {
-    this.mockAccessibilityPrivate.enableFeatureForTest(
-        'dictationPumpkinParsing', true);
-    this.mockAccessibilityPrivate.enableFeatureForTest(
-        'dictationMoreCommands', true);
     await this.mockAccessibilityPrivate.initializePumpkinData();
     // Re-initialize PumpkinParseStrategy after mock Pumpkin data has been
     // created.
@@ -51,6 +27,10 @@
         '/accessibility_common/dictation/parse/speech_parser.js');
 
     await super.setUpDeferred();
+
+    // By default, Dictation JS tests use regex parsing. Enable Pumpkin for
+    // this test suite.
+    this.getPumpkinParseStrategy().setEnabled(true);
   }
 
   /**
@@ -70,28 +50,6 @@
       }, 300);
     });
   }
-
-  /**
-   * @param {!ParseTestCase} testCase
-   * @return {!Promise}
-   */
-  async runParseTestCase(testCase) {
-    const expectedMacroName = testCase.expectedMacroName;
-    const expectedRepeat = testCase.expectedRepeat;
-    const macro = await this.getPumpkinParseStrategy().parse(testCase.text);
-    if (!macro) {
-      assertEquals(undefined, expectedMacroName);
-      assertEquals(undefined, expectedRepeat);
-      return;
-    }
-
-    if (expectedMacroName) {
-      assertEquals(expectedMacroName, macro.getMacroNameString());
-    }
-    if (expectedRepeat) {
-      assertEquals(expectedRepeat, macro.repeat_);
-    }
-  }
 };
 
 // Tests that we can use the SandboxedPumpkinTagger to convert speech into a
@@ -103,48 +61,49 @@
 
   /** @type {!Array<!ParseTestCase>} */
   const testCases = [
-    new ParseTestCase('Hello world'),
-    new ParseTestCase('dictate delete', 'INPUT_TEXT_VIEW'),
-    new ParseTestCase('backspace', 'DELETE_PREV_CHAR'),
-    new ParseTestCase('left one character', 'NAV_PREV_CHAR'),
-    new ParseTestCase('right one character', 'NAV_NEXT_CHAR'),
-    new ParseTestCase('up one line', 'NAV_PREV_LINE'),
-    new ParseTestCase('down one line', 'NAV_NEXT_LINE'),
-    new ParseTestCase('copy selected text', 'COPY_SELECTED_TEXT'),
-    new ParseTestCase('paste copied text', 'PASTE_TEXT'),
-    new ParseTestCase('cut highlighted text', 'CUT_SELECTED_TEXT'),
-    new ParseTestCase('undo that', 'UNDO_TEXT_EDIT'),
-    new ParseTestCase('redo that', 'REDO_ACTION'),
-    new ParseTestCase('select everything', 'SELECT_ALL_TEXT'),
-    new ParseTestCase('deselect selection', 'UNSELECT_TEXT'),
-    new ParseTestCase('what can I say', 'LIST_COMMANDS'),
-    new ParseTestCase('new line'),
-    new ParseTestCase('avada kedavra', 'STOP_LISTENING'),
-    new ParseTestCase('clear one word', 'DELETE_PREV_WORD'),
-    new ParseTestCase('erase sentence', 'DELETE_PREV_SENT'),
-    new ParseTestCase('right one word', 'NAV_NEXT_WORD'),
-    new ParseTestCase('back one word', 'NAV_PREV_WORD'),
-    new ParseTestCase('delete avada kedavra', 'SMART_DELETE_PHRASE'),
-    new ParseTestCase('replace hello with goodbye', 'SMART_REPLACE_PHRASE'),
+    new ParseTestCase('Hello world', {}),
+    new ParseTestCase('dictate delete', {name: 'INPUT_TEXT_VIEW'}),
+    new ParseTestCase('backspace', {name: 'DELETE_PREV_CHAR'}),
+    new ParseTestCase('left one character', {name: 'NAV_PREV_CHAR'}),
+    new ParseTestCase('right one character', {name: 'NAV_NEXT_CHAR'}),
+    new ParseTestCase('up one line', {name: 'NAV_PREV_LINE'}),
+    new ParseTestCase('down one line', {name: 'NAV_NEXT_LINE'}),
+    new ParseTestCase('copy selected text', {name: 'COPY_SELECTED_TEXT'}),
+    new ParseTestCase('paste copied text', {name: 'PASTE_TEXT'}),
+    new ParseTestCase('cut highlighted text', {name: 'CUT_SELECTED_TEXT'}),
+    new ParseTestCase('undo that', {name: 'UNDO_TEXT_EDIT'}),
+    new ParseTestCase('redo that', {name: 'REDO_ACTION'}),
+    new ParseTestCase('select everything', {name: 'SELECT_ALL_TEXT'}),
+    new ParseTestCase('deselect selection', {name: 'UNSELECT_TEXT'}),
+    new ParseTestCase('what can I say', {name: 'LIST_COMMANDS'}),
+    new ParseTestCase('new line', {}),
+    new ParseTestCase('avada kedavra', {name: 'STOP_LISTENING'}),
+    new ParseTestCase('clear one word', {name: 'DELETE_PREV_WORD'}),
+    new ParseTestCase('erase sentence', {name: 'DELETE_PREV_SENT'}),
+    new ParseTestCase('right one word', {name: 'NAV_NEXT_WORD'}),
+    new ParseTestCase('back one word', {name: 'NAV_PREV_WORD'}),
+    new ParseTestCase('delete avada kedavra', {name: 'SMART_DELETE_PHRASE'}),
     new ParseTestCase(
-        'insert hello in front of goodbye', 'SMART_INSERT_BEFORE'),
+        'replace hello with goodbye', {name: 'SMART_REPLACE_PHRASE'}),
+    new ParseTestCase(
+        'insert hello in front of goodbye', {name: 'SMART_INSERT_BEFORE'}),
     new ParseTestCase(
         'highlight everything between hello and goodbye',
-        'SMART_SELECT_BTWN_INCL'),
-    new ParseTestCase('forward one sentence', 'NAV_NEXT_SENT'),
-    new ParseTestCase('one sentence back', 'NAV_PREV_SENT'),
-    new ParseTestCase('clear', 'DELETE_ALL_TEXT'),
-    new ParseTestCase('to start', 'NAV_START_TEXT'),
-    new ParseTestCase('to end', 'NAV_END_TEXT'),
-    new ParseTestCase('highlight back one word', 'SELECT_PREV_WORD'),
-    new ParseTestCase('highlight right one word', 'SELECT_NEXT_WORD'),
-    new ParseTestCase('select next letter', 'SELECT_NEXT_CHAR'),
-    new ParseTestCase('select previous letter', 'SELECT_PREV_CHAR'),
-    new ParseTestCase('try that action again', 'REPEAT'),
+        {name: 'SMART_SELECT_BTWN_INCL'}),
+    new ParseTestCase('forward one sentence', {name: 'NAV_NEXT_SENT'}),
+    new ParseTestCase('one sentence back', {name: 'NAV_PREV_SENT'}),
+    new ParseTestCase('clear', {name: 'DELETE_ALL_TEXT'}),
+    new ParseTestCase('to start', {name: 'NAV_START_TEXT'}),
+    new ParseTestCase('to end', {name: 'NAV_END_TEXT'}),
+    new ParseTestCase('highlight back one word', {name: 'SELECT_PREV_WORD'}),
+    new ParseTestCase('highlight right one word', {name: 'SELECT_NEXT_WORD'}),
+    new ParseTestCase('select next letter', {name: 'SELECT_NEXT_CHAR'}),
+    new ParseTestCase('select previous letter', {name: 'SELECT_PREV_CHAR'}),
+    new ParseTestCase('try that action again', {name: 'REPEAT'}),
   ];
 
   for (const test of testCases) {
-    await this.runParseTestCase(test);
+    await this.runPumpkinParseTestCase(test);
   }
 });
 
@@ -156,20 +115,31 @@
 
       /** @type {!Array<!ParseTestCase>} */
       const testCases = [
-        new ParseTestCase('remove two characters', 'DELETE_PREV_CHAR', 2),
-        new ParseTestCase('left five characters', 'NAV_PREV_CHAR', 5),
-        new ParseTestCase('clear five words', 'DELETE_PREV_WORD', 5),
-        new ParseTestCase('forward three words', 'NAV_NEXT_WORD', 3),
-        new ParseTestCase('backward three words', 'NAV_PREV_WORD', 3),
-        new ParseTestCase('highlight back three words', 'SELECT_PREV_WORD', 3),
-        new ParseTestCase('highlight right three words', 'SELECT_NEXT_WORD', 3),
-        new ParseTestCase('select next three letters', 'SELECT_NEXT_CHAR', 3),
         new ParseTestCase(
-            'select previous three letters', 'SELECT_PREV_CHAR', 3),
+            'remove two characters', {name: 'DELETE_PREV_CHAR', repeat: 2}),
+        new ParseTestCase(
+            'left five characters', {name: 'NAV_PREV_CHAR', repeat: 5}),
+        new ParseTestCase(
+            'clear five words', {name: 'DELETE_PREV_WORD', repeat: 5}),
+        new ParseTestCase(
+            'forward three words', {name: 'NAV_NEXT_WORD', repeat: 3}),
+        new ParseTestCase(
+            'backward three words', {name: 'NAV_PREV_WORD', repeat: 3}),
+        new ParseTestCase(
+            'highlight back three words',
+            {name: 'SELECT_PREV_WORD', repeat: 3}),
+        new ParseTestCase(
+            'highlight right three words',
+            {name: 'SELECT_NEXT_WORD', repeat: 3}),
+        new ParseTestCase(
+            'select next three letters', {name: 'SELECT_NEXT_CHAR', repeat: 3}),
+        new ParseTestCase(
+            'select previous three letters',
+            {name: 'SELECT_PREV_CHAR', repeat: 3}),
       ];
 
       for (const test of testCases) {
-        await this.runParseTestCase(test);
+        await this.runPumpkinParseTestCase(test);
       }
     });
 
@@ -207,25 +177,36 @@
   const testCases = [
     {
       locale: 'fr-FR',
-      testCase: new ParseTestCase('copier', 'COPY_SELECTED_TEXT'),
+      testCase: new ParseTestCase('copier', {name: 'COPY_SELECTED_TEXT'}),
     },
     {
       locale: 'fr-FR',
       testCase: new ParseTestCase(
-          'supprimer deux caractères précédent', 'DELETE_PREV_CHAR', 2),
+          'supprimer deux caractères précédent',
+          {name: 'DELETE_PREV_CHAR', repeat: 2}),
     },
-    {locale: 'it-IT', testCase: new ParseTestCase('annulla', 'UNDO_TEXT_EDIT')},
-    {locale: 'de-DE', testCase: new ParseTestCase('hilf mir', 'LIST_COMMANDS')},
-    {locale: 'es-ES', testCase: new ParseTestCase('ayuda', 'LIST_COMMANDS')},
+    {
+      locale: 'it-IT',
+      testCase: new ParseTestCase('annulla', {name: 'UNDO_TEXT_EDIT'}),
+    },
+    {
+      locale: 'de-DE',
+      testCase: new ParseTestCase('hilf mir', {name: 'LIST_COMMANDS'}),
+    },
+    {
+      locale: 'es-ES',
+      testCase: new ParseTestCase('ayuda', {name: 'LIST_COMMANDS'}),
+    },
     {
       locale: 'en-GB',
-      testCase: new ParseTestCase('copy selected text', 'COPY_SELECTED_TEXT'),
+      testCase:
+          new ParseTestCase('copy selected text', {name: 'COPY_SELECTED_TEXT'}),
     },
   ];
   for (const {locale, testCase} of testCases) {
     await this.setPref(Dictation.DICTATION_LOCALE_PREF, locale);
     await this.waitForPumpkinParseStrategy_();
-    await this.runParseTestCase(testCase);
+    await this.runPumpkinParseTestCase(testCase);
   }
 });
 
@@ -234,11 +215,12 @@
   this.alwaysEnableCommands();
   await this.setPref(Dictation.DICTATION_LOCALE_PREF, 'ja');
   await this.waitForPumpkinParseStrategy_();
-  await this.runParseTestCase(new ParseTestCase('copy selected text'));
+  await this.runPumpkinParseTestCase(
+      new ParseTestCase('copy selected text', {}));
   // Would produce an UNDO_TEXT_EDIT macro if Japanese was supported.
-  await this.runParseTestCase(new ParseTestCase('もとどおりにする'));
+  await this.runPumpkinParseTestCase(new ParseTestCase('もとどおりにする', {}));
   await this.setPref(Dictation.DICTATION_LOCALE_PREF, 'en-US');
   await this.waitForPumpkinParseStrategy_();
-  await this.runParseTestCase(
-      new ParseTestCase('copy selected text', 'COPY_SELECTED_TEXT'));
+  await this.runPumpkinParseTestCase(
+      new ParseTestCase('copy selected text', {name: 'COPY_SELECTED_TEXT'}));
 });
diff --git a/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/parse/parse_strategy.js b/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/parse/parse_strategy.js
index c280d58..c7039d8e 100644
--- a/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/parse/parse_strategy.js
+++ b/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/parse/parse_strategy.js
@@ -33,6 +33,11 @@
     return this.enabled;
   }
 
+  /** @param {boolean} enabled */
+  setEnabled(enabled) {
+    this.enabled = enabled;
+  }
+
   /** Refreshes this strategy when the locale changes. */
   refresh() {}
 
diff --git a/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/parse/pumpkin_parse_strategy.js b/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/parse/pumpkin_parse_strategy.js
index 425bce86..987b315 100644
--- a/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/parse/pumpkin_parse_strategy.js
+++ b/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/parse/pumpkin_parse_strategy.js
@@ -31,11 +31,6 @@
   /** @param {!InputController} inputController */
   constructor(inputController) {
     super(inputController);
-    /**
-     * Whether or not the feature flag gating this object's logic is enabled.
-     * @private {boolean}
-     */
-    this.featureEnabled_ = false;
     /** @private {?PumpkinConstants.PumpkinData} */
     this.pumpkinData_ = null;
     /** @private {boolean} */
@@ -48,49 +43,23 @@
     this.locale_ = null;
     /** @private {boolean} */
     this.requestedPumpkinInstall_ = false;
-    /** @private {boolean} */
-    this.isMoreCommandsFeatureEnabled_ = false;
-
-    /** @private {!Array<!MacroName>}*/
-    this.moreCommandsSet_ = [
-      MacroName.DELETE_ALL_TEXT,
-      MacroName.NAV_START_TEXT,
-      MacroName.NAV_END_TEXT,
-      MacroName.SELECT_PREV_WORD,
-      MacroName.SELECT_NEXT_WORD,
-      MacroName.SELECT_NEXT_CHAR,
-      MacroName.SELECT_PREV_CHAR,
-      MacroName.REPEAT,
-    ];
 
     this.init_();
   }
 
   /** @private */
   init_() {
-    const pumpkinFeature = chrome.accessibilityPrivate.AccessibilityFeature
-                               .DICTATION_PUMPKIN_PARSING;
-    chrome.accessibilityPrivate.isFeatureEnabled(pumpkinFeature, enabled => {
-      this.featureEnabled_ = enabled;
-      this.refreshLocale_();
-      if (!enabled || !this.locale_) {
-        return;
-      }
+    this.refreshLocale_();
+    if (!this.locale_) {
+      return;
+    }
 
-      this.requestedPumpkinInstall_ = true;
-      chrome.accessibilityPrivate.installPumpkinForDictation(data => {
-        // TODO(crbug.comg/1258190): Consider retrying installation at a later
-        // time if it failed.
-        this.onPumpkinInstalled_(data);
-      });
+    this.requestedPumpkinInstall_ = true;
+    chrome.accessibilityPrivate.installPumpkinForDictation(data => {
+      // TODO(crbug.comg/1258190): Consider retrying installation at a later
+      // time if it failed.
+      this.onPumpkinInstalled_(data);
     });
-
-    const moreCommandsFeature = chrome.accessibilityPrivate.AccessibilityFeature
-                                    .DICTATION_MORE_COMMANDS;
-    chrome.accessibilityPrivate.isFeatureEnabled(
-        moreCommandsFeature, enabled => {
-          this.isMoreCommandsFeatureEnabled_ = enabled;
-        });
   }
 
   /**
@@ -98,10 +67,8 @@
    * @private
    */
   onPumpkinInstalled_(data) {
-    if (!this.featureEnabled_ || !data) {
-      console.warn(
-          'Pumpkin installed, but either data is empty or feature ' +
-          'flag is not enabled');
+    if (!data) {
+      console.warn('Pumpkin installed, but data is empty');
       return;
     }
 
@@ -367,31 +334,11 @@
       return null;
     }
 
-    const macro =
-        this.macroFromPumpkinHypothesis_(taggerResults.hypothesisList[0]);
-    return this.shouldReturnMacro_(macro) ? macro : null;
+    return this.macroFromPumpkinHypothesis_(taggerResults.hypothesisList[0]);
   }
 
   /** @override */
   isEnabled() {
-    return this.enabled && this.featureEnabled_;
-  }
-
-  /**
-   * @param {Macro} macro
-   * @return {boolean}
-   * @private
-   */
-  shouldReturnMacro_(macro) {
-    if (!macro) {
-      return false;
-    }
-
-    const isNewCommand = this.moreCommandsSet_.includes(macro.getMacroName());
-    if (!isNewCommand) {
-      return true;
-    }
-
-    return this.isMoreCommandsFeatureEnabled_;
+    return this.enabled;
   }
 }
diff --git a/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/parse/speech_parser.js b/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/parse/speech_parser.js
index 19b32f3..7f53f603 100644
--- a/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/parse/speech_parser.js
+++ b/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/parse/speech_parser.js
@@ -68,4 +68,9 @@
     return await /** @type {!Promise<!Macro>} */ (
         this.inputTextStrategy_.parse(text));
   }
+
+  /** For testing purposes only. */
+  disablePumpkinForTesting() {
+    this.pumpkinParseStrategy_.setEnabled(false);
+  }
 }
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/command_handler.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/command_handler.js
index 833bf15..d6aea7bf 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/command_handler.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/command_handler.js
@@ -595,8 +595,7 @@
             .send();
         return false;
       case Command.SHOW_TABLES_LIST:
-        (new PanelCommand(PanelCommandType.OPEN_MENUS, 'table_strategy'))
-            .send();
+        (new PanelCommand(PanelCommandType.OPEN_MENUS, 'role_table')).send();
         return false;
       case Command.TOGGLE_SEARCH_WIDGET:
         (new PanelCommand(PanelCommandType.SEARCH)).send();
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output.js
index 95acf30..dcaf21cf 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output.js
@@ -672,23 +672,6 @@
   }
 
   /** @override */
-  /** @override */
-  formatTableCellIndex_(data, token, options) {
-    const buff = data.outputBuffer;
-    const node = data.node;
-    const formatLog = data.outputFormatLogger;
-
-    let value = node[token];
-    if (value === undefined) {
-      return;
-    }
-    value = String(value + 1);
-    options.annotation.push(token);
-    this.append_(buff, value, options);
-    formatLog.writeTokenWithValue(token, value);
-  }
-
-  /** @override */
   formatCellIndexText_(data, token, options) {
     const buff = data.outputBuffer;
     const node = data.node;
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output_formatter.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output_formatter.js
index 4961b7f3..cfa54678 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output_formatter.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output_formatter.js
@@ -95,7 +95,7 @@
       this.formatInputType_(this.params_, token, options);
     } else if (
         token === 'tableCellRowIndex' || token === 'tableCellColumnIndex') {
-      this.output_.formatTableCellIndex_(this.params_, token, options);
+      this.formatTableCellIndex_(this.params_, token, options);
     } else if (token === 'cellIndexText') {
       this.output_.formatCellIndexText_(this.params_, token, options);
     } else if (token === 'node') {
@@ -572,6 +572,27 @@
    * @param {!outputTypes.OutputFormattingData} data
    * @param {string} token
    * @param {!{annotation: Array<*>, isUnique: (boolean|undefined)}} options
+   * @private
+   */
+  formatTableCellIndex_(data, token, options) {
+    const buff = data.outputBuffer;
+    const node = data.node;
+    const formatLog = data.outputFormatLogger;
+
+    let value = node[token];
+    if (value === undefined) {
+      return;
+    }
+    value = String(value + 1);
+    options.annotation.push(token);
+    this.output_.append_(buff, value, options);
+    formatLog.writeTokenWithValue(token, value);
+  }
+
+  /**
+   * @param {!outputTypes.OutputFormattingData} data
+   * @param {string} token
+   * @param {!{annotation: Array<*>, isUnique: (boolean|undefined)}} options
    */
   formatUrlFilename_(data, token, options) {
     const buff = data.outputBuffer;
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output_interface.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output_interface.js
index 248b7d3a..be41ead 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output_interface.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output_interface.js
@@ -113,13 +113,6 @@
    * @param {string} token
    * @param {!{annotation: Array<*>, isUnique: (boolean|undefined)}} options
    */
-  formatTableCellIndex_(data, token, options) {}
-
-  /**
-   * @param {!outputTypes.OutputFormattingData} data
-   * @param {string} token
-   * @param {!{annotation: Array<*>, isUnique: (boolean|undefined)}} options
-   */
   formatTextContent_(data, token, options) {}
 
   /**
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak.js
index 6569f5c..ecf81e8 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak.js
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak.js
@@ -1784,7 +1784,7 @@
   }
 
   /**
-   * Used by C++ tests to ensure STS load is competed.
+   * Used by C++ tests to ensure STS load is completed.
    * @param {!function()} callback Callback for when desktop is loaded from
    * automation.
    */
diff --git a/chrome/browser/resources/chromeos/cloud_upload/BUILD.gn b/chrome/browser/resources/chromeos/cloud_upload/BUILD.gn
index 3ce6133..fdb29103 100644
--- a/chrome/browser/resources/chromeos/cloud_upload/BUILD.gn
+++ b/chrome/browser/resources/chromeos/cloud_upload/BUILD.gn
@@ -11,6 +11,7 @@
     "icons/check.svg",
     "icons/docs.svg",
     "icons/download.svg",
+    "icons/error.svg",
     "icons/office.svg",
     "icons/sheets.svg",
     "icons/slides.svg",
diff --git a/chrome/browser/resources/chromeos/cloud_upload/cloud_upload_dialog.ts b/chrome/browser/resources/chromeos/cloud_upload/cloud_upload_dialog.ts
index 436334c..01ac70d 100644
--- a/chrome/browser/resources/chromeos/cloud_upload/cloud_upload_dialog.ts
+++ b/chrome/browser/resources/chromeos/cloud_upload/cloud_upload_dialog.ts
@@ -50,11 +50,12 @@
   }
 
   async init(): Promise<void> {
-    const [, {installed: isOfficeWebAppInstalled}] = await Promise.all([
-      this.processDialogArgs(),
-      this.proxy.handler.isOfficeWebAppInstalled(),
-    ]);
-    const odfsMounted = false;
+    const [, {installed: isOfficeWebAppInstalled}, {mounted: isOdfsMounted}] =
+        await Promise.all([
+          this.processDialogArgs(),
+          this.proxy.handler.isOfficeWebAppInstalled(),
+          this.proxy.handler.isODFSMounted(),
+        ]);
 
     // TODO(b/251046341): Adjust this once the rest of the pages are in place.
     this.pages.push(new WelcomePageElement());
@@ -63,7 +64,7 @@
       this.pages.push(new OfficePwaInstallPageElement());
     }
 
-    if (!odfsMounted) {
+    if (!isOdfsMounted) {
       this.pages.push(new SignInPageElement());
     }
 
diff --git a/chrome/browser/resources/chromeos/cloud_upload/icons/error.svg b/chrome/browser/resources/chromeos/cloud_upload/icons/error.svg
new file mode 100644
index 0000000..f045b7f
--- /dev/null
+++ b/chrome/browser/resources/chromeos/cloud_upload/icons/error.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="17" height="16" fill="none"><path fill="#1B1B1F" d="M7.876 4.605h1.857v3.308H7.876V4.605Zm0 4.584h1.857v1.857H7.876V9.189Z"/><path fill="#1B1B1F" fill-rule="evenodd" d="M15.377 8a6.5 6.5 0 1 1-13 0 6.5 6.5 0 0 1 13 0ZM13.52 8a4.643 4.643 0 1 1-9.286 0 4.643 4.643 0 0 1 9.286 0Z" clip-rule="evenodd"/></svg>
\ No newline at end of file
diff --git a/chrome/browser/resources/chromeos/cloud_upload/sign_in_page.html b/chrome/browser/resources/chromeos/cloud_upload/sign_in_page.html
index dca3cec99..4a40134 100644
--- a/chrome/browser/resources/chromeos/cloud_upload/sign_in_page.html
+++ b/chrome/browser/resources/chromeos/cloud_upload/sign_in_page.html
@@ -2,6 +2,28 @@
   a {
     color: var(--cros-text-color-prominent);
   }
+
+  #error-message {
+    align-items: center;
+    color: var(--cros-text-color-alert);
+    display: flex;
+  }
+
+  #error-message[hidden] {
+    /* display: flex overrides the hidden visibility */
+    display: none;
+  }
+
+  .icon {
+    -webkit-mask-image: url(icons/error.svg);
+    background-color: var(--cros-icon-color-alert);
+    height: 16px;
+    width: 17px;
+  }
+
+  .error-text {
+    margin-inline-start: 5px;
+  }
 </style>
 
 <!-- TODO(b/254586358): Use localized strings -->
@@ -19,8 +41,12 @@
   <p>
     You'll need to sign in with your Microsoft account to continue.
   </p>
+  <div id="error-message" hidden>
+    <div class="icon"></div>
+    <p class="error-text">Unable to connect to OneDrive. Please try again.</p>
+  </div>
 </div>
 <div slot="button-container">
-  <cr-button class="cancel-button">Cancel</cr-button>
+  <cr-button class="cancel-button">$i18n{cancel}</cr-button>
   <cr-button class="action-button">Connect to OneDrive</cr-button>
 </div>
diff --git a/chrome/browser/resources/chromeos/cloud_upload/sign_in_page.ts b/chrome/browser/resources/chromeos/cloud_upload/sign_in_page.ts
index 59fd486c..d716d822 100644
--- a/chrome/browser/resources/chromeos/cloud_upload/sign_in_page.ts
+++ b/chrome/browser/resources/chromeos/cloud_upload/sign_in_page.ts
@@ -5,6 +5,7 @@
 import 'chrome://resources/cr_elements/cr_button/cr_button.js';
 
 import {BaseSetupPageElement, CANCEL_SETUP_EVENT, NEXT_PAGE_EVENT} from './base_setup_page.js';
+import {CloudUploadBrowserProxy} from './cloud_upload_browser_proxy.js';
 import {getTemplate} from './sign_in_page.html.js';
 
 /**
@@ -12,6 +13,10 @@
  * OneDrive.
  */
 export class SignInPageElement extends BaseSetupPageElement {
+  private get proxy(): CloudUploadBrowserProxy {
+    return CloudUploadBrowserProxy.getInstance();
+  }
+
   /**
    * Initialises the page specific content inside the page.
    */
@@ -24,9 +29,18 @@
     cancelButton.addEventListener('click', () => this.onCancelButtonClick());
   }
 
-  private onConnectButtonClick(): void {
-    this.dispatchEvent(
-        new CustomEvent(NEXT_PAGE_EVENT, {bubbles: true, composed: true}));
+  async onConnectButtonClick(): Promise<void> {
+    const {success: signInSuccess} =
+        await this.proxy.handler.signInToOneDrive();
+    if (signInSuccess) {
+      this.dispatchEvent(
+          new CustomEvent(NEXT_PAGE_EVENT, {bubbles: true, composed: true}));
+    } else {
+      const connectButton = this.querySelector<HTMLElement>('.action-button')!;
+      const errorMessage = this.querySelector<HTMLElement>('#error-message')!;
+      connectButton.innerText = 'Retry';
+      errorMessage.toggleAttribute('hidden', false);
+    }
   }
 
   private onCancelButtonClick(): void {
diff --git a/chrome/browser/resources/chromeos/cryptohome.html b/chrome/browser/resources/chromeos/cryptohome.html
index 0883446..cecffd1 100644
--- a/chrome/browser/resources/chromeos/cryptohome.html
+++ b/chrome/browser/resources/chromeos/cryptohome.html
@@ -45,5 +45,12 @@
         <td id="is-tpm-token-ready"></td>
       </tr>
     </table>
+    <h3>Cryptohome recovery:</h3>
+    <table width=200>
+      <tr>
+        <td>Latest RecoveryIds</td>
+        <td id="recovery_ids"></td>
+      </tr>
+    </table>
   </body>
 </html>
diff --git a/chrome/browser/resources/chromeos/login/BUILD.gn b/chrome/browser/resources/chromeos/login/BUILD.gn
index ff2498ae..b845ac7 100644
--- a/chrome/browser/resources/chromeos/login/BUILD.gn
+++ b/chrome/browser/resources/chromeos/login/BUILD.gn
@@ -259,6 +259,7 @@
     "screens/common/arc_terms_of_service.js",
     "screens/common/assistant_optin.js",
     "screens/common/autolaunch.js",
+    "screens/common/choobe.js",
     "screens/common/consolidated_consent.js",
     "screens/common/cryptohome_recovery_setup.js",
     "screens/common/device_disabled.js",
diff --git a/chrome/browser/resources/chromeos/login/components/gaia_dialog.js b/chrome/browser/resources/chromeos/login/components/gaia_dialog.js
index 3c59c78f..8141f2ea 100644
--- a/chrome/browser/resources/chromeos/login/components/gaia_dialog.js
+++ b/chrome/browser/resources/chromeos/login/components/gaia_dialog.js
@@ -166,7 +166,7 @@
        */
       primaryActionButtonEnabled_: {
         type: Boolean,
-        value: true,
+        value: false,
       },
 
       /**
@@ -184,7 +184,7 @@
        */
       secondaryActionButtonEnabled_: {
         type: Boolean,
-        value: true,
+        value: false,
       },
 
       /**
@@ -223,13 +223,6 @@
      * @private
      */
     this.clickPrimaryActionButtonForTesting_ = false;
-    /**
-     * Emulate click on the primary action button when it is visible and
-     * enabled.
-     * @type {boolean}
-     * @private
-     */
-    this.clickPrimaryActionButtonForTesting_ = false;
 
     /**
      * @type {!Authenticator|undefined}
diff --git a/chrome/browser/resources/chromeos/login/components/oobe_screens_list.html b/chrome/browser/resources/chromeos/login/components/oobe_screens_list.html
index 7b17df9..9719080 100644
--- a/chrome/browser/resources/chromeos/login/components/oobe_screens_list.html
+++ b/chrome/browser/resources/chromeos/login/components/oobe_screens_list.html
@@ -80,11 +80,11 @@
 
 </style>
 <div id="screensList">
-  <template is="dom-repeat" items="{{screensList}}">
-    <cr-button class="screen-item" on-click="onclick_">
+  <template is="dom-repeat" items="{{screensList}}" as="screen">
+    <cr-button class="screen-item" on-click="onClick_">
       <div class="flex horizontal layout center-justified center">
-          <iron-icon class="screen-icon" icon="[[item.icon]]"></iron-icon>
-          <div class="screen-title">[[item.title]]</div>
+          <iron-icon class="screen-icon" icon="[[screen.icon]]"></iron-icon>
+          <div class="screen-title">[[i18nDynamic(locale, screen.title)]]</div>
       </div>
     </cr-button>
   </template>
diff --git a/chrome/browser/resources/chromeos/login/components/oobe_screens_list.js b/chrome/browser/resources/chromeos/login/components/oobe_screens_list.js
index 42794df..6686e3d 100644
--- a/chrome/browser/resources/chromeos/login/components/oobe_screens_list.js
+++ b/chrome/browser/resources/chromeos/login/components/oobe_screens_list.js
@@ -52,9 +52,16 @@
         value: [],
       },
       /**
-       * Number of screens selected.
+       * List of selected screens.
        */
       screensSelected: {
+        type: Array,
+        value: [],
+      },
+      /**
+       * Number of selected screens.
+       */
+      selectedScreensCount: {
         type: Number,
         value: 0,
         notify: true,
@@ -62,14 +69,32 @@
     };
   }
 
-  onclick_(e) {
-    var selected = e.model.item.selected;
-    e.model.item.selected = !selected;
+  /**
+   * Initialize the list of screens.
+   */
+  init(screens) {
+    this.screensList = screens;
+  }
+
+  /**
+   * Return the list of selected screens.
+   */
+  getScreenSelected() {
+    return this.screensSelected;
+  }
+
+  onClick_(e) {
+    const clickedScreen = e.model.screen;
+    const selected = clickedScreen.selected;
+    clickedScreen.selected = !selected;
     e.currentTarget.setAttribute('checked', !selected);
     if (!selected) {
-      this.screensSelected++;
+      this.selectedScreensCount++;
+      this.screensSelected.push(clickedScreen.screenID);
     } else {
-      this.screensSelected--;
+      this.selectedScreensCount--;
+      this.screensSelected.splice(
+          this.screensSelected.indexOf(clickedScreen.screenID), 1);
     }
   }
 }
diff --git a/chrome/browser/resources/chromeos/login/oobe_auto_imports.gni b/chrome/browser/resources/chromeos/login/oobe_auto_imports.gni
index c6cc576..304554c 100644
--- a/chrome/browser/resources/chromeos/login/oobe_auto_imports.gni
+++ b/chrome/browser/resources/chromeos/login/oobe_auto_imports.gni
@@ -48,6 +48,8 @@
   "chrome/browser/resources/chromeos/login/components/keyboard_utils_for_injection.html|KEYBOARD_UTILS_FOR_INJECTION",
   "chrome/browser/resources/chromeos/login/components/long_touch_detector.html|LongTouchDetector",
   "chrome/browser/resources/chromeos/login/components/oobe_apps_list.html|OobeAppsList",
+  "chrome/browser/resources/chromeos/login/components/oobe_screens_list.html|OobeScreensList",
+  "chrome/browser/resources/chromeos/login/components/oobe_types.html|OobeTypes",
   "chrome/browser/resources/chromeos/login/components/oobe_a11y_option.html|OobeA11yOption",
   "chrome/browser/resources/chromeos/login/components/web_view_helper.html|WebViewHelper",
   "chrome/browser/resources/chromeos/login/components/web_view_loader.html|WebViewLoader,CLEAR_ANCHORS_CONTENT_SCRIPT",
diff --git a/chrome/browser/resources/chromeos/login/screens.js b/chrome/browser/resources/chromeos/login/screens.js
index 48d4449..d36a2060 100644
--- a/chrome/browser/resources/chromeos/login/screens.js
+++ b/chrome/browser/resources/chromeos/login/screens.js
@@ -12,6 +12,7 @@
 import './screens/common/arc_terms_of_service.js';
 import './screens/common/assistant_optin.js';
 import './screens/common/autolaunch.js';
+import './screens/common/choobe.js';
 import './screens/common/consolidated_consent.js';
 import './screens/common/cryptohome_recovery_setup.js';
 import './screens/common/device_disabled.js';
@@ -80,6 +81,11 @@
   },
   {tag: 'assistant-optin-element', id: 'assistant-optin-flow'},
   {tag: 'autolaunch-element', id: 'autolaunch'},
+  {
+    tag: 'choobe-element',
+    id: 'choobe',
+    condition: 'isChoobeEnabled',
+  },
   {tag: 'consolidated-consent-element', id: 'consolidated-consent'},
   {tag: 'cryptohome-recovery-setup-element', id: 'cryptohome-recovery-setup'},
   {tag: 'device-disabled-element', id: 'device-disabled'},
diff --git a/chrome/browser/resources/chromeos/login/screens/common/BUILD.gn b/chrome/browser/resources/chromeos/login/screens/common/BUILD.gn
index c10fe24..5e65f70 100644
--- a/chrome/browser/resources/chromeos/login/screens/common/BUILD.gn
+++ b/chrome/browser/resources/chromeos/login/screens/common/BUILD.gn
@@ -11,7 +11,6 @@
 
 assert(is_chromeos, "OOBE UI is only available on ChromeOS builds")
 
-
 group("closure_compile") {
   deps = [
     ":closure_compile_local",
@@ -36,6 +35,7 @@
 
     #":assistant_optin",
     ":autolaunch",
+    ":choobe",
     ":consolidated_consent",
     ":cryptohome_recovery_setup",
     ":device_disabled",
@@ -155,6 +155,25 @@
   extra_deps = [ ":web_components" ]
 }
 
+js_library("choobe") {
+  sources = [ "$root_gen_dir/chrome/browser/resources/chromeos/login/screens/common/choobe.js" ]
+  deps = [
+    "../../components:display_manager_types",
+    "../../components:oobe_screens_list",
+    "../../components:oobe_types",
+    "../../components/behaviors:login_screen_behavior.m",
+    "../../components/behaviors:multi_step_behavior.m",
+    "../../components/behaviors:oobe_dialog_host_behavior.m",
+    "../../components/behaviors:oobe_i18n_behavior",
+    "../../components/buttons:oobe_back_button",
+    "../../components/buttons:oobe_next_button",
+    "../../components/buttons:oobe_text_button",
+    "../../components/dialogs:oobe_adaptive_dialog",
+    "//ui/webui/resources/js:cr.m",
+  ]
+  extra_deps = [ ":web_components" ]
+}
+
 js_library("consolidated_consent") {
   sources = [ "$root_gen_dir/chrome/browser/resources/chromeos/login/screens/common/consolidated_consent.js" ]
   deps = [
@@ -676,6 +695,7 @@
     "arc_terms_of_service.js",
     "assistant_optin.js",
     "autolaunch.js",
+    "choobe.js",
     "consolidated_consent.js",
     "device_disabled.js",
     "enable_kiosk.js",
diff --git a/chrome/browser/resources/chromeos/login/screens/common/choobe.html b/chrome/browser/resources/chromeos/login/screens/common/choobe.html
new file mode 100644
index 0000000..63dd65ca
--- /dev/null
+++ b/chrome/browser/resources/chromeos/login/screens/common/choobe.html
@@ -0,0 +1,33 @@
+<!--
+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.
+-->
+
+
+<style include="oobe-dialog-host-styles">
+</style>
+<oobe-adaptive-dialog id="choobeDialog" role="presentation"
+      for-step="overview">
+    <iron-icon slot="icon" icon="oobe-32:game-controller"></iron-icon>
+    <h1 slot="title" id="choobe-title">
+      [[i18nDynamic(locale, 'choobeScreenTitle')]]
+    </h1>
+    <div slot="subtitle" id="choobe-subtitle">
+      [[i18nDynamic(locale, 'choobeScreenDescription')]]
+    </div>
+    <div slot="content" class="layout vertical landscape-vertical-centered">
+      <oobe-screens-list id="screensList"
+              selected-screens-count="{{numberOfSelectedScreens_}}">
+      </oobe-screens-list>
+    </div>
+    <div slot="bottom-buttons">
+        <oobe-text-button id="skipButton"
+            text-key="choobeScreenSkip" on-click="onSkip_" border>
+        </oobe-text-button>
+        <oobe-next-button id="nextButton" class="focus-on-show"
+            on-click="onNextClicked_"
+            disabled="[[!canProceed_(numberOfSelectedScreens_)]]">
+        </oobe-next-button>
+    </div>
+</oobe-adaptive-dialog>
\ No newline at end of file
diff --git a/chrome/browser/resources/chromeos/login/screens/common/choobe.js b/chrome/browser/resources/chromeos/login/screens/common/choobe.js
new file mode 100644
index 0000000..5e9cf859
--- /dev/null
+++ b/chrome/browser/resources/chromeos/login/screens/common/choobe.js
@@ -0,0 +1,106 @@
+// 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.
+/**
+ * @fileoverview Polymer element for theme selection screen.
+ */
+
+import '//resources/polymer/v3_0/iron-icon/iron-icon.js';
+import '//resources/polymer/v3_0/iron-iconset-svg/iron-iconset-svg.js';
+import '../../components/buttons/oobe_next_button.js';
+import '../../components/buttons/oobe_text_button.js';
+import '../../components/oobe_icons.m.js';
+import '../../components/common_styles/oobe_common_styles.m.js';
+import '../../components/common_styles/oobe_dialog_host_styles.m.js';
+import '../../components/dialogs/oobe_adaptive_dialog.js';
+
+import {html, mixinBehaviors, PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {LoginScreenBehavior, LoginScreenBehaviorInterface} from '../../components/behaviors/login_screen_behavior.m.js';
+import {MultiStepBehavior, MultiStepBehaviorInterface} from '../../components/behaviors/multi_step_behavior.m.js';
+import {OobeI18nBehavior, OobeI18nBehaviorInterface} from '../../components/behaviors/oobe_i18n_behavior.js';
+import {OOBE_UI_STATE} from '../../components/display_manager_types.js';
+import {OobeScreensList} from '../../components/oobe_screens_list.js';
+
+/**
+ * @constructor
+ * @extends {PolymerElement}
+ * @implements {LoginScreenBehaviorInterface}
+ * @implements {OobeI18nBehaviorInterface}
+ * @implements {MultiStepBehaviorInterface}
+ */
+const ChoobeScreenElementBase = mixinBehaviors(
+    [OobeI18nBehavior, LoginScreenBehavior, MultiStepBehavior], PolymerElement);
+
+
+const ChoobeStep = {
+  OVERVIEW: 'overview',
+};
+
+/**
+ * Available user actions.
+ * @enum {string}
+ */
+const UserAction = {
+  SKIP: 'choobeSkip',
+  NEXT: 'choobeSelect',
+};
+
+/**
+ * @polymer
+ */
+class ChoobeScreen extends ChoobeScreenElementBase {
+  static get is() {
+    return 'choobe-element';
+  }
+
+  static get template() {
+    return html`{__html_template__}`;
+  }
+
+  static get properties() {
+    return {
+      numberOfSelectedScreens_: {
+        type: Number,
+        value: 0,
+      },
+    };
+  }
+
+  get UI_STEPS() {
+    return ChoobeStep;
+  }
+
+  defaultUIStep() {
+    return ChoobeStep.OVERVIEW;
+  }
+
+  ready() {
+    super.ready();
+    this.initializeLoginScreen('ChoobeScreen');
+  }
+
+  onBeforeShow(data) {
+    if ('screens' in data) {
+      this.$.screensList.init(data['screens']);
+    }
+  }
+
+  getOobeUIInitialState() {
+    return OOBE_UI_STATE.ONBOARDING;
+  }
+
+  onNextClicked_() {
+    const screenSelected = this.$.screensList.getScreenSelected();
+    this.userActed([UserAction.NEXT, screenSelected]);
+  }
+
+  onSkip_() {
+    this.userActed(UserAction.SKIP);
+  }
+
+  canProceed_() {
+    return this.numberOfSelectedScreens_ > 0;
+  }
+}
+customElements.define(ChoobeScreen.is, ChoobeScreen);
\ No newline at end of file
diff --git a/chrome/browser/resources/discards/graph_doc.ts b/chrome/browser/resources/discards/graph_doc.ts
index 469f5d07..3c94f42 100644
--- a/chrome/browser/resources/discards/graph_doc.ts
+++ b/chrome/browser/resources/discards/graph_doc.ts
@@ -266,7 +266,7 @@
    */
   setInitialPosition(graphWidth: number, graphHeight: number) {
     this.x = graphWidth / 2;
-    this.y = this.targetYPosition(graphHeight);
+    this.y = this.targetPositionY(graphHeight);
     this.vx = 0;
     this.vy = 0;
   }
@@ -274,8 +274,8 @@
   /**
    * @param graphHeight Height of the graph view (svg).
    */
-  targetYPosition(graphHeight: number): number {
-    const bounds = this.allowedYRange(graphHeight);
+  targetPositionY(graphHeight: number): number {
+    const bounds = this.allowedRangeY(graphHeight);
     return (bounds[0] + bounds[1]) / 2;
   }
 
@@ -298,7 +298,7 @@
   /**
    * @param graphHeight Height of the graph view.
    */
-  allowedYRange(graphHeight: number): [number, number] {
+  allowedRangeY(graphHeight: number): [number, number] {
     // By default, nodes just need to be in bounds of the graph.
     return [0, graphHeight];
   }
@@ -363,7 +363,7 @@
     return 0.5;
   }
 
-  override allowedYRange(_graphHeight: number): [number, number] {
+  override allowedRangeY(_graphHeight: number): [number, number] {
     return [0, kPageNodesYRange];
   }
 
@@ -396,11 +396,11 @@
     return this.frame.url.url.length > 0 ? this.frame.url.url : 'Frame';
   }
 
-  override targetYPosition(_graphHeight: number) {
+  override targetPositionY(_graphHeight: number) {
     return kFrameNodesTargetY;
   }
 
-  override allowedYRange(graphHeight: number): [number, number] {
+  override allowedRangeY(graphHeight: number): [number, number] {
     return [kFrameNodesTopMargin, graphHeight - kFrameNodesBottomMargin];
   }
 
@@ -440,7 +440,7 @@
     return 0.5;
   }
 
-  override allowedYRange(graphHeight: number): [number, number] {
+  override allowedRangeY(graphHeight: number): [number, number] {
     return [graphHeight - kProcessNodesYRange, graphHeight];
   }
 
@@ -469,7 +469,7 @@
     return kHighYStrength;
   }
 
-  override allowedYRange(graphHeight: number): [number, number] {
+  override allowedRangeY(graphHeight: number): [number, number] {
     return [
       graphHeight - kWorkerNodesYRange,
       graphHeight - kProcessNodesYRange,
@@ -492,7 +492,7 @@
 }
 
 /**
- * A force that bounds GraphNodes |allowedYRange| in Y,
+ * A force that bounds GraphNodes |allowedRangeY| in Y,
  * as well as bounding them to stay in page bounds in X.
  */
 function boundingForce(graphHeight: number, graphWidth: number) {
@@ -532,7 +532,7 @@
   force.initialize = function(n: GraphNode[]) {
     nodes = n;
     bounds = nodes.map(node => {
-      const nodeBounds = node.allowedYRange(graphHeight);
+      const nodeBounds = node.allowedRangeY(graphHeight);
       // Leave space for the node circle plus a small border.
       nodeBounds[0] += kNodeRadius * 2;
       nodeBounds[1] -= kNodeRadius * 2;
@@ -1045,7 +1045,7 @@
     }
     // Leave the node pinned where it was dropped. Return it to free
     // positioning if it's dropped outside its designated area.
-    const bounds = d.allowedYRange(this.height_);
+    const bounds = d.allowedRangeY(this.height_);
     if (d3.event.y < bounds[0] || d3.event.y > bounds[1]) {
       d.fx = null;
       d.fy = null;
@@ -1055,11 +1055,11 @@
     d3.select(`#circle-${d.id}`).classed('pinned', d.fx != null);
   }
 
-  private getTargetYPosition_(d: GraphNode): number {
-    return d.targetYPosition(this.height_);
+  private getTargetPositionY_(d: GraphNode): number {
+    return d.targetPositionY(this.height_);
   }
 
-  private getTargetYPositionStrength_(d: GraphNode): number {
+  private getTargetPositionStrengthY_(d: GraphNode): number {
     return d.targetYPositionStrength;
   }
 
@@ -1135,8 +1135,8 @@
     // Reset both X and Y attractive forces, as they're cached.
     const xForce = d3.forceX().x(this.width_ / 2).strength(0.1);
     const yForce = (d3.forceY() as d3.ForceY<GraphNode>)
-                       .y(this.getTargetYPosition_.bind(this))
-                       .strength(this.getTargetYPositionStrength_.bind(this));
+                       .y(this.getTargetPositionY_.bind(this))
+                       .strength(this.getTargetPositionStrengthY_.bind(this));
     this.simulation_!.force('x_pos', xForce);
     this.simulation_!.force('y_pos', yForce);
     this.simulation_!.force(
diff --git a/chrome/browser/resources/gaia_auth_host/authenticator.js b/chrome/browser/resources/gaia_auth_host/authenticator.js
index cf0e35a..3962a5a 100644
--- a/chrome/browser/resources/gaia_auth_host/authenticator.js
+++ b/chrome/browser/resources/gaia_auth_host/authenticator.js
@@ -555,6 +555,7 @@
     this.maybeClearGaiaTimeout_();
     this.syncTrustedVaultKeys_ = null;
     this.closeViewReceived_ = false;
+    this.disableAllActions_();
   }
 
   /**
@@ -1499,6 +1500,15 @@
   }
 
   /**
+   * Disables all navigation actions until explicitly re-enabled by GAIA.
+   * @private
+   */
+  disableAllActions_() {
+    this.dispatchEvent(
+        new CustomEvent('setAllActionsEnabled', {detail: false}));
+  }
+
+  /**
    * Set the user's email.
    * @param {string} email New email value.
    * @private
diff --git a/chrome/browser/resources/internals/lens/BUILD.gn b/chrome/browser/resources/internals/lens/BUILD.gn
index 029c7b4..087b8bb 100644
--- a/chrome/browser/resources/internals/lens/BUILD.gn
+++ b/chrome/browser/resources/internals/lens/BUILD.gn
@@ -2,33 +2,28 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import("//third_party/closure_compiler/compile_js.gni")
+import("//tools/typescript/ts_library.gni")
 import("//ui/webui/resources/tools/generate_grd.gni")
 
 assert(is_android)
 
+ts_library("build_ts") {
+  root_dir = "."
+  out_dir = "$target_gen_dir/tsc"
+  in_files = [
+    "lens_internals.ts",
+    "lens_internals_browser_proxy.ts",
+  ]
+  definitions = [ "//tools/typescript/definitions/chrome_send.d.ts" ]
+  deps = [ "//ui/webui/resources:library" ]
+}
+
 generate_grd("build_grdp") {
   grd_prefix = "lens_internals"
   out_grd = "$target_gen_dir/resources.grdp"
-  input_files = [
-    "lens_internals_browser_proxy.js",
-    "lens_internals.html",
-    "lens_internals.js",
-  ]
+  input_files = [ "lens_internals.html" ]
   input_files_base_dir = rebase_path(".", "//")
-}
-
-js_type_check("closure_compile") {
-  deps = [
-    ":lens_internals",
-    ":lens_internals_browser_proxy",
-  ]
-}
-
-js_library("lens_internals") {
-  deps = [ ":lens_internals_browser_proxy" ]
-}
-
-js_library("lens_internals_browser_proxy") {
-  deps = [ "//ui/webui/resources/js:cr.m" ]
+  deps = [ ":build_ts" ]
+  manifest_files =
+      filter_include(get_target_outputs(":build_ts"), [ "*.manifest" ])
 }
diff --git a/chrome/browser/resources/internals/lens/lens_internals.js b/chrome/browser/resources/internals/lens/lens_internals.ts
similarity index 69%
rename from chrome/browser/resources/internals/lens/lens_internals.js
rename to chrome/browser/resources/internals/lens/lens_internals.ts
index b87e1716..3ff015f2 100644
--- a/chrome/browser/resources/internals/lens/lens_internals.js
+++ b/chrome/browser/resources/internals/lens/lens_internals.ts
@@ -2,21 +2,22 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import {getRequiredElement} from 'chrome://resources/js/util_ts.js';
+
 import {LensInternalsBrowserProxy, LensInternalsBrowserProxyImpl} from './lens_internals_browser_proxy.js';
 
-/** @param {boolean} showEnableButton Whether to show the "start" button. */
-function toggleDebugModeButton(showEnableButton) {
-  document.body.querySelector('#start-debug-mode')
+/** @param showEnableButton Whether to show the "start" button. */
+function toggleDebugModeButton(showEnableButton: boolean) {
+  getRequiredElement('start-debug-mode')
       .toggleAttribute('hidden', !showEnableButton);
-  document.body.querySelector('#stop-debug-mode')
+  getRequiredElement('stop-debug-mode')
       .toggleAttribute('hidden', showEnableButton);
 }
 
 /**
- * @param {!Array<!Array<string>>} data The debug data to display in table
- *     form.
+ * @param data The debug data to display in table form.
  */
-function onDebugDataRefreshed(data) {
+function onDebugDataRefreshed(data: string[][]) {
   if (data.length === 0) {
     toggleDebugModeButton(/*showEnableButton=*/ true);
   } else {
@@ -26,10 +27,9 @@
 }
 
 /**
- * @param {!Array<!Array<string>>} tableData The debug data to display in
- *     table form.
+ * @param tableData The debug data to display in table form.
  */
-function updateDebugDataTable(tableData) {
+function updateDebugDataTable(tableData: string[][]) {
   const table = document.createElement('table');
   table.style.display = 'block';
   const tableBody = document.createElement('tbody');
@@ -45,15 +45,15 @@
   });
   table.appendChild(tableBody);
 
-  document.body.querySelector('#debug-data-table').replaceWith(table);
+  getRequiredElement('debug-data-table').replaceWith(table);
   table.id = 'debug-data-table';
 }
 
 function initialize() {
-  /** @type {!LensInternalsBrowserProxy} */
-  const browserProxy = LensInternalsBrowserProxyImpl.getInstance();
+  const browserProxy: LensInternalsBrowserProxy =
+      LensInternalsBrowserProxyImpl.getInstance();
 
-  document.body.querySelector('#start-debug-mode').onclick = function() {
+  getRequiredElement('start-debug-mode').onclick = function() {
     browserProxy.startDebugMode().then(function() {
       // After starting debug mode automatically refresh data.  This will
       // toggle the button if the call was successful.
@@ -61,7 +61,7 @@
     });
   };
 
-  document.body.querySelector('#stop-debug-mode').onclick = function() {
+  getRequiredElement('stop-debug-mode').onclick = function() {
     browserProxy.stopDebugMode().then(function() {
       // After stopping debug mode automatically refresh data.  This will
       // toggle the button if the call was successful.
diff --git a/chrome/browser/resources/internals/lens/lens_internals_browser_proxy.js b/chrome/browser/resources/internals/lens/lens_internals_browser_proxy.js
deleted file mode 100644
index e8f06356..0000000
--- a/chrome/browser/resources/internals/lens/lens_internals_browser_proxy.js
+++ /dev/null
@@ -1,55 +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.
-
-import {sendWithPromise} from 'chrome://resources/js/cr.m.js';
-
-/** @type {?LensInternalsBrowserProxy} */
-let instance = null;
-
-/** @interface */
-export class LensInternalsBrowserProxy {
-  /**
-   * Start debug mode collection for proactive.
-   * @return {!Promise<void>} A promise firing when the call is complete.
-   */
-  startDebugMode() {}
-
-  /**
-   * Start debug mode collection for proactive.
-   * @return {!Promise<!Array<!Array<string>>>} A promise firing when the call
-   *     is complete.
-   */
-  refreshDebugData() {}
-
-  /**
-   * Stop debug mode collection for proactive.
-   * @return {!Promise<void>} A promise firing when the call is complete.
-   */
-  stopDebugMode() {}
-}
-
-/**
- * @implements {LensInternalsBrowserProxy}
- */
-export class LensInternalsBrowserProxyImpl {
-  /** @override */
-  startDebugMode() {
-    return sendWithPromise('startDebugMode');
-  }
-
-  /** @override */
-  refreshDebugData() {
-    return sendWithPromise('refreshDebugData');
-  }
-
-  /** @override */
-  stopDebugMode() {
-    return sendWithPromise('stopDebugMode');
-  }
-
-  /** @return {!LensInternalsBrowserProxy} */
-  static getInstance() {
-    return instance || (instance = new LensInternalsBrowserProxyImpl());
-  }
-}
diff --git a/chrome/browser/resources/internals/lens/lens_internals_browser_proxy.ts b/chrome/browser/resources/internals/lens/lens_internals_browser_proxy.ts
new file mode 100644
index 0000000..8fa7b5d
--- /dev/null
+++ b/chrome/browser/resources/internals/lens/lens_internals_browser_proxy.ts
@@ -0,0 +1,46 @@
+// 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 {sendWithPromise} from 'chrome://resources/js/cr.js';
+
+let instance: LensInternalsBrowserProxy|null = null;
+
+export interface LensInternalsBrowserProxy {
+  /**
+   * Start debug mode collection for proactive.
+   * @return A promise firing when the call is complete.
+   */
+  startDebugMode(): Promise<void>;
+
+  /**
+   * Start debug mode collection for proactive.
+   * @return  A promise firing when the call is complete.
+   */
+  refreshDebugData(): Promise<string[][]>;
+
+  /**
+   * Stop debug mode collection for proactive.
+   * @return A promise firing when the call is complete.
+   */
+  stopDebugMode(): Promise<void>;
+}
+
+export class LensInternalsBrowserProxyImpl implements
+    LensInternalsBrowserProxy {
+  startDebugMode() {
+    return sendWithPromise('startDebugMode');
+  }
+
+  refreshDebugData() {
+    return sendWithPromise('refreshDebugData');
+  }
+
+  stopDebugMode() {
+    return sendWithPromise('stopDebugMode');
+  }
+
+  static getInstance(): LensInternalsBrowserProxy {
+    return instance || (instance = new LensInternalsBrowserProxyImpl());
+  }
+}
diff --git a/chrome/browser/resources/new_tab_page/middle_slot_promo.html b/chrome/browser/resources/new_tab_page/middle_slot_promo.html
index 08a192d..19009470 100644
--- a/chrome/browser/resources/new_tab_page/middle_slot_promo.html
+++ b/chrome/browser/resources/new_tab_page/middle_slot_promo.html
@@ -30,7 +30,7 @@
   }
 
   #promoContainer > :first-child {
-    padding-inline-start: 8px;
+    margin-inline-start: 8px;
   }
 
   a {
diff --git a/chrome/browser/resources/password_manager/password_details_card.html b/chrome/browser/resources/password_manager/password_details_card.html
index c235a23..212c35f 100644
--- a/chrome/browser/resources/password_manager/password_details_card.html
+++ b/chrome/browser/resources/password_manager/password_details_card.html
@@ -68,13 +68,13 @@
       </div>
       <div class="column-container">
         <!-- TODO(crbug.com/1350947): Support displaying apps. -->
-        <!-- TODO(crbug.com/1350947): Support displaying multiple sites. -->
         <div class="cr-form-field-label">$i18n{sitesLabel}</div>
-        <a id="linkValue" href="[[password.urls.link]]" class="site-link">
-          <span class="elide-left">
-            <span>[[password.urls.shown]]</span>
-          </span>
-        </a>
+        <template id="links" is="dom-repeat"
+            items="[[password.affiliatedDomains]]">
+          <div class="elide-left">
+            <a href="[[item.url]]" class="site-link">[[item.name]]</a>
+          </div>
+        </template>
       </div>
     </div>
     <div class="row-container">
diff --git a/chrome/browser/resources/password_manager/password_details_card.ts b/chrome/browser/resources/password_manager/password_details_card.ts
index 170b342..df167f9 100644
--- a/chrome/browser/resources/password_manager/password_details_card.ts
+++ b/chrome/browser/resources/password_manager/password_details_card.ts
@@ -25,7 +25,6 @@
     copyUsernameButton: CrIconButtonElement,
     deleteButton: CrButtonElement,
     editButton: CrButtonElement,
-    linkValue: HTMLElement,
     passwordValue: CrInputElement,
     showPasswordButton: CrIconButtonElement,
     toast: CrToastElement,
diff --git a/chrome/browser/resources/password_manager/password_manager.ts b/chrome/browser/resources/password_manager/password_manager.ts
index 789f5179..995c39f6 100644
--- a/chrome/browser/resources/password_manager/password_manager.ts
+++ b/chrome/browser/resources/password_manager/password_manager.ts
@@ -5,6 +5,7 @@
 import './password_manager_app.js';
 
 export {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.js';
+export {CrInputElement} from 'chrome://resources/cr_elements/cr_input/cr_input.js';
 export {PasswordsExportDialogElement} from './dialogs/passwords_export_dialog.js';
 export {PasswordDetailsCardElement} from './password_details_card.js';
 export {PasswordDetailsSectionElement} from './password_details_section.js';
diff --git a/chrome/browser/resources/password_manager/shared_style.css b/chrome/browser/resources/password_manager/shared_style.css
index 93791aa..f097fb0 100644
--- a/chrome/browser/resources/password_manager/shared_style.css
+++ b/chrome/browser/resources/password_manager/shared_style.css
@@ -40,7 +40,7 @@
   direction: rtl;
 }
 
-.elide-left > span {
+.elide-left > a {
   direction: ltr;
   /* This styling is necessary to fix the display of domains starting with
    * numbers.
diff --git a/chrome/browser/resources/settings/BUILD.gn b/chrome/browser/resources/settings/BUILD.gn
index 250255f9..e18822f 100644
--- a/chrome/browser/resources/settings/BUILD.gn
+++ b/chrome/browser/resources/settings/BUILD.gn
@@ -14,24 +14,7 @@
 
 if (is_chromeos_ash) {
   group("closure_compile") {
-    deps = [
-      ":closure_compile_local",
-      "chromeos:closure_compile_module",
-    ]
-  }
-
-  js_type_check("closure_compile_local") {
-    is_polymer3 = true
-    closure_flags = settings_closure_flags
-    deps = [ ":router" ]
-  }
-
-  js_library("router") {
-    deps = [
-      "//ui/webui/resources/js:assert",
-      "//ui/webui/resources/js:load_time_data.m",
-    ]
-    externs_list = [ "$externs_path/metrics_private.js" ]
+    deps = [ "chromeos:closure_compile_module" ]
   }
 }
 
@@ -357,13 +340,12 @@
     "relaunch_mixin.ts",
     "reset_page/reset_browser_proxy.ts",
     "route.ts",
-    "router.js",
+    "router.ts",
     "safety_check_page/safety_check_browser_proxy.ts",
     "search_engines_page/search_engines_browser_proxy.ts",
     "search_settings.ts",
     "settings.ts",
     "settings_page/main_page_mixin.ts",
-    "settings_routes.ts",
     "site_settings/constants.ts",
     "site_settings/site_settings_mixin.ts",
     "site_settings/site_settings_permissions_browser_proxy.ts",
diff --git a/chrome/browser/resources/settings/chromeos/BUILD.gn b/chrome/browser/resources/settings/chromeos/BUILD.gn
index d4588bd..dff9af6 100644
--- a/chrome/browser/resources/settings/chromeos/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/BUILD.gn
@@ -338,9 +338,18 @@
     ":prefs_behavior",
     ":route_observer_behavior",
     ":route_origin_behavior",
+    ":router",
   ]
 }
 
+js_library("router") {
+  deps = [
+    "//ui/webui/resources/js:assert",
+    "//ui/webui/resources/js:load_time_data.m",
+  ]
+  externs_list = [ "$externs_path/metrics_private.js" ]
+}
+
 js_library("os_settings") {
 }
 
@@ -349,7 +358,7 @@
 
 js_library("deep_linking_behavior") {
   deps = [
-    "..:router",
+    ":router",
     "//chrome/browser/ui/webui/settings/chromeos/constants:mojom_webui_js",
     "//ui/webui/resources/js:assert",
     "//ui/webui/resources/js:load_time_data.m",
@@ -368,7 +377,7 @@
 js_library("global_scroll_target_behavior") {
   deps = [
     ":route_observer_behavior",
-    "..:router",
+    ":router",
     "//ash/webui/common/resources:promise_resolver",
   ]
 }
@@ -387,7 +396,7 @@
 js_library("os_route") {
   deps = [
     ":os_settings_routes",
-    "..:router",
+    ":router",
     "//chrome/browser/ui/webui/settings/chromeos/constants:mojom_webui_js",
     "//ui/webui/resources/js:cr.m",
     "//ui/webui/resources/js:load_time_data.m",
@@ -396,7 +405,7 @@
 
 js_library("os_settings_routes") {
   deps = [
-    "..:router",
+    ":router",
     "//ui/webui/resources/js:load_time_data.m",
   ]
 }
@@ -408,7 +417,7 @@
 
 js_library("route_observer_behavior") {
   deps = [
-    "..:router",
+    ":router",
     "//ui/webui/resources/js:assert",
   ]
 }
diff --git a/chrome/browser/resources/settings/chromeos/crostini_page/bruschetta_subpage.ts b/chrome/browser/resources/settings/chromeos/crostini_page/bruschetta_subpage.ts
index 70c11e5..d4037db 100644
--- a/chrome/browser/resources/settings/chromeos/crostini_page/bruschetta_subpage.ts
+++ b/chrome/browser/resources/settings/chromeos/crostini_page/bruschetta_subpage.ts
@@ -13,7 +13,7 @@
 
 import {mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-import {Route, Router} from '../../router.js';
+import {Route, Router} from '../router.js';
 import {routes} from '../os_route.js';
 import {RouteOriginBehavior, RouteOriginBehaviorInterface} from '../route_origin_behavior.js';
 
diff --git a/chrome/browser/resources/settings/chromeos/crostini_page/crostini_arc_adb.ts b/chrome/browser/resources/settings/chromeos/crostini_page/crostini_arc_adb.ts
index 5bb0b08..f46b7e4 100644
--- a/chrome/browser/resources/settings/chromeos/crostini_page/crostini_arc_adb.ts
+++ b/chrome/browser/resources/settings/chromeos/crostini_page/crostini_arc_adb.ts
@@ -22,7 +22,7 @@
 import {mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface} from '../../router.js';
+import {Route, RouteObserverMixin, RouteObserverMixinInterface} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {routes} from '../os_route.js';
 
diff --git a/chrome/browser/resources/settings/chromeos/crostini_page/crostini_export_import.ts b/chrome/browser/resources/settings/chromeos/crostini_page/crostini_export_import.ts
index 1ab3ff6..40c5cd3 100644
--- a/chrome/browser/resources/settings/chromeos/crostini_page/crostini_export_import.ts
+++ b/chrome/browser/resources/settings/chromeos/crostini_page/crostini_export_import.ts
@@ -16,7 +16,7 @@
 import {mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface} from '../../router.js';
+import {Route, RouteObserverMixin, RouteObserverMixinInterface} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {ContainerInfo, GuestId} from '../guest_os/guest_os_browser_proxy.js';
 import {equalContainerId} from '../guest_os/guest_os_container_select.js';
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 56b4bf1..5d66db4 100644
--- a/chrome/browser/resources/settings/chromeos/crostini_page/crostini_page.ts
+++ b/chrome/browser/resources/settings/chromeos/crostini_page/crostini_page.ts
@@ -33,7 +33,7 @@
 
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
 import {PrefsMixin, PrefsMixinInterface} from '../../prefs/prefs_mixin.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../../router.js';
+import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {routes} from '../os_route.js';
 
diff --git a/chrome/browser/resources/settings/chromeos/crostini_page/crostini_subpage.ts b/chrome/browser/resources/settings/chromeos/crostini_page/crostini_subpage.ts
index 6cb46af..718a00d 100644
--- a/chrome/browser/resources/settings/chromeos/crostini_page/crostini_subpage.ts
+++ b/chrome/browser/resources/settings/chromeos/crostini_page/crostini_subpage.ts
@@ -25,7 +25,7 @@
 import {SettingsToggleButtonElement} from '../../controls/settings_toggle_button.js';
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
 import {PrefsMixin, PrefsMixinInterface} from '../../prefs/prefs_mixin.js';
-import {Route, Router} from '../../router.js';
+import {Route, Router} from '../router.js';
 import {castExists} from '../assert_extras.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {TERMINA_VM_TYPE} from '../guest_os/guest_os_browser_proxy.js';
diff --git a/chrome/browser/resources/settings/chromeos/date_time_page/date_time_page.ts b/chrome/browser/resources/settings/chromeos/date_time_page/date_time_page.ts
index a99f3aea..03a6d14 100644
--- a/chrome/browser/resources/settings/chromeos/date_time_page/date_time_page.ts
+++ b/chrome/browser/resources/settings/chromeos/date_time_page/date_time_page.ts
@@ -25,7 +25,7 @@
 import {loadTimeData} from '../../i18n_setup.js';
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
 import {PrefsMixin, PrefsMixinInterface} from '../../prefs/prefs_mixin.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../../router.js';
+import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {routes} from '../os_route.js';
 
diff --git a/chrome/browser/resources/settings/chromeos/date_time_page/timezone_subpage.ts b/chrome/browser/resources/settings/chromeos/date_time_page/timezone_subpage.ts
index 90a4699..b019bd3 100644
--- a/chrome/browser/resources/settings/chromeos/date_time_page/timezone_subpage.ts
+++ b/chrome/browser/resources/settings/chromeos/date_time_page/timezone_subpage.ts
@@ -20,7 +20,7 @@
 import {SettingsDropdownMenuElement} from '../../controls/settings_dropdown_menu.js';
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
 import {PrefsMixin, PrefsMixinInterface} from '../../prefs/prefs_mixin.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface} from '../../router.js';
+import {Route, RouteObserverMixin, RouteObserverMixinInterface} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {routes} from '../os_route.js';
 
diff --git a/chrome/browser/resources/settings/chromeos/device_page/audio.html b/chrome/browser/resources/settings/chromeos/device_page/audio.html
index 331c331..f6819bc 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/audio.html
+++ b/chrome/browser/resources/settings/chromeos/device_page/audio.html
@@ -138,6 +138,18 @@
             </template>
           </select>
         </div>
+        <div id="audioInputDeviceSubsection">
+          <div id="audioInputGainLabel">Volume</div>
+          <div>
+            <!--TODO(b/260277007): Replace icon once mic-on and mic-off icons
+                available. -->
+            <cr-icon-button id="audioInputGainMuteButton" iron-icon="cr:mic">
+            </cr-icon-button>
+            <cr-slider id ="audioInputGainVolumeSlider" min="0" max="100"
+                value="50">
+            </cr-slider>
+          </div>
+        </div>
       </div>
     </div>
   </div>
diff --git a/chrome/browser/resources/settings/chromeos/device_page/audio.ts b/chrome/browser/resources/settings/chromeos/device_page/audio.ts
index b697857..bad2770 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/audio.ts
+++ b/chrome/browser/resources/settings/chromeos/device_page/audio.ts
@@ -19,7 +19,7 @@
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {AudioDevice, AudioSystemPropertiesObserverReceiver, MuteState} from '../../mojom-webui/audio/cros_audio_config.mojom-webui.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface} from '../../router.js';
+import {Route, RouteObserverMixin, RouteObserverMixinInterface} from '../router.js';
 import {routes} from '../os_route.js';
 
 import {getTemplate} from './audio.html.js';
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 c832cd7..4b01680 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/device_page.ts
+++ b/chrome/browser/resources/settings/chromeos/device_page/device_page.ts
@@ -26,7 +26,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 {RouteObserverMixin, RouteObserverMixinInterface, Router} from '../../router.js';
+import {RouteObserverMixin, RouteObserverMixinInterface, Router} from '../router.js';
 import {routes} from '../os_route.js';
 
 import {getTemplate} from './device_page.html.js';
diff --git a/chrome/browser/resources/settings/chromeos/device_page/display.ts b/chrome/browser/resources/settings/chromeos/device_page/display.ts
index e0bd9168..293758f 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/display.ts
+++ b/chrome/browser/resources/settings/chromeos/device_page/display.ts
@@ -36,7 +36,7 @@
 import {SettingsSliderElement} from '../../controls/settings_slider.js';
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
 import {PrefsMixin, PrefsMixinInterface} from '../../prefs/prefs_mixin.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface} from '../../router.js';
+import {Route, RouteObserverMixin, RouteObserverMixinInterface} from '../router.js';
 import {assertExists, cast, castExists} from '../assert_extras.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {routes} from '../os_route.js';
diff --git a/chrome/browser/resources/settings/chromeos/device_page/fake_cros_audio_config.ts b/chrome/browser/resources/settings/chromeos/device_page/fake_cros_audio_config.ts
index cd9b14c2..22dba36 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/fake_cros_audio_config.ts
+++ b/chrome/browser/resources/settings/chromeos/device_page/fake_cros_audio_config.ts
@@ -57,6 +57,7 @@
 // handle audio input.
 export interface AudioSystemProperties extends AudioSystemPropertiesMojom {
   inputDevices: AudioDevice[];
+  inputMuteState: MuteState;
 }
 
 export interface FakePropertiesObserverInterface {
@@ -68,6 +69,7 @@
   outputVolumePercent: 75,
   outputMuteState: MuteState.kNotMuted,
   inputDevices: [fakeInternalFrontMic, fakeBluetoothMic],
+  inputMuteState: MuteState.kNotMuted,
 };
 
 /** Creates an audio device based on provided device and isActive override. */
@@ -80,6 +82,7 @@
 export interface FakeCrosAudioConfigInterface extends CrosAudioConfigInterface {
   setActiveDevice(outputDevice: AudioDevice): void;
   setOutputMuted(muted: boolean): void;
+  setInputMuted(muted: boolean): void;
 }
 
 export class FakeCrosAudioConfig implements FakeCrosAudioConfigInterface {
@@ -137,6 +140,16 @@
   }
 
   /**
+   * Sets the input device mute state to `kMutedByUser` when true and
+   * `kNotMuted` when false.
+   */
+  setInputMuted(muted: boolean): void {
+    const muteState = muted ? MuteState.kMutedByUser : MuteState.kNotMuted;
+    this.audioSystemProperties.inputMuteState = muteState;
+    this.notifyAudioSystemPropertiesUpdated();
+  }
+
+  /**
    * Sets the `outputVolumePercent` to the desired volume and notifies
    * observers.
    */
diff --git a/chrome/browser/resources/settings/chromeos/device_page/keyboard.ts b/chrome/browser/resources/settings/chromeos/device_page/keyboard.ts
index 312b980..06b4d8c 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/keyboard.ts
+++ b/chrome/browser/resources/settings/chromeos/device_page/keyboard.ts
@@ -22,7 +22,7 @@
 import {FocusConfig} from '../../focus_config.js';
 import {loadTimeData} from '../../i18n_setup.js';
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../../router.js';
+import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../router.js';
 import {castExists} from '../assert_extras.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {routes} from '../os_route.js';
diff --git a/chrome/browser/resources/settings/chromeos/device_page/pointers.ts b/chrome/browser/resources/settings/chromeos/device_page/pointers.ts
index 5e2fe9f4..07fabaf 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/pointers.ts
+++ b/chrome/browser/resources/settings/chromeos/device_page/pointers.ts
@@ -20,7 +20,7 @@
 
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
 import {PrefsMixin, PrefsMixinInterface} from '../../prefs/prefs_mixin.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface} from '../../router.js';
+import {Route, RouteObserverMixin, RouteObserverMixinInterface} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {routes} from '../os_route.js';
 
diff --git a/chrome/browser/resources/settings/chromeos/device_page/power.ts b/chrome/browser/resources/settings/chromeos/device_page/power.ts
index 603bc17..1716cabe 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/power.ts
+++ b/chrome/browser/resources/settings/chromeos/device_page/power.ts
@@ -23,7 +23,7 @@
 import {SettingsToggleButtonElement} from '../../controls/settings_toggle_button.js';
 import {SettingChangeValue} from '../../mojom-webui/search/user_action_recorder.mojom-webui.js';
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface} from '../../router.js';
+import {Route, RouteObserverMixin, RouteObserverMixinInterface} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {recordSettingChange} from '../metrics_recorder.js';
 import {routes} from '../os_route.js';
diff --git a/chrome/browser/resources/settings/chromeos/device_page/storage.ts b/chrome/browser/resources/settings/chromeos/device_page/storage.ts
index 02a90960..7a925fd 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/storage.ts
+++ b/chrome/browser/resources/settings/chromeos/device_page/storage.ts
@@ -16,7 +16,7 @@
 import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
 import {mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-import {Route, Router} from '../../router.js';
+import {Route, Router} from '../router.js';
 import {routes} from '../os_route.js';
 import {RouteOriginBehavior, RouteOriginBehaviorInterface} from '../route_origin_behavior.js';
 
diff --git a/chrome/browser/resources/settings/chromeos/device_page/stylus.ts b/chrome/browser/resources/settings/chromeos/device_page/stylus.ts
index 9d292dcb..0816efc 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/stylus.ts
+++ b/chrome/browser/resources/settings/chromeos/device_page/stylus.ts
@@ -20,7 +20,7 @@
 import {microTask, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface} from '../../router.js';
+import {Route, RouteObserverMixin, RouteObserverMixinInterface} from '../router.js';
 import {assertExists} from '../assert_extras.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {recordSettingChange} from '../metrics_recorder.js';
diff --git a/chrome/browser/resources/settings/chromeos/global_scroll_target_behavior.js b/chrome/browser/resources/settings/chromeos/global_scroll_target_behavior.js
index 3e7dd30f..5e268e18 100644
--- a/chrome/browser/resources/settings/chromeos/global_scroll_target_behavior.js
+++ b/chrome/browser/resources/settings/chromeos/global_scroll_target_behavior.js
@@ -15,7 +15,7 @@
  */
 
 import {PromiseResolver} from 'chrome://resources/ash/common/promise_resolver.js';
-import {Route, Router} from '../router.js';
+import {Route, Router} from './router.js';
 import {RouteObserverBehavior} from './route_observer_behavior.js';
 
 let scrollTargetResolver = new PromiseResolver();
diff --git a/chrome/browser/resources/settings/chromeos/google_assistant_page/google_assistant_page.ts b/chrome/browser/resources/settings/chromeos/google_assistant_page/google_assistant_page.ts
index 94f1557..18fab41 100644
--- a/chrome/browser/resources/settings/chromeos/google_assistant_page/google_assistant_page.ts
+++ b/chrome/browser/resources/settings/chromeos/google_assistant_page/google_assistant_page.ts
@@ -23,7 +23,7 @@
 import {SettingsToggleButtonElement} from '../../controls/settings_toggle_button.js';
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
 import {PrefsMixin, PrefsMixinInterface} from '../../prefs/prefs_mixin.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface} from '../../router.js';
+import {Route, RouteObserverMixin, RouteObserverMixinInterface} from '../router.js';
 import {cast, castExists} from '../assert_extras.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {recordSettingChange} from '../metrics_recorder.js';
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/internet_page/BUILD.gn
index 1e46997..db83821 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/internet_page/BUILD.gn
@@ -45,8 +45,8 @@
     "//ash/webui/common/resources/network:mojo_interface_provider",
     "//ash/webui/common/resources/network:network_listener_behavior",
     "//ash/webui/common/resources/network:onc_mojo",
-    "//chrome/browser/resources/settings:router",
     "//chrome/browser/resources/settings/chromeos:route_observer_behavior",
+    "//chrome/browser/resources/settings/chromeos:router",
     "//chromeos/services/network_config/public/mojom:mojom_webui_js",
     "//chromeos/services/network_config/public/mojom:network_types_webui_js",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
@@ -70,7 +70,7 @@
 js_library("hotspot_summary_item") {
   deps = [
     "//ash/webui/common/resources:i18n_behavior",
-    "//chrome/browser/resources/settings:router",
+    "//chrome/browser/resources/settings/chromeos:router",
     "//third_party/polymer/v3_0/components-chromium/iron-flex-layout:iron-flex-layout-classes",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
   ]
@@ -83,9 +83,9 @@
     "//ash/webui/common/resources:util",
     "//ash/webui/common/resources/network:network_config",
     "//ash/webui/common/resources/network:onc_mojo",
-    "//chrome/browser/resources/settings:router",
     "//chrome/browser/resources/settings/chromeos:metrics_recorder",
     "//chrome/browser/resources/settings/chromeos:route_observer_behavior",
+    "//chrome/browser/resources/settings/chromeos:router",
     "//third_party/polymer/v3_0/components-chromium/iron-flex-layout:iron-flex-layout-classes",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
   ]
@@ -102,9 +102,9 @@
     "//ash/webui/common/resources/cellular_setup:esim_manager_listener_behavior",
     "//ash/webui/common/resources/network:mojo_interface_provider",
     "//ash/webui/common/resources/network:onc_mojo",
-    "//chrome/browser/resources/settings:router",
     "//chrome/browser/resources/settings/chromeos:deep_linking_behavior",
     "//chrome/browser/resources/settings/chromeos:route_observer_behavior",
+    "//chrome/browser/resources/settings/chromeos:router",
     "//chromeos/ash/services/cellular_setup/public/mojom:mojom_webui_js",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
   ]
@@ -139,10 +139,10 @@
     "//ash/webui/common/resources/network:onc_mojo",
     "//ash/webui/common/resources/network_health:network_health_container",
     "//ash/webui/common/resources/traffic_counters:traffic_counters",
-    "//chrome/browser/resources/settings:router",
     "//chrome/browser/resources/settings/chromeos:deep_linking_behavior",
     "//chrome/browser/resources/settings/chromeos:metrics_recorder",
     "//chrome/browser/resources/settings/chromeos:route_observer_behavior",
+    "//chrome/browser/resources/settings/chromeos:router",
     "//chrome/browser/resources/settings/chromeos/os_people_page:os_sync_browser_proxy",
     "//third_party/polymer/v3_0/components-chromium/iron-collapse:iron-collapse",
     "//third_party/polymer/v3_0/components-chromium/iron-flex-layout:iron-flex-layout-classes",
@@ -167,11 +167,11 @@
     "//ash/webui/common/resources/network:mojo_interface_provider",
     "//ash/webui/common/resources/network:network_listener_behavior",
     "//ash/webui/common/resources/network:onc_mojo",
-    "//chrome/browser/resources/settings:router",
     "//chrome/browser/resources/settings/chromeos:deep_linking_behavior",
     "//chrome/browser/resources/settings/chromeos:metrics_recorder",
     "//chrome/browser/resources/settings/chromeos:os_route",
     "//chrome/browser/resources/settings/chromeos:route_observer_behavior",
+    "//chrome/browser/resources/settings/chromeos:router",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/js:assert",
   ]
@@ -202,11 +202,11 @@
     "//ash/webui/common/resources/network:network_listener_behavior",
     "//ash/webui/common/resources/network:onc_mojo",
     "//ash/webui/common/resources/network:sim_lock_dialogs",
-    "//chrome/browser/resources/settings:router",
     "//chrome/browser/resources/settings/chromeos:deep_linking_behavior",
     "//chrome/browser/resources/settings/chromeos:metrics_recorder",
     "//chrome/browser/resources/settings/chromeos:os_route",
     "//chrome/browser/resources/settings/chromeos:route_observer_behavior",
+    "//chrome/browser/resources/settings/chromeos:router",
     "//chromeos/ash/services/hotspot_config/public/mojom:mojom_webui_js",
     "//third_party/polymer/v3_0/components-chromium/iron-icon:iron-icon",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
@@ -241,12 +241,12 @@
     "//ash/webui/common/resources/network:network_list",
     "//ash/webui/common/resources/network:network_listener_behavior",
     "//ash/webui/common/resources/network:onc_mojo",
-    "//chrome/browser/resources/settings:router",
     "//chrome/browser/resources/settings/chromeos:deep_linking_behavior",
     "//chrome/browser/resources/settings/chromeos:metrics_recorder",
     "//chrome/browser/resources/settings/chromeos:os_route",
     "//chrome/browser/resources/settings/chromeos:route_observer_behavior",
     "//chrome/browser/resources/settings/chromeos:route_origin_behavior",
+    "//chrome/browser/resources/settings/chromeos:router",
     "//third_party/polymer/v3_0/components-chromium/iron-flex-layout:iron-flex-layout-classes",
     "//third_party/polymer/v3_0/components-chromium/iron-icon:iron-icon",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
@@ -281,10 +281,10 @@
     "//ash/webui/common/resources/network:cr_policy_network_behavior_mojo",
     "//ash/webui/common/resources/network:cr_policy_network_indicator_mojo",
     "//ash/webui/common/resources/network:network_proxy",
-    "//chrome/browser/resources/settings:router",
     "//chrome/browser/resources/settings/chromeos:os_route",
     "//chrome/browser/resources/settings/chromeos:prefs_behavior",
     "//chrome/browser/resources/settings/chromeos:route_observer_behavior",
+    "//chrome/browser/resources/settings/chromeos:router",
     "//third_party/polymer/v3_0/components-chromium/iron-flex-layout:iron-flex-layout-classes",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
   ]
@@ -411,9 +411,9 @@
     "//ash/webui/common/resources:i18n_behavior",
     "//ash/webui/common/resources/cellular_setup:esim_manager_utils",
     "//ash/webui/common/resources/network:onc_mojo",
-    "//chrome/browser/resources/settings:router",
     "//chrome/browser/resources/settings/chromeos:os_route",
     "//chrome/browser/resources/settings/chromeos:route_observer_behavior",
+    "//chrome/browser/resources/settings/chromeos:router",
     "//chromeos/ash/services/cellular_setup/public/mojom:mojom_webui_js",
   ]
   externs_list = [
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/apn_subpage.js b/chrome/browser/resources/settings/chromeos/internet_page/apn_subpage.js
index baf1eb9..899755e 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/apn_subpage.js
+++ b/chrome/browser/resources/settings/chromeos/internet_page/apn_subpage.js
@@ -19,7 +19,7 @@
 import {NetworkType} from 'chrome://resources/mojo/chromeos/services/network_config/public/mojom/network_types.mojom-webui.js';
 import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-import {Route, Router} from '../../router.js';
+import {Route, Router} from '../router.js';
 import {routes} from '../os_route.js';
 import {RouteObserverBehavior, RouteObserverBehaviorInterface} from '../route_observer_behavior.js';
 
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/esim_remove_profile_dialog.js b/chrome/browser/resources/settings/chromeos/internet_page/esim_remove_profile_dialog.js
index 2ad55c7..a1ba84e 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/esim_remove_profile_dialog.js
+++ b/chrome/browser/resources/settings/chromeos/internet_page/esim_remove_profile_dialog.js
@@ -17,7 +17,7 @@
 import {NetworkType} from 'chrome://resources/mojo/chromeos/services/network_config/public/mojom/network_types.mojom-webui.js';
 import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-import {Router} from '../../router.js';
+import {Router} from '../router.js';
 import {routes} from '../os_route.js';
 
 /**
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/hotspot_summary_item.js b/chrome/browser/resources/settings/chromeos/internet_page/hotspot_summary_item.js
index 399f090..aab4d10 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/hotspot_summary_item.js
+++ b/chrome/browser/resources/settings/chromeos/internet_page/hotspot_summary_item.js
@@ -15,7 +15,7 @@
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-import {Router} from '../../router.js';
+import {Router} from '../router.js';
 import {routes} from '../os_route.js';
 
 /**
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/internet_detail_menu.js b/chrome/browser/resources/settings/chromeos/internet_page/internet_detail_menu.js
index 113cfd2..7e66a828 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/internet_detail_menu.js
+++ b/chrome/browser/resources/settings/chromeos/internet_page/internet_detail_menu.js
@@ -21,7 +21,7 @@
 import {afterNextRender, html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
-import {Route, Router} from '../../router.js';
+import {Route, Router} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {routes} from '../os_route.js';
 import {RouteObserverBehavior, RouteObserverBehaviorInterface} from '../route_observer_behavior.js';
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/internet_detail_page.js b/chrome/browser/resources/settings/chromeos/internet_page/internet_detail_page.js
index 8594a3eb..ea5aeaf 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/internet_detail_page.js
+++ b/chrome/browser/resources/settings/chromeos/internet_page/internet_detail_page.js
@@ -50,7 +50,7 @@
 
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
 import {SyncBrowserProxyImpl} from '../../people_page/sync_browser_proxy.js';
-import {Route, Router} from '../../router.js';
+import {Route, Router} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {recordSettingChange} from '../metrics_recorder.js';
 import {OsSyncBrowserProxy, OsSyncBrowserProxyImpl, OsSyncPrefs} from '../os_people_page/os_sync_browser_proxy.js';
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/internet_known_networks_page.js b/chrome/browser/resources/settings/chromeos/internet_page/internet_known_networks_page.js
index 8e735ef..ea69742 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/internet_known_networks_page.js
+++ b/chrome/browser/resources/settings/chromeos/internet_page/internet_known_networks_page.js
@@ -25,7 +25,7 @@
 import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
-import {Route} from '../../router.js';
+import {Route} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {recordSettingChange} from '../metrics_recorder.js';
 import {routes} from '../os_route.js';
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/internet_page.js b/chrome/browser/resources/settings/chromeos/internet_page/internet_page.js
index dc35f25c..b7e0b7d 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/internet_page.js
+++ b/chrome/browser/resources/settings/chromeos/internet_page/internet_page.js
@@ -49,7 +49,7 @@
 import {afterNextRender, html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
-import {Route, Router} from '../../router.js';
+import {Route, Router} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {recordSettingChange} from '../metrics_recorder.js';
 import {routes} from '../os_route.js';
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/internet_subpage.js b/chrome/browser/resources/settings/chromeos/internet_page/internet_subpage.js
index ae46dfe..9a5fcf5 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/internet_subpage.js
+++ b/chrome/browser/resources/settings/chromeos/internet_page/internet_subpage.js
@@ -35,7 +35,7 @@
 import {afterNextRender, html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
-import {Route, Router} from '../../router.js';
+import {Route, Router} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {recordSettingChange} from '../metrics_recorder.js';
 import {routes} from '../os_route.js';
diff --git a/chrome/browser/resources/settings/chromeos/kerberos_page/kerberos_accounts.ts b/chrome/browser/resources/settings/chromeos/kerberos_page/kerberos_accounts.ts
index 202716e5..c3910777 100644
--- a/chrome/browser/resources/settings/chromeos/kerberos_page/kerberos_accounts.ts
+++ b/chrome/browser/resources/settings/chromeos/kerberos_page/kerberos_accounts.ts
@@ -25,7 +25,7 @@
 
 import {loadTimeData} from '../../i18n_setup.js';
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../../router.js';
+import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../router.js';
 import {cast, castExists} from '../assert_extras.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {getImage} from '../icon.js';
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 3b42014d..23c95e5 100644
--- a/chrome/browser/resources/settings/chromeos/kerberos_page/kerberos_page.ts
+++ b/chrome/browser/resources/settings/chromeos/kerberos_page/kerberos_page.ts
@@ -20,7 +20,7 @@
 import {WebUiListenerMixin} from 'chrome://resources/cr_elements/web_ui_listener_mixin.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-import {Router} from '../../router.js';
+import {Router} from '../router.js';
 import {routes} from '../os_route.js';
 
 import {getTemplate} from './kerberos_page.html.js';
diff --git a/chrome/browser/resources/settings/chromeos/main_page_mixin.ts b/chrome/browser/resources/settings/chromeos/main_page_mixin.ts
index 15521b4..a41c7487 100644
--- a/chrome/browser/resources/settings/chromeos/main_page_mixin.ts
+++ b/chrome/browser/resources/settings/chromeos/main_page_mixin.ts
@@ -5,7 +5,7 @@
 import {assert, assertNotReached} from 'chrome://resources/js/assert_ts.js';
 import {beforeNextRender, dedupingMixin, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-import {MinimumRoutes, Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../router.js';
+import {MinimumRoutes, Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from './router.js';
 
 import {castExists} from './assert_extras.js';
 import {ensureLazyLoaded} from './ensure_lazy_loaded.js';
diff --git a/chrome/browser/resources/settings/chromeos/metrics_utils.ts b/chrome/browser/resources/settings/chromeos/metrics_utils.ts
index 067fb81..e9cb6664 100644
--- a/chrome/browser/resources/settings/chromeos/metrics_utils.ts
+++ b/chrome/browser/resources/settings/chromeos/metrics_utils.ts
@@ -25,7 +25,7 @@
 export function convertPrefToSettingMetric(
     prefKey: string, prefValue: unknown): SettingMetric|null {
   switch (prefKey) {
-    // device_page/keyboard.js
+    // device_page/keyboard.ts
     case 'settings.language.send_function_keys':
       assert(typeof prefValue === 'boolean');
       return {
@@ -33,7 +33,7 @@
         value: {boolValue: prefValue} as SettingChangeValue,
       };
 
-    // device_page/pointers.js
+    // device_page/pointers.ts
     case 'settings.touchpad.sensitivity2':
       assert(typeof prefValue === 'number');
       return {
@@ -41,6 +41,21 @@
         value: {intValue: prefValue} as SettingChangeValue,
       };
 
+    // os_a11y_page/display_and_magnification_page.ts
+    case 'settings.a11y.screen_magnifier_focus_following':
+      assert(typeof prefValue === 'boolean');
+      return {
+        setting: Setting.kFullscreenMagnifierFocusFollowing,
+        value: {boolValue: prefValue} as SettingChangeValue,
+      };
+
+    case 'settings.a11y.screen_magnifier_mouse_following_mode':
+      assert(typeof prefValue === 'number');
+      return {
+        setting: Setting.kFullscreenMagnifierMouseFollowingMode,
+        value: {intValue: prefValue} as SettingChangeValue,
+      };
+
     // os_privacy_page/os_privacy_page.js
     case 'cros.device.peripheral_data_access_enabled':
       assert(typeof prefValue === 'boolean');
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/multidevice_page/BUILD.gn
index 97407330..b52c1e2 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/BUILD.gn
@@ -58,7 +58,7 @@
     ":multidevice_metrics_logger",
     "..:os_route",
     "..:route_origin_behavior",
-    "../..:router",
+    "..:router",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/js:assert",
   ]
@@ -84,7 +84,7 @@
     "..:os_route",
     "..:prefs_behavior",
     "..:route_observer_behavior",
-    "../..:router",
+    "..:router",
     "//ash/webui/common/resources:web_ui_listener_behavior",
     "//chrome/browser/resources/nearby_share/shared:nearby_share_settings_behavior",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
@@ -128,7 +128,7 @@
     "..:os_route",
     "..:os_settings_routes",
     "..:route_observer_behavior",
-    "../..:router",
+    "..:router",
     "//ash/webui/common/resources/network:network_listener_behavior",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
   ]
@@ -176,7 +176,7 @@
     ":multidevice_wifi_sync_disabled_link",
     "..:os_route",
     "..:route_origin_behavior",
-    "../..:router",
+    "..:router",
     "../os_people_page:os_sync_browser_proxy",
     "//ash/webui/common/resources:i18n_behavior",
     "//ash/webui/common/resources:web_ui_listener_behavior",
@@ -189,7 +189,7 @@
 js_library("multidevice_task_continuation_disabled_link") {
   deps = [
     "..:os_route",
-    "../..:router",
+    "..:router",
     "//ash/webui/common/resources:i18n_behavior",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/js:load_time_data.m",
@@ -201,7 +201,7 @@
     ":multidevice_feature_behavior",
     "..:os_route",
     "..:route_origin_behavior",
-    "../..:router",
+    "..:router",
     "//ash/webui/common/resources:i18n_behavior",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/js:load_time_data.m",
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_feature_item.js b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_feature_item.js
index 8351058..ab245c6 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_feature_item.js
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_feature_item.js
@@ -21,7 +21,7 @@
 import {assert} from 'chrome://resources/js/assert.js';
 import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-import {Route, Router} from '../../router.js';
+import {Route, Router} from '../router.js';
 import {routes} from '../os_route.js';
 import {RouteOriginBehavior, RouteOriginBehaviorInterface} from '../route_origin_behavior.js';
 
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_page.js b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_page.js
index 61e599af..86248264 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_page.js
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_page.js
@@ -24,7 +24,7 @@
 
 import {loadTimeData} from '../../i18n_setup.js';
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
-import {Route, Router} from '../../router.js';
+import {Route, Router} from '../router.js';
 import {NearbyShareSettingsBehavior, NearbyShareSettingsBehaviorInterface} from '../../shared/nearby_share_settings_behavior.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {recordSettingChange} from '../metrics_recorder.js';
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_subpage.js b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_subpage.js
index ea7087a6..d129afe 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_subpage.js
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_subpage.js
@@ -25,7 +25,7 @@
 
 import {loadTimeData} from '../../i18n_setup.js';
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
-import {Route} from '../../router.js';
+import {Route} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {routes} from '../os_route.js';
 import {OsSettingsRoutes} from '../os_settings_routes.js';
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_task_continuation_disabled_link.js b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_task_continuation_disabled_link.js
index 3b313387..9767f1c2 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_task_continuation_disabled_link.js
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_task_continuation_disabled_link.js
@@ -19,7 +19,7 @@
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-import {Router} from '../../router.js';
+import {Router} from '../router.js';
 import {routes} from '../os_route.js';
 
 /**
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_wifi_sync_disabled_link.js b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_wifi_sync_disabled_link.js
index 50ecd82..73c80ba 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_wifi_sync_disabled_link.js
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_wifi_sync_disabled_link.js
@@ -18,7 +18,7 @@
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-import {Router} from '../../router.js';
+import {Router} from '../router.js';
 import {routes} from '../os_route.js';
 
 import {MultiDeviceFeatureBehavior, MultiDeviceFeatureBehaviorInterface} from './multidevice_feature_behavior.js';
diff --git a/chrome/browser/resources/settings/chromeos/nearby_share_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/nearby_share_page/BUILD.gn
index 67265a9..625fe35 100644
--- a/chrome/browser/resources/settings/chromeos/nearby_share_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/nearby_share_page/BUILD.gn
@@ -128,7 +128,7 @@
     "..:os_route",
     "..:prefs_behavior",
     "..:route_observer_behavior",
-    "../..:router",
+    "..:router",
     "//ash/webui/common/resources:i18n_behavior",
     "//chrome/browser/resources/nearby_share/shared:nearby_onboarding_one_page",
     "//chrome/browser/resources/nearby_share/shared:nearby_onboarding_page",
diff --git a/chrome/browser/resources/settings/chromeos/nearby_share_page/nearby_share_subpage.js b/chrome/browser/resources/settings/chromeos/nearby_share_page/nearby_share_subpage.js
index 72e1191..03db3d4 100644
--- a/chrome/browser/resources/settings/chromeos/nearby_share_page/nearby_share_subpage.js
+++ b/chrome/browser/resources/settings/chromeos/nearby_share_page/nearby_share_subpage.js
@@ -24,7 +24,7 @@
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
-import {Route, Router} from '../../router.js';
+import {Route, Router} from '../router.js';
 import {getContactManager} from '../../shared/nearby_contact_manager.js';
 import {NearbySettings} from '../../shared/nearby_share_settings_behavior.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_a11y_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_a11y_page/BUILD.gn
index 2f4ba07f..f127550 100644
--- a/chrome/browser/resources/settings/chromeos/os_a11y_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_a11y_page/BUILD.gn
@@ -21,11 +21,11 @@
     ":manage_a11y_page_browser_proxy",
     "//ash/webui/common/resources:i18n_behavior",
     "//ash/webui/common/resources:web_ui_listener_behavior",
-    "//chrome/browser/resources/settings:router",
     "//chrome/browser/resources/settings/chromeos:deep_linking_behavior",
     "//chrome/browser/resources/settings/chromeos:os_route",
     "//chrome/browser/resources/settings/chromeos:route_observer_behavior",
     "//chrome/browser/resources/settings/chromeos:route_origin_behavior",
+    "//chrome/browser/resources/settings/chromeos:router",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/js:load_time_data.m",
   ]
diff --git a/chrome/browser/resources/settings/chromeos/os_a11y_page/audio_and_captions_page.ts b/chrome/browser/resources/settings/chromeos/os_a11y_page/audio_and_captions_page.ts
index fc5645112..319645f 100644
--- a/chrome/browser/resources/settings/chromeos/os_a11y_page/audio_and_captions_page.ts
+++ b/chrome/browser/resources/settings/chromeos/os_a11y_page/audio_and_captions_page.ts
@@ -26,7 +26,7 @@
 import {afterNextRender, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
-import {Route} from '../../router.js';
+import {Route} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {routes} from '../os_route.js';
 import {RouteOriginBehavior, RouteOriginBehaviorImpl, RouteOriginBehaviorInterface} from '../route_origin_behavior.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_a11y_page/cursor_and_touchpad_page.html b/chrome/browser/resources/settings/chromeos/os_a11y_page/cursor_and_touchpad_page.html
index 7b09859..9bbdb6f2 100644
--- a/chrome/browser/resources/settings/chromeos/os_a11y_page/cursor_and_touchpad_page.html
+++ b/chrome/browser/resources/settings/chromeos/os_a11y_page/cursor_and_touchpad_page.html
@@ -23,6 +23,7 @@
 </style>
 
 <settings-toggle-button
+    id="autoClickToggle"
     pref="{{prefs.settings.a11y.autoclick}}"
     icon="os-settings:autoclick"
     label="$i18n{clickOnStopLabel}"
@@ -34,13 +35,15 @@
     <div class="start settings-box-text" aria-hidden="true">
       $i18n{delayBeforeClickLabel}
     </div>
-    <settings-dropdown-menu label="$i18n{delayBeforeClickLabel}"
+    <settings-dropdown-menu id="delayBeforeClickDropdown"
+        label="$i18n{delayBeforeClickLabel}"
         pref="{{prefs.settings.a11y.autoclick_delay_ms}}"
         menu-options="[[autoClickDelayOptions_]]"
         disabled="[[!prefs.settings.a11y.autoclick.value]]">
     </settings-dropdown-menu>
   </div>
-  <settings-toggle-button class="settings-box hr"
+  <settings-toggle-button id="autoClickStabilizePositionToggle"
+      class="settings-box hr"
       pref="{{prefs.settings.a11y.autoclick_stabilize_position}}"
       label="$i18n{autoclickStabilizeCursorPosition}">
   </settings-toggle-button>
@@ -48,14 +51,15 @@
     <div class="start settings-box-text" aria-hidden="true">
       $i18n{autoclickMovementThresholdLabel}
     </div>
-    <settings-dropdown-menu
+    <settings-dropdown-menu id="autoclickMovementThresholdDropdown"
         label="$i18n{autoclickMovementThresholdLabel}"
         pref="{{prefs.settings.a11y.autoclick_movement_threshold}}"
         menu-options="[[autoClickMovementThresholdOptions_]]"
         disabled="[[!prefs.settings.a11y.autoclick.value]]">
     </settings-dropdown-menu>
   </div>
-  <settings-toggle-button class="settings-box hr"
+  <settings-toggle-button id="autoClickRevertToLeftClickToggle"
+      class="settings-box hr"
       pref="{{prefs.settings.a11y.autoclick_revert_to_left_click}}"
       label="$i18n{autoclickRevertToLeftClick}">
   </settings-toggle-button>
diff --git a/chrome/browser/resources/settings/chromeos/os_a11y_page/cursor_and_touchpad_page.ts b/chrome/browser/resources/settings/chromeos/os_a11y_page/cursor_and_touchpad_page.ts
index 5f8ee62..6191f9b 100644
--- a/chrome/browser/resources/settings/chromeos/os_a11y_page/cursor_and_touchpad_page.ts
+++ b/chrome/browser/resources/settings/chromeos/os_a11y_page/cursor_and_touchpad_page.ts
@@ -26,7 +26,7 @@
 import {SettingsToggleButtonElement} from '../../controls/settings_toggle_button.js';
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
 import {PrefsMixin, PrefsMixinInterface} from '../../prefs/prefs_mixin.js';
-import {Route, Router} from '../../router.js';
+import {Route, Router} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {DevicePageBrowserProxy, DevicePageBrowserProxyImpl} from '../device_page/device_page_browser_proxy.js';
 import {routes} from '../os_route.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_a11y_page/display_and_magnification_page.ts b/chrome/browser/resources/settings/chromeos/os_a11y_page/display_and_magnification_page.ts
index da330bf..882a39a 100644
--- a/chrome/browser/resources/settings/chromeos/os_a11y_page/display_and_magnification_page.ts
+++ b/chrome/browser/resources/settings/chromeos/os_a11y_page/display_and_magnification_page.ts
@@ -23,7 +23,7 @@
 import {mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
-import {Route, Router} from '../../router.js';
+import {Route, Router} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {routes} from '../os_route.js';
 import {RouteOriginBehavior, RouteOriginBehaviorImpl, RouteOriginBehaviorInterface} from '../route_origin_behavior.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_a11y_page/keyboard_and_text_input_page.ts b/chrome/browser/resources/settings/chromeos/os_a11y_page/keyboard_and_text_input_page.ts
index c9dc03f..8fc96a7 100644
--- a/chrome/browser/resources/settings/chromeos/os_a11y_page/keyboard_and_text_input_page.ts
+++ b/chrome/browser/resources/settings/chromeos/os_a11y_page/keyboard_and_text_input_page.ts
@@ -25,7 +25,7 @@
 import {SettingsToggleButtonElement} from '../../controls/settings_toggle_button.js';
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
 import {PrefsMixin, PrefsMixinInterface} from '../../prefs/prefs_mixin.js';
-import {Route, Router} from '../../router.js';
+import {Route, Router} from '../router.js';
 import {cast} from '../assert_extras.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {routes} from '../os_route.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_a11y_page/manage_a11y_page.js b/chrome/browser/resources/settings/chromeos/os_a11y_page/manage_a11y_page.js
index 1e9f0062..87857c4f 100644
--- a/chrome/browser/resources/settings/chromeos/os_a11y_page/manage_a11y_page.js
+++ b/chrome/browser/resources/settings/chromeos/os_a11y_page/manage_a11y_page.js
@@ -23,7 +23,7 @@
 import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
-import {Route, Router} from '../../router.js';
+import {Route, Router} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {DevicePageBrowserProxyImpl} from '../device_page/device_page_browser_proxy.js';
 import {routes} from '../os_route.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_a11y_page/os_a11y_page.ts b/chrome/browser/resources/settings/chromeos/os_a11y_page/os_a11y_page.ts
index d53252a..7070c945 100644
--- a/chrome/browser/resources/settings/chromeos/os_a11y_page/os_a11y_page.ts
+++ b/chrome/browser/resources/settings/chromeos/os_a11y_page/os_a11y_page.ts
@@ -28,7 +28,7 @@
 
 import {SettingsToggleButtonElement} from '../../controls/settings_toggle_button.js';
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
-import {Route, Router} from '../../router.js';
+import {Route, Router} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {routes} from '../os_route.js';
 import {RouteOriginBehavior, RouteOriginBehaviorInterface} from '../route_origin_behavior.js';
@@ -174,10 +174,6 @@
       this.addFocusConfig(
           routes.A11Y_AUDIO_AND_CAPTIONS, '#audio-and-captions-page-trigger');
     }
-    if (routes.A11Y_SELECT_TO_SPEAK) {
-      this.addFocusConfig(
-          routes.A11Y_SELECT_TO_SPEAK, '#select-to-speak-subpage-trigger');
-    }
 
     this.addWebUiListener(
         'screen-reader-state-changed',
diff --git a/chrome/browser/resources/settings/chromeos/os_a11y_page/select_to_speak_subpage.html b/chrome/browser/resources/settings/chromeos/os_a11y_page/select_to_speak_subpage.html
index db7be92..0b74121 100644
--- a/chrome/browser/resources/settings/chromeos/os_a11y_page/select_to_speak_subpage.html
+++ b/chrome/browser/resources/settings/chromeos/os_a11y_page/select_to_speak_subpage.html
@@ -110,16 +110,43 @@
 
 <h2>$i18n{selectToSpeakOptionsSpeech}</h2>
 <div class="sub-item">
+  <div class="settings-box continuation">
+    <div class="start settings-box-text">
+      $i18n{selectToSpeakOptionsLanguagesFilterDescription}
+    </div>
+    <settings-dropdown-menu
+        label="$i18n{selectToSpeakOptionsLanguagesFilterDescription}"
+        pref="{{languageFilterVirtualPref_}}"
+        menu-options="[[languagesMenuOptions_]]">
+    </settings-dropdown-menu>
+  </div>
+  <div class="settings-box continuation">
+    <div class="start settings-box-text">
+      $i18n{selectToSpeakOptionsVoiceDescription}
+    </div>
+    <settings-dropdown-menu id="voiceDropdown"
+        label="$i18n{selectToSpeakOptionsVoiceDescription}"
+        pref="{{prefs.settings.a11y.select_to_speak_voice_name}}"
+        menu-options="[[localVoicesMenuOptions_]]"
+        deep-link-focus-id$="[[Setting.kSelectToSpeakVoice]]">
+      </settings-dropdown-menu>
+  </div>
   <template is="dom-if"
       if="[[isExperimentalAccessibilitySelectToSpeakVoiceSwitchingEnabled_]]">
+    <!-- TODO(crbug.com/1354821): Figure out how to make settings-toggle-button
+    and other control labels speakable with Select-to-speak. -->
+    <!-- TODO(crbug.com/1354821): Disable and turn off or hide
+    voiceSwitchingToggle when enhanced network voices is enabled. -->
     <settings-toggle-button
         id="voiceSwitchingToggle"
-        class="settings-box continuation"
+        class="settings-box"
         pref="{{prefs.settings.a11y.select_to_speak_voice_switching}}"
         label="$i18n{selectToSpeakOptionsVoiceSwitchingDescription}"
         deep-link-focus-id$="[[Setting.kSelectToSpeakVoiceSwitching]]">
     </settings-toggle-button>
   </template>
+  <!-- TODO(crbug.com/1354821): Disable and turn off or hide enhanced network
+  voices settings when disabled by policy. -->
   <settings-toggle-button
       id="enhancedNetworkVoicesToggle"
       class="settings-box"
@@ -129,6 +156,22 @@
       learn-more-url="$i18n{selectToSpeakLearnMoreUrl}"
       deep-link-focus-id$="[[Setting.kSelectToSpeakEnhancedNetworkVoices]]">
   </settings-toggle-button>
+  <template is="dom-if"
+      if="[[prefs.settings.a11y.select_to_speak_enhanced_network_voices.value]]">
+    <div class="indented">
+      <div class="settings-box">
+        <div class="start settings-box-text">
+          $i18n{selectToSpeakOptionsEnhancedNetworkVoice}
+        </div>
+        <settings-dropdown-menu id="enhancedNetworkVoiceDropdown"
+            label="$i18n{selectToSpeakOptionsEnhancedNetworkVoice}"
+            pref="{{prefs.settings.a11y.select_to_speak_enhanced_voice_name}}"
+            menu-options="[[networkVoicesMenuOptions_]]"
+            deep-link-focus-id$="[[Setting.kSelectToSpeakEnhancedNetworkVoice]]">
+        </settings-dropdown-menu>
+      </div>
+    </div>
+  </template>
 </div>
 
 <h2>$i18n{selectToSpeakOptionsHighlight}</h2>
@@ -158,7 +201,7 @@
       if="[[prefs.settings.a11y.select_to_speak_word_highlight.value]]">
     <div class="indented">
       <div class="settings-box continuation">
-        <div class="start settings-box-text" aria-hidden="true">
+        <div class="start settings-box-text">
           $i18n{selectToSpeakOptionsHighlightColorDescription}
         </div>
         <settings-dropdown-menu id="highlightColorDropdown"
@@ -169,7 +212,7 @@
       </div>
     </div>
   </template>
-  <div class="background-preview" aria-hidden=true>
+  <div class="background-preview">
     <p class="background-preview-unselected">
       $i18n{selectToSpeakOptionsSampleText}
     </p>
diff --git a/chrome/browser/resources/settings/chromeos/os_a11y_page/select_to_speak_subpage.ts b/chrome/browser/resources/settings/chromeos/os_a11y_page/select_to_speak_subpage.ts
index 5c52ea9..da6ca250f 100644
--- a/chrome/browser/resources/settings/chromeos/os_a11y_page/select_to_speak_subpage.ts
+++ b/chrome/browser/resources/settings/chromeos/os_a11y_page/select_to_speak_subpage.ts
@@ -17,14 +17,63 @@
 import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
 import {mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
+import {DropdownMenuOptionList} from '../../controls/settings_dropdown_menu.js';
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
 import {PrefsMixin, PrefsMixinInterface} from '../../prefs/prefs_mixin.js';
-import {Route} from '../../router.js';
+import {Route} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
+import {LanguagesBrowserProxy, LanguagesBrowserProxyImpl} from '../os_languages_page/languages_browser_proxy.js';
 import {routes} from '../os_route.js';
 import {RouteOriginBehavior, RouteOriginBehaviorInterface} from '../route_origin_behavior.js';
 
 import {getTemplate} from './select_to_speak_subpage.html.js';
+import {SelectToSpeakSubpageBrowserProxy, SelectToSpeakSubpageBrowserProxyImpl} from './select_to_speak_subpage_browser_proxy.js';
+
+/**
+ * Constant used as the value for a menu option representing the current device
+ * language.
+ */
+const USE_DEVICE_LANGUAGE = 'select_to_speak_device_language';
+
+/**
+ * Constant representing the system TTS voice.
+ */
+const SYSTEM_VOICE = 'select_to_speak_system_voice';
+
+/**
+ * Constant representing the voice name for the default (server-selected)
+ * enhanced network TTS voice.
+ */
+const DEFAULT_NETWORK_VOICE = 'default-wavenet';
+
+/**
+ * Extension ID of the enhanced network TTS voices extension.
+ */
+const ENHANCED_TTS_EXTENSION_ID = 'jacnkoglebceckolkoapelihnglgaicd';
+
+/**
+ * Subset of TtsEventType enum from:
+ *   chromium/src/content/public/browser/tts_utterance.h
+ * String conversion can be found in:
+ *   chrome/browser/speech/extension_api/tts_extension_api.cc
+ */
+enum EventType {
+  START = 'start',
+  END = 'end',
+  WORD = 'word',
+  CANCELLED = 'cancelled',
+}
+
+interface HandlerVoice {
+  eventTypes: EventType[];
+  extensionId: string;
+  lang: string;
+  voiceName: string;
+  displayName?: string;
+  displayLanguage?: string;
+  displayLanguageAndCountry?: string;
+  languageCode?: string;
+}
 
 const SettingsSelectToSpeakSubpageElementBase =
     mixinBehaviors(
@@ -51,6 +100,47 @@
   static get properties() {
     return {
       /**
+       * The language sort dropdown state as a fake preference object (so we can
+       * use <settings-dropdown-menu> without overriding with custom handlers)
+       */
+      languageFilterVirtualPref_: {
+        type: Object,
+        observer: 'languageChanged_',
+        notify: true,
+        value(): chrome.settingsPrivate.PrefObject {
+          return {
+            key: 'fakeLanguagePref',
+            type: chrome.settingsPrivate.PrefType.STRING,
+            value: USE_DEVICE_LANGUAGE,
+          };
+        },
+      },
+
+      /**
+       * List of options for the languages menu.
+       */
+      languagesMenuOptions_: {
+        type: Array,
+        value: [],
+      },
+
+      /**
+       * List of options for the local voices menu.
+       */
+      localVoicesMenuOptions_: {
+        type: Array,
+        value: [],
+      },
+
+      /**
+       * List of options for the network voices menu.
+       */
+      networkVoicesMenuOptions_: {
+        type: Array,
+        value: [],
+      },
+
+      /**
        * List of options for the text size drop-down menu.
        */
       highlightColorOptions_: {
@@ -111,19 +201,46 @@
 
   static get observers() {
     return [
-      'onHighlightColorChanged(prefs.settings.a11y.select_to_speak_highlight_color.value)',
+      'onHighlightColorChanged_(prefs.settings.a11y.select_to_speak_highlight_color.value)',
+      'languageChanged_(languageFilterVirtualPref_.*)',
     ];
   }
 
   private route_: Route;
+  private langBrowserProxy_: LanguagesBrowserProxy;
+  private languageFilterVirtualPref_: chrome.settingsPrivate.PrefObject<string>;
+  private languagesMenuOptions_: DropdownMenuOptionList;
+  private localVoicesMenuOptions_: DropdownMenuOptionList;
+  private networkVoicesMenuOptions_: DropdownMenuOptionList;
+  private appLocale_ = '';
+  private selectToSpeakBrowserProxy_: SelectToSpeakSubpageBrowserProxy;
+  private voices_: HandlerVoice[] = [];
 
   constructor() {
     super();
 
+    this.selectToSpeakBrowserProxy_ =
+        SelectToSpeakSubpageBrowserProxyImpl.getInstance();
+    this.langBrowserProxy_ = LanguagesBrowserProxyImpl.getInstance();
+
     /** RouteOriginBehavior override */
     this.route_ = routes.A11Y_SELECT_TO_SPEAK;
   }
 
+  override ready() {
+    super.ready();
+
+    this.addWebUiListener(
+        'all-sts-voice-data-updated',
+        (voices: HandlerVoice[]) => this.updateVoices_(voices));
+    this.addWebUiListener(
+        'app-locale-updated',
+        (appLocale: string) => this.updateAppLocale_(appLocale));
+    this.selectToSpeakBrowserProxy_.getAllTtsVoiceData();
+    this.selectToSpeakBrowserProxy_.getAppLocale();
+    this.selectToSpeakBrowserProxy_.refreshTtsVoices();
+  }
+
   /**
    * Note: Overrides RouteOriginBehavior implementation.
    */
@@ -138,10 +255,233 @@
     this.attemptDeepLink();
   }
 
-  private onHighlightColorChanged(color: string) {
+  private onHighlightColorChanged_(color: string) {
     this.shadowRoot!.getElementById('lightHighlight')!.style.background = color;
     this.shadowRoot!.getElementById('darkHighlight')!.style.background = color;
   }
+
+  private languageChanged_() {
+    this.populateVoicesAndLanguages_();
+  }
+
+  /**
+   * Updates the lists of all voices and the UI to use in display.
+   */
+  private updateVoices_(voices: HandlerVoice[]): void {
+    this.voices_ = voices;
+    this.populateVoicesAndLanguages_();
+  }
+
+  /**
+   * Updates the app locale and repopulates voices and languages.
+   */
+  private updateAppLocale_(appLocale: string): void {
+    this.appLocale_ = appLocale.toLowerCase();
+    this.populateVoicesAndLanguages_();
+  }
+
+  /**
+   * Populate select elements corresponding to local and network voices with a
+   * list of corresponding TTS voices, and select element corresponding to
+   * language with a list of languages covered by the available voices.
+   * @private
+   */
+  private populateVoicesAndLanguages_() {
+    let lang = this.languageFilterVirtualPref_.value || USE_DEVICE_LANGUAGE;
+    if (lang === USE_DEVICE_LANGUAGE) {
+      lang = this.getLanguageShortCode_(this.appLocale_);
+    }
+
+    const languagesMenuOptions = [{
+      value: USE_DEVICE_LANGUAGE,
+      name: this.i18n('selectToSpeakOptionsDeviceLanguage'),
+    }];
+
+    const localVoicesMenuOptions = [{
+      value: SYSTEM_VOICE,
+      name: this.i18n('selectToSpeakOptionsSystemVoice'),
+    }];
+    const networkVoicesMenuOptions = [{
+      value: DEFAULT_NETWORK_VOICE,
+      name: this.i18n('selectToSpeakOptionsDefaultNetworkVoice'),
+    }];
+
+    // Group voices by language, and languages by language family.
+    this.groupAndAddLanguagesAndVoices_(
+        this.voices_, lang, languagesMenuOptions, localVoicesMenuOptions,
+        networkVoicesMenuOptions);
+
+    // Update the dropdowns on the page.
+    this.languagesMenuOptions_ = languagesMenuOptions;
+    this.localVoicesMenuOptions_ = localVoicesMenuOptions;
+    this.networkVoicesMenuOptions_ = networkVoicesMenuOptions;
+  }
+
+  /**
+   * Group and sort available voices by language, and add languages, local
+   * voices, and network voices to their respective select elements.
+   * TODO(crbug.com/1234115): Add unit tests for this method.
+   */
+  private groupAndAddLanguagesAndVoices_(
+      voices: HandlerVoice[], preferredLang: string,
+      languageOptions: DropdownMenuOptionList,
+      localOptions: DropdownMenuOptionList,
+      networkOptions: DropdownMenuOptionList) {
+    // Group voices by language.
+    const languageDisplayNames = new Map();
+    const localVoices = new Map();
+    const networkVoices = new Map();
+
+    voices.forEach(voice => {
+      if (!this.isVoiceUsable_(voice)) {
+        return;
+      }
+      // Only show language names based on base language code.
+      const languageCode = this.getLanguageShortCode_(voice.lang || '');
+      const displayName = voice.displayLanguage;
+      if (!displayName) {
+        return;
+      }
+      languageDisplayNames.set(languageCode, displayName);
+      if (voice.extensionId === ENHANCED_TTS_EXTENSION_ID) {
+        // Get display name from locale for enhanced voices, since the
+        // supplied voiceName is not human-readable (e.g. enc-wavenet).
+        voice.displayName = voice.displayLanguageAndCountry;
+        this.addVoiceToMapForLanguage_(voice, networkVoices, languageCode);
+      } else {
+        voice.displayName = voice.voiceName;
+        this.addVoiceToMapForLanguage_(voice, localVoices, languageCode);
+      }
+    });
+
+    this.populateLanguages_(languageDisplayNames, languageOptions);
+
+    // Sort voices by language, with the preferred language on top.
+    const voiceLanguagesList = Array.from(languageDisplayNames.keys());
+    voiceLanguagesList.sort(
+        (lang1, lang2) => (Number(lang2 === preferredLang) -
+                           Number(lang1 === preferredLang)) ||
+            lang1.localeCompare(lang2));
+
+    // Populate local and network selects.
+    voiceLanguagesList.forEach(voiceLang => {
+      this.appendVoicesToOptions_(
+          localOptions, localVoices.get(voiceLang), /*numberVoices=*/ false);
+      this.appendVoicesToOptions_(
+          networkOptions, networkVoices.get(voiceLang),
+          /*numberVoices=*/ true);
+    });
+  }
+
+  /**
+   * Populate language select element with language display names.
+   * |languageDisplayNames| is a Map of language code (e.g. en) to display name
+   * (e.g. English).
+   */
+  private populateLanguages_(
+      languageDisplayNames: Map<string, string>,
+      languageOptions: DropdownMenuOptionList) {
+    const supportedLanguagesList = Array.from(languageDisplayNames.keys());
+    supportedLanguagesList.sort(
+        (lang1, lang2) => languageDisplayNames.get(lang1)!.localeCompare(
+            languageDisplayNames.get(lang2)!));
+    supportedLanguagesList.forEach(language => {
+      languageOptions.push(
+          {value: language, name: languageDisplayNames.get(language)!});
+    });
+  }
+
+  /**
+   * Checks if a voice has the properties and events needed for Select-to-speak.
+   */
+  private isVoiceUsable_(voice: HandlerVoice): boolean {
+    if (!voice.voiceName || !voice.lang) {
+      return false;
+    }
+    if (!voice.eventTypes.includes(EventType.START) ||
+        !voice.eventTypes.includes(EventType.END) ||
+        !voice.eventTypes.includes(EventType.WORD) ||
+        !voice.eventTypes.includes(EventType.CANCELLED)) {
+      // Required event types for Select-to-Speak.
+      return false;
+    }
+    return true;
+  }
+
+  /**
+   * Returns the ISO 639 code (e.g. en or yue) for the given language code (e.g.
+   * en-us).
+   */
+  private getLanguageShortCode_(lang: string): string {
+    return lang.trim().split(/-|_/)[0];
+  }
+
+  /**
+   * Groups voices by display name (e.g. English (Australia)) and if there is
+   * more than one voice per display name, adds a numerical index to them (e.g.
+   * English (Australia) 1) for disambiguation.
+   */
+  private addIndexToVoiceDisplayNames_(voiceList: HandlerVoice[]) {
+    const displayNameCounts = new Map<string, HandlerVoice[]>();
+    voiceList.forEach(voice => {
+      if (!displayNameCounts.has(voice.displayName!)) {
+        displayNameCounts.set(voice.displayName!, [voice]);
+      } else {
+        displayNameCounts.get(voice.displayName!)!.push(voice);
+      }
+    });
+    for (const voiceGroup of displayNameCounts.values()) {
+      if (voiceGroup.length > 1) {
+        let index = 1;
+        voiceGroup.forEach(voice => {
+          voice.displayName =
+              String(this.i18nAdvanced('selectToSpeakOptionsNaturalVoiceName', {
+                substitutions: [
+                  voice.displayName!,
+                  String(index),
+                ],
+              }));
+          index += 1;
+        });
+      }
+    }
+  }
+
+  /**
+   * Add options corresponding to the given list of voices to a select element.
+   * If |numberVoices| is true, add numbers to disambiguate voices with
+   * identical display names.
+   */
+  private appendVoicesToOptions_(
+      options: DropdownMenuOptionList, voiceList: HandlerVoice[],
+      numberVoices: boolean) {
+    if (!voiceList) {
+      return;
+    }
+    if (voiceList.length > 1) {
+      voiceList.sort((a, b) => a.displayName!.localeCompare(b.displayName!));
+      if (numberVoices) {
+        this.addIndexToVoiceDisplayNames_(voiceList);
+      }
+    }
+
+    voiceList.forEach(
+        voice =>
+            options.push({value: voice.voiceName, name: voice.displayName!}));
+  }
+
+  /**
+   * Adds a voice to the map entry corresponding to the given language.
+   */
+  private addVoiceToMapForLanguage_(
+      voice: HandlerVoice, map: Map<string, HandlerVoice[]>, lang: string) {
+    voice.languageCode = lang;
+    if (map.has(lang)) {
+      map.get(lang)!.push(voice);
+    } else {
+      map.set(lang, [voice]);
+    }
+  }
 }
 
 declare global {
diff --git a/chrome/browser/resources/settings/chromeos/os_a11y_page/select_to_speak_subpage_browser_proxy.ts b/chrome/browser/resources/settings/chromeos/os_a11y_page/select_to_speak_subpage_browser_proxy.ts
new file mode 100644
index 0000000..259adaac
--- /dev/null
+++ b/chrome/browser/resources/settings/chromeos/os_a11y_page/select_to_speak_subpage_browser_proxy.ts
@@ -0,0 +1,58 @@
+// 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.
+
+export interface SelectToSpeakSubpageBrowserProxy {
+  /**
+   * Requests the updated voice data. Returned by the
+   * 'all-sts-voice-data-updated' WebUI Listener event.
+   */
+  getAllTtsVoiceData(): void;
+
+  /**
+   * Requests the app locale. Returned by the 'app-locale-updated' WebUI
+   * Listener event.
+   */
+  getAppLocale(): void;
+
+  /**
+   * Requests the tts preview. Returns a success boolean in the
+   * 'tts-preview-state-changed' WebUI Listener event.
+   */
+  previewTtsVoice(previewText: string, previewVoice: string): void;
+
+  /**
+   * Triggers the TtsPlatform to update its list of voices and relay that update
+   * through VoicesChanged.
+   */
+  refreshTtsVoices(): void;
+}
+
+let instance: SelectToSpeakSubpageBrowserProxy|null = null;
+
+export class SelectToSpeakSubpageBrowserProxyImpl implements
+    SelectToSpeakSubpageBrowserProxy {
+  static getInstance(): SelectToSpeakSubpageBrowserProxy {
+    return instance || (instance = new SelectToSpeakSubpageBrowserProxyImpl());
+  }
+
+  static setInstanceForTesting(obj: SelectToSpeakSubpageBrowserProxy): void {
+    instance = obj;
+  }
+
+  getAllTtsVoiceData(): void {
+    chrome.send('getAllTtsVoiceDataForSts');
+  }
+
+  getAppLocale(): void {
+    chrome.send('getAppLocale');
+  }
+
+  previewTtsVoice(previewText: string, previewVoice: string): void {
+    chrome.send('previewTtsVoiceForSts', [previewText, previewVoice]);
+  }
+
+  refreshTtsVoices(): void {
+    chrome.send('refreshTtsVoices');
+  }
+}
diff --git a/chrome/browser/resources/settings/chromeos/os_a11y_page/switch_access_setup_guide_dialog.ts b/chrome/browser/resources/settings/chromeos/os_a11y_page/switch_access_setup_guide_dialog.ts
index ba54df9a..85852d4 100644
--- a/chrome/browser/resources/settings/chromeos/os_a11y_page/switch_access_setup_guide_dialog.ts
+++ b/chrome/browser/resources/settings/chromeos/os_a11y_page/switch_access_setup_guide_dialog.ts
@@ -20,7 +20,7 @@
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {PrefsMixin} from '../../prefs/prefs_mixin.js';
-import {Router} from '../../router.js';
+import {Router} from '../router.js';
 import {castExists} from '../assert_extras.js';
 import {routes} from '../os_route.js';
 
diff --git a/chrome/browser/resources/settings/chromeos/os_a11y_page/switch_access_subpage.ts b/chrome/browser/resources/settings/chromeos/os_a11y_page/switch_access_subpage.ts
index 1185c9a..8086ed7 100644
--- a/chrome/browser/resources/settings/chromeos/os_a11y_page/switch_access_subpage.ts
+++ b/chrome/browser/resources/settings/chromeos/os_a11y_page/switch_access_subpage.ts
@@ -26,7 +26,7 @@
 
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
 import {PrefsMixin, PrefsMixinInterface} from '../../prefs/prefs_mixin.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface} from '../../router.js';
+import {Route, RouteObserverMixin, RouteObserverMixinInterface} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {routes} from '../os_route.js';
 
diff --git a/chrome/browser/resources/settings/chromeos/os_a11y_page/text_to_speech_page.ts b/chrome/browser/resources/settings/chromeos/os_a11y_page/text_to_speech_page.ts
index b6bb7e3..9fc9984d 100644
--- a/chrome/browser/resources/settings/chromeos/os_a11y_page/text_to_speech_page.ts
+++ b/chrome/browser/resources/settings/chromeos/os_a11y_page/text_to_speech_page.ts
@@ -18,7 +18,7 @@
 import {mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
-import {Route, Router} from '../../router.js';
+import {Route, Router} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {DevicePageBrowserProxy, DevicePageBrowserProxyImpl} from '../device_page/device_page_browser_proxy.js';
 import {routes} from '../os_route.js';
@@ -116,6 +116,8 @@
   override ready() {
     super.ready();
 
+    this.addFocusConfig(
+        routes.A11Y_SELECT_TO_SPEAK, '#select-to-speak-subpage-trigger');
     this.addFocusConfig(routes.MANAGE_TTS_SETTINGS, '#ttsSubpageButton');
   }
 
diff --git a/chrome/browser/resources/settings/chromeos/os_a11y_page/tts_subpage.ts b/chrome/browser/resources/settings/chromeos/os_a11y_page/tts_subpage.ts
index 3e38ba5..b85a88d 100644
--- a/chrome/browser/resources/settings/chromeos/os_a11y_page/tts_subpage.ts
+++ b/chrome/browser/resources/settings/chromeos/os_a11y_page/tts_subpage.ts
@@ -21,7 +21,7 @@
 import {DomRepeat, DomRepeatEvent, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface} from '../../router.js';
+import {Route, RouteObserverMixin, RouteObserverMixinInterface} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {LanguagesBrowserProxy, LanguagesBrowserProxyImpl} from '../os_languages_page/languages_browser_proxy.js';
 import {routes} from '../os_route.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_about_page/detailed_build_info.ts b/chrome/browser/resources/settings/chromeos/os_about_page/detailed_build_info.ts
index 6064887d..c043afe2 100644
--- a/chrome/browser/resources/settings/chromeos/os_about_page/detailed_build_info.ts
+++ b/chrome/browser/resources/settings/chromeos/os_about_page/detailed_build_info.ts
@@ -26,7 +26,7 @@
 import {loadTimeData} from '../../i18n_setup.js';
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
 import {PrefsMixin, PrefsMixinInterface} from '../../prefs/prefs_mixin.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface} from '../../router.js';
+import {Route, RouteObserverMixin, RouteObserverMixinInterface} from '../router.js';
 import {castExists} from '../assert_extras.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {routes} from '../os_route.js';
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 5c37ecc..6f2d6fc 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
@@ -35,7 +35,7 @@
 import {loadTimeData} from '../../i18n_setup.js';
 import {LifetimeBrowserProxyImpl} from '../../lifetime_browser_proxy.js';
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
-import {Route, Router} from '../../router.js';
+import {Route, Router} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {MainPageMixin, MainPageMixinInterface} from '../main_page_mixin.js';
 import {recordSettingChange} from '../metrics_recorder.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/android_apps_subpage.ts b/chrome/browser/resources/settings/chromeos/os_apps_page/android_apps_subpage.ts
index b2be007..e54968f 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/android_apps_subpage.ts
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/android_apps_subpage.ts
@@ -19,7 +19,7 @@
 
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
 import {PrefsMixin, PrefsMixinInterface} from '../../prefs/prefs_mixin.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../../router.js';
+import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../router.js';
 import {castExists} from '../assert_extras.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {routes} from '../os_route.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/app_detail_view.ts b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/app_detail_view.ts
index e56b2cff..5d13062 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/app_detail_view.ts
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/app_detail_view.ts
@@ -16,7 +16,7 @@
 import {assertNotReached} from 'chrome://resources/js/assert_ts.js';
 import {microTask, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../../../router.js';
+import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../../router.js';
 import {castExists} from '../../assert_extras.js';
 import {routes} from '../../os_route.js';
 
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/borealis_page/borealis_detail_view.ts b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/borealis_page/borealis_detail_view.ts
index fe6b71f..6a39d35 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/borealis_page/borealis_detail_view.ts
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/borealis_page/borealis_detail_view.ts
@@ -12,7 +12,7 @@
 import {getSelectedApp} from 'chrome://resources/cr_components/app_management/util.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-import {Router} from '../../../../router.js';
+import {Router} from '../../../router.js';
 import {routes} from '../../../os_route.js';
 import {AppManagementStoreMixin} from '../store_mixin.js';
 
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/main_view.ts b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/main_view.ts
index 9b910c2..d9a2286 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/main_view.ts
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/main_view.ts
@@ -13,7 +13,7 @@
 import {assert} from 'chrome://resources/js/assert_ts.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-import {Route, RouteObserverMixin, RouteObserverMixinInterface} from '../../../router.js';
+import {Route, RouteObserverMixin, RouteObserverMixinInterface} from '../../router.js';
 import {routes} from '../../os_route.js';
 
 import {getTemplate} from './main_view.html.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/plugin_vm_page/plugin_vm_detail_view.ts b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/plugin_vm_page/plugin_vm_detail_view.ts
index 1961b87..5cc8b91 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/plugin_vm_page/plugin_vm_detail_view.ts
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/plugin_vm_page/plugin_vm_detail_view.ts
@@ -17,7 +17,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 {Router} from '../../../../router.js';
+import {Router} from '../../../router.js';
 import {cast} from '../../../assert_extras.js';
 import {routes} from '../../../os_route.js';
 import {AppManagementStoreMixin} from '../store_mixin.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/util.ts b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/util.ts
index 546db6f00..217c277 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/util.ts
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/util.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 {Router} from '../../../router.js';
+import {Router} from '../../router.js';
 import {routes} from '../../os_route.js';
 
 /**
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/app_notifications_subpage.html b/chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/app_notifications_subpage.html
index 5bdc079..72d5463 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/app_notifications_subpage.html
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/app_notifications_subpage.html
@@ -23,6 +23,7 @@
       id="appBadgingToggleButton"
       class="hr"
       label="$i18n{appBadgingToggleLabel}"
+      sub-label="$i18n{appBadgingToggleSublabel}"
       pref="{{prefs.ash.app_notification_badging_enabled}}"
       deep-link-focus-id$="[[Setting.kAppBadgingOnOff]]">
   </settings-toggle-button>
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/app_notifications_subpage.ts b/chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/app_notifications_subpage.ts
index fc54ab4..63a3a0be 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/app_notifications_subpage.ts
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/app_notifications_subpage.ts
@@ -18,7 +18,7 @@
 import {App, AppNotificationsHandlerInterface, AppNotificationsObserverReceiver} from '../../../mojom-webui/os_apps_page/app_notification_handler.mojom-webui.js';
 import {SettingChangeValue} from '../../../mojom-webui/search/user_action_recorder.mojom-webui.js';
 import {Setting} from '../../../mojom-webui/setting.mojom-webui.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface} from '../../../router.js';
+import {Route, RouteObserverMixin, RouteObserverMixinInterface} from '../../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../../deep_linking_behavior.js';
 import {recordSettingChange} from '../../metrics_recorder.js';
 import {routes} from '../../os_route.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/os_apps_page.ts b/chrome/browser/resources/settings/chromeos/os_apps_page/os_apps_page.ts
index 860bef0..6a49308 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/os_apps_page.ts
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/os_apps_page.ts
@@ -37,7 +37,7 @@
 import {App as AppWithNotifications, AppNotificationsHandlerInterface, AppNotificationsObserverReceiver, Readiness} from '../../mojom-webui/os_apps_page/app_notification_handler.mojom-webui.js';
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
 import {PrefsMixin, PrefsMixinInterface} from '../../prefs/prefs_mixin.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../../router.js';
+import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {routes} from '../os_route.js';
 
diff --git a/chrome/browser/resources/settings/chromeos/os_bluetooth_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_bluetooth_page/BUILD.gn
index 4d515a9..4f01dde9 100644
--- a/chrome/browser/resources/settings/chromeos/os_bluetooth_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_bluetooth_page/BUILD.gn
@@ -48,7 +48,7 @@
     "..:deep_linking_behavior",
     "..:os_route",
     "..:route_observer_behavior",
-    "../..:router",
+    "..:router",
     "//ash/webui/common/resources:i18n_behavior",
     "//ash/webui/common/resources:web_ui_listener_behavior",
     "//ash/webui/common/resources/bluetooth:bluetooth_metrics_utils",
@@ -69,7 +69,7 @@
     "..:os_route",
     "..:route_observer_behavior",
     "..:route_origin_behavior",
-    "../..:router",
+    "..:router",
     "//ash/webui/common/resources:i18n_behavior",
     "//ash/webui/common/resources/bluetooth:bluetooth_utils",
     "//ash/webui/common/resources/bluetooth:cros_bluetooth_config",
@@ -91,7 +91,7 @@
 js_library("os_paired_bluetooth_list_item") {
   deps = [
     "..:os_route",
-    "../..:router",
+    "..:router",
     "//ash/webui/common/resources:i18n_behavior",
     "//ash/webui/common/resources/bluetooth:bluetooth_device_battery_info",
     "//ash/webui/common/resources/bluetooth:bluetooth_icon",
@@ -118,7 +118,7 @@
     "..:os_route",
     "..:route_observer_behavior",
     "..:route_origin_behavior",
-    "../..:router",
+    "..:router",
     "//ash/webui/common/resources:i18n_behavior",
     "//ash/webui/common/resources/bluetooth:bluetooth_device_battery_info",
     "//ash/webui/common/resources/bluetooth:bluetooth_metrics_utils",
@@ -137,7 +137,7 @@
     "..:os_route",
     "..:route_observer_behavior",
     "..:route_origin_behavior",
-    "../..:router",
+    "..:router",
     "//ash/webui/common/resources:i18n_behavior",
     "//ash/webui/common/resources/bluetooth:bluetooth_device_battery_info",
     "//ash/webui/common/resources/bluetooth:bluetooth_metrics_utils",
diff --git a/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_bluetooth_device_detail_subpage.js b/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_bluetooth_device_detail_subpage.js
index 1890fb30..5a7f284 100644
--- a/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_bluetooth_device_detail_subpage.js
+++ b/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_bluetooth_device_detail_subpage.js
@@ -26,7 +26,7 @@
 import {AudioOutputCapability, BluetoothSystemProperties, DeviceConnectionState, DeviceType, PairedBluetoothDeviceProperties} from 'chrome://resources/mojo/chromeos/ash/services/bluetooth_config/public/mojom/cros_bluetooth_config.mojom-webui.js';
 import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-import {Route, Router} from '../../router.js';
+import {Route, Router} from '../router.js';
 import {routes} from '../os_route.js';
 import {RouteObserverBehavior, RouteObserverBehaviorInterface} from '../route_observer_behavior.js';
 import {RouteOriginBehavior, RouteOriginBehaviorInterface} from '../route_origin_behavior.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_bluetooth_devices_subpage.js b/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_bluetooth_devices_subpage.js
index 06e538f3..867a44c 100644
--- a/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_bluetooth_devices_subpage.js
+++ b/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_bluetooth_devices_subpage.js
@@ -21,7 +21,7 @@
 import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
-import {Route, Router} from '../../router.js';
+import {Route, Router} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {routes} from '../os_route.js';
 import {RouteObserverBehavior, RouteObserverBehaviorInterface} from '../route_observer_behavior.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_bluetooth_saved_devices_subpage.js b/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_bluetooth_saved_devices_subpage.js
index 82ca01a..3c91336 100644
--- a/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_bluetooth_saved_devices_subpage.js
+++ b/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_bluetooth_saved_devices_subpage.js
@@ -17,7 +17,7 @@
 import {WebUIListenerBehavior, WebUIListenerBehaviorInterface} from 'chrome://resources/ash/common/web_ui_listener_behavior.js';
 import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-import {Route, Router} from '../../router.js';
+import {Route, Router} from '../router.js';
 import {routes} from '../os_route.js';
 import {RouteObserverBehavior, RouteObserverBehaviorInterface} from '../route_observer_behavior.js';
 
diff --git a/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_bluetooth_summary.js b/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_bluetooth_summary.js
index b37ff0a58..ea6dd46 100644
--- a/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_bluetooth_summary.js
+++ b/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_bluetooth_summary.js
@@ -20,7 +20,7 @@
 import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {loadTimeData} from '../../i18n_setup.js';
-import {Router} from '../../router.js';
+import {Router} from '../router.js';
 import {routes} from '../os_route.js';
 import {RouteObserverBehavior, RouteObserverBehaviorInterface} from '../route_observer_behavior.js';
 import {RouteOriginBehavior, RouteOriginBehaviorInterface} from '../route_origin_behavior.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_paired_bluetooth_list_item.js b/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_paired_bluetooth_list_item.js
index d0b46b7..04c046f 100644
--- a/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_paired_bluetooth_list_item.js
+++ b/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_paired_bluetooth_list_item.js
@@ -23,7 +23,7 @@
 import {DeviceConnectionState, DeviceType, PairedBluetoothDeviceProperties} from 'chrome://resources/mojo/chromeos/ash/services/bluetooth_config/public/mojom/cros_bluetooth_config.mojom-webui.js';
 import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-import {Router} from '../../router.js';
+import {Router} from '../router.js';
 import {routes} from '../os_route.js';
 
 /**
diff --git a/chrome/browser/resources/settings/chromeos/os_files_page/os_files_page.ts b/chrome/browser/resources/settings/chromeos/os_files_page/os_files_page.ts
index 5f08c060..23c18ca 100644
--- a/chrome/browser/resources/settings/chromeos/os_files_page/os_files_page.ts
+++ b/chrome/browser/resources/settings/chromeos/os_files_page/os_files_page.ts
@@ -16,7 +16,7 @@
 import {mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../../router.js';
+import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {routes} from '../os_route.js';
 
diff --git a/chrome/browser/resources/settings/chromeos/os_files_page/smb_shares_page.ts b/chrome/browser/resources/settings/chromeos/os_files_page/smb_shares_page.ts
index 9202a6e..884bbd43 100644
--- a/chrome/browser/resources/settings/chromeos/os_files_page/smb_shares_page.ts
+++ b/chrome/browser/resources/settings/chromeos/os_files_page/smb_shares_page.ts
@@ -13,7 +13,7 @@
 
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../../router.js';
+import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../router.js';
 import {routes} from '../os_route.js';
 
 import {getTemplate} from './smb_shares_page.html.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_languages_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_languages_page/BUILD.gn
index c149e05..5e257a1 100644
--- a/chrome/browser/resources/settings/chromeos/os_languages_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_languages_page/BUILD.gn
@@ -143,7 +143,7 @@
     ":input_method_types",
     "..:os_route",
     "..:route_observer_behavior",
-    "../..:router",
+    "..:router",
     "//ui/webui/resources/js:cr.m",
   ]
 }
@@ -170,7 +170,7 @@
     "..:deep_linking_behavior",
     "..:os_route",
     "..:route_observer_behavior",
-    "../..:router",
+    "..:router",
     "//ash/webui/common/resources:focus_without_ink_js",
     "//ash/webui/common/resources:i18n_behavior",
     "//ui/webui/resources/js:assert",
@@ -222,7 +222,7 @@
     "..:metrics_recorder",
     "..:os_route",
     "..:route_observer_behavior",
-    "../..:router",
+    "..:router",
     "//ash/webui/common/resources:focus_without_ink_js",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/js:assert",
@@ -241,7 +241,7 @@
     ":languages",
     ":os_languages_page_v2",
     "..:os_route",
-    "../..:router",
+    "..:router",
     "//ash/webui/common/resources:focus_without_ink_js",
     "//ash/webui/common/resources:i18n_behavior",
     "//ui/webui/resources/js:assert",
@@ -256,7 +256,7 @@
     "..:os_route",
     "..:prefs_behavior",
     "..:route_observer_behavior",
-    "../..:router",
+    "..:router",
     "//ui/webui/resources/js:load_time_data.m",
   ]
 }
diff --git a/chrome/browser/resources/settings/chromeos/os_languages_page/input_method_options_page.js b/chrome/browser/resources/settings/chromeos/os_languages_page/input_method_options_page.js
index 666be28..e1448fc 100644
--- a/chrome/browser/resources/settings/chromeos/os_languages_page/input_method_options_page.js
+++ b/chrome/browser/resources/settings/chromeos/os_languages_page/input_method_options_page.js
@@ -18,7 +18,7 @@
 import {afterNextRender, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {loadTimeData} from '../../i18n_setup.js';
-import {Route, Router} from '../../router.js';
+import {Route, Router} from '../router.js';
 import {routes} from '../os_route.js';
 import {PrefsBehavior, PrefsBehaviorInterface} from '../prefs_behavior.js';
 import {RouteObserverBehavior, RouteObserverBehaviorInterface} from '../route_observer_behavior.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_languages_page/input_method_util.js b/chrome/browser/resources/settings/chromeos/os_languages_page/input_method_util.js
index fb96ce8..361a742 100644
--- a/chrome/browser/resources/settings/chromeos/os_languages_page/input_method_util.js
+++ b/chrome/browser/resources/settings/chromeos/os_languages_page/input_method_util.js
@@ -5,7 +5,7 @@
 import {assert, assertNotReached} from 'chrome://resources/js/assert.js';
 
 import {loadTimeData} from '../../i18n_setup.js';
-import {Route} from '../../router.js';
+import {Route} from '../router.js';
 import {routes} from '../os_route.js';
 
 import {getInputMethodSettings, SettingsType} from './input_method_settings.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_languages_page/input_page.js b/chrome/browser/resources/settings/chromeos/os_languages_page/input_page.js
index 389c3fc..95ea1d3b 100644
--- a/chrome/browser/resources/settings/chromeos/os_languages_page/input_page.js
+++ b/chrome/browser/resources/settings/chromeos/os_languages_page/input_page.js
@@ -27,7 +27,7 @@
 
 import {loadTimeData} from '../../i18n_setup.js';
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
-import {Route, Router} from '../../router.js';
+import {Route, Router} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {recordSettingChange} from '../metrics_recorder.js';
 import {routes} from '../os_route.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_languages_page/os_edit_dictionary_page.js b/chrome/browser/resources/settings/chromeos/os_languages_page/os_edit_dictionary_page.js
index 9c2656ec..77440f2 100644
--- a/chrome/browser/resources/settings/chromeos/os_languages_page/os_edit_dictionary_page.js
+++ b/chrome/browser/resources/settings/chromeos/os_languages_page/os_edit_dictionary_page.js
@@ -18,7 +18,7 @@
 import {I18nBehavior, I18nBehaviorInterface} from 'chrome://resources/ash/common/i18n_behavior.js';
 import {mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-import {Route} from '../../router.js';
+import {Route} from '../router.js';
 import {GlobalScrollTargetBehavior, GlobalScrollTargetBehaviorInterface} from '../global_scroll_target_behavior.js';
 import {recordSettingChange} from '../metrics_recorder.js';
 import {routes} from '../os_route.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_languages_page/os_japanese_manage_user_dictionary_page.js b/chrome/browser/resources/settings/chromeos/os_languages_page/os_japanese_manage_user_dictionary_page.js
index 9a52c29..e48219b5 100644
--- a/chrome/browser/resources/settings/chromeos/os_languages_page/os_japanese_manage_user_dictionary_page.js
+++ b/chrome/browser/resources/settings/chromeos/os_languages_page/os_japanese_manage_user_dictionary_page.js
@@ -13,7 +13,7 @@
 import {I18nBehavior, I18nBehaviorInterface} from 'chrome://resources/ash/common/i18n_behavior.js';
 import {mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-import {Route} from '../../router.js';
+import {Route} from '../router.js';
 import {GlobalScrollTargetBehavior, GlobalScrollTargetBehaviorInterface} from '../global_scroll_target_behavior.js';
 import {routes} from '../os_route.js';
 
diff --git a/chrome/browser/resources/settings/chromeos/os_languages_page/os_languages_page_v2.js b/chrome/browser/resources/settings/chromeos/os_languages_page/os_languages_page_v2.js
index 3c4c8d2..041cab2 100644
--- a/chrome/browser/resources/settings/chromeos/os_languages_page/os_languages_page_v2.js
+++ b/chrome/browser/resources/settings/chromeos/os_languages_page/os_languages_page_v2.js
@@ -29,7 +29,7 @@
 
 import {loadTimeData} from '../../i18n_setup.js';
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
-import {Route, Router} from '../../router.js';
+import {Route, Router} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {recordSettingChange} from '../metrics_recorder.js';
 import {routes} from '../os_route.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_languages_page/os_languages_section.js b/chrome/browser/resources/settings/chromeos/os_languages_page/os_languages_section.js
index 0a8f745d..7466e98 100644
--- a/chrome/browser/resources/settings/chromeos/os_languages_page/os_languages_section.js
+++ b/chrome/browser/resources/settings/chromeos/os_languages_page/os_languages_section.js
@@ -25,7 +25,7 @@
 import {mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {loadTimeData} from '../../i18n_setup.js';
-import {Router} from '../../router.js';
+import {Router} from '../router.js';
 import {routes} from '../os_route.js';
 
 import {LanguageHelper, LanguagesModel} from './languages_types.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_languages_page/smart_inputs_page.js b/chrome/browser/resources/settings/chromeos/os_languages_page/smart_inputs_page.js
index 432f04ac8..7628a042 100644
--- a/chrome/browser/resources/settings/chromeos/os_languages_page/smart_inputs_page.js
+++ b/chrome/browser/resources/settings/chromeos/os_languages_page/smart_inputs_page.js
@@ -16,7 +16,7 @@
 
 import {loadTimeData} from '../../i18n_setup.js';
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
-import {Route} from '../../router.js';
+import {Route} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {routes} from '../os_route.js';
 import {PrefsBehavior, PrefsBehaviorInterface} from '../prefs_behavior.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_people_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_people_page/BUILD.gn
index 21faf35..0fe0ce82 100644
--- a/chrome/browser/resources/settings/chromeos/os_people_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_people_page/BUILD.gn
@@ -41,7 +41,7 @@
     "..:metrics_recorder",
     "..:os_route",
     "..:route_observer_behavior",
-    "../..:router",
+    "..:router",
     "//ash/webui/common/resources:i18n_behavior",
     "//ash/webui/common/resources:web_ui_listener_behavior",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
@@ -67,7 +67,7 @@
     "..:metrics_recorder",
     "..:os_route",
     "..:route_observer_behavior",
-    "../..:router",
+    "..:router",
     "//ash/webui/common/resources:focus_without_ink_js",
     "//ash/webui/common/resources:i18n_behavior",
     "//ash/webui/common/resources:web_ui_listener_behavior",
@@ -94,7 +94,7 @@
     "..:deep_linking_behavior",
     "..:os_route",
     "..:route_observer_behavior",
-    "../..:router",
+    "..:router",
     "../multidevice_page:multidevice_smartlock_item",
     "//ash/webui/common/resources:focus_without_ink_js",
     "//ash/webui/common/resources:i18n_behavior",
@@ -118,7 +118,7 @@
 
 js_library("lock_state_behavior") {
   deps = [
-    "../..:router",
+    "..:router",
     "//ash/webui/common/resources:i18n_behavior",
     "//ash/webui/common/resources:web_ui_listener_behavior",
     "//chromeos/ash/services/auth_factor_config/public/mojom:mojom_webui_js",
@@ -140,7 +140,7 @@
     "..:os_page_visibility",
     "..:os_route",
     "..:route_observer_behavior",
-    "../..:router",
+    "..:router",
     "//ash/webui/common/resources:focus_without_ink_js",
     "//ash/webui/common/resources:i18n_behavior",
     "//ash/webui/common/resources:web_ui_listener_behavior",
@@ -168,7 +168,7 @@
     "..:deep_linking_behavior",
     "..:metrics_recorder",
     "..:os_route",
-    "../../:router",
+    "..:router",
     "//ash/webui/common/resources:web_ui_listener_behavior",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/js:assert",
@@ -195,7 +195,7 @@
 js_library("setup_pin_dialog") {
   deps = [
     ":lock_screen_password_prompt_dialog",
-    "../..:router",
+    "..:router",
     "//ash/webui/common/resources:i18n_behavior",
     "//ash/webui/common/resources/quick_unlock:setup_pin_keyboard",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
@@ -206,7 +206,7 @@
   deps = [
     "..:os_route",
     "..:route_observer_behavior",
-    "../..:router",
+    "..:router",
     "//ash/webui/common/resources:cr_scrollable_behavior",
     "//ash/webui/common/resources:i18n_behavior",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
@@ -238,7 +238,7 @@
     "..:deep_linking_behavior",
     "..:os_route",
     "..:route_observer_behavior",
-    "../..:router",
+    "..:router",
     "//ash/webui/common/resources:focus_without_ink_js",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/js:assert",
diff --git a/chrome/browser/resources/settings/chromeos/os_people_page/account_manager.js b/chrome/browser/resources/settings/chromeos/os_people_page/account_manager.js
index 5443be0..06fddf0 100644
--- a/chrome/browser/resources/settings/chromeos/os_people_page/account_manager.js
+++ b/chrome/browser/resources/settings/chromeos/os_people_page/account_manager.js
@@ -23,7 +23,7 @@
 
 import {loadTimeData} from '../../i18n_setup.js';
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
-import {Route} from '../../router.js';
+import {Route} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {getImage} from '../icon.js';
 import {recordSettingChange} from '../metrics_recorder.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_people_page/fingerprint_list.js b/chrome/browser/resources/settings/chromeos/os_people_page/fingerprint_list.js
index b0da48a..1e733ef 100644
--- a/chrome/browser/resources/settings/chromeos/os_people_page/fingerprint_list.js
+++ b/chrome/browser/resources/settings/chromeos/os_people_page/fingerprint_list.js
@@ -21,7 +21,7 @@
 import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
-import {Route, Router} from '../../router.js';
+import {Route, Router} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {recordSettingChange} from '../metrics_recorder.js';
 import {routes} from '../os_route.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_people_page/lock_screen.js b/chrome/browser/resources/settings/chromeos/os_people_page/lock_screen.js
index 411ae83..b31a2374 100644
--- a/chrome/browser/resources/settings/chromeos/os_people_page/lock_screen.js
+++ b/chrome/browser/resources/settings/chromeos/os_people_page/lock_screen.js
@@ -38,7 +38,7 @@
 
 import {loadTimeData} from '../../i18n_setup.js';
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
-import {Route, Router} from '../../router.js';
+import {Route, Router} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {routes} from '../os_route.js';
 import {RouteObserverBehavior, RouteObserverBehaviorInterface} from '../route_observer_behavior.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_people_page/os_people_page.html b/chrome/browser/resources/settings/chromeos/os_people_page/os_people_page.html
index cec67fe..5329dd3 100644
--- a/chrome/browser/resources/settings/chromeos/os_people_page/os_people_page.html
+++ b/chrome/browser/resources/settings/chromeos/os_people_page/os_people_page.html
@@ -86,11 +86,11 @@
     <os-settings-subpage
         page-title="$i18n{syncPageTitle}"
         learn-more-url="$i18n{syncAndGoogleServicesLearnMoreURL}">
-      <settings-sync-page
+      <os-settings-sync-page
           sync-status="[[syncStatus]]" prefs="{{prefs}}"
           page-visibility="[[pageVisibility.privacy]]"
           focus-config="[[focusConfig_]]">
-      </settings-sync-page>
+      </os-settings-sync-page>
     </os-settings-subpage>
   </template>
   <template is="dom-if" route-path="/osSync">
@@ -108,7 +108,7 @@
   </template>
 </os-settings-animated-pages>
 <template is="dom-if" if="[[showSignoutDialog_]]" restamp>
-  <settings-signout-dialog sync-status="[[syncStatus]]"
+  <os-settings-signout-dialog sync-status="[[syncStatus]]"
       on-close="onDisconnectDialogClosed_">
-  </settings-signout-dialog>
+  </os-settings-signout-dialog>
 </template>
diff --git a/chrome/browser/resources/settings/chromeos/os_people_page/os_people_page.js b/chrome/browser/resources/settings/chromeos/os_people_page/os_people_page.js
index 2f0a8c40..729d1b0 100644
--- a/chrome/browser/resources/settings/chromeos/os_people_page/os_people_page.js
+++ b/chrome/browser/resources/settings/chromeos/os_people_page/os_people_page.js
@@ -13,9 +13,6 @@
 import 'chrome://resources/cr_elements/cr_shared_vars.css.js';
 import 'chrome://resources/polymer/v3_0/iron-flex-layout/iron-flex-layout-classes.js';
 import '../../controls/settings_toggle_button.js';
-import '../../people_page/signout_dialog.js';
-import '../../people_page/sync_controls.js';
-import '../../people_page/sync_page.js';
 import '../os_settings_page/os_settings_animated_pages.js';
 import '../os_settings_page/os_settings_subpage.js';
 import '../../settings_shared.css.js';
@@ -26,6 +23,8 @@
 import './lock_screen_password_prompt_dialog.js';
 import './users_page.js';
 import './os_sync_controls.js';
+import './os_signout_dialog.js';
+import './os_sync_page.js';
 
 import {convertImageSequenceToPng} from 'chrome://resources/ash/common/cr_picture/png.js';
 import {focusWithoutInk} from 'chrome://resources/ash/common/focus_without_ink_js.js';
@@ -39,7 +38,7 @@
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
 import {ProfileInfoBrowserProxyImpl} from '../../people_page/profile_info_browser_proxy.js';
 import {SyncBrowserProxyImpl} from '../../people_page/sync_browser_proxy.js';
-import {Route, Router} from '../../router.js';
+import {Route, Router} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {getImage} from '../icon.js';
 import {OSPageVisibility} from '../os_page_visibility.js';
@@ -334,8 +333,8 @@
       // component.
       case Setting.kNonSplitSyncEncryptionOptions:
         this.afterRenderShowDeepLink_(settingId, () => {
-          const syncPage = /** @type {?SettingsSyncPageElement} */ (
-              this.shadowRoot.querySelector('settings-sync-page'));
+          const syncPage = /** @type {?OsSettingsSyncPageElement} */ (
+              this.shadowRoot.querySelector('os-settings-sync-page'));
           // Expand the encryption collapse.
           syncPage.forceEncryptionExpanded = true;
           flush();
@@ -346,8 +345,8 @@
 
       case Setting.kAutocompleteSearchesAndUrls:
         this.afterRenderShowDeepLink_(settingId, () => {
-          const syncPage = /** @type {?SettingsSyncPageElement} */ (
-              this.shadowRoot.querySelector('settings-sync-page'));
+          const syncPage = /** @type {?OsSettingsSyncPageElement} */ (
+              this.shadowRoot.querySelector('os-settings-sync-page'));
           return syncPage && syncPage.getPersonalizationOptions() &&
               syncPage.getPersonalizationOptions().getSearchSuggestToggle();
         });
@@ -355,8 +354,8 @@
 
       case Setting.kMakeSearchesAndBrowsingBetter:
         this.afterRenderShowDeepLink_(settingId, () => {
-          const syncPage = /** @type {?SettingsSyncPageElement} */ (
-              this.shadowRoot.querySelector('settings-sync-page'));
+          const syncPage = /** @type {?OsSettingsSyncPageElement} */ (
+              this.shadowRoot.querySelector('os-settings-sync-page'));
           return syncPage && syncPage.getPersonalizationOptions() &&
               syncPage.getPersonalizationOptions().getUrlCollectionToggle();
         });
@@ -364,8 +363,8 @@
 
       case Setting.kGoogleDriveSearchSuggestions:
         this.afterRenderShowDeepLink_(settingId, () => {
-          const syncPage = /** @type {?SettingsSyncPageElement} */ (
-              this.shadowRoot.querySelector('settings-sync-page'));
+          const syncPage = /** @type {?OsSettingsSyncPageElement} */ (
+              this.shadowRoot.querySelector('os-settings-sync-page'));
           return syncPage && syncPage.getPersonalizationOptions() &&
               syncPage.getPersonalizationOptions().getDriveSuggestToggle();
         });
diff --git a/chrome/browser/resources/settings/chromeos/os_people_page/os_personalization_options.html b/chrome/browser/resources/settings/chromeos/os_people_page/os_personalization_options.html
new file mode 100644
index 0000000..2334684
--- /dev/null
+++ b/chrome/browser/resources/settings/chromeos/os_people_page/os_personalization_options.html
@@ -0,0 +1,24 @@
+<style include="settings-shared">
+  :host(.list-frame) settings-toggle-button {
+    padding-inline-end: 0;
+    padding-inline-start: 0;
+  }
+
+  :host(.list-frame) settings-toggle-button:first-of-type {
+    border-top: none;
+  }
+</style>
+<!-- On ChromeOS OS settings show the toggle for metrics reporting. -->
+<settings-toggle-button class="hr"
+    pref="{{prefs.cros.metrics.reportingEnabled}}"
+    label="$i18n{enablePersonalizationLogging}"
+    sub-label="$i18n{enablePersonalizationLoggingDesc}">
+</settings-toggle-button>
+<settings-toggle-button id="spellCheckControl"
+    class="hr"
+    pref="{{prefs.spellcheck.use_spelling_service}}"
+    on-settings-boolean-control-change="onUseSpellingServiceToggle_"
+    label="$i18n{spellingPref}"
+    sub-label="$i18n{spellingDescription}"
+    hidden="[[!showSpellCheckControlToggle_(prefs.spellcheck.dictionaries)]]">
+</settings-toggle-button>
diff --git a/chrome/browser/resources/settings/chromeos/os_people_page/os_personalization_options.ts b/chrome/browser/resources/settings/chromeos/os_people_page/os_personalization_options.ts
new file mode 100644
index 0000000..4d23853
--- /dev/null
+++ b/chrome/browser/resources/settings/chromeos/os_people_page/os_personalization_options.ts
@@ -0,0 +1,68 @@
+// Copyright 2018 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview
+ * 'personalization-options' contains several toggles related to
+ * personalizations.
+ */
+import '../../controls/settings_toggle_button.js';
+import '../../prefs/prefs.js';
+import '../../settings_shared.css.js';
+
+import {PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {SettingsToggleButtonElement} from '../../controls/settings_toggle_button.js';
+import {PrefsMixin} from '../../prefs/prefs_mixin.js';
+
+import {getTemplate} from './os_personalization_options.html.js';
+
+const OsSettingsPersonalizationOptionsElementBase = PrefsMixin(PolymerElement);
+
+export class OsSettingsPersonalizationOptionsElement extends
+    OsSettingsPersonalizationOptionsElementBase {
+  static get is() {
+    return 'os-settings-personalization-options';
+  }
+
+  static get template() {
+    return getTemplate();
+  }
+
+  static get properties() {
+    return {
+      prefs: {
+        type: Object,
+        notify: true,
+      },
+    };
+  }
+
+  // <if expr="_google_chrome">
+  private onUseSpellingServiceToggle_(event: Event) {
+    // If turning on using the spelling service, automatically turn on
+    // spellcheck so that the spelling service can run.
+    if ((event.target as SettingsToggleButtonElement).checked) {
+      this.setPrefValue('browser.enable_spellchecking', true);
+    }
+  }
+
+  private showSpellCheckControlToggle_(): boolean {
+    return (
+        !!(this.prefs as {spellcheck?: any}).spellcheck &&
+        (this.getPref('spellcheck.dictionaries').value as string[]).length > 0);
+  }
+
+  // </if><!-- _google_chrome -->
+}
+
+declare global {
+  interface HTMLElementTagNameMap {
+    'os-settings-personalization-options': OsSettingsPersonalizationOptionsElement;
+  }
+}
+
+customElements.define(
+    OsSettingsPersonalizationOptionsElement.is,
+    OsSettingsPersonalizationOptionsElement);
diff --git a/chrome/browser/resources/settings/chromeos/os_people_page/os_signout_dialog.html b/chrome/browser/resources/settings/chromeos/os_people_page/os_signout_dialog.html
new file mode 100644
index 0000000..7b5c7bf
--- /dev/null
+++ b/chrome/browser/resources/settings/chromeos/os_people_page/os_signout_dialog.html
@@ -0,0 +1,43 @@
+<style include="cr-shared-style settings-shared iron-flex">
+  .delete-profile-warning {
+    padding-bottom: 10px;
+    padding-inline-end: var(--cr-section-padding);
+    /* In order to line up with the checkbox text. */
+    padding-inline-start: calc(var(--cr-section-padding) + 32px);
+    padding-top: 10px;
+  }
+
+  #wideFooter {
+    /* Override the cr-dialog footer padding. */
+    padding: 0;
+  }
+
+  #dialog-body {
+    /* Add space for the link focus ring. See https://crbug.com/916939. */
+    padding-bottom: 2px;
+  }
+</style>
+
+<cr-dialog id="dialog" ignore-enter-key close-text="$i18n{close}">
+  <div slot="title">$i18n{syncDisconnectTitle}</div>
+  <div id="dialog-body" slot="body">
+    <div inner-h-t-m-l="[[
+        getDisconnectExplanationHtml_(syncStatus.domain)]]">
+    </div>
+  </div>
+  <div slot="button-container">
+    <cr-button id="disconnectCancel" class="cancel-button"
+        on-click="onDisconnectCancel_">
+      $i18n{cancel}
+    </cr-button>
+    <cr-button id="disconnectConfirm" class="action-button"
+        hidden="[[syncStatus.domain]]" on-click="onDisconnectConfirm_">
+      $i18n{syncDisconnect}
+    </cr-button>
+    <cr-button id="disconnectManagedProfileConfirm"
+        class="action-button" hidden="[[!syncStatus.domain]]"
+        on-click="onDisconnectConfirm_">
+      $i18n{syncDisconnectConfirm}
+    </cr-button>
+  </div>
+</cr-dialog>
diff --git a/chrome/browser/resources/settings/chromeos/os_people_page/os_signout_dialog.ts b/chrome/browser/resources/settings/chromeos/os_people_page/os_signout_dialog.ts
new file mode 100644
index 0000000..7abd79c
--- /dev/null
+++ b/chrome/browser/resources/settings/chromeos/os_people_page/os_signout_dialog.ts
@@ -0,0 +1,149 @@
+// Copyright 2018 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview 'settings-signout-dialog' is a dialog that allows the
+ * user to turn off sync and sign out of Chromium.
+ */
+import '//resources/cr_elements/cr_button/cr_button.js';
+import '//resources/cr_elements/cr_dialog/cr_dialog.js';
+import '//resources/cr_elements/cr_shared_style.css.js';
+import '//resources/cr_elements/cr_shared_vars.css.js';
+import '//resources/polymer/v3_0/iron-flex-layout/iron-flex-layout-classes.js';
+import '../../settings_shared.css.js';
+
+import {CrDialogElement} from '//resources/cr_elements/cr_dialog/cr_dialog.js';
+import {WebUiListenerMixin} from '//resources/cr_elements/web_ui_listener_mixin.js';
+import {sanitizeInnerHtml} from '//resources/js/parse_html_subset.js';
+import {microTask, PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {loadTimeData} from '../../i18n_setup.js';
+
+import {getTemplate} from './os_signout_dialog.html.js';
+import {SyncBrowserProxyImpl, SyncStatus} from '../../people_page/sync_browser_proxy.js';
+
+export interface OsSettingsSignoutDialogElement {
+  $: {
+    dialog: CrDialogElement,
+    disconnectConfirm: HTMLElement,
+  };
+}
+
+const OsSettingsSignoutDialogElementBase = WebUiListenerMixin(PolymerElement);
+
+export class OsSettingsSignoutDialogElement extends
+    OsSettingsSignoutDialogElementBase {
+  static get is() {
+    return 'os-settings-signout-dialog';
+  }
+
+  static get template() {
+    return getTemplate();
+  }
+
+  static get properties() {
+    return {
+      /**
+       * The current sync status, supplied by the parent.
+       */
+      syncStatus: {
+        type: Object,
+        observer: 'syncStatusChanged_',
+      },
+
+      /**
+       * True if the checkbox to delete the profile has been checked.
+       */
+      deleteProfile_: Boolean,
+
+      /**
+       * True if the profile deletion warning is visible.
+       */
+      deleteProfileWarningVisible_: Boolean,
+
+      /**
+       * The profile deletion warning. The message indicates the number of
+       * profile stats that will be deleted if a non-zero count for the profile
+       * stats is returned from the browser.
+       */
+      deleteProfileWarning_: String,
+    };
+  }
+
+  syncStatus: SyncStatus|null;
+  private deleteProfile_: boolean;
+  private deleteProfileWarningVisible_: boolean;
+  private deleteProfileWarning_: string;
+
+  override connectedCallback() {
+    super.connectedCallback();
+
+    this.addWebUiListener(
+        'profile-stats-count-ready', this.handleProfileStatsCount_.bind(this));
+    microTask.run(() => {
+      this.$.dialog.showModal();
+    });
+  }
+
+  /**
+   * @return true when the user selected 'Confirm'.
+   */
+  wasConfirmed(): boolean {
+    return this.$.dialog.getNative().returnValue === 'success';
+  }
+
+  /**
+   * Handler for when the profile stats count is pushed from the browser.
+   */
+  private handleProfileStatsCount_(count: number) {
+    const username = this.syncStatus!.signedInUsername || '';
+    if (count === 0) {
+      this.deleteProfileWarning_ = loadTimeData.getStringF(
+          'deleteProfileWarningWithoutCounts', username);
+    } else if (count === 1) {
+      this.deleteProfileWarning_ = loadTimeData.getStringF(
+          'deleteProfileWarningWithCountsSingular', username);
+    } else {
+      this.deleteProfileWarning_ = loadTimeData.getStringF(
+          'deleteProfileWarningWithCountsPlural', count, username);
+    }
+  }
+
+  /**
+   * Polymer observer for syncStatus.
+   */
+  private syncStatusChanged_() {
+    if (!this.syncStatus!.signedIn && this.$.dialog.open) {
+      this.$.dialog.close();
+    }
+  }
+
+  private getDisconnectExplanationHtml_(_domain: string): TrustedHTML {
+    return sanitizeInnerHtml(
+        loadTimeData.getString('syncDisconnectExplanation'));
+  }
+
+  private onDisconnectCancel_() {
+    this.$.dialog.cancel();
+  }
+
+  private onDisconnectConfirm_() {
+    this.$.dialog.close();
+    // Chrome OS users are always signed-in, so just turn off sync.
+    SyncBrowserProxyImpl.getInstance().turnOffSync();
+  }
+
+  private isDeleteProfileFooterVisible_(): boolean {
+    return !this.syncStatus!.domain;
+  }
+}
+
+declare global {
+  interface HTMLElementTagNameMap {
+    'os-settings-signout-dialog': OsSettingsSignoutDialogElement;
+  }
+}
+
+customElements.define(
+    OsSettingsSignoutDialogElement.is, OsSettingsSignoutDialogElement);
diff --git a/chrome/browser/resources/settings/chromeos/os_people_page/os_sync_controls.js b/chrome/browser/resources/settings/chromeos/os_people_page/os_sync_controls.js
index e51a38c..a688e967 100644
--- a/chrome/browser/resources/settings/chromeos/os_people_page/os_sync_controls.js
+++ b/chrome/browser/resources/settings/chromeos/os_people_page/os_sync_controls.js
@@ -15,7 +15,7 @@
 import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
-import {Route} from '../../router.js';
+import {Route} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {routes} from '../os_route.js';
 import {RouteObserverBehavior, RouteObserverBehaviorInterface} from '../route_observer_behavior.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_people_page/os_sync_encryption_options.html b/chrome/browser/resources/settings/chromeos/os_people_page/os_sync_encryption_options.html
new file mode 100644
index 0000000..ef3f165
--- /dev/null
+++ b/chrome/browser/resources/settings/chromeos/os_people_page/os_sync_encryption_options.html
@@ -0,0 +1,71 @@
+<style include="cr-shared-style settings-shared">
+  #create-password-box {
+    margin-bottom: 1em;
+  }
+
+  #create-password-box .list-item {
+    margin-bottom: var(--cr-form-field-bottom-spacing);
+  }
+
+  cr-input {
+    --cr-input-width: var(--settings-input-max-width);
+  }
+
+  .passphrase-reset-icon {
+    margin-inline-end: 8px;
+  }
+
+  cr-radio-button[name='encrypt-with-passphrase'] {
+    align-items: start;
+  }
+</style>
+
+<template is="dom-if" if="[[!syncPrefs.passphraseRequired]]">
+  <div id="encryptionRadioGroupContainer" class="list-frame">
+    <cr-radio-group
+        id="encryptionRadioGroup"
+        selected="[[selectedEncryptionRadio_(syncPrefs)]]"
+        on-selected-changed="onEncryptionRadioSelectionChanged_"
+        disabled$="[[disableEncryptionOptions_]]">
+      <cr-radio-button name="encrypt-with-google" class="list-item"
+          aria-label="$i18n{encryptWithGoogleCredentialsLabel}">
+        $i18n{encryptWithGoogleCredentialsLabel}
+      </cr-radio-button>
+      <cr-radio-button name="encrypt-with-passphrase"
+          class="list-item">
+        <span hidden="[[!existingPassphraseLabel]]">
+          [[existingPassphraseLabel]]
+        </span>
+        <span on-click="onLearnMoreClick_"
+            hidden="[[existingPassphraseLabel]]">
+          $i18nRaw{encryptWithSyncPassphraseLabel}
+        </span>
+        <template is="dom-if" if="[[creatingNewPassphrase_]]" restamp>
+          <div id="create-password-box">
+            <div class="list-item">
+              <span>$i18nRaw{passphraseExplanationText}</span>
+            </div>
+            <cr-input id="passphraseInput" type="password"
+                value="{{passphrase_}}"
+                placeholder="$i18n{passphrasePlaceholder}"
+                error-message="$i18n{emptyPassphraseError}"
+                on-keypress="onNewPassphraseInputKeypress_">
+            </cr-input>
+            <cr-input id="passphraseConfirmationInput" type="password"
+                value="{{confirmation_}}"
+                placeholder="$i18n{passphraseConfirmationPlaceholder}"
+                error-message="$i18n{mismatchedPassphraseError}"
+                on-keypress="onNewPassphraseInputKeypress_">
+            </cr-input>
+            <cr-button id="saveNewPassphrase"
+                on-click="onSaveNewPassphraseClick_" class="action-button"
+                disabled="[[!isSaveNewPassphraseEnabled_(
+                              passphrase_, confirmation_)]]">
+              $i18n{save}
+            </cr-button>
+          </div>
+        </template>
+      </cr-radio-button>
+    </cr-radio-group>
+  </div>
+</template>
diff --git a/chrome/browser/resources/settings/chromeos/os_people_page/os_sync_encryption_options.ts b/chrome/browser/resources/settings/chromeos/os_people_page/os_sync_encryption_options.ts
new file mode 100644
index 0000000..46f8ed6
--- /dev/null
+++ b/chrome/browser/resources/settings/chromeos/os_people_page/os_sync_encryption_options.ts
@@ -0,0 +1,245 @@
+// 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 '//resources/cr_elements/cr_button/cr_button.js';
+import '//resources/cr_elements/cr_input/cr_input.js';
+import '//resources/cr_elements/cr_radio_button/cr_radio_button.js';
+import '//resources/cr_elements/cr_radio_group/cr_radio_group.js';
+import '//resources/cr_elements/cr_shared_style.css.js';
+import '../../settings_shared.css.js';
+import '../../settings_vars.css.js';
+
+import {CrInputElement} from '//resources/cr_elements/cr_input/cr_input.js';
+import {CrRadioGroupElement} from '//resources/cr_elements/cr_radio_group/cr_radio_group.js';
+
+import {assert} from '//resources/js/assert_ts.js';
+import {PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {SyncBrowserProxyImpl, SyncPrefs, SyncStatus} from '../../people_page/sync_browser_proxy.js';
+import {getTemplate} from './os_sync_encryption_options.html.js';
+
+/**
+ * Names of the radio buttons which allow the user to choose their encryption
+ * mechanism.
+ */
+enum RadioButtonNames {
+  ENCRYPT_WITH_GOOGLE = 'encrypt-with-google',
+  ENCRYPT_WITH_PASSPHRASE = 'encrypt-with-passphrase',
+}
+
+export class OsSettingsSyncEncryptionOptionsElement extends PolymerElement {
+  static get is() {
+    return 'os-settings-sync-encryption-options';
+  }
+
+  static get template() {
+    return getTemplate();
+  }
+
+  static get properties() {
+    return {
+      syncPrefs: {
+        type: Object,
+        notify: true,
+      },
+
+      syncStatus: Object,
+
+      existingPassphraseLabel: {
+        type: String,
+      },
+
+      /**
+       * Whether the "create passphrase" inputs should be shown. These inputs
+       * give the user the opportunity to use a custom passphrase instead of
+       * authenticating with their Google credentials.
+       */
+      creatingNewPassphrase_: {
+        type: Boolean,
+        value: false,
+      },
+
+      /**
+       * The passphrase input field value.
+       */
+      passphrase_: {
+        type: String,
+        value: '',
+      },
+
+      /**
+       * The passphrase confirmation input field value.
+       */
+      confirmation_: {
+        type: String,
+        value: '',
+      },
+
+      disableEncryptionOptions_: {
+        type: Boolean,
+        computed: 'computeDisableEncryptionOptions_(' +
+            'syncPrefs, syncStatus)',
+        observer: 'disableEncryptionOptionsChanged_',
+      },
+    };
+  }
+
+  syncPrefs: SyncPrefs|null;
+  syncStatus: SyncStatus|null;
+  existingPassphraseLabel: string;
+  private creatingNewPassphrase_: boolean;
+  private passphrase_: string;
+  private confirmation_: string;
+  private disableEncryptionOptions_: boolean;
+  private isSettingEncryptionPassphrase_: boolean;
+
+  constructor() {
+    super();
+
+    /**
+     * Whether there's a setEncryptionPassphrase() call pending response, in
+     * which case the component should wait before making a new call.
+     */
+    this.isSettingEncryptionPassphrase_ = false;
+  }
+
+  /**
+   * Returns the encryption options CrRadioGroupElement.
+   */
+  getEncryptionsRadioButtons(): CrRadioGroupElement|null {
+    return this.shadowRoot!.querySelector('cr-radio-group');
+  }
+
+  /**
+   * Whether we should disable the radio buttons that allow choosing the
+   * encryption options for Sync.
+   * We disable the buttons if:
+   * (a) full data encryption is enabled, or,
+   * (b) full data encryption is not allowed (so far, only applies to
+   * supervised accounts), or,
+   * (c) current encryption keys are missing, or,
+   * (d) the user is a supervised account.
+   */
+  private computeDisableEncryptionOptions_(): boolean {
+    return !!(
+        (this.syncPrefs &&
+         (this.syncPrefs.encryptAllData ||
+          !this.syncPrefs.customPassphraseAllowed ||
+          this.syncPrefs.trustedVaultKeysRequired)) ||
+        (this.syncStatus && this.syncStatus.supervisedUser));
+  }
+
+  private disableEncryptionOptionsChanged_() {
+    if (this.disableEncryptionOptions_) {
+      this.creatingNewPassphrase_ = false;
+    }
+  }
+
+  /**
+   * @param passphrase The passphrase input field value
+   * @param confirmation The passphrase confirmation input field value.
+   * @return Whether the passphrase save button should be enabled.
+   */
+  private isSaveNewPassphraseEnabled_(passphrase: string, confirmation: string):
+      boolean {
+    return passphrase !== '' && confirmation !== '';
+  }
+
+  private onNewPassphraseInputKeypress_(e: KeyboardEvent) {
+    if (e.type === 'keypress' && e.key !== 'Enter') {
+      return;
+    }
+    this.saveNewPassphrase_();
+  }
+
+  private onSaveNewPassphraseClick_() {
+    this.saveNewPassphrase_();
+  }
+
+  /**
+   * Sends the newly created custom sync passphrase to the browser.
+   */
+  private saveNewPassphrase_() {
+    assert(this.creatingNewPassphrase_);
+    chrome.metricsPrivate.recordUserAction('Sync_SaveNewPassphraseClicked');
+
+    if (this.isSettingEncryptionPassphrase_) {
+      return;
+    }
+
+    // If a new password has been entered but it is invalid, do not send the
+    // sync state to the API.
+    if (!this.validateCreatedPassphrases_()) {
+      return;
+    }
+
+    this.isSettingEncryptionPassphrase_ = true;
+    SyncBrowserProxyImpl.getInstance()
+        .setEncryptionPassphrase(this.passphrase_)
+        .then(successfullySet => {
+          // TODO(crbug.com/1139060): Rename the event, there is no change if
+          // |successfullySet| is false. It should also mention 'encryption
+          // passphrase' in its name.
+          this.dispatchEvent(new CustomEvent('passphrase-changed', {
+            bubbles: true,
+            composed: true,
+            detail: {didChange: successfullySet},
+          }));
+          this.isSettingEncryptionPassphrase_ = false;
+        });
+  }
+
+  private onEncryptionRadioSelectionChanged_(event:
+                                                 CustomEvent<{value: string}>) {
+    this.creatingNewPassphrase_ =
+        event.detail.value === RadioButtonNames.ENCRYPT_WITH_PASSPHRASE;
+  }
+
+  /**
+   * Computed binding returning the selected encryption radio button.
+   */
+  private selectedEncryptionRadio_() {
+    return this.syncPrefs!.encryptAllData || this.creatingNewPassphrase_ ?
+        RadioButtonNames.ENCRYPT_WITH_PASSPHRASE :
+        RadioButtonNames.ENCRYPT_WITH_GOOGLE;
+  }
+
+  /**
+   * Checks the supplied passphrases to ensure that they are not empty and that
+   * they match each other. Additionally, displays error UI if they are invalid.
+   * @return Whether the check was successful (i.e., that the passphrases were
+   *     valid).
+   */
+  private validateCreatedPassphrases_(): boolean {
+    const emptyPassphrase = !this.passphrase_;
+    const mismatchedPassphrase = this.passphrase_ !== this.confirmation_;
+
+    this.shadowRoot!.querySelector<CrInputElement>(
+                        '#passphraseInput')!.invalid = emptyPassphrase;
+    this.shadowRoot!
+        .querySelector<CrInputElement>(
+            '#passphraseConfirmationInput')!.invalid =
+        !emptyPassphrase && mismatchedPassphrase;
+
+    return !emptyPassphrase && !mismatchedPassphrase;
+  }
+
+  private onLearnMoreClick_(event: Event) {
+    if ((event.target as HTMLElement).tagName === 'A') {
+      // Stop the propagation of events, so that clicking on links inside
+      // checkboxes or radio buttons won't change the value.
+      event.stopPropagation();
+    }
+  }
+}
+
+declare global {
+  interface HTMLElementTagNameMap {
+    'os-settings-sync-encryption-options': OsSettingsSyncEncryptionOptionsElement;
+  }
+}
+
+customElements.define(
+    OsSettingsSyncEncryptionOptionsElement.is,
+    OsSettingsSyncEncryptionOptionsElement);
diff --git a/chrome/browser/resources/settings/chromeos/os_people_page/os_sync_page.html b/chrome/browser/resources/settings/chromeos/os_people_page/os_sync_page.html
new file mode 100644
index 0000000..983669fd
--- /dev/null
+++ b/chrome/browser/resources/settings/chromeos/os_people_page/os_sync_page.html
@@ -0,0 +1,175 @@
+<style include="cr-shared-style settings-shared iron-flex">
+  #sync-separator {
+    border-bottom: var(--cr-separator-line);
+  }
+
+  #create-password-box {
+    /* In order to line up with the encryption radio box text. */
+    margin-inline-start: var(--cr-section-indent-width);
+  }
+
+  #create-password-box {
+    margin-bottom: 1em;
+  }
+
+  #create-password-box .list-item {
+    margin-bottom: var(--cr-form-field-bottom-spacing);
+  }
+
+  cr-input {
+    --cr-input-width: var(--settings-input-max-width);
+  }
+
+  #existingPassphrase {
+    border-bottom: var(--cr-separator-line);
+    border-top: var(--cr-separator-line);
+    /* This particular list frame is not indented. */
+    padding-inline-start: var(--cr-section-padding);
+  }
+
+  #submitExistingPassphrase {
+    /* The submit button for the existing passphrase is on the same line. */
+    margin-inline-start: 16px;
+  }
+
+  #passphraseRecoverHint {
+    align-items: center;
+  }
+
+  #other-sync-items {
+    padding-bottom: 8px;
+  }
+
+  .passphrase-reset-icon {
+    margin-inline-end: 8px;
+  }
+
+  #disabled-by-admin-icon {
+    text-align: center;
+    width: 40px;
+  }
+
+  #toast {
+    left: 0;
+    z-index: 1;
+  }
+
+  :host-context([dir='rtl']) #toast {
+    left: auto;
+    right: 0;
+  }
+
+  cr-link-row {
+    padding-inline-end: 0;
+    padding-inline-start: 0;
+  }
+</style>
+
+<div class="cr-row first" hidden="[[!syncDisabledByAdmin_]]">
+  <iron-icon id="disabled-by-admin-icon" icon="cr20:domain"></iron-icon>
+  <div class="flex cr-padded-text">
+    $i18n{syncDisabledByAdministrator}
+  </div>
+</div>
+
+<!-- TODO(crbug.com/1231500): Remove this warning after Lacros side-by-side
+  rollout stage. -->
+<template is="dom-if" if="[[shouldShowLacrosSideBySideWarning_()]]">
+  <div class="cr-row first cr-padded-text">
+    $i18n{syncSettingsLacrosSideBySideWarning}
+  </div>
+</template>
+
+<template is="dom-if" if="[[showExistingPassphraseBelowAccount_]]"
+    on-dom-change="focusPassphraseInput_">
+  <div id="existingPassphrase" class="list-frame">
+    <div id="existingPassphraseTitle" class="list-item">
+        <div class="start cr-padded-text">
+          <div>$i18n{existingPassphraseTitle}</div>
+          <div id="enterPassphraseLabel" class="secondary"
+              inner-h-t-m-l="[[enterPassphraseLabel_]]">
+          </div>
+        </div>
+    </div>
+    <div id="existingPassphraseContainer" class="list-item">
+      <cr-input id="existingPassphraseInput" type="password"
+          value="{{existingPassphrase_}}"
+          placeholder="$i18n{passphrasePlaceholder}"
+          error-message="$i18n{incorrectPassphraseError}"
+          on-keypress="onSubmitExistingPassphraseTap_">
+        <cr-button id="submitExistingPassphrase" slot="suffix"
+            on-click="onSubmitExistingPassphraseTap_"
+            class="action-button" disabled="[[!existingPassphrase_]]">
+          $i18n{submitPassphraseButton}
+        </cr-button>
+      </cr-input>
+    </div>
+    <div id="passphraseRecoverHint" class="list-item">
+      <div class="cr-padded-text">$i18nRaw{passphraseRecover}</div>
+    </div>
+  </div>
+</template>
+
+<div id="sync-separator" hidden="[[!syncSectionDisabled_]]"></div>
+
+<div id="sync-section" hidden="[[syncSectionDisabled_]]">
+  <div class="cr-row first">
+    <h2 class="cr-title-text">$i18n{sync}</h2>
+  </div>
+
+  <div id="[[pageStatusEnum_.SPINNER]]" class="cr-row first cr-padded-text"
+      hidden$="[[!isStatus_(pageStatusEnum_.SPINNER, pageStatus_)]]">
+    $i18n{syncLoading}
+  </div>
+  <div id="[[pageStatusEnum_.CONFIGURE]]"
+      hidden$="[[!isStatus_(pageStatusEnum_.CONFIGURE, pageStatus_)]]">
+    <div id="other-sync-items" class="list-frame">
+      <cr-link-row id="sync-advanced-row"
+          label="$i18n{syncAdvancedPageTitle}"
+          role-description="$i18n{subpageArrowRoleDescription}"
+          on-click="onSyncAdvancedClick_"></cr-link-row>
+
+      <cr-link-row id="syncDashboardLink" class="hr"
+          label="$i18n{manageSyncedDataTitle}"
+          on-click="onSyncDashboardLinkClick_"
+          hidden="[[syncStatus.supervisedUser]]" external></cr-link-row>
+
+      <cr-expand-button id="encryptionDescription"
+          hidden="[[syncPrefs.passphraseRequired]]"
+          expanded="{{encryptionExpanded_}}"
+          class="hr">
+        $i18n{encryptionOptionsTitle}
+        <div class="secondary">
+          $i18n{syncDataEncryptedText}
+          <div on-click="onResetSyncClick_"
+              hidden="[[!syncPrefs.encryptAllData]]">
+            <iron-icon icon="cr:info-outline"
+                class="passphrase-reset-icon">
+            </iron-icon>
+            $i18nRaw{passphraseResetHintEncryption}
+          </div>
+        </div>
+      </cr-expand-button>
+
+      <iron-collapse id="encryptionCollapse"
+          opened="[[encryptionExpanded_]]">
+        <os-settings-sync-encryption-options
+            sync-status="[[syncStatus]]" sync-prefs="{{syncPrefs}}"
+            existing-passphrase-label="[[existingPassphraseLabel_]]"
+            on-passphrase-changed="onPassphraseChanged_">
+        </os-settings-sync-encryption-options>
+      </iron-collapse>
+
+    </div>
+  </div>
+</div>
+
+<div class="cr-row first">
+  <h2 class="cr-title-text">
+    $i18n{nonPersonalizedServicesSectionLabel}
+  </h2>
+</div>
+<if expr="_google_chrome">
+<os-settings-personalization-options class="list-frame" prefs="{{prefs}}">
+</os-settings-personalization-options>
+</if>
diff --git a/chrome/browser/resources/settings/chromeos/os_people_page/os_sync_page.ts b/chrome/browser/resources/settings/chromeos/os_people_page/os_sync_page.ts
new file mode 100644
index 0000000..035e1fa2
--- /dev/null
+++ b/chrome/browser/resources/settings/chromeos/os_people_page/os_sync_page.ts
@@ -0,0 +1,596 @@
+// Copyright 2015 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview
+ * 'os-settings-sync-page' is the settings page containing sync settings.
+ */
+
+import '//resources/js/util_ts.js';
+import '//resources/cr_elements/cr_button/cr_button.js';
+import '//resources/cr_elements/cr_dialog/cr_dialog.js';
+import '//resources/cr_elements/cr_input/cr_input.js';
+import '//resources/cr_elements/cr_link_row/cr_link_row.js';
+import '//resources/cr_elements/icons.html.js';
+import '//resources/cr_elements/cr_shared_style.css.js';
+import '//resources/cr_elements/cr_shared_vars.css.js';
+import '//resources/cr_elements/cr_expand_button/cr_expand_button.js';
+import '//resources/polymer/v3_0/iron-collapse/iron-collapse.js';
+import '//resources/polymer/v3_0/iron-icon/iron-icon.js';
+import '//resources/polymer/v3_0/iron-flex-layout/iron-flex-layout-classes.js';
+// <if expr="_google_chrome">
+import './os_personalization_options.js';
+// </if>
+import './os_sync_encryption_options.js';
+import '../../settings_shared.css.js';
+import '../../settings_vars.css.js';
+
+import {CrInputElement} from '//resources/cr_elements/cr_input/cr_input.js';
+import {assert, assertNotReached} from '//resources/js/assert_ts.js';
+import {focusWithoutInk} from '//resources/js/focus_without_ink.js';
+import {WebUiListenerMixin, WebUiListenerMixinInterface} from '//resources/cr_elements/web_ui_listener_mixin.js';
+import {IronCollapseElement} from '//resources/polymer/v3_0/iron-collapse/iron-collapse.js';
+import {PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {I18nMixin, I18nMixinInterface} from 'chrome://resources/cr_elements/i18n_mixin.js';
+
+import {FocusConfig} from '../../focus_config.js';
+import {loadTimeData} from '../../i18n_setup.js';
+
+import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../router.js';
+
+import {PageStatus, StatusAction, SyncBrowserProxy, SyncBrowserProxyImpl, SyncPrefs, SyncStatus} from '../../people_page/sync_browser_proxy.js';
+import {OsSettingsPersonalizationOptionsElement} from './os_personalization_options.js';
+import {OsSettingsSyncEncryptionOptionsElement} from './os_sync_encryption_options.js';
+
+import {getTemplate} from './os_sync_page.html.js';
+
+interface SyncRoutes {
+  BASIC: Route;
+  PEOPLE: Route;
+  SYNC: Route;
+  SYNC_ADVANCED: Route;
+  OS_SYNC: Route;
+  OS_PEOPLE: Route;
+}
+
+function getSyncRoutes(): SyncRoutes {
+  const router = Router.getInstance();
+  return router.getRoutes() as SyncRoutes;
+}
+
+export interface OsSettingsSyncPageElement {
+  $: {
+    encryptionCollapse: IronCollapseElement,
+  };
+}
+
+const OsSettingsSyncPageElementBase =
+    RouteObserverMixin(WebUiListenerMixin(I18nMixin(PolymerElement))) as {
+      new (): PolymerElement & WebUiListenerMixinInterface &
+          I18nMixinInterface & RouteObserverMixinInterface,
+    };
+
+export class OsSettingsSyncPageElement extends OsSettingsSyncPageElementBase {
+  static get is() {
+    return 'os-settings-sync-page';
+  }
+
+  static get template() {
+    return getTemplate();
+  }
+
+  static get properties() {
+    return {
+      /**
+       * Preferences state.
+       */
+      prefs: {
+        type: Object,
+        notify: true,
+      },
+
+      focusConfig: {
+        type: Object,
+        observer: 'onFocusConfigChange_',
+      },
+
+      pageStatusEnum_: {
+        type: Object,
+        value: PageStatus,
+        readOnly: true,
+      },
+
+      /**
+       * The current page status. Defaults to |CONFIGURE| such that the
+       * searching algorithm can search useful content when the page is not
+       * visible to the user.
+       */
+      pageStatus_: {
+        type: String,
+        value: PageStatus.CONFIGURE,
+      },
+
+      /**
+       * Dictionary defining page visibility.
+       */
+      pageVisibility: Object,
+
+      /**
+       * The current sync preferences, supplied by SyncBrowserProxy.
+       */
+      syncPrefs: Object,
+
+      syncStatus: Object,
+
+      dataEncrypted_: {
+        type: Boolean,
+        computed: 'computeDataEncrypted_(syncPrefs.encryptAllData)',
+      },
+
+      encryptionExpanded_: {
+        type: Boolean,
+        value: false,
+      },
+
+      /** If true, override |encryptionExpanded_| to be true. */
+      forceEncryptionExpanded: {
+        type: Boolean,
+        value: false,
+      },
+
+      /**
+       * The existing passphrase input field value.
+       */
+      existingPassphrase_: {
+        type: String,
+        value: '',
+      },
+
+      /*
+       * Whether enter existing passphrase UI should be shown.
+       */
+      showExistingPassphraseBelowAccount_: {
+        type: Boolean,
+        value: false,
+        computed: 'computeShowExistingPassphraseBelowAccount_(' +
+            'syncStatus.signedIn, syncPrefs.passphraseRequired)',
+      },
+
+      signedIn_: {
+        type: Boolean,
+        value: true,
+        computed: 'computeSignedIn_(syncStatus.signedIn)',
+      },
+
+      syncDisabledByAdmin_: {
+        type: Boolean,
+        value: false,
+        computed: 'computeSyncDisabledByAdmin_(syncStatus.managed)',
+      },
+
+      syncSectionDisabled_: {
+        type: Boolean,
+        value: false,
+        computed: 'computeSyncSectionDisabled_(' +
+            'syncStatus.signedIn, syncStatus.disabled, ' +
+            'syncStatus.hasError, syncStatus.statusAction, ' +
+            'syncPrefs.trustedVaultKeysRequired)',
+      },
+
+      enterPassphraseLabel_: {
+        type: String,
+        computed: 'computeEnterPassphraseLabel_(syncPrefs.encryptAllData,' +
+            'syncPrefs.explicitPassphraseTime)',
+      },
+
+      existingPassphraseLabel_: {
+        type: String,
+        computed: 'computeExistingPassphraseLabel_(syncPrefs.encryptAllData,' +
+            'syncPrefs.explicitPassphraseTime)',
+      },
+    };
+  }
+
+  static get observers() {
+    return [
+      'expandEncryptionIfNeeded_(dataEncrypted_, forceEncryptionExpanded)',
+    ];
+  }
+
+  prefs: {[key: string]: any};
+  focusConfig: FocusConfig;
+  private pageStatus_: PageStatus;
+  syncPrefs?: SyncPrefs;
+  syncStatus: SyncStatus;
+  private dataEncrypted_: boolean;
+  private encryptionExpanded_: boolean;
+  forceEncryptionExpanded: boolean;
+  private existingPassphrase_: string;
+  private signedIn_: boolean;
+  private syncDisabledByAdmin_: boolean;
+  private syncSectionDisabled_: boolean;
+
+  private enterPassphraseLabel_: TrustedHTML;
+  private existingPassphraseLabel_: TrustedHTML;
+
+  private browserProxy_: SyncBrowserProxy = SyncBrowserProxyImpl.getInstance();
+  private collapsibleSectionsInitialized_: boolean;
+  private didAbort_: boolean;
+  private setupCancelConfirmed_: boolean;
+  private beforeunloadCallback_: ((e: Event) => void)|null;
+  private unloadCallback_: (() => void)|null;
+
+  constructor() {
+    super();
+
+    /**
+     * The beforeunload callback is used to show the 'Leave site' dialog. This
+     * makes sure that the user has the chance to go back and confirm the sync
+     * opt-in before leaving.
+     *
+     * This property is non-null if the user is currently navigated on the sync
+     * settings route.
+     */
+    this.beforeunloadCallback_ = null;
+
+    /**
+     * The unload callback is used to cancel the sync setup when the user hits
+     * the browser back button after arriving on the page.
+     * Note = Cases like closing the tab or reloading don't need to be handled;
+     * because they are already caught in |PeopleHandler::~PeopleHandler|
+     * from the C++ code.
+     */
+    this.unloadCallback_ = null;
+
+    /**
+     * Whether the initial layout for collapsible sections has been computed. It
+     * is computed only once; the first time the sync status is updated.
+     */
+    this.collapsibleSectionsInitialized_ = false;
+
+    /**
+     * Whether the user decided to abort sync.
+     */
+    this.didAbort_ = true;
+
+    /**
+     * Whether the user confirmed the cancellation of sync.
+     */
+    this.setupCancelConfirmed_ = false;
+  }
+
+  override connectedCallback() {
+    super.connectedCallback();
+
+    this.addWebUiListener(
+        'page-status-changed', this.handlePageStatusChanged_.bind(this));
+    this.addWebUiListener(
+        'sync-prefs-changed', this.handleSyncPrefsChanged_.bind(this));
+
+    const router = Router.getInstance();
+    if (router.getCurrentRoute() === getSyncRoutes().SYNC) {
+      this.onNavigateToPage_();
+    }
+  }
+
+  override disconnectedCallback() {
+    super.disconnectedCallback();
+
+    const router = Router.getInstance();
+    if (getSyncRoutes().SYNC.contains(router.getCurrentRoute())) {
+      this.onNavigateAwayFromPage_();
+    }
+
+    if (this.beforeunloadCallback_) {
+      window.removeEventListener('beforeunload', this.beforeunloadCallback_);
+      this.beforeunloadCallback_ = null;
+    }
+    if (this.unloadCallback_) {
+      window.removeEventListener('unload', this.unloadCallback_);
+      this.unloadCallback_ = null;
+    }
+  }
+
+  getEncryptionOptions(): OsSettingsSyncEncryptionOptionsElement|null {
+    return this.shadowRoot!.querySelector(
+        'os-settings-sync-encryption-options');
+  }
+
+  getPersonalizationOptions(): OsSettingsPersonalizationOptionsElement|null {
+// <if expr="_google_chrome">
+    return null;
+// </if>
+// <if expr="not _google_chrome">
+    return this.shadowRoot!.querySelector(
+        'os-settings-personalization-options');
+// </if>
+  }
+
+  private shouldShowLacrosSideBySideWarning_(): boolean {
+    return loadTimeData.getBoolean('shouldShowLacrosSideBySideWarning');
+  }
+
+  private showActivityControls_(): boolean {
+    // Should be hidden in OS settings.
+    return false;
+  }
+
+  private computeSignedIn_(): boolean {
+    return !!this.syncStatus.signedIn;
+  }
+
+  private computeSyncSectionDisabled_(): boolean {
+    return this.syncStatus !== undefined &&
+        (!this.syncStatus.signedIn || !!this.syncStatus.disabled ||
+         (!!this.syncStatus.hasError &&
+          this.syncStatus.statusAction !== StatusAction.ENTER_PASSPHRASE &&
+          this.syncStatus.statusAction !==
+              StatusAction.RETRIEVE_TRUSTED_VAULT_KEYS));
+  }
+
+  private computeSyncDisabledByAdmin_(): boolean {
+    return this.syncStatus !== undefined && !!this.syncStatus.managed;
+  }
+
+  private onFocusConfigChange_() {
+    this.focusConfig.set(getSyncRoutes().OS_SYNC.path, () => {
+      const toFocus =
+          this.shadowRoot!.querySelector<HTMLElement>('#sync-advanced-row');
+      assert(toFocus);
+      focusWithoutInk(toFocus);
+    });
+  }
+
+  override currentRouteChanged() {
+    const router = Router.getInstance();
+    if (router.getCurrentRoute() === getSyncRoutes().SYNC) {
+      this.onNavigateToPage_();
+      return;
+    }
+
+    if (getSyncRoutes().SYNC.contains(router.getCurrentRoute())) {
+      return;
+    }
+
+    const searchParams =
+        Router.getInstance().getQueryParameters().get('search');
+    if (searchParams) {
+      // User navigated away via searching. Cancel sync without showing
+      // confirmation dialog.
+      this.onNavigateAwayFromPage_();
+      return;
+    }
+
+    this.onNavigateAwayFromPage_();
+  }
+
+  private isStatus_(expectedPageStatus: PageStatus): boolean {
+    return expectedPageStatus === this.pageStatus_;
+  }
+
+  private onNavigateToPage_() {
+    const router = Router.getInstance();
+    assert(router.getCurrentRoute() === getSyncRoutes().SYNC);
+    if (this.beforeunloadCallback_) {
+      return;
+    }
+
+    this.collapsibleSectionsInitialized_ = false;
+
+    // Display loading page until the settings have been retrieved.
+    this.pageStatus_ = PageStatus.SPINNER;
+
+    this.browserProxy_.didNavigateToSyncPage();
+
+    this.beforeunloadCallback_ = event => {
+      // When the user tries to leave the sync setup, show the 'Leave site'
+      // dialog.
+      if (this.syncStatus && this.syncStatus.firstSetupInProgress) {
+        event.preventDefault();
+
+        chrome.metricsPrivate.recordUserAction(
+            'Signin_Signin_AbortAdvancedSyncSettings');
+      }
+    };
+    window.addEventListener('beforeunload', this.beforeunloadCallback_);
+
+    this.unloadCallback_ = this.onNavigateAwayFromPage_.bind(this);
+    window.addEventListener('unload', this.unloadCallback_);
+  }
+
+  private onNavigateAwayFromPage_() {
+    if (!this.beforeunloadCallback_) {
+      return;
+    }
+
+    // Reset the status to CONFIGURE such that the searching algorithm can
+    // search useful content when the page is not visible to the user.
+    this.pageStatus_ = PageStatus.CONFIGURE;
+
+    this.browserProxy_.didNavigateAwayFromSyncPage(this.didAbort_);
+
+    window.removeEventListener('beforeunload', this.beforeunloadCallback_);
+    this.beforeunloadCallback_ = null;
+
+    if (this.unloadCallback_) {
+      window.removeEventListener('unload', this.unloadCallback_);
+      this.unloadCallback_ = null;
+    }
+  }
+
+  /**
+   * Handler for when the sync preferences are updated.
+   */
+  private handleSyncPrefsChanged_(syncPrefs: SyncPrefs) {
+    this.syncPrefs = syncPrefs;
+    this.pageStatus_ = PageStatus.CONFIGURE;
+  }
+
+  private onSyncDashboardLinkClick_() {
+    window.open(loadTimeData.getString('syncDashboardUrl'));
+  }
+
+  private computeDataEncrypted_(): boolean {
+    return !!this.syncPrefs && this.syncPrefs.encryptAllData;
+  }
+
+  private computeEnterPassphraseLabel_(): TrustedHTML {
+    if (!this.syncPrefs || !this.syncPrefs.encryptAllData) {
+      return window.trustedTypes!.emptyHTML;
+    }
+
+    if (!this.syncPrefs.explicitPassphraseTime) {
+      // TODO(crbug.com/1207432): There's no reason why this dateless label
+      // shouldn't link to 'syncErrorsHelpUrl' like the other one.
+      return this.i18nAdvanced('enterPassphraseLabel');
+    }
+
+    return this.i18nAdvanced('enterPassphraseLabelWithDate', {
+      tags: ['a'],
+      substitutions: [
+        loadTimeData.getString('syncErrorsHelpUrl'),
+        this.syncPrefs.explicitPassphraseTime,
+      ],
+    });
+  }
+
+  private computeExistingPassphraseLabel_(): TrustedHTML {
+    if (!this.syncPrefs || !this.syncPrefs.encryptAllData) {
+      return window.trustedTypes!.emptyHTML;
+    }
+
+    if (!this.syncPrefs.explicitPassphraseTime) {
+      return this.i18nAdvanced('existingPassphraseLabel');
+    }
+
+    return this.i18nAdvanced('existingPassphraseLabelWithDate', {
+      substitutions: [this.syncPrefs.explicitPassphraseTime],
+    });
+  }
+
+  /**
+   * Whether the encryption dropdown should be expanded by default.
+   */
+  private expandEncryptionIfNeeded_() {
+    // Force the dropdown to expand.
+    if (this.forceEncryptionExpanded) {
+      this.forceEncryptionExpanded = false;
+      this.encryptionExpanded_ = true;
+      return;
+    }
+
+    this.encryptionExpanded_ = this.dataEncrypted_;
+  }
+
+  private onResetSyncClick_(event: Event) {
+    if ((event.target as HTMLElement).tagName === 'A') {
+      // Stop the propagation of events as the |cr-expand-button|
+      // prevents the default which will prevent the navigation to the link.
+      event.stopPropagation();
+    }
+  }
+
+  /**
+   * Sends the user-entered existing password to re-enable sync.
+   */
+  private onSubmitExistingPassphraseTap_(e: KeyboardEvent) {
+    if (e.type === 'keypress' && e.key !== 'Enter') {
+      return;
+    }
+
+    this.browserProxy_.setDecryptionPassphrase(this.existingPassphrase_)
+        .then(
+            sucessfullySet => this.handlePageStatusChanged_(
+                this.computePageStatusAfterPassphraseChange_(sucessfullySet)));
+
+    this.existingPassphrase_ = '';
+  }
+
+  private onPassphraseChanged_(e: CustomEvent<{didChange: boolean}>) {
+    this.handlePageStatusChanged_(
+        this.computePageStatusAfterPassphraseChange_(e.detail.didChange));
+  }
+
+  private computePageStatusAfterPassphraseChange_(successfullyChanged: boolean):
+      PageStatus {
+    if (!successfullyChanged) {
+      return PageStatus.PASSPHRASE_FAILED;
+    }
+
+    // Stay on the setup page if the user hasn't approved sync settings yet.
+    // Otherwise, close sync setup.
+    return this.syncStatus && this.syncStatus.firstSetupInProgress ?
+        PageStatus.CONFIGURE :
+        PageStatus.DONE;
+  }
+
+  /**
+   * Called when the page status updates.
+   */
+  private handlePageStatusChanged_(pageStatus: PageStatus) {
+    const router = Router.getInstance();
+    switch (pageStatus) {
+      case PageStatus.SPINNER:
+      case PageStatus.CONFIGURE:
+        this.pageStatus_ = pageStatus;
+        return;
+      case PageStatus.DONE:
+        if (router.getCurrentRoute() === getSyncRoutes().SYNC) {
+          router.navigateTo(getSyncRoutes().OS_PEOPLE);
+        }
+        return;
+      case PageStatus.PASSPHRASE_FAILED:
+        if (this.pageStatus_ === PageStatus.CONFIGURE && this.syncPrefs &&
+            this.syncPrefs.passphraseRequired) {
+          const passphraseInput =
+              this.shadowRoot!.querySelector<CrInputElement>(
+                  '#existingPassphraseInput')!;
+          passphraseInput.invalid = true;
+          passphraseInput.focusInput();
+        }
+        return;
+      default:
+        assertNotReached();
+    }
+  }
+
+  private onLearnMoreTap_(event: Event) {
+    if ((event.target as HTMLElement).tagName === 'A') {
+      // Stop the propagation of events, so that clicking on links inside
+      // checkboxes or radio buttons won't change the value.
+      event.stopPropagation();
+    }
+  }
+
+  private computeShowExistingPassphraseBelowAccount_(): boolean {
+    return this.syncStatus !== undefined && !!this.syncStatus.signedIn &&
+        this.syncPrefs !== undefined && !!this.syncPrefs.passphraseRequired;
+  }
+
+  private onSyncAdvancedClick_() {
+    const router = Router.getInstance();
+    router.navigateTo(getSyncRoutes().OS_SYNC);
+  }
+
+  /**
+   * Focuses the passphrase input element if it is available and the page is
+   * visible.
+   */
+  private focusPassphraseInput_() {
+    const passphraseInput = this.shadowRoot!.querySelector<CrInputElement>(
+        '#existingPassphraseInput');
+    const router = Router.getInstance();
+    if (passphraseInput && router.getCurrentRoute() === getSyncRoutes().SYNC) {
+      passphraseInput.focus();
+    }
+  }
+}
+
+declare global {
+  interface HTMLElementTagNameMap {
+    'os-settings-sync-page': OsSettingsSyncPageElement;
+  }
+}
+
+customElements.define(OsSettingsSyncPageElement.is, OsSettingsSyncPageElement);
diff --git a/chrome/browser/resources/settings/chromeos/os_people_page/user_list.js b/chrome/browser/resources/settings/chromeos/os_people_page/user_list.js
index 803233a..f0bb2ac 100644
--- a/chrome/browser/resources/settings/chromeos/os_people_page/user_list.js
+++ b/chrome/browser/resources/settings/chromeos/os_people_page/user_list.js
@@ -25,7 +25,7 @@
 import {I18nBehavior, I18nBehaviorInterface} from 'chrome://resources/ash/common/i18n_behavior.js';
 import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-import {Router} from '../../router.js';
+import {Router} from '../router.js';
 import {routes} from '../os_route.js';
 import {RouteObserverBehavior, RouteObserverBehaviorInterface} from '../route_observer_behavior.js';
 
diff --git a/chrome/browser/resources/settings/chromeos/os_people_page/users_page.js b/chrome/browser/resources/settings/chromeos/os_people_page/users_page.js
index cb38a60..f0c343f 100644
--- a/chrome/browser/resources/settings/chromeos/os_people_page/users_page.js
+++ b/chrome/browser/resources/settings/chromeos/os_people_page/users_page.js
@@ -22,7 +22,7 @@
 
 import {loadTimeData} from '../../i18n_setup.js';
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
-import {Route} from '../../router.js';
+import {Route} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {routes} from '../os_route.js';
 import {RouteObserverBehavior, RouteObserverBehaviorInterface} from '../route_observer_behavior.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_printing_page/cups_printers.ts b/chrome/browser/resources/settings/chromeos/os_printing_page/cups_printers.ts
index b5059cd..2e8f639 100644
--- a/chrome/browser/resources/settings/chromeos/os_printing_page/cups_printers.ts
+++ b/chrome/browser/resources/settings/chromeos/os_printing_page/cups_printers.ts
@@ -44,7 +44,7 @@
 import {afterNextRender, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
-import {Route} from '../../router.js';
+import {Route} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {routes} from '../os_route.js';
 import {RouteObserverBehavior, RouteObserverBehaviorInterface} from '../route_observer_behavior.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_printing_page/os_printing_page.ts b/chrome/browser/resources/settings/chromeos/os_printing_page/os_printing_page.ts
index 5853976..3f870bf 100644
--- a/chrome/browser/resources/settings/chromeos/os_printing_page/os_printing_page.ts
+++ b/chrome/browser/resources/settings/chromeos/os_printing_page/os_printing_page.ts
@@ -11,7 +11,7 @@
 import {mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../../router.js';
+import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {recordSettingChange} from '../metrics_recorder.js';
 import {routes} from '../os_route.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_privacy_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_privacy_page/BUILD.gn
index 14c61e6..2984110 100644
--- a/chrome/browser/resources/settings/chromeos/os_privacy_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_privacy_page/BUILD.gn
@@ -28,7 +28,7 @@
     "..:deep_linking_behavior",
     "..:os_route",
     "..:route_observer_behavior",
-    "../..:router",
+    "..:router",
     "../os_people_page:lock_screen",
     "../os_people_page:lock_state_behavior",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
diff --git a/chrome/browser/resources/settings/chromeos/os_privacy_page/os_privacy_page.js b/chrome/browser/resources/settings/chromeos/os_privacy_page/os_privacy_page.js
index 67aa2309..0207c52e 100644
--- a/chrome/browser/resources/settings/chromeos/os_privacy_page/os_privacy_page.js
+++ b/chrome/browser/resources/settings/chromeos/os_privacy_page/os_privacy_page.js
@@ -26,7 +26,7 @@
 import {afterNextRender, html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
-import {Route, Router} from '../../router.js';
+import {Route, Router} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {LockStateBehavior, LockStateBehaviorInterface} from '../os_people_page/lock_state_behavior.js';
 import {routes} from '../os_route.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_privacy_page/privacy_hub_page.html b/chrome/browser/resources/settings/chromeos/os_privacy_page/privacy_hub_page.html
index 5519ab1..4558071 100644
--- a/chrome/browser/resources/settings/chromeos/os_privacy_page/privacy_hub_page.html
+++ b/chrome/browser/resources/settings/chromeos/os_privacy_page/privacy_hub_page.html
@@ -48,6 +48,12 @@
       sub-label="$i18n{microphoneToggleSubtext}"
       disabled="[[shouldDisableMicrophoneToggle_]]"
       on-change="onMicrophoneToggleChanged_">
+    <cr-tooltip-icon
+      hidden$="[[!microphoneHardwareToggleActive_]]"
+      tooltip-text="$i18n{microphoneHwToggleTooltip}"
+      icon-class="cr:info-outline"
+      slot="more-actions">
+    </cr-tooltip-icon>
   </settings-toggle-button>
 
   <div class="list-frame">
diff --git a/chrome/browser/resources/settings/chromeos/os_privacy_page/privacy_hub_page.js b/chrome/browser/resources/settings/chromeos/os_privacy_page/privacy_hub_page.js
index 2fe73a0..90c2743 100644
--- a/chrome/browser/resources/settings/chromeos/os_privacy_page/privacy_hub_page.js
+++ b/chrome/browser/resources/settings/chromeos/os_privacy_page/privacy_hub_page.js
@@ -20,7 +20,7 @@
 import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
-import {Route} from '../../router.js';
+import {Route} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {routes} from '../os_route.js';
 import {RouteObserverBehavior, RouteObserverBehaviorInterface} from '../route_observer_behavior.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_privacy_page/smart_privacy_page.js b/chrome/browser/resources/settings/chromeos/os_privacy_page/smart_privacy_page.js
index 86fedf2..849c98318 100644
--- a/chrome/browser/resources/settings/chromeos/os_privacy_page/smart_privacy_page.js
+++ b/chrome/browser/resources/settings/chromeos/os_privacy_page/smart_privacy_page.js
@@ -20,7 +20,7 @@
 import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
-import {Route} from '../../router.js';
+import {Route} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {routes} from '../os_route.js';
 import {PrefsBehavior, PrefsBehaviorInterface} from '../prefs_behavior.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_reset_page/os_powerwash_dialog.ts b/chrome/browser/resources/settings/chromeos/os_reset_page/os_powerwash_dialog.ts
index 435493a2..5300a01 100644
--- a/chrome/browser/resources/settings/chromeos/os_reset_page/os_powerwash_dialog.ts
+++ b/chrome/browser/resources/settings/chromeos/os_reset_page/os_powerwash_dialog.ts
@@ -22,7 +22,7 @@
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {LifetimeBrowserProxy, LifetimeBrowserProxyImpl} from '../../lifetime_browser_proxy.js';
-import {Router} from '../../router.js';
+import {Router} from '../router.js';
 import {recordSettingChange} from '../metrics_recorder.js';
 import {routes} from '../os_route.js';
 
diff --git a/chrome/browser/resources/settings/chromeos/os_reset_page/os_reset_page.ts b/chrome/browser/resources/settings/chromeos/os_reset_page/os_reset_page.ts
index 46042da..be67782 100644
--- a/chrome/browser/resources/settings/chromeos/os_reset_page/os_reset_page.ts
+++ b/chrome/browser/resources/settings/chromeos/os_reset_page/os_reset_page.ts
@@ -16,7 +16,7 @@
 import {mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface} from '../../router.js';
+import {Route, RouteObserverMixin, RouteObserverMixinInterface} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {routes} from '../os_route.js';
 
diff --git a/chrome/browser/resources/settings/chromeos/os_route.js b/chrome/browser/resources/settings/chromeos/os_route.js
index a14e870..1f9cacd8 100644
--- a/chrome/browser/resources/settings/chromeos/os_route.js
+++ b/chrome/browser/resources/settings/chromeos/os_route.js
@@ -7,7 +7,7 @@
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 
 import * as routesMojomWebui from '../mojom-webui/routes.mojom-webui.js';
-import {Route, Router} from '../router.js';
+import {Route, Router} from './router.js';
 
 import {OsSettingsRoutes} from './os_settings_routes.js';
 
diff --git a/chrome/browser/resources/settings/chromeos/os_search_page/os_search_page.ts b/chrome/browser/resources/settings/chromeos/os_search_page/os_search_page.ts
index 222e0d0..6e759d00 100644
--- a/chrome/browser/resources/settings/chromeos/os_search_page/os_search_page.ts
+++ b/chrome/browser/resources/settings/chromeos/os_search_page/os_search_page.ts
@@ -27,7 +27,7 @@
 import {mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../../router.js';
+import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {routes} from '../os_route.js';
 
diff --git a/chrome/browser/resources/settings/chromeos/os_search_page/search_subpage.ts b/chrome/browser/resources/settings/chromeos/os_search_page/search_subpage.ts
index 9597e47..401c07a 100644
--- a/chrome/browser/resources/settings/chromeos/os_search_page/search_subpage.ts
+++ b/chrome/browser/resources/settings/chromeos/os_search_page/search_subpage.ts
@@ -26,7 +26,7 @@
 
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
 import {PrefsMixin, PrefsMixinInterface} from '../../prefs/prefs_mixin.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../../router.js';
+import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../router.js';
 import {castExists} from '../assert_extras.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {routes} from '../os_route.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_settings.gni b/chrome/browser/resources/settings/chromeos/os_settings.gni
index edf1754..2fd3aac 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings.gni
+++ b/chrome/browser/resources/settings/chromeos/os_settings.gni
@@ -115,6 +115,10 @@
   "chromeos/os_languages_page/os_languages_page_v2.js",
   "chromeos/os_languages_page/os_languages_section.js",
   "chromeos/os_languages_page/smart_inputs_page.js",
+  "chromeos/os_people_page/os_personalization_options.ts",
+  "chromeos/os_people_page/os_signout_dialog.ts",
+  "chromeos/os_people_page/os_sync_encryption_options.ts",
+  "chromeos/os_people_page/os_sync_page.ts",
   "chromeos/os_reset_page/os_powerwash_dialog.ts",
   "chromeos/os_reset_page/os_powerwash_dialog_esim_item.ts",
   "chromeos/os_reset_page/os_reset_page.ts",
@@ -161,12 +165,6 @@
   "controls/settings_radio_group.ts",
   "controls/settings_slider.ts",
   "controls/settings_toggle_button.ts",
-  "people_page/signout_dialog.ts",
-  "people_page/sync_account_control.ts",
-  "people_page/sync_controls.ts",
-  "people_page/sync_encryption_options.ts",
-  "people_page/sync_page.ts",
-  "privacy_page/personalization_options.ts",
   "privacy_page/secure_dns.ts",
   "privacy_page/secure_dns_input.ts",
   "site_favicon.ts",
@@ -258,6 +256,7 @@
   "chromeos/os_a11y_page/switch_access_types.ts",
   "chromeos/os_a11y_page/text_to_speech_page_browser_proxy.ts",
   "chromeos/os_a11y_page/tts_subpage_browser_proxy.ts",
+  "chromeos/os_a11y_page/select_to_speak_subpage_browser_proxy.ts",
   "chromeos/os_about_page/about_page_browser_proxy.ts",
   "chromeos/os_about_page/device_name_browser_proxy.ts",
   "chromeos/os_about_page/device_name_util.ts",
@@ -306,6 +305,7 @@
   "chromeos/prefs_behavior.js",
   "chromeos/route_observer_behavior.js",
   "chromeos/route_origin_behavior.js",
+  "chromeos/router.js",
   "chromeos/search/combined_search_handler.ts",
   "chromeos/search/personalization_search_handler.ts",
   "chromeos/search/settings_search_handler.ts",
@@ -330,7 +330,6 @@
   "prefs/prefs_types.ts",
   "privacy_page/privacy_page_browser_proxy.ts",
   "relaunch_mixin.ts",
-  "router.js",
 ]
 
 # Files that are generated by html_to_js() or other build rule.
diff --git a/chrome/browser/resources/settings/chromeos/os_settings.js b/chrome/browser/resources/settings/chromeos/os_settings.js
index a782a7c..2eb9549 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings.js
+++ b/chrome/browser/resources/settings/chromeos/os_settings.js
@@ -123,7 +123,7 @@
 export {PageStatus, StatusAction, SyncBrowserProxyImpl} from '../people_page/sync_browser_proxy.js';
 export {CrSettingsPrefs} from '../prefs/prefs_types.js';
 export {PrivacyPageBrowserProxyImpl, SecureDnsMode, SecureDnsUiManagementMode} from '../privacy_page/privacy_page_browser_proxy.js';
-export {Route, Router} from '../router.js';
+export {Route, Router} from './router.js';
 export {getContactManager, observeContactManager, setContactManagerForTesting} from '../shared/nearby_contact_manager.js';
 export {getNearbyShareSettings, observeNearbyShareSettings, setNearbyShareSettingsForTesting} from '../shared/nearby_share_settings.js';
 export {NearbySettings, NearbyShareSettingsBehavior} from '../shared/nearby_share_settings_behavior.js';
@@ -144,6 +144,7 @@
 export {dataUsageStringToEnum, NearbyShareDataUsage} from './nearby_share_page/types.js';
 export {ManageA11yPageBrowserProxy, ManageA11yPageBrowserProxyImpl} from './os_a11y_page/manage_a11y_page_browser_proxy.js';
 export {OsA11yPageBrowserProxy, OsA11yPageBrowserProxyImpl} from './os_a11y_page/os_a11y_page_browser_proxy.js';
+export {SelectToSpeakSubpageBrowserProxy, SelectToSpeakSubpageBrowserProxyImpl} from './os_a11y_page/select_to_speak_subpage_browser_proxy.js';
 export {SwitchAccessSubpageBrowserProxy, SwitchAccessSubpageBrowserProxyImpl} from './os_a11y_page/switch_access_subpage_browser_proxy.js';
 export {TextToSpeechPageBrowserProxy, TextToSpeechPageBrowserProxyImpl} from './os_a11y_page/text_to_speech_page_browser_proxy.js';
 export {TtsSubpageBrowserProxy, TtsSubpageBrowserProxyImpl} from './os_a11y_page/tts_subpage_browser_proxy.js';
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 7341116..df9b514 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
@@ -20,7 +20,7 @@
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {loadTimeData} from '../../i18n_setup.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../../router.js';
+import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../router.js';
 import {assertExists} from '../assert_extras.js';
 import {OSPageVisibility} from '../os_page_visibility.js';
 import {routes} from '../os_route.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_menu/os_settings_menu.ts b/chrome/browser/resources/settings/chromeos/os_settings_menu/os_settings_menu.ts
index 40c58fb..93193be 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_menu/os_settings_menu.ts
+++ b/chrome/browser/resources/settings/chromeos/os_settings_menu/os_settings_menu.ts
@@ -19,7 +19,7 @@
 import {IronSelectorElement} from 'chrome://resources/polymer/v3_0/iron-selector/iron-selector.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../../router.js';
+import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../router.js';
 import {castExists} from '../assert_extras.js';
 import {routes} from '../os_route.js';
 
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_animated_pages.ts b/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_animated_pages.ts
index 9914f1d..2ffc8f72 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_animated_pages.ts
+++ b/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_animated_pages.ts
@@ -22,7 +22,7 @@
 import {DomIf, FlattenedNodesObserver, microTask, PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {FocusConfig} from '../../focus_config.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../../router.js';
+import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../router.js';
 import {getSettingIdParameter} from '../setting_id_param_util.js';
 
 import {getTemplate} from './os_settings_animated_pages.html.js';
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 d76dee2..edad547 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
@@ -33,7 +33,7 @@
 import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
 import {beforeNextRender, microTask, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-import {Route, Router} from '../../router.js';
+import {Route, Router} from '../router.js';
 import {castExists} from '../assert_extras.js';
 import {MainPageMixin} from '../main_page_mixin.js';
 import {AndroidAppsBrowserProxyImpl, AndroidAppsInfo} from '../os_apps_page/android_apps_browser_proxy.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_subpage.ts b/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_subpage.ts
index 14f6c39..88bf000 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_subpage.ts
+++ b/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_subpage.ts
@@ -27,7 +27,7 @@
 import {EventTracker} from 'chrome://resources/js/event_tracker.js';
 
 import {loadTimeData} from '../../i18n_setup.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../../router.js';
+import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../router.js';
 import {getSettingIdParameter} from '../setting_id_param_util.js';
 
 import {getTemplate} from './os_settings_subpage.html.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_routes.js b/chrome/browser/resources/settings/chromeos/os_settings_routes.js
index 36814d8..8abb0e3a 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_routes.js
+++ b/chrome/browser/resources/settings/chromeos/os_settings_routes.js
@@ -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 {Route} from '../router.js';
+import {Route} from './router.js';
 
 /**
  * Specifies all possible os routes in settings.
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_search_result_row.ts b/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_search_result_row.ts
index aad27b4..97ec2a3 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_search_result_row.ts
+++ b/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_search_result_row.ts
@@ -20,7 +20,7 @@
 import {SearchResult as SettingsSearchResult, SearchResultIdentifier, SearchResultType} from '../../mojom-webui/search/search.mojom-webui.js';
 import {SearchResultIcon} from '../../mojom-webui/search/search_result_icon.mojom-webui.js';
 import {OpenWindowProxyImpl} from '../../open_window_proxy.js';
-import {Router} from '../../router.js';
+import {Router} from '../router.js';
 import {SearchResult} from '../search/combined_search_handler.js';
 
 import {getTemplate} from './os_search_result_row.html.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_settings_search_box.ts b/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_settings_search_box.ts
index ed5e92cd..4b6e1d2 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_settings_search_box.ts
+++ b/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_settings_search_box.ts
@@ -27,7 +27,7 @@
 
 import {SearchResultsObserverInterface as PersonalizationSearchResultsObserverInterface, SearchResultsObserverReceiver as PersonalizationSearchResultsObserverReceiver} from '../../mojom-webui/personalization/search.mojom-webui.js';
 import {ParentResultBehavior, SearchResultsObserverInterface, SearchResultsObserverReceiver} from '../../mojom-webui/search/search.mojom-webui.js';
-import {Router} from '../../router.js';
+import {Router} from '../router.js';
 import {castExists} from '../assert_extras.js';
 import {recordSearch} from '../metrics_recorder.js';
 import {AboutPageBrowserProxy, AboutPageBrowserProxyImpl} from '../os_about_page/about_page_browser_proxy.js';
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 4e4bc1c..d9e30df 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
@@ -31,7 +31,7 @@
 
 import {loadTimeData} from '../../i18n_setup.js';
 import {SettingsPrefsElement} from '../../prefs/prefs.js';
-import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../../router.js';
+import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../router.js';
 import {castExists} from '../assert_extras.js';
 import {setGlobalScrollTarget} from '../global_scroll_target_behavior.js';
 import {recordClick, recordNavigation, recordPageBlur, recordPageFocus, recordSettingChange} from '../metrics_recorder.js';
diff --git a/chrome/browser/resources/settings/chromeos/route_observer_behavior.js b/chrome/browser/resources/settings/chromeos/route_observer_behavior.js
index dba95b9..710e847 100644
--- a/chrome/browser/resources/settings/chromeos/route_observer_behavior.js
+++ b/chrome/browser/resources/settings/chromeos/route_observer_behavior.js
@@ -4,7 +4,7 @@
 
 import {assertNotReached} from 'chrome://resources/js/assert.js';
 
-import {Route, Router} from '../router.js';
+import {Route, Router} from './router.js';
 
 /** @polymerBehavior */
 export const RouteObserverBehavior = {
diff --git a/chrome/browser/resources/settings/chromeos/route_origin_behavior.js b/chrome/browser/resources/settings/chromeos/route_origin_behavior.js
index 18ac912..7bdd135 100644
--- a/chrome/browser/resources/settings/chromeos/route_origin_behavior.js
+++ b/chrome/browser/resources/settings/chromeos/route_origin_behavior.js
@@ -6,7 +6,7 @@
 import {assert} from 'chrome://resources/js/assert.js';
 import {beforeNextRender} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-import {Route, Router} from '../router.js';
+import {Route, Router} from './router.js';
 
 import {RouteObserverBehavior} from './route_observer_behavior.js';
 
diff --git a/chrome/browser/resources/settings/router.js b/chrome/browser/resources/settings/chromeos/router.js
similarity index 97%
rename from chrome/browser/resources/settings/router.js
rename to chrome/browser/resources/settings/chromeos/router.js
index c45a047..c594955 100644
--- a/chrome/browser/resources/settings/router.js
+++ b/chrome/browser/resources/settings/chromeos/router.js
@@ -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 './i18n_setup.js';
+import '../i18n_setup.js';
 
 import {assert, assertNotReached} from 'chrome://resources/js/assert.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
@@ -387,10 +387,7 @@
     assert(urlPath.startsWith('/'));
     assert(!urlPath.match(/\?/g));
 
-    const metricName = loadTimeData.valueExists('isOSSettings') &&
-            loadTimeData.getBoolean('isOSSettings') ?
-        'ChromeOS.Settings.PathVisited' :
-        'WebUI.Settings.PathVisited';
+    const metricName = 'ChromeOS.Settings.PathVisited';
     chrome.metricsPrivate.recordSparseValueWithPersistentHash(
         metricName, urlPath);
   }
diff --git a/chrome/browser/resources/settings/chromeos/setting_id_param_util.ts b/chrome/browser/resources/settings/chromeos/setting_id_param_util.ts
index 61f6d484..22ef645 100644
--- a/chrome/browser/resources/settings/chromeos/setting_id_param_util.ts
+++ b/chrome/browser/resources/settings/chromeos/setting_id_param_util.ts
@@ -7,7 +7,7 @@
  * from the Url parameter.
  */
 
-import {Router} from '../router.js';
+import {Router} from './router.js';
 
 const SETTING_ID_URL_PARAM_NAME: string = 'settingId';
 
diff --git a/chrome/browser/resources/settings/chromeos/settings_controls_types.js b/chrome/browser/resources/settings/chromeos/settings_controls_types.js
index 63c5ec7e..e08631d 100644
--- a/chrome/browser/resources/settings/chromeos/settings_controls_types.js
+++ b/chrome/browser/resources/settings/chromeos/settings_controls_types.js
@@ -83,10 +83,10 @@
  * @constructor
  * @extends {HTMLElement}
  */
-function SettingsSyncEncryptionOptionsElement() {}
+function OsSettingsSyncEncryptionOptionsElement() {}
 
 /** @return {?HTMLElement} */
-SettingsSyncEncryptionOptionsElement.prototype.getEncryptionsRadioButtons =
+OsSettingsSyncEncryptionOptionsElement.prototype.getEncryptionsRadioButtons =
     function() {};
 
 
@@ -94,13 +94,13 @@
  * @constructor
  * @extends {HTMLElement}
  */
-function SettingsSyncPageElement() {}
+function OsSettingsSyncPageElement() {}
 
 /** @return {?SettingsPersonalizationOptionsElement} */
-SettingsSyncPageElement.prototype.getPersonalizationOptions = function() {};
+OsSettingsSyncPageElement.prototype.getPersonalizationOptions = function() {};
 
-/** @return {?SettingsSyncEncryptionOptionsElement} */
-SettingsSyncPageElement.prototype.getEncryptionOptions = function() {};
+/** @return {?OsSettingsSyncEncryptionOptionsElement} */
+OsSettingsSyncPageElement.prototype.getEncryptionOptions = function() {};
 
 /**
  * Must be kept in sync with the return values of getSyncErrorAction in
diff --git a/chrome/browser/resources/settings/people_page/sync_account_control.ts b/chrome/browser/resources/settings/people_page/sync_account_control.ts
index d9261e5..c9bba29 100644
--- a/chrome/browser/resources/settings/people_page/sync_account_control.ts
+++ b/chrome/browser/resources/settings/people_page/sync_account_control.ts
@@ -25,7 +25,7 @@
 
 import {loadTimeData} from '../i18n_setup.js';
 import {PrefsMixin} from '../prefs/prefs_mixin.js';
-import {Route, Router} from '../router.js';
+import {Router} from '../router.js';
 
 import {getTemplate} from './sync_account_control.html.js';
 import {StatusAction, StoredAccount, SyncBrowserProxy, SyncBrowserProxyImpl, SyncStatus} from './sync_browser_proxy.js';
@@ -349,8 +349,7 @@
 
   private onErrorButtonTap_() {
     const router = Router.getInstance();
-    const routes =
-        router.getRoutes() as {SIGN_OUT: Route, SYNC: Route, ABOUT: Route};
+    const routes = router.getRoutes();
     switch (this.syncStatus.statusAction) {
       // <if expr="not chromeos_ash">
       case StatusAction.REAUTHENTICATE:
@@ -405,7 +404,7 @@
   private onTurnOffButtonTap_() {
     /* This will route to people_page's disconnect dialog. */
     const router = Router.getInstance();
-    router.navigateTo((router.getRoutes() as {SIGN_OUT: Route}).SIGN_OUT);
+    router.navigateTo(router.getRoutes().SIGN_OUT);
   }
 
   private onMenuButtonTap_() {
diff --git a/chrome/browser/resources/settings/people_page/sync_page.html b/chrome/browser/resources/settings/people_page/sync_page.html
index 2880e8f..fe72b2f8 100644
--- a/chrome/browser/resources/settings/people_page/sync_page.html
+++ b/chrome/browser/resources/settings/people_page/sync_page.html
@@ -144,7 +144,6 @@
 
           <cr-link-row class="hr"
               label="$i18n{personalizeGoogleServicesTitle}"
-              hidden="[[!showActivityControls_()]]"
               on-click="onActivityControlsClick_" external></cr-link-row>
 
           <cr-link-row id="syncDashboardLink" class="hr"
diff --git a/chrome/browser/resources/settings/people_page/sync_page.ts b/chrome/browser/resources/settings/people_page/sync_page.ts
index 2615aae5..39f0c4c 100644
--- a/chrome/browser/resources/settings/people_page/sync_page.ts
+++ b/chrome/browser/resources/settings/people_page/sync_page.ts
@@ -39,7 +39,7 @@
 import {SettingsPersonalizationOptionsElement} from '../privacy_page/personalization_options.js';
 // </if>
 
-import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../router.js';
+import {RouteObserverMixin, RouteObserverMixinInterface, Router} from '../router.js';
 
 import {PageStatus, StatusAction, SyncBrowserProxy, SyncBrowserProxyImpl, SyncPrefs, SyncStatus} from './sync_browser_proxy.js';
 // <if expr="chromeos_ash">
@@ -48,22 +48,6 @@
 
 import {getTemplate} from './sync_page.html.js';
 
-// TODO(rbpotter): Remove this typedef when this file is no longer needed by OS
-// Settings.
-interface SyncRoutes {
-  BASIC: Route;
-  PEOPLE: Route;
-  SYNC: Route;
-  SYNC_ADVANCED: Route;
-  OS_SYNC: Route;
-  OS_PEOPLE: Route;
-}
-
-function getSyncRoutes(): SyncRoutes {
-  const router = Router.getInstance();
-  return router.getRoutes() as SyncRoutes;
-}
-
 export interface SettingsSyncPageElement {
   $: {
     encryptionCollapse: IronCollapseElement,
@@ -293,7 +277,7 @@
         'sync-prefs-changed', this.handleSyncPrefsChanged_.bind(this));
 
     const router = Router.getInstance();
-    if (router.getCurrentRoute() === getSyncRoutes().SYNC) {
+    if (router.getCurrentRoute() === router.getRoutes().SYNC) {
       this.onNavigateToPage_();
     }
   }
@@ -302,7 +286,7 @@
     super.disconnectedCallback();
 
     const router = Router.getInstance();
-    if (getSyncRoutes().SYNC.contains(router.getCurrentRoute())) {
+    if (router.getRoutes().SYNC.contains(router.getCurrentRoute())) {
       this.onNavigateAwayFromPage_();
     }
 
@@ -317,16 +301,10 @@
   }
 
   // <if expr="chromeos_ash">
-  /**
-   * @return The encryption options SettingsSyncEncryptionOptionsElement.
-   */
   getEncryptionOptions(): SettingsSyncEncryptionOptionsElement|null {
     return this.shadowRoot!.querySelector('settings-sync-encryption-options');
   }
 
-  /**
-   * @return The personalization options SettingsPersonalizationOptionsElement.
-   */
   getPersonalizationOptions(): SettingsPersonalizationOptionsElement|null {
     return this.shadowRoot!.querySelector('settings-personalization-options');
   }
@@ -338,16 +316,6 @@
   }
   // </if>
 
-  private showActivityControls_(): boolean {
-    // <if expr="chromeos_ash">
-    // Should be hidden in OS settings.
-    if (loadTimeData.getBoolean('isOSSettings')) {
-      return false;
-    }
-    // </if>
-    return true;
-  }
-
   private computeSignedIn_(): boolean {
     return !!this.syncStatus.signedIn;
   }
@@ -365,31 +333,9 @@
     return this.syncStatus !== undefined && !!this.syncStatus.managed;
   }
 
-  private getSyncAdvancedPageRoute_(): Route {
-    // <if expr="chromeos_ash">
-    if (loadTimeData.getBoolean('isOSSettings')) {
-      // In OS settings on ChromeOS a different page is used to show the list of
-      // sync data types.
-      return getSyncRoutes().OS_SYNC;
-    }
-    // </if>
-
-    return getSyncRoutes().SYNC_ADVANCED;
-  }
-
-  private getPeoplePageRoute_(): Route {
-    // <if expr="chromeos_ash">
-    if (loadTimeData.getBoolean('isOSSettings')) {
-      // In OS settings on ChromeOS a different page is used as a people page.
-      return getSyncRoutes().OS_PEOPLE;
-    }
-    // </if>
-
-    return getSyncRoutes().PEOPLE;
-  }
-
   private onFocusConfigChange_() {
-    this.focusConfig.set(this.getSyncAdvancedPageRoute_().path, () => {
+    this.focusConfig.set(
+        Router.getInstance().getRoutes().SYNC_ADVANCED.path, () => {
       const toFocus =
           this.shadowRoot!.querySelector<HTMLElement>('#sync-advanced-row');
       assert(toFocus);
@@ -410,7 +356,7 @@
     this.shadowRoot!.querySelector<CrDialogElement>(
                         '#setupCancelDialog')!.close();
     const router = Router.getInstance();
-    router.navigateTo(getSyncRoutes().BASIC);
+    router.navigateTo(router.getRoutes().BASIC);
     chrome.metricsPrivate.recordUserAction(
         'Signin_Signin_ConfirmCancelAdvancedSyncSettings');
   }
@@ -422,12 +368,12 @@
 
   override currentRouteChanged() {
     const router = Router.getInstance();
-    if (router.getCurrentRoute() === getSyncRoutes().SYNC) {
+    if (router.getCurrentRoute() === router.getRoutes().SYNC) {
       this.onNavigateToPage_();
       return;
     }
 
-    if (getSyncRoutes().SYNC.contains(router.getCurrentRoute())) {
+    if (router.getRoutes().SYNC.contains(router.getCurrentRoute())) {
       return;
     }
 
@@ -452,7 +398,7 @@
       // firing). Triggering navigation from within an observer leads to some
       // undefined behavior and runtime errors.
       requestAnimationFrame(() => {
-        router.navigateTo(getSyncRoutes().SYNC);
+        router.navigateTo(router.getRoutes().SYNC);
         this.showSetupCancelDialog_ = true;
         // Flush to make sure that the setup cancel dialog is attached.
         flush();
@@ -476,7 +422,7 @@
 
   private onNavigateToPage_() {
     const router = Router.getInstance();
-    assert(router.getCurrentRoute() === getSyncRoutes().SYNC);
+    assert(router.getCurrentRoute() === router.getRoutes().SYNC);
     if (this.beforeunloadCallback_) {
       return;
     }
@@ -647,8 +593,8 @@
         this.pageStatus_ = pageStatus;
         return;
       case PageStatus.DONE:
-        if (router.getCurrentRoute() === getSyncRoutes().SYNC) {
-          router.navigateTo(this.getPeoplePageRoute_());
+        if (router.getCurrentRoute() === router.getRoutes().SYNC) {
+          router.navigateTo(router.getRoutes().PEOPLE);
         }
         return;
       case PageStatus.PASSPHRASE_FAILED:
@@ -692,7 +638,7 @@
 
   private onSyncAdvancedClick_() {
     const router = Router.getInstance();
-    router.navigateTo(this.getSyncAdvancedPageRoute_());
+    router.navigateTo(router.getRoutes().SYNC_ADVANCED);
   }
 
   /**
@@ -709,7 +655,7 @@
           'Signin_Signin_CancelAdvancedSyncSettings');
     }
     const router = Router.getInstance();
-    router.navigateTo(getSyncRoutes().BASIC);
+    router.navigateTo(router.getRoutes().BASIC);
   }
 
   /**
@@ -720,7 +666,7 @@
     const passphraseInput = this.shadowRoot!.querySelector<CrInputElement>(
         '#existingPassphraseInput');
     const router = Router.getInstance();
-    if (passphraseInput && router.getCurrentRoute() === getSyncRoutes().SYNC) {
+    if (passphraseInput && router.getCurrentRoute() === router.getRoutes().SYNC) {
       passphraseInput.focus();
     }
   }
diff --git a/chrome/browser/resources/settings/privacy_page/personalization_options.html b/chrome/browser/resources/settings/privacy_page/personalization_options.html
index 5c5dea3..aad9cef9 100644
--- a/chrome/browser/resources/settings/privacy_page/personalization_options.html
+++ b/chrome/browser/resources/settings/privacy_page/personalization_options.html
@@ -36,23 +36,12 @@
     </template>
 <if expr="_google_chrome">
 <if expr="chromeos_ash">
-    <!-- On ChromeOS OS settings show the toggle for metrics reporting. -->
-    <template is="dom-if" if="[[!showMetricsReportingAsLink_()]]" restamp>
-      <settings-toggle-button class="hr"
-          pref="{{prefs.cros.metrics.reportingEnabled}}"
-          label="$i18n{enablePersonalizationLogging}"
-          sub-label="$i18n{enablePersonalizationLoggingDesc}">
-      </settings-toggle-button>
-    </template>
-
     <!-- Ash Browser settings show a link to the OS settings instead. -->
-    <template is="dom-if" if="[[showMetricsReportingAsLink_()]]" restamp>
-      <cr-link-row class="hr"
-          label="$i18n{enablePersonalizationLogging}"
-          sub-label="$i18n{enablePersonalizationLoggingDesc}"
-          on-click="onMetricsReportingLinkClick_" external>
-      </cr-link-row>
-    </template>
+    <cr-link-row class="hr"
+        label="$i18n{enablePersonalizationLogging}"
+        sub-label="$i18n{enablePersonalizationLoggingDesc}"
+        on-click="onMetricsReportingLinkClick_" external>
+    </cr-link-row>
 </if><!-- chromeos -->
 <if expr="not chromeos_ash">
     <settings-toggle-button id="metricsReportingControl"
@@ -70,15 +59,14 @@
     </settings-toggle-button>
 </if><!-- not chromeos -->
 </if><!-- _google_chrome -->
-    <template is="dom-if" if="[[showUrlCollectionToggle_()]]" restamp>
-      <settings-toggle-button id="urlCollectionToggle"
-          class="hr"
-          pref="{{prefs.url_keyed_anonymized_data_collection.enabled}}"
-          label="$i18n{urlKeyedAnonymizedDataCollection}"
-          sub-label="$i18n{urlKeyedAnonymizedDataCollectionDesc}">
-      </settings-toggle-button>
-    </template>
+    <settings-toggle-button id="urlCollectionToggle"
+        class="hr"
+        pref="{{prefs.url_keyed_anonymized_data_collection.enabled}}"
+        label="$i18n{urlKeyedAnonymizedDataCollection}"
+        sub-label="$i18n{urlKeyedAnonymizedDataCollectionDesc}">
+    </settings-toggle-button>
 <if expr="_google_chrome">
+<if expr="not chromeos_ash">
     <settings-toggle-button id="spellCheckControl"
         class="hr"
         pref="{{prefs.spellcheck.use_spelling_service}}"
@@ -87,7 +75,7 @@
         sub-label="$i18n{spellingDescription}"
         hidden="[[!showSpellCheckControlToggle_(prefs.spellcheck.dictionaries)]]">
     </settings-toggle-button>
-
+</if>
 <if expr="chromeos_ash">
     <!-- On ChromeOS the toggle for spellcheck is shown in the OS settings,
          and Ash Browser settings show a link to the OS settings instead. -->
diff --git a/chrome/browser/resources/settings/privacy_page/personalization_options.ts b/chrome/browser/resources/settings/privacy_page/personalization_options.ts
index 1324da1..0f869c2 100644
--- a/chrome/browser/resources/settings/privacy_page/personalization_options.ts
+++ b/chrome/browser/resources/settings/privacy_page/personalization_options.ts
@@ -65,11 +65,6 @@
         notify: true,
       },
 
-      /**
-       * TODO(dpapad): Restore actual type !PrivacyPageVisibility after this
-       * file is no longer reused by chrome://os-settings. Dictionary defining
-       * page visibility.
-       */
       pageVisibility: Object,
 
       syncStatus: Object,
@@ -129,12 +124,6 @@
   }
 
   private showPriceEmailNotificationsToggle_(): boolean {
-    // <if expr="chromeos_ash">
-    if (loadTimeData.getBoolean('isOSSettings')) {
-      // Should be hidden in OS settings.
-      return false;
-    }
-    // </if>
     // Only show the toggle when the user signed in.
     return loadTimeData.getBoolean('changePriceEmailNotificationsEnabled') &&
         !!this.syncStatus && !!this.syncStatus.signedIn;
@@ -214,12 +203,6 @@
   // </if>
 
   private showSearchSuggestToggle_(): boolean {
-    // <if expr="chromeos_ash">
-    if (loadTimeData.getBoolean('isOSSettings')) {
-      // Should be hidden in OS settings.
-      return false;
-    }
-    // </if>
     if (this.pageVisibility === undefined) {
       // pageVisibility isn't defined in non-Guest profiles (crbug.com/1288911).
       return true;
@@ -228,25 +211,11 @@
   }
 
   // <if expr="chromeos_ash">
-  private showMetricsReportingAsLink_(): boolean {
-    return !loadTimeData.getBoolean('isOSSettings');
-  }
-
   private onMetricsReportingLinkClick_() {
     window.location.href = loadTimeData.getString('osSyncSetupSettingsUrl');
   }
   // </if>
 
-  private showUrlCollectionToggle_(): boolean {
-    // <if expr="chromeos_ash">
-    // Should be hidden in OS settings.
-    if (loadTimeData.getBoolean('isOSSettings')) {
-      return false;
-    }
-    // </if>
-    return true;
-  }
-
   // <if expr="_google_chrome">
   private onUseSpellingServiceToggle_(event: Event) {
     // If turning on using the spelling service, automatically turn on
@@ -256,24 +225,16 @@
     }
   }
 
+  // <if expr="not chromeos_ash">
   private showSpellCheckControlToggle_(): boolean {
-    // <if expr="chromeos_ash">
-    if (!loadTimeData.getBoolean('isOSSettings')) {
-      // The toggle should be hidden in Ash Browser settings page
-      // (it shows a link to the OS Settings page instead).
-      return false;
-    }
-    // </if>
     return (
         !!(this.prefs as {spellcheck?: any}).spellcheck &&
         (this.getPref('spellcheck.dictionaries').value as string[]).length > 0);
   }
+  // </if><!-- not chromeos -->
 
   // <if expr="chromeos_ash">
   private showSpellCheckControlLink_(): boolean {
-    if (loadTimeData.getBoolean('isOSSettings')) {
-      return false;  // Should be hidden in OS settings.
-    }
     return (
         !!(this.prefs as {spellcheck?: any}).spellcheck &&
         (this.getPref('spellcheck.dictionaries').value as string[]).length > 0);
@@ -286,12 +247,6 @@
   // </if><!-- _google_chrome -->
 
   private shouldShowDriveSuggest_(): boolean {
-    // <if expr="chromeos_ash">
-    if (loadTimeData.getBoolean('isOSSettings')) {
-      // Should be hidden in OS settings.
-      return false;
-    }
-    // </if>
     return loadTimeData.getBoolean('driveSuggestAvailable') &&
         !!this.syncStatus && !!this.syncStatus.signedIn &&
         this.syncStatus.statusAction !== StatusAction.REAUTHENTICATE;
diff --git a/chrome/browser/resources/settings/route.ts b/chrome/browser/resources/settings/route.ts
index 19d49e4f..fe29bca 100644
--- a/chrome/browser/resources/settings/route.ts
+++ b/chrome/browser/resources/settings/route.ts
@@ -6,8 +6,7 @@
 import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
 
 import {pageVisibility} from './page_visibility.js';
-import {Route, Router} from './router.js';
-import {SettingsRoutes} from './settings_routes.js';
+import {Route, Router, SettingsRoutes} from './router.js';
 
 /**
  * Add all of the child routes that originate from the privacy route,
@@ -115,7 +114,7 @@
 /**
  * Adds Route objects for each path.
  */
-function createBrowserSettingsRoutes(): Partial<SettingsRoutes> {
+function createBrowserSettingsRoutes(): SettingsRoutes {
   const r: Partial<SettingsRoutes> = {};
 
   // Root pages.
@@ -124,6 +123,7 @@
 
   r.SEARCH = r.BASIC.createSection(
       '/search', 'search', loadTimeData.getString('searchPageTitle'));
+
   if (!loadTimeData.getBoolean('isGuest')) {
     r.PEOPLE = r.BASIC.createSection(
         '/people', 'people', loadTimeData.getString('peoplePageTitle'));
@@ -255,18 +255,14 @@
           loadTimeData.getString('performancePageTitle'));
     }
   }
-  return r;
+  return r as unknown as SettingsRoutes;
 }
 
 /**
  * @return A router with the browser settings routes.
  */
 export function buildRouter(): Router {
-  return new Router(createBrowserSettingsRoutes() as {
-    BASIC: Route,
-    ADVANCED: Route,
-    ABOUT: Route,
-  });
+  return new Router(createBrowserSettingsRoutes());
 }
 
 Router.setInstance(buildRouter());
diff --git a/chrome/browser/resources/settings/router.ts b/chrome/browser/resources/settings/router.ts
new file mode 100644
index 0000000..384f546
--- /dev/null
+++ b/chrome/browser/resources/settings/router.ts
@@ -0,0 +1,487 @@
+// Copyright 2019 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import './i18n_setup.js';
+
+import {assert, assertNotReached} from 'chrome://resources/js/assert_ts.js';
+import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
+import {dedupingMixin, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+/**
+ * Specifies all possible routes in settings.
+ */
+export interface SettingsRoutes {
+  ABOUT: Route;
+  ACCESSIBILITY: Route;
+  ADDRESSES: Route;
+  ADVANCED: Route;
+  APPEARANCE: Route;
+  AUTOFILL: Route;
+  BASIC: Route;
+  CAPTIONS: Route;
+  CERTIFICATES: Route;
+  CHECK_PASSWORDS: Route;
+  CHROME_CLEANUP: Route;
+  CLEAR_BROWSER_DATA: Route;
+  COOKIES: Route;
+  DEFAULT_BROWSER: Route;
+  DOWNLOADS: Route;
+  EDIT_DICTIONARY: Route;
+  FONTS: Route;
+  IMPORT_DATA: Route;
+  INCOMPATIBLE_APPLICATIONS: Route;
+  LANGUAGES: Route;
+  MANAGE_PROFILE: Route;
+  ON_STARTUP: Route;
+  PASSKEYS: Route;
+  PASSWORDS: Route;
+  PASSWORD_VIEW: Route;
+  DEVICE_PASSWORDS: Route;
+  PAYMENTS: Route;
+  PEOPLE: Route;
+  PERFORMANCE: Route;
+  PRIVACY: Route;
+  PRIVACY_GUIDE: Route;
+  PRIVACY_SANDBOX: Route;
+  PRIVACY_SANDBOX_AD_MEASUREMENT: Route;
+  PRIVACY_SANDBOX_FLEDGE: Route;
+  PRIVACY_SANDBOX_TOPICS: Route;
+  RESET: Route;
+  RESET_DIALOG: Route;
+  SAFETY_CHECK: Route;
+  SEARCH: Route;
+  SEARCH_ENGINES: Route;
+  SECURITY: Route;
+  SECURITY_KEYS: Route;
+  SECURITY_KEYS_PHONES: Route;
+  SIGN_OUT: Route;
+  SITE_SETTINGS: Route;
+  SITE_SETTINGS_ADS: Route;
+  SITE_SETTINGS_ALL: Route;
+  SITE_SETTINGS_AR: Route;
+  SITE_SETTINGS_AUTOMATIC_DOWNLOADS: Route;
+  SITE_SETTINGS_BACKGROUND_SYNC: Route;
+  SITE_SETTINGS_BLUETOOTH_DEVICES: Route;
+  SITE_SETTINGS_BLUETOOTH_SCANNING: Route;
+  SITE_SETTINGS_CAMERA: Route;
+  SITE_SETTINGS_CLIPBOARD: Route;
+  SITE_SETTINGS_COOKIES: Route;
+  SITE_SETTINGS_FEDERATED_IDENTITY_API: Route;
+  SITE_SETTINGS_HANDLERS: Route;
+  SITE_SETTINGS_HID_DEVICES: Route;
+  SITE_SETTINGS_IDLE_DETECTION: Route;
+  SITE_SETTINGS_IMAGES: Route;
+  SITE_SETTINGS_LOCAL_FONTS: Route;
+  SITE_SETTINGS_MIXEDSCRIPT: Route;
+  SITE_SETTINGS_JAVASCRIPT: Route;
+  SITE_SETTINGS_SENSORS: Route;
+  SITE_SETTINGS_SOUND: Route;
+  SITE_SETTINGS_LOCATION: Route;
+  SITE_SETTINGS_MICROPHONE: Route;
+  SITE_SETTINGS_MIDI_DEVICES: Route;
+  SITE_SETTINGS_FILE_SYSTEM_WRITE: Route;
+  SITE_SETTINGS_NOTIFICATIONS: Route;
+  SITE_SETTINGS_PAYMENT_HANDLER: Route;
+  SITE_SETTINGS_PDF_DOCUMENTS: Route;
+  SITE_SETTINGS_POPUPS: Route;
+  SITE_SETTINGS_PROTECTED_CONTENT: Route;
+  SITE_SETTINGS_SITE_DATA: Route;
+  SITE_SETTINGS_SITE_DETAILS: Route;
+  SITE_SETTINGS_USB_DEVICES: Route;
+  SITE_SETTINGS_SERIAL_PORTS: Route;
+  SITE_SETTINGS_VR: Route;
+  SITE_SETTINGS_WINDOW_MANAGEMENT: Route;
+  SITE_SETTINGS_ZOOM_LEVELS: Route;
+  SPELL_CHECK: Route;
+  SYNC: Route;
+  SYNC_ADVANCED: Route;
+  SYSTEM: Route;
+  TRIGGERED_RESET_DIALOG: Route;
+}
+
+/** Class for navigable routes. */
+export class Route {
+  path: string;
+  parent: Route|null = null;
+  depth: number = 0;
+  title: string|undefined;
+
+  /**
+   * Whether this route corresponds to a navigable dialog. Those routes must
+   * belong to a "section".
+   */
+  isNavigableDialog: boolean = false;
+
+  // Legacy property to provide compatibility with the old routing system.
+  section: string = '';
+
+  constructor(path: string, title?: string) {
+    this.path = path;
+    this.title = title;
+  }
+
+  /**
+   * Returns a new Route instance that's a child of this route.
+   * @param path Extends this route's path if it doesn't contain a
+   *     leading slash.
+   */
+  createChild(path: string, title?: string): Route {
+    assert(path);
+
+    // |path| extends this route's path if it doesn't have a leading slash.
+    // If it does have a leading slash, it's just set as the new route's URL.
+    const newUrl = path[0] === '/' ? path : `${this.path}/${path}`;
+
+    const route = new Route(newUrl, title);
+    route.parent = this;
+    route.section = this.section;
+    route.depth = this.depth + 1;
+
+    return route;
+  }
+
+  /**
+   * Returns a new Route instance that's a child section of this route.
+   * TODO(tommycli): Remove once we've obsoleted the concept of sections.
+   */
+  createSection(path: string, section: string, title?: string): Route {
+    const route = this.createChild(path, title);
+    route.section = section;
+    return route;
+  }
+
+  /**
+   * Returns the absolute path string for this Route, assuming this function
+   * has been called from within chrome://settings.
+   */
+  getAbsolutePath(): string {
+    return window.location.origin + this.path;
+  }
+
+  /**
+   * Returns true if this route matches or is an ancestor of the parameter.
+   */
+  contains(route: Route): boolean {
+    for (let r: Route|null = route; r != null; r = r.parent) {
+      if (this === r) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Returns true if this route is a subpage of a section.
+   */
+  isSubpage(): boolean {
+    return !this.isNavigableDialog && !!this.parent && !!this.section &&
+        this.parent.section === this.section;
+  }
+}
+
+/**
+ * Regular expression that captures the leading slash, the content and the
+ * trailing slash in three different groups.
+ */
+const CANONICAL_PATH_REGEX: RegExp = /(^\/)([\/-\w]+)(\/$)/;
+
+let routerInstance: Router|null = null;
+
+export class Router {
+  /**
+   * List of available routes. This is populated taking into account current
+   * state (like guest mode).
+   */
+  private routes_: SettingsRoutes;
+
+  /**
+   * The current active route. This updated is only by settings.navigateTo
+   * or settings.initializeRouteFromUrl.
+   */
+  currentRoute: Route;
+
+  /**
+   * The current query parameters. This is updated only by
+   * settings.navigateTo or settings.initializeRouteFromUrl.
+   */
+  private currentQueryParameters_: URLSearchParams = new URLSearchParams();
+
+  private wasLastRouteChangePopstate_: boolean = false;
+
+  private initializeRouteFromUrlCalled_: boolean = false;
+
+  private routeObservers_: Set<RouteObserverMixinInterface> = new Set();
+
+  /** @return The singleton instance. */
+  static getInstance(): Router {
+    assert(routerInstance);
+    return routerInstance;
+  }
+
+  /** @param instance */
+  static setInstance(instance: Router) {
+    assert(!routerInstance);
+    routerInstance = instance;
+  }
+
+  static resetInstanceForTesting(instance: Router) {
+    if (routerInstance) {
+      instance.routeObservers_ = routerInstance.routeObservers_;
+    }
+    routerInstance = instance;
+  }
+
+  constructor(availableRoutes: SettingsRoutes) {
+    this.routes_ = availableRoutes;
+    this.currentRoute = this.routes_.BASIC;
+  }
+
+  addObserver(observer: RouteObserverMixinInterface) {
+    assert(!this.routeObservers_.has(observer));
+    this.routeObservers_.add(observer);
+  }
+
+  removeObserver(observer: RouteObserverMixinInterface) {
+    assert(this.routeObservers_.delete(observer));
+  }
+
+  getRoute(routeName: string): Route {
+    return this.routeDictionary_()[routeName];
+  }
+
+  getRoutes(): SettingsRoutes {
+    return this.routes_;
+  }
+
+  /**
+   * Helper function to set the current route and notify all observers.
+   */
+  setCurrentRoute(route: Route, queryParameters: URLSearchParams,
+                  isPopstate: boolean) {
+    this.recordMetrics(route.path);
+
+    const oldRoute = this.currentRoute;
+    this.currentRoute = route;
+    this.currentQueryParameters_ = queryParameters;
+    this.wasLastRouteChangePopstate_ = isPopstate;
+    new Set(this.routeObservers_).forEach((observer) => {
+      observer.currentRouteChanged(this.currentRoute, oldRoute);
+    });
+
+    this.updateTitle_();
+  }
+
+  /**
+   * Updates the page title to reflect the current route.
+   */
+  private updateTitle_() {
+    if (this.currentRoute.title) {
+      document.title = loadTimeData.getStringF(
+          'settingsAltPageTitle', this.currentRoute.title);
+    } else if (
+        this.currentRoute.isNavigableDialog && this.currentRoute.parent &&
+        this.currentRoute.parent.title) {
+      document.title = loadTimeData.getStringF(
+          'settingsAltPageTitle', this.currentRoute.parent.title);
+    } else if (
+        !this.currentRoute.isSubpage() &&
+        !this.routes_.ABOUT.contains(this.currentRoute)) {
+      document.title = loadTimeData.getString('settings');
+    }
+  }
+
+  getCurrentRoute(): Route {
+    return this.currentRoute;
+  }
+
+  getQueryParameters(): URLSearchParams {
+    return new URLSearchParams(
+        this.currentQueryParameters_);  // Defensive copy.
+  }
+
+  lastRouteChangeWasPopstate(): boolean {
+    return this.wasLastRouteChangePopstate_;
+  }
+
+  private routeDictionary_(): {[key: string]: Route} {
+    return this.routes_ as unknown as {[key: string]: Route};
+  }
+
+  /**
+   * @return The matching canonical route, or null if none matches.
+   */
+  getRouteForPath(path: string): Route|null {
+    // Allow trailing slash in paths.
+    const canonicalPath = path.replace(CANONICAL_PATH_REGEX, '$1$2');
+
+    // TODO(tommycli): Use Object.values once Closure compilation supports it.
+    const matchingKey =
+        Object.keys(this.routes_)
+            .find((key) => this.routeDictionary_()[key].path === canonicalPath);
+
+    return matchingKey ? this.routeDictionary_()[matchingKey] : null;
+  }
+
+  /**
+   * Updates the URL parameters of the current route via exchanging the
+   * window history state. This changes the Settings route path, but doesn't
+   * change the route itself, hence does not push a new route history entry.
+   * Notifies routeChangedObservers.
+   */
+  updateRouteParams(params: URLSearchParams) {
+    let url = this.currentRoute.path;
+    const queryString = params.toString();
+    if (queryString) {
+      url += '?' + queryString;
+    }
+    window.history.replaceState(window.history.state, '', url);
+
+    // We can't call |setCurrentRoute()| for the following, as it would also
+    // update |oldRoute| and |currentRoute|, which should not happen when
+    // only the URL parameters are updated.
+    this.currentQueryParameters_ = params;
+    new Set(this.routeObservers_).forEach((observer) => {
+      observer.currentRouteChanged(this.currentRoute, this.currentRoute);
+    });
+  }
+
+  /**
+   * Navigates to a canonical route and pushes a new history entry.
+   * @param dynamicParameters Navigations to the same
+   *     URL parameters in a different order will still push to history.
+   * @param removeSearch Whether to strip the 'search' URL
+   *     parameter during navigation. Defaults to false.
+   */
+  navigateTo(route: Route, dynamicParameters?: URLSearchParams,
+      removeSearch: boolean = false) {
+    // The ADVANCED route only serves as a parent of subpages, and should not
+    // be possible to navigate to it directly.
+    if (route === this.routes_.ADVANCED) {
+      route = this.routes_.BASIC;
+    }
+
+    const params = dynamicParameters || new URLSearchParams();
+
+    const oldSearchParam = this.getQueryParameters().get('search') || '';
+    const newSearchParam = params.get('search') || '';
+
+    if (!removeSearch && oldSearchParam && !newSearchParam) {
+      params.append('search', oldSearchParam);
+    }
+
+    let url = route.path;
+    const queryString = params.toString();
+    if (queryString) {
+      url += '?' + queryString;
+    }
+
+    // History serializes the state, so we don't push the actual route object.
+    window.history.pushState(this.currentRoute.path, '', url);
+    this.setCurrentRoute(route, params, false);
+  }
+
+  /**
+   * Navigates to the previous route if it has an equal or lesser depth.
+   * If there is no previous route in history meeting those requirements,
+   * this navigates to the immediate parent. This will never exit Settings.
+   */
+  navigateToPreviousRoute() {
+    let previousRoute = null;
+    if (window.history.state) {
+      previousRoute = this.getRouteForPath(window.history.state);
+      assert(previousRoute);
+    }
+
+    if (previousRoute && previousRoute.depth <= this.currentRoute.depth) {
+      window.history.back();
+    } else {
+      this.navigateTo(this.currentRoute.parent || this.routes_.BASIC);
+    }
+  }
+
+  /**
+   * Initialize the route and query params from the URL.
+   */
+  initializeRouteFromUrl() {
+    assert(!this.initializeRouteFromUrlCalled_);
+    this.initializeRouteFromUrlCalled_ = true;
+
+    const route = this.getRouteForPath(window.location.pathname);
+
+    // Record all correct paths entered on the settings page, and
+    // as all incorrect paths are routed to the main settings page,
+    // record all incorrect paths as hitting the main settings page.
+    this.recordMetrics(route ? route.path : this.routes_.BASIC.path);
+
+    // Never allow direct navigation to ADVANCED.
+    if (route && route !== this.routes_.ADVANCED) {
+      this.currentRoute = route;
+      this.currentQueryParameters_ =
+          new URLSearchParams(window.location.search);
+    } else {
+      window.history.replaceState(undefined, '', this.routes_.BASIC.path);
+    }
+
+    this.updateTitle_();
+  }
+
+  /**
+   * Make a UMA note about visiting this URL path.
+   * @param urlPath The url path (only).
+   */
+  recordMetrics(urlPath: string) {
+    assert(!urlPath.startsWith('chrome://'));
+    assert(!urlPath.startsWith('settings'));
+    assert(urlPath.startsWith('/'));
+    assert(!urlPath.match(/\?/g));
+
+    const metricName = 'WebUI.Settings.PathVisited';
+    chrome.metricsPrivate.recordSparseValueWithPersistentHash(
+        metricName, urlPath);
+  }
+
+  resetRouteForTesting() {
+    this.initializeRouteFromUrlCalled_ = false;
+    this.wasLastRouteChangePopstate_ = false;
+    this.currentRoute = this.routes_.BASIC;
+    this.currentQueryParameters_ = new URLSearchParams();
+  }
+}
+
+type Constructor<T> = new (...args: any[]) => T;
+
+export const RouteObserverMixin = dedupingMixin(
+    <T extends Constructor<PolymerElement>>(superClass: T): T&
+    Constructor<RouteObserverMixinInterface> => {
+
+      class RouteObserverMixin extends superClass implements
+          RouteObserverMixinInterface {
+        override connectedCallback() {
+          super.connectedCallback();
+
+          assert(routerInstance);
+          routerInstance.addObserver(this);
+
+          // Emulating Polymer data bindings, the observer is called when the
+          // element starts observing the route.
+          this.currentRouteChanged(routerInstance.currentRoute, undefined);
+        }
+
+        override disconnectedCallback() {
+          super.disconnectedCallback();
+
+          assert(routerInstance);
+          routerInstance.removeObserver(this);
+        }
+
+        currentRouteChanged(_newRoute: Route, _oldRoute?: Route) {
+          assertNotReached();
+        }
+      }
+      return RouteObserverMixin;
+    });
+
+export interface RouteObserverMixinInterface {
+  currentRouteChanged(newRoute: Route, oldRoute?: Route): void;
+}
diff --git a/chrome/browser/resources/settings/settings.ts b/chrome/browser/resources/settings/settings.ts
index db35018..21d4f4b8 100644
--- a/chrome/browser/resources/settings/settings.ts
+++ b/chrome/browser/resources/settings/settings.ts
@@ -75,7 +75,7 @@
 export {ResetBrowserProxy, ResetBrowserProxyImpl} from './reset_page/reset_browser_proxy.js';
 export {SettingsResetProfileBannerElement} from './reset_page/reset_profile_banner.js';
 export {buildRouter, routes} from './route.js';
-export {MinimumRoutes, Route, Router} from './router.js';
+export {SettingsRoutes, Route, Router} from './router.js';
 export {SafetyCheckBrowserProxy, SafetyCheckBrowserProxyImpl, SafetyCheckCallbackConstants, SafetyCheckChromeCleanerStatus, SafetyCheckExtensionsStatus, SafetyCheckParentStatus, SafetyCheckPasswordsStatus, SafetyCheckSafeBrowsingStatus, SafetyCheckUpdatesStatus} from './safety_check_page/safety_check_browser_proxy.js';
 export {SafetyCheckIconStatus, SettingsSafetyCheckChildElement} from './safety_check_page/safety_check_child.js';
 // <if expr="_google_chrome and is_win">
diff --git a/chrome/browser/resources/settings/settings_page/main_page_mixin.ts b/chrome/browser/resources/settings/settings_page/main_page_mixin.ts
index e4a17d8..36c490c9 100644
--- a/chrome/browser/resources/settings/settings_page/main_page_mixin.ts
+++ b/chrome/browser/resources/settings/settings_page/main_page_mixin.ts
@@ -11,7 +11,7 @@
 import {ensureLazyLoaded} from '../ensure_lazy_loaded.js';
 import {loadTimeData} from '../i18n_setup.js';
 import {routes} from '../route.js';
-import {MinimumRoutes, Route, Router} from '../router.js';
+import {Route, Router} from '../router.js';
 // clang-format on
 
 /**
@@ -44,7 +44,7 @@
   if (!route) {
     return RouteState.INITIAL;
   }
-  const routes = Router.getInstance().getRoutes() as MinimumRoutes;
+  const routes = Router.getInstance().getRoutes();
   if (route === routes.BASIC) {
     return RouteState.TOP_LEVEL;
   }
@@ -125,8 +125,8 @@
         }
 
         private shouldExpandAdvanced_(route: Route): boolean {
-          const routes = Router.getInstance().getRoutes() as MinimumRoutes;
-          return this.tagName === 'SETTINGS-BASIC-PAGE' && routes.ADVANCED &&
+          const routes = Router.getInstance().getRoutes();
+          return this.tagName === 'SETTINGS-BASIC-PAGE' && !!routes.ADVANCED &&
               routes.ADVANCED.contains(route);
         }
 
diff --git a/chrome/browser/resources/settings/settings_routes.ts b/chrome/browser/resources/settings/settings_routes.ts
deleted file mode 100644
index 021b9c1..0000000
--- a/chrome/browser/resources/settings/settings_routes.ts
+++ /dev/null
@@ -1,97 +0,0 @@
-// Copyright 2019 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import {Route} from './router.js';
-
-/**
- * Specifies all possible routes in settings.
- */
-export interface SettingsRoutes {
-  ABOUT: Route;
-  ACCESSIBILITY: Route;
-  ADVANCED: Route;
-  ADDRESSES: Route;
-  APPEARANCE: Route;
-  AUTOFILL: Route;
-  BASIC: Route;
-  CAPTIONS: Route;
-  CERTIFICATES: Route;
-  CHECK_PASSWORDS: Route;
-  CHROME_CLEANUP: Route;
-  CLEAR_BROWSER_DATA: Route;
-  COOKIES: Route;
-  DEFAULT_BROWSER: Route;
-  DOWNLOADS: Route;
-  EDIT_DICTIONARY: Route;
-  FONTS: Route;
-  IMPORT_DATA: Route;
-  INCOMPATIBLE_APPLICATIONS: Route;
-  LANGUAGES: Route;
-  MANAGE_PROFILE: Route;
-  ON_STARTUP: Route;
-  PASSKEYS: Route;
-  PASSWORDS: Route;
-  PASSWORD_VIEW: Route;
-  DEVICE_PASSWORDS: Route;
-  PAYMENTS: Route;
-  PEOPLE: Route;
-  PERFORMANCE: Route;
-  PRIVACY: Route;
-  PRIVACY_GUIDE: Route;
-  PRIVACY_SANDBOX: Route;
-  PRIVACY_SANDBOX_AD_MEASUREMENT: Route;
-  PRIVACY_SANDBOX_FLEDGE: Route;
-  PRIVACY_SANDBOX_TOPICS: Route;
-  RESET: Route;
-  RESET_DIALOG: Route;
-  SAFETY_CHECK: Route;
-  SEARCH: Route;
-  SEARCH_ENGINES: Route;
-  SECURITY: Route;
-  SECURITY_KEYS: Route;
-  SECURITY_KEYS_PHONES: Route;
-  SIGN_OUT: Route;
-  SITE_SETTINGS: Route;
-  SITE_SETTINGS_ADS: Route;
-  SITE_SETTINGS_ALL: Route;
-  SITE_SETTINGS_AR: Route;
-  SITE_SETTINGS_AUTOMATIC_DOWNLOADS: Route;
-  SITE_SETTINGS_BACKGROUND_SYNC: Route;
-  SITE_SETTINGS_BLUETOOTH_DEVICES: Route;
-  SITE_SETTINGS_BLUETOOTH_SCANNING: Route;
-  SITE_SETTINGS_CAMERA: Route;
-  SITE_SETTINGS_CLIPBOARD: Route;
-  SITE_SETTINGS_COOKIES: Route;
-  SITE_SETTINGS_FEDERATED_IDENTITY_API: Route;
-  SITE_SETTINGS_HANDLERS: Route;
-  SITE_SETTINGS_HID_DEVICES: Route;
-  SITE_SETTINGS_IDLE_DETECTION: Route;
-  SITE_SETTINGS_IMAGES: Route;
-  SITE_SETTINGS_LOCAL_FONTS: Route;
-  SITE_SETTINGS_MIXEDSCRIPT: Route;
-  SITE_SETTINGS_JAVASCRIPT: Route;
-  SITE_SETTINGS_SENSORS: Route;
-  SITE_SETTINGS_SOUND: Route;
-  SITE_SETTINGS_LOCATION: Route;
-  SITE_SETTINGS_MICROPHONE: Route;
-  SITE_SETTINGS_MIDI_DEVICES: Route;
-  SITE_SETTINGS_FILE_SYSTEM_WRITE: Route;
-  SITE_SETTINGS_NOTIFICATIONS: Route;
-  SITE_SETTINGS_PAYMENT_HANDLER: Route;
-  SITE_SETTINGS_PDF_DOCUMENTS: Route;
-  SITE_SETTINGS_POPUPS: Route;
-  SITE_SETTINGS_PROTECTED_CONTENT: Route;
-  SITE_SETTINGS_SITE_DATA: Route;
-  SITE_SETTINGS_SITE_DETAILS: Route;
-  SITE_SETTINGS_USB_DEVICES: Route;
-  SITE_SETTINGS_SERIAL_PORTS: Route;
-  SITE_SETTINGS_VR: Route;
-  SITE_SETTINGS_WINDOW_MANAGEMENT: Route;
-  SITE_SETTINGS_ZOOM_LEVELS: Route;
-  SPELL_CHECK: Route;
-  SYNC: Route;
-  SYNC_ADVANCED: Route;
-  SYSTEM: Route;
-  TRIGGERED_RESET_DIALOG: Route;
-}
diff --git a/chrome/browser/resources/side_panel/bookmarks/bookmarks_api_proxy.ts b/chrome/browser/resources/side_panel/bookmarks/bookmarks_api_proxy.ts
index 3114f77..49b4181 100644
--- a/chrome/browser/resources/side_panel/bookmarks/bookmarks_api_proxy.ts
+++ b/chrome/browser/resources/side_panel/bookmarks/bookmarks_api_proxy.ts
@@ -11,7 +11,7 @@
 
 export interface BookmarksApiProxy {
   callbackRouter: {[key: string]: ChromeEvent<Function>};
-  bookmarkCurrentTab(): void;
+  bookmarkCurrentTabInFolder(folderId: string): void;
   cutBookmark(id: string): void;
   copyBookmark(id: string): Promise<void>;
   getActiveUrl(): Promise<string|undefined>;
@@ -46,8 +46,8 @@
         this.handler.$.bindNewPipeAndPassReceiver());
   }
 
-  bookmarkCurrentTab() {
-    this.handler.bookmarkCurrentTab();
+  bookmarkCurrentTabInFolder(folderId: string) {
+    this.handler.bookmarkCurrentTabInFolder(BigInt(folderId));
   }
 
   cutBookmark(id: string) {
diff --git a/chrome/browser/resources/side_panel/bookmarks/power_bookmarks_list.ts b/chrome/browser/resources/side_panel/bookmarks/power_bookmarks_list.ts
index 7e5a5f8b..7457908 100644
--- a/chrome/browser/resources/side_panel/bookmarks/power_bookmarks_list.ts
+++ b/chrome/browser/resources/side_panel/bookmarks/power_bookmarks_list.ts
@@ -676,7 +676,11 @@
   }
 
   private onAddTabClicked_() {
-    this.bookmarksApi_.bookmarkCurrentTab();
+    const newParent = this.getActiveFolder_() ||
+        this.folders_.find(
+            (folder: chrome.bookmarks.BookmarkTreeNode) =>
+                folder.id === loadTimeData.getString('otherBookmarksId'));
+    this.bookmarksApi_.bookmarkCurrentTabInFolder(newParent!.id);
   }
 
   private disableBackButton_(): boolean {
diff --git a/chrome/browser/resources/side_panel/customize_chrome/BUILD.gn b/chrome/browser/resources/side_panel/customize_chrome/BUILD.gn
index 4270c52..49168664 100644
--- a/chrome/browser/resources/side_panel/customize_chrome/BUILD.gn
+++ b/chrome/browser/resources/side_panel/customize_chrome/BUILD.gn
@@ -21,6 +21,7 @@
     "colors.ts",
     "appearance.ts",
     "themes.ts",
+    "theme_snapshot.ts",
   ]
 
   non_web_component_files = [ "customize_chrome_api_proxy.ts" ]
diff --git a/chrome/browser/resources/side_panel/customize_chrome/appearance.html b/chrome/browser/resources/side_panel/customize_chrome/appearance.html
index 1fe748d..b81fbde 100644
--- a/chrome/browser/resources/side_panel/customize_chrome/appearance.html
+++ b/chrome/browser/resources/side_panel/customize_chrome/appearance.html
@@ -3,5 +3,6 @@
     margin-bottom: 24px;
   }
 </style>
+<customize-chrome-theme-snapshot></customize-chrome-theme-snapshot>
 <button id="editThemeButton" on-click="onEditThemeClicked_">Edit Theme</button>
 <customize-chrome-colors></customize-chrome-colors>
diff --git a/chrome/browser/resources/side_panel/customize_chrome/appearance.ts b/chrome/browser/resources/side_panel/customize_chrome/appearance.ts
index 5b8b221..4bce10af 100644
--- a/chrome/browser/resources/side_panel/customize_chrome/appearance.ts
+++ b/chrome/browser/resources/side_panel/customize_chrome/appearance.ts
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 import './colors.js';
+import './theme_snapshot.js';
 
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
diff --git a/chrome/browser/resources/side_panel/customize_chrome/color.html b/chrome/browser/resources/side_panel/customize_chrome/color.html
index 39dc417..be49563 100644
--- a/chrome/browser/resources/side_panel/customize_chrome/color.html
+++ b/chrome/browser/resources/side_panel/customize_chrome/color.html
@@ -1,4 +1,8 @@
 <style>
+  :host {
+    cursor: pointer;
+  }
+
   :host,
   svg {
     border-radius: 25px;
diff --git a/chrome/browser/resources/side_panel/customize_chrome/colors.html b/chrome/browser/resources/side_panel/customize_chrome/colors.html
index 35cd768e..6138b869 100644
--- a/chrome/browser/resources/side_panel/customize_chrome/colors.html
+++ b/chrome/browser/resources/side_panel/customize_chrome/colors.html
@@ -11,14 +11,25 @@
 </style>
 <!-- TODO(crbug.com/1384229): Make grid adaptive. -->
 <cr-grid columns="4">
-  <!-- TODO(crbug.com/1384229): Add extracted/default color. -->
-  <template is="dom-repeat" items="[[colors_]]">
+  <!-- TODO(crbug.com/1384229): Add extracted color. -->
+  <customize-chrome-check-mark-wrapper checked>
+    <customize-chrome-color
+        id="defaultColor"
+        background-color="[[defaultColor_.background]]"
+        foreground-color="[[defaultColor_.foreground]]"
+        title="$i18n{defaultColorName}"
+        on-click="onDefaultColorClick_">
+    </customize-chrome-color>
+  </customize-chrome-check-mark-wrapper>
+  <template id="chromeColors" is="dom-repeat" items="[[colors_]]">
     <!-- TODO(crbug.com/1384229): Only set checked if selected. -->
     <customize-chrome-check-mark-wrapper checked>
       <customize-chrome-color
+          class="chrome-color"
           background-color="[[item.background]]"
           foreground-color="[[item.foreground]]"
-          title="[[item.name]]">
+          title="[[item.name]]"
+          on-click="onChromeColorClick_">
       </customize-chrome-color>
     </customize-chrome-check-mark-wrapper>
   </template>
diff --git a/chrome/browser/resources/side_panel/customize_chrome/colors.ts b/chrome/browser/resources/side_panel/customize_chrome/colors.ts
index a1d207c5..926fd80 100644
--- a/chrome/browser/resources/side_panel/customize_chrome/colors.ts
+++ b/chrome/browser/resources/side_panel/customize_chrome/colors.ts
@@ -6,12 +6,36 @@
 import './color.js';
 import './check_mark_wrapper.js';
 
-import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {SkColor} from 'chrome://resources/mojo/skia/public/mojom/skcolor.mojom-webui.js';
+import {DomRepeat, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
+import {ColorElement} from './color.js';
 import {getTemplate} from './colors.html.js';
-import {ChromeColor} from './customize_chrome.mojom-webui.js';
+import {ChromeColor, Theme} from './customize_chrome.mojom-webui.js';
 import {CustomizeChromeApiProxy} from './customize_chrome_api_proxy.js';
 
+export interface Color {
+  background: SkColor;
+  foreground: SkColor;
+}
+
+export const LIGHT_DEFAULT_COLOR: Color = {
+  background: {value: 0xffffffff},
+  foreground: {value: 0xffdee1e6},
+};
+
+export const DARK_DEFAULT_COLOR: Color = {
+  background: {value: 0xff323639},
+  foreground: {value: 0xff202124},
+};
+
+export interface ColorsElement {
+  $: {
+    defaultColor: ColorElement,
+    chromeColors: DomRepeat,
+  };
+}
+
 export class ColorsElement extends PolymerElement {
   static get is() {
     return 'customize-chrome-colors';
@@ -23,11 +47,18 @@
 
   static get properties() {
     return {
+      defaultColor_: {
+        type: Object,
+        computed: 'computeDefaultColor_(theme_)',
+      },
       colors_: Array,
+      theme_: Object,
     };
   }
 
   private colors_: ChromeColor[];
+  private theme_: Theme;
+  private setThemeListenerId_: number|null = null;
 
   constructor() {
     super();
@@ -36,6 +67,36 @@
           this.colors_ = colors;
         });
   }
+
+  override connectedCallback() {
+    super.connectedCallback();
+    this.setThemeListenerId_ =
+        CustomizeChromeApiProxy.getInstance()
+            .callbackRouter.setTheme.addListener((theme: Theme) => {
+              this.theme_ = theme;
+            });
+    CustomizeChromeApiProxy.getInstance().handler.updateTheme();
+  }
+
+  override disconnectedCallback() {
+    super.disconnectedCallback();
+    CustomizeChromeApiProxy.getInstance().callbackRouter.removeListener(
+        this.setThemeListenerId_!);
+  }
+
+  private computeDefaultColor_(): Color {
+    return this.theme_.systemDarkMode ? DARK_DEFAULT_COLOR :
+                                        LIGHT_DEFAULT_COLOR;
+  }
+
+  private onDefaultColorClick_() {
+    CustomizeChromeApiProxy.getInstance().handler.setDefaultColor();
+  }
+
+  private onChromeColorClick_(e: Event) {
+    CustomizeChromeApiProxy.getInstance().handler.setForegroundColor(
+        this.$.chromeColors.itemForElement(e.target as HTMLElement).foreground);
+  }
 }
 
 declare global {
diff --git a/chrome/browser/resources/side_panel/customize_chrome/theme_snapshot.html b/chrome/browser/resources/side_panel/customize_chrome/theme_snapshot.html
new file mode 100644
index 0000000..c3f8d30
--- /dev/null
+++ b/chrome/browser/resources/side_panel/customize_chrome/theme_snapshot.html
@@ -0,0 +1,29 @@
+<style>
+  #themeSnapshot {
+    align-items: center;
+    display: flex;
+    flex-wrap: wrap;
+    justify-content: center;
+    margin-inline-start: 16px;
+    width: 288px;
+  }
+
+  #themeSnapshot img {
+    border-radius: 20px;
+    height: 162px;
+    margin-bottom: 4px;
+    object-fit: fill;
+  }
+
+  #themeTitle {
+    margin-bottom: 8px;
+    overflow: hidden;
+    white-space: nowrap;
+  }
+</style>
+<div id="themeSnapshot">
+  <img is="cr-auto-img" auto-src="[[theme_.backgroundImage.url.url]]"
+      draggable="false">
+  </img>
+  <label id="themeTitle">[[theme_.backgroundImage.title]]</label>
+</div>
diff --git a/chrome/browser/resources/side_panel/customize_chrome/theme_snapshot.ts b/chrome/browser/resources/side_panel/customize_chrome/theme_snapshot.ts
new file mode 100644
index 0000000..dd60169
--- /dev/null
+++ b/chrome/browser/resources/side_panel/customize_chrome/theme_snapshot.ts
@@ -0,0 +1,65 @@
+// 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.
+
+import 'chrome://resources/cr_elements/cr_auto_img/cr_auto_img.js';
+
+import {assert} from 'chrome://resources/js/assert_ts.js';
+import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {CustomizeChromePageCallbackRouter, CustomizeChromePageHandlerInterface, Theme} from './customize_chrome.mojom-webui.js';
+import {CustomizeChromeApiProxy} from './customize_chrome_api_proxy.js';
+import {getTemplate} from './theme_snapshot.html.js';
+
+/** Element used to display a snapshot of a NTP custom background. */
+export class ThemeSnapshotElement extends PolymerElement {
+  static get is() {
+    return 'customize-chrome-theme-snapshot';
+  }
+
+  static get template() {
+    return getTemplate();
+  }
+
+  static get properties() {
+    return {
+      theme_: Object,
+    };
+  }
+
+  private theme_: Theme;
+  private setThemeListenerId_: number|null = null;
+
+  private callbackRouter_: CustomizeChromePageCallbackRouter;
+  private pageHandler_: CustomizeChromePageHandlerInterface;
+
+  constructor() {
+    super();
+    this.pageHandler_ = CustomizeChromeApiProxy.getInstance().handler;
+    this.callbackRouter_ = CustomizeChromeApiProxy.getInstance().callbackRouter;
+  }
+
+  override connectedCallback() {
+    super.connectedCallback();
+    this.setThemeListenerId_ =
+        this.callbackRouter_.setTheme.addListener((theme: Theme) => {
+          this.theme_ = theme;
+        });
+    this.pageHandler_.updateTheme();
+  }
+
+
+  override disconnectedCallback() {
+    super.disconnectedCallback();
+    assert(this.setThemeListenerId_);
+    this.callbackRouter_.removeListener(this.setThemeListenerId_);
+  }
+}
+
+declare global {
+  interface HTMLElementTagNameMap {
+    'customize-chrome-theme-snapshot': ThemeSnapshotElement;
+  }
+}
+
+customElements.define(ThemeSnapshotElement.is, ThemeSnapshotElement);
diff --git a/chrome/browser/resources/side_panel/read_anything/app.ts b/chrome/browser/resources/side_panel/read_anything/app.ts
index 78427fb..f5f7ac84 100644
--- a/chrome/browser/resources/side_panel/read_anything/app.ts
+++ b/chrome/browser/resources/side_panel/read_anything/app.ts
@@ -129,11 +129,11 @@
     // would create a shadow node element representing each AXNode, because
     // experimentation found the shadow node creation to be ~8-10x slower than
     // constructing and appending nodes directly to the container element.
-    const displayRootId = chrome.readAnything.displayRootId;
-    if (!displayRootId) {
+    const rootId = chrome.readAnything.rootId;
+    if (!rootId) {
       return;
     }
-    const node = this.buildSubtree_(displayRootId);
+    const node = this.buildSubtree_(rootId);
     container.appendChild(node);
   }
 
diff --git a/chrome/browser/resources/side_panel/read_anything/read_anything.d.ts b/chrome/browser/resources/side_panel/read_anything/read_anything.d.ts
index 0e069b1..e1548b1 100644
--- a/chrome/browser/resources/side_panel/read_anything/read_anything.d.ts
+++ b/chrome/browser/resources/side_panel/read_anything/read_anything.d.ts
@@ -11,7 +11,7 @@
     /////////////////////////////////////////////////////////////////////
 
     // The root AXNodeID of the tree to be displayed.
-    let displayRootId: number;
+    let rootId: number;
 
     // Items in the ReadAnythingTheme struct, see read_anything.mojom for info.
     let fontName: string;
diff --git a/chrome/browser/resources/tab_strip/tab_list.ts b/chrome/browser/resources/tab_strip/tab_list.ts
index 190b6c8b..be1db0f 100644
--- a/chrome/browser/resources/tab_strip/tab_list.ts
+++ b/chrome/browser/resources/tab_strip/tab_list.ts
@@ -235,7 +235,7 @@
 
     const callbackRouter = this.tabsApi_.getCallbackRouter();
     callbackRouter.layoutChanged.addListener(
-        this.applyCSSDictionary_.bind(this));
+        this.applyCssDictionary_.bind(this));
 
     callbackRouter.tabThumbnailUpdated.addListener(
         this.tabThumbnailUpdated_.bind(this));
@@ -326,7 +326,7 @@
     this.currentScrollUpdateFrame_ = requestAnimationFrame(onAnimationFrame);
   }
 
-  private applyCSSDictionary_(dictionary: {[key: string]: string}) {
+  private applyCssDictionary_(dictionary: {[key: string]: string}) {
     for (const [cssVariable, value] of Object.entries(dictionary)) {
       this.style.setProperty(cssVariable, value);
     }
@@ -339,7 +339,7 @@
 
   connectedCallback() {
     this.tabsApi_.getLayout().then(
-        ({layout}) => this.applyCSSDictionary_(layout));
+        ({layout}) => this.applyCssDictionary_(layout));
 
     const getTabsStartTimestamp = Date.now();
     this.tabsApi_.getTabs().then(({tabs}) => {
diff --git a/chrome/browser/resources/webui_gallery/demos/cr_action_menu/cr_action_menu_demo.html b/chrome/browser/resources/webui_gallery/demos/cr_action_menu/cr_action_menu_demo.html
index e8823748..161dc39 100644
--- a/chrome/browser/resources/webui_gallery/demos/cr_action_menu/cr_action_menu_demo.html
+++ b/chrome/browser/resources/webui_gallery/demos/cr_action_menu/cr_action_menu_demo.html
@@ -59,7 +59,7 @@
       <label id="alignmentXLabel">AlignmentX</label>
       <select
           aria-labelledby="alignmentXLabel"
-          on-change="onAlignmentXChanged_">
+          on-change="onAlignmentChangedX_">
         <option value="">Default</option>
         <template is="dom-repeat" items="[[alignmentOptions_]]">
           <option value="[[item]]">[[item]]</option>
@@ -71,7 +71,7 @@
       <label id="alignmentYLabel">AlignmentY</label>
       <select
           aria-labelledby="alignmentYLabel"
-          on-change="onAlignmentYChanged_">
+          on-change="onAlignmentChangedY_">
         <option value="">Default</option>
         <template is="dom-repeat" items="[[alignmentOptions_]]">
           <option value="[[item]]">[[item]]</option>
diff --git a/chrome/browser/resources/webui_gallery/demos/cr_action_menu/cr_action_menu_demo.ts b/chrome/browser/resources/webui_gallery/demos/cr_action_menu/cr_action_menu_demo.ts
index 0e5e52b..2baac4a4 100644
--- a/chrome/browser/resources/webui_gallery/demos/cr_action_menu/cr_action_menu_demo.ts
+++ b/chrome/browser/resources/webui_gallery/demos/cr_action_menu/cr_action_menu_demo.ts
@@ -68,7 +68,7 @@
     this.addEventListener('contextmenu', this.onContextMenu_.bind(this));
   }
 
-  private onAlignmentXChanged_(event: Event) {
+  private onAlignmentChangedX_(event: Event) {
     const select = event.target as HTMLSelectElement;
     if (!select.value) {
       delete this.showAtPositionConfig_.anchorAlignmentX;
@@ -78,7 +78,7 @@
     this.showAtPositionConfig_.anchorAlignmentX = AnchorAlignment[key];
   }
 
-  private onAlignmentYChanged_(event: Event) {
+  private onAlignmentChangedY_(event: Event) {
     const select = event.target as HTMLSelectElement;
     if (!select.value) {
       delete this.showAtPositionConfig_.anchorAlignmentY;
diff --git a/chrome/browser/safe_browsing/advanced_protection_status_manager.cc b/chrome/browser/safe_browsing/advanced_protection_status_manager.cc
index 4c2dcdd..7c5dd62a8 100644
--- a/chrome/browser/safe_browsing/advanced_protection_status_manager.cc
+++ b/chrome/browser/safe_browsing/advanced_protection_status_manager.cc
@@ -161,19 +161,9 @@
     signin::AccessTokenInfo token_info) {
   DCHECK(access_token_fetcher_);
 
-  if (is_under_advanced_protection_) {
-    // Those already known to be under AP should have much lower error rates.
-    UMA_HISTOGRAM_ENUMERATION(
-        "SafeBrowsing.AdvancedProtection.APTokenFetchStatus", error.state(),
-        GoogleServiceAuthError::NUM_STATES);
-  }
-
   if (error.state() == GoogleServiceAuthError::NONE)
     OnGetIDToken(account_id, token_info.id_token);
 
-  UMA_HISTOGRAM_ENUMERATION("SafeBrowsing.AdvancedProtection.TokenFetchStatus",
-                            error.state(), GoogleServiceAuthError::NUM_STATES);
-
   access_token_fetcher_.reset();
 
   // If failure is transient, we'll retry in 5 minutes.
diff --git a/chrome/browser/safe_browsing/advanced_protection_status_manager_unittest.cc b/chrome/browser/safe_browsing/advanced_protection_status_manager_unittest.cc
index 48b901f..b7f82af 100644
--- a/chrome/browser/safe_browsing/advanced_protection_status_manager_unittest.cc
+++ b/chrome/browser/safe_browsing/advanced_protection_status_manager_unittest.cc
@@ -31,11 +31,6 @@
     "eyAic2VydmljZXMiOiBbXSB9"  // payload: { "services": [] }
     ".dummy-signature";
 
-static const char* kAPTokenFetchStatusMetric =
-    "SafeBrowsing.AdvancedProtection.APTokenFetchStatus";
-static const char* kTokenFetchStatusMetric =
-    "SafeBrowsing.AdvancedProtection.TokenFetchStatus";
-
 // Helper class that ensure RegisterProfilePrefs() is called on the test
 // PrefService's registry before the IdentityTestEnvironment constructor
 // is invoked.
@@ -128,11 +123,6 @@
                           /* is_transient_error = */ true);
   EXPECT_FALSE(aps_manager.IsUnderAdvancedProtection());
 
-  EXPECT_THAT(histograms.GetAllSamples(kTokenFetchStatusMetric),
-              testing::ElementsAre(base::Bucket(3 /*CONNECTION_FAILED*/, 1)));
-  EXPECT_THAT(histograms.GetAllSamples(kAPTokenFetchStatusMetric),
-              testing::IsEmpty());
-
   // A retry should be scheduled.
   EXPECT_TRUE(aps_manager.IsRefreshScheduled());
   EXPECT_FALSE(
@@ -159,12 +149,6 @@
                           /* is_transient_error = */ false);
   EXPECT_FALSE(aps_manager.IsUnderAdvancedProtection());
 
-  EXPECT_THAT(
-      histograms.GetAllSamples(kTokenFetchStatusMetric),
-      testing::ElementsAre(base::Bucket(1 /*INVALID_GAIA_CREDENTIALS*/, 1)));
-  EXPECT_THAT(histograms.GetAllSamples(kAPTokenFetchStatusMetric),
-              testing::IsEmpty());
-
   // No retry should be scheduled.
   EXPECT_FALSE(aps_manager.IsRefreshScheduled());
   aps_manager.UnsubscribeFromSigninEvents();
@@ -193,11 +177,6 @@
   EXPECT_TRUE(
       pref_service_.HasPrefPath(prefs::kAdvancedProtectionLastRefreshInUs));
 
-  EXPECT_THAT(histograms.GetAllSamples(kTokenFetchStatusMetric),
-              testing::ElementsAre(base::Bucket(0 /*NONE*/, 1)));
-  EXPECT_THAT(histograms.GetAllSamples(kAPTokenFetchStatusMetric),
-              testing::IsEmpty());
-
   aps_manager.UnsubscribeFromSigninEvents();
 }
 
diff --git a/chrome/browser/safe_browsing/extension_telemetry/extension_telemetry_service.cc b/chrome/browser/safe_browsing/extension_telemetry/extension_telemetry_service.cc
index a3534e6..daea39b 100644
--- a/chrome/browser/safe_browsing/extension_telemetry/extension_telemetry_service.cc
+++ b/chrome/browser/safe_browsing/extension_telemetry/extension_telemetry_service.cc
@@ -316,7 +316,7 @@
 }
 
 bool ExtensionTelemetryService::SignalDataPresent() {
-  return (extension_store_.empty());
+  return (extension_store_.size() > 0);
 }
 
 void ExtensionTelemetryService::AddSignal(
diff --git a/chrome/browser/safe_browsing/extension_telemetry/extension_telemetry_service_unittest.cc b/chrome/browser/safe_browsing/extension_telemetry/extension_telemetry_service_unittest.cc
index d38d85bb..c284064 100644
--- a/chrome/browser/safe_browsing/extension_telemetry/extension_telemetry_service_unittest.cc
+++ b/chrome/browser/safe_browsing/extension_telemetry/extension_telemetry_service_unittest.cc
@@ -439,14 +439,14 @@
   }
 }
 
-TEST_F(ExtensionTelemetryServiceTest, PersistsReportsOnShutdown) {
+TEST_F(ExtensionTelemetryServiceTest,
+       PersistsReportsOnShutdownWithSignalDataPresent) {
   // Setting up the persister and signals.
   telemetry_service_->SetEnabled(false);
   scoped_feature_list.InitAndEnableFeature(kExtensionTelemetryPersistence);
   telemetry_service_->SetEnabled(true);
   PrimeTelemetryServiceWithSignal();
   task_environment_.RunUntilIdle();
-  std::unique_ptr<TelemetryReport> telemetry_report_pb = GetTelemetryReport();
   // After a shutdown, the persister should create a file of persisted data.
   telemetry_service_->Shutdown();
   base::FilePath persisted_dir = profile_.GetPath();
@@ -461,6 +461,23 @@
   EXPECT_FALSE(base::PathExists(persisted_dir));
 }
 
+TEST_F(ExtensionTelemetryServiceTest,
+       DoesNotPersistsReportsOnShutdownWithNoSignalDataPresent) {
+  // Setting up the persister and signals.
+  telemetry_service_->SetEnabled(false);
+  scoped_feature_list.InitAndEnableFeature(kExtensionTelemetryPersistence);
+  telemetry_service_->SetEnabled(true);
+  task_environment_.RunUntilIdle();
+  // After a shutdown, the persister should not persist a file. There are
+  // extensions installed but there is no signal data present.
+  telemetry_service_->Shutdown();
+  base::FilePath persisted_dir = profile_.GetPath();
+  persisted_dir = persisted_dir.AppendASCII("CRXTelemetry");
+  base::FilePath persisted_file = persisted_dir.AppendASCII("CRXTelemetry_0");
+  task_environment_.RunUntilIdle();
+  EXPECT_FALSE(base::PathExists(persisted_file));
+}
+
 TEST_F(ExtensionTelemetryServiceTest, PersistsReportOnFailedUpload) {
   // Setting up the persister, signals, upload/write intervals, and the
   // uploader itself.
diff --git a/chrome/browser/sessions/session_restore_browsertest.cc b/chrome/browser/sessions/session_restore_browsertest.cc
index 14a4adb..20511ff 100644
--- a/chrome/browser/sessions/session_restore_browsertest.cc
+++ b/chrome/browser/sessions/session_restore_browsertest.cc
@@ -4023,7 +4023,7 @@
 
   // Expect a tabbed app was opened with a pinned tab.
   EXPECT_TRUE(web_app::WebAppProvider::GetForTest(profile)
-                  ->registrar()
+                  ->registrar_unsafe()
                   .IsTabbedWindowModeEnabled(app_id));
   EXPECT_TRUE(tab_strip->IsTabPinned(0));
 
@@ -4073,7 +4073,7 @@
     if (browser->type() == Browser::Type::TYPE_APP) {
       EXPECT_TRUE(web_app::AppBrowserController::IsForWebApp(browser, app_id));
       EXPECT_TRUE(web_app::WebAppProvider::GetForTest(browser->profile())
-                      ->registrar()
+                      ->registrar_unsafe()
                       .IsTabbedWindowModeEnabled(app_id));
 
       EXPECT_EQ(browser->tab_strip_model()->GetWebContentsAt(0)->GetURL(),
diff --git a/chrome/browser/speech/extension_api/tts_engine_extension_api.cc b/chrome/browser/speech/extension_api/tts_engine_extension_api.cc
index d55c3d36..f45aa44 100644
--- a/chrome/browser/speech/extension_api/tts_engine_extension_api.cc
+++ b/chrome/browser/speech/extension_api/tts_engine_extension_api.cc
@@ -18,6 +18,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/speech/extension_api/tts_extension_api.h"
 #include "chrome/browser/speech/extension_api/tts_extension_api_constants.h"
+#include "chrome/browser/ui/chrome_pages.h"
 #include "chrome/common/extensions/api/speech/tts_engine_manifest_handler.h"
 #include "chrome/common/extensions/extension_constants.h"
 #include "content/public/browser/render_frame_host.h"
@@ -39,6 +40,7 @@
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "ash/constants/ash_pref_names.h"
+#include "chrome/browser/ui/webui/settings/chromeos/constants/routes.mojom.h"
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 using extensions::EventRouter;
@@ -197,8 +199,11 @@
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 
 bool CanUseEnhancedNetworkVoices(const GURL& source_url, Profile* profile) {
-  // Currently only Select-to-speak can use Enhanced Network voices.
-  if (source_url.host() != extension_misc::kSelectToSpeakExtensionId)
+  // Currently only Select-to-speak and its settings page can use Enhanced
+  // Network voices.
+  if (source_url.host() != extension_misc::kSelectToSpeakExtensionId &&
+      source_url != chrome::GetOSSettingsUrl(
+                        chromeos::settings::mojom::kSelectToSpeakSubpagePath))
     return false;
 
   // Check if these voices are disallowed by policy.
diff --git a/chrome/browser/subresource_filter/ad_tagging_browsertest.cc b/chrome/browser/subresource_filter/ad_tagging_browsertest.cc
index d0e0a10..e179108 100644
--- a/chrome/browser/subresource_filter/ad_tagging_browsertest.cc
+++ b/chrome/browser/subresource_filter/ad_tagging_browsertest.cc
@@ -1022,6 +1022,267 @@
                           NavigationInitiationType::kAnchorLinkActivate),
         ::testing::Bool()));
 
+class AdClickNavigationHandleStatusBrowserTest : public AdTaggingBrowserTest {
+ public:
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    // Popups without user gesture is blocked by default. Turn off the switch
+    // here so as to test more variations of the landing page's status.
+    command_line->AppendSwitch(embedder_support::kDisablePopupBlocking);
+  }
+};
+
+IN_PROC_BROWSER_TEST_F(AdClickNavigationHandleStatusBrowserTest,
+                       BrowserInitiated) {
+  GURL url =
+      embedded_test_server()->GetURL("a.com", "/ad_tagging/frame_factory.html");
+
+  content::TestNavigationObserver navigation_observer(web_contents());
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
+
+  EXPECT_EQ(
+      navigation_observer.last_navigation_initiator_activation_and_ad_status(),
+      blink::mojom::NavigationInitiatorActivationAndAdStatus::
+          kDidNotStartWithTransientActivation);
+}
+
+IN_PROC_BROWSER_TEST_F(AdClickNavigationHandleStatusBrowserTest, WindowOpen) {
+  GURL url =
+      embedded_test_server()->GetURL("a.com", "/ad_tagging/frame_factory.html");
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
+
+  GURL popup_url =
+      embedded_test_server()->GetURL("c.com", "/ad_tagging/frame_factory.html");
+
+  // Popup from ad script without gesture
+  {
+    browser()->tab_strip_model()->MoveSelectedTabsTo(0);
+
+    content::WebContentsAddedObserver observer;
+    content::ExecuteScriptAsyncWithoutUserGesture(
+        GetWebContents()->GetPrimaryMainFrame(),
+        content::JsReplace("windowOpenFromAdScript($1)", popup_url));
+
+    content::WebContents* new_web_contents = observer.GetWebContents();
+    content::TestNavigationObserver navigation_observer(new_web_contents);
+    navigation_observer.Wait();
+
+    ASSERT_EQ(2, browser()->tab_strip_model()->count());
+
+    EXPECT_EQ(navigation_observer
+                  .last_navigation_initiator_activation_and_ad_status(),
+              blink::mojom::NavigationInitiatorActivationAndAdStatus::
+                  kDidNotStartWithTransientActivation);
+  }
+
+  // Popup from non-ad script with gesture
+  {
+    browser()->tab_strip_model()->MoveSelectedTabsTo(0);
+
+    content::WebContentsAddedObserver observer;
+    content::ExecuteScriptAsync(
+        GetWebContents()->GetPrimaryMainFrame(),
+        content::JsReplace("window.open($1)", popup_url));
+
+    content::WebContents* new_web_contents = observer.GetWebContents();
+    content::TestNavigationObserver navigation_observer(new_web_contents);
+    navigation_observer.Wait();
+
+    ASSERT_EQ(3, browser()->tab_strip_model()->count());
+
+    EXPECT_EQ(navigation_observer
+                  .last_navigation_initiator_activation_and_ad_status(),
+              blink::mojom::NavigationInitiatorActivationAndAdStatus::
+                  kStartedWithTransientActivationFromNonAd);
+  }
+
+  // Popup from ad script with gesture
+  {
+    browser()->tab_strip_model()->MoveSelectedTabsTo(0);
+
+    content::WebContentsAddedObserver observer;
+    content::ExecuteScriptAsync(
+        GetWebContents()->GetPrimaryMainFrame(),
+        content::JsReplace("windowOpenFromAdScript($1)", popup_url));
+
+    content::WebContents* new_web_contents = observer.GetWebContents();
+    content::TestNavigationObserver navigation_observer(new_web_contents);
+    navigation_observer.Wait();
+
+    ASSERT_EQ(4, browser()->tab_strip_model()->count());
+
+    EXPECT_EQ(navigation_observer
+                  .last_navigation_initiator_activation_and_ad_status(),
+              blink::mojom::NavigationInitiatorActivationAndAdStatus::
+                  kStartedWithTransientActivationFromAd);
+  }
+
+  RenderFrameHost* child =
+      CreateSrcFrame(GetWebContents(),
+                     embedded_test_server()->GetURL(
+                         "b.com", "/ad_tagging/frame_factory.html?1&ad=true"));
+
+  // Popup from ad iframe without gesture
+  {
+    browser()->tab_strip_model()->MoveSelectedTabsTo(0);
+
+    content::WebContentsAddedObserver observer;
+    content::ExecuteScriptAsyncWithoutUserGesture(
+        child, content::JsReplace("window.open($1)", popup_url));
+
+    content::WebContents* new_web_contents = observer.GetWebContents();
+    content::TestNavigationObserver navigation_observer(new_web_contents);
+    navigation_observer.Wait();
+
+    ASSERT_EQ(5, browser()->tab_strip_model()->count());
+
+    EXPECT_EQ(navigation_observer
+                  .last_navigation_initiator_activation_and_ad_status(),
+              blink::mojom::NavigationInitiatorActivationAndAdStatus::
+                  kDidNotStartWithTransientActivation);
+  }
+
+  // Popup from ad iframe with gesture
+  {
+    browser()->tab_strip_model()->MoveSelectedTabsTo(0);
+
+    content::WebContentsAddedObserver observer;
+    content::ExecuteScriptAsync(
+        child, content::JsReplace("window.open($1)", popup_url));
+
+    content::WebContents* new_web_contents = observer.GetWebContents();
+    content::TestNavigationObserver navigation_observer(new_web_contents);
+    navigation_observer.Wait();
+
+    ASSERT_EQ(6, browser()->tab_strip_model()->count());
+
+    EXPECT_EQ(navigation_observer
+                  .last_navigation_initiator_activation_and_ad_status(),
+              blink::mojom::NavigationInitiatorActivationAndAdStatus::
+                  kStartedWithTransientActivationFromAd);
+  }
+}
+
+IN_PROC_BROWSER_TEST_F(AdClickNavigationHandleStatusBrowserTest,
+                       SetTopLocationFromCrossOriginAdIframe) {
+  GURL url =
+      embedded_test_server()->GetURL("a.com", "/ad_tagging/frame_factory.html");
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
+
+  RenderFrameHost* child =
+      CreateSrcFrame(GetWebContents(),
+                     embedded_test_server()->GetURL(
+                         "b.com", "/ad_tagging/frame_factory.html?1&ad=true"));
+
+  GURL new_url =
+      embedded_test_server()->GetURL("c.com", "/ad_tagging/frame_factory.html");
+
+  content::TestNavigationObserver navigation_observer(web_contents());
+  EXPECT_TRUE(
+      content::ExecJs(child, content::JsReplace("top.location = $1", new_url)));
+  navigation_observer.Wait();
+
+  EXPECT_EQ(
+      navigation_observer.last_navigation_initiator_activation_and_ad_status(),
+      blink::mojom::NavigationInitiatorActivationAndAdStatus::
+          kStartedWithTransientActivationFromAd);
+}
+
+IN_PROC_BROWSER_TEST_F(AdClickNavigationHandleStatusBrowserTest,
+                       SetTopLocationFromCrossOriginNonAdIframe) {
+  GURL url =
+      embedded_test_server()->GetURL("a.com", "/ad_tagging/frame_factory.html");
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
+
+  RenderFrameHost* child = CreateSrcFrame(
+      GetWebContents(), embedded_test_server()->GetURL(
+                            "b.com", "/ad_tagging/frame_factory.html"));
+
+  GURL new_url =
+      embedded_test_server()->GetURL("c.com", "/ad_tagging/frame_factory.html");
+
+  content::TestNavigationObserver navigation_observer(web_contents());
+  EXPECT_TRUE(
+      content::ExecJs(child, content::JsReplace("top.location = $1", new_url)));
+  navigation_observer.Wait();
+
+  EXPECT_EQ(
+      navigation_observer.last_navigation_initiator_activation_and_ad_status(),
+      blink::mojom::NavigationInitiatorActivationAndAdStatus::
+          kStartedWithTransientActivationFromNonAd);
+}
+
+IN_PROC_BROWSER_TEST_F(AdClickNavigationHandleStatusBrowserTest,
+                       NavigateCrossOriginIframeFromNonAdScript) {
+  GURL url =
+      embedded_test_server()->GetURL("a.com", "/ad_tagging/frame_factory.html");
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
+
+  {
+    content::TestNavigationObserver navigation_observer(web_contents(), 1);
+    CreateSrcFrame(GetWebContents(),
+                   embedded_test_server()->GetURL(
+                       "b.com", "/ad_tagging/frame_factory.html?1&ad=true"));
+
+    EXPECT_EQ(navigation_observer
+                  .last_navigation_initiator_activation_and_ad_status(),
+              blink::mojom::NavigationInitiatorActivationAndAdStatus::
+                  kStartedWithTransientActivationFromNonAd);
+  }
+
+  {
+    GURL new_url = embedded_test_server()->GetURL(
+        "c.com", "/ad_tagging/frame_factory.html");
+
+    content::TestNavigationObserver navigation_observer(GetWebContents(), 1);
+    EXPECT_TRUE(content::ExecJs(
+        GetWebContents()->GetPrimaryMainFrame(),
+        content::JsReplace("document.getElementsByName('frame_0')[0].src = $1",
+                           new_url)));
+    navigation_observer.Wait();
+
+    EXPECT_EQ(navigation_observer
+                  .last_navigation_initiator_activation_and_ad_status(),
+              blink::mojom::NavigationInitiatorActivationAndAdStatus::
+                  kStartedWithTransientActivationFromNonAd);
+  }
+}
+
+IN_PROC_BROWSER_TEST_F(AdClickNavigationHandleStatusBrowserTest,
+                       NavigateCrossOriginIframeFromAdScript) {
+  GURL url =
+      embedded_test_server()->GetURL("a.com", "/ad_tagging/frame_factory.html");
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
+
+  {
+    content::TestNavigationObserver navigation_observer(GetWebContents(), 1);
+    CreateSrcFrame(GetWebContents(),
+                   embedded_test_server()->GetURL(
+                       "b.com", "/ad_tagging/frame_factory.html?1&ad=true"));
+
+    EXPECT_EQ(navigation_observer
+                  .last_navigation_initiator_activation_and_ad_status(),
+              blink::mojom::NavigationInitiatorActivationAndAdStatus::
+                  kStartedWithTransientActivationFromNonAd);
+  }
+
+  {
+    GURL new_url = embedded_test_server()->GetURL(
+        "c.com", "/ad_tagging/frame_factory.html");
+
+    content::TestNavigationObserver navigation_observer(GetWebContents(), 1);
+    EXPECT_TRUE(content::ExecJs(
+        GetWebContents()->GetPrimaryMainFrame(),
+        content::JsReplace("navigateIframeFromAdScript('frame_0', $1)",
+                           new_url)));
+    navigation_observer.Wait();
+
+    EXPECT_EQ(navigation_observer
+                  .last_navigation_initiator_activation_and_ad_status(),
+              blink::mojom::NavigationInitiatorActivationAndAdStatus::
+                  kStartedWithTransientActivationFromAd);
+  }
+}
+
 class AdTaggingEventFromSubframeBrowserTest
     : public AdTaggingBrowserTest,
       public ::testing::WithParamInterface<
@@ -1367,6 +1628,115 @@
   }
 }
 
+IN_PROC_BROWSER_TEST_F(AdTaggingFencedFrameBrowserTest,
+                       AdClickNavigationHandleStatus_PopupFromAdFencedFrame) {
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(
+      browser(), GetURL("frame_factory.html?primary")));
+
+  const GURL kOuterUrl = GetURL("frame_factory.html?fencedframe&ad=true");
+  RenderFrameHost* fenced_frame =
+      CreateFencedFrame(PrimaryMainFrame(), kOuterUrl);
+
+  ASSERT_TRUE(EvaluatedChildFrameLoad(kOuterUrl));
+  ASSERT_TRUE(IsAdFrame(fenced_frame));
+
+  GURL new_url = GetURL("frame_factory.html?primary123");
+
+  content::WebContentsAddedObserver observer;
+  content::ExecuteScriptAsync(fenced_frame,
+                              content::JsReplace("window.open($1)", new_url));
+
+  content::WebContents* new_web_contents = observer.GetWebContents();
+  content::TestNavigationObserver navigation_observer(new_web_contents);
+  navigation_observer.Wait();
+
+  EXPECT_EQ(
+      navigation_observer.last_navigation_initiator_activation_and_ad_status(),
+      blink::mojom::NavigationInitiatorActivationAndAdStatus::
+          kStartedWithTransientActivationFromAd);
+}
+
+IN_PROC_BROWSER_TEST_F(
+    AdTaggingFencedFrameBrowserTest,
+    AdClickNavigationHandleStatus_PopupFromNonAdFencedFrame) {
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(
+      browser(), GetURL("frame_factory.html?primary")));
+
+  const GURL kOuterUrl = GetURL("frame_factory.html?fencedframe");
+  RenderFrameHost* fenced_frame =
+      CreateFencedFrame(PrimaryMainFrame(), kOuterUrl);
+
+  ASSERT_TRUE(EvaluatedChildFrameLoad(kOuterUrl));
+  ASSERT_FALSE(IsAdFrame(fenced_frame));
+
+  GURL new_url = GetURL("frame_factory.html?primary");
+
+  content::WebContentsAddedObserver observer;
+  content::ExecuteScriptAsync(fenced_frame,
+                              content::JsReplace("window.open($1)", new_url));
+
+  content::WebContents* new_web_contents = observer.GetWebContents();
+  content::TestNavigationObserver navigation_observer(new_web_contents);
+  navigation_observer.Wait();
+
+  EXPECT_EQ(
+      navigation_observer.last_navigation_initiator_activation_and_ad_status(),
+      blink::mojom::NavigationInitiatorActivationAndAdStatus::
+          kStartedWithTransientActivationFromNonAd);
+}
+
+IN_PROC_BROWSER_TEST_F(
+    AdTaggingFencedFrameBrowserTest,
+    AdClickNavigationHandleStatus_TopNavigationFromOpaqueModeAdFencedFrame) {
+  GURL main_url = GetURL("frame_factory.html?primary");
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_url));
+
+  const GURL kOuterUrl = GetURL("frame_factory.html?fencedframe&ad=true");
+  content::RenderFrameHost* fenced_frame =
+      fenced_frame_test_helper().CreateFencedFrame(
+          PrimaryMainFrame(), kOuterUrl, net::OK,
+          blink::mojom::FencedFrameMode::kOpaqueAds);
+
+  GURL new_url = GetURL("frame_factory.html?primary");
+
+  content::TestNavigationObserver top_navigation_observer(web_contents());
+  EXPECT_TRUE(
+      ExecJs(fenced_frame,
+             content::JsReplace("window.open($1, '_unfencedTop')", new_url)));
+  top_navigation_observer.Wait();
+
+  EXPECT_EQ(top_navigation_observer
+                .last_navigation_initiator_activation_and_ad_status(),
+            blink::mojom::NavigationInitiatorActivationAndAdStatus::
+                kStartedWithTransientActivationFromAd);
+}
+
+IN_PROC_BROWSER_TEST_F(
+    AdTaggingFencedFrameBrowserTest,
+    AdClickNavigationHandleStatus_TopNavigationFromOpaqueModeNonAdFencedFrame) {
+  GURL main_url = GetURL("frame_factory.html?primary");
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_url));
+
+  const GURL kOuterUrl = GetURL("frame_factory.html?fencedframe");
+  content::RenderFrameHost* fenced_frame =
+      fenced_frame_test_helper().CreateFencedFrame(
+          PrimaryMainFrame(), kOuterUrl, net::OK,
+          blink::mojom::FencedFrameMode::kOpaqueAds);
+
+  GURL new_url = GetURL("frame_factory.html?primary");
+
+  content::TestNavigationObserver top_navigation_observer(web_contents());
+  EXPECT_TRUE(
+      ExecJs(fenced_frame,
+             content::JsReplace("window.open($1, '_unfencedTop')", new_url)));
+  top_navigation_observer.Wait();
+
+  EXPECT_EQ(top_navigation_observer
+                .last_navigation_initiator_activation_and_ad_status(),
+            blink::mojom::NavigationInitiatorActivationAndAdStatus::
+                kStartedWithTransientActivationFromNonAd);
+}
+
 }  // namespace
 
 }  // namespace subresource_filter
diff --git a/chrome/browser/sync/test/integration/apps_helper.cc b/chrome/browser/sync/test/integration/apps_helper.cc
index 7036f00..b41a4bf 100644
--- a/chrome/browser/sync/test/integration/apps_helper.cc
+++ b/chrome/browser/sync/test/integration/apps_helper.cc
@@ -55,7 +55,7 @@
 
     std::vector<web_app::AppId> apps_to_be_sync_installed =
         web_app::WebAppProvider::GetForTest(profile)
-            ->registrar()
+            ->registrar_unsafe()
             .GetAppsFromSyncAndPendingInstallation();
     apps_to_be_installed.insert(apps_to_be_sync_installed.begin(),
                                 apps_to_be_sync_installed.end());
@@ -227,7 +227,7 @@
     // registry.
     auto* provider = web_app::WebAppProvider::GetForTest(profile);
     std::vector<web_app::AppId> sync_apps_pending_install =
-        provider->registrar().GetAppsFromSyncAndPendingInstallation();
+        provider->registrar_unsafe().GetAppsFromSyncAndPendingInstallation();
     if (!sync_apps_pending_install.empty()) {
       LOG(ERROR) << "Apps from sync are still pending installation: "
                  << sync_apps_pending_install.size();
@@ -235,7 +235,7 @@
     }
 
     std::vector<web_app::AppId> apps_in_uninstall =
-        provider->registrar().GetAppsPendingUninstall();
+        provider->registrar_unsafe().GetAppsPendingUninstall();
     if (!apps_in_uninstall.empty()) {
       LOG(ERROR) << "App uninstalls are still pending: "
                  << apps_in_uninstall.size();
@@ -264,7 +264,7 @@
 
   run_loop.Run();
 
-  const web_app::WebAppRegistrar& registrar = provider->registrar();
+  const web_app::WebAppRegistrar& registrar = provider->registrar_unsafe();
   DCHECK_EQ(base::UTF8ToUTF16(registrar.GetAppShortName(app_id)), info.title);
   DCHECK_EQ(registrar.GetAppStartUrl(app_id), info.start_url);
 
diff --git a/chrome/browser/sync/test/integration/single_client_web_apps_sync_test.cc b/chrome/browser/sync/test/integration/single_client_web_apps_sync_test.cc
index 639dd3ca..38a45cd1 100644
--- a/chrome/browser/sync/test/integration/single_client_web_apps_sync_test.cc
+++ b/chrome/browser/sync/test/integration/single_client_web_apps_sync_test.cc
@@ -154,7 +154,7 @@
   AwaitWebAppQuiescence();
 
   auto& web_app_registrar =
-      WebAppProvider::GetForTest(GetProfile(0))->registrar();
+      WebAppProvider::GetForTest(GetProfile(0))->registrar_unsafe();
   EXPECT_TRUE(web_app_registrar.IsInstalled(app_id));
 }
 
@@ -167,7 +167,7 @@
   ASSERT_TRUE(SetupSync());
   AwaitWebAppQuiescence();
   auto& web_app_registrar =
-      WebAppProvider::GetForTest(GetProfile(0))->registrar();
+      WebAppProvider::GetForTest(GetProfile(0))->registrar_unsafe();
 
   EXPECT_EQ(web_app_registrar.GetAppById(app_id), nullptr);
 }
@@ -182,7 +182,7 @@
   ASSERT_TRUE(SetupSync());
   AwaitWebAppQuiescence();
   auto& web_app_registrar =
-      WebAppProvider::GetForTest(GetProfile(0))->registrar();
+      WebAppProvider::GetForTest(GetProfile(0))->registrar_unsafe();
 
   EXPECT_FALSE(web_app_registrar.IsInstalled(app_id));
 }
@@ -216,7 +216,7 @@
   AwaitWebAppQuiescence();
 
   auto& web_app_registrar =
-      WebAppProvider::GetForTest(GetProfile(0))->registrar();
+      WebAppProvider::GetForTest(GetProfile(0))->registrar_unsafe();
 
   EXPECT_FALSE(web_app_registrar.IsInstalled(app_id));
 }
@@ -232,7 +232,7 @@
   AwaitWebAppQuiescence();
 
   auto& web_app_registrar =
-      WebAppProvider::GetForTest(GetProfile(0))->registrar();
+      WebAppProvider::GetForTest(GetProfile(0))->registrar_unsafe();
 
   EXPECT_TRUE(web_app_registrar.IsInstalled(app_id));
 
@@ -262,7 +262,7 @@
   AwaitWebAppQuiescence();
 
   auto& web_app_registrar =
-      WebAppProvider::GetForTest(GetProfile(0))->registrar();
+      WebAppProvider::GetForTest(GetProfile(0))->registrar_unsafe();
 
   EXPECT_TRUE(web_app_registrar.IsInstalled(app_id));
 
diff --git a/chrome/browser/sync/test/integration/two_client_web_apps_bmo_sync_test.cc b/chrome/browser/sync/test/integration/two_client_web_apps_bmo_sync_test.cc
index 8b173bc..8f9be29 100644
--- a/chrome/browser/sync/test/integration/two_client_web_apps_bmo_sync_test.cc
+++ b/chrome/browser/sync/test/integration/two_client_web_apps_bmo_sync_test.cc
@@ -158,7 +158,7 @@
   }
 
   const WebAppRegistrar& GetRegistrar(Profile* profile) {
-    return WebAppProvider::GetForTest(profile)->registrar();
+    return WebAppProvider::GetForTest(profile)->registrar_unsafe();
   }
 
   FakeOsIntegrationManager& GetOsIntegrationManager(Profile* profile) {
diff --git a/chrome/browser/sync/test/integration/two_client_web_apps_sync_test.cc b/chrome/browser/sync/test/integration/two_client_web_apps_sync_test.cc
index ea75cc5..f3fc8ac 100644
--- a/chrome/browser/sync/test/integration/two_client_web_apps_sync_test.cc
+++ b/chrome/browser/sync/test/integration/two_client_web_apps_sync_test.cc
@@ -78,7 +78,7 @@
   }
 
   const WebAppRegistrar& GetRegistrar(Profile* profile) {
-    return WebAppProvider::GetForTest(profile)->registrar();
+    return WebAppProvider::GetForTest(profile)->registrar_unsafe();
   }
 
   bool AllProfilesHaveSameWebAppIds() {
@@ -424,7 +424,7 @@
   EXPECT_TRUE(AllProfilesHaveSameWebAppIds());
 
   auto* provider1 = WebAppProvider::GetForTest(GetProfile(1));
-  WebAppRegistrar& registrar1 = provider1->registrar();
+  WebAppRegistrar& registrar1 = provider1->registrar_unsafe();
   EXPECT_EQ(registrar1.GetAppUserDisplayMode(app_id),
             UserDisplayMode::kStandalone);
 
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index cdb153f..5aa9075 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -1174,7 +1174,6 @@
       "media_router/query_result_manager.h",
       "media_router/ui_media_sink.cc",
       "media_router/ui_media_sink.h",
-      "native_window_tracker.h",
       "omnibox/alternate_nav_infobar_delegate.cc",
       "omnibox/alternate_nav_infobar_delegate.h",
       "omnibox/chrome_omnibox_client.cc",
@@ -2000,6 +1999,16 @@
       "../ash/app_list/reorder/app_list_reorder_delegate.h",
       "../ash/app_list/reorder/app_list_reorder_util.cc",
       "../ash/app_list/reorder/app_list_reorder_util.h",
+      "../ash/app_list/search/app_result.cc",
+      "../ash/app_list/search/app_result.h",
+      "../ash/app_list/search/app_search_data_source.cc",
+      "../ash/app_list/search/app_search_data_source.h",
+      "../ash/app_list/search/app_search_provider.cc",
+      "../ash/app_list/search/app_search_provider.h",
+      "../ash/app_list/search/app_service_app_result.cc",
+      "../ash/app_list/search/app_service_app_result.h",
+      "../ash/app_list/search/app_zero_state_provider.cc",
+      "../ash/app_list/search/app_zero_state_provider.h",
       "../ash/app_list/search/arc/arc_app_shortcut_search_result.cc",
       "../ash/app_list/search/arc/arc_app_shortcut_search_result.h",
       "../ash/app_list/search/arc/arc_app_shortcuts_search_provider.cc",
@@ -2015,6 +2024,12 @@
       "../ash/app_list/search/arc/recommend_apps_fetcher_delegate.h",
       "../ash/app_list/search/arc/recommend_apps_fetcher_impl.cc",
       "../ash/app_list/search/arc/recommend_apps_fetcher_impl.h",
+      "../ash/app_list/search/assistant_text_search_provider.cc",
+      "../ash/app_list/search/assistant_text_search_provider.h",
+      "../ash/app_list/search/burnin_controller.cc",
+      "../ash/app_list/search/burnin_controller.h",
+      "../ash/app_list/search/chrome_search_result.cc",
+      "../ash/app_list/search/chrome_search_result.h",
       "../ash/app_list/search/common/icon_constants.cc",
       "../ash/app_list/search/common/icon_constants.h",
       "../ash/app_list/search/common/search_result_util.cc",
@@ -2057,6 +2072,16 @@
       "../ash/app_list/search/games/game_provider.h",
       "../ash/app_list/search/games/game_result.cc",
       "../ash/app_list/search/games/game_result.h",
+      "../ash/app_list/search/help_app_provider.cc",
+      "../ash/app_list/search/help_app_provider.h",
+      "../ash/app_list/search/help_app_zero_state_provider.cc",
+      "../ash/app_list/search/help_app_zero_state_provider.h",
+      "../ash/app_list/search/keyboard_shortcut_data.cc",
+      "../ash/app_list/search/keyboard_shortcut_data.h",
+      "../ash/app_list/search/keyboard_shortcut_provider.cc",
+      "../ash/app_list/search/keyboard_shortcut_provider.h",
+      "../ash/app_list/search/keyboard_shortcut_result.cc",
+      "../ash/app_list/search/keyboard_shortcut_result.h",
       "../ash/app_list/search/omnibox/omnibox_answer_result.cc",
       "../ash/app_list/search/omnibox/omnibox_answer_result.h",
       "../ash/app_list/search/omnibox/omnibox_lacros_provider.cc",
@@ -2069,6 +2094,10 @@
       "../ash/app_list/search/omnibox/omnibox_util.h",
       "../ash/app_list/search/omnibox/open_tab_result.cc",
       "../ash/app_list/search/omnibox/open_tab_result.h",
+      "../ash/app_list/search/os_settings_provider.cc",
+      "../ash/app_list/search/os_settings_provider.h",
+      "../ash/app_list/search/personalization_provider.cc",
+      "../ash/app_list/search/personalization_provider.h",
       "../ash/app_list/search/ranking/answer_ranker.cc",
       "../ash/app_list/search/ranking/answer_ranker.h",
       "../ash/app_list/search/ranking/best_match_ranker.cc",
@@ -2098,6 +2127,23 @@
       "../ash/app_list/search/ranking/scoring.h",
       "../ash/app_list/search/ranking/util.cc",
       "../ash/app_list/search/ranking/util.h",
+      "../ash/app_list/search/search_controller.h",
+      "../ash/app_list/search/search_controller_factory.cc",
+      "../ash/app_list/search/search_controller_factory.h",
+      "../ash/app_list/search/search_controller_impl.cc",
+      "../ash/app_list/search/search_controller_impl.h",
+      "../ash/app_list/search/search_features.cc",
+      "../ash/app_list/search/search_features.h",
+      "../ash/app_list/search/search_metrics_manager.cc",
+      "../ash/app_list/search/search_metrics_manager.h",
+      "../ash/app_list/search/search_metrics_util.cc",
+      "../ash/app_list/search/search_metrics_util.h",
+      "../ash/app_list/search/search_provider.cc",
+      "../ash/app_list/search/search_provider.h",
+      "../ash/app_list/search/search_session_metrics_manager.cc",
+      "../ash/app_list/search/search_session_metrics_manager.h",
+      "../ash/app_list/search/types.cc",
+      "../ash/app_list/search/types.h",
       "../ash/app_list/search/util/ftrl_optimizer.cc",
       "../ash/app_list/search/util/ftrl_optimizer.h",
       "../ash/app_list/search/util/mrfu_cache.cc",
@@ -2144,53 +2190,6 @@
       "app_list/extension_app_utils.h",
       "app_list/md_icon_normalizer.cc",
       "app_list/md_icon_normalizer.h",
-      "app_list/search/app_result.cc",
-      "app_list/search/app_result.h",
-      "app_list/search/app_search_data_source.cc",
-      "app_list/search/app_search_data_source.h",
-      "app_list/search/app_search_provider.cc",
-      "app_list/search/app_search_provider.h",
-      "app_list/search/app_service_app_result.cc",
-      "app_list/search/app_service_app_result.h",
-      "app_list/search/app_zero_state_provider.cc",
-      "app_list/search/app_zero_state_provider.h",
-      "app_list/search/assistant_text_search_provider.cc",
-      "app_list/search/assistant_text_search_provider.h",
-      "app_list/search/burnin_controller.cc",
-      "app_list/search/burnin_controller.h",
-      "app_list/search/chrome_search_result.cc",
-      "app_list/search/chrome_search_result.h",
-      "app_list/search/help_app_provider.cc",
-      "app_list/search/help_app_provider.h",
-      "app_list/search/help_app_zero_state_provider.cc",
-      "app_list/search/help_app_zero_state_provider.h",
-      "app_list/search/keyboard_shortcut_data.cc",
-      "app_list/search/keyboard_shortcut_data.h",
-      "app_list/search/keyboard_shortcut_provider.cc",
-      "app_list/search/keyboard_shortcut_provider.h",
-      "app_list/search/keyboard_shortcut_result.cc",
-      "app_list/search/keyboard_shortcut_result.h",
-      "app_list/search/os_settings_provider.cc",
-      "app_list/search/os_settings_provider.h",
-      "app_list/search/personalization_provider.cc",
-      "app_list/search/personalization_provider.h",
-      "app_list/search/search_controller.h",
-      "app_list/search/search_controller_factory.cc",
-      "app_list/search/search_controller_factory.h",
-      "app_list/search/search_controller_impl.cc",
-      "app_list/search/search_controller_impl.h",
-      "app_list/search/search_features.cc",
-      "app_list/search/search_features.h",
-      "app_list/search/search_metrics_manager.cc",
-      "app_list/search/search_metrics_manager.h",
-      "app_list/search/search_metrics_util.cc",
-      "app_list/search/search_metrics_util.h",
-      "app_list/search/search_provider.cc",
-      "app_list/search/search_provider.h",
-      "app_list/search/search_session_metrics_manager.cc",
-      "app_list/search/search_session_metrics_manager.h",
-      "app_list/search/types.cc",
-      "app_list/search/types.h",
       "ash/accessibility/accessibility_controller_client.cc",
       "ash/accessibility/accessibility_controller_client.h",
       "ash/ambient/ambient_client_impl.cc",
@@ -2698,6 +2697,8 @@
       "webui/ash/login/base_webui_handler.h",
       "webui/ash/login/check_passwords_against_cryptohome_helper.cc",
       "webui/ash/login/check_passwords_against_cryptohome_helper.h",
+      "webui/ash/login/choobe_screen_handler.cc",
+      "webui/ash/login/choobe_screen_handler.h",
       "webui/ash/login/consolidated_consent_screen_handler.cc",
       "webui/ash/login/consolidated_consent_screen_handler.h",
       "webui/ash/login/cookie_waiter.cc",
@@ -3053,10 +3054,14 @@
       "webui/settings/ash/search/search_tag_registry.h",
       "webui/settings/ash/search_section.cc",
       "webui/settings/ash/search_section.h",
+      "webui/settings/ash/select_to_speak_handler.cc",
+      "webui/settings/ash/select_to_speak_handler.h",
       "webui/settings/ash/server_printer_url_util.cc",
       "webui/settings/ash/server_printer_url_util.h",
       "webui/settings/ash/settings_user_action_tracker.cc",
       "webui/settings/ash/settings_user_action_tracker.h",
+      "webui/settings/ash/settings_with_tts_preview_handler.cc",
+      "webui/settings/ash/settings_with_tts_preview_handler.h",
       "webui/settings/ash/switch_access_handler.cc",
       "webui/settings/ash/switch_access_handler.h",
       "webui/settings/ash/tts_handler.cc",
@@ -3774,8 +3779,6 @@
       "cocoa/main_menu_builder.h",
       "cocoa/main_menu_builder.mm",
       "cocoa/main_menu_item.h",
-      "cocoa/native_window_tracker_cocoa.h",
-      "cocoa/native_window_tracker_cocoa.mm",
       "cocoa/profiles/profile_menu_controller.h",
       "cocoa/profiles/profile_menu_controller.mm",
       "cocoa/renderer_context_menu/render_view_context_menu_mac.h",
@@ -5061,14 +5064,14 @@
       "views/tabs/tab_hover_card_bubble_view.h",
       "views/tabs/tab_hover_card_controller.cc",
       "views/tabs/tab_hover_card_controller.h",
-      "views/tabs/tab_hover_card_metrics.cc",
-      "views/tabs/tab_hover_card_metrics.h",
       "views/tabs/tab_hover_card_thumbnail_observer.cc",
       "views/tabs/tab_hover_card_thumbnail_observer.h",
       "views/tabs/tab_icon.cc",
       "views/tabs/tab_icon.h",
       "views/tabs/tab_layout_state.cc",
       "views/tabs/tab_layout_state.h",
+      "views/tabs/tab_scrolling_animation.cc",
+      "views/tabs/tab_scrolling_animation.h",
       "views/tabs/tab_search_button.cc",
       "views/tabs/tab_search_button.h",
       "views/tabs/tab_slot_animation_delegate.cc",
@@ -5482,8 +5485,6 @@
     sources += [
       "aura/accessibility/automation_manager_aura.cc",
       "aura/accessibility/automation_manager_aura.h",
-      "aura/native_window_tracker_aura.cc",
-      "aura/native_window_tracker_aura.h",
       "aura/tab_contents/web_drag_bookmark_handler_aura.cc",
       "aura/tab_contents/web_drag_bookmark_handler_aura.h",
       "views/accelerator_utils_aura.cc",
@@ -5611,6 +5612,8 @@
       "views/javascript_app_modal_event_blocker.h",
       "web_applications/app_browser_controller.cc",
       "web_applications/app_browser_controller.h",
+      "web_applications/commands/launch_web_app_command.cc",
+      "web_applications/commands/launch_web_app_command.h",
       "web_applications/diagnostics/app_type_initialized_event.cc",
       "web_applications/diagnostics/app_type_initialized_event.h",
       "web_applications/diagnostics/callback_utils.h",
@@ -5990,6 +5993,7 @@
     ]
     deps += [
       "//ash/public/cpp",
+      "//chrome/browser/ui/webui/settings/chromeos/constants:mojom",
       "//chromeos/ash/components/dbus",
       "//chromeos/ui/base",
     ]
diff --git a/chrome/browser/ui/app_list/app_list_client_impl.cc b/chrome/browser/ui/app_list/app_list_client_impl.cc
index 4c101d6..3ac4bda 100644
--- a/chrome/browser/ui/app_list/app_list_client_impl.cc
+++ b/chrome/browser/ui/app_list/app_list_client_impl.cc
@@ -19,8 +19,11 @@
 #include "base/metrics/histogram_macros.h"
 #include "base/metrics/user_metrics.h"
 #include "base/strings/strcat.h"
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
 #include "chrome/browser/ash/app_list/search/cros_action_history/cros_action_recorder.h"
 #include "chrome/browser/ash/app_list/search/ranking/launch_data.h"
+#include "chrome/browser/ash/app_list/search/search_controller.h"
+#include "chrome/browser/ash/app_list/search/search_controller_factory.h"
 #include "chrome/browser/ash/crosapi/browser_util.h"
 #include "chrome/browser/ash/crosapi/url_handler_ash.h"
 #include "chrome/browser/profiles/profile.h"
@@ -32,9 +35,6 @@
 #include "chrome/browser/ui/app_list/app_list_syncable_service.h"
 #include "chrome/browser/ui/app_list/app_list_syncable_service_factory.h"
 #include "chrome/browser/ui/app_list/app_sync_ui_state_watcher.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
-#include "chrome/browser/ui/app_list/search/search_controller.h"
-#include "chrome/browser/ui/app_list/search/search_controller_factory.h"
 #include "chrome/browser/ui/ash/shelf/app_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"
diff --git a/chrome/browser/ui/app_list/app_list_client_impl_browsertest.cc b/chrome/browser/ui/app_list/app_list_client_impl_browsertest.cc
index 3a04e44..0174923 100644
--- a/chrome/browser/ui/app_list/app_list_client_impl_browsertest.cc
+++ b/chrome/browser/ui/app_list/app_list_client_impl_browsertest.cc
@@ -27,6 +27,7 @@
 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
 #include "chrome/browser/apps/app_service/launch_utils.h"
 #include "chrome/browser/apps/platform_apps/app_browsertest_util.h"
+#include "chrome/browser/ash/app_list/search/search_controller.h"
 #include "chrome/browser/ash/app_list/test/chrome_app_list_test_support.h"
 #include "chrome/browser/ash/login/demo_mode/demo_session.h"
 #include "chrome/browser/ash/login/login_manager_test.h"
@@ -46,7 +47,6 @@
 #include "chrome/browser/ui/app_list/app_list_model_updater.h"
 #include "chrome/browser/ui/app_list/app_list_syncable_service_factory.h"
 #include "chrome/browser/ui/app_list/chrome_app_list_item.h"
-#include "chrome/browser/ui/app_list/search/search_controller.h"
 #include "chrome/browser/ui/ash/system_web_apps/system_web_app_ui_utils.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_finder.h"
diff --git a/chrome/browser/ui/app_list/app_list_sort_browsertest.cc b/chrome/browser/ui/app_list/app_list_sort_browsertest.cc
index 0f0d2b28..a891dcb 100644
--- a/chrome/browser/ui/app_list/app_list_sort_browsertest.cc
+++ b/chrome/browser/ui/app_list/app_list_sort_browsertest.cc
@@ -1075,19 +1075,67 @@
 
   ash::ShellTestApi().SetTabletModeEnabledForTest(false);
   WaitForAppListTransitionAnimation();
+  EXPECT_NE(ReorderAnimationEndState::kFadeOutAborted, actual_state);
+
   ash::AcceleratorController::Get()->PerformActionIfEnabled(
       ash::TOGGLE_APP_LIST, {});
   app_list_test_api_.WaitForBubbleWindow(/*wait_for_opening_animation=*/true);
 
-  // Verify that the reorder animation is aborted.
-  EXPECT_EQ(ReorderAnimationEndState::kFadeInAborted, actual_state);
-
-  // Before switching to the tablet mode, the app list is closed so the
-  // temporary sorting order is committed.
+  // When switching out of the tablet mode, the tablet mode app list gets
+  // closed so the temporary sorting order is committed.
   EXPECT_EQ(GetAppIdsInOrdinalOrder(),
             std::vector<std::string>({app1_id_, app2_id_, app3_id_}));
 
-  // Verify that reordering in tablet mode works.
+  ReorderTopLevelAppsGridAndWaitForCompletion(
+      ash::AppListSortOrder::kColor, MenuType::kAppListNonFolderItemMenu);
+  EXPECT_EQ(GetAppIdsInOrdinalOrder(),
+            std::vector<std::string>({app2_id_, app3_id_, app1_id_}));
+}
+
+// Verify that switching to clamshell mode when the fade in animation in tablet
+// mode is running, and gets aborted during tablet mode transition works as
+// expected.
+IN_PROC_BROWSER_TEST_F(AppListSortBrowserTest,
+                       TransitionToClamshellModeDuringAbortedFadeInAnimation) {
+  ash::ShellTestApi().SetTabletModeEnabledForTest(true);
+
+  ash::AcceleratorController::Get()->PerformActionIfEnabled(
+      ash::TOGGLE_APP_LIST, {});
+  app_list_test_api_.WaitForAppListShowAnimation(/*is_bubble_window=*/false);
+
+  // Verify the default app order.
+  EXPECT_EQ(GetAppIdsInOrdinalOrder(),
+            std::vector<std::string>({app3_id_, app2_id_, app1_id_}));
+
+  ReorderAnimationEndState actual_state;
+  app_list_test_api_.ReorderByMouseClickAtToplevelAppsGridMenu(
+      ash::AppListSortOrder::kNameAlphabetical,
+      MenuType::kAppListNonFolderItemMenu, event_generator_.get(),
+      /*target_state=*/ReorderAnimationEndState::kFadeInAborted, &actual_state);
+
+  // Verify that there is active reorder animations.
+  EXPECT_TRUE(app_list_test_api_.HasAnyWaitingReorderDoneCallback());
+
+  // The app order should change because the fade out animation ends.
+  EXPECT_EQ(GetAppIdsInOrdinalOrder(),
+            std::vector<std::string>({app1_id_, app2_id_, app3_id_}));
+
+  ash::ShellTestApi().SetTabletModeEnabledForTest(false);
+  // Progress tablet mode animation to the end before item fade in animation
+  // completes - this should hide the tablet mode app list and abort the fade in
+  // aniamtion.
+  app_list_test_api_.GetAppListViewLayer()->GetAnimator()->StopAnimating();
+  EXPECT_EQ(ReorderAnimationEndState::kFadeInAborted, actual_state);
+
+  ash::AcceleratorController::Get()->PerformActionIfEnabled(
+      ash::TOGGLE_APP_LIST, {});
+  app_list_test_api_.WaitForBubbleWindow(/*wait_for_opening_animation=*/true);
+
+  // When switching out of the tablet mode, the tablet mode app list gets
+  // closed so the temporary sorting order is committed.
+  EXPECT_EQ(GetAppIdsInOrdinalOrder(),
+            std::vector<std::string>({app1_id_, app2_id_, app3_id_}));
+
   ReorderTopLevelAppsGridAndWaitForCompletion(
       ash::AppListSortOrder::kColor, MenuType::kAppListNonFolderItemMenu);
   EXPECT_EQ(GetAppIdsInOrdinalOrder(),
diff --git a/chrome/browser/ui/app_list/chrome_app_list_model_updater.cc b/chrome/browser/ui/app_list/chrome_app_list_model_updater.cc
index 41cdaad..647712dd 100644
--- a/chrome/browser/ui/app_list/chrome_app_list_model_updater.cc
+++ b/chrome/browser/ui/app_list/chrome_app_list_model_updater.cc
@@ -19,11 +19,11 @@
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/ash/app_list/reorder/app_list_reorder_core.h"
 #include "chrome/browser/ash/app_list/reorder/app_list_reorder_delegate.h"
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
 #include "chrome/browser/ui/app_list/app_list_controller_delegate.h"
 #include "chrome/browser/ui/app_list/app_list_sync_model_sanitizer.h"
 #include "chrome/browser/ui/app_list/chrome_app_list_item.h"
 #include "chrome/browser/ui/app_list/chrome_app_list_item_manager.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
 #include "extensions/common/constants.h"
 #include "ui/base/models/menu_model.h"
 
diff --git a/chrome/browser/ui/app_list/search/DEPS b/chrome/browser/ui/app_list/search/DEPS
deleted file mode 100644
index b9d912a..0000000
--- a/chrome/browser/ui/app_list/search/DEPS
+++ /dev/null
@@ -1,11 +0,0 @@
-# TODO(https://crbug.com/1164001): When this file is edited, same file in
-# //chrome/browser/ash/app_list/search should be updated as well. We need
-# to sync both files until the migration is done.
-include_rules = [
-  "+ash/accelerators",
-  "+ash/assistant/model",
-  "+ash/assistant/util",
-  "+ash/shortcut_viewer",
-  "+ash/strings/grit",
-  "+chromeos/ui/vector_icons",
-]
diff --git a/chrome/browser/ui/app_list/search/OWNERS b/chrome/browser/ui/app_list/search/OWNERS
deleted file mode 100644
index 054371b6..0000000
--- a/chrome/browser/ui/app_list/search/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-file://chrome/browser/ash/app_list/search/OWNERS
diff --git a/chrome/browser/ui/app_list/search/README.md b/chrome/browser/ui/app_list/search/README.md
deleted file mode 100644
index 683daa2d..0000000
--- a/chrome/browser/ui/app_list/search/README.md
+++ /dev/null
@@ -1,94 +0,0 @@
-# About
-
-This folder contains the backend implementation of Chrome OS launcher search.
-
-# Overview of search infrastructure
-
-## Important classes
-
-### Core
-
-- **SearchController**. This controls all the core search functions such as
-  starting a search, collecting results, ranking and publishing. Implemented by
-  `SearchControllerImplNew`.
-
-  To interact with the frontend, it calls the `AppListController` and
-  `AppListModelUpdater`, and is called by the `AppListClient`.
-- **SearchProvider**. The base class for all search providers. Each search
-  provider typically handles one type of result, such as settings, apps or
-  files. Some search providers implement their search function locally, while
-  others call out to further backends.
-- **SearchControllerFactory**. Responsible for the creation of the search
-  controller and its providers at start-up time.
-- **ChromeSearchResult**. The base class for all search results. Each
-  `ChromeSearchResult` contains the information associated with one result. This
-  information is stored in a `SearchResultMetadata` object which is piped to the
-  frontend code.
-
-### Ranking
-
-Ranking is the process of assigning scores to each result and category to
-determine their final display order. Located inside the `ranking/` subdirectory.
-
-- **RankerManager**. This owns the ranking stack and determines the order of
-  ranking steps.
-- **Ranker**. The base class for all rankers. Rankers can be used for all kinds
-  of post-processing steps, including but not limited to ranking.
-
-### Metrics
-
-- **AppListNotifierImpl**. Located in the parent directory
-  `chrome/browser/ui/app_list/`. Contains a state machine that converts raw UI
-  events into information such as impressions and launches.
-- **SearchMetricsManager**. Observes the `AppListNotifier` and logs metrics
-  accordingly.
-
-## Life of a search query
-
-1. The user types a query into the launcher search box. This filters through UI
-   code until it eventually reaches `SearchController::StartSearch(query)`.
-2. The `SearchController` forwards this query to its various search providers.
-3. Search providers return their results **asynchronously**.
-4. The `SearchController` collects these results and performs ranking on the
-   results and their categories.
-5. Results are published to the UI.
-
-Steps #3-5 may be repeated several times due to the asynchronous nature of #3.
-The `BurnInController` contains timing logic to reduce the UI effect of results
-popping in.
-
-Training may be performed:
-
-6. The user clicks on a result.
-7. The `SearchController` forwards this information to its various search
-   providers and rankers, which can use this information to inform future
-   searches and ranking.
-
-## Life of zero state
-
-Zero state is the UI shown before the user types any query. It consists of the
-Continue section (recent files), the recent apps row, as well as the app grid.
-The `SearchController` handles ranking for continue files and recent apps.
-
-Steps #1-4 closely mirror query search, but publishing is handled differently.
-
-1. The user opens the launcher. This eventually reaches
-   `SearchController::StartZeroState(callback, timeout)`.
-   - The UI blocks itself until `callback` is run, which by contract should
-     happen no later than `timeout`.
-2. The `SearchController` forwards this request to its various zero state
-   providers.
-3. Providers return their results **asynchronously**.
-4. The `SearchController` collects these results and performs ranking on the
-   results and their categories.
-5. Once either of the following two conditions is satisfied, the
-   `SearchController` will publish any existing results and unblock the UI:
-   - `timeout` has elapsed,
-   - All zero state providers have returned.
-6. If there are any providers still pending, the `SearchController` waits until
-   all of them have returned and publishes results once more to the UI.
-
-The most common situation is that recent apps return before the timeout, but the
-continue files providers return later.
-
-Training may be performed, the same as with query search.
diff --git a/chrome/browser/ui/ash/app_access_notifier_unittest.cc b/chrome/browser/ui/ash/app_access_notifier_unittest.cc
index a5431e3..569e62c 100644
--- a/chrome/browser/ui/ash/app_access_notifier_unittest.cc
+++ b/chrome/browser/ui/ash/app_access_notifier_unittest.cc
@@ -28,7 +28,6 @@
 #include "components/services/app_service/public/cpp/app_registry_cache_wrapper.h"
 #include "components/services/app_service/public/cpp/app_types.h"
 #include "components/services/app_service/public/cpp/capability_access.h"
-#include "components/services/app_service/public/cpp/features.h"
 #include "components/user_manager/scoped_user_manager.h"
 #include "components/user_manager/user.h"
 #include "content/public/test/browser_task_environment.h"
@@ -251,17 +250,15 @@
 
   // AppAccessNotifierBaseTest:
   void SetUp() override {
-    std::vector<base::test::FeatureRef> enabled_features{
-        apps::kAppServiceCapabilityAccessWithoutMojom};
     std::vector<base::test::FeatureRef> disabled_features;
 
-    (IsPrivacyIndicatorsFeatureEnabled() ? enabled_features : disabled_features)
-        .push_back(ash::features::kPrivacyIndicators);
+    if (!IsPrivacyIndicatorsFeatureEnabled())
+      disabled_features.push_back(ash::features::kPrivacyIndicators);
 
-    (IsCrosPrivacyHubEnabled() ? enabled_features : disabled_features)
-        .push_back(ash::features::kCrosPrivacyHub);
+    if (!IsCrosPrivacyHubEnabled())
+      disabled_features.push_back(ash::features::kCrosPrivacyHub);
 
-    scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features);
+    scoped_feature_list_.InitWithFeatures({}, disabled_features);
 
     AppAccessNotifierBaseTest::SetUp();
   }
@@ -287,10 +284,8 @@
 
   // AppAccessNotifierBaseTest:
   void SetUp() override {
-    scoped_feature_list_.InitWithFeatures(
-        {apps::kAppServiceCapabilityAccessWithoutMojom,
-         ash::features::kPrivacyIndicators},
-        {});
+    scoped_feature_list_.InitWithFeatures({ash::features::kPrivacyIndicators},
+                                          {});
     AppAccessNotifierBaseTest::SetUp();
   }
 
diff --git a/chrome/browser/ui/ash/app_list/app_list_with_recent_apps_browsertest.cc b/chrome/browser/ui/ash/app_list/app_list_with_recent_apps_browsertest.cc
index 65386c8..95e2267 100644
--- a/chrome/browser/ui/ash/app_list/app_list_with_recent_apps_browsertest.cc
+++ b/chrome/browser/ui/ash/app_list/app_list_with_recent_apps_browsertest.cc
@@ -4,9 +4,9 @@
 
 #include "ash/public/cpp/test/app_list_test_api.h"
 #include "ash/public/cpp/test/shell_test_api.h"
+#include "chrome/browser/ash/app_list/search/search_controller.h"
 #include "chrome/browser/extensions/extension_browsertest.h"
 #include "chrome/browser/ui/app_list/app_list_client_impl.h"
-#include "chrome/browser/ui/app_list/search/search_controller.h"
 #include "chrome/browser/ui/browser_window.h"
 #include "content/public/test/browser_test.h"
 #include "ui/aura/window.h"
diff --git a/chrome/browser/ui/ash/media_client_impl_unittest.cc b/chrome/browser/ui/ash/media_client_impl_unittest.cc
index e100cfa..df262e11 100644
--- a/chrome/browser/ui/ash/media_client_impl_unittest.cc
+++ b/chrome/browser/ui/ash/media_client_impl_unittest.cc
@@ -7,7 +7,6 @@
 #include <memory>
 
 #include "ash/public/cpp/media_controller.h"
-#include "base/test/scoped_feature_list.h"
 #include "chrome/browser/ash/extensions/media_player_api.h"
 #include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
 #include "chrome/browser/notifications/notification_display_service.h"
@@ -23,8 +22,6 @@
 #include "components/services/app_service/public/cpp/app_types.h"
 #include "components/services/app_service/public/cpp/capability_access.h"
 #include "components/services/app_service/public/cpp/capability_access_update.h"
-#include "components/services/app_service/public/cpp/features.h"
-#include "components/services/app_service/public/mojom/types.mojom-forward.h"
 #include "components/user_manager/fake_user_manager.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -213,10 +210,7 @@
 
 class MediaClientAppUsingCameraTest : public testing::Test {
  public:
-  MediaClientAppUsingCameraTest() {
-    scoped_feature_list_.InitAndEnableFeature(
-        apps::kAppServiceCapabilityAccessWithoutMojom);
-  }
+  MediaClientAppUsingCameraTest() = default;
   MediaClientAppUsingCameraTest(const MediaClientAppUsingCameraTest&) = delete;
   MediaClientAppUsingCameraTest& operator=(
       const MediaClientAppUsingCameraTest&) = delete;
@@ -264,8 +258,6 @@
         std::move(capability_access_deltas));
   }
 
-  base::test::ScopedFeatureList scoped_feature_list_;
-
   const std::string kPrimaryProfileName = "primary_profile";
   const AccountId account_id_ = AccountId::FromUserEmail(kPrimaryProfileName);
 
diff --git a/chrome/browser/ui/browser_commands.cc b/chrome/browser/ui/browser_commands.cc
index 60b0f8c..d031c0a 100644
--- a/chrome/browser/ui/browser_commands.cc
+++ b/chrome/browser/ui/browser_commands.cc
@@ -100,6 +100,7 @@
 #include "chrome/common/pref_names.h"
 #include "chrome/common/url_constants.h"
 #include "components/bookmarks/browser/bookmark_model.h"
+#include "components/bookmarks/browser/bookmark_node.h"
 #include "components/bookmarks/browser/bookmark_utils.h"
 #include "components/bookmarks/common/bookmark_pref_names.h"
 #include "components/browsing_data/content/browsing_data_helper.h"
@@ -271,6 +272,31 @@
          model->IsUrlSupported(*url) && !browser->profile()->IsGuestSession();
 }
 
+bool BookmarkCurrentTabHelper(Browser* browser,
+                              bookmarks::BookmarkModel* model,
+                              GURL* url,
+                              std::u16string* title) {
+  if (!model || !model->loaded())
+    return false;  // Ignore requests until bookmarks are loaded.
+
+  content::WebContents* const web_contents =
+      browser->tab_strip_model()->GetActiveWebContents();
+  // |web_contents| can be nullptr if the last tab in the browser was closed
+  // but the browser wasn't closed yet. https://crbug.com/799668
+  if (!web_contents)
+    return false;
+  if (!chrome::GetURLAndTitleToBookmark(web_contents, url, title))
+    return false;
+  bool is_bookmarked_by_any = model->IsBookmarked(*url);
+  if (!is_bookmarked_by_any &&
+      web_contents->GetBrowserContext()->IsOffTheRecord()) {
+    // If we're incognito the favicon may not have been saved. Save it now
+    // so that bookmarks have an icon for the page.
+    favicon::SaveFaviconEvenIfInIncognito(web_contents);
+  }
+  return true;
+}
+
 }  // namespace
 
 using base::UserMetricsAction;
@@ -1077,29 +1103,13 @@
 }
 
 void BookmarkCurrentTab(Browser* browser) {
-  base::RecordAction(UserMetricsAction("Star"));
-
+  base::RecordAction(base::UserMetricsAction("Star"));
   BookmarkModel* model =
       BookmarkModelFactory::GetForBrowserContext(browser->profile());
-  if (!model || !model->loaded())
-    return;  // Ignore requests until bookmarks are loaded.
-
   GURL url;
   std::u16string title;
-  WebContents* web_contents =
-      browser->tab_strip_model()->GetActiveWebContents();
-  // |web_contents| can be nullptr if the last tab in the browser was closed
-  // but the browser wasn't closed yet. https://crbug.com/799668
-  if (!web_contents)
+  if (!BookmarkCurrentTabHelper(browser, model, &url, &title)) {
     return;
-  if (!GetURLAndTitleToBookmark(web_contents, &url, &title))
-    return;
-  bool is_bookmarked_by_any = model->IsBookmarked(url);
-  if (!is_bookmarked_by_any &&
-      web_contents->GetBrowserContext()->IsOffTheRecord()) {
-    // If we're incognito the favicon may not have been saved. Save it now
-    // so that bookmarks have an icon for the page.
-    favicon::SaveFaviconEvenIfInIncognito(web_contents);
   }
   bool was_bookmarked_by_user = bookmarks::IsBookmarkedByUser(model, url);
   bookmarks::AddIfNotBookmarked(model, url, title);
@@ -1116,6 +1126,25 @@
     RecordBookmarksAdded(browser->profile());
 }
 
+void BookmarkCurrentTabInFolder(Browser* browser, int64_t folder_id) {
+  BookmarkModel* const model =
+      BookmarkModelFactory::GetForBrowserContext(browser->profile());
+  GURL url;
+  std::u16string title;
+  if (!BookmarkCurrentTabHelper(browser, model, &url, &title)) {
+    return;
+  }
+  const bookmarks::BookmarkNode* parent =
+      bookmarks::GetBookmarkNodeByID(model, folder_id);
+  if (parent) {
+    bool was_bookmarked_by_user = bookmarks::IsBookmarkedByUser(model, url);
+    model->AddNewURL(parent, 0, title, url);
+    bool is_bookmarked_by_user = bookmarks::IsBookmarkedByUser(model, url);
+    if (!was_bookmarked_by_user && is_bookmarked_by_user)
+      RecordBookmarksAdded(browser->profile());
+  }
+}
+
 bool CanBookmarkCurrentTab(const Browser* browser) {
   BookmarkModel* model =
       BookmarkModelFactory::GetForBrowserContext(browser->profile());
diff --git a/chrome/browser/ui/browser_commands.h b/chrome/browser/ui/browser_commands.h
index e8532e9..96ef76d 100644
--- a/chrome/browser/ui/browser_commands.h
+++ b/chrome/browser/ui/browser_commands.h
@@ -148,7 +148,12 @@
 void CloseTabsToRight(Browser* browser);
 void CloseOtherTabs(Browser* browser);
 void Exit();
+// Bookmarks the current tab in the most recently used folder and shows the
+// edit dialog.
 void BookmarkCurrentTab(Browser* browser);
+// Bookmarks the current tab in the given folder and does not show the edit
+// dialog.
+void BookmarkCurrentTabInFolder(Browser* browser, int64_t folder_id);
 bool CanBookmarkCurrentTab(const Browser* browser);
 void BookmarkAllTabs(Browser* browser);
 bool CanBookmarkAllTabs(const Browser* browser);
diff --git a/chrome/browser/ui/extensions/hosted_app_browsertest.cc b/chrome/browser/ui/extensions/hosted_app_browsertest.cc
index b7ee647..70a53ba 100644
--- a/chrome/browser/ui/extensions/hosted_app_browsertest.cc
+++ b/chrome/browser/ui/extensions/hosted_app_browsertest.cc
@@ -319,7 +319,7 @@
   web_app::WebAppRegistrar& registrar() {
     auto* provider = web_app::WebAppProvider::GetForTest(profile());
     CHECK(provider);
-    return provider->registrar();
+    return provider->registrar_unsafe();
   }
 
   apps::AppServiceTest& app_service_test() { return app_service_test_; }
diff --git a/chrome/browser/ui/native_window_tracker_browsertest.cc b/chrome/browser/ui/native_window_tracker_browsertest.cc
index 2464397..6822283 100644
--- a/chrome/browser/ui/native_window_tracker_browsertest.cc
+++ b/chrome/browser/ui/native_window_tracker_browsertest.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/ui/native_window_tracker.h"
+#include "ui/views/native_window_tracker.h"
 
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_window.h"
@@ -15,10 +15,12 @@
 IN_PROC_BROWSER_TEST_F(NativeWindowTrackerTest, Basic) {
   // Create a second browser to prevent the app from exiting when the browser is
   // closed.
+
   CreateBrowser(browser()->profile());
 
-  std::unique_ptr<NativeWindowTracker> tracker =
-      NativeWindowTracker::Create(browser()->window()->GetNativeWindow());
+  std::unique_ptr<views::NativeWindowTracker> tracker =
+      views::NativeWindowTracker::Create(
+          browser()->window()->GetNativeWindow());
   EXPECT_FALSE(tracker->WasNativeWindowClosed());
 
   browser()->window()->Close();
diff --git a/chrome/browser/ui/omnibox/omnibox_pedal_implementations.cc b/chrome/browser/ui/omnibox/omnibox_pedal_implementations.cc
index 541469c1..ca272fd 100644
--- a/chrome/browser/ui/omnibox/omnibox_pedal_implementations.cc
+++ b/chrome/browser/ui/omnibox/omnibox_pedal_implementations.cc
@@ -1940,7 +1940,14 @@
   bool IsReadyToTrigger(
       const AutocompleteInput& input,
       const AutocompleteProviderClient& client) const override {
-    return shell_integration::CanSetAsDefaultBrowser();
+    // Note: shell_integration::CanSetAsDefaultBrowser() uses this call too,
+    // and if permission is SET_DEFAULT_NOT_ALLOWED, this method returns false.
+    const shell_integration::DefaultWebClientSetPermission permission =
+        shell_integration::GetDefaultWebClientSetPermission();
+    return (permission == shell_integration::SET_DEFAULT_INTERACTIVE &&
+            OmniboxFieldTrial::kDefaultBrowserPedalInteractive.Get()) ||
+           (permission == shell_integration::SET_DEFAULT_UNATTENDED &&
+            OmniboxFieldTrial::kDefaultBrowserPedalUnattended.Get());
   }
 
   void Execute(ExecutionContext& context) const override {
diff --git a/chrome/browser/ui/omnibox/omnibox_view_browsertest.cc b/chrome/browser/ui/omnibox/omnibox_view_browsertest.cc
index 6e117e6..f9a4227 100644
--- a/chrome/browser/ui/omnibox/omnibox_view_browsertest.cc
+++ b/chrome/browser/ui/omnibox/omnibox_view_browsertest.cc
@@ -15,6 +15,7 @@
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/task/single_thread_task_runner.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
@@ -65,6 +66,7 @@
 #include "ui/events/event_constants.h"
 #include "ui/events/keycodes/keyboard_codes.h"
 #include "ui/gfx/geometry/point.h"
+#include "url/url_features.h"
 
 using base::ASCIIToUTF16;
 using base::UTF16ToUTF8;
@@ -1422,6 +1424,11 @@
   static constexpr char kHistogram[] =
       "Navigation.HostnameHasDeviationCharacters";
 
+  NavigationMetricsRecorderIDNABrowserTest() {
+    scoped_feature_list_.InitAndDisableFeature(
+        url::kUseIDNA2008NonTransitional);
+  }
+
   void SetUpOnMainThread() override {
     host_resolver()->AddRule("*", "127.0.0.1");
     test_ukm_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>();
@@ -1467,6 +1474,7 @@
 
  private:
   std::unique_ptr<ukm::TestAutoSetUkmRecorder> test_ukm_recorder_;
+  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
 IN_PROC_BROWSER_TEST_F(NavigationMetricsRecorderIDNABrowserTest,
diff --git a/chrome/browser/ui/sharing_hub/sharing_hub_bubble_controller_chromeos_impl.cc b/chrome/browser/ui/sharing_hub/sharing_hub_bubble_controller_chromeos_impl.cc
index 9017966..578339c9 100644
--- a/chrome/browser/ui/sharing_hub/sharing_hub_bubble_controller_chromeos_impl.cc
+++ b/chrome/browser/ui/sharing_hub/sharing_hub_bubble_controller_chromeos_impl.cc
@@ -164,7 +164,7 @@
   // Save the window in order to close the Sharesheet if the tab is closed. This
   // will return the incorrect window if called later.
   parent_window_ = GetWebContents().GetTopLevelNativeWindow();
-  parent_window_tracker_ = NativeWindowTracker::Create(parent_window_);
+  parent_window_tracker_ = views::NativeWindowTracker::Create(parent_window_);
 }
 
 void SharingHubBubbleControllerChromeOsImpl::CloseSharesheet() {
diff --git a/chrome/browser/ui/sharing_hub/sharing_hub_bubble_controller_chromeos_impl.h b/chrome/browser/ui/sharing_hub/sharing_hub_bubble_controller_chromeos_impl.h
index f6c7c74..c73a303 100644
--- a/chrome/browser/ui/sharing_hub/sharing_hub_bubble_controller_chromeos_impl.h
+++ b/chrome/browser/ui/sharing_hub/sharing_hub_bubble_controller_chromeos_impl.h
@@ -7,10 +7,10 @@
 
 #include "base/memory/weak_ptr.h"
 #include "build/chromeos_buildflags.h"
-#include "chrome/browser/ui/native_window_tracker.h"
 #include "chrome/browser/ui/sharing_hub/sharing_hub_bubble_controller.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "content/public/browser/web_contents_user_data.h"
+#include "ui/views/native_window_tracker.h"
 #include "ui/views/view_tracker.h"
 #include "ui/views/widget/widget.h"
 
@@ -95,7 +95,7 @@
 
   views::ViewTracker highlighted_button_tracker_;
   gfx::NativeWindow parent_window_ = nullptr;
-  std::unique_ptr<NativeWindowTracker> parent_window_tracker_ = nullptr;
+  std::unique_ptr<views::NativeWindowTracker> parent_window_tracker_ = nullptr;
   bool bubble_showing_ = false;
 
   WEB_CONTENTS_USER_DATA_KEY_DECL();
diff --git a/chrome/browser/ui/startup/startup_browser_creator_impl.cc b/chrome/browser/ui/startup/startup_browser_creator_impl.cc
index 6d143c95..a8e60adf 100644
--- a/chrome/browser/ui/startup/startup_browser_creator_impl.cc
+++ b/chrome/browser/ui/startup/startup_browser_creator_impl.cc
@@ -342,7 +342,19 @@
       browser->tab_strip_model()->ActivateTabAt(0);
   }
 
-  browser->window()->Show();
+#if BUILDFLAG(IS_MAC)
+  // On Mac, LaunchServices will send activation events if necessary.
+  // Prefer not activating the browser window when opening new tabs, leaving the
+  // activation task to the system.
+  if (process_startup == chrome::startup::IsProcessStartup::kNo &&
+      BrowserList::GetInstance()->GetLastActive() == browser) {
+    browser->window()->ShowInactive();
+  } else {
+#endif
+    browser->window()->Show();
+#if BUILDFLAG(IS_MAC)
+  }
+#endif
 
   return browser;
 }
diff --git a/chrome/browser/ui/tab_contents/core_tab_helper.cc b/chrome/browser/ui/tab_contents/core_tab_helper.cc
index b1cfbfab..323fed0 100644
--- a/chrome/browser/ui/tab_contents/core_tab_helper.cc
+++ b/chrome/browser/ui/tab_contents/core_tab_helper.cc
@@ -143,6 +143,7 @@
 
 bool CoreTabHelper::IsSidePanelEnabledFor3PDse() {
   return IsSidePanelEnabled() &&
+         !search::DefaultSearchProviderIsGoogle(GetTemplateURLService()) &&
          base::FeatureList::IsEnabled(features::kUnifiedSidePanel) &&
          lens::features::GetEnableImageSearchUnifiedSidePanelFor3PDse();
 }
diff --git a/chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_model_listener.cc b/chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_model_listener.cc
index 32b4b992..5249987 100644
--- a/chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_model_listener.cc
+++ b/chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_model_listener.cc
@@ -15,61 +15,27 @@
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "components/saved_tab_groups/saved_tab_group_model.h"
 
-SavedTabGroupModelListener::SavedTabGroupModelListener() = default;
-
-SavedTabGroupModelListener::SavedTabGroupModelListener(
-    SavedTabGroupModel* model,
-    Profile* profile)
-    : model_(model), profile_(profile) {
-  DCHECK(model);
-  DCHECK(profile);
-  BrowserList::GetInstance()->AddObserver(this);
-  for (Browser* browser : *BrowserList::GetInstance())
-    OnBrowserAdded(browser);
+// TODO(crbug/1376259): Update SavedTabGroupModel state with any groups that
+// should be in the SavedTabGroupModel.
+SavedTabGroupBrowserListener::SavedTabGroupBrowserListener(
+    Browser* browser,
+    SavedTabGroupModel* model)
+    : browser_(browser), model_(model) {
+  DCHECK(browser_);
+  browser_->tab_strip_model()->AddObserver(this);
 }
 
-SavedTabGroupModelListener::~SavedTabGroupModelListener() {
-  BrowserList::GetInstance()->RemoveObserver(this);
-  // Note: Can no longer call OnBrowserRemoved here because model_ is already
-  // destroyed.
-  for (Browser* browser : observed_browsers_)
-    browser->tab_strip_model()->RemoveObserver(this);
-
-  observed_browsers_.clear();
+SavedTabGroupBrowserListener::~SavedTabGroupBrowserListener() {
+  if (browser_)
+    browser_->tab_strip_model()->RemoveObserver(this);
 }
 
-TabStripModel* SavedTabGroupModelListener::GetTabStripModelWithTabGroupId(
-    tab_groups::TabGroupId group_id) {
-  auto contains_tab_group = [&](TabStripModel* model) {
-    return model->group_model()->ContainsTabGroup(group_id);
-  };
-  base::flat_set<raw_ptr<Browser>>::iterator it = base::ranges::find_if(
-      observed_browsers_, contains_tab_group, &Browser::tab_strip_model);
-  return it != observed_browsers_.end() ? it->get()->tab_strip_model()
-                                        : nullptr;
+bool SavedTabGroupBrowserListener::ContainsTabGroup(
+    tab_groups::TabGroupId group_id) const {
+  return browser_->tab_strip_model()->group_model()->ContainsTabGroup(group_id);
 }
 
-void SavedTabGroupModelListener::OnBrowserAdded(Browser* browser) {
-  if (profile_ != browser->profile())
-    return;
-  if (observed_browsers_.count(browser)) {
-    // TODO(crbug.com/1345680): Investigate the root cause of duplicate calls.
-    return;
-  }
-  observed_browsers_.insert(browser);
-  browser->tab_strip_model()->AddObserver(this);
-  // TODO(crbug/1376259): Update SavedTabGroupModel state with any groups that
-  // should be in the SavedTabGroupModel.
-}
-
-void SavedTabGroupModelListener::OnBrowserRemoved(Browser* browser) {
-  if (profile_ != browser->profile())
-    return;
-  observed_browsers_.erase(browser);
-  browser->tab_strip_model()->RemoveObserver(this);
-}
-
-void SavedTabGroupModelListener::OnTabGroupChanged(
+void SavedTabGroupBrowserListener::OnTabGroupChanged(
     const TabGroupChange& change) {
   const TabStripModel* tab_strip_model = change.model;
   if (!model_->Contains(change.group))
@@ -108,3 +74,49 @@
     }
   }
 }
+
+SavedTabGroupModelListener::SavedTabGroupModelListener() = default;
+
+SavedTabGroupModelListener::SavedTabGroupModelListener(
+    SavedTabGroupModel* model,
+    Profile* profile)
+    : model_(model), profile_(profile) {
+  DCHECK(model);
+  DCHECK(profile);
+  BrowserList::GetInstance()->AddObserver(this);
+  for (Browser* browser : *BrowserList::GetInstance())
+    OnBrowserAdded(browser);
+}
+
+SavedTabGroupModelListener::~SavedTabGroupModelListener() {
+  BrowserList::GetInstance()->RemoveObserver(this);
+  observed_browser_listeners_.clear();
+}
+
+TabStripModel* SavedTabGroupModelListener::GetTabStripModelWithTabGroupId(
+    tab_groups::TabGroupId group_id) {
+  auto it = base::ranges::find_if(
+      observed_browser_listeners_, [group_id](const auto& listener_pair) {
+        return listener_pair.second.ContainsTabGroup(group_id);
+      });
+  return it != observed_browser_listeners_.end()
+             ? it->second.browser()->tab_strip_model()
+             : nullptr;
+}
+
+void SavedTabGroupModelListener::OnBrowserAdded(Browser* browser) {
+  if (profile_ != browser->profile())
+    return;
+
+  // TODO(crbug.com/1345680): Investigate the root cause of duplicate calls.
+  if (observed_browser_listeners_.count(browser) > 0)
+    return;
+
+  observed_browser_listeners_.try_emplace(browser, browser, model_);
+}
+
+void SavedTabGroupModelListener::OnBrowserRemoved(Browser* browser) {
+  if (profile_ != browser->profile())
+    return;
+  observed_browser_listeners_.erase(browser);
+}
diff --git a/chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_model_listener.h b/chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_model_listener.h
index b874270..d147413 100644
--- a/chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_model_listener.h
+++ b/chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_model_listener.h
@@ -10,16 +10,36 @@
 #include "chrome/browser/ui/browser_list_observer.h"
 #include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
 #include "components/tab_groups/tab_group_id.h"
+#include "content/public/browser/web_contents.h"
 
-class Browser;
 class SavedTabGroupModel;
 class TabStripModel;
 class Profile;
 
+// Manages the listening state for each individual tabstrip.
+class SavedTabGroupBrowserListener : public TabStripModelObserver {
+ public:
+  SavedTabGroupBrowserListener(Browser* browser, SavedTabGroupModel* model);
+  ~SavedTabGroupBrowserListener() override;
+
+  bool ContainsTabGroup(tab_groups::TabGroupId group_id) const;
+
+  // TabStripModelObserver:
+  void OnTabGroupChanged(const TabGroupChange& change) override;
+
+  Browser* browser() { return browser_; }
+  SavedTabGroupModel* saved_tab_group_model() { return model_; }
+
+ private:
+  std::unordered_map<content::WebContents*, base::Token>
+      web_contents_to_tab_id_map_;
+  raw_ptr<Browser> browser_;
+  raw_ptr<SavedTabGroupModel> model_;
+};
+
 // Serves to maintain and listen to browsers who contain saved tab groups and
 // update the model if a saved tab group was changed.
-class SavedTabGroupModelListener : public BrowserListObserver,
-                                   public TabStripModelObserver {
+class SavedTabGroupModelListener : public BrowserListObserver {
  public:
   // Used for testing.
   SavedTabGroupModelListener();
@@ -37,11 +57,15 @@
   void OnBrowserAdded(Browser* browser) override;
   void OnBrowserRemoved(Browser* browser) override;
 
-  // TabStripModelObserver:
-  void OnTabGroupChanged(const TabGroupChange& change) override;
+  // Testing Accessors.
+  std::unordered_map<Browser*, SavedTabGroupBrowserListener>&
+  GetBrowserListenerMapForTesting() {
+    return observed_browser_listeners_;
+  }
 
  private:
-  base::flat_set<raw_ptr<Browser>> observed_browsers_;
+  std::unordered_map<Browser*, SavedTabGroupBrowserListener>
+      observed_browser_listeners_;
   raw_ptr<SavedTabGroupModel> model_ = nullptr;
   raw_ptr<Profile> profile_;
 };
diff --git a/chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_model_listener_unittest.cc b/chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_model_listener_unittest.cc
new file mode 100644
index 0000000..31747e3
--- /dev/null
+++ b/chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_model_listener_unittest.cc
@@ -0,0 +1,99 @@
+// 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/ui/tabs/saved_tab_groups/saved_tab_group_model_listener.h"
+
+#include <memory>
+
+#include "chrome/test/base/browser_with_test_window_test.h"
+#include "chrome/test/base/testing_profile.h"
+#include "components/saved_tab_groups/saved_tab_group_model.h"
+#include "components/tab_groups/tab_group_id.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/test/browser_task_environment.h"
+#include "content/public/test/web_contents_tester.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/page_transition_types.h"
+#include "ui/base/ui_base_types.h"
+
+class SavedTabGroupModelListenerTest : public BrowserWithTestWindowTest {
+ public:
+  SavedTabGroupModelListenerTest() = default;
+  SavedTabGroupModelListenerTest(const SavedTabGroupModelListenerTest&) =
+      delete;
+  SavedTabGroupModelListenerTest& operator=(
+      const SavedTabGroupModelListenerTest&) = delete;
+
+  Browser* AddBrowser() {
+    Browser::CreateParams native_params(profile_.get(), true);
+    native_params.initial_show_state = ui::SHOW_STATE_DEFAULT;
+    std::unique_ptr<Browser> browser =
+        CreateBrowserWithTestWindowForParams(native_params);
+    Browser* browser_ptr = browser.get();
+    browsers_.emplace_back(std::move(browser));
+    return browser_ptr;
+  }
+
+  void AddTabToBrowser(Browser* browser, int index) {
+    std::unique_ptr<content::WebContents> web_contents =
+        content::WebContentsTester::CreateTestWebContents(profile_.get(),
+                                                          nullptr);
+    browser->tab_strip_model()->AddWebContents(
+        std::move(web_contents), index,
+        ui::PageTransition::PAGE_TRANSITION_TYPED, AddTabTypes::ADD_ACTIVE);
+  }
+
+  SavedTabGroupModelListener* listener() { return listener_.get(); }
+
+ private:
+  void SetUp() override {
+    profile_ = std::make_unique<TestingProfile>();
+    model_ = std::make_unique<SavedTabGroupModel>();
+    listener_ = std::make_unique<SavedTabGroupModelListener>(model_.get(),
+                                                             profile_.get());
+  }
+  void TearDown() override {
+    for (auto& browser : browsers_) {
+      browser->tab_strip_model()->CloseAllTabs();
+    }
+  }
+
+  content::RenderViewHostTestEnabler rvh_test_enabler_;
+
+  std::unique_ptr<TestingProfile> profile_;
+  std::unique_ptr<SavedTabGroupModel> model_;
+  std::unique_ptr<SavedTabGroupModelListener> listener_;
+
+  std::vector<std::unique_ptr<Browser>> browsers_;
+};
+
+TEST_F(SavedTabGroupModelListenerTest, CreatesRemovesBrowserListener) {
+  Browser* browser_1 = AddBrowser();
+  Browser* browser_2 = AddBrowser();
+
+  EXPECT_EQ(listener()->GetBrowserListenerMapForTesting().count(browser_1), 1u);
+  EXPECT_EQ(listener()->GetBrowserListenerMapForTesting().count(browser_2), 1u);
+
+  listener()->OnBrowserRemoved(browser_1);
+  EXPECT_EQ(listener()->GetBrowserListenerMapForTesting().count(browser_1), 0u);
+  EXPECT_EQ(listener()->GetBrowserListenerMapForTesting().count(browser_2), 1u);
+}
+
+TEST_F(SavedTabGroupModelListenerTest, GetTabStripModelWithTabGroupId) {
+  Browser* browser_1 = AddBrowser();
+
+  EXPECT_TRUE(listener()->GetBrowserListenerMapForTesting().count(browser_1) >
+              0);
+
+  // Create a new tab and add it to a group.
+  ASSERT_EQ(0, browser_1->tab_strip_model()->count());
+  AddTabToBrowser(browser_1, 0);
+  ASSERT_EQ(1, browser_1->tab_strip_model()->count());
+  tab_groups::TabGroupId group_id =
+      browser_1->tab_strip_model()->AddToNewGroup({0});
+
+  EXPECT_EQ(browser_1->tab_strip_model(),
+            listener()->GetTabStripModelWithTabGroupId(group_id));
+}
diff --git a/chrome/browser/ui/tabs/tab_group.cc b/chrome/browser/ui/tabs/tab_group.cc
index c15e97a..5798706a7 100644
--- a/chrome/browser/ui/tabs/tab_group.cc
+++ b/chrome/browser/ui/tabs/tab_group.cc
@@ -161,7 +161,7 @@
   if (!backend || !backend->model())
     return;
   SavedTabGroup saved_tab_group(visual_data_->title(), visual_data_->color(),
-                                tabs, saved_group_guid, id_);
+                                tabs, saved_group_guid, absl::nullopt, id_);
   backend->model()->Add(saved_tab_group);
 }
 
diff --git a/chrome/browser/ui/views/apps/app_dialog/app_uninstall_dialog_view_browsertest.cc b/chrome/browser/ui/views/apps/app_dialog/app_uninstall_dialog_view_browsertest.cc
index 83449c20..1e03fbdc 100644
--- a/chrome/browser/ui/views/apps/app_dialog/app_uninstall_dialog_view_browsertest.cc
+++ b/chrome/browser/ui/views/apps/app_dialog/app_uninstall_dialog_view_browsertest.cc
@@ -195,7 +195,7 @@
 
     auto* provider = web_app::WebAppProvider::GetForTest(browser()->profile());
     DCHECK(provider);
-    app_name_ = provider->registrar().GetAppShortName(app_id_);
+    app_name_ = provider->registrar_unsafe().GetAppShortName(app_id_);
   }
 
   GURL GetAppURL() const {
diff --git a/chrome/browser/ui/views/autofill/payments/card_unmask_authentication_selection_dialog_view.cc b/chrome/browser/ui/views/autofill/payments/card_unmask_authentication_selection_dialog_view.cc
index 13d65778..5d337cc 100644
--- a/chrome/browser/ui/views/autofill/payments/card_unmask_authentication_selection_dialog_view.cc
+++ b/chrome/browser/ui/views/autofill/payments/card_unmask_authentication_selection_dialog_view.cc
@@ -230,10 +230,11 @@
 CardUnmaskAuthenticationSelectionDialogView::CreateChallengeOptionRadioButton(
     CardUnmaskChallengeOption challenge_option) {
   auto radio_button = std::make_unique<views::RadioButton>();
-  radio_button->SetCallback(
-      base::BindRepeating(&CardUnmaskAuthenticationSelectionDialogController::
-                              SetSelectedChallengeOptionId,
-                          base::Unretained(controller_), challenge_option.id));
+  radio_button_checked_changed_subscriptions_.push_back(
+      radio_button->AddCheckedChangedCallback(base::BindRepeating(
+          &CardUnmaskAuthenticationSelectionDialogController::
+              SetSelectedChallengeOptionId,
+          base::Unretained(controller_), challenge_option.id)));
   radio_button->SetAccessibleName(
       controller_->GetAuthenticationModeLabel(challenge_option) + u". " +
       challenge_option.challenge_info);
diff --git a/chrome/browser/ui/views/autofill/payments/card_unmask_authentication_selection_dialog_view.h b/chrome/browser/ui/views/autofill/payments/card_unmask_authentication_selection_dialog_view.h
index e0e2d6cd..9d7ab32 100644
--- a/chrome/browser/ui/views/autofill/payments/card_unmask_authentication_selection_dialog_view.h
+++ b/chrome/browser/ui/views/autofill/payments/card_unmask_authentication_selection_dialog_view.h
@@ -72,6 +72,12 @@
   // CardUnmaskAuthenticationSelectionDialog.
   raw_ptr<CardUnmaskAuthenticationSelectionDialogController> controller_ =
       nullptr;
+
+  // Vector of radio button checked changed subscriptions. Stored due to the
+  // requirement that the subscription must be in memory when the callback is
+  // used.
+  std::vector<base::CallbackListSubscription>
+      radio_button_checked_changed_subscriptions_;
 };
 
 }  // namespace autofill
diff --git a/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_bar_browsertest.cc b/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_bar_browsertest.cc
index 03483c47..5007a5d 100644
--- a/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_bar_browsertest.cc
+++ b/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_bar_browsertest.cc
@@ -21,6 +21,7 @@
 #include "components/tab_groups/tab_group_id.h"
 #include "content/public/test/browser_test.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 using SavedTabGroupBarBrowserTest = InProcessBrowserTest;
 
@@ -123,7 +124,7 @@
       std::u16string(u"test_title_1"), tab_groups::TabGroupColorId::kGrey,
       {SavedTabGroupTab(GURL("chrome://newtab"), u"New Tab Title", guid)
            .SetFavicon(favicon::GetDefaultFavicon())},
-      guid, group_id));
+      guid, absl::nullopt, group_id));
   EXPECT_TRUE(model->group_model()->GetTabGroup(group_id)->IsSaved());
 
   // Remove the group from the SavedTabGroupModel and expect it is no longer
diff --git a/chrome/browser/ui/views/download/bubble/download_bubble_security_view.cc b/chrome/browser/ui/views/download/bubble/download_bubble_security_view.cc
index 995bffc..a97ace7 100644
--- a/chrome/browser/ui/views/download/bubble/download_bubble_security_view.cc
+++ b/chrome/browser/ui/views/download/bubble/download_bubble_security_view.cc
@@ -252,6 +252,7 @@
     bubble_delegate_->SetCancelCallback(std::move(callback));
     bubble_delegate_->SetButtonEnabled(button_type, !has_checkbox);
     views::LabelButton* button = bubble_delegate_->GetCancelButton();
+    button->SetEnabledTextColorReadabilityAdjustment(true);
     button->SetEnabledTextColors(color);
     secondary_button_ = button;
   } else {
diff --git a/chrome/browser/ui/views/eye_dropper/eye_dropper_view_aura.cc b/chrome/browser/ui/views/eye_dropper/eye_dropper_view_aura.cc
index 96a4afc..fd71244 100644
--- a/chrome/browser/ui/views/eye_dropper/eye_dropper_view_aura.cc
+++ b/chrome/browser/ui/views/eye_dropper/eye_dropper_view_aura.cc
@@ -6,10 +6,10 @@
 
 #include "base/memory/raw_ptr.h"
 #include "build/build_config.h"
-#include "chrome/browser/ui/native_window_tracker.h"
 #include "ui/aura/client/cursor_client.h"
 #include "ui/aura/window.h"
 #include "ui/base/ui_base_features.h"
+#include "ui/views/native_window_tracker.h"
 
 class EyeDropperView::PreEventDispatchHandler::KeyboardHandler
     : public ui::EventHandler {
@@ -24,7 +24,7 @@
 
   raw_ptr<EyeDropperView> view_;
   raw_ptr<aura::Window> parent_;
-  std::unique_ptr<NativeWindowTracker> parent_tracker_;
+  std::unique_ptr<views::NativeWindowTracker> parent_tracker_;
 };
 
 EyeDropperView::PreEventDispatchHandler::KeyboardHandler::KeyboardHandler(
@@ -32,7 +32,7 @@
     aura::Window* parent)
     : view_(view),
       parent_(parent),
-      parent_tracker_(NativeWindowTracker::Create(parent)) {
+      parent_tracker_(views::NativeWindowTracker::Create(parent)) {
   // Because the eye dropper is not focused in order to not dismiss the color
   // popup, we need to listen for key events on the parent window that has
   // focus.
diff --git a/chrome/browser/ui/views/frame/browser_frame_view_paint_utils_linux.cc b/chrome/browser/ui/views/frame/browser_frame_view_paint_utils_linux.cc
index 0c006d6..e85efdb 100644
--- a/chrome/browser/ui/views/frame/browser_frame_view_paint_utils_linux.cc
+++ b/chrome/browser/ui/views/frame/browser_frame_view_paint_utils_linux.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/ui/views/frame/browser_frame_view_paint_utils_linux.h"
 
+#include "third_party/skia/include/core/SkRRect.h"
 #include "ui/color/color_provider.h"
 #include "ui/gfx/geometry/rect_conversions.h"
 #include "ui/gfx/scoped_canvas.h"
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 65476519..4afe3bf 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
@@ -70,7 +70,7 @@
     auto* provider =
         web_app::WebAppProvider::GetForWebApps(browser_view->GetProfile());
     always_show_toolbar_in_fullscreen_observation_.Observe(
-        &provider->registrar());
+        &provider->registrar_unsafe());
   } else {
     show_fullscreen_toolbar_.Init(
         prefs::kShowFullscreenToolbar, browser_view->GetProfile()->GetPrefs(),
diff --git a/chrome/browser/ui/views/frame/browser_view.cc b/chrome/browser/ui/views/frame/browser_view.cc
index fd0c69ad..978b9cb 100644
--- a/chrome/browser/ui/views/frame/browser_view.cc
+++ b/chrome/browser/ui/views/frame/browser_view.cc
@@ -1317,8 +1317,7 @@
 }
 
 void BrowserView::ShowInactive() {
-  if (!frame_->IsVisible())
-    frame_->ShowInactive();
+  frame_->ShowInactive();
 }
 
 void BrowserView::Hide() {
diff --git a/chrome/browser/ui/views/lens/lens_region_search_instructions_view.cc b/chrome/browser/ui/views/lens/lens_region_search_instructions_view.cc
index 2982ca4..225111f3 100644
--- a/chrome/browser/ui/views/lens/lens_region_search_instructions_view.cc
+++ b/chrome/browser/ui/views/lens/lens_region_search_instructions_view.cc
@@ -33,16 +33,6 @@
 constexpr int kCloseButtonExtraMargin = 4;
 constexpr int kCloseButtonSize = 17;
 constexpr int kCornerRadius = 18;
-constexpr int kLabelExtraLeftMargin = 2;
-
-int GetLensInstructionChipString() {
-  if (features::UseAltChipString()) {
-    return IDS_LENS_REGION_SEARCH_BUBBLE_TEXT_ALT2;
-  }
-  return features::IsLensInstructionChipImprovementsEnabled()
-             ? IDS_LENS_REGION_SEARCH_BUBBLE_TEXT_ALT1
-             : IDS_LENS_REGION_SEARCH_BUBBLE_TEXT;
-}
 
 LensRegionSearchInstructionsView::LensRegionSearchInstructionsView(
     views::View* anchor_view,
@@ -55,35 +45,16 @@
   // The cancel close_callback is called when VKEY_ESCAPE is hit.
   SetCancelCallback(std::move(escape_callback));
 
-  if (features::IsLensInstructionChipImprovementsEnabled()) {
-    // Create a close button that is always white instead of conforming to
-    // native theme.
-    // TODO(crbug/1353948): Refactor/migrate this callback away from using
-    // base::Passed.
-    close_button_ = views::CreateVectorImageButton(base::BindRepeating(
-        [](base::OnceClosure callback) {
-          DCHECK(callback);
-          std::move(callback).Run();
-        },
-        base::Passed(std::move(close_callback))));
-  }
-
-  // Create our own close button to align with label. We need to rebind our
-  // OnceClosure to repeating due tot ImageButton::PressedCallback
-  // inheritance. However, this callback should still only be called once and
-  // this is verified with a DCHECK.
-  if (!close_button_) {
-    // TODO(crbug/1353948): Refactor/migrate this callback away from using
-    // base::Passed.
-    close_button_ = views::CreateVectorImageButtonWithNativeTheme(
-        base::BindRepeating(
-            [](base::OnceClosure callback) {
-              DCHECK(callback);
-              std::move(callback).Run();
-            },
-            base::Passed(std::move(close_callback))),
-        views::kIcCloseIcon, kCloseButtonSize);
-  }
+  // Create a close button that is always white instead of conforming to
+  // native theme.
+  // TODO(crbug/1353948): Refactor/migrate this callback away from using
+  // base::Passed.
+  close_button_ = views::CreateVectorImageButton(base::BindRepeating(
+      [](base::OnceClosure callback) {
+        DCHECK(callback);
+        std::move(callback).Run();
+      },
+      base::Passed(std::move(close_callback))));
   close_button_->SetTooltipText(l10n_util::GetStringUTF16(IDS_ACCNAME_CLOSE));
 }
 
@@ -95,13 +66,8 @@
       .SetCollapseMargins(true);
 
   ChromeLayoutProvider* const layout_provider = ChromeLayoutProvider::Get();
-  int left_margin =
-      features::IsLensInstructionChipImprovementsEnabled()
-          ? layout_provider->GetDistanceMetric(
-                views::DistanceMetric::DISTANCE_RELATED_CONTROL_HORIZONTAL)
-          : layout_provider->GetDistanceMetric(
-                views::DistanceMetric::DISTANCE_RELATED_LABEL_HORIZONTAL) +
-                kLabelExtraLeftMargin;
+  int left_margin = layout_provider->GetDistanceMetric(
+      views::DistanceMetric::DISTANCE_RELATED_CONTROL_HORIZONTAL);
   set_margins(gfx::Insets::TLBR(
       layout_provider->GetInsetsMetric(views::InsetsMetric::INSETS_LABEL_BUTTON)
           .top(),
@@ -115,19 +81,14 @@
   set_close_on_deactivate(false);
   set_corner_radius(kCornerRadius);
 
-  // Add the leading drag selection icon if enabled.
-  if (features::IsLensInstructionChipImprovementsEnabled()) {
-    const gfx::VectorIcon& selection_icon =
-        features::UseSelectionIconWithImage()
-            ? views::kDragImageSelectionIcon
-            : views::kDragGeneralSelectionIcon;
-    auto selection_icon_view =
-        std::make_unique<views::ImageView>(ui::ImageModel::FromVectorIcon(
-            selection_icon, kColorFeatureLensPromoBubbleForeground,
-            layout_provider->GetDistanceMetric(
-                DISTANCE_BUBBLE_HEADER_VECTOR_ICON_SIZE)));
-    AddChildView(std::move(selection_icon_view));
-  }
+  // Add the leading drag selection icon.
+  auto selection_icon_view =
+      std::make_unique<views::ImageView>(ui::ImageModel::FromVectorIcon(
+          views::kDragGeneralSelectionIcon,
+          kColorFeatureLensPromoBubbleForeground,
+          layout_provider->GetDistanceMetric(
+              DISTANCE_BUBBLE_HEADER_VECTOR_ICON_SIZE)));
+  AddChildView(std::move(selection_icon_view));
 
   gfx::Font default_font;
   // We need to derive a font size delta between our desired font size and the
@@ -135,26 +96,23 @@
   // the font list.
   int font_size_delta = kTextFontSize - default_font.GetFontSize();
   auto label = std::make_unique<views::Label>(
-      l10n_util::GetStringUTF16(GetLensInstructionChipString()));
+      l10n_util::GetStringUTF16(IDS_LENS_REGION_SEARCH_BUBBLE_TEXT));
   label->SetFontList(gfx::FontList().Derive(font_size_delta, gfx::Font::NORMAL,
                                             gfx::Font::Weight::MEDIUM));
   label->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_CENTER);
   label->SetVerticalAlignment(gfx::VerticalAlignment::ALIGN_MIDDLE);
-  // If chip improvements are enabled, we force the label color to white.
-  if (features::IsLensInstructionChipImprovementsEnabled()) {
-    // Set label margins to vector icons in chips, including adjustments for the
-    // extra margin that the close button sets below.
-    label->SetProperty(
-        views::kMarginsKey,
-        gfx::Insets::TLBR(
-            0,
-            layout_provider->GetDistanceMetric(
-                views::DistanceMetric::DISTANCE_RELATED_LABEL_HORIZONTAL),
-            0,
-            layout_provider->GetDistanceMetric(
-                views::DistanceMetric::DISTANCE_RELATED_LABEL_HORIZONTAL) -
-                kCloseButtonExtraMargin));
-  }
+  // Set label margins to vector icons in chips, including adjustments for the
+  // extra margin that the close button sets below.
+  label->SetProperty(
+      views::kMarginsKey,
+      gfx::Insets::TLBR(
+          0,
+          layout_provider->GetDistanceMetric(
+              views::DistanceMetric::DISTANCE_RELATED_LABEL_HORIZONTAL),
+          0,
+          layout_provider->GetDistanceMetric(
+              views::DistanceMetric::DISTANCE_RELATED_LABEL_HORIZONTAL) -
+              kCloseButtonExtraMargin));
   label_ = AddChildView(std::move(label));
 
   close_button_->SetImageHorizontalAlignment(views::ImageButton::ALIGN_CENTER);
@@ -168,8 +126,6 @@
 
 void LensRegionSearchInstructionsView::OnThemeChanged() {
   BubbleDialogDelegateView::OnThemeChanged();
-  if (!features::IsLensInstructionChipImprovementsEnabled())
-    return;
   const auto* const color_provider = GetColorProvider();
   auto foreground_color =
       color_provider->GetColor(kColorFeatureLensPromoBubbleForeground);
diff --git a/chrome/browser/ui/views/omnibox/omnibox_match_cell_view.cc b/chrome/browser/ui/views/omnibox/omnibox_match_cell_view.cc
index a81b4ac0..9164281c 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_match_cell_view.cc
+++ b/chrome/browser/ui/views/omnibox/omnibox_match_cell_view.cc
@@ -18,6 +18,7 @@
 #include "components/omnibox/browser/autocomplete_match.h"
 #include "components/omnibox/browser/autocomplete_match_type.h"
 #include "components/omnibox/browser/omnibox_field_trial.h"
+#include "components/omnibox/browser/suggestion_answer.h"
 #include "components/omnibox/browser/vector_icons.h"
 #include "components/omnibox/common/omnibox_features.h"
 #include "content/public/common/color_parser.h"
@@ -47,7 +48,14 @@
 
 // The edge length of the entity suggestions images.
 static constexpr int kEntityImageSize = 32;
-// TODO: set image size to 28 px if UniformRowHeight feature is enabled.
+// The edge length of the entity suggestions if the
+// kUniformRowHeight flag is enabled
+static constexpr int kEntityImageSizeSmall = 28;
+
+int GetEntityImageSize() {
+  return OmniboxFieldTrial::IsUniformRowHeightEnabled() ? kEntityImageSizeSmall
+                                                        : kEntityImageSize;
+}
 
 ////////////////////////////////////////////////////////////////////////////////
 // PlaceholderImageSource:
@@ -261,7 +269,10 @@
           GetOmniboxBackgroundColorId(result_view->GetThemeState()));
       content::ParseHexColorString(match.image_dominant_color, &color);
       color = SkColorSetA(color, 0x40);  // 25% transparency (arbitrary).
-      constexpr gfx::Size size(kEntityImageSize, kEntityImageSize);
+
+      const auto size_px = GetEntityImageSize();
+
+      gfx::Size size(size_px, size_px);
       answer_image_view_->SetImageSize(size);
       answer_image_view_->SetImage(
           gfx::CanvasImageSource::MakeImageSkia<PlaceholderImageSource>(size,
@@ -285,8 +296,9 @@
   if (width == height)
     return;
   const int max = std::max(width, height);
-  width = kEntityImageSize * width / max;
-  height = kEntityImageSize * height / max;
+  int imageSize = GetEntityImageSize();
+  width = imageSize * width / max;
+  height = imageSize * height / max;
   answer_image_view_->SetImageSize(gfx::Size(width, height));
 }
 
@@ -294,8 +306,8 @@
   const bool single_line = layout_style_ == LayoutStyle::ONE_LINE_SUGGESTION;
 
   const int vertical_margin =
-      OmniboxFieldTrial::IsUniformRowHeightEnabled()
-          ? OmniboxFieldTrial::kSuggestionVerticalMargin.Get()
+      OmniboxFieldTrial::IsUniformRowHeightEnabled() && has_image_
+          ? OmniboxFieldTrial::kRichSuggestionVerticalMargin.Get()
           : ChromeLayoutProvider::Get()->GetDistanceMetric(
                 single_line ? DISTANCE_OMNIBOX_CELL_VERTICAL_PADDING
                             : DISTANCE_OMNIBOX_TWO_LINE_CELL_VERTICAL_PADDING);
@@ -369,7 +381,11 @@
 }
 
 gfx::Size OmniboxMatchCellView::CalculatePreferredSize() const {
-  int height = content_view_->GetLineHeight() + GetInsets().height();
+  int contentHeight = content_view_->GetLineHeight();
+  int height = OmniboxFieldTrial::IsUniformRowHeightEnabled() && has_image_
+                   ? std::max(contentHeight, kEntityImageSizeSmall) +
+                         GetInsets().height()
+                   : contentHeight + GetInsets().height();
   if (layout_style_ == LayoutStyle::TWO_LINE_SUGGESTION)
     height += description_view_->GetHeightForWidth(width() - GetTextIndent());
   // Width is not calculated because it's not needed by current callers.
diff --git a/chrome/browser/ui/views/omnibox/omnibox_result_view.cc b/chrome/browser/ui/views/omnibox/omnibox_result_view.cc
index 966a8c68..2664450 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_result_view.cc
+++ b/chrome/browser/ui/views/omnibox/omnibox_result_view.cc
@@ -29,6 +29,7 @@
 #include "components/omnibox/browser/autocomplete_match_type.h"
 #include "components/omnibox/browser/omnibox.mojom-shared.h"
 #include "components/omnibox/browser/omnibox_edit_model.h"
+#include "components/omnibox/browser/omnibox_field_trial.h"
 #include "components/omnibox/browser/omnibox_popup_selection.h"
 #include "components/omnibox/browser/vector_icons.h"
 #include "components/strings/grit/components_strings.h"
@@ -264,7 +265,8 @@
     // calculator answers are 2-line but not deemphasized.
     const bool deemphasize =
         match_.type == AutocompleteMatchType::SEARCH_SUGGEST_ENTITY &&
-        OmniboxMatchCellView::ShouldDisplayImage(match_);
+        OmniboxMatchCellView::ShouldDisplayImage(match_) &&
+        !OmniboxFieldTrial::IsUniformRowHeightEnabled();
     suggestion_view_->description()->SetTextWithStyling(
         match_.description, match_.description_class, deemphasize);
   }
diff --git a/chrome/browser/ui/views/omnibox/omnibox_suggestion_button_row_view.cc b/chrome/browser/ui/views/omnibox/omnibox_suggestion_button_row_view.cc
index 34695824..01b22808 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_suggestion_button_row_view.cc
+++ b/chrome/browser/ui/views/omnibox/omnibox_suggestion_button_row_view.cc
@@ -155,10 +155,8 @@
     : popup_contents_view_(popup_contents_view),
       model_(model),
       model_index_(model_index) {
-  int bottom_margin = OmniboxFieldTrial::IsUniformRowHeightEnabled()
-                          ? OmniboxFieldTrial::kSuggestionVerticalMargin.Get()
-                          : ChromeLayoutProvider::Get()->GetDistanceMetric(
-                                DISTANCE_OMNIBOX_CELL_VERTICAL_PADDING);
+  int bottom_margin = ChromeLayoutProvider::Get()->GetDistanceMetric(
+      DISTANCE_OMNIBOX_CELL_VERTICAL_PADDING);
   SetLayoutManager(std::make_unique<views::FlexLayout>())
       ->SetCrossAxisAlignment(views::LayoutAlignment::kStart)
       .SetCollapseMargins(true)
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 987148f8..f410ba9 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
@@ -202,16 +202,27 @@
         std::vector<content::DesktopMediaID>{});
   }
 
+  struct UiExpectations {
+    Browser* browser;
+    int capturing_tab;
+    int captured_tab;
+    size_t infobar_count = 1;
+    bool has_border = true;
+    int tab_with_disabled_button = kNullTabIndex;
+  };
+
   // Verify that tab sharing infobars are displayed on all tabs, and content
   // border and tab capture indicator are only visible on the shared tab. Pass
   // |kNullTabIndex| for |captured_tab| to indicate the shared tab is
   // not in |browser|.
-  void VerifyUi(Browser* browser,
-                int capturing_tab,
-                int captured_tab,
-                size_t infobar_count = 1,
-                bool has_border = true,
-                int tab_with_disabled_button = kNullTabIndex) {
+  void VerifyUi(const UiExpectations& expectations) {
+    Browser* const browser = expectations.browser;
+    const int capturing_tab = expectations.capturing_tab;
+    const int captured_tab = expectations.captured_tab;
+    const size_t infobar_count = expectations.infobar_count;
+    const bool has_border = expectations.has_border;
+    const int tab_with_disabled_button = expectations.tab_with_disabled_button;
+
     DCHECK((capturing_tab != kNullTabIndex && captured_tab != kNullTabIndex) ||
            (capturing_tab == kNullTabIndex && captured_tab == kNullTabIndex));
 
@@ -362,16 +373,19 @@
 
   // Test that before sharing there are no infobars, content border or tab
   // capture indicator.
-  VerifyUi(browser(), /*capturing_tab=*/kNullTabIndex,
-           /*captured_tab=*/kNullTabIndex, /*infobar_count=*/0,
-           /*has_border=*/false);
+  VerifyUi(UiExpectations{.browser = browser(),
+                          .capturing_tab = kNullTabIndex,
+                          .captured_tab = kNullTabIndex,
+                          .infobar_count = 0,
+                          .has_border = false});
 
   // Create UI and start sharing the tab at index 1.
   CreateUiAndStartSharing(browser(), /*capturing_tab=*/0, /*captured_tab=*/1);
 
   // Test that infobars were created, and contents border and tab capture
   // indicator are displayed on the shared tab.
-  VerifyUi(browser(), /*capturing_tab=*/0, /*captured_tab=*/1);
+  VerifyUi(UiExpectations{
+      .browser = browser(), .capturing_tab = 0, .captured_tab = 1});
 }
 
 IN_PROC_BROWSER_TEST_P(TabSharingUIViewsBrowserTest, SwitchSharedTab) {
@@ -385,7 +399,8 @@
   CreateUiAndStartSharing(browser(), /*capturing_tab=*/0, /*captured_tab=*/2);
 
   // Test that the UI has been updated.
-  VerifyUi(browser(), /*capturing_tab=*/0, /*captured_tab=*/2);
+  VerifyUi(UiExpectations{
+      .browser = browser(), .capturing_tab = 0, .captured_tab = 2});
 }
 
 IN_PROC_BROWSER_TEST_P(TabSharingUIViewsBrowserTest,
@@ -397,12 +412,16 @@
   AddTabs(browser(), 2);
   ASSERT_EQ(browser()->tab_strip_model()->count(), 3);
   CreateUiAndStartSharing(browser(), /*capturing_tab=*/0, /*captured_tab=*/1);
-  VerifyUi(browser(), kCapturingTab, kCapturedTab);  // Sanity.
+  VerifyUi(UiExpectations{.browser = browser(),
+                          .capturing_tab = kCapturingTab,
+                          .captured_tab = kCapturedTab});  // Sanity.
 
   // Simulate changing the tab favicon to a unique new favicon, then waiting
   // until the change is picked up by the next periodic update.
   UpdateTabFavicon(browser(), kCapturingTab);
-  VerifyUi(browser(), kCapturingTab, kCapturedTab);
+  VerifyUi(UiExpectations{.browser = browser(),
+                          .capturing_tab = kCapturingTab,
+                          .captured_tab = kCapturedTab});
 }
 
 IN_PROC_BROWSER_TEST_P(TabSharingUIViewsBrowserTest, ChangeCapturedTabFavicon) {
@@ -413,12 +432,16 @@
   AddTabs(browser(), 2);
   ASSERT_EQ(browser()->tab_strip_model()->count(), 3);
   CreateUiAndStartSharing(browser(), /*capturing_tab=*/0, /*captured_tab=*/1);
-  VerifyUi(browser(), kCapturingTab, kCapturedTab);  // Sanity.
+  VerifyUi(UiExpectations{.browser = browser(),
+                          .capturing_tab = kCapturingTab,
+                          .captured_tab = kCapturedTab});  // Sanity.
 
   // Simulate changing the tab favicon to a unique new favicon, then waiting
   // until the change is picked up by the next periodic update.
   UpdateTabFavicon(browser(), kCapturedTab);
-  VerifyUi(browser(), kCapturingTab, kCapturedTab);
+  VerifyUi(UiExpectations{.browser = browser(),
+                          .capturing_tab = kCapturingTab,
+                          .captured_tab = kCapturedTab});
 }
 
 IN_PROC_BROWSER_TEST_P(TabSharingUIViewsBrowserTest, ChangeOtherTabFavicon) {
@@ -430,12 +453,16 @@
   AddTabs(browser(), 2);
   ASSERT_EQ(browser()->tab_strip_model()->count(), 3);
   CreateUiAndStartSharing(browser(), /*capturing_tab=*/0, /*captured_tab=*/1);
-  VerifyUi(browser(), kCapturingTab, kCapturedTab);  // Sanity.
+  VerifyUi(UiExpectations{.browser = browser(),
+                          .capturing_tab = kCapturingTab,
+                          .captured_tab = kCapturedTab});  // Sanity.
 
   // Simulate changing the tab favicon to a unique new favicon, then waiting
   // until the change is picked up by the next periodic update.
   UpdateTabFavicon(browser(), kOtherTab);
-  VerifyUi(browser(), kCapturingTab, kCapturedTab);
+  VerifyUi(UiExpectations{.browser = browser(),
+                          .capturing_tab = kCapturingTab,
+                          .captured_tab = kCapturedTab});
 }
 
 IN_PROC_BROWSER_TEST_P(TabSharingUIViewsBrowserTest, StopSharing) {
@@ -447,8 +474,10 @@
 
   // Test that the infobars have been removed, and the contents border and tab
   // capture indicator are no longer visible.
-  VerifyUi(browser(), /*capturing_tab=*/kNullTabIndex,
-           /*captured_tab=*/kNullTabIndex, /*infobar_count=*/0);
+  VerifyUi(UiExpectations{.browser = browser(),
+                          .capturing_tab = kNullTabIndex,
+                          .captured_tab = kNullTabIndex,
+                          .infobar_count = 0});
 }
 
 IN_PROC_BROWSER_TEST_P(TabSharingUIViewsBrowserTest, CloseTab) {
@@ -463,7 +492,8 @@
       tab_strip_model->GetWebContentsAt(2));
   tab_strip_model->CloseWebContentsAt(2, TabCloseTypes::CLOSE_NONE);
   tab_2_destroyed_watcher.Wait();
-  VerifyUi(browser(), /*capturing_tab=*/0, /*captured_tab=*/1);
+  VerifyUi(UiExpectations{
+      .browser = browser(), .capturing_tab = 0, .captured_tab = 1});
 
   // Close the shared tab and wait until it's actually closed, then verify that
   // sharing is stopped, i.e. the UI is removed.
@@ -471,8 +501,10 @@
       tab_strip_model->GetWebContentsAt(1));
   tab_strip_model->CloseWebContentsAt(1, TabCloseTypes::CLOSE_NONE);
   tab_1_destroyed_watcher.Wait();
-  VerifyUi(browser(), /*capturing_tab=*/kNullTabIndex,
-           /*captured_tab=*/kNullTabIndex, /*infobar_count=*/0);
+  VerifyUi(UiExpectations{.browser = browser(),
+                          .capturing_tab = kNullTabIndex,
+                          .captured_tab = kNullTabIndex,
+                          .infobar_count = 0});
 }
 
 IN_PROC_BROWSER_TEST_P(TabSharingUIViewsBrowserTest,
@@ -489,7 +521,8 @@
   CreateUiAndStartSharing(new_browser, /*capturing_tab=*/0, /*captured_tab=*/2);
 
   // Test that the UI has been updated.
-  VerifyUi(new_browser, /*capturing_tab=*/0, /*captured_tab=*/2);
+  VerifyUi(UiExpectations{
+      .browser = new_browser, .capturing_tab = 0, .captured_tab = 2});
 
   auto contents_border_weakptr = GetContentsBorder(new_browser)->GetWeakPtr();
   CloseBrowserSynchronously(new_browser);
@@ -511,29 +544,39 @@
   ASSERT_EQ(incognito_browser->tab_strip_model()->count(), 4);
   CreateUiAndStartSharing(incognito_browser, /*capturing_tab=*/0,
                           /*captured_tab=*/1);
-  VerifyUi(incognito_browser, /*capturing_tab=*/0, /*captured_tab=*/1);
-  VerifyUi(browser(), /*capturing_tab=*/kNullTabIndex,
-           /*captured_tab=*/kNullTabIndex, /*infobar_count=*/1,
-           /*has_border=*/false);
+  VerifyUi(UiExpectations{
+      .browser = incognito_browser, .capturing_tab = 0, .captured_tab = 1});
+  VerifyUi(UiExpectations{.browser = browser(),
+                          .capturing_tab = kNullTabIndex,
+                          .captured_tab = kNullTabIndex,
+                          .infobar_count = 1,
+                          .has_border = false});
 
   // Close a tab different than the shared one and test that the UI has not
   // changed.
   TabStripModel* tab_strip_model = incognito_browser->tab_strip_model();
   tab_strip_model->CloseWebContentsAt(2, TabCloseTypes::CLOSE_NONE);
-  VerifyUi(incognito_browser, /*capturing_tab=*/0, /*captured_tab=*/1);
-  VerifyUi(browser(), /*capturing_tab=*/kNullTabIndex,
-           /*captured_tab=*/kNullTabIndex, /*infobar_count=*/1,
-           /*has_border=*/false);
+  VerifyUi(UiExpectations{
+      .browser = incognito_browser, .capturing_tab = 0, .captured_tab = 1});
+  VerifyUi(UiExpectations{.browser = browser(),
+                          .capturing_tab = kNullTabIndex,
+                          .captured_tab = kNullTabIndex,
+                          .infobar_count = 1,
+                          .has_border = false});
 
   // Close the shared tab in the incognito browser and test that the UI is
   // removed.
   incognito_browser->tab_strip_model()->CloseWebContentsAt(
       1, TabCloseTypes::CLOSE_NONE);
-  VerifyUi(incognito_browser, /*capturing_tab=*/kNullTabIndex,
-           /*captured_tab=*/kNullTabIndex, /*infobar_count=*/0);
-  VerifyUi(browser(), /*capturing_tab=*/kNullTabIndex,
-           /*captured_tab=*/kNullTabIndex, /*infobar_count=*/0,
-           /*has_border=*/false);
+  VerifyUi(UiExpectations{.browser = incognito_browser,
+                          .capturing_tab = kNullTabIndex,
+                          .captured_tab = kNullTabIndex,
+                          .infobar_count = 0});
+  VerifyUi(UiExpectations{.browser = browser(),
+                          .capturing_tab = kNullTabIndex,
+                          .captured_tab = kNullTabIndex,
+                          .infobar_count = 0,
+                          .has_border = false});
 }
 
 IN_PROC_BROWSER_TEST_P(TabSharingUIViewsBrowserTest, KillTab) {
@@ -575,8 +618,10 @@
   shared_tab_crash_observer.Wait();
 
   // Verify that killing the shared tab stopped sharing.
-  VerifyUi(browser(), /*capturing_tab=*/kNullTabIndex,
-           /*captured_tab=*/kNullTabIndex, /*infobar_count=*/0);
+  VerifyUi(UiExpectations{.browser = browser(),
+                          .capturing_tab = kNullTabIndex,
+                          .captured_tab = kNullTabIndex,
+                          .infobar_count = 0});
 }
 
 IN_PROC_BROWSER_TEST_P(TabSharingUIViewsBrowserTest,
@@ -633,9 +678,12 @@
 
   // Test that infobars were created, and contents border and tab capture
   // indicator are displayed on the shared tab.
-  VerifyUi(browser(), /*capturing_tab=*/0, /*captured_tab=*/1,
-           /*infobar_count=*/1, /*has_border=*/true,
-           /*tab_with_disabled_button=*/kNullTabIndex);
+  VerifyUi(UiExpectations{.browser = browser(),
+                          .capturing_tab = 0,
+                          .captured_tab = 1,
+                          .infobar_count = 1,
+                          .has_border = true,
+                          .tab_with_disabled_button = kNullTabIndex});
 
   constexpr int kRestrictedTab = 2;
   content::WebContents* web_contents =
@@ -644,17 +692,23 @@
   ASSERT_TRUE(content::NavigateToURL(web_contents, kUrlRestricted));
 
   // Test that button on tab 2 is now disabled.
-  VerifyUi(browser(), /*capturing_tab=*/0, /*captured_tab=*/1,
-           /*infobar_count=*/1, /*has_border=*/true,
-           /*tab_with_disabled_button=*/kRestrictedTab);
+  VerifyUi(UiExpectations{.browser = browser(),
+                          .capturing_tab = 0,
+                          .captured_tab = 1,
+                          .infobar_count = 1,
+                          .has_border = true,
+                          .tab_with_disabled_button = kRestrictedTab});
 
   // Navigate to unrestricted URL.
   ASSERT_TRUE(content::NavigateToURL(web_contents, kUrlUnrestricted));
 
   // Verify that button on tab 2 is re-enabled.
-  VerifyUi(browser(), /*capturing_tab=*/0, /*captured_tab=*/1,
-           /*infobar_count=*/1, /*has_border=*/true,
-           /*tab_with_disabled_button=*/kNullTabIndex);
+  VerifyUi(UiExpectations{.browser = browser(),
+                          .capturing_tab = 0,
+                          .captured_tab = 1,
+                          .infobar_count = 1,
+                          .has_border = true,
+                          .tab_with_disabled_button = kNullTabIndex});
 }
 #endif  // BUILDFLAG(IS_CHROMEOS)
 
diff --git a/chrome/browser/ui/views/tabs/compound_tab_container.cc b/chrome/browser/ui/views/tabs/compound_tab_container.cc
index 7f981461..341a091 100644
--- a/chrome/browser/ui/views/tabs/compound_tab_container.cc
+++ b/chrome/browser/ui/views/tabs/compound_tab_container.cc
@@ -12,6 +12,7 @@
 #include "chrome/browser/ui/views/tabs/tab.h"
 #include "chrome/browser/ui/views/tabs/tab_container_impl.h"
 #include "chrome/browser/ui/views/tabs/tab_hover_card_controller.h"
+#include "chrome/browser/ui/views/tabs/tab_scrolling_animation.h"
 #include "chrome/browser/ui/views/tabs/tab_slot_animation_delegate.h"
 #include "chrome/browser/ui/views/tabs/tab_slot_view.h"
 #include "chrome/browser/ui/views/tabs/tab_strip_controller.h"
@@ -225,6 +226,7 @@
           tab_slot_controller,
           scroll_contents_view))),
       hover_card_controller_(hover_card_controller),
+      scroll_contents_view_(scroll_contents_view),
       bounds_animator_(this) {
   const views::FlexSpecification tab_container_flex_spec =
       views::FlexSpecification(views::LayoutOrientation::kHorizontal,
@@ -367,7 +369,17 @@
 }
 
 void CompoundTabContainer::ScrollTabContainerByOffset(int offset) {
-  // TODO(crbug.com/1346023): ditto
+  absl::optional<gfx::Rect> visible_content_rect = GetVisibleContentRect();
+  if (!visible_content_rect.has_value() || offset == 0)
+    return;
+
+  // If tabcontainer is scrolled towards trailing tab, the start edge should
+  // have the x coordinate of the right bound. If it is scrolled towards the
+  // leading tab it should have the x coordinate of the left bound.
+  int start_edge =
+      (offset > 0) ? visible_content_rect->right() : visible_content_rect->x();
+
+  AnimateScrollToShowXCoordinate(start_edge, start_edge + offset);
 }
 
 void CompoundTabContainer::OnGroupCreated(const tab_groups::TabGroupId& group) {
@@ -801,5 +813,29 @@
          GetUnpinnedContainerIdealLeadingX();
 }
 
+absl::optional<gfx::Rect> CompoundTabContainer::GetVisibleContentRect() {
+  views::ScrollView* scroll_container =
+      views::ScrollView::GetScrollViewForContents(scroll_contents_view_);
+  if (!scroll_container)
+    return absl::nullopt;
+
+  return scroll_container->GetVisibleRect();
+}
+
+void CompoundTabContainer::AnimateScrollToShowXCoordinate(
+    const int start_edge,
+    const int target_edge) {
+  if (tab_scrolling_animation_)
+    tab_scrolling_animation_->Stop();
+
+  gfx::Rect start_rect(start_edge, 0, 0, 0);
+  gfx::Rect target_rect(target_edge, 0, 0, 0);
+
+  tab_scrolling_animation_ = std::make_unique<TabScrollingAnimation>(
+      scroll_contents_view_, bounds_animator_.container(),
+      bounds_animator_.GetAnimationDuration(), start_rect, target_rect);
+  tab_scrolling_animation_->Start();
+}
+
 BEGIN_METADATA(CompoundTabContainer, views::View)
 END_METADATA
diff --git a/chrome/browser/ui/views/tabs/compound_tab_container.h b/chrome/browser/ui/views/tabs/compound_tab_container.h
index 14b21a7..5db4691 100644
--- a/chrome/browser/ui/views/tabs/compound_tab_container.h
+++ b/chrome/browser/ui/views/tabs/compound_tab_container.h
@@ -153,6 +153,15 @@
   int GetAvailableWidthForUnpinnedTabContainer(
       base::RepeatingCallback<int()> available_width_callback);
 
+  // Private getter to retrieve the visible rect of the scroll container.
+  absl::optional<gfx::Rect> GetVisibleContentRect();
+
+  // Animates and scrolls the tab container from the start_edge to the
+  // target_edge. If the target_edge is beyond the tab strip it will be clamped
+  // bounds of the tabstrip.
+  void AnimateScrollToShowXCoordinate(const int start_edge,
+                                      const int target_edge);
+
   const raw_ref<TabContainerController> controller_;
 
   // Adapts `pinned_tab_container_`'s interactions with the model to account for
@@ -174,6 +183,13 @@
   const raw_ptr<TabHoverCardController, DanglingUntriaged>
       hover_card_controller_;
 
+  // The View that is to be scrolled by |tab_scrolling_animation_|. May be
+  // nullptr in tests.
+  const raw_ptr<views::View> scroll_contents_view_;
+
+  // Responsible for animating the scroll of the tab container.
+  std::unique_ptr<gfx::LinearAnimation> tab_scrolling_animation_;
+
   // Animates tabs between pinned and unpinned states.
   views::BoundsAnimator bounds_animator_;
 };
diff --git a/chrome/browser/ui/views/tabs/tab_container_impl.cc b/chrome/browser/ui/views/tabs/tab_container_impl.cc
index 6b93c6a..43485feb 100644
--- a/chrome/browser/ui/views/tabs/tab_container_impl.cc
+++ b/chrome/browser/ui/views/tabs/tab_container_impl.cc
@@ -18,6 +18,7 @@
 #include "chrome/browser/ui/views/tabs/tab_group_underline.h"
 #include "chrome/browser/ui/views/tabs/tab_group_views.h"
 #include "chrome/browser/ui/views/tabs/tab_hover_card_controller.h"
+#include "chrome/browser/ui/views/tabs/tab_scrolling_animation.h"
 #include "chrome/browser/ui/views/tabs/tab_slot_animation_delegate.h"
 #include "chrome/browser/ui/views/tabs/tab_strip.h"
 #include "chrome/browser/ui/views/tabs/tab_strip_controller.h"
@@ -47,48 +48,6 @@
       is_down ? IDR_TAB_DROP_DOWN : IDR_TAB_DROP_UP);
 }
 
-// Helper class that manages the tab scrolling animation.
-class TabScrollingAnimation : public gfx::LinearAnimation,
-                              public gfx::AnimationDelegate {
- public:
-  explicit TabScrollingAnimation(
-      views::View* contents_view,
-      gfx::AnimationContainer* bounds_animator_container,
-      base::TimeDelta duration,
-      const gfx::Rect start_visible_rect,
-      const gfx::Rect end_visible_rect)
-      : gfx::LinearAnimation(duration,
-                             gfx::LinearAnimation::kDefaultFrameRate,
-                             this),
-        contents_view_(contents_view),
-        start_visible_rect_(start_visible_rect),
-        end_visible_rect_(end_visible_rect) {
-    SetContainer(bounds_animator_container);
-  }
-  TabScrollingAnimation(const TabScrollingAnimation&) = delete;
-  TabScrollingAnimation& operator=(const TabScrollingAnimation&) = delete;
-  ~TabScrollingAnimation() override = default;
-
-  void AnimateToState(double state) override {
-    gfx::Rect intermediary_rect(
-        start_visible_rect_.x() +
-            (end_visible_rect_.x() - start_visible_rect_.x()) * state,
-        start_visible_rect_.y(), start_visible_rect_.width(),
-        start_visible_rect_.height());
-
-    contents_view_->ScrollRectToVisible(intermediary_rect);
-  }
-
-  void AnimationEnded(const gfx::Animation* animation) override {
-    contents_view_->ScrollRectToVisible(end_visible_rect_);
-  }
-
- private:
-  const raw_ptr<views::View> contents_view_;
-  const gfx::Rect start_visible_rect_;
-  const gfx::Rect end_visible_rect_;
-};
-
 }  // namespace
 
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/chrome/browser/ui/views/tabs/tab_hover_card_controller.cc b/chrome/browser/ui/views/tabs/tab_hover_card_controller.cc
index f6d5ccf..3cd0b6fc 100644
--- a/chrome/browser/ui/views/tabs/tab_hover_card_controller.cc
+++ b/chrome/browser/ui/views/tabs/tab_hover_card_controller.cc
@@ -241,8 +241,7 @@
 bool TabHoverCardController::disable_animations_for_testing_ = false;
 
 TabHoverCardController::TabHoverCardController(TabStrip* tab_strip)
-    : tab_strip_(tab_strip),
-      metrics_(std::make_unique<TabHoverCardMetrics>(this)) {
+    : tab_strip_(tab_strip) {
   // Possibly apply memory pressure override for testing.
   auto override = GetMemoryPressureOverride();
   if (override) {
@@ -302,7 +301,7 @@
 
   switch (update_type) {
     case TabSlotController::HoverCardUpdateType::kSelectionChanged:
-      metrics_->TabSelectionChanged();
+      ResetCardsSeenCount();
       break;
     case TabSlotController::HoverCardUpdateType::kHover:
       if (!tab)
@@ -332,10 +331,6 @@
   last_mouse_exit_timestamp_ = base::TimeTicks();
 }
 
-void TabHoverCardController::TabSelectedViaMouse(Tab* tab) {
-  metrics_->TabSelectedViaMouse(tab);
-}
-
 void TabHoverCardController::UpdateOrShowCard(
     Tab* tab,
     TabSlotController::HoverCardUpdateType update_type) {
@@ -356,7 +351,6 @@
   // Cancel any pending fades.
   if (hover_card_ && fade_animator_->IsFadingOut()) {
     fade_animator_->CancelFadeOut();
-    metrics_->CardFadeCanceled();
   }
 
   if (hover_card_) {
@@ -381,7 +375,7 @@
   // complex and time-consuming.
   const bool is_initial = !ShouldShowImmediately(tab);
   if (is_initial)
-    metrics_->InitialCardBeingShown();
+    ResetCardsSeenCount();
   if (is_initial && !disable_animations_for_testing_) {
     delayed_show_timer_.Start(
         FROM_HERE, GetShowDelay(tab->width()),
@@ -429,7 +423,6 @@
     return;
   }
 
-  metrics_->CardFadingIn();
   fade_animator_->FadeIn();
 }
 
@@ -437,19 +430,19 @@
   if (!hover_card_ || hover_card_->GetWidget()->IsClosed())
     return;
 
+  // Required for test metrics.
+  hover_card_last_seen_on_tab_ = nullptr;
+
   if (thumbnail_observer_) {
     thumbnail_observer_->Observe(nullptr);
     thumbnail_wait_state_ = ThumbnailWaitState::kNotWaiting;
   }
 
   // Cancel any pending fade-in.
-  if (fade_animator_->IsFadingIn()) {
+  if (fade_animator_->IsFadingIn())
     fade_animator_->CancelFadeIn();
-    metrics_->CardFadeCanceled();
-  }
 
   // This needs to be called whether we're doing a fade or a pop out.
-  metrics_->CardWillBeHidden();
   slide_animator_->StopAnimation();
   if (!UseAnimations()) {
     hover_card_->GetWidget()->Close();
@@ -458,7 +451,6 @@
   if (fade_animator_->IsFadingOut())
     return;
 
-  metrics_->CardFadingOut();
   fade_animator_->FadeOut();
 }
 
@@ -501,18 +493,10 @@
     OnViewIsDeleting(observed_view);
 }
 
-size_t TabHoverCardController::GetTabCount() const {
-  return tab_count_metrics::TabCount();
-}
-
 bool TabHoverCardController::ArePreviewsEnabled() const {
   return static_cast<bool>(thumbnail_observer_);
 }
 
-views::Widget* TabHoverCardController::GetHoverCardWidget() {
-  return hover_card_ ? hover_card_->GetWidget() : nullptr;
-}
-
 void TabHoverCardController::CreateHoverCard(Tab* tab) {
   hover_card_ = new TabHoverCardBubbleView(tab);
   hover_card_observation_.Observe(hover_card_.get());
@@ -703,12 +687,16 @@
 }
 
 void TabHoverCardController::OnCardFullyVisible() {
-  // We have to do a bunch of validity checks here because this happens on a
-  // callback and so the tab may no longer be valid (or part of the original
-  // tabstrip).
-  const bool has_preview = ArePreviewsEnabled() && TargetTabIsValid() &&
-                           !target_tab_->IsActive() && !waiting_for_preview();
-  metrics_->CardFullyVisibleOnTab(target_tab_, has_preview);
+  DCHECK(target_tab_);
+  if (target_tab_ == hover_card_last_seen_on_tab_)
+    return;
+  hover_card_last_seen_on_tab_ = target_tab_;
+  ++hover_cards_seen_count_;
+}
+
+void TabHoverCardController::ResetCardsSeenCount() {
+  hover_card_last_seen_on_tab_ = nullptr;
+  hover_cards_seen_count_ = 0;
 }
 
 void TabHoverCardController::OnFadeAnimationEnded(
@@ -720,7 +708,6 @@
   if (target_tab_ && fade_type == views::WidgetFadeAnimator::FadeType::kFadeIn)
     OnCardFullyVisible();
 
-  metrics_->CardFadeComplete();
   if (fade_type == views::WidgetFadeAnimator::FadeType::kFadeOut)
     hover_card_->GetWidget()->Close();
 }
@@ -763,14 +750,11 @@
     gfx::ImageSkia thumbnail_image) {
   DCHECK_EQ(thumbnail_observer_.get(), observer);
 
-  const bool was_waiting_for_preview = waiting_for_preview();
   thumbnail_wait_state_ = ThumbnailWaitState::kNotWaiting;
 
   // The hover card could be destroyed before the preview image is delivered.
   if (!hover_card_)
     return;
-  if (was_waiting_for_preview && target_tab_)
-    metrics_->ImageLoadedForTab(target_tab_);
   // Can still set image on a fading-out hover card (we can change this behavior
   // later if we want).
   hover_card_->SetTargetTabImage(thumbnail_image);
diff --git a/chrome/browser/ui/views/tabs/tab_hover_card_controller.h b/chrome/browser/ui/views/tabs/tab_hover_card_controller.h
index 33f6779..707d4b6f 100644
--- a/chrome/browser/ui/views/tabs/tab_hover_card_controller.h
+++ b/chrome/browser/ui/views/tabs/tab_hover_card_controller.h
@@ -15,7 +15,6 @@
 #include "base/scoped_observation.h"
 #include "base/time/time.h"
 #include "base/timer/timer.h"
-#include "chrome/browser/ui/views/tabs/tab_hover_card_metrics.h"
 #include "chrome/browser/ui/views/tabs/tab_slot_controller.h"
 #include "ui/events/event.h"
 #include "ui/views/animation/bubble_slide_animator.h"
@@ -33,8 +32,7 @@
 class TabStrip;
 
 // Controls how hover cards are shown and hidden for tabs.
-class TabHoverCardController : public views::ViewObserver,
-                               public TabHoverCardMetrics::Delegate {
+class TabHoverCardController : public views::ViewObserver {
  public:
   explicit TabHoverCardController(TabStrip* tab_strip);
   ~TabHoverCardController() override;
@@ -51,19 +49,19 @@
   void UpdateHoverCard(Tab* tab,
                        TabSlotController::HoverCardUpdateType update_type);
   void PreventImmediateReshow();
-  void TabSelectedViaMouse(Tab* tab);
 
   TabHoverCardBubbleView* hover_card_for_testing() { return hover_card_.get(); }
 
+  size_t hover_cards_seen_count_for_testing() const {
+    return hover_cards_seen_count_;
+  }
+
   static void set_disable_animations_for_testing(
       bool disable_animations_for_testing) {
     disable_animations_for_testing_ = disable_animations_for_testing;
   }
 
-  TabHoverCardMetrics* metrics_for_testing() const { return metrics_.get(); }
-
  private:
-  friend class TabHoverCardMetrics;
   FRIEND_TEST_ALL_PREFIXES(TabHoverCardControllerTest, ShowWrongTabDoesntCrash);
   FRIEND_TEST_ALL_PREFIXES(TabHoverCardControllerTest,
                            SetPreviewWithNoHoverCardDoesntCrash);
@@ -80,10 +78,7 @@
   void OnViewVisibilityChanged(views::View* observed_view,
                                views::View* starting_view) override;
 
-  // TabHoverCardMetrics::Delegate:
-  size_t GetTabCount() const override;
-  bool ArePreviewsEnabled() const override;
-  views::Widget* GetHoverCardWidget() override;
+  bool ArePreviewsEnabled() const;
 
   void CreateHoverCard(Tab* tab);
   void UpdateCardContent(Tab* tab);
@@ -103,9 +98,12 @@
   // TabHoverCardController from an asynchronous callback.
   bool TargetTabIsValid() const;
 
-  // Helper for recording metrics when a card becomes fully visible to the user.
+  // Helper for recording when a card becomes fully visible to the user.
   void OnCardFullyVisible();
 
+  // Helper for resetting the cards seen count for testing.
+  void ResetCardsSeenCount();
+
   // Animator events:
   void OnFadeAnimationEnded(views::WidgetFadeAnimator* animator,
                             views::WidgetFadeAnimator::FadeType fade_type);
@@ -137,8 +135,11 @@
       target_tab_observation_{this};
   std::unique_ptr<EventSniffer> event_sniffer_;
 
-  // Handles metrics around cards being seen by the user.
-  std::unique_ptr<TabHoverCardMetrics> metrics_;
+  // These are used to track when a hover card is shown on a new tab for
+  // testing purposes. Counts cards seen from the time the first card is shown
+  // to a tab is selected, or the hover card is shown from scratch again.
+  const void* hover_card_last_seen_on_tab_ = nullptr;
+  size_t hover_cards_seen_count_ = 0;
 
   // Fade animations interfere with browser tests so we disable them in tests.
   static bool disable_animations_for_testing_;
diff --git a/chrome/browser/ui/views/tabs/tab_hover_card_metrics.cc b/chrome/browser/ui/views/tabs/tab_hover_card_metrics.cc
deleted file mode 100644
index 248c28f3..0000000
--- a/chrome/browser/ui/views/tabs/tab_hover_card_metrics.cc
+++ /dev/null
@@ -1,281 +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.
-
-#include "chrome/browser/ui/views/tabs/tab_hover_card_metrics.h"
-
-#include "base/metrics/field_trial_params.h"
-#include "base/metrics/histogram_macros.h"
-#include "chrome/browser/ui/views/tabs/tab.h"
-#include "ui/compositor/compositor.h"
-#include "ui/views/widget/widget.h"
-
-namespace {
-
-constexpr int kMinHoverCardTimeMilliseconds = 0;
-constexpr int kMaxHoverCardTimeMilliseconds = 10000;
-constexpr int kHoverCardTimeBuckets = 50;
-constexpr int kMinHoverCardsSeen = 0;
-constexpr int kMaxHoverCardsSeen = 100;
-constexpr int kHistogramBucketCount = 50;
-
-// These buckets are modeled after the buckets in components/tab_count_metrics
-// but with the very smallest groups collepsed to reduce the number of
-// histograms. The values correspond to roughly the 25th, 50th, 75th, 95th, and
-// 99th percentile.
-constexpr size_t kTabCountBucketMinimums[]{0, 3, 5, 8, 20, 40};
-constexpr const char* kTabCountBucketNames[]{".0To2Tabs",   ".3To4Tabs",
-                                             ".5To7Tabs",   ".8To19Tabs",
-                                             ".20To39Tabs", ".40OrMoreTabs"};
-constexpr size_t kNumTabCountBuckets =
-    sizeof(kTabCountBucketNames) / sizeof(kTabCountBucketNames[0]);
-static_assert(kNumTabCountBuckets == sizeof(kTabCountBucketMinimums) /
-                                         sizeof(kTabCountBucketMinimums[0]),
-              "Array sizes must be equal.");
-
-// Histograms with STATIC_HISTOGRAM_* need to be declared inline, but this is
-// also boilerplate code, so create macros to create standard histogram types to
-// use for hover cards.
-
-#define RECORD_COUNT_METRIC(name, count, bucket)                          \
-  STATIC_HISTOGRAM_POINTER_GROUP(                                         \
-      GetBucketHistogramName((name), (bucket)), static_cast<int>(bucket), \
-      static_cast<int>(kNumTabCountBuckets), Add(count),                  \
-      base::Histogram::FactoryGet(                                        \
-          GetBucketHistogramName((name), (bucket)), kMinHoverCardsSeen,   \
-          kMaxHoverCardsSeen, kHistogramBucketCount,                      \
-          base::HistogramBase::kUmaTargetedHistogramFlag))
-
-#define RECORD_TIME_METRIC(name, start_time, bucket)                       \
-  STATIC_HISTOGRAM_POINTER_GROUP(                                          \
-      GetBucketHistogramName((name), (bucket)), static_cast<int>(bucket),  \
-      static_cast<int>(kNumTabCountBuckets),                               \
-      Add((start_time).is_null()                                           \
-              ? 0                                                          \
-              : (base::TimeTicks::Now() - (start_time)).InMilliseconds()), \
-      base::Histogram::FactoryGet(                                         \
-          GetBucketHistogramName((name), (bucket)),                        \
-          kMinHoverCardTimeMilliseconds, kMaxHoverCardTimeMilliseconds,    \
-          kHoverCardTimeBuckets,                                           \
-          base::HistogramBase::kUmaTargetedHistogramFlag))
-
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-// UMA histograms that record animation smoothness for fade-in and fade-out
-// animations of tab hover card.
-void RecordFadeInSmoothness(int smoothness) {
-  constexpr char kHoverCardFadeInSmoothnessHistogramName[] =
-      "Chrome.Tabs.AnimationSmoothness.HoverCard.FadeIn";
-  UMA_HISTOGRAM_PERCENTAGE(kHoverCardFadeInSmoothnessHistogramName, smoothness);
-}
-
-void RecordFadeOutSmoothness(int smoothness) {
-  constexpr char kHoverCardFadeOutSmoothnessHistogramName[] =
-      "Chrome.Tabs.AnimationSmoothness.HoverCard.FadeOut";
-  UMA_HISTOGRAM_PERCENTAGE(kHoverCardFadeOutSmoothnessHistogramName,
-                           smoothness);
-}
-#endif
-
-void RecordTimeSinceLastSeenMetric(base::TimeTicks last_seen_time) {
-  constexpr base::TimeDelta kMaxHoverCardReshowTimeDelta = base::Seconds(5);
-  const base::TimeDelta elapsed_time = base::TimeTicks::Now() - last_seen_time;
-  constexpr base::TimeDelta kMinHoverCardReshowTimeDelta =
-      base::Milliseconds(1);
-  if (elapsed_time < kMinHoverCardReshowTimeDelta ||
-      elapsed_time > kMaxHoverCardReshowTimeDelta) {
-    return;
-  }
-
-  constexpr int kHoverCardHistogramBucketCount = 50;
-  UMA_HISTOGRAM_CUSTOM_TIMES(
-      TabHoverCardMetrics::kHistogramTimeSinceLastVisible, elapsed_time,
-      kMinHoverCardReshowTimeDelta, kMaxHoverCardReshowTimeDelta,
-      kHoverCardHistogramBucketCount);
-}
-
-}  // namespace
-
-TabHoverCardMetrics::Delegate::~Delegate() = default;
-
-// static
-const char TabHoverCardMetrics::kHistogramTimeSinceLastVisible[] =
-    "TabHoverCards.TimeSinceLastVisible";
-
-// static
-const char
-    TabHoverCardMetrics::kHistogramPrefixHoverCardsSeenBeforeSelection[] =
-        "TabHoverCards.TabHoverCardsSeenBeforeTabSelection";
-
-// static
-const char TabHoverCardMetrics::kHistogramPrefixPreviewsSeenBeforeSelection[] =
-    "TabHoverCards.TabPreviewsSeenBeforeTabSelection";
-
-// static
-const char TabHoverCardMetrics::kHistogramPrefixTabHoverCardTime[] =
-    "TabHoverCards.TabHoverCardViewedTime";
-
-// static
-const char TabHoverCardMetrics::kHistogramPrefixTabPreviewTime[] =
-    "TabHoverCards.TabHoverCardPreviewTime";
-
-// static
-const char TabHoverCardMetrics::kHistogramPrefixLastTabHoverCardTime[] =
-    "TabHoverCards.LastTabHoverCardViewedTime";
-
-// static
-const char TabHoverCardMetrics::kHistogramPrefixLastTabPreviewTime[] =
-    "TabHoverCards.LastTabHoverCardPreviewTime";
-
-TabHoverCardMetrics::TabHoverCardMetrics(Delegate* delegate)
-    : delegate_(delegate) {}
-TabHoverCardMetrics::~TabHoverCardMetrics() = default;
-
-void TabHoverCardMetrics::TabSelectionChanged() {
-  Reset();
-}
-
-void TabHoverCardMetrics::InitialCardBeingShown() {
-  Reset();
-}
-
-void TabHoverCardMetrics::CardFadingIn() {
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-  // This may be null during tests, so we can skip if it is.
-  auto* const widget = delegate_->GetHoverCardWidget();
-  if (widget) {
-    throughput_tracker_.emplace(
-        widget->GetCompositor()->RequestNewThroughputTracker());
-    throughput_tracker_->Start(ash::metrics_util::ForSmoothness(
-        base::BindRepeating(&RecordFadeInSmoothness)));
-  }
-#endif
-}
-
-void TabHoverCardMetrics::CardWillBeHidden() {
-  if (!last_tab_)
-    return;
-
-  RecordTabTimeMetrics();
-  times_for_last_tab_ = last_tab_;
-  last_visible_timestamp_ = base::TimeTicks::Now();
-  last_tab_ = TabHandle();
-}
-
-void TabHoverCardMetrics::CardFadingOut() {
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-  // This may be null during tests, so we can skip if it is.
-  auto* const widget = delegate_->GetHoverCardWidget();
-  if (widget) {
-    throughput_tracker_.emplace(
-        widget->GetCompositor()->RequestNewThroughputTracker());
-    throughput_tracker_->Start(ash::metrics_util::ForSmoothness(
-        base::BindRepeating(&RecordFadeOutSmoothness)));
-  }
-#endif
-}
-
-void TabHoverCardMetrics::CardFadeComplete() {
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-  if (throughput_tracker_.has_value())
-    throughput_tracker_->Stop();
-#endif
-}
-
-void TabHoverCardMetrics::CardFadeCanceled() {
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-  throughput_tracker_.reset();
-#endif
-}
-
-void TabHoverCardMetrics::CardFullyVisibleOnTab(TabHandle tab,
-                                                bool has_preview) {
-  if (tab == last_tab_)
-    return;
-
-  if (last_tab_)
-    RecordTabTimeMetrics();
-  else
-    RecordTimeSinceLastSeenMetric(last_visible_timestamp_);
-
-  last_tab_ = tab;
-  last_tab_time_ = base::TimeTicks::Now();
-  ++cards_seen_count_;
-
-  // If the tab isn't active and we're done waiting for a preview image, mark
-  // the image as seen now.
-  if (has_preview) {
-    ImageLoadedForTab(tab);
-    last_image_time_ = base::TimeTicks::Now();
-  } else {
-    last_image_time_ = base::TimeTicks();
-  }
-}
-
-void TabHoverCardMetrics::ImageLoadedForTab(TabHandle tab) {
-  if (tab != last_tab_)
-    return;
-
-  ++images_seen_count_;
-  last_image_time_ = base::TimeTicks::Now();
-}
-
-void TabHoverCardMetrics::TabSelectedViaMouse(TabHandle tab) {
-  const size_t tab_count = delegate_->GetTabCount();
-  const size_t bucket = GetBucketForTabCount(tab_count);
-  RECORD_COUNT_METRIC(kHistogramPrefixHoverCardsSeenBeforeSelection,
-                      cards_seen_count_, bucket);
-  RECORD_COUNT_METRIC(kHistogramPrefixPreviewsSeenBeforeSelection,
-                      images_seen_count_, bucket);
-
-  // |last_tab| may have been cleared out if the hide signal arrived before the
-  // selection event, so if it's null, use the backup |times_for_last_tab| we
-  // stored during the hide event.
-  const bool is_last_tab = tab == (last_tab_ ? last_tab_ : times_for_last_tab_);
-  RECORD_TIME_METRIC(kHistogramPrefixLastTabHoverCardTime,
-                     is_last_tab ? last_tab_time_ : base::TimeTicks(), bucket);
-  if (delegate_->ArePreviewsEnabled()) {
-    RECORD_TIME_METRIC(kHistogramPrefixLastTabPreviewTime,
-                       is_last_tab ? last_image_time_ : base::TimeTicks(),
-                       bucket);
-  }
-}
-
-// static
-size_t TabHoverCardMetrics::GetBucketForTabCount(size_t tab_count) {
-  for (size_t bucket = 1; bucket < kNumTabCountBuckets; ++bucket) {
-    if (tab_count < kTabCountBucketMinimums[bucket])
-      return bucket - 1;
-  }
-  return kNumTabCountBuckets - 1;
-}
-
-// static
-std::string TabHoverCardMetrics::GetBucketHistogramName(
-    const std::string& prefix,
-    size_t bucket) {
-  DCHECK_LT(bucket, kNumTabCountBuckets);
-  return prefix + ".ByTabCount" + kTabCountBucketNames[bucket];
-}
-
-void TabHoverCardMetrics::Reset() {
-  cards_seen_count_ = 0;
-  images_seen_count_ = 0;
-  last_tab_ = TabHandle();
-  times_for_last_tab_ = TabHandle();
-  last_tab_time_ = base::TimeTicks();
-  last_image_time_ = base::TimeTicks();
-}
-
-void TabHoverCardMetrics::RecordTabTimeMetrics() {
-  const size_t tab_count = delegate_->GetTabCount();
-  const size_t bucket = GetBucketForTabCount(tab_count);
-  if (!last_tab_time_.is_null()) {
-    RECORD_TIME_METRIC(kHistogramPrefixTabHoverCardTime, last_tab_time_,
-                       bucket);
-  }
-
-  if (delegate_->ArePreviewsEnabled() && !last_image_time_.is_null()) {
-    RECORD_TIME_METRIC(kHistogramPrefixTabPreviewTime, last_image_time_,
-                       bucket);
-  }
-}
diff --git a/chrome/browser/ui/views/tabs/tab_hover_card_metrics.h b/chrome/browser/ui/views/tabs/tab_hover_card_metrics.h
deleted file mode 100644
index 1af4387..0000000
--- a/chrome/browser/ui/views/tabs/tab_hover_card_metrics.h
+++ /dev/null
@@ -1,127 +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_UI_VIEWS_TABS_TAB_HOVER_CARD_METRICS_H_
-#define CHROME_BROWSER_UI_VIEWS_TABS_TAB_HOVER_CARD_METRICS_H_
-
-#include "base/memory/raw_ptr.h"
-#include "base/time/time.h"
-#include "build/build_config.h"
-#include "build/chromeos_buildflags.h"
-
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-#include "ash/public/cpp/metrics_util.h"
-#include "third_party/abseil-cpp/absl/types/optional.h"
-#include "ui/compositor/throughput_tracker.h"
-#endif
-
-namespace views {
-class Widget;
-}
-
-// Records metrics around the number and duration of hover cards and hover card
-// images that the user sees.
-class TabHoverCardMetrics {
- public:
-  // Proxy for the hover card controller that can be stubbed out during tests.
-  class Delegate {
-   public:
-    virtual ~Delegate();
-
-    // Returns the number of tabs. Provided by the delegate so we don't need a
-    // live browser to get the tab count in tests.
-    virtual size_t GetTabCount() const = 0;
-
-    // Returns true if preview images are enabled.
-    virtual bool ArePreviewsEnabled() const = 0;
-
-    // Returns the hover card widget, or nullptr if none. Can be stubbed to
-    // return nullptr for tests (low-level performance metrics may not be
-    // recorded).
-    virtual views::Widget* GetHoverCardWidget() = 0;
-  };
-
-  // Use an immutable, opaque pointer to tabs because these pointers could
-  // become stale so we should not attempt to dereference them.
-  using TabHandle = const void*;
-
-  // Histogram names:
-  static const char kHistogramPrefixHoverCardsSeenBeforeSelection[];
-  static const char kHistogramPrefixPreviewsSeenBeforeSelection[];
-  static const char kHistogramPrefixTabHoverCardTime[];
-  static const char kHistogramPrefixTabPreviewTime[];
-  static const char kHistogramPrefixLastTabHoverCardTime[];
-  static const char kHistogramPrefixLastTabPreviewTime[];
-  static const char kHistogramTimeSinceLastVisible[];
-
-  explicit TabHoverCardMetrics(Delegate* delegate);
-  TabHoverCardMetrics(const TabHoverCardMetrics& other) = delete;
-  ~TabHoverCardMetrics();
-  void operator=(const TabHoverCardMetrics& other) = delete;
-
-  void TabSelectionChanged();
-  void InitialCardBeingShown();
-  void CardFadingIn();
-  void CardWillBeHidden();
-  void CardFadingOut();
-  void CardFadeComplete();
-  void CardFadeCanceled();
-
-  // Notes that a card becomes fully visible or lands on `tab`. Set
-  // `has_preview` to true if there is already a preview image loaded for the
-  // tab.
-  void CardFullyVisibleOnTab(TabHandle tab, bool has_preview);
-
-  // Note that an image was shown for |tab|.
-  void ImageLoadedForTab(TabHandle tab);
-
-  // Records the number of cards seen before a mouse selection. Should be called
-  // when the mouse is clicked on a tab, but before the selection is committed.
-  void TabSelectedViaMouse(TabHandle tab);
-
-  int cards_seen_count() const { return cards_seen_count_; }
-  int images_seen_count() const { return images_seen_count_; }
-
-  // Replacement for tab_count_metrics::BucketForTabCount() which reduces the
-  // number of buckets, especially at the low end.
-  static size_t GetBucketForTabCount(size_t tab_count);
-
-  // Replacement for tab_count_metrics::HistogramName() which reduces the
-  // number of buckets, especially at the low end.
-  static std::string GetBucketHistogramName(const std::string& prefix,
-                                            size_t bucket);
-
- private:
-  // Clears all data for a fresh set of metrics.
-  void Reset();
-
-  void RecordTabTimeMetrics();
-
-  int cards_seen_count_ = 0;
-  int images_seen_count_ = 0;
-  base::TimeTicks last_tab_time_;
-  base::TimeTicks last_image_time_;
-
-  // Timestamp of the last time a hover card was visible, recorded before it is
-  // hidden. This is used for metrics.
-  base::TimeTicks last_visible_timestamp_;
-
-  // Keep this as an opaque pointer to avoid the temptation to dereference it;
-  // there's a chance it could be dead.
-  TabHandle last_tab_ = TabHandle();
-
-  // The last tab we have times for. This helps us know after a fade-out caused
-  // by a tab selection which tab |last_tab_time| and |last_image_time| are for.
-  TabHandle times_for_last_tab_ = TabHandle();
-
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-  absl::optional<ui::ThroughputTracker> throughput_tracker_;
-#endif
-
-  // TOOD(dfried): in future, change this to a delegate object in order to be
-  // able to test it in isolation.
-  const raw_ptr<Delegate> delegate_;
-};
-
-#endif  // CHROME_BROWSER_UI_VIEWS_TABS_TAB_HOVER_CARD_METRICS_H_
diff --git a/chrome/browser/ui/views/tabs/tab_hover_card_metrics_unittest.cc b/chrome/browser/ui/views/tabs/tab_hover_card_metrics_unittest.cc
deleted file mode 100644
index 187641b..0000000
--- a/chrome/browser/ui/views/tabs/tab_hover_card_metrics_unittest.cc
+++ /dev/null
@@ -1,339 +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.
-
-#include "chrome/browser/ui/views/tabs/tab_hover_card_metrics.h"
-
-#include <initializer_list>
-#include <memory>
-
-#include "base/logging.h"
-#include "base/test/gtest_util.h"
-#include "base/test/metrics/histogram_tester.h"
-#include "base/test/task_environment.h"
-#include "base/time/time.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace {
-
-// Provides a mock delegate used for testing without having to have a browser,
-// tabstrip, tabs, or a real controller.
-class MockHoverCardMetricsDelegate : public TabHoverCardMetrics::Delegate {
- public:
-  void set_tab_count(size_t tab_count) {
-    DCHECK_GT(tab_count, 0U);
-    tab_count_ = tab_count;
-  }
-
-  void set_previews_enabled(bool previews_enabled) {
-    previews_enabled_ = previews_enabled;
-  }
-
-  // TabHoverCardMetrics::Delegate:
-  size_t GetTabCount() const override { return tab_count_; }
-  bool ArePreviewsEnabled() const override { return previews_enabled_; }
-  views::Widget* GetHoverCardWidget() override { return nullptr; }
-
- private:
-  size_t tab_count_ = 1U;
-  bool previews_enabled_ = false;
-};
-
-// Create some sample tab handles that don't correspond to real tabs, but which
-// are unique.
-const TabHoverCardMetrics::TabHandle kTabHandle1 =
-    reinterpret_cast<TabHoverCardMetrics::TabHandle>(1);
-const TabHoverCardMetrics::TabHandle kTabHandle2 =
-    reinterpret_cast<TabHoverCardMetrics::TabHandle>(2);
-const TabHoverCardMetrics::TabHandle kTabHandle3 =
-    reinterpret_cast<TabHoverCardMetrics::TabHandle>(3);
-
-// Create some intervals that will fall into different buckets in the histogram.
-constexpr int kShortDelayMS = 200;
-constexpr int kMediumDelayMS = 500;
-constexpr int kLongDelayMS = 1000;
-constexpr base::TimeDelta kShortDelay = base::Milliseconds(kShortDelayMS);
-constexpr base::TimeDelta kMediumDelay = base::Milliseconds(kMediumDelayMS);
-constexpr base::TimeDelta kLongDelay = base::Milliseconds(kLongDelayMS);
-
-std::string GetFullHistogramName(const char* prefix, size_t tab_count) {
-  return TabHoverCardMetrics::GetBucketHistogramName(
-      prefix, TabHoverCardMetrics::GetBucketForTabCount(tab_count));
-}
-
-}  // namespace
-
-class TabHoverCardMetricsTest : public ::testing::Test {
- public:
-  void SetUp() override {
-    delegate_ = std::make_unique<MockHoverCardMetricsDelegate>();
-    metrics_ = std::make_unique<TabHoverCardMetrics>(delegate_.get());
-  }
-
-  void TearDown() override {
-    // Dump all histograms on failure so we can debug what went wrong.
-    if (HasFailure())
-      LOG(WARNING) << histograms_.GetAllHistogramsRecorded();
-  }
-
-  struct BucketCount {
-    int sample;
-    size_t count;
-  };
-
-  void ExpectResults(const char* prefix,
-                     size_t tab_count,
-                     std::initializer_list<BucketCount> counts) {
-    const std::string name = GetFullHistogramName(prefix, tab_count);
-    size_t total_count = 0;
-    for (const BucketCount& bucket_count : counts) {
-      total_count += bucket_count.count;
-      histograms_.ExpectBucketCount(name, bucket_count.sample,
-                                    bucket_count.count);
-    }
-    histograms_.ExpectTotalCount(name, total_count);
-  }
-
- protected:
-  const char* kCardsSeenPrefix =
-      TabHoverCardMetrics::kHistogramPrefixHoverCardsSeenBeforeSelection;
-  const char* kPreviewsSeenPrefix =
-      TabHoverCardMetrics::kHistogramPrefixPreviewsSeenBeforeSelection;
-  const char* kCardTimePrefix =
-      TabHoverCardMetrics::kHistogramPrefixTabHoverCardTime;
-  const char* kPreviewTimePrefix =
-      TabHoverCardMetrics::kHistogramPrefixTabPreviewTime;
-  const char* kLastCardTimePrefix =
-      TabHoverCardMetrics::kHistogramPrefixLastTabHoverCardTime;
-  const char* kLastPreviewTimePrefix =
-      TabHoverCardMetrics::kHistogramPrefixLastTabPreviewTime;
-
-  base::test::SingleThreadTaskEnvironment task_environment_{
-      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
-  std::unique_ptr<MockHoverCardMetricsDelegate> delegate_;
-  std::unique_ptr<TabHoverCardMetrics> metrics_;
-  base::HistogramTester histograms_;
-};
-
-TEST_F(TabHoverCardMetricsTest, SimpleSequenceWithoutPreviews) {
-  metrics_->InitialCardBeingShown();
-  metrics_->CardFullyVisibleOnTab(kTabHandle1, false);
-  task_environment_.AdvanceClock(kShortDelay);
-  metrics_->CardFullyVisibleOnTab(kTabHandle2, false);
-  task_environment_.AdvanceClock(kMediumDelay);
-  metrics_->CardWillBeHidden();
-  metrics_->TabSelectedViaMouse(kTabHandle2);
-
-  ExpectResults(kCardsSeenPrefix, 1, {{2, 1}});
-  ExpectResults(kPreviewsSeenPrefix, 1, {{0, 1}});
-  ExpectResults(kLastCardTimePrefix, 1, {{kMediumDelayMS, 1}});
-  ExpectResults(kCardTimePrefix, 1, {{kShortDelayMS, 1}, {kMediumDelayMS, 1}});
-
-  // These should have no data:
-  ExpectResults(kLastPreviewTimePrefix, 1, {});
-  ExpectResults(kPreviewTimePrefix, 1, {});
-}
-
-TEST_F(TabHoverCardMetricsTest, HideSignalFollowsSelection) {
-  metrics_->InitialCardBeingShown();
-  metrics_->CardFullyVisibleOnTab(kTabHandle1, false);
-  task_environment_.AdvanceClock(kShortDelay);
-  metrics_->CardFullyVisibleOnTab(kTabHandle2, false);
-  task_environment_.AdvanceClock(kMediumDelay);
-  metrics_->TabSelectedViaMouse(kTabHandle2);
-  metrics_->CardWillBeHidden();
-
-  ExpectResults(kCardsSeenPrefix, 1, {{2, 1}});
-  ExpectResults(kPreviewsSeenPrefix, 1, {{0, 1}});
-  ExpectResults(kLastCardTimePrefix, 1, {{kMediumDelayMS, 1}});
-  ExpectResults(kCardTimePrefix, 1, {{kShortDelayMS, 1}, {kMediumDelayMS, 1}});
-
-  // These should have no data:
-  ExpectResults(kLastPreviewTimePrefix, 1, {});
-  ExpectResults(kPreviewTimePrefix, 1, {});
-}
-
-TEST_F(TabHoverCardMetricsTest, SimpleSequenceWithPreviews) {
-  delegate_->set_previews_enabled(true);
-
-  metrics_->InitialCardBeingShown();
-  metrics_->CardFullyVisibleOnTab(kTabHandle1, false);
-  task_environment_.AdvanceClock(kShortDelay);
-  metrics_->CardFullyVisibleOnTab(kTabHandle2, false);
-  task_environment_.AdvanceClock(kShortDelay);
-  metrics_->ImageLoadedForTab(kTabHandle2);
-  task_environment_.AdvanceClock(kMediumDelay);
-  metrics_->CardWillBeHidden();
-  metrics_->TabSelectedViaMouse(kTabHandle2);
-
-  ExpectResults(kCardsSeenPrefix, 1, {{2, 1}});
-  ExpectResults(kPreviewsSeenPrefix, 1, {{1, 1}});
-  ExpectResults(kLastCardTimePrefix, 1, {{kShortDelayMS + kMediumDelayMS, 1}});
-  ExpectResults(kCardTimePrefix, 1,
-                {{kShortDelayMS, 1}, {kShortDelayMS + kMediumDelayMS, 1}});
-  ExpectResults(kLastPreviewTimePrefix, 1, {{kMediumDelayMS, 1}});
-  ExpectResults(kPreviewTimePrefix, 1, {{kMediumDelayMS, 1}});
-}
-
-TEST_F(TabHoverCardMetricsTest, CardDoesNotReachLastTab) {
-  delegate_->set_previews_enabled(true);
-
-  metrics_->InitialCardBeingShown();
-  metrics_->CardFullyVisibleOnTab(kTabHandle1, false);
-  task_environment_.AdvanceClock(kShortDelay);
-  metrics_->ImageLoadedForTab(kTabHandle1);
-  task_environment_.AdvanceClock(kShortDelay);
-  metrics_->CardWillBeHidden();
-  metrics_->TabSelectedViaMouse(kTabHandle2);
-
-  ExpectResults(kCardsSeenPrefix, 1, {{1, 1}});
-  ExpectResults(kPreviewsSeenPrefix, 1, {{1, 1}});
-  ExpectResults(kLastCardTimePrefix, 1, {{0, 1}});
-  ExpectResults(kCardTimePrefix, 1, {{kShortDelayMS * 2, 1}});
-  ExpectResults(kLastPreviewTimePrefix, 1, {{0, 1}});
-  ExpectResults(kPreviewTimePrefix, 1, {{kShortDelayMS, 1}});
-}
-
-TEST_F(TabHoverCardMetricsTest, ImageAlreadyLoadedForTab) {
-  delegate_->set_previews_enabled(true);
-
-  metrics_->InitialCardBeingShown();
-  metrics_->CardFullyVisibleOnTab(kTabHandle1, true);
-  task_environment_.AdvanceClock(kShortDelay);
-  metrics_->CardWillBeHidden();
-  metrics_->TabSelectedViaMouse(kTabHandle1);
-
-  ExpectResults(kCardsSeenPrefix, 1, {{1, 1}});
-  ExpectResults(kPreviewsSeenPrefix, 1, {{1, 1}});
-  ExpectResults(kLastCardTimePrefix, 1, {{kShortDelayMS, 1}});
-  ExpectResults(kCardTimePrefix, 1, {{kShortDelayMS, 1}});
-  ExpectResults(kLastPreviewTimePrefix, 1, {{kShortDelayMS, 1}});
-  ExpectResults(kPreviewTimePrefix, 1, {{kShortDelayMS, 1}});
-}
-
-TEST_F(TabHoverCardMetricsTest, MediumTabCount) {
-  delegate_->set_tab_count(7);
-
-  metrics_->InitialCardBeingShown();
-  metrics_->CardFullyVisibleOnTab(kTabHandle1, false);
-  task_environment_.AdvanceClock(kShortDelay);
-  metrics_->CardFullyVisibleOnTab(kTabHandle2, false);
-  task_environment_.AdvanceClock(kMediumDelay);
-  metrics_->CardWillBeHidden();
-  metrics_->TabSelectedViaMouse(kTabHandle2);
-
-  ExpectResults(kCardsSeenPrefix, 7, {{2, 1}});
-  ExpectResults(kCardTimePrefix, 7, {{kShortDelayMS, 1}, {kMediumDelayMS, 1}});
-  ExpectResults(kLastCardTimePrefix, 7, {{kMediumDelayMS, 1}});
-}
-
-TEST_F(TabHoverCardMetricsTest, VeryLargeTabCount) {
-  delegate_->set_tab_count(60);
-
-  metrics_->InitialCardBeingShown();
-  metrics_->CardFullyVisibleOnTab(kTabHandle1, false);
-  task_environment_.AdvanceClock(kShortDelay);
-  metrics_->CardFullyVisibleOnTab(kTabHandle2, false);
-  task_environment_.AdvanceClock(kMediumDelay);
-  metrics_->CardWillBeHidden();
-  metrics_->TabSelectedViaMouse(kTabHandle2);
-
-  ExpectResults(kCardsSeenPrefix, 60, {{2, 1}});
-  ExpectResults(kCardTimePrefix, 60, {{kShortDelayMS, 1}, {kMediumDelayMS, 1}});
-  ExpectResults(kLastCardTimePrefix, 60, {{kMediumDelayMS, 1}});
-}
-
-TEST_F(TabHoverCardMetricsTest, RepeatingSequence) {
-  metrics_->InitialCardBeingShown();
-  metrics_->CardFullyVisibleOnTab(kTabHandle1, false);
-  task_environment_.AdvanceClock(kShortDelay);
-  metrics_->CardFullyVisibleOnTab(kTabHandle2, false);
-  task_environment_.AdvanceClock(kMediumDelay);
-  metrics_->CardFullyVisibleOnTab(kTabHandle3, false);
-  task_environment_.AdvanceClock(kLongDelay);
-  metrics_->CardFullyVisibleOnTab(kTabHandle2, false);
-  task_environment_.AdvanceClock(kShortDelay);
-  metrics_->CardWillBeHidden();
-  metrics_->TabSelectedViaMouse(kTabHandle2);
-
-  ExpectResults(kCardsSeenPrefix, 1, {{4, 1}});
-  ExpectResults(kLastCardTimePrefix, 1, {{kShortDelayMS, 1}});
-  ExpectResults(kCardTimePrefix, 1,
-                {{kShortDelayMS, 2}, {kMediumDelayMS, 1}, {kLongDelayMS, 1}});
-}
-
-TEST_F(TabHoverCardMetricsTest, MultipleSequences) {
-  metrics_->InitialCardBeingShown();
-  metrics_->CardFullyVisibleOnTab(kTabHandle1, false);
-  task_environment_.AdvanceClock(kMediumDelay);
-  metrics_->CardFullyVisibleOnTab(kTabHandle2, false);
-  task_environment_.AdvanceClock(kShortDelay);
-  metrics_->CardWillBeHidden();
-  metrics_->TabSelectedViaMouse(kTabHandle2);
-
-  metrics_->InitialCardBeingShown();
-  metrics_->CardFullyVisibleOnTab(kTabHandle1, false);
-  task_environment_.AdvanceClock(kLongDelay);
-  metrics_->CardFullyVisibleOnTab(kTabHandle2, false);
-  task_environment_.AdvanceClock(kShortDelay);
-  metrics_->CardFullyVisibleOnTab(kTabHandle3, false);
-  task_environment_.AdvanceClock(kMediumDelay);
-  metrics_->CardWillBeHidden();
-  metrics_->TabSelectedViaMouse(kTabHandle3);
-
-  ExpectResults(kCardsSeenPrefix, 1, {{2, 1}, {3, 1}});
-  ExpectResults(kLastCardTimePrefix, 1,
-                {{kShortDelayMS, 1}, {kMediumDelayMS, 1}});
-  ExpectResults(kCardTimePrefix, 1,
-                {{kShortDelayMS, 2}, {kMediumDelayMS, 2}, {kLongDelayMS, 1}});
-}
-
-TEST_F(TabHoverCardMetricsTest, ResumeSequenceFromSameTab) {
-  metrics_->InitialCardBeingShown();
-  metrics_->CardFullyVisibleOnTab(kTabHandle1, false);
-  task_environment_.AdvanceClock(kShortDelay);
-  metrics_->CardFullyVisibleOnTab(kTabHandle2, false);
-  task_environment_.AdvanceClock(kMediumDelay);
-  metrics_->CardWillBeHidden();
-  metrics_->CardFullyVisibleOnTab(kTabHandle2, false);
-  task_environment_.AdvanceClock(kMediumDelay);
-  metrics_->CardFullyVisibleOnTab(kTabHandle3, false);
-  task_environment_.AdvanceClock(kLongDelay);
-  metrics_->CardWillBeHidden();
-  metrics_->TabSelectedViaMouse(kTabHandle3);
-
-  ExpectResults(kCardsSeenPrefix, 1, {{4, 1}});
-  ExpectResults(kPreviewsSeenPrefix, 1, {{0, 1}});
-  ExpectResults(kLastCardTimePrefix, 1, {{kLongDelayMS, 1}});
-  ExpectResults(kCardTimePrefix, 1,
-                {{kShortDelayMS, 1}, {kMediumDelayMS, 2}, {kLongDelayMS, 1}});
-
-  // These should have no data:
-  ExpectResults(kLastPreviewTimePrefix, 1, {});
-  ExpectResults(kPreviewTimePrefix, 1, {});
-}
-
-TEST_F(TabHoverCardMetricsTest, ResumeSequenceFromDifferentTab) {
-  metrics_->InitialCardBeingShown();
-  metrics_->CardFullyVisibleOnTab(kTabHandle1, false);
-  task_environment_.AdvanceClock(kShortDelay);
-  metrics_->CardFullyVisibleOnTab(kTabHandle2, false);
-  task_environment_.AdvanceClock(kMediumDelay);
-  metrics_->CardWillBeHidden();
-  metrics_->CardFullyVisibleOnTab(kTabHandle3, false);
-  task_environment_.AdvanceClock(kMediumDelay);
-  metrics_->CardFullyVisibleOnTab(kTabHandle2, false);
-  task_environment_.AdvanceClock(kLongDelay);
-  metrics_->CardWillBeHidden();
-  metrics_->TabSelectedViaMouse(kTabHandle2);
-
-  ExpectResults(kCardsSeenPrefix, 1, {{4, 1}});
-  ExpectResults(kPreviewsSeenPrefix, 1, {{0, 1}});
-  ExpectResults(kLastCardTimePrefix, 1, {{kLongDelayMS, 1}});
-  ExpectResults(kCardTimePrefix, 1,
-                {{kShortDelayMS, 1}, {kMediumDelayMS, 2}, {kLongDelayMS, 1}});
-
-  // These should have no data:
-  ExpectResults(kLastPreviewTimePrefix, 1, {});
-  ExpectResults(kPreviewTimePrefix, 1, {});
-}
diff --git a/chrome/browser/ui/views/tabs/tab_hover_card_test_util.cc b/chrome/browser/ui/views/tabs/tab_hover_card_test_util.cc
index 2cad892..56f9ecfe 100644
--- a/chrome/browser/ui/views/tabs/tab_hover_card_test_util.cc
+++ b/chrome/browser/ui/views/tabs/tab_hover_card_test_util.cc
@@ -55,8 +55,7 @@
 int TabHoverCardTestUtil::GetHoverCardsSeenCount(Browser* browser) {
   return GetTabStrip(browser)
       ->hover_card_controller_for_testing()
-      ->metrics_for_testing()
-      ->cards_seen_count();
+      ->hover_cards_seen_count_for_testing();
 }
 
 // static
@@ -64,9 +63,6 @@
                                                                int tab_index) {
   auto* const tab_strip = GetTabStrip(browser);
 
-  LOG(ERROR) << "SimulateHoverTab, index: " << tab_index;
-  LOG(ERROR) << "TabStrip tab count: " << tab_strip->GetTabCount();
-
   // We don't use Tab::OnMouseEntered here to invoke the hover card because
   // that path is disabled in browser tests. If we enabled it, the real mouse
   // might interfere with the test.
diff --git a/chrome/browser/ui/views/tabs/tab_scrolling_animation.cc b/chrome/browser/ui/views/tabs/tab_scrolling_animation.cc
new file mode 100644
index 0000000..28ee00d
--- /dev/null
+++ b/chrome/browser/ui/views/tabs/tab_scrolling_animation.cc
@@ -0,0 +1,33 @@
+// 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/ui/views/tabs/tab_scrolling_animation.h"
+
+TabScrollingAnimation::TabScrollingAnimation(
+    views::View* contents_view,
+    gfx::AnimationContainer* bounds_animator_container,
+    base::TimeDelta duration,
+    const gfx::Rect start_visible_rect,
+    const gfx::Rect end_visible_rect)
+    : gfx::LinearAnimation(duration,
+                           gfx::LinearAnimation::kDefaultFrameRate,
+                           this),
+      contents_view_(contents_view),
+      start_visible_rect_(start_visible_rect),
+      end_visible_rect_(end_visible_rect) {
+  SetContainer(bounds_animator_container);
+}
+
+void TabScrollingAnimation::AnimateToState(double state) {
+  gfx::Rect intermediary_rect(
+      start_visible_rect_.x() +
+          (end_visible_rect_.x() - start_visible_rect_.x()) * state,
+      start_visible_rect_.y(), start_visible_rect_.width(),
+      start_visible_rect_.height());
+  contents_view_->ScrollRectToVisible(intermediary_rect);
+}
+
+void TabScrollingAnimation::AnimationEnded(const gfx::Animation* animation) {
+  contents_view_->ScrollRectToVisible(end_visible_rect_);
+}
diff --git a/chrome/browser/ui/views/tabs/tab_scrolling_animation.h b/chrome/browser/ui/views/tabs/tab_scrolling_animation.h
new file mode 100644
index 0000000..9cc2fab
--- /dev/null
+++ b/chrome/browser/ui/views/tabs/tab_scrolling_animation.h
@@ -0,0 +1,40 @@
+// 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_UI_VIEWS_TABS_TAB_SCROLLING_ANIMATION_H_
+#define CHROME_BROWSER_UI_VIEWS_TABS_TAB_SCROLLING_ANIMATION_H_
+
+#include "ui/gfx/animation/animation_delegate.h"
+#include "ui/gfx/animation/linear_animation.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/views/view.h"
+
+// Helper class that manages the tab scrolling animation.
+class TabScrollingAnimation : public gfx::LinearAnimation,
+                              public gfx::AnimationDelegate {
+ public:
+  explicit TabScrollingAnimation(
+      views::View* contents_view,
+      gfx::AnimationContainer* bounds_animator_container,
+      base::TimeDelta duration,
+      const gfx::Rect start_visible_rect,
+      const gfx::Rect end_visible_rect);
+
+  TabScrollingAnimation(const TabScrollingAnimation&) = delete;
+  TabScrollingAnimation& operator=(const TabScrollingAnimation&) = delete;
+  ~TabScrollingAnimation() override = default;
+
+  // gfx::LinearAnimation:
+  void AnimateToState(double state) override;
+
+  // gfx::AnimationDelegate:
+  void AnimationEnded(const gfx::Animation* animation) override;
+
+ private:
+  const raw_ptr<views::View> contents_view_;
+  const gfx::Rect start_visible_rect_;
+  const gfx::Rect end_visible_rect_;
+};
+
+#endif  // CHROME_BROWSER_UI_VIEWS_TABS_TAB_SCROLLING_ANIMATION_H_
diff --git a/chrome/browser/ui/views/tabs/tab_strip.cc b/chrome/browser/ui/views/tabs/tab_strip.cc
index 8e1faaa3..87215215 100644
--- a/chrome/browser/ui/views/tabs/tab_strip.cc
+++ b/chrome/browser/ui/views/tabs/tab_strip.cc
@@ -1424,12 +1424,6 @@
     }
   }
 
-  // Selecting a tab via mouse affects what statistics we collect.
-  if (event.type() == ui::ET_MOUSE_PRESSED && !tab->IsActive() &&
-      hover_card_controller_) {
-    hover_card_controller_->TabSelectedViaMouse(tab);
-  }
-
   controller_->SelectTab(model_index, event);
 }
 
diff --git a/chrome/browser/ui/views/tabs/tab_strip_layout_helper.cc b/chrome/browser/ui/views/tabs/tab_strip_layout_helper.cc
index e2643ae9..8f613d63 100644
--- a/chrome/browser/ui/views/tabs/tab_strip_layout_helper.cc
+++ b/chrome/browser/ui/views/tabs/tab_strip_layout_helper.cc
@@ -71,9 +71,7 @@
     : controller_(controller),
       get_tabs_callback_(get_tabs_callback),
       active_tab_width_(TabStyle::GetStandardWidth()),
-      inactive_tab_width_(TabStyle::GetStandardWidth()),
-      first_non_pinned_tab_index_(0),
-      first_non_pinned_tab_x_(0) {}
+      inactive_tab_width_(TabStyle::GetStandardWidth()) {}
 
 TabStripLayoutHelper::~TabStripLayoutHelper() = default;
 
diff --git a/chrome/browser/ui/views/tabs/tab_strip_layout_helper.h b/chrome/browser/ui/views/tabs/tab_strip_layout_helper.h
index 979516f..57017498 100644
--- a/chrome/browser/ui/views/tabs/tab_strip_layout_helper.h
+++ b/chrome/browser/ui/views/tabs/tab_strip_layout_helper.h
@@ -50,8 +50,6 @@
 
   int active_tab_width() { return active_tab_width_; }
   int inactive_tab_width() { return inactive_tab_width_; }
-  int first_non_pinned_tab_index() { return first_non_pinned_tab_index_; }
-  int first_non_pinned_tab_x() { return first_non_pinned_tab_x_; }
 
   // Returns the number of pinned tabs in the tabstrip.
   size_t GetPinnedTabCount() const;
@@ -169,9 +167,6 @@
   // into these widths, the initial tabs in the strip will be 1 px larger.
   int active_tab_width_;
   int inactive_tab_width_;
-
-  int first_non_pinned_tab_index_;
-  int first_non_pinned_tab_x_;
 };
 
 #endif  // CHROME_BROWSER_UI_VIEWS_TABS_TAB_STRIP_LAYOUT_HELPER_H_
diff --git a/chrome/browser/ui/views/tabs/tab_style_views.cc b/chrome/browser/ui/views/tabs/tab_style_views.cc
index 8880950..50e89eae 100644
--- a/chrome/browser/ui/views/tabs/tab_style_views.cc
+++ b/chrome/browser/ui/views/tabs/tab_style_views.cc
@@ -27,6 +27,7 @@
 #include "chrome/browser/ui/views/tabs/tab_slot_controller.h"
 #include "chrome/grit/theme_resources.h"
 #include "components/tab_groups/tab_group_visual_data.h"
+#include "third_party/skia/include/core/SkRRect.h"
 #include "third_party/skia/include/core/SkScalar.h"
 #include "third_party/skia/include/pathops/SkPathOps.h"
 #include "ui/base/theme_provider.h"
diff --git a/chrome/browser/ui/views/web_apps/web_app_uninstall_dialog_view.cc b/chrome/browser/ui/views/web_apps/web_app_uninstall_dialog_view.cc
index f291992..a2ca4d5 100644
--- a/chrome/browser/ui/views/web_apps/web_app_uninstall_dialog_view.cc
+++ b/chrome/browser/ui/views/web_apps/web_app_uninstall_dialog_view.cc
@@ -9,7 +9,6 @@
 #include "base/metrics/histogram_macros.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
-#include "chrome/browser/ui/native_window_tracker.h"
 #include "chrome/browser/ui/views/chrome_layout_provider.h"
 #include "chrome/browser/ui/views/chrome_typography.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
@@ -35,6 +34,7 @@
 #include "ui/views/controls/image_view.h"
 #include "ui/views/controls/label.h"
 #include "ui/views/layout/box_layout.h"
+#include "ui/views/native_window_tracker.h"
 #include "ui/views/view.h"
 #include "ui/views/widget/widget.h"
 #include "ui/views/window/dialog_delegate.h"
@@ -222,7 +222,7 @@
                                                        gfx::NativeWindow parent)
     : parent_(parent), profile_(profile) {
   if (parent)
-    parent_window_tracker_ = NativeWindowTracker::Create(parent);
+    parent_window_tracker_ = views::NativeWindowTracker::Create(parent);
 }
 
 WebAppUninstallDialogViews::~WebAppUninstallDialogViews() {
diff --git a/chrome/browser/ui/views/web_apps/web_app_uninstall_dialog_view.h b/chrome/browser/ui/views/web_apps/web_app_uninstall_dialog_view.h
index 60d64c1..5399f96 100644
--- a/chrome/browser/ui/views/web_apps/web_app_uninstall_dialog_view.h
+++ b/chrome/browser/ui/views/web_apps/web_app_uninstall_dialog_view.h
@@ -25,7 +25,6 @@
 #include "ui/views/window/dialog_delegate.h"
 #include "url/gurl.h"
 
-class NativeWindowTracker;
 class Profile;
 class WebAppUninstallDialogViews;
 
@@ -36,6 +35,7 @@
 
 namespace views {
 class Checkbox;
+class NativeWindowTracker;
 }
 
 // The dialog's view, owned by the views framework.
@@ -134,7 +134,7 @@
   base::OnceClosure dialog_shown_callback_for_testing_;
 
   // Tracks whether |parent_| got destroyed.
-  std::unique_ptr<NativeWindowTracker> parent_window_tracker_;
+  std::unique_ptr<views::NativeWindowTracker> parent_window_tracker_;
 
   base::ScopedObservation<web_app::WebAppInstallManager,
                           web_app::WebAppInstallManagerObserver>
diff --git a/chrome/browser/ui/web_applications/BUILD.gn b/chrome/browser/ui/web_applications/BUILD.gn
index be7af40..aa6bdbd 100644
--- a/chrome/browser/ui/web_applications/BUILD.gn
+++ b/chrome/browser/ui/web_applications/BUILD.gn
@@ -30,6 +30,7 @@
   testonly = true
 
   sources = [
+    "commands/launch_web_app_command_browsertest.cc",
     "create_shortcut_browsertest.cc",
     "pwa_mixed_content_browsertest.cc",
     "sub_apps_service_impl_browsertest.cc",
diff --git a/chrome/browser/ui/web_applications/commands/launch_web_app_command.cc b/chrome/browser/ui/web_applications/commands/launch_web_app_command.cc
new file mode 100644
index 0000000..9be95a74
--- /dev/null
+++ b/chrome/browser/ui/web_applications/commands/launch_web_app_command.cc
@@ -0,0 +1,152 @@
+// 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/ui/web_applications/commands/launch_web_app_command.h"
+
+#include <string>
+#include <utility>
+
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/functional/bind.h"
+#include "base/json/values_util.h"
+#include "base/numerics/safe_conversions.h"
+#include "base/strings/strcat.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/values.h"
+#include "chrome/browser/apps/app_service/app_launch_params.h"
+#include "chrome/browser/apps/app_service/launch_utils.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_finder.h"
+#include "chrome/browser/ui/web_applications/web_app_launch_manager.h"
+#include "chrome/browser/ui/web_applications/web_app_launch_process.h"
+#include "chrome/browser/ui/web_applications/web_app_launch_utils.h"
+#include "chrome/browser/web_applications/locks/app_lock.h"
+#include "chrome/browser/web_applications/os_integration/os_integration_manager.h"
+#include "chrome/browser/web_applications/web_app_registrar.h"
+#include "chrome/browser/web_applications/web_app_ui_manager.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "ui/base/window_open_disposition.h"
+#include "url/gurl.h"
+
+#if BUILDFLAG(IS_WIN)
+#include "base/strings/utf_string_conversions.h"
+#endif
+
+namespace web_app {
+
+namespace {
+
+base::Value::Dict ToDebugDict(const apps::AppLaunchParams& params) {
+  base::Value::Dict value;
+  value.Set("app_id", params.app_id);
+  value.Set("launch_id", params.launch_id);
+  value.Set("container", static_cast<int>(params.container));
+  value.Set("disposition", static_cast<int>(params.disposition));
+  value.Set("override_url", params.override_url.spec());
+  value.Set("override_bounds", params.override_bounds.ToString());
+  value.Set("override_app_name", params.override_app_name);
+  value.Set("restore_id", params.restore_id);
+#if BUILDFLAG(IS_WIN)
+  value.Set("command_line",
+            base::WideToUTF8(params.command_line.GetCommandLineString()));
+#else
+  value.Set("command_line", params.command_line.GetCommandLineString());
+#endif
+  value.Set("current_directory",
+            base::FilePathToValue(params.current_directory));
+  value.Set("launch_source", static_cast<int>(params.launch_source));
+  value.Set("display_id", base::saturated_cast<int>(params.display_id));
+  base::Value::List files_list;
+  for (const base::FilePath& file : params.launch_files) {
+    files_list.Append(base::FilePathToValue(file));
+  }
+  value.Set("launch_files", std::move(files_list));
+  value.Set("intent", params.intent ? "<set>" : "<not set>");
+  value.Set("url_handler_launch_url",
+            params.url_handler_launch_url.value_or(GURL()).spec());
+  value.Set("protocol_handler_launch_url",
+            params.protocol_handler_launch_url.value_or(GURL()).spec());
+  value.Set("omit_from_session_restore", params.omit_from_session_restore);
+  return value;
+}
+
+}  // namespace
+
+base::Value LaunchWebApp(apps::AppLaunchParams params,
+                         LaunchWebAppWindowSetting launch_setting,
+                         Profile& profile,
+                         LaunchWebAppCallback callback,
+                         AppLock& app_lock) {
+  base::Value::Dict debug_value;
+  debug_value.Set("launch_params", ToDebugDict(params));
+  debug_value.Set("launch_window_setting", static_cast<int>(launch_setting));
+
+  if (launch_setting == LaunchWebAppWindowSetting::kOverrideWithWebAppConfig) {
+    DisplayMode display_mode =
+        app_lock.registrar().GetAppEffectiveDisplayMode(params.app_id);
+    switch (display_mode) {
+      case DisplayMode::kUndefined:
+      case DisplayMode::kFullscreen:
+      case DisplayMode::kBrowser:
+        params.container = apps::LaunchContainer::kLaunchContainerTab;
+        params.disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB;
+        break;
+      case DisplayMode::kMinimalUi:
+      case DisplayMode::kWindowControlsOverlay:
+      case DisplayMode::kTabbed:
+      case DisplayMode::kBorderless:
+      case DisplayMode::kStandalone:
+        params.container = apps::LaunchContainer::kLaunchContainerWindow;
+        params.disposition = WindowOpenDisposition::NEW_WINDOW;
+        break;
+    }
+  }
+  DCHECK_NE(params.container, apps::LaunchContainer::kLaunchContainerNone);
+  DCHECK_NE(params.disposition, WindowOpenDisposition::UNKNOWN);
+
+  apps::LaunchContainer container;
+  Browser* browser = nullptr;
+  content::WebContents* web_contents = nullptr;
+  // Do not launch anything if the profile is being deleted.
+  if (Browser::GetCreationStatusForProfile(&profile) ==
+      Browser::CreationStatus::kOk) {
+    if (app_lock.registrar().IsInstalled(params.app_id)) {
+      container = params.container;
+      if (WebAppLaunchManager::GetOpenApplicationCallbackForTesting()) {
+        web_contents =
+            WebAppLaunchManager::GetOpenApplicationCallbackForTesting().Run(
+                std::move(params));
+      } else {
+        web_contents = WebAppLaunchProcess::CreateAndRun(
+            profile, app_lock.registrar(), app_lock.os_integration_manager(),
+            params);
+      }
+      if (web_contents)
+        browser = chrome::FindBrowserWithWebContents(web_contents);
+    } else {
+      debug_value.Set("error", "Unknown app id.");
+      // Open an empty browser window as the app_id is invalid.
+      DVLOG(1) << "Cannot launch app with unknown id: " << params.app_id;
+      container = apps::LaunchContainer::kLaunchContainerNone;
+      browser = apps::CreateBrowserWithNewTabPage(&profile);
+    }
+  } else {
+    std::string error_str = base::StringPrintf(
+        "Cannot launch app %s without profile creation: %d",
+        params.app_id.c_str(),
+        static_cast<int>(Browser::GetCreationStatusForProfile(&profile)));
+    debug_value.Set("error", error_str);
+    DVLOG(1) << error_str;
+  }
+  base::SequencedTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE,
+      base::BindOnce(std::move(callback), browser, web_contents, container));
+  return base::Value(std::move(debug_value));
+}
+
+}  // namespace web_app
diff --git a/chrome/browser/ui/web_applications/commands/launch_web_app_command.h b/chrome/browser/ui/web_applications/commands/launch_web_app_command.h
new file mode 100644
index 0000000..8bd0193a
--- /dev/null
+++ b/chrome/browser/ui/web_applications/commands/launch_web_app_command.h
@@ -0,0 +1,28 @@
+// 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_UI_WEB_APPLICATIONS_COMMANDS_LAUNCH_WEB_APP_COMMAND_H_
+#define CHROME_BROWSER_UI_WEB_APPLICATIONS_COMMANDS_LAUNCH_WEB_APP_COMMAND_H_
+
+#include "chrome/browser/web_applications/web_app_ui_manager.h"
+
+class Profile;
+
+namespace apps {
+struct AppLaunchParams;
+}
+
+namespace web_app {
+
+class AppLock;
+
+base::Value LaunchWebApp(apps::AppLaunchParams params,
+                         LaunchWebAppWindowSetting launch_setting,
+                         Profile& profile,
+                         LaunchWebAppCallback callback,
+                         AppLock& lock);
+
+}  // namespace web_app
+
+#endif  // CHROME_BROWSER_UI_WEB_APPLICATIONS_COMMANDS_LAUNCH_WEB_APP_COMMAND_H_
diff --git a/chrome/browser/ui/web_applications/commands/launch_web_app_command_browsertest.cc b/chrome/browser/ui/web_applications/commands/launch_web_app_command_browsertest.cc
new file mode 100644
index 0000000..d5a237c
--- /dev/null
+++ b/chrome/browser/ui/web_applications/commands/launch_web_app_command_browsertest.cc
@@ -0,0 +1,133 @@
+// 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 <memory>
+#include <string>
+#include <tuple>
+
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/run_loop.h"
+#include "base/test/bind.h"
+#include "base/test/test_future.h"
+#include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
+#include "chrome/browser/apps/app_service/app_launch_params.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_list.h"
+#include "chrome/browser/ui/browser_navigator_params.h"
+#include "chrome/browser/ui/web_applications/app_browser_controller.h"
+#include "chrome/browser/ui/web_applications/commands/launch_web_app_command.h"
+#include "chrome/browser/ui/web_applications/web_app_controller_browsertest.h"
+#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
+#include "chrome/browser/web_applications/web_app_command_scheduler.h"
+#include "chrome/browser/web_applications/web_app_provider.h"
+#include "chrome/common/chrome_switches.h"
+#include "components/services/app_service/public/cpp/app_launch_util.h"
+#include "content/public/test/browser_test.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "url/gurl.h"
+
+namespace web_app {
+namespace {
+
+#if BUILDFLAG(IS_WIN)
+const base::FilePath::CharType kCurrentDirectory[] =
+    FILE_PATH_LITERAL("\\path");
+#else
+const base::FilePath::CharType kCurrentDirectory[] = FILE_PATH_LITERAL("/path");
+#endif  // BUILDFLAG(IS_WIN)
+
+class LaunchWebAppCommandTest : public WebAppControllerBrowserTest {
+ public:
+  const std::string kAppName = "TestApp";
+  const GURL kAppStartUrl = GURL("https://example.com");
+
+  void SetUpOnMainThread() override {
+    WebAppControllerBrowserTest::SetUpOnMainThread();
+    app_id_ = test::InstallDummyWebApp(profile(), kAppName, kAppStartUrl);
+  }
+
+ protected:
+  WebAppProvider& provider() {
+    return *web_app::WebAppProvider::GetForTest(profile());
+  }
+
+  std::tuple<Browser*, content::WebContents*, apps::LaunchContainer> DoLaunch(
+      apps::AppLaunchParams params) {
+    base::test::TestFuture<Browser*, content::WebContents*,
+                           apps::LaunchContainer>
+        future;
+    provider().scheduler().LaunchAppWithCustomParams(std::move(params),
+                                                     future.GetCallback());
+    return future.Get();
+  }
+
+  base::CommandLine CreateCommandLine() {
+    base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
+    command_line.AppendSwitchASCII(switches::kAppId, app_id_);
+    return command_line;
+  }
+
+  apps::AppLaunchParams CreateLaunchParams(
+      apps::LaunchContainer container,
+      WindowOpenDisposition disposition,
+      apps::LaunchSource source,
+      const std::vector<base::FilePath>& launch_files,
+      const absl::optional<GURL>& url_handler_launch_url,
+      const absl::optional<GURL>& protocol_handler_launch_url) {
+    apps::AppLaunchParams params(app_id_, container, disposition, source);
+    params.current_directory = base::FilePath(kCurrentDirectory);
+    params.command_line = CreateCommandLine();
+    params.launch_files = launch_files;
+    params.url_handler_launch_url = url_handler_launch_url;
+    params.protocol_handler_launch_url = protocol_handler_launch_url;
+
+    return params;
+  }
+
+  AppId app_id_;
+};
+
+IN_PROC_BROWSER_TEST_F(LaunchWebAppCommandTest, TabbedLaunchCurrentBrowser) {
+  apps::AppLaunchParams launch_params = CreateLaunchParams(
+      apps::LaunchContainer::kLaunchContainerTab,
+      WindowOpenDisposition::NEW_FOREGROUND_TAB,
+      apps::LaunchSource::kFromCommandLine, {}, absl::nullopt, absl::nullopt);
+
+  Browser* launch_browser;
+  content::WebContents* web_contents;
+  apps::LaunchContainer launch_container;
+  std::tie(launch_browser, web_contents, launch_container) =
+      DoLaunch(std::move(launch_params));
+
+  EXPECT_FALSE(AppBrowserController::IsWebApp(launch_browser));
+  EXPECT_EQ(launch_browser, browser());
+  EXPECT_EQ(launch_browser->tab_strip_model()->count(), 2);
+  EXPECT_EQ(web_contents->GetVisibleURL(), kAppStartUrl);
+}
+
+IN_PROC_BROWSER_TEST_F(LaunchWebAppCommandTest, StandaloneLaunch) {
+  apps::AppLaunchParams launch_params = CreateLaunchParams(
+      apps::LaunchContainer::kLaunchContainerWindow,
+      WindowOpenDisposition::CURRENT_TAB, apps::LaunchSource::kFromCommandLine,
+      {}, absl::nullopt, absl::nullopt);
+
+  Browser* launch_browser;
+  content::WebContents* web_contents;
+  apps::LaunchContainer launch_container;
+  std::tie(launch_browser, web_contents, launch_container) =
+      DoLaunch(std::move(launch_params));
+
+  EXPECT_TRUE(AppBrowserController::IsWebApp(launch_browser));
+  EXPECT_NE(launch_browser, browser());
+  EXPECT_EQ(BrowserList::GetInstance()->size(), 2ul);
+  EXPECT_EQ(launch_browser->tab_strip_model()->count(), 1);
+  EXPECT_EQ(web_contents->GetVisibleURL(), kAppStartUrl);
+}
+
+}  // namespace
+}  // namespace web_app
diff --git a/chrome/browser/ui/web_applications/create_shortcut_browsertest.cc b/chrome/browser/ui/web_applications/create_shortcut_browsertest.cc
index 6300505..93c10e6 100644
--- a/chrome/browser/ui/web_applications/create_shortcut_browsertest.cc
+++ b/chrome/browser/ui/web_applications/create_shortcut_browsertest.cc
@@ -61,7 +61,7 @@
   WebAppRegistrar& registrar() {
     auto* provider = WebAppProvider::GetForTest(profile());
     CHECK(provider);
-    return provider->registrar();
+    return provider->registrar_unsafe();
   }
 
   WebAppSyncBridge& sync_bridge() {
diff --git a/chrome/browser/ui/web_applications/diagnostics/web_app_icon_diagnostic.cc b/chrome/browser/ui/web_applications/diagnostics/web_app_icon_diagnostic.cc
index 875d6647..20ea442 100644
--- a/chrome/browser/ui/web_applications/diagnostics/web_app_icon_diagnostic.cc
+++ b/chrome/browser/ui/web_applications/diagnostics/web_app_icon_diagnostic.cc
@@ -18,7 +18,7 @@
     : profile_(profile),
       app_id_(std::move(app_id)),
       provider_(WebAppProvider::GetForLocalAppsUnchecked(profile_.get())),
-      app_(provider_->registrar().GetAppById(app_id_)) {}
+      app_(provider_->registrar_unsafe().GetAppById(app_id_)) {}
 
 WebAppIconDiagnostic::~WebAppIconDiagnostic() = default;
 
diff --git a/chrome/browser/ui/web_applications/diagnostics/web_app_icon_health_checks.cc b/chrome/browser/ui/web_applications/diagnostics/web_app_icon_health_checks.cc
index 455ec26..a7b82caf 100644
--- a/chrome/browser/ui/web_applications/diagnostics/web_app_icon_health_checks.cc
+++ b/chrome/browser/ui/web_applications/diagnostics/web_app_icon_health_checks.cc
@@ -48,7 +48,7 @@
 
   install_manager_observation_.Observe(&provider->install_manager());
 
-  std::vector<AppId> app_ids = provider->registrar().GetAppIds();
+  std::vector<AppId> app_ids = provider->registrar_unsafe().GetAppIds();
   run_complete_callback_ = base::BarrierClosure(
       app_ids.size(),
       base::BindOnce(&WebAppIconHealthChecks::RecordDiagnosticResults,
diff --git a/chrome/browser/ui/web_applications/lacros_web_app_browsertest.cc b/chrome/browser/ui/web_applications/lacros_web_app_browsertest.cc
index b44be0d..9da5b73 100644
--- a/chrome/browser/ui/web_applications/lacros_web_app_browsertest.cc
+++ b/chrome/browser/ui/web_applications/lacros_web_app_browsertest.cc
@@ -63,7 +63,8 @@
   const GURL app_url =
       https_server()->GetURL("/web_apps/file_handler_index.html");
   const AppId app_id = InstallWebAppFromManifest(browser(), app_url);
-  EXPECT_EQ(provider().registrar().GetAppFileHandlers(app_id)->size(), 1U);
+  EXPECT_EQ(provider().registrar_unsafe().GetAppFileHandlers(app_id)->size(),
+            1U);
 
   LaunchWebAppBrowser(app_id);
 
@@ -123,8 +124,9 @@
   const GURL app_url =
       https_server()->GetURL("/web_app_shortcuts/shortcuts.html");
   const AppId app_id = InstallWebAppFromManifest(browser(), app_url);
-  EXPECT_EQ(provider().registrar().GetAppShortcutsMenuItemInfos(app_id).size(),
-            6U);
+  EXPECT_EQ(
+      provider().registrar_unsafe().GetAppShortcutsMenuItemInfos(app_id).size(),
+      6U);
 
   // Wait for item to exist in shelf.
   ASSERT_TRUE(browser_test_util::WaitForShelfItem(app_id, /*exists=*/true));
diff --git a/chrome/browser/ui/web_applications/sub_apps_service_impl.cc b/chrome/browser/ui/web_applications/sub_apps_service_impl.cc
index e964ba7..1d6757da 100644
--- a/chrome/browser/ui/web_applications/sub_apps_service_impl.cc
+++ b/chrome/browser/ui/web_applications/sub_apps_service_impl.cc
@@ -196,7 +196,7 @@
     return;
   }
 
-  WebAppRegistrar& registrar = provider->registrar();
+  WebAppRegistrar& registrar = provider->registrar_unsafe();
 
   std::vector<SubAppsServiceListInfoPtr> sub_apps;
   for (const AppId& web_app_id : registrar.GetAllSubAppIds(*parent_app_id)) {
@@ -236,7 +236,7 @@
   }
 
   AppId sub_app_id = GenerateAppIdFromUnhashed(unhashed_app_id);
-  const WebApp* app = provider->registrar().GetAppById(sub_app_id);
+  const WebApp* app = provider->registrar_unsafe().GetAppById(sub_app_id);
 
   // Verify that the app we're trying to remove exists, that its parent_app is
   // the one doing the current call, and that the app was locally installed.
diff --git a/chrome/browser/ui/web_applications/sub_apps_service_impl_browsertest.cc b/chrome/browser/ui/web_applications/sub_apps_service_impl_browsertest.cc
index 45929ad6..dcd98e5 100644
--- a/chrome/browser/ui/web_applications/sub_apps_service_impl_browsertest.cc
+++ b/chrome/browser/ui/web_applications/sub_apps_service_impl_browsertest.cc
@@ -107,7 +107,7 @@
   }
 
   std::vector<AppId> GetAllSubAppIds(const AppId& parent_app_id) {
-    return provider().registrar().GetAllSubAppIds(parent_app_id);
+    return provider().registrar_unsafe().GetAllSubAppIds(parent_app_id);
   }
 
   void BindRemote(content::WebContents* web_contents = nullptr) {
@@ -288,12 +288,13 @@
             CallAdd({{unhashed_sub_app_id, kSubAppUrl}}));
 
   // Verify a bunch of things for the newly installed sub-app.
-  EXPECT_TRUE(provider().registrar().IsInstalled(sub_app_id));
-  EXPECT_TRUE(provider().registrar().IsLocallyInstalled(sub_app_id));
-  EXPECT_EQ(DisplayMode::kStandalone,
-            provider().registrar().GetAppEffectiveDisplayMode(sub_app_id));
+  EXPECT_TRUE(provider().registrar_unsafe().IsInstalled(sub_app_id));
+  EXPECT_TRUE(provider().registrar_unsafe().IsLocallyInstalled(sub_app_id));
+  EXPECT_EQ(
+      DisplayMode::kStandalone,
+      provider().registrar_unsafe().GetAppEffectiveDisplayMode(sub_app_id));
 
-  const WebApp* sub_app = provider().registrar().GetAppById(sub_app_id);
+  const WebApp* sub_app = provider().registrar_unsafe().GetAppById(sub_app_id);
   EXPECT_EQ(parent_app_id_, sub_app->parent_app_id());
   EXPECT_EQ(std::vector<AppId>{sub_app->app_id()},
             GetAllSubAppIds(parent_app_id_));
@@ -598,21 +599,21 @@
                           SubAppsServiceAddResultCode::kSuccessNewInstall),
             CallAdd({{unhashed_sub_app_id_3, GetURL(kSubAppPath3)}}));
 
-  EXPECT_TRUE(provider().registrar().IsInstalled(
+  EXPECT_TRUE(provider().registrar_unsafe().IsInstalled(
       GenerateAppIdFromUnhashed(unhashed_sub_app_id_1)));
-  EXPECT_TRUE(provider().registrar().IsInstalled(
+  EXPECT_TRUE(provider().registrar_unsafe().IsInstalled(
       GenerateAppIdFromUnhashed(unhashed_sub_app_id_2)));
-  EXPECT_TRUE(provider().registrar().IsInstalled(
+  EXPECT_TRUE(provider().registrar_unsafe().IsInstalled(
       GenerateAppIdFromUnhashed(unhashed_sub_app_id_3)));
 
   UninstallParentApp();
   // Verify that both parent app and sub apps are no longer installed.
-  EXPECT_FALSE(provider().registrar().IsInstalled(parent_app_id_));
-  EXPECT_FALSE(provider().registrar().IsInstalled(
+  EXPECT_FALSE(provider().registrar_unsafe().IsInstalled(parent_app_id_));
+  EXPECT_FALSE(provider().registrar_unsafe().IsInstalled(
       GenerateAppIdFromUnhashed(unhashed_sub_app_id_1)));
-  EXPECT_FALSE(provider().registrar().IsInstalled(
+  EXPECT_FALSE(provider().registrar_unsafe().IsInstalled(
       GenerateAppIdFromUnhashed(unhashed_sub_app_id_2)));
-  EXPECT_FALSE(provider().registrar().IsInstalled(
+  EXPECT_FALSE(provider().registrar_unsafe().IsInstalled(
       GenerateAppIdFromUnhashed(unhashed_sub_app_id_3)));
 }
 
@@ -645,21 +646,23 @@
                           SubAppsServiceAddResultCode::kSuccessNewInstall),
             CallAdd({{unhashed_sub_app_id_2, GetURL(kSubAppPath2)}}));
 
-  EXPECT_TRUE(provider().registrar().IsInstalled(
+  EXPECT_TRUE(provider().registrar_unsafe().IsInstalled(
       GenerateAppIdFromUnhashed(unhashed_sub_app_id_1)));
-  EXPECT_TRUE(provider().registrar().IsInstalled(
+  EXPECT_TRUE(provider().registrar_unsafe().IsInstalled(
       GenerateAppIdFromUnhashed(unhashed_sub_app_id_2)));
 
   UninstallParentAppBySource(WebAppManagement::kDefault);
   // Verify that parent app and sub_apps are still installed, only
   // the default install source is removed from the parent app.
-  EXPECT_TRUE(provider().registrar().IsInstalled(parent_app_id_));
-  EXPECT_FALSE(
-      provider().registrar().GetAppById(parent_app_id_)->IsPreinstalledApp());
+  EXPECT_TRUE(provider().registrar_unsafe().IsInstalled(parent_app_id_));
+  EXPECT_FALSE(provider()
+                   .registrar_unsafe()
+                   .GetAppById(parent_app_id_)
+                   ->IsPreinstalledApp());
 
-  EXPECT_TRUE(provider().registrar().IsInstalled(
+  EXPECT_TRUE(provider().registrar_unsafe().IsInstalled(
       GenerateAppIdFromUnhashed(unhashed_sub_app_id_1)));
-  EXPECT_TRUE(provider().registrar().IsInstalled(
+  EXPECT_TRUE(provider().registrar_unsafe().IsInstalled(
       GenerateAppIdFromUnhashed(unhashed_sub_app_id_2)));
 }
 
@@ -679,7 +682,7 @@
                           SubAppsServiceAddResultCode::kSuccessNewInstall),
             CallAdd({{unhashed_sub_app_id, kSubAppUrl}}));
   EXPECT_EQ(DisplayMode::kStandalone,
-            provider().registrar().GetAppEffectiveDisplayMode(
+            provider().registrar_unsafe().GetAppEffectiveDisplayMode(
                 GenerateAppIdFromUnhashed(unhashed_sub_app_id)));
 
   GURL kSubAppWithMinialUiUrl = GetURL(kSubAppPathMinimalUi);
@@ -689,7 +692,7 @@
                     SubAppsServiceAddResultCode::kSuccessAlreadyInstalled),
       CallAdd({{unhashed_sub_app_id, kSubAppWithMinialUiUrl}}));
   EXPECT_EQ(DisplayMode::kStandalone,
-            provider().registrar().GetAppEffectiveDisplayMode(
+            provider().registrar_unsafe().GetAppEffectiveDisplayMode(
                 GenerateAppIdFromUnhashed(unhashed_sub_app_id)));
 }
 
@@ -716,12 +719,12 @@
                           SubAppsServiceAddResultCode::kSuccessNewInstall),
             CallAdd({{unhashed_sub_app_id, GetURL(kSubAppPath)}}));
 
-  EXPECT_TRUE(provider().registrar().IsInstalled(
+  EXPECT_TRUE(provider().registrar_unsafe().IsInstalled(
       GenerateAppIdFromUnhashed(unhashed_sub_app_id)));
 
   // Add standalone app as sub-app.
   const WebApp* standalone_app =
-      provider().registrar().GetAppById(standalone_app_id);
+      provider().registrar_unsafe().GetAppById(standalone_app_id);
   EXPECT_EQ(AddResultMojo(unhashed_standalone_app_id,
                           SubAppsServiceAddResultCode::kSuccessNewInstall),
             CallAdd({{unhashed_standalone_app_id, GetURL(kSubAppPath2)}}));
@@ -735,11 +738,11 @@
   UninstallParentApp();
 
   // Verify that normal sub-app is uninstalled.
-  EXPECT_FALSE(provider().registrar().IsInstalled(
+  EXPECT_FALSE(provider().registrar_unsafe().IsInstalled(
       GenerateAppIdFromUnhashed(unhashed_sub_app_id)));
 
   // Verify that previous standalone is still installed.
-  EXPECT_TRUE(provider().registrar().IsInstalled(standalone_app_id));
+  EXPECT_TRUE(provider().registrar_unsafe().IsInstalled(standalone_app_id));
 
   // Verify that there are no apps registered as parent app's sub apps.
   EXPECT_EQ(0ul, GetAllSubAppIds(parent_app_id_).size());
@@ -853,11 +856,11 @@
                           SubAppsServiceAddResultCode::kSuccessNewInstall),
             CallAdd({{unhashed_app_id, GetURL(kSubAppPath)}}));
   EXPECT_EQ(1ul, GetAllSubAppIds(parent_app_id_).size());
-  EXPECT_TRUE(provider().registrar().IsInstalled(app_id));
+  EXPECT_TRUE(provider().registrar_unsafe().IsInstalled(app_id));
 
   EXPECT_EQ(SubAppsServiceResult::kSuccess, CallRemove(unhashed_app_id));
   EXPECT_EQ(0ul, GetAllSubAppIds(parent_app_id_).size());
-  EXPECT_FALSE(provider().registrar().IsInstalled(app_id));
+  EXPECT_FALSE(provider().registrar_unsafe().IsInstalled(app_id));
 }
 
 // Remove fails for a regular installed app.
diff --git a/chrome/browser/ui/web_applications/tabbed_web_app_navigation_throttle.cc b/chrome/browser/ui/web_applications/tabbed_web_app_navigation_throttle.cc
index c58ac46..ffbec66 100644
--- a/chrome/browser/ui/web_applications/tabbed_web_app_navigation_throttle.cc
+++ b/chrome/browser/ui/web_applications/tabbed_web_app_navigation_throttle.cc
@@ -56,13 +56,13 @@
   const AppId& app_id = browser->app_controller()->app_id();
 
   absl::optional<GURL> home_tab_url =
-      provider->registrar().GetAppPinnedHomeTabUrl(app_id);
+      provider->registrar_unsafe().GetAppPinnedHomeTabUrl(app_id);
 
   auto* tab_helper = WebAppTabHelper::FromWebContents(web_contents);
 
   // Only create the throttle for tabbed web apps that have a home tab.
   if (tab_helper && tab_helper->acting_as_app() &&
-      provider->registrar().IsTabbedWindowModeEnabled(app_id) &&
+      provider->registrar_unsafe().IsTabbedWindowModeEnabled(app_id) &&
       home_tab_url.has_value()) {
     return std::make_unique<TabbedWebAppNavigationThrottle>(handle);
   }
@@ -85,14 +85,14 @@
   const AppId& app_id = app_controller->app_id();
 
   absl::optional<GURL> home_tab_url =
-      provider->registrar().GetAppPinnedHomeTabUrl(app_id);
+      provider->registrar_unsafe().GetAppPinnedHomeTabUrl(app_id);
   DCHECK(home_tab_url.has_value());
 
   auto* tab_helper = WebAppTabHelper::FromWebContents(web_contents);
   DCHECK(tab_helper);
   bool navigating_from_home_tab = tab_helper->is_pinned_home_tab();
   bool navigation_url_is_home_url = IsPinnedHomeTabUrl(
-      provider->registrar(), app_id, navigation_handle()->GetURL());
+      provider->registrar_unsafe(), app_id, navigation_handle()->GetURL());
 
   // Navigations from the home tab to another URL should open in a new tab.
   if (navigating_from_home_tab && !navigation_url_is_home_url) {
diff --git a/chrome/browser/ui/web_applications/test/isolated_web_app_test_utils.cc b/chrome/browser/ui/web_applications/test/isolated_web_app_test_utils.cc
index 3c4482c..c2a4b45 100644
--- a/chrome/browser/ui/web_applications/test/isolated_web_app_test_utils.cc
+++ b/chrome/browser/ui/web_applications/test/isolated_web_app_test_utils.cc
@@ -144,7 +144,7 @@
 content::RenderFrameHost* IsolatedWebAppBrowserTestHarness::OpenApp(
     const AppId& app_id) {
   WebAppRegistrar& registrar =
-      WebAppProvider::GetForWebApps(profile())->registrar();
+      WebAppProvider::GetForWebApps(profile())->registrar_unsafe();
   const WebApp* app = registrar.GetAppById(app_id);
   EXPECT_TRUE(app);
   Browser* app_window = Browser::Create(Browser::CreateParams::CreateForApp(
diff --git a/chrome/browser/ui/web_applications/test/web_app_browsertest_util.cc b/chrome/browser/ui/web_applications/test/web_app_browsertest_util.cc
index cae4de2..5ad0249 100644
--- a/chrome/browser/ui/web_applications/test/web_app_browsertest_util.cc
+++ b/chrome/browser/ui/web_applications/test/web_app_browsertest_util.cc
@@ -237,7 +237,8 @@
                                     const AppId& app_id,
                                     WindowOpenDisposition disposition) {
   ui_test_utils::UrlLoadObserver url_observer(
-      WebAppProvider::GetForTest(profile)->registrar().GetAppLaunchUrl(app_id),
+      WebAppProvider::GetForTest(profile)->registrar_unsafe().GetAppLaunchUrl(
+          app_id),
       content::NotificationService::AllSources());
   Browser* const app_browser =
       LaunchWebAppBrowser(profile, app_id, disposition);
diff --git a/chrome/browser/ui/web_applications/test/web_app_navigation_browsertest.cc b/chrome/browser/ui/web_applications/test/web_app_navigation_browsertest.cc
index 7d6ec0d..f166e46d 100644
--- a/chrome/browser/ui/web_applications/test/web_app_navigation_browsertest.cc
+++ b/chrome/browser/ui/web_applications/test/web_app_navigation_browsertest.cc
@@ -235,7 +235,7 @@
 void WebAppNavigationBrowserTest::TearDownOnMainThread() {
 #if BUILDFLAG(IS_CHROMEOS)
   auto* const provider = WebAppProvider::GetForWebApps(profile());
-  const WebAppRegistrar& registrar = provider->registrar();
+  const WebAppRegistrar& registrar = provider->registrar_unsafe();
   std::vector<AppId> app_ids = registrar.GetAppIds();
   for (const auto& app_id : app_ids) {
     if (!registrar.IsInstalled(app_id)) {
@@ -332,7 +332,7 @@
 
 const GURL& WebAppNavigationBrowserTest::test_web_app_start_url() {
   auto* const provider = WebAppProvider::GetForWebApps(profile());
-  const WebAppRegistrar& registrar = provider->registrar();
+  const WebAppRegistrar& registrar = provider->registrar_unsafe();
   return registrar.GetAppStartUrl(test_web_app_);
 }
 
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 70e7842..4d55c61 100644
--- a/chrome/browser/ui/web_applications/web_app_badging_browsertest.cc
+++ b/chrome/browser/ui/web_applications/web_app_badging_browsertest.cc
@@ -123,7 +123,7 @@
 
   // WebAppControllerBrowserTest:
   void TearDownOnMainThread() override {
-    WebAppRegistrar& registrar = provider().registrar();
+    WebAppRegistrar& registrar = provider().registrar_unsafe();
     for (const auto& app_id : registrar.GetAppIds()) {
       web_app::test::UninstallWebApp(profile(), app_id);
       AppReadinessWaiter(profile(), app_id, apps::Readiness::kUninstalledByUser)
@@ -514,7 +514,7 @@
 
 IN_PROC_BROWSER_TEST_F(WebAppBadgingBrowserTest, ClearLastBadgingTime) {
   ExecuteScriptAndWaitForBadgeChange("navigator.setAppBadge()", main_frame_);
-  WebAppRegistrar& registrar = provider().registrar();
+  WebAppRegistrar& registrar = provider().registrar_unsafe();
   EXPECT_NE(registrar.GetAppLastBadgingTime(main_app_id()), base::Time());
   EXPECT_NE(registrar.GetAppLastLaunchTime(main_app_id()), base::Time());
 
diff --git a/chrome/browser/ui/web_applications/web_app_browser_controller.cc b/chrome/browser/ui/web_applications/web_app_browser_controller.cc
index ed7be44..097b9903 100644
--- a/chrome/browser/ui/web_applications/web_app_browser_controller.cc
+++ b/chrome/browser/ui/web_applications/web_app_browser_controller.cc
@@ -459,8 +459,8 @@
 
   std::u16string raw_title = AppBrowserController::GetTitle();
 
-  std::u16string app_name =
-      base::UTF8ToUTF16(provider_->registrar().GetAppShortName(app_id()));
+  std::u16string app_name = base::UTF8ToUTF16(
+      provider_->registrar_unsafe().GetAppShortName(app_id()));
   if (base::StartsWith(raw_title, app_name)) {
     return raw_title;
   }
@@ -521,7 +521,7 @@
 }
 
 const WebAppRegistrar& WebAppBrowserController::registrar() const {
-  return provider_->registrar();
+  return provider_->registrar_unsafe();
 }
 
 const WebAppInstallManager& WebAppBrowserController::install_manager() const {
diff --git a/chrome/browser/ui/web_applications/web_app_browsertest.cc b/chrome/browser/ui/web_applications/web_app_browsertest.cc
index 089cdc2..e2ac588b 100644
--- a/chrome/browser/ui/web_applications/web_app_browsertest.cc
+++ b/chrome/browser/ui/web_applications/web_app_browsertest.cc
@@ -333,7 +333,8 @@
   AppId app_id = InstallWebApp(std::move(web_app_info));
 
   auto* provider = WebAppProvider::GetForTest(profile());
-  EXPECT_EQ(provider->registrar().GetAppBackgroundColor(app_id), SK_ColorBLUE);
+  EXPECT_EQ(provider->registrar_unsafe().GetAppBackgroundColor(app_id),
+            SK_ColorBLUE);
 }
 
 IN_PROC_BROWSER_TEST_F(WebAppBrowserTest, ShortcutBackgroundColor) {
@@ -341,7 +342,8 @@
   const AppId app_id = InstallWebAppFromPage(browser(), app_url);
   auto* provider = WebAppProvider::GetForTest(profile());
 
-  EXPECT_EQ(provider->registrar().GetAppBackgroundColor(app_id), SK_ColorBLUE);
+  EXPECT_EQ(provider->registrar_unsafe().GetAppBackgroundColor(app_id),
+            SK_ColorBLUE);
 }
 
 IN_PROC_BROWSER_TEST_F(WebAppBrowserTest, ManifestWithColor) {
@@ -350,9 +352,10 @@
   const AppId app_id = InstallWebAppFromPage(browser(), app_url);
   auto* provider = WebAppProvider::GetForTest(profile());
 
-  EXPECT_EQ(provider->registrar().GetAppBackgroundColor(app_id),
+  EXPECT_EQ(provider->registrar_unsafe().GetAppBackgroundColor(app_id),
             SK_ColorYELLOW);
-  EXPECT_EQ(provider->registrar().GetAppThemeColor(app_id), SK_ColorGREEN);
+  EXPECT_EQ(provider->registrar_unsafe().GetAppThemeColor(app_id),
+            SK_ColorGREEN);
 }
 
 // Also see BackgroundColorChangeSystemWebAppBrowserTest.BackgroundColorChange
@@ -619,12 +622,13 @@
   auto* provider = WebAppProvider::GetForTest(profile());
 
   // last_launch_time is not set before launch
-  EXPECT_TRUE(provider->registrar().GetAppLastLaunchTime(app_id).is_null());
+  EXPECT_TRUE(
+      provider->registrar_unsafe().GetAppLastLaunchTime(app_id).is_null());
 
   auto before_launch = base::Time::Now();
   LaunchWebAppBrowser(app_id);
 
-  EXPECT_TRUE(provider->registrar().GetAppLastLaunchTime(app_id) >=
+  EXPECT_TRUE(provider->registrar_unsafe().GetAppLastLaunchTime(app_id) >=
               before_launch);
 }
 
@@ -662,7 +666,7 @@
   auto* provider = WebAppProvider::GetForTest(profile());
 
   std::vector<DisplayMode> app_display_mode_override =
-      provider->registrar().GetAppDisplayModeOverride(app_id);
+      provider->registrar_unsafe().GetAppDisplayModeOverride(app_id);
 
   ASSERT_EQ(2u, app_display_mode_override.size());
   EXPECT_EQ(DisplayMode::kMinimalUi, app_display_mode_override[0]);
@@ -1064,15 +1068,15 @@
 
   const AppId app_id = test::InstallPwaForCurrentUrl(browser());
   auto* provider = WebAppProvider::GetForTest(profile());
-  EXPECT_EQ(provider->registrar().GetAppShortName(app_id),
+  EXPECT_EQ(provider->registrar_unsafe().GetAppShortName(app_id),
             GetInstallableAppName());
 
   // Installed PWAs should launch in their own window.
-  EXPECT_EQ(provider->registrar().GetAppUserDisplayMode(app_id),
+  EXPECT_EQ(provider->registrar_unsafe().GetAppUserDisplayMode(app_id),
             web_app::UserDisplayMode::kStandalone);
 
   // Installed PWAs should have install time set.
-  EXPECT_TRUE(provider->registrar().GetAppInstallTime(app_id) >=
+  EXPECT_TRUE(provider->registrar_unsafe().GetAppInstallTime(app_id) >=
               before_install_time);
 
   EXPECT_EQ(1, user_action_tester.GetActionCount("InstallWebAppFromMenu"));
@@ -1248,7 +1252,8 @@
   ExternallyManagedAppManagerInstall(profile(), options);
 
   auto* provider = WebAppProvider::GetForTest(browser()->profile());
-  AppId app_id = provider->registrar().LookupExternalAppId(install_url).value();
+  AppId app_id =
+      provider->registrar_unsafe().LookupExternalAppId(install_url).value();
 
   EXPECT_FALSE(provider->install_finalizer().CanUserUninstallWebApp(app_id));
 
@@ -1257,7 +1262,7 @@
   // Performing a user install on the page should not override the "policy"
   // install source.
   EXPECT_FALSE(provider->install_finalizer().CanUserUninstallWebApp(app_id));
-  const WebApp& web_app = *provider->registrar().GetAppById(app_id);
+  const WebApp& web_app = *provider->registrar_unsafe().GetAppById(app_id);
   EXPECT_TRUE(web_app.IsSynced());
   EXPECT_TRUE(web_app.IsPolicyInstalledApp());
 }
@@ -1307,11 +1312,11 @@
 #if BUILDFLAG(IS_MAC)
   shortcut_path =
       registration->shortcut_override->chrome_apps_folder.GetPath().Append(
-          provider->registrar().GetAppShortName(app_id) + ".app");
+          provider->registrar_unsafe().GetAppShortName(app_id) + ".app");
 #elif BUILDFLAG(IS_WIN)
   shortcut_path =
       registration->shortcut_override->application_menu.GetPath().AppendASCII(
-          provider->registrar().GetAppShortName(app_id) + ".lnk");
+          provider->registrar_unsafe().GetAppShortName(app_id) + ".lnk");
   expected_pixel_colors.push_back(SkColorSetRGB(91, 91, 91));
   expected_pixel_colors.push_back(SkColorSetRGB(90, 90, 90));
 #endif
@@ -1514,14 +1519,14 @@
   run_loop_install.Run();
   app_loaded_observer.Wait();
 
-  EXPECT_TRUE(provider->registrar().IsInstalled(app_id));
-  EXPECT_EQ(provider->registrar().GetAppShortName(app_id),
+  EXPECT_TRUE(provider->registrar_unsafe().IsInstalled(app_id));
+  EXPECT_EQ(provider->registrar_unsafe().GetAppShortName(app_id),
             GetInstallableAppName());
 
 #if BUILDFLAG(IS_WIN)
   std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
   std::wstring shortcut_filename = converter.from_bytes(
-      provider->registrar().GetAppShortName(app_id) + ".lnk");
+      provider->registrar_unsafe().GetAppShortName(app_id) + ".lnk");
   base::FilePath desktop_shortcut_path =
       registration->shortcut_override->desktop.GetPath().Append(
           shortcut_filename);
@@ -1532,7 +1537,7 @@
   EXPECT_TRUE(base::PathExists(app_menu_shortcut_path));
 #elif BUILDFLAG(IS_MAC)
   std::string shortcut_filename =
-      provider->registrar().GetAppShortName(app_id) + ".app";
+      provider->registrar_unsafe().GetAppShortName(app_id) + ".app";
   base::FilePath app_shortcut_path =
       registration->shortcut_override->chrome_apps_folder.GetPath().Append(
           shortcut_filename);
@@ -1606,7 +1611,7 @@
       /*use_fallback=*/false);
 
   const AppId& app_id = install_future.Get<0>();
-  EXPECT_EQ(provider->registrar().GetAppShortName(app_id),
+  EXPECT_EQ(provider->registrar_unsafe().GetAppShortName(app_id),
             GetInstallableAppName());
 
   {
@@ -1667,11 +1672,12 @@
   EXPECT_TRUE(app_browser->app_controller()->HasMinimalUiButtons());
 
   auto* provider = WebAppProvider::GetForTest(profile());
-  EXPECT_EQ(provider->registrar().GetAppUserDisplayMode(app_id),
+  EXPECT_EQ(provider->registrar_unsafe().GetAppUserDisplayMode(app_id),
             UserDisplayMode::kStandalone);
-  EXPECT_EQ(provider->registrar().GetAppEffectiveDisplayMode(app_id),
+  EXPECT_EQ(provider->registrar_unsafe().GetAppEffectiveDisplayMode(app_id),
             DisplayMode::kMinimalUi);
-  EXPECT_FALSE(provider->registrar().GetAppLastLaunchTime(app_id).is_null());
+  EXPECT_FALSE(
+      provider->registrar_unsafe().GetAppLastLaunchTime(app_id).is_null());
   tester.ExpectUniqueSample("Extensions.BookmarkAppLaunchContainer",
                             apps::LaunchContainer::kLaunchContainerWindow, 1);
   tester.ExpectUniqueSample("Extensions.BookmarkAppLaunchSource",
@@ -1969,7 +1975,7 @@
   auto* provider = WebAppProvider::GetForTest(profile());
 
   std::vector<DisplayMode> app_display_mode_override =
-      provider->registrar().GetAppDisplayModeOverride(app_id);
+      provider->registrar_unsafe().GetAppDisplayModeOverride(app_id);
 
   ASSERT_EQ(1u, app_display_mode_override.size());
   EXPECT_EQ(DisplayMode::kWindowControlsOverlay, app_display_mode_override[0]);
@@ -1990,7 +1996,7 @@
   auto* provider = WebAppProvider::GetForTest(profile());
 
   std::vector<DisplayMode> app_display_mode_override =
-      provider->registrar().GetAppDisplayModeOverride(app_id);
+      provider->registrar_unsafe().GetAppDisplayModeOverride(app_id);
 
   ASSERT_EQ(1u, app_display_mode_override.size());
   EXPECT_EQ(DisplayMode::kBorderless, app_display_mode_override[0]);
@@ -2010,11 +2016,12 @@
   auto* provider = WebAppProvider::GetForTest(profile());
 
   std::vector<DisplayMode> app_display_mode_override =
-      provider->registrar().GetAppDisplayModeOverride(app_id);
+      provider->registrar_unsafe().GetAppDisplayModeOverride(app_id);
 
   ASSERT_EQ(1u, app_display_mode_override.size());
   EXPECT_EQ(DisplayMode::kTabbed, app_display_mode_override[0]);
-  EXPECT_EQ(true, provider->registrar().IsTabbedWindowModeEnabled(app_id));
+  EXPECT_EQ(true,
+            provider->registrar_unsafe().IsTabbedWindowModeEnabled(app_id));
 }
 
 IN_PROC_BROWSER_TEST_F(WebAppBrowserTest, DISABLE_POSIX(RemoveStatusBar)) {
@@ -2065,12 +2072,12 @@
 
   const AppId app_id = test::InstallPwaForCurrentUrl(browser());
   auto* provider = WebAppProvider::GetForTest(profile());
-  auto* app = provider->registrar().GetAppById(app_id);
+  auto* app = provider->registrar_unsafe().GetAppById(app_id);
 
-  EXPECT_EQ(
-      web_app::GenerateAppId(/*manifest_id=*/absl::nullopt,
-                             provider->registrar().GetAppStartUrl(app_id)),
-      app_id);
+  EXPECT_EQ(web_app::GenerateAppId(
+                /*manifest_id=*/absl::nullopt,
+                provider->registrar_unsafe().GetAppStartUrl(app_id)),
+            app_id);
   EXPECT_EQ(app->start_url().spec().substr(
                 app->start_url().DeprecatedGetOriginAsURL().spec().size()),
             app->manifest_id());
@@ -2084,7 +2091,7 @@
 
   const AppId app_id = test::InstallPwaForCurrentUrl(browser());
   auto* provider = WebAppProvider::GetForTest(profile());
-  auto* app = provider->registrar().GetAppById(app_id);
+  auto* app = provider->registrar_unsafe().GetAppById(app_id);
 
   EXPECT_EQ(web_app::GenerateAppId(app->manifest_id(), app->start_url()),
             app_id);
@@ -2303,8 +2310,8 @@
   const AppId app_id = test::InstallPwaForCurrentUrl(browser());
   run_loop_install.Run();
 
-  EXPECT_TRUE(provider->registrar().IsInstalled(app_id));
-  EXPECT_EQ(provider->registrar().GetAppShortName(app_id),
+  EXPECT_TRUE(provider->registrar_unsafe().IsInstalled(app_id));
+  EXPECT_EQ(provider->registrar_unsafe().GetAppShortName(app_id),
             GetInstallableAppName());
   // This does NOT uninstall the web app, it just flags it for uninstall on
   // startup.
@@ -2326,7 +2333,8 @@
   // The test body here handles both cases, and ensures that the app has been
   // uninstalled.
   std::set<AppId> apps;
-  for (const auto& web_app : provider->registrar().GetAppsIncludingStubs()) {
+  for (const auto& web_app :
+       provider->registrar_unsafe().GetAppsIncludingStubs()) {
     EXPECT_TRUE(web_app.is_uninstalling());
     apps.insert(web_app.app_id());
   }
@@ -2338,7 +2346,7 @@
   // TODO(dmurph): Remove AppSet, it's too hard to use.
   int app_count = 0;
   const web_app::WebAppRegistrar::AppSet& app_set =
-      provider->registrar().GetAppsIncludingStubs();
+      provider->registrar_unsafe().GetAppsIncludingStubs();
   for (auto it = app_set.begin(); it != app_set.end(); ++it) {
     ++app_count;
   }
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 a40b820..f1c83375 100644
--- a/chrome/browser/ui/web_applications/web_app_controller_browsertest.cc
+++ b/chrome/browser/ui/web_applications/web_app_controller_browsertest.cc
@@ -164,13 +164,13 @@
 
 absl::optional<AppId> WebAppControllerBrowserTest::FindAppWithUrlInScope(
     const GURL& url) {
-  return provider().registrar().FindAppWithUrlInScope(url);
+  return provider().registrar_unsafe().FindAppWithUrlInScope(url);
 }
 
 content::WebContents* WebAppControllerBrowserTest::OpenApplication(
     const AppId& app_id) {
   ui_test_utils::UrlLoadObserver url_observer(
-      provider().registrar().GetAppStartUrl(app_id),
+      provider().registrar_unsafe().GetAppStartUrl(app_id),
       content::NotificationService::AllSources());
 
   apps::AppLaunchParams params(
diff --git a/chrome/browser/ui/web_applications/web_app_dark_mode_browsertest.cc b/chrome/browser/ui/web_applications/web_app_dark_mode_browsertest.cc
index 3f7fa2fc..388f84ca 100644
--- a/chrome/browser/ui/web_applications/web_app_dark_mode_browsertest.cc
+++ b/chrome/browser/ui/web_applications/web_app_dark_mode_browsertest.cc
@@ -94,16 +94,19 @@
       "Blink.UseCounter.Features",
       blink::mojom::WebFeature::kWebAppManifestUserPreferences, 1);
 
-  EXPECT_EQ(provider().registrar().GetAppThemeColor(app_id).value(),
+  EXPECT_EQ(provider().registrar_unsafe().GetAppThemeColor(app_id).value(),
             SK_ColorBLUE);
-  EXPECT_EQ(provider().registrar().GetAppBackgroundColor(app_id).value(),
+  EXPECT_EQ(provider().registrar_unsafe().GetAppBackgroundColor(app_id).value(),
             SK_ColorBLUE);
 
-  EXPECT_EQ(provider().registrar().GetAppDarkModeThemeColor(app_id).value(),
-            SK_ColorRED);
   EXPECT_EQ(
-      provider().registrar().GetAppDarkModeBackgroundColor(app_id).value(),
+      provider().registrar_unsafe().GetAppDarkModeThemeColor(app_id).value(),
       SK_ColorRED);
+  EXPECT_EQ(provider()
+                .registrar_unsafe()
+                .GetAppDarkModeBackgroundColor(app_id)
+                .value(),
+            SK_ColorRED);
 }
 
 IN_PROC_BROWSER_TEST_F(WebAppDarkModeBrowserTest, NoUserPreferences) {
@@ -222,11 +225,13 @@
 
   // Origin trial should grant the app access.
   WebAppProvider& provider = *WebAppProvider::GetForTest(browser()->profile());
-  EXPECT_EQ(provider.registrar().GetAppById(app_id)->dark_mode_theme_color(),
-            SK_ColorRED);
   EXPECT_EQ(
-      provider.registrar().GetAppById(app_id)->dark_mode_background_color(),
+      provider.registrar_unsafe().GetAppById(app_id)->dark_mode_theme_color(),
       SK_ColorRED);
+  EXPECT_EQ(provider.registrar_unsafe()
+                .GetAppById(app_id)
+                ->dark_mode_background_color(),
+            SK_ColorRED);
 
   // Open the page again with the token missing.
   {
@@ -240,11 +245,13 @@
 
   // The app should update to no longer have dark mode colors defined without
   // the origin trial.
-  EXPECT_EQ(provider.registrar().GetAppById(app_id)->dark_mode_theme_color(),
-            absl::nullopt);
   EXPECT_EQ(
-      provider.registrar().GetAppById(app_id)->dark_mode_background_color(),
+      provider.registrar_unsafe().GetAppById(app_id)->dark_mode_theme_color(),
       absl::nullopt);
+  EXPECT_EQ(provider.registrar_unsafe()
+                .GetAppById(app_id)
+                ->dark_mode_background_color(),
+            absl::nullopt);
 }
 
 }  // namespace web_app
diff --git a/chrome/browser/ui/web_applications/web_app_file_handling_browsertest.cc b/chrome/browser/ui/web_applications/web_app_file_handling_browsertest.cc
index 687e2d1..2f30827 100644
--- a/chrome/browser/ui/web_applications/web_app_file_handling_browsertest.cc
+++ b/chrome/browser/ui/web_applications/web_app_file_handling_browsertest.cc
@@ -72,7 +72,7 @@
     return provider()->os_integration_manager().file_handler_manager();
   }
 
-  WebAppRegistrar& registrar() { return provider()->registrar(); }
+  WebAppRegistrar& registrar() { return provider()->registrar_unsafe(); }
 
   GURL GetSecureAppURL() {
     return https_server()->GetURL("app.com", "/ssl/google.html");
@@ -555,7 +555,7 @@
       embedded_test_server()->GetURL("/web_app_file_handling/icons_app.html"));
   const AppId app_id = InstallWebAppFromManifest(browser(), app_url);
   auto* provider = WebAppProvider::GetForTest(browser()->profile());
-  const WebApp* web_app = provider->registrar().GetAppById(app_id);
+  const WebApp* web_app = provider->registrar_unsafe().GetAppById(app_id);
   ASSERT_TRUE(web_app);
 
   ASSERT_EQ(1U, web_app->file_handlers().size());
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 c902fe9..3d7fe27 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
@@ -84,7 +84,7 @@
   }
 
   const WebApp* GetWebApp(const AppId& app_id) {
-    return WebAppProvider::GetForTest(profile())->registrar().GetAppById(
+    return WebAppProvider::GetForTest(profile())->registrar_unsafe().GetAppById(
         app_id);
   }
 
@@ -620,7 +620,7 @@
 
   // Origin trial should grant the app access.
   WebAppProvider& provider = *WebAppProvider::GetForTest(browser()->profile());
-  EXPECT_EQ(provider.registrar().GetAppById(app_id)->launch_handler(),
+  EXPECT_EQ(provider.registrar_unsafe().GetAppById(app_id)->launch_handler(),
             (LaunchHandler{ClientMode::kFocusExisting}));
 
   // Open the page again with the token missing.
@@ -635,7 +635,7 @@
 
   // The app should update to no longer have launch_handler defined without the
   // origin trial.
-  EXPECT_EQ(provider.registrar().GetAppById(app_id)->launch_handler(),
+  EXPECT_EQ(provider.registrar_unsafe().GetAppById(app_id)->launch_handler(),
             absl::nullopt);
 }
 
diff --git a/chrome/browser/ui/web_applications/web_app_launch_manager.cc b/chrome/browser/ui/web_applications/web_app_launch_manager.cc
index 2bf9c8f..b092016c 100644
--- a/chrome/browser/ui/web_applications/web_app_launch_manager.cc
+++ b/chrome/browser/ui/web_applications/web_app_launch_manager.cc
@@ -34,17 +34,6 @@
 
 namespace web_app {
 
-namespace {
-
-WebAppLaunchManager::OpenApplicationCallback&
-GetOpenApplicationCallbackForTesting() {
-  static base::NoDestructor<WebAppLaunchManager::OpenApplicationCallback>
-      callback;
-  return *callback;
-}
-
-}  // namespace
-
 WebAppLaunchManager::WebAppLaunchManager(Profile* profile)
     : profile_(profile),
       provider_(WebAppProvider::GetForLocalAppsUnchecked(profile)) {}
@@ -59,9 +48,9 @@
   WebAppProvider* provider =
       WebAppProvider::GetForLocalAppsUnchecked(profile_.get());
   DCHECK(provider);
-  return WebAppLaunchProcess::CreateAndRun(*profile_, provider->registrar(),
-                                           provider->os_integration_manager(),
-                                           params);
+  return WebAppLaunchProcess::CreateAndRun(
+      *profile_, provider->registrar_unsafe(),
+      provider->os_integration_manager(), params);
 }
 
 void WebAppLaunchManager::LaunchApplication(
@@ -128,15 +117,22 @@
   GetOpenApplicationCallbackForTesting() = std::move(callback);
 }
 
+// static
+WebAppLaunchManager::OpenApplicationCallback&
+WebAppLaunchManager::GetOpenApplicationCallbackForTesting() {
+  static base::NoDestructor<WebAppLaunchManager::OpenApplicationCallback>
+      callback;
+  return *callback;
+}
 void WebAppLaunchManager::LaunchWebApplication(
     apps::AppLaunchParams&& params,
     base::OnceCallback<void(Browser* browser, apps::LaunchContainer container)>
         callback) {
   apps::LaunchContainer container;
   Browser* browser = nullptr;
-  if (provider_->registrar().IsInstalled(params.app_id)) {
-    if (provider_->registrar().GetAppEffectiveDisplayMode(params.app_id) ==
-        blink::mojom::DisplayMode::kBrowser) {
+  if (provider_->registrar_unsafe().IsInstalled(params.app_id)) {
+    if (provider_->registrar_unsafe().GetAppEffectiveDisplayMode(
+            params.app_id) == blink::mojom::DisplayMode::kBrowser) {
       params.container = apps::LaunchContainer::kLaunchContainerTab;
       params.disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB;
     }
diff --git a/chrome/browser/ui/web_applications/web_app_launch_manager.h b/chrome/browser/ui/web_applications/web_app_launch_manager.h
index e612bcf..58e49abb8 100644
--- a/chrome/browser/ui/web_applications/web_app_launch_manager.h
+++ b/chrome/browser/ui/web_applications/web_app_launch_manager.h
@@ -61,6 +61,9 @@
   static void SetOpenApplicationCallbackForTesting(
       OpenApplicationCallback callback);
 
+  // Created temporarily while this class is migrated to the command system.
+  static OpenApplicationCallback& GetOpenApplicationCallbackForTesting();
+
  private:
   virtual void LaunchWebApplication(
       apps::AppLaunchParams&& params,
diff --git a/chrome/browser/ui/web_applications/web_app_launch_utils.cc b/chrome/browser/ui/web_applications/web_app_launch_utils.cc
index b94fde0..a35d5195 100644
--- a/chrome/browser/ui/web_applications/web_app_launch_utils.cc
+++ b/chrome/browser/ui/web_applications/web_app_launch_utils.cc
@@ -193,7 +193,7 @@
   const bool has_tab_strip =
       !browser->is_type_app_popup() &&
       (should_have_tab_strip_for_swa ||
-       provider->registrar().IsTabbedWindowModeEnabled(app_id));
+       provider->registrar_unsafe().IsTabbedWindowModeEnabled(app_id));
   return std::make_unique<WebAppBrowserController>(*provider, browser, app_id,
 #if BUILDFLAG(IS_CHROMEOS_ASH)
                                                    system_app,
@@ -227,7 +227,7 @@
   if (!web_contents)
     return absl::nullopt;
 
-  return provider->registrar().FindInstalledAppWithUrlInScope(
+  return provider->registrar_unsafe().FindInstalledAppWithUrlInScope(
       web_contents->GetPrimaryMainFrame()->GetLastCommittedURL());
 }
 
@@ -271,7 +271,7 @@
   // disabled if the previous page was outside scope. Packaged apps are not
   // affected.
   WebAppProvider* provider = WebAppProvider::GetForWebApps(profile);
-  WebAppRegistrar& registrar = provider->registrar();
+  WebAppRegistrar& registrar = provider->registrar_unsafe();
   const WebApp* web_app = registrar.GetAppById(app_id);
   if (!web_app)
     return nullptr;
@@ -360,7 +360,7 @@
   const AppId app_id = GetAppIdFromApplicationName(browser->app_name());
   auto* const provider =
       WebAppProvider::GetForLocalAppsUnchecked(browser->profile());
-  if (provider && provider->registrar().IsInstalled(app_id)) {
+  if (provider && provider->registrar_unsafe().IsInstalled(app_id)) {
 #if BUILDFLAG(IS_CHROMEOS)
     if (profiles::IsKioskSession() &&
         base::FeatureList::IsEnabled(features::kKioskEnableAppService)) {
@@ -381,7 +381,8 @@
 
 void MaybeAddPinnedHomeTab(Browser* browser, const std::string& app_id) {
   WebAppRegistrar& registrar =
-      WebAppProvider::GetForLocalAppsUnchecked(browser->profile())->registrar();
+      WebAppProvider::GetForLocalAppsUnchecked(browser->profile())
+          ->registrar_unsafe();
   absl::optional<GURL> pinned_home_tab_url =
       registrar.GetAppPinnedHomeTabUrl(app_id);
 
@@ -447,7 +448,7 @@
                                                 NavigateParams& nav_params) {
   WebAppRegistrar& registrar =
       WebAppProvider::GetForLocalAppsUnchecked(nav_params.browser->profile())
-          ->registrar();
+          ->registrar_unsafe();
 
   if (IsPinnedHomeTabUrl(registrar, app_id, nav_params.url)) {
     // Navigations to the home tab URL in tabbed apps should happen in the home
@@ -526,12 +527,12 @@
   if (!provider)
     return;
 
-  const WebApp* web_app = provider->registrar().GetAppById(app_id);
+  const WebApp* web_app = provider->registrar_unsafe().GetAppById(app_id);
   if (!web_app)
     return;
 
   DisplayMode display =
-      provider->registrar().GetEffectiveDisplayModeFromManifest(app_id);
+      provider->registrar_unsafe().GetEffectiveDisplayModeFromManifest(app_id);
   if (display != DisplayMode::kUndefined) {
     DCHECK_LT(DisplayMode::kUndefined, display);
     DCHECK_LE(display, DisplayMode::kMaxValue);
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 ff65f93..478a91c6 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
@@ -152,7 +152,7 @@
   }
 
   absl::optional<LaunchHandler> GetLaunchHandler(const AppId& app_id) {
-    return provider().registrar().GetAppById(app_id)->launch_handler();
+    return provider().registrar_unsafe().GetAppById(app_id)->launch_handler();
   }
 
  protected:
diff --git a/chrome/browser/ui/web_applications/web_app_metrics.cc b/chrome/browser/ui/web_applications/web_app_metrics.cc
index ff94aea..c33cb8b9 100644
--- a/chrome/browser/ui/web_applications/web_app_metrics.cc
+++ b/chrome/browser/ui/web_applications/web_app_metrics.cc
@@ -145,7 +145,7 @@
   // No HostedAppBrowserController if app is running as a tab in common browser.
   const bool in_window = !!browser->app_controller();
   const bool user_installed = WebAppProvider::GetForLocalAppsUnchecked(profile_)
-                                  ->registrar()
+                                  ->registrar_unsafe()
                                   .WasInstalledByUser(*app_id);
 
   // Record all web apps:
@@ -319,7 +319,8 @@
 
   WebAppProvider* provider = WebAppProvider::GetForLocalAppsUnchecked(profile_);
 
-  num_user_installed_apps_ = provider->registrar().CountUserInstalledApps();
+  num_user_installed_apps_ =
+      provider->registrar_unsafe().CountUserInstalledApps();
   DCHECK_NE(kNumUserInstalledAppsNotCounted, num_user_installed_apps_);
   DCHECK_GE(num_user_installed_apps_, 0);
 }
@@ -340,16 +341,16 @@
   DailyInteraction features;
 
   const AppId* app_id = WebAppTabHelper::GetAppId(web_contents);
-  if (app_id && provider->registrar().IsLocallyInstalled(*app_id)) {
+  if (app_id && provider->registrar_unsafe().IsLocallyInstalled(*app_id)) {
     // App is installed
-    features.start_url = provider->registrar().GetAppStartUrl(*app_id);
+    features.start_url = provider->registrar_unsafe().GetAppStartUrl(*app_id);
     features.installed = true;
     auto install_source =
-        provider->registrar().GetAppInstallSourceForMetrics(*app_id);
+        provider->registrar_unsafe().GetAppInstallSourceForMetrics(*app_id);
     if (install_source)
       features.install_source = static_cast<int>(*install_source);
     DisplayMode display_mode =
-        provider->registrar().GetAppEffectiveDisplayMode(*app_id);
+        provider->registrar_unsafe().GetAppEffectiveDisplayMode(*app_id);
     features.effective_display_mode = static_cast<int>(display_mode);
     // AppBannerManager treats already-installed web-apps as non-promotable, so
     // include already-installed findings as promotable.
diff --git a/chrome/browser/ui/web_applications/web_app_profile_deletion_browsertest.cc b/chrome/browser/ui/web_applications/web_app_profile_deletion_browsertest.cc
index 45f0e41..ecce23d 100644
--- a/chrome/browser/ui/web_applications/web_app_profile_deletion_browsertest.cc
+++ b/chrome/browser/ui/web_applications/web_app_profile_deletion_browsertest.cc
@@ -27,7 +27,7 @@
   WebAppRegistrar& registrar() {
     auto* provider = WebAppProvider::GetForTest(profile());
     CHECK(provider);
-    return provider->registrar();
+    return provider->registrar_unsafe();
   }
 
   void ScheduleCurrentProfileForDeletion() {
diff --git a/chrome/browser/ui/web_applications/web_app_ui_manager_impl.cc b/chrome/browser/ui/web_applications/web_app_ui_manager_impl.cc
index a68088c4..0e435215 100644
--- a/chrome/browser/ui/web_applications/web_app_ui_manager_impl.cc
+++ b/chrome/browser/ui/web_applications/web_app_ui_manager_impl.cc
@@ -21,6 +21,7 @@
 #include "chrome/browser/ui/browser_list.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/ui/web_applications/app_browser_controller.h"
+#include "chrome/browser/ui/web_applications/commands/launch_web_app_command.h"
 #include "chrome/browser/ui/web_applications/web_app_dialog_manager.h"
 #include "chrome/browser/ui/web_applications/web_app_launch_utils.h"
 #include "chrome/browser/ui/web_applications/web_app_metrics.h"
@@ -432,6 +433,16 @@
       new_icon, web_contents, std::move(callback));
 }
 
+base::Value WebAppUiManagerImpl::LaunchWebApp(
+    apps::AppLaunchParams params,
+    LaunchWebAppWindowSetting launch_setting,
+    Profile& profile,
+    LaunchWebAppCallback callback,
+    AppLock& lock) {
+  return ::web_app::LaunchWebApp(std::move(params), launch_setting, profile,
+                                 std::move(callback), lock);
+}
+
 void WebAppUiManagerImpl::OnBrowserAdded(Browser* browser) {
   DCHECK(started_);
   if (!IsBrowserForInstalledApp(browser))
diff --git a/chrome/browser/ui/web_applications/web_app_ui_manager_impl.h b/chrome/browser/ui/web_applications/web_app_ui_manager_impl.h
index dea1440a..5800774 100644
--- a/chrome/browser/ui/web_applications/web_app_ui_manager_impl.h
+++ b/chrome/browser/ui/web_applications/web_app_ui_manager_impl.h
@@ -75,6 +75,12 @@
       content::WebContents* web_contents,
       web_app::AppIdentityDialogCallback callback) override;
 
+  base::Value LaunchWebApp(apps::AppLaunchParams params,
+                           LaunchWebAppWindowSetting launch_setting,
+                           Profile& profile,
+                           LaunchWebAppCallback callback,
+                           AppLock& lock) override;
+
   // BrowserListObserver:
   void OnBrowserAdded(Browser* browser) override;
   void OnBrowserRemoved(Browser* browser) override;
diff --git a/chrome/browser/ui/web_applications/web_app_ui_utils.cc b/chrome/browser/ui/web_applications/web_app_ui_utils.cc
index 54d95856..950b783 100644
--- a/chrome/browser/ui/web_applications/web_app_ui_utils.cc
+++ b/chrome/browser/ui/web_applications/web_app_ui_utils.cc
@@ -52,7 +52,7 @@
     return absl::nullopt;
 
   if (!WebAppProvider::GetForWebApps(browser->profile())
-           ->registrar()
+           ->registrar_unsafe()
            .IsInstalled(*app_id)) {
     return absl::nullopt;
   }
diff --git a/chrome/browser/ui/web_applications/web_app_uninstall_browsertest.cc b/chrome/browser/ui/web_applications/web_app_uninstall_browsertest.cc
index adeff5a..60a53c3 100644
--- a/chrome/browser/ui/web_applications/web_app_uninstall_browsertest.cc
+++ b/chrome/browser/ui/web_applications/web_app_uninstall_browsertest.cc
@@ -159,7 +159,7 @@
 
   // Trigger app uninstall without waiting for result.
   WebAppProvider* const provider = WebAppProvider::GetForTest(profile());
-  EXPECT_TRUE(provider->registrar().IsInstalled(app_id));
+  EXPECT_TRUE(provider->registrar_unsafe().IsInstalled(app_id));
   DCHECK(provider->install_finalizer().CanUserUninstallWebApp(app_id));
   provider->install_finalizer().UninstallWebApp(
       app_id, webapps::WebappUninstallSource::kAppMenu,
@@ -190,14 +190,14 @@
         EXPECT_FALSE(uninstall_delegate_called);
 
         // Validate that uninstalling flag is set
-        auto* app = provider->registrar().GetAppById(app_id);
+        auto* app = provider->registrar_unsafe().GetAppById(app_id);
         EXPECT_TRUE(app);
         EXPECT_TRUE(app->is_uninstalling());
         uninstall_delegate_called = true;
       }));
 
   run_loop.Run();
-  EXPECT_FALSE(provider->registrar().IsInstalled(app_id));
+  EXPECT_FALSE(provider->registrar_unsafe().IsInstalled(app_id));
 }
 
 IN_PROC_BROWSER_TEST_F(WebAppUninstallBrowserTest, PrefsRemovedAfterUninstall) {
diff --git a/chrome/browser/ui/web_applications/web_app_url_handling_browsertest.cc b/chrome/browser/ui/web_applications/web_app_url_handling_browsertest.cc
index 6595c626..c14479c72 100644
--- a/chrome/browser/ui/web_applications/web_app_url_handling_browsertest.cc
+++ b/chrome/browser/ui/web_applications/web_app_url_handling_browsertest.cc
@@ -73,7 +73,7 @@
       "manifest_test_page.html?manifest=manifest_url_handlers.json",
       /*await_metric=*/true);
   apps::UrlHandlers url_handlers =
-      provider().registrar().GetAppUrlHandlers(app_id);
+      provider().registrar_unsafe().GetAppUrlHandlers(app_id);
 
   // One handler has an invalid host so it shouldn't be in the result.
   ASSERT_EQ(3u, url_handlers.size());
@@ -99,7 +99,7 @@
       InstallTestApp("/banners/manifest_test_page.html?manifest=manifest.json",
                      /*await_metric=*/false);
   apps::UrlHandlers url_handlers =
-      provider().registrar().GetAppUrlHandlers(app_id);
+      provider().registrar_unsafe().GetAppUrlHandlers(app_id);
   ASSERT_EQ(0u, url_handlers.size());
 
   histogram_tester_.ExpectBucketCount(kUseCounterHistogram,
diff --git a/chrome/browser/ui/web_applications/web_app_window_controls_overlay_browsertest.cc b/chrome/browser/ui/web_applications/web_app_window_controls_overlay_browsertest.cc
index 8ece760..64c01276 100644
--- a/chrome/browser/ui/web_applications/web_app_window_controls_overlay_browsertest.cc
+++ b/chrome/browser/ui/web_applications/web_app_window_controls_overlay_browsertest.cc
@@ -72,7 +72,7 @@
       /*await_metric=*/true);
 
   std::vector<DisplayMode> display_mode_override =
-      provider().registrar().GetAppDisplayModeOverride(app_id);
+      provider().registrar_unsafe().GetAppDisplayModeOverride(app_id);
 
   ASSERT_EQ(1u, display_mode_override.size());
 
@@ -87,7 +87,7 @@
                      /*await_metric=*/false);
 
   std::vector<DisplayMode> display_mode_override =
-      provider().registrar().GetAppDisplayModeOverride(app_id);
+      provider().registrar_unsafe().GetAppDisplayModeOverride(app_id);
 
   ASSERT_EQ(0u, display_mode_override.size());
 
diff --git a/chrome/browser/ui/web_applications/web_share_target_browsertest.cc b/chrome/browser/ui/web_applications/web_share_target_browsertest.cc
index 4457be3..244e138 100644
--- a/chrome/browser/ui/web_applications/web_share_target_browsertest.cc
+++ b/chrome/browser/ui/web_applications/web_share_target_browsertest.cc
@@ -349,7 +349,7 @@
   const AppId app_id = web_app::InstallWebAppFromManifest(browser(), app_url);
   const apps::ShareTarget* share_target =
       WebAppProvider::GetForTest(browser()->profile())
-          ->registrar()
+          ->registrar_unsafe()
           .GetAppShareTarget(app_id);
   EXPECT_EQ(share_target->method, apps::ShareTarget::Method::kPost);
   EXPECT_EQ(share_target->enctype, apps::ShareTarget::Enctype::kFormUrlEncoded);
@@ -380,7 +380,7 @@
   const AppId app_id = web_app::InstallWebAppFromManifest(browser(), app_url);
   const apps::ShareTarget* share_target =
       WebAppProvider::GetForTest(browser()->profile())
-          ->registrar()
+          ->registrar_unsafe()
           .GetAppShareTarget(app_id);
   EXPECT_EQ(share_target->method, apps::ShareTarget::Method::kGet);
   EXPECT_EQ(share_target->enctype, apps::ShareTarget::Enctype::kFormUrlEncoded);
diff --git a/chrome/browser/ui/webauthn/sheet_models.cc b/chrome/browser/ui/webauthn/sheet_models.cc
index a78d3bff..9ae245b2 100644
--- a/chrome/browser/ui/webauthn/sheet_models.cc
+++ b/chrome/browser/ui/webauthn/sheet_models.cc
@@ -877,12 +877,6 @@
   }
 }
 
-bool AuthenticatorPaaskSheetModel::IsOtherMechanismButtonVisible() const {
-  DCHECK(base::FeatureList::IsEnabled(
-      device::kWebAuthnNewDiscoverableCredentialsUi));
-  return false;
-}
-
 ui::MenuModel* AuthenticatorPaaskSheetModel::GetOtherMechanismsMenuModel() {
   switch (dialog_model()->experiment_server_link_sheet_) {
     case AuthenticatorRequestDialogModel::ExperimentServerLinkSheet::CONTROL:
@@ -958,13 +952,6 @@
   return other_mechanisms_menu_model_.get();
 }
 
-bool AuthenticatorAndroidAccessorySheetModel::IsOtherMechanismButtonVisible()
-    const {
-  DCHECK(base::FeatureList::IsEnabled(
-      device::kWebAuthnNewDiscoverableCredentialsUi));
-  return false;
-}
-
 // AuthenticatorClientPinEntrySheetModel
 // -----------------------------------------
 
diff --git a/chrome/browser/ui/webauthn/sheet_models.h b/chrome/browser/ui/webauthn/sheet_models.h
index 5a3c07a..a458e7f 100644
--- a/chrome/browser/ui/webauthn/sheet_models.h
+++ b/chrome/browser/ui/webauthn/sheet_models.h
@@ -315,7 +315,6 @@
   std::u16string GetStepDescription() const override;
   ui::MenuModel* GetOtherMechanismsMenuModel() override;
   void OnBack() override;
-  bool IsOtherMechanismButtonVisible() const override;
 
   std::unique_ptr<OtherMechanismsMenuModel> other_mechanisms_menu_model_;
 };
@@ -336,7 +335,6 @@
   std::u16string GetStepTitle() const override;
   std::u16string GetStepDescription() const override;
   ui::MenuModel* GetOtherMechanismsMenuModel() override;
-  bool IsOtherMechanismButtonVisible() const override;
 
   std::unique_ptr<OtherMechanismsMenuModel> other_mechanisms_menu_model_;
 };
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 0c9cd23..3276766 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
@@ -6,11 +6,11 @@
 #define CHROME_BROWSER_UI_WEBUI_ACCESS_CODE_CAST_ACCESS_CODE_CAST_HANDLER_H_
 
 #include "base/scoped_observation.h"
+#include "chrome/browser/ash/app_list/search/search_controller.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"
 #include "chrome/browser/media/router/discovery/mdns/cast_media_sink_service_impl.h"
 #include "chrome/browser/media/router/discovery/mdns/media_sink_util.h"
-#include "chrome/browser/ui/app_list/search/search_controller.h"
 #include "chrome/browser/ui/media_router/media_cast_mode.h"
 #include "chrome/browser/ui/media_router/media_route_starter.h"
 #include "chrome/browser/ui/media_router/media_router_ui.h"
diff --git a/chrome/browser/ui/webui/app_management/app_management_page_handler.cc b/chrome/browser/ui/webui/app_management/app_management_page_handler.cc
index db63c95..33eaa20 100644
--- a/chrome/browser/ui/webui/app_management/app_management_page_handler.cc
+++ b/chrome/browser/ui/webui/app_management/app_management_page_handler.cc
@@ -179,7 +179,7 @@
   // There's no need to update twice.
 #if !BUILDFLAG(IS_CHROMEOS)
   auto* provider = web_app::WebAppProvider::GetForWebApps(profile_);
-  registrar_observation_.Observe(&provider->registrar());
+  registrar_observation_.Observe(&provider->registrar_unsafe());
 #endif
 }
 
@@ -339,7 +339,7 @@
   auto* provider = web_app::WebAppProvider::GetForLocalAppsUnchecked(profile_);
 
   // Changing window mode is not allowed for isolated web apps.
-  if (provider->registrar().IsIsolated(app_id)) {
+  if (provider->registrar_unsafe().IsIsolated(app_id)) {
     NOTREACHED();
   } else {
     if (base::FeatureList::IsEnabled(apps::kAppServiceWithoutMojom)) {
@@ -541,7 +541,7 @@
 
 #if !BUILDFLAG(IS_CHROMEOS)
   auto* provider = web_app::WebAppProvider::GetForLocalAppsUnchecked(profile_);
-  app->hide_window_mode = provider->registrar().IsIsolated(app->id);
+  app->hide_window_mode = provider->registrar_unsafe().IsIsolated(app->id);
 #endif
 
   app->publisher_id = update.PublisherId();
diff --git a/chrome/browser/ui/webui/ash/assistant_optin/assistant_optin_ui.cc b/chrome/browser/ui/webui/ash/assistant_optin/assistant_optin_ui.cc
index da43bfb..72ee0d1 100644
--- a/chrome/browser/ui/webui/ash/assistant_optin/assistant_optin_ui.cc
+++ b/chrome/browser/ui/webui/ash/assistant_optin/assistant_optin_ui.cc
@@ -90,7 +90,8 @@
       base::make_span(kAssistantOptinResources, kAssistantOptinResourcesSize));
   source->SetDefaultResource(IDR_ASSISTANT_OPTIN_ASSISTANT_OPTIN_HTML);
   source->OverrideContentSecurityPolicy(
-      network::mojom::CSPDirectiveName::WorkerSrc, "worker-src blob: 'self';");
+      network::mojom::CSPDirectiveName::WorkerSrc,
+      "worker-src blob: chrome://resources 'self';");
   source->DisableTrustedTypesCSP();
   content::WebUIDataSource::Add(Profile::FromWebUI(web_ui), source);
 
diff --git a/chrome/browser/ui/webui/ash/cloud_upload/cloud_upload.mojom b/chrome/browser/ui/webui/ash/cloud_upload/cloud_upload.mojom
index 3a65457..009f250c 100644
--- a/chrome/browser/ui/webui/ash/cloud_upload/cloud_upload.mojom
+++ b/chrome/browser/ui/webui/ash/cloud_upload/cloud_upload.mojom
@@ -59,6 +59,12 @@
   // successfully installed or was already installed.
   InstallOfficeWebApp() => (bool installed);
 
+  // Returns whether or not the OneDrive filesystem is mounted.
+  IsODFSMounted() => (bool mounted);
+
+  // Starts the OneDrive authentication process.
+  SignInToOneDrive() => (bool success);
+
   // Returns the user selected action and requests the dialog to be closed.
   RespondAndClose(UserAction response);
 
diff --git a/chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_dialog.cc b/chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_dialog.cc
index 4e80488..1ebf52c 100644
--- a/chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_dialog.cc
+++ b/chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_dialog.cc
@@ -29,7 +29,9 @@
 
 const char kOpenWebActionId[] = "OPEN_WEB";
 
-void OpenDriveUrl(const GURL& url) {
+// Open a hosted MS Office file e.g. .docx, from a url hosted in
+// DriveFS. Check the file was successfully uploaded to DriveFS.
+void OpenUploadedDriveUrl(const GURL& url) {
   if (url.is_empty()) {
     UMA_HISTOGRAM_ENUMERATION(kDriveTaskResultMetricName,
                               OfficeTaskResult::FAILED);
@@ -40,14 +42,33 @@
   file_manager::util::OpenNewTabForHostedOfficeFile(url);
 }
 
-// Copied from file_tasks.cc.
-void OpenODFSUrl(const storage::FileSystemURL& uploaded_file_url) {
-  if (!uploaded_file_url.is_valid()) {
+// Open an already hosted MS Office file e.g. .docx, from a url hosted in
+// DriveFS. Check there was no error retrieving the file's metadata.
+void OpenAlreadyHostedDriveUrl(drive::FileError error,
+                               drivefs::mojom::FileMetadataPtr metadata) {
+  if (error != drive::FILE_ERROR_OK) {
+    UMA_HISTOGRAM_ENUMERATION(
+        file_manager::file_tasks::kDriveErrorMetricName,
+        file_manager::file_tasks::OfficeDriveErrors::NO_METADATA);
+    LOG(ERROR) << "Drive metadata error: " << error;
+    return;
+  }
+
+  GURL hosted_url(metadata->alternate_url);
+  bool opened = file_manager::util::OpenNewTabForHostedOfficeFile(hosted_url);
+
+  if (opened) {
+    UMA_HISTOGRAM_ENUMERATION(kDriveTaskResultMetricName,
+                              OfficeTaskResult::OPENED);
+  }
+}
+
+void OpenODFSUrl(const storage::FileSystemURL& url) {
+  if (!url.is_valid()) {
     LOG(ERROR) << "Invalid uploaded file URL";
     return;
   }
-  ash::file_system_provider::util::FileSystemURLParser parser(
-      uploaded_file_url);
+  ash::file_system_provider::util::FileSystemURLParser parser(url);
   if (!parser.Parse()) {
     LOG(ERROR) << "Path not in FSP";
     return;
@@ -58,17 +79,41 @@
       base::BindOnce([](base::File::Error result) {
         if (result != base::File::Error::FILE_OK) {
           LOG(ERROR) << "Error executing action: " << result;
+          // TODO(cassycc): Run GetUserFallbackChoice().
         }
       }));
 }
 
+void OpenODFSUrls(const std::vector<storage::FileSystemURL>& file_urls) {
+  for (const auto& file_url : file_urls) {
+    OpenODFSUrl(file_url);
+  }
+}
+
+void OpenAlreadyHostedDriveUrls(
+    Profile* profile,
+    const std::vector<storage::FileSystemURL>& file_urls) {
+  drive::DriveIntegrationService* integration_service =
+      drive::DriveIntegrationServiceFactory::FindForProfile(profile);
+  base::FilePath relative_path;
+  for (const auto& file_url : file_urls) {
+    if (integration_service->GetRelativeDrivePath(file_url.path(),
+                                                  &relative_path)) {
+      integration_service->GetDriveFsInterface()->GetMetadata(
+          relative_path, base::BindOnce(&OpenAlreadyHostedDriveUrl));
+    } else {
+      LOG(ERROR) << "Unexpected error obtaining the relative path ";
+    }
+  }
+}
+
 void StartUpload(Profile* profile,
                  const std::vector<storage::FileSystemURL>& file_urls,
                  const CloudProvider cloud_provider) {
   if (cloud_provider == CloudProvider::kGoogleDrive) {
     for (const auto& file_url : file_urls) {
       DriveUploadHandler::Upload(profile, file_url,
-                                 base::BindOnce(&OpenDriveUrl));
+                                 base::BindOnce(&OpenUploadedDriveUrl));
     }
   } else if (cloud_provider == CloudProvider::kOneDrive) {
     for (const auto& file_url : file_urls) {
@@ -95,6 +140,28 @@
   }
 }
 
+bool FileIsOnDriveFS(Profile* profile, const base::FilePath& file_path) {
+  drive::DriveIntegrationService* integration_service =
+      drive::DriveIntegrationServiceFactory::FindForProfile(profile);
+  base::FilePath relative_path;
+  return integration_service->GetRelativeDrivePath(file_path, &relative_path);
+}
+
+bool FileIsOnODFS(Profile* profile, const FileSystemURL& url) {
+  ash::file_system_provider::util::FileSystemURLParser parser(url);
+  if (!parser.Parse()) {
+    return false;
+  }
+
+  file_system_provider::ProviderId provider_id =
+      file_system_provider::ProviderId::CreateFromExtensionId(
+          file_manager::file_tasks::kODFSExtensionId);
+  if (parser.file_system()->GetFileSystemInfo().provider_id() != provider_id) {
+    return false;
+  }
+  return true;
+}
+
 void OnDialogComplete(Profile* profile,
                       const std::vector<storage::FileSystemURL>& file_urls,
                       const std::string& action) {
@@ -111,10 +178,11 @@
     SetPowerPointFileHandler(
         profile, file_manager::file_tasks::kActionIdWebDriveOfficePowerPoint);
     SetOfficeSetupComplete(profile);
-    ConfirmMoveOrStartUpload(profile, file_urls, CloudProvider::kGoogleDrive);
+    OpenOrMoveFiles(profile, file_urls, CloudProvider::kGoogleDrive);
   } else if (action == kUserActionConfirmOrUploadToOneDrive) {
-    // Default handlers have already been set by this point for Office/OneDrive.
-    ConfirmMoveOrStartUpload(profile, file_urls, CloudProvider::kOneDrive);
+    // Default handlers have already been set by this point for
+    // Office/OneDrive.
+    OpenOrMoveFiles(profile, file_urls, CloudProvider::kOneDrive);
   } else if (action == kUserActionUploadToGoogleDrive) {
     StartUpload(profile, file_urls, CloudProvider::kGoogleDrive);
   } else if (action == kUserActionUploadToOneDrive) {
@@ -133,24 +201,41 @@
 
 }  // namespace
 
-bool UploadAndOpen(Profile* profile,
-                   const std::vector<storage::FileSystemURL>& file_urls,
-                   const CloudProvider cloud_provider) {
-  // Run the setup flow if it's never been completed.
-  if (!file_manager::file_tasks::OfficeSetupComplete(profile)) {
-    return CloudUploadDialog::Show(profile, file_urls,
-                                   mojom::DialogPage::kFileHandlerDialog);
-  }
-
+bool OpenFilesWithCloudProvider(
+    Profile* profile,
+    const std::vector<storage::FileSystemURL>& file_urls,
+    const CloudProvider cloud_provider) {
   bool empty_selection = file_urls.empty();
   DCHECK(!empty_selection);
   if (empty_selection) {
     return false;
   }
-  ConfirmMoveOrStartUpload(profile, file_urls, cloud_provider);
+  // Run the setup flow if it's never been completed.
+  if (!file_manager::file_tasks::OfficeSetupComplete(profile)) {
+    return CloudUploadDialog::Show(profile, file_urls,
+                                   mojom::DialogPage::kFileHandlerDialog);
+  }
+  OpenOrMoveFiles(profile, file_urls, cloud_provider);
   return true;
 }
 
+void OpenOrMoveFiles(Profile* profile,
+                     const std::vector<storage::FileSystemURL>& file_urls,
+                     const CloudProvider cloud_provider) {
+  if (cloud_provider == CloudProvider::kGoogleDrive &&
+      FileIsOnDriveFS(profile, file_urls.front().path())) {
+    // The files are on Drive already.
+    OpenAlreadyHostedDriveUrls(profile, file_urls);
+  } else if (cloud_provider == CloudProvider::kOneDrive &&
+             FileIsOnODFS(profile, file_urls.front())) {
+    // The files are on OneDrive already.
+    OpenODFSUrls(file_urls);
+  } else {
+    // The files need to be moved.
+    ConfirmMoveOrStartUpload(profile, file_urls, cloud_provider);
+  }
+}
+
 // static
 bool CloudUploadDialog::Show(
     Profile* profile,
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 99aebe1..6f4ca68 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
@@ -33,10 +33,19 @@
   kOneDrive,
 };
 
-// Initiates the upload workflow.
-bool UploadAndOpen(Profile* profile,
-                   const std::vector<storage::FileSystemURL>& file_urls,
-                   const CloudProvider cloud_provider);
+// Opens the `file_urls` from the `cloud_provider`. Runs setup for for Office
+// files if it has not been run before. Uploads the files to the cloud if
+// needed.
+bool OpenFilesWithCloudProvider(
+    Profile* profile,
+    const std::vector<storage::FileSystemURL>& file_urls,
+    const CloudProvider cloud_provider);
+
+// Open office files if they are in the correct cloud already.
+// Otherwise move the files before opening.
+void OpenOrMoveFiles(Profile* profile,
+                     const std::vector<storage::FileSystemURL>& file_urls,
+                     const CloudProvider cloud_provider);
 
 // Defines the web dialog used to help users upload Office files to the cloud.
 class CloudUploadDialog : public SystemWebDialogDelegate {
diff --git a/chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_page_handler.cc b/chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_page_handler.cc
index 1bbf242..812e17cd 100644
--- a/chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_page_handler.cc
+++ b/chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_page_handler.cc
@@ -8,6 +8,9 @@
 #include "chrome/browser/apps/app_service/app_service_proxy.h"
 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
 #include "chrome/browser/ash/file_manager/file_tasks.h"
+#include "chrome/browser/ash/file_system_provider/provided_file_system_info.h"
+#include "chrome/browser/ash/file_system_provider/service.h"
+#include "chrome/browser/chromeos/extensions/file_system_provider/provider_function.h"
 #include "chrome/browser/ui/webui/ash/cloud_upload/cloud_upload.mojom.h"
 #include "chrome/browser/web_applications/external_install_options.h"
 #include "chrome/browser/web_applications/user_display_mode.h"
@@ -21,23 +24,38 @@
 #include "url/gurl.h"
 
 namespace ash::cloud_upload {
-
 namespace {
 const char kMicrosoft365WebAppUrl[] = "https://www.office.com/?from=Homescreen";
 }  // namespace
 
+using ash::file_system_provider::ProvidedFileSystemInfo;
+using ash::file_system_provider::ProviderId;
+using ash::file_system_provider::Service;
+
 CloudUploadPageHandler::CloudUploadPageHandler(
+    content::WebUI* web_ui,
     Profile* profile,
     mojom::DialogArgsPtr args,
     mojo::PendingReceiver<mojom::PageHandler> pending_page_handler,
     RespondAndCloseCallback callback)
     : profile_{profile},
+      web_ui_{web_ui},
       dialog_args_{std::move(args)},
       receiver_{this, std::move(pending_page_handler)},
       callback_{std::move(callback)} {}
 
 CloudUploadPageHandler::~CloudUploadPageHandler() = default;
 
+void CloudUploadPageHandler::OnMountResponse(
+    CloudUploadPageHandler::SignInToOneDriveCallback callback,
+    base::File::Error result) {
+  gfx::NativeWindow window =
+      web_ui_->GetWebContents()->GetTopLevelNativeWindow();
+  window->Show();
+  window->Focus();
+  std::move(callback).Run(result == base::File::FILE_OK);
+}
+
 void CloudUploadPageHandler::GetDialogArgs(GetDialogArgsCallback callback) {
   std::move(callback).Run(dialog_args_ ? dialog_args_.Clone()
                                        : mojom::DialogArgs::New());
@@ -97,6 +115,29 @@
   std::move(callback).Run(webapps::IsSuccess(result.code));
 }
 
+void CloudUploadPageHandler::IsODFSMounted(IsODFSMountedCallback callback) {
+  Service* service = Service::Get(profile_);
+  ProviderId provider_id = ProviderId::CreateFromExtensionId(
+      file_manager::file_tasks::kODFSExtensionId);
+  std::vector<ProvidedFileSystemInfo> file_systems =
+      service->GetProvidedFileSystemInfoList(provider_id);
+
+  // Assume any file system mounted by ODFS is the correct one.
+  std::move(callback).Run(!file_systems.empty());
+}
+
+void CloudUploadPageHandler::SignInToOneDrive(
+    SignInToOneDriveCallback callback) {
+  Service* service = Service::Get(profile_);
+  ProviderId provider_id = ProviderId::CreateFromExtensionId(
+      file_manager::file_tasks::kODFSExtensionId);
+  web_ui_->GetWebContents()->GetTopLevelNativeWindow()->Hide();
+  service->RequestMount(
+      provider_id,
+      base::BindOnce(&CloudUploadPageHandler::OnMountResponse,
+                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
+}
+
 void CloudUploadPageHandler::RespondAndClose(mojom::UserAction action) {
   if (callback_) {
     std::move(callback_).Run(action);
diff --git a/chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_page_handler.h b/chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_page_handler.h
index 4696d8ab..5bd54e0 100644
--- a/chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_page_handler.h
+++ b/chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_page_handler.h
@@ -6,6 +6,7 @@
 #define CHROME_BROWSER_UI_WEBUI_ASH_CLOUD_UPLOAD_CLOUD_UPLOAD_PAGE_HANDLER_H_
 
 #include "base/callback.h"
+#include "base/files/file.h"
 #include "base/memory/weak_ptr.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/webui/ash/cloud_upload/cloud_upload.mojom-shared.h"
@@ -29,6 +30,7 @@
   using RespondAndCloseCallback =
       base::OnceCallback<void(mojom::UserAction action)>;
   CloudUploadPageHandler(
+      content::WebUI* web_ui,
       Profile* profile,
       mojom::DialogArgsPtr args,
       mojo::PendingReceiver<mojom::PageHandler> pending_page_handler,
@@ -39,11 +41,17 @@
 
   ~CloudUploadPageHandler() override;
 
+  void OnMountResponse(
+      CloudUploadPageHandler::SignInToOneDriveCallback callback,
+      base::File::Error result);
+
   // mojom::PageHandler:
   void GetDialogArgs(GetDialogArgsCallback callback) override;
   void IsOfficeWebAppInstalled(
       IsOfficeWebAppInstalledCallback callback) override;
   void InstallOfficeWebApp(InstallOfficeWebAppCallback callback) override;
+  void IsODFSMounted(IsODFSMountedCallback callback) override;
+  void SignInToOneDrive(SignInToOneDriveCallback callback) override;
   void RespondAndClose(mojom::UserAction action) override;
   void SetOfficeAsDefaultHandler() override;
   void SetAlwaysMoveOfficeFiles(bool always_move) override;
@@ -55,7 +63,7 @@
       web_app::ExternallyManagedAppManager::InstallResult result);
 
   Profile* profile_;
-
+  content::WebUI* web_ui_;
   mojom::DialogArgsPtr dialog_args_;
 
   mojo::Receiver<PageHandler> receiver_;
diff --git a/chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_ui.cc b/chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_ui.cc
index 7bc81814..6433ad4 100644
--- a/chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_ui.cc
+++ b/chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_ui.cc
@@ -55,7 +55,7 @@
 void CloudUploadUI::CreatePageHandler(
     mojo::PendingReceiver<mojom::PageHandler> receiver) {
   page_handler_ = std::make_unique<CloudUploadPageHandler>(
-      Profile::FromWebUI(web_ui()), std::move(dialog_args_),
+      web_ui(), Profile::FromWebUI(web_ui()), std::move(dialog_args_),
       std::move(receiver),
       // base::Unretained() because |page_handler_| will not out-live |this|.
       base::BindOnce(&CloudUploadUI::RespondAndCloseDialog,
diff --git a/chrome/browser/ui/webui/ash/cryptohome_web_ui_handler.cc b/chrome/browser/ui/webui/ash/cryptohome_web_ui_handler.cc
index c79138c7..523c1e7 100644
--- a/chrome/browser/ui/webui/ash/cryptohome_web_ui_handler.cc
+++ b/chrome/browser/ui/webui/ash/cryptohome_web_ui_handler.cc
@@ -4,6 +4,8 @@
 
 #include "chrome/browser/ui/webui/ash/cryptohome_web_ui_handler.h"
 
+#include <numeric>
+
 #include "base/bind.h"
 #include "base/logging.h"
 #include "base/values.h"
@@ -11,7 +13,10 @@
 #include "chromeos/ash/components/dbus/dbus_thread_manager.h"
 #include "chromeos/ash/components/dbus/userdataauth/cryptohome_pkcs11_client.h"
 #include "chromeos/ash/components/dbus/userdataauth/userdataauth_client.h"
+#include "chromeos/ash/components/login/auth/auth_factor_editor.h"
+#include "chromeos/ash/components/login/auth/public/cryptohome_key_constants.h"
 #include "chromeos/dbus/tpm_manager/tpm_manager_client.h"
+#include "components/user_manager/user_manager.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/web_ui.h"
@@ -23,6 +28,9 @@
 
 namespace {
 
+// Maximal number of RecoveryId entries we want to display.
+constexpr int kRecoveryIdHistoryDepth = 5;
+
 void ForwardToUIThread(base::OnceCallback<void(bool)> ui_callback,
                        bool result) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
@@ -30,6 +38,23 @@
       FROM_HERE, base::BindOnce(std::move(ui_callback), result));
 }
 
+user_data_auth::GetAuthFactorExtendedInfoRequest
+GenerateAuthFactorExtendedInfoRequest(int depth) {
+  user_data_auth::GetAuthFactorExtendedInfoRequest req;
+  user_data_auth::RecoveryExtendedInfoRequest req_extended_info;
+  const user_manager::User* primary_user =
+      user_manager::UserManager::Get()->GetPrimaryUser();
+  if (primary_user) {
+    *req.mutable_account_id() =
+        cryptohome::CreateAccountIdentifierFromAccountId(
+            primary_user->GetAccountId());
+    *req.mutable_auth_factor_label() = kCryptohomeRecoveryKeyLabel;
+    req_extended_info.set_max_depth(depth);
+    *req.mutable_recovery_info_request() = std::move(req_extended_info);
+  }
+  return req;
+}
+
 }  // namespace
 
 CryptohomeWebUIHandler::CryptohomeWebUIHandler() {}
@@ -60,6 +85,12 @@
       base::BindOnce(&CryptohomeWebUIHandler::OnPkcs11IsTpmTokenReady,
                      weak_ptr_factory_.GetWeakPtr()));
 
+  user_data_auth::GetAuthFactorExtendedInfoRequest req =
+      GenerateAuthFactorExtendedInfoRequest(kRecoveryIdHistoryDepth);
+  userdataauth_client->GetAuthFactorExtendedInfo(
+      req, base::BindOnce(&CryptohomeWebUIHandler::OnGetAuthFactorExtendedInfo,
+                          weak_ptr_factory_.GetWeakPtr()));
+
   auto ui_callback =
       base::BindOnce(&CryptohomeWebUIHandler::GotIsTPMTokenEnabledOnUIThread,
                      weak_ptr_factory_.GetWeakPtr());
@@ -93,6 +124,21 @@
                         base::Value(reply.has_reset_lock_permissions()));
 }
 
+void CryptohomeWebUIHandler::OnGetAuthFactorExtendedInfo(
+    absl::optional<user_data_auth::GetAuthFactorExtendedInfoReply> reply) {
+  std::string recovery_ids = "<empty>";
+  if (reply.has_value() &&
+      !reply->recovery_info_reply().recovery_ids().empty()) {
+    recovery_ids =
+        std::accumulate(reply->recovery_info_reply().recovery_ids().begin(),
+                        reply->recovery_info_reply().recovery_ids().end(),
+                        std::string(), [](std::string ss, std::string s) {
+                          return ss.empty() ? s : ss + " " + s;
+                        });
+  }
+  SetCryptohomeProperty("recovery_ids", base::Value(recovery_ids));
+}
+
 void CryptohomeWebUIHandler::OnIsMounted(
     absl::optional<user_data_auth::IsMountedReply> reply) {
   bool mounted = false;
diff --git a/chrome/browser/ui/webui/ash/cryptohome_web_ui_handler.h b/chrome/browser/ui/webui/ash/cryptohome_web_ui_handler.h
index 7f49da7..93bf67e 100644
--- a/chrome/browser/ui/webui/ash/cryptohome_web_ui_handler.h
+++ b/chrome/browser/ui/webui/ash/cryptohome_web_ui_handler.h
@@ -50,6 +50,11 @@
   void OnGetTpmStatus(
       const ::tpm_manager::GetTpmNonsensitiveStatusReply& reply);
 
+  // Called when Cryptohome D-Bus GetAuthFactorExtendedInfo call completes.
+  // Gets requested AuthFactor with additional metadata in reply.
+  void OnGetAuthFactorExtendedInfo(
+      absl::optional<user_data_auth::GetAuthFactorExtendedInfoReply> reply);
+
   // Sets textcontent of the element whose id is |destination_id| to |value|.
   void SetCryptohomeProperty(const std::string& destination_id,
                              const base::Value& value);
diff --git a/chrome/browser/ui/webui/ash/launcher_internals/launcher_internals_handler.cc b/chrome/browser/ui/webui/ash/launcher_internals/launcher_internals_handler.cc
index 7ab8618..f3f6514 100644
--- a/chrome/browser/ui/webui/ash/launcher_internals/launcher_internals_handler.cc
+++ b/chrome/browser/ui/webui/ash/launcher_internals/launcher_internals_handler.cc
@@ -5,8 +5,8 @@
 #include "chrome/browser/ui/webui/ash/launcher_internals/launcher_internals_handler.h"
 
 #include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
 #include "chrome/browser/ash/app_list/search/common/types_util.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
 
 namespace ash {
 
diff --git a/chrome/browser/ui/webui/ash/launcher_internals/launcher_internals_handler.h b/chrome/browser/ui/webui/ash/launcher_internals/launcher_internals_handler.h
index 2d5b2ae..d138507 100644
--- a/chrome/browser/ui/webui/ash/launcher_internals/launcher_internals_handler.h
+++ b/chrome/browser/ui/webui/ash/launcher_internals/launcher_internals_handler.h
@@ -6,7 +6,7 @@
 #define CHROME_BROWSER_UI_WEBUI_ASH_LAUNCHER_INTERNALS_LAUNCHER_INTERNALS_HANDLER_H_
 
 #include "base/scoped_observation.h"
-#include "chrome/browser/ui/app_list/search/search_controller.h"
+#include "chrome/browser/ash/app_list/search/search_controller.h"
 #include "chrome/browser/ui/webui/ash/launcher_internals/launcher_internals.mojom.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/remote.h"
diff --git a/chrome/browser/ui/webui/ash/login/choobe_screen_handler.cc b/chrome/browser/ui/webui/ash/login/choobe_screen_handler.cc
new file mode 100644
index 0000000..d828b9b
--- /dev/null
+++ b/chrome/browser/ui/webui/ash/login/choobe_screen_handler.cc
@@ -0,0 +1,53 @@
+// 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/ui/webui/ash/login/choobe_screen_handler.h"
+
+#include "base/logging.h"
+
+#include "chrome/browser/ash/login/choobe_flow_controller.h"
+#include "chrome/browser/ash/login/oobe_screen.h"
+#include "chrome/browser/ash/login/wizard_controller.h"
+#include "chrome/browser/ui/webui/ash/login/base_screen_handler.h"
+#include "chrome/browser/ui/webui/ash/login/theme_selection_screen_handler.h"
+#include "chrome/grit/generated_resources.h"
+#include "components/login/localized_values_builder.h"
+
+namespace ash {
+
+ChoobeScreenHandler::ChoobeScreenHandler() : BaseScreenHandler(kScreenId) {}
+
+ChoobeScreenHandler::~ChoobeScreenHandler() = default;
+
+void ChoobeScreenHandler::DeclareLocalizedValues(
+    ::login::LocalizedValuesBuilder* builder) {
+  builder->Add("choobeScreenTitle", IDS_OOBE_CHOOBE_TITLE);
+  builder->Add("choobeScreenDescription", IDS_OOBE_CHOOBE_DESCRIPTION);
+  builder->Add("choobeScreenSkip", IDS_OOBE_CHOOBE_SKIP_BUTTON);
+
+  auto resources = ChoobeFlowController::GetOptionalScreensResources();
+  for (auto value : resources) {
+    builder->Add(value.key, value.message_id);
+  }
+}
+
+void ChoobeScreenHandler::Show(
+    const std::vector<ChoobeFlowController::OptionalScreen>& screens) {
+  base::Value::List screens_list;
+  for (auto screen : screens) {
+    base::Value::Dict screen_dict;
+    screen_dict.Set("screenID", base::Value(screen.screen_id.name));
+    screen_dict.Set("title", base::Value(screen.title_resource.key));
+    screen_dict.Set("icon", base::Value(screen.icon_id));
+    screen_dict.Set("selected", false);
+    screens_list.Append(std::move(screen_dict));
+  }
+
+  base::Value::Dict data;
+  data.Set("screens", std::move(screens_list));
+
+  ShowInWebUI(std::move(data));
+}
+
+}  // namespace ash
diff --git a/chrome/browser/ui/webui/ash/login/choobe_screen_handler.h b/chrome/browser/ui/webui/ash/login/choobe_screen_handler.h
new file mode 100644
index 0000000..57f7e385
--- /dev/null
+++ b/chrome/browser/ui/webui/ash/login/choobe_screen_handler.h
@@ -0,0 +1,53 @@
+// 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_UI_WEBUI_ASH_LOGIN_CHOOBE_SCREEN_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_ASH_LOGIN_CHOOBE_SCREEN_HANDLER_H_
+
+#include "base/memory/weak_ptr.h"
+#include "base/values.h"
+#include "chrome/browser/ash/login/choobe_flow_controller.h"
+#include "chrome/browser/ui/webui/ash/login/base_screen_handler.h"
+
+namespace ash {
+
+class ChobbeScreen;
+
+// Interface for dependency injection between ChobbeScreen and its
+// WebUI representation.
+class ChoobeScreenView : public base::SupportsWeakPtr<ChoobeScreenView> {
+ public:
+  inline constexpr static StaticOobeScreenId kScreenId{"choobe",
+                                                       "ChoobeScreen"};
+
+  virtual ~ChoobeScreenView() = default;
+
+  // Shows the contents of the screen.
+  virtual void Show(
+      const std::vector<ChoobeFlowController::OptionalScreen>& screens) = 0;
+};
+
+class ChoobeScreenHandler : public BaseScreenHandler, public ChoobeScreenView {
+ public:
+  using TView = ChoobeScreenView;
+
+  ChoobeScreenHandler();
+
+  ChoobeScreenHandler(const ChoobeScreenHandler&) = delete;
+  ChoobeScreenHandler& operator=(const ChoobeScreenHandler&) = delete;
+
+  ~ChoobeScreenHandler() override;
+
+  // BaseScreenHandler:
+  void DeclareLocalizedValues(
+      ::login::LocalizedValuesBuilder* builder) override;
+
+  // ChoobeScreenView:
+  void Show(const std::vector<ChoobeFlowController::OptionalScreen>& screens)
+      override;
+};
+
+}  // namespace ash
+
+#endif  // CHROME_BROWSER_UI_WEBUI_ASH_LOGIN_CHOOBE_SCREEN_HANDLER_H_
diff --git a/chrome/browser/ui/webui/ash/login/oobe_ui.cc b/chrome/browser/ui/webui/ash/login/oobe_ui.cc
index f587a16..976c2ed 100644
--- a/chrome/browser/ui/webui/ash/login/oobe_ui.cc
+++ b/chrome/browser/ui/webui/ash/login/oobe_ui.cc
@@ -47,6 +47,7 @@
 #include "chrome/browser/ui/webui/ash/login/assistant_optin_flow_screen_handler.h"
 #include "chrome/browser/ui/webui/ash/login/auto_enrollment_check_screen_handler.h"
 #include "chrome/browser/ui/webui/ash/login/base_screen_handler.h"
+#include "chrome/browser/ui/webui/ash/login/choobe_screen_handler.h"
 #include "chrome/browser/ui/webui/ash/login/consolidated_consent_screen_handler.h"
 #include "chrome/browser/ui/webui/ash/login/cryptohome_recovery_screen_handler.h"
 #include "chrome/browser/ui/webui/ash/login/cryptohome_recovery_setup_screen_handler.h"
@@ -225,7 +226,8 @@
   source->AddResourcePaths(
       base::make_span(kAssistantOptinResources, kAssistantOptinResourcesSize));
   source->OverrideContentSecurityPolicy(
-      network::mojom::CSPDirectiveName::WorkerSrc, "worker-src blob: 'self';");
+      network::mojom::CSPDirectiveName::WorkerSrc,
+      "worker-src blob: chrome://resources 'self';");
 }
 
 void AddMultiDeviceSetupResources(content::WebUIDataSource* source) {
@@ -234,7 +236,8 @@
   source->AddResourcePath("multidevice_setup_dark.json",
                           IDR_MULTIDEVICE_SETUP_ANIMATION_DARK);
   source->OverrideContentSecurityPolicy(
-      network::mojom::CSPDirectiveName::WorkerSrc, "worker-src blob: 'self';");
+      network::mojom::CSPDirectiveName::WorkerSrc,
+      "worker-src blob: chrome://resources 'self';");
 }
 
 void AddDebuggerResources(content::WebUIDataSource* source) {
@@ -285,6 +288,7 @@
   source->AddBoolean("isOsInstallAllowed", switches::IsOsInstallAllowed());
   source->AddBoolean("isOobeFlow", is_oobe_flow);
   source->AddBoolean("isMaterialNext", features::IsOobeMaterialNextEnabled());
+  source->AddBoolean("isChoobeEnabled", features::IsOobeChoobeEnabled());
 
   // Configure shared resources
   AddProductLogoResources(source);
@@ -479,6 +483,10 @@
 
   AddScreenHandler(std::make_unique<ThemeSelectionScreenHandler>());
 
+  if (features::IsOobeChoobeEnabled()) {
+    AddScreenHandler(std::make_unique<ChoobeScreenHandler>());
+  }
+
   AddScreenHandler(std::make_unique<LocalStateErrorScreenHandler>());
 
   AddScreenHandler(std::make_unique<CryptohomeRecoveryScreenHandler>());
@@ -621,7 +629,8 @@
 
   source->AddResourcePath("spinner.json", IDR_LOGIN_SPINNER_ANIMATION);
   source->OverrideContentSecurityPolicy(
-      network::mojom::CSPDirectiveName::WorkerSrc, "worker-src blob: 'self';");
+      network::mojom::CSPDirectiveName::WorkerSrc,
+      "worker-src blob: chrome://resources 'self';");
 }
 
 CoreOobeView* OobeUI::GetCoreOobeView() {
diff --git a/chrome/browser/ui/webui/ash/multidevice_setup/multidevice_setup_localized_strings_provider.cc b/chrome/browser/ui/webui/ash/multidevice_setup/multidevice_setup_localized_strings_provider.cc
index 8f34daf..365c233 100644
--- a/chrome/browser/ui/webui/ash/multidevice_setup/multidevice_setup_localized_strings_provider.cc
+++ b/chrome/browser/ui/webui/ash/multidevice_setup/multidevice_setup_localized_strings_provider.cc
@@ -145,7 +145,8 @@
   html_source->AddResourcePath("multidevice_setup_light.json",
                                IDR_MULTIDEVICE_SETUP_ANIMATION_LIGHT);
   html_source->OverrideContentSecurityPolicy(
-      network::mojom::CSPDirectiveName::WorkerSrc, "worker-src blob: 'self';");
+      network::mojom::CSPDirectiveName::WorkerSrc,
+      "worker-src blob: chrome://resources 'self';");
 }
 
 void AddLocalizedValuesToBuilder(::login::LocalizedValuesBuilder* builder) {
diff --git a/chrome/browser/ui/webui/extensions/extensions_internals_source.cc b/chrome/browser/ui/webui/extensions/extensions_internals_source.cc
index 3003389..05816ec0b 100644
--- a/chrome/browser/ui/webui/extensions/extensions_internals_source.cc
+++ b/chrome/browser/ui/webui/extensions/extensions_internals_source.cc
@@ -423,7 +423,7 @@
       listener_data.Set(kIsLazyKey, listener_entry->IsLazy());
       listener_data.Set(kListenerUrlKey, listener_entry->listener_url().spec());
       // Add the filter if one exists.
-      base::Value::Dict* const filter = listener_entry->filter();
+      const base::Value::Dict* const filter = listener_entry->filter();
       if (filter) {
         listener_data.Set(kFilterKey, filter->Clone());
       }
diff --git a/chrome/browser/ui/webui/nearby_share/nearby_share_dialog_ui.cc b/chrome/browser/ui/webui/nearby_share/nearby_share_dialog_ui.cc
index b08b4fcd..0b46b09f 100644
--- a/chrome/browser/ui/webui/nearby_share/nearby_share_dialog_ui.cc
+++ b/chrome/browser/ui/webui/nearby_share/nearby_share_dialog_ui.cc
@@ -88,7 +88,8 @@
   // increases, set this as the default so manual override is no longer
   // required.
   html_source->OverrideContentSecurityPolicy(
-      network::mojom::CSPDirectiveName::WorkerSrc, "worker-src blob: 'self';");
+      network::mojom::CSPDirectiveName::WorkerSrc,
+      "worker-src blob: chrome://resources 'self';");
 
   html_source->AddBoolean(
       "isOnePageOnboardingEnabled",
diff --git a/chrome/browser/ui/webui/offline/offline_internals_ui_message_handler.cc b/chrome/browser/ui/webui/offline/offline_internals_ui_message_handler.cc
index acfed83..d52657a1 100644
--- a/chrome/browser/ui/webui/offline/offline_internals_ui_message_handler.cc
+++ b/chrome/browser/ui/webui/offline/offline_internals_ui_message_handler.cc
@@ -315,9 +315,10 @@
   message.append("\n");
 
   // Construct a JSON array containing all the URLs. To guard against malicious
-  // URLs that might contain special characters, we create a ListValue and then
-  // serialize it into JSON, instead of doing direct string manipulation.
-  base::ListValue urls;
+  // URLs that might contain special characters, we create a base::Value::List
+  // and then serialize it into JSON, instead of doing direct string
+  // manipulation.
+  base::Value::List urls;
   for (const auto& prefetch_url : prefetch_urls) {
     urls.Append(prefetch_url.url.spec());
   }
diff --git a/chrome/browser/ui/webui/settings/ash/accessibility_section.cc b/chrome/browser/ui/webui/settings/ash/accessibility_section.cc
index ae23da2..b25201f5 100644
--- a/chrome/browser/ui/webui/settings/ash/accessibility_section.cc
+++ b/chrome/browser/ui/webui/settings/ash/accessibility_section.cc
@@ -23,6 +23,7 @@
 #include "chrome/browser/ui/webui/settings/accessibility_main_handler.h"
 #include "chrome/browser/ui/webui/settings/ash/accessibility_handler.h"
 #include "chrome/browser/ui/webui/settings/ash/search/search_tag_registry.h"
+#include "chrome/browser/ui/webui/settings/ash/select_to_speak_handler.h"
 #include "chrome/browser/ui/webui/settings/ash/switch_access_handler.h"
 #include "chrome/browser/ui/webui/settings/ash/tts_handler.h"
 #include "chrome/browser/ui/webui/settings/captions_handler.h"
@@ -721,12 +722,20 @@
        IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_DISABLED_DESCRIPTION},
       {"selectToSpeakLinkTitle",
        IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_LINK_TITLE},
+      {"selectToSpeakOptionsLanguagesFilterDescription",
+       IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_LANGUAGES_FILTER_DESCRIPTION},
+      {"selectToSpeakOptionsVoiceDescription",
+       IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_VOICE_DESCRIPTION},
       {"selectToSpeakOptionsVoiceSwitchingDescription",
        IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_VOICE_SWITCHING_DESCRIPTION},
       {"selectToSpeakOptionsEnhancedNetworkVoicesDescription",
        IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_ENHANCED_NETWORK_VOICES_DESCRIPTION},
       {"selectToSpeakOptionsEnhancedNetworkVoicesSubtitle",
        IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_ENHANCED_NETWORK_VOICES_SUBTITLE},
+      {"selectToSpeakOptionsEnhancedNetworkVoice",
+       IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_ENHANCED_NETWORK_VOICE},
+      {"selectToSpeakOptionsNaturalVoiceName",
+       IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_NATURAL_VOICE_NAME},
       {"selectToSpeakOptionsHighlightDescription",
        IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_HIGHLIGHT_DESCRIPTION},
       {"selectToSpeakOptionsHighlightColorDescription",
@@ -757,6 +766,12 @@
        IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_HIGHLIGHT},
       {"selectToSpeakOptionsSpeech",
        IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_SPEECH},
+      {"selectToSpeakOptionsDeviceLanguage",
+       IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_DEVICE_LANGUAGE},
+      {"selectToSpeakOptionsSystemVoice",
+       IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_SYSTEM_VOICE},
+      {"selectToSpeakOptionsDefaultNetworkVoice",
+       IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_DEFAULT_NETWORK_VOICE},
       {"selectToSpeakOptionsLabel",
        IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_OPTIONS_LABEL},
       {"selectToSpeakTitle", IDS_SETTINGS_ACCESSIBILITY_SELECT_TO_SPEAK_TITLE},
@@ -961,6 +976,7 @@
   web_ui->AddMessageHandler(
       std::make_unique<SwitchAccessHandler>(profile()->GetPrefs()));
   web_ui->AddMessageHandler(std::make_unique<TtsHandler>());
+  web_ui->AddMessageHandler(std::make_unique<SelectToSpeakHandler>());
   web_ui->AddMessageHandler(
       std::make_unique<::settings::FontHandler>(profile()));
   web_ui->AddMessageHandler(
diff --git a/chrome/browser/ui/webui/settings/ash/apps_section.cc b/chrome/browser/ui/webui/settings/ash/apps_section.cc
index 12948128..b3a8a971 100644
--- a/chrome/browser/ui/webui/settings/ash/apps_section.cc
+++ b/chrome/browser/ui/webui/settings/ash/apps_section.cc
@@ -415,6 +415,7 @@
       {"appNotificationsDoNotDisturbDescription",
        IDS_SETTINGS_APP_NOTIFICATIONS_DND_ENABLED_SUBLABEL_TEXT},
       {"appBadgingToggleLabel", IDS_SETTINGS_APP_BADGING_TOGGLE_LABEL},
+      {"appBadgingToggleSublabel", IDS_SETTINGS_APP_BADGING_TOGGLE_SUBLABEL},
   };
   html_source->AddLocalizedStrings(kLocalizedStrings);
 
diff --git a/chrome/browser/ui/webui/settings/ash/fake_os_settings_section.cc b/chrome/browser/ui/webui/settings/ash/fake_os_settings_section.cc
index eee4e11..7a81be12 100644
--- a/chrome/browser/ui/webui/settings/ash/fake_os_settings_section.cc
+++ b/chrome/browser/ui/webui/settings/ash/fake_os_settings_section.cc
@@ -6,13 +6,17 @@
 
 #include <sstream>
 
+#include "base/containers/contains.h"
+#include "chrome/browser/ui/webui/settings/chromeos/constants/routes.mojom-shared.h"
 #include "chrome/grit/generated_resources.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace ash::settings {
 
 namespace mojom {
 using ::chromeos::settings::mojom::Section;
 using ::chromeos::settings::mojom::Setting;
+using ::chromeos::settings::mojom::Subpage;
 }  // namespace mojom
 
 FakeOsSettingsSection::FakeOsSettingsSection(mojom::Section section)
@@ -20,6 +24,42 @@
 
 FakeOsSettingsSection::~FakeOsSettingsSection() = default;
 
+void FakeOsSettingsSection::AddSubpageAndSetting(
+    absl::optional<chromeos::settings::mojom::Subpage> subpage,
+    absl::optional<chromeos::settings::mojom::Setting> setting) {
+  if (subpage) {
+    if (!base::Contains(subpages_, subpage.value())) {
+      // This is a new `subpage` and settings on this subpage.
+      subpages_.insert({subpage.value(), {}});
+    }
+
+    if (setting) {
+      subpages_[subpage.value()].push_back(setting.value());
+    }
+  } else {
+    // `subpage` is empty, this is a setting directly on the section.
+    CHECK(setting);
+    settings_.push_back(setting.value());
+  }
+}
+
+void FakeOsSettingsSection::RegisterHierarchy(
+    HierarchyGenerator* generator) const {
+  for (auto& it : subpages_) {
+    generator->RegisterTopLevelSubpage(
+        /* Arbitrary title string */ IDS_SETTINGS_KERBEROS_ACCOUNTS_PAGE_TITLE,
+        it.first,
+        /* Arbitrary icon */ mojom::SearchResultIcon::kAuthKey,
+        mojom::SearchResultDefaultRank::kMedium,
+        "fake/url_path_with_parameters");
+    RegisterNestedSettingBulk(it.first, it.second, generator);
+  }
+
+  for (auto& it : settings_) {
+    generator->RegisterTopLevelAltSetting(it);
+  }
+}
+
 int FakeOsSettingsSection::GetSectionNameMessageId() const {
   return IDS_INTERNAL_APP_SETTINGS;
 }
diff --git a/chrome/browser/ui/webui/settings/ash/fake_os_settings_section.h b/chrome/browser/ui/webui/settings/ash/fake_os_settings_section.h
index 05afaf2..448c693 100644
--- a/chrome/browser/ui/webui/settings/ash/fake_os_settings_section.h
+++ b/chrome/browser/ui/webui/settings/ash/fake_os_settings_section.h
@@ -10,6 +10,8 @@
 
 #include "base/values.h"
 #include "chrome/browser/ui/webui/settings/ash/os_settings_section.h"
+#include "chrome/browser/ui/webui/settings/chromeos/constants/setting.mojom-shared.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace ash::settings {
 
@@ -28,9 +30,15 @@
     return logged_metrics_;
   }
 
+  // Add fake subpage and settings information. If `subpage` is nullopt, the
+  // `setting` must exist and is attached directly to the section.
+  void AddSubpageAndSetting(
+      absl::optional<chromeos::settings::mojom::Subpage> subpage,
+      absl::optional<chromeos::settings::mojom::Setting> setting);
+
   // OsSettingsSection:
   void AddLoadTimeData(content::WebUIDataSource* html_source) override {}
-  void RegisterHierarchy(HierarchyGenerator* generator) const override {}
+  void RegisterHierarchy(HierarchyGenerator* generator) const override;
 
   // Returns the settings app name as a default value.
   int GetSectionNameMessageId() const override;
@@ -58,6 +66,10 @@
 
  private:
   const chromeos::settings::mojom::Section section_;
+  std::map<chromeos::settings::mojom::Subpage,
+           std::vector<chromeos::settings::mojom::Setting>>
+      subpages_;
+  std::vector<chromeos::settings::mojom::Setting> settings_;
   // Use mutable to modify this vector within the overridden const LogMetric.
   mutable std::vector<chromeos::settings::mojom::Setting> logged_metrics_;
 };
diff --git a/chrome/browser/ui/webui/settings/ash/fake_os_settings_sections.cc b/chrome/browser/ui/webui/settings/ash/fake_os_settings_sections.cc
index 2b47f28..419eacb 100644
--- a/chrome/browser/ui/webui/settings/ash/fake_os_settings_sections.cc
+++ b/chrome/browser/ui/webui/settings/ash/fake_os_settings_sections.cc
@@ -4,12 +4,15 @@
 
 #include "chrome/browser/ui/webui/settings/ash/fake_os_settings_sections.h"
 
+#include "base/rand_util.h"
 #include "chrome/browser/ui/webui/settings/ash/constants/constants_util.h"
 #include "chrome/browser/ui/webui/settings/ash/fake_os_settings_section.h"
+#include "chrome/browser/ui/webui/settings/chromeos/constants/routes.mojom-shared.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace ash::settings {
 
-FakeOsSettingsSections::FakeOsSettingsSections() : OsSettingsSections() {
+FakeOsSettingsSections::FakeOsSettingsSections() {
   for (const auto& section : AllSections()) {
     auto fake_section = std::make_unique<FakeOsSettingsSection>(section);
     sections_map_[section] = fake_section.get();
@@ -19,4 +22,28 @@
 
 FakeOsSettingsSections::~FakeOsSettingsSections() = default;
 
+void FakeOsSettingsSections::FillWithFakeSettings() {
+  std::vector<chromeos::settings::mojom::Subpage> shuffled_subpages =
+      AllSubpages();
+  base::RandomShuffle(shuffled_subpages.begin(), shuffled_subpages.end());
+
+  std::vector<chromeos::settings::mojom::Setting> shuffled_settings =
+      AllSettings();
+  base::RandomShuffle(shuffled_settings.begin(), shuffled_settings.end());
+
+  auto subpage_it = shuffled_subpages.begin();
+  auto setting_it = shuffled_settings.begin();
+
+  for (const auto& section : AllSections()) {
+    auto* fake_section =
+        static_cast<FakeOsSettingsSection*>(sections_map_[section]);
+    // one subpage with one setting.
+    fake_section->AddSubpageAndSetting(*subpage_it, *setting_it);
+    // one setting directly on the section.
+    fake_section->AddSubpageAndSetting(absl::nullopt, *setting_it);
+    subpage_it++;
+    setting_it++;
+  }
+}
+
 }  // namespace ash::settings
diff --git a/chrome/browser/ui/webui/settings/ash/fake_os_settings_sections.h b/chrome/browser/ui/webui/settings/ash/fake_os_settings_sections.h
index c525ed08..405412aa 100644
--- a/chrome/browser/ui/webui/settings/ash/fake_os_settings_sections.h
+++ b/chrome/browser/ui/webui/settings/ash/fake_os_settings_sections.h
@@ -17,6 +17,8 @@
   FakeOsSettingsSections& operator=(const FakeOsSettingsSections& other) =
       delete;
   ~FakeOsSettingsSections() override;
+
+  void FillWithFakeSettings();
 };
 
 }  // namespace ash::settings
diff --git a/chrome/browser/ui/webui/settings/ash/hierarchy.cc b/chrome/browser/ui/webui/settings/ash/hierarchy.cc
index 04f2251..9aec3b4 100644
--- a/chrome/browser/ui/webui/settings/ash/hierarchy.cc
+++ b/chrome/browser/ui/webui/settings/ash/hierarchy.cc
@@ -5,7 +5,9 @@
 #include "chrome/browser/ui/webui/settings/ash/hierarchy.h"
 
 #include <utility>
+#include <vector>
 
+#include "base/containers/contains.h"
 #include "chrome/browser/ui/webui/settings/ash/constants/constants_util.h"
 #include "chrome/browser/ui/webui/settings/ash/os_settings_section.h"
 #include "chrome/browser/ui/webui/settings/ash/os_settings_sections.h"
@@ -290,4 +292,112 @@
   return hierarchy_strings;
 }
 
+#ifdef DCHECK
+namespace {
+
+// Number of spaces for each indent level.
+constexpr int kIndent = 2;
+
+void PrintSettings(std::ostream& os,
+                   int indent,
+                   std::vector<mojom::Setting>& collection) {
+  for (auto& it : collection) {
+    os << std::string(indent, ' ') << "(s)" << it << std::endl;
+  }
+}
+
+void PrintSubpages(
+    std::ostream& os,
+    int indent,
+    std::vector<mojom::Subpage>& collection,
+    std::map<mojom::Subpage, std::vector<mojom::Subpage>>& subpage_subpage,
+    std::map<mojom::Subpage, std::vector<mojom::Setting>>& subpage_setting) {
+  for (auto& it : collection) {
+    os << std::string(indent, ' ') << "(p)" << it << std::endl;
+    PrintSettings(os, indent + kIndent, subpage_setting[it]);
+    PrintSubpages(os, indent + kIndent, subpage_subpage[it], subpage_subpage,
+                  subpage_setting);
+  }
+}
+
+}  // namespace
+
+std::ostream& operator<<(std::ostream& os, const Hierarchy& h) {
+  // This method logs all sections -> subpages -> settings
+
+  // First restructure the `Hierarchy` data into hierarchies we can work with.
+  std::map<mojom::Section, std::vector<mojom::Subpage>> section_subpage;
+  std::map<mojom::Section, std::vector<mojom::Setting>> section_setting;
+  std::map<mojom::Subpage, std::vector<mojom::Subpage>> subpage_subpage;
+  std::map<mojom::Subpage, std::vector<mojom::Setting>> subpage_setting;
+  std::vector<mojom::Subpage> none_exist_subpage;
+  std::vector<mojom::Setting> none_exist_setting;
+
+  for (auto& section_id : AllSections()) {
+    section_subpage.insert({section_id, {}});
+    section_setting.insert({section_id, {}});
+  }
+
+  for (auto& subpage_id : AllSubpages()) {
+    subpage_subpage.insert({subpage_id, {}});
+    subpage_setting.insert({subpage_id, {}});
+
+    if (!base::Contains(h.subpage_map_, subpage_id)) {
+      none_exist_subpage.push_back(subpage_id);
+      continue;
+    }
+
+    auto& subpage = h.GetSubpageMetadata(subpage_id);
+    if (subpage.parent_subpage) {
+      // if this is a nested subpage, only record the immediate parent subpage.
+      subpage_subpage[subpage.parent_subpage.value()].push_back(subpage_id);
+    } else {
+      section_subpage[subpage.section].push_back(subpage_id);
+    }
+  }
+
+  for (auto& setting_id : AllSettings()) {
+    if (!base::Contains(h.setting_map_, setting_id)) {
+      none_exist_setting.push_back(setting_id);
+      continue;
+    }
+    auto& setting = h.GetSettingMetadata(setting_id);
+
+    if (setting.primary.subpage) {
+      subpage_setting[setting.primary.subpage.value()].push_back(setting_id);
+    } else {
+      section_setting[setting.primary.section].push_back(setting_id);
+    }
+
+    for (auto& alt : setting.alternates) {
+      if (alt.subpage) {
+        subpage_setting[alt.subpage.value()].push_back(setting_id);
+      } else {
+        section_setting[alt.section].push_back(setting_id);
+      }
+    }
+  }
+
+  // Print out all the information.
+  os << "Settings Hierarchy:" << std::endl;
+  for (auto& section_id : AllSections()) {
+    auto& subpages = section_subpage[section_id];
+    auto& settings = section_setting[section_id];
+
+    os << "[" << section_id << "]" << std::endl;
+    PrintSubpages(os, kIndent, subpages, subpage_subpage, subpage_setting);
+    PrintSettings(os, kIndent, settings);
+  }
+
+  os << "Unused Subpages: " << std::endl;
+  PrintSubpages(os, kIndent, none_exist_subpage, subpage_subpage,
+                subpage_setting);
+
+  os << "Unused Settings: " << std::endl;
+  PrintSettings(os, kIndent, none_exist_setting);
+
+  return os;
+}
+#endif
+
 }  // namespace ash::settings
diff --git a/chrome/browser/ui/webui/settings/ash/hierarchy.h b/chrome/browser/ui/webui/settings/ash/hierarchy.h
index 6007660..b48040b 100644
--- a/chrome/browser/ui/webui/settings/ash/hierarchy.h
+++ b/chrome/browser/ui/webui/settings/ash/hierarchy.h
@@ -161,6 +161,7 @@
 
  private:
   class PerSectionHierarchyGenerator;
+  friend std::ostream& operator<<(std::ostream& os, const Hierarchy& h);
 
   // Generates an array with the Settings app name and |section|'s name.
   std::vector<std::u16string> GenerateHierarchyStrings(
@@ -172,9 +173,14 @@
       OsSettingsIdentifier id,
       const std::string& url_to_modify) const;
 
-  const OsSettingsSections* sections_;
+  const OsSettingsSections* sections_;  // Owned by |OsSettingsManager|
 };
 
+#ifdef DCHECK
+// For logging use only. Prints out text representation of the `Hierarchy`.
+std::ostream& operator<<(std::ostream& os, const Hierarchy& h);
+#endif
+
 }  // namespace ash::settings
 
 #endif  // CHROME_BROWSER_UI_WEBUI_SETTINGS_ASH_HIERARCHY_H_
diff --git a/chrome/browser/ui/webui/settings/ash/hierarchy_unittest.cc b/chrome/browser/ui/webui/settings/ash/hierarchy_unittest.cc
new file mode 100644
index 0000000..36f85388
--- /dev/null
+++ b/chrome/browser/ui/webui/settings/ash/hierarchy_unittest.cc
@@ -0,0 +1,43 @@
+// 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/ui/webui/settings/ash/hierarchy.h"
+
+#include <sstream>
+
+#include "chrome/browser/ui/webui/settings/ash/fake_os_settings_sections.h"
+#include "chrome/browser/ui/webui/settings/chromeos/constants/routes.mojom-shared.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace ash::settings {
+
+namespace {
+
+class HierarchyTest : public testing::Test {
+ protected:
+  HierarchyTest() { fake_sections_.FillWithFakeSettings(); }
+
+  ~HierarchyTest() override = default;
+
+  FakeOsSettingsSections fake_sections_;
+};
+
+TEST_F(HierarchyTest, Init) {
+  // Should successfully initialize with fake data.
+  Hierarchy h(&fake_sections_);
+
+#ifdef DCHECK
+  // Should print out some text with debug output.
+  std::stringstream os;
+  os << h;
+  ASSERT_GT(os.str().size(), 0u);
+#endif
+
+  // Should contain "About" section.
+  h.GetSectionMetadata(chromeos::settings::mojom::Section::kAboutChromeOs);
+}
+
+}  // namespace
+
+}  // namespace ash::settings
diff --git a/chrome/browser/ui/webui/settings/ash/os_settings_recovery_browsertest.cc b/chrome/browser/ui/webui/settings/ash/os_settings_recovery_browsertest.cc
index 9376586..b39306f 100644
--- a/chrome/browser/ui/webui/settings/ash/os_settings_recovery_browsertest.cc
+++ b/chrome/browser/ui/webui/settings/ash/os_settings_recovery_browsertest.cc
@@ -4,6 +4,7 @@
 
 #include "ash/constants/ash_features.h"
 #include "base/test/scoped_feature_list.h"
+#include "chrome/browser/ash/login/quick_unlock/quick_unlock_factory.h"
 #include "chrome/browser/ash/login/test/cryptohome_mixin.h"
 #include "chrome/browser/ash/login/test/logged_in_user_mixin.h"
 #include "chrome/browser/ui/webui/settings/ash/os_settings_browser_test_mixin.h"
@@ -19,8 +20,7 @@
 
 namespace {
 
-const char kPassword[] = "asdf";
-const char kAuthToken[] = "123";
+const char kPassword[] = "the-password";
 
 }  // namespace
 
@@ -28,13 +28,15 @@
 
 class OSSettingsRecoveryTest : public MixinBasedInProcessBrowserTest {
  public:
-  void SetUpOnMainThread() override {
-    MixinBasedInProcessBrowserTest::SetUpOnMainThread();
-
+  OSSettingsRecoveryTest() {
     cryptohome_.set_enable_auth_check(true);
+    cryptohome_.set_supports_low_entropy_credentials(true);
     cryptohome_.MarkUserAsExisting(GetAccountId());
     cryptohome_.AddGaiaPassword(GetAccountId(), kPassword);
+  }
 
+  void SetUpOnMainThread() override {
+    MixinBasedInProcessBrowserTest::SetUpOnMainThread();
     logged_in_user_mixin_.LogInUser();
   }
 
@@ -44,6 +46,7 @@
     CHECK(!os_settings_driver_remote_.is_bound());
     os_settings_driver_remote_ =
         mojo::Remote{os_settings_mixin_.OpenOSSettings()};
+
     CHECK(!lock_screen_settings_remote_.is_bound());
     lock_screen_settings_remote_ = mojo::Remote{
         mojom::OSSettingsDriverAsyncWaiter{os_settings_driver_remote_.get()}
@@ -59,12 +62,13 @@
     return logged_in_user_mixin_.GetAccountId();
   }
 
- private:
+ protected:
   CryptohomeMixin cryptohome_{&mixin_host_};
   LoggedInUserMixin logged_in_user_mixin_{
       &mixin_host_, LoggedInUserMixin::LogInType::kRegular,
       embedded_test_server(), this};
   OSSettingsBrowserTestMixin os_settings_mixin_{&mixin_host_};
+
   mojo::Remote<mojom::OSSettingsDriver> os_settings_driver_remote_;
   mojo::Remote<mojom::LockScreenSettings> lock_screen_settings_remote_;
 };
@@ -103,76 +107,53 @@
   lock_screen_settings.AssertRecoveryControlVisibility(true);
 }
 
-// TODO(b/239416325): This should eventually check state in fake user data
-// auth, not in the auth factor config mojo service.
 IN_PROC_BROWSER_TEST_F(OSSettingsRecoveryTestWithFeature, CheckingEnables) {
-  auto auth_factor_config = auth::GetAuthFactorConfigForTesting();
-  auto recovery_editor = auth::GetRecoveryFactorEditorForTesting();
-
-  ASSERT_EQ(auth::mojom::RecoveryFactorEditor::ConfigureResult::kSuccess,
-            recovery_editor.Configure(kAuthToken, false));
+  EXPECT_FALSE(cryptohome_.HasRecoveryFactor(GetAccountId()));
 
   mojom::LockScreenSettingsAsyncWaiter lock_screen_settings =
       OpenLockScreenSettings();
   lock_screen_settings.AssertRecoveryConfigured(false);
   lock_screen_settings.EnableRecoveryConfiguration();
+  lock_screen_settings.AssertRecoveryConfigured(true);
 
-  EXPECT_TRUE(auth_factor_config.IsConfigured(
-      kAuthToken, auth::mojom::AuthFactor::kRecovery));
+  EXPECT_TRUE(cryptohome_.HasRecoveryFactor(GetAccountId()));
 }
 
-// TODO(b/239416325): This should eventually check state in fake user data
-// auth, not in the auth factor config mojo service.
-
-// The following test set the cryptohome recovery toggle is on.
-// Clicks on the recovery toggle, expecting the recovery dialog to show up.
-// Clicks on the cancel button of the dialog.
-// Expected result: the dialog disappears and the toggle is still on.
+// The following test sets the cryptohome recovery toggle to "on".
+// It clicks on the recovery toggle, expecting the recovery dialog to show up.
+// It then clicks on the cancel button of the dialog.
+// Expected result: The dialog disappears and the toggle is still on.
 IN_PROC_BROWSER_TEST_F(OSSettingsRecoveryTestWithFeature,
                        UncheckingDisablesAndCancelClick) {
-  auto auth_factor_config = auth::GetAuthFactorConfigForTesting();
-  auto recovery_editor = auth::GetRecoveryFactorEditorForTesting();
-
-  ASSERT_EQ(auth::mojom::RecoveryFactorEditor::ConfigureResult::kSuccess,
-            recovery_editor.Configure(/*auth_token=*/kAuthToken,
-                                      /*enabled=*/true));
+  cryptohome_.AddRecoveryFactor(GetAccountId());
 
   mojom::LockScreenSettingsAsyncWaiter lock_screen_settings =
       OpenLockScreenSettings();
   lock_screen_settings.AssertRecoveryConfigured(true);
   lock_screen_settings.DisableRecoveryConfiguration(
       mojom::LockScreenSettings::RecoveryDialogAction::CancelDialog);
+  lock_screen_settings.AssertRecoveryConfigured(true);
   // After the CancelClick on the dialog, the recovery configuration
   // should remain enabled.
-  EXPECT_TRUE(auth_factor_config.IsConfigured(
-      /*auth_token=*/kAuthToken,
-      /*factor=*/auth::mojom::AuthFactor::kRecovery));
+  EXPECT_TRUE(cryptohome_.HasRecoveryFactor(GetAccountId()));
 }
 
-// TODO(b/239416325): This should eventually check state in fake user data
-// auth, not in the auth factor config mojo service.
-
-// The following test set the cryptohome recovery toggle is on.
-// Clicks on the recovery toggle, expecting the recovery dialog to show up.
-// Clicks on the disable button of the dialog.
-// Expected result: the dialog disappears and the toggle is off.
+// The following test sets the cryptohome recovery toggle to "on".
+// It clicks on the recovery toggle, expecting the recovery dialog to show up.
+// It then clicks on the disable button of the dialog.
+// Expected result: The dialog disappears and the toggle is off.
 IN_PROC_BROWSER_TEST_F(OSSettingsRecoveryTestWithFeature,
                        UncheckingDisablesAndDisableClick) {
-  auto auth_factor_config = auth::GetAuthFactorConfigForTesting();
-  auto recovery_editor = auth::GetRecoveryFactorEditorForTesting();
-
-  ASSERT_EQ(auth::mojom::RecoveryFactorEditor::ConfigureResult::kSuccess,
-            recovery_editor.Configure(/*auth_token=*/kAuthToken,
-                                      /*enabled=*/true));
+  cryptohome_.AddRecoveryFactor(GetAccountId());
 
   mojom::LockScreenSettingsAsyncWaiter lock_screen_settings =
       OpenLockScreenSettings();
   lock_screen_settings.AssertRecoveryConfigured(true);
   lock_screen_settings.DisableRecoveryConfiguration(
       mojom::LockScreenSettings::RecoveryDialogAction::ConfirmDisabling);
-  EXPECT_FALSE(auth_factor_config.IsConfigured(
-      /*auth_token=*/kAuthToken,
-      /*factor=*/auth::mojom::AuthFactor::kRecovery));
+  lock_screen_settings.AssertRecoveryConfigured(false);
+
+  EXPECT_FALSE(cryptohome_.HasRecoveryFactor(GetAccountId()));
 }
 
 }  // namespace ash::settings
diff --git a/chrome/browser/ui/webui/settings/ash/os_settings_ui.cc b/chrome/browser/ui/webui/settings/ash/os_settings_ui.cc
index dc85176..ba84e6f 100644
--- a/chrome/browser/ui/webui/settings/ash/os_settings_ui.cc
+++ b/chrome/browser/ui/webui/settings/ash/os_settings_ui.cc
@@ -15,6 +15,7 @@
 #include "ash/webui/personalization_app/search/search.mojom.h"
 #include "ash/webui/personalization_app/search/search_handler.h"
 #include "base/metrics/histogram_functions.h"
+#include "chrome/browser/ash/login/quick_unlock/quick_unlock_factory.h"
 #include "chrome/browser/ash/web_applications/personalization_app/personalization_app_manager.h"
 #include "chrome/browser/ash/web_applications/personalization_app/personalization_app_manager_factory.h"
 #include "chrome/browser/ash/web_applications/personalization_app/personalization_app_utils.h"
@@ -229,12 +230,14 @@
 
 void OSSettingsUI::BindInterface(
     mojo::PendingReceiver<auth::mojom::AuthFactorConfig> receiver) {
-  auth::BindToAuthFactorConfig(std::move(receiver));
+  auth::BindToAuthFactorConfig(std::move(receiver),
+                               quick_unlock::QuickUnlockFactory::GetDelegate());
 }
 
 void OSSettingsUI::BindInterface(
     mojo::PendingReceiver<auth::mojom::RecoveryFactorEditor> receiver) {
-  auth::BindToRecoveryFactorEditor(std::move(receiver));
+  auth::BindToRecoveryFactorEditor(
+      std::move(receiver), quick_unlock::QuickUnlockFactory::GetDelegate());
 }
 
 WEB_UI_CONTROLLER_TYPE_IMPL(OSSettingsUI)
diff --git a/chrome/browser/ui/webui/settings/ash/privacy_section.cc b/chrome/browser/ui/webui/settings/ash/privacy_section.cc
index e8af46b..3e5b1c1 100644
--- a/chrome/browser/ui/webui/settings/ash/privacy_section.cc
+++ b/chrome/browser/ui/webui/settings/ash/privacy_section.cc
@@ -400,6 +400,8 @@
        IDS_OS_SETTINGS_PRIVACY_HUB_NO_MICROPHONE_CONNECTED_TEXT},
       {"geolocationToggleTitle",
        IDS_OS_SETTINGS_PRIVACY_HUB_GEOLOCATION_TOGGLE_TITLE},
+      {"microphoneHwToggleTooltip",
+       IDS_OS_SETTINGS_PRIVACY_HUB_HW_MICROPHONE_TOGGLE_TOOLTIP},
   };
   html_source->AddLocalizedStrings(kLocalizedStrings);
 
diff --git a/chrome/browser/ui/webui/settings/ash/select_to_speak_handler.cc b/chrome/browser/ui/webui/settings/ash/select_to_speak_handler.cc
new file mode 100644
index 0000000..fc046d1
--- /dev/null
+++ b/chrome/browser/ui/webui/settings/ash/select_to_speak_handler.cc
@@ -0,0 +1,107 @@
+// 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/ui/webui/settings/ash/select_to_speak_handler.h"
+
+#include "base/bind.h"
+#include "base/json/json_reader.h"
+#include "base/values.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/speech/extension_api/tts_engine_extension_api.h"
+#include "chrome/browser/speech/extension_api/tts_engine_extension_observer_chromeos.h"
+#include "chrome/browser/speech/extension_api/tts_extension_api.h"
+#include "chrome/browser/ui/chrome_pages.h"
+#include "chrome/browser/ui/webui/settings/chromeos/constants/routes.mojom.h"
+#include "chrome/common/extensions/extension_constants.h"
+#include "chrome/grit/generated_resources.h"
+#include "content/public/browser/tts_controller.h"
+#include "content/public/browser/web_ui.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/process_manager.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_set.h"
+#include "extensions/common/manifest_handlers/options_page_info.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace ash::settings {
+
+SelectToSpeakHandler::SelectToSpeakHandler() = default;
+
+SelectToSpeakHandler::~SelectToSpeakHandler() = default;
+
+void SelectToSpeakHandler::HandleGetAppLocale(const base::Value::List& args) {
+  const std::string& app_locale = g_browser_process->GetApplicationLocale();
+  AllowJavascript();
+  FireWebUIListener("app-locale-updated", base::Value(app_locale));
+}
+
+void SelectToSpeakHandler::OnVoicesChanged() {
+  content::TtsController* tts_controller =
+      content::TtsController::GetInstance();
+  std::vector<content::VoiceData> voices;
+  tts_controller->GetVoices(
+      Profile::FromWebUI(web_ui()),
+      GURL(chrome::GetOSSettingsUrl(
+          chromeos::settings::mojom::kSelectToSpeakSubpagePath)),
+      &voices);
+  base::Value::List responses;
+  for (const auto& voice : voices) {
+    base::Value::Dict response;
+    base::Value::List event_types;
+    std::string language_code;
+    std::string language_and_country_code = voice.lang;
+    if (!language_and_country_code.empty()) {
+      // Normalize underscores to hyphens because enhanced voices use
+      // underscores, and l10n_util::GetLanguage uses hyphens.
+      std::replace(language_and_country_code.begin(),
+                   language_and_country_code.end(), '_', '-');
+      language_code = l10n_util::GetLanguage(language_and_country_code);
+      response.Set(
+          "displayLanguage",
+          l10n_util::GetDisplayNameForLocale(
+              language_code, g_browser_process->GetApplicationLocale(), true));
+      response.Set("displayLanguageAndCountry",
+                   l10n_util::GetDisplayNameForLocale(
+                       language_and_country_code,
+                       g_browser_process->GetApplicationLocale(), true));
+    }
+    for (auto& event : voice.events) {
+      const char* event_name_constant = TtsEventTypeToString(event);
+      event_types.Append(event_name_constant);
+    }
+    response.Set("eventTypes", std::move(event_types));
+    response.Set("extensionId", voice.engine_id);
+    response.Set("voiceName", voice.name);
+    response.Set("lang", voice.lang);
+    responses.Append(std::move(response));
+  }
+  AllowJavascript();
+  FireWebUIListener("all-sts-voice-data-updated", responses);
+}
+
+void SelectToSpeakHandler::RegisterMessages() {
+  SettingsWithTtsPreviewHandler::RegisterMessages();
+  web_ui()->RegisterMessageCallback(
+      "getAllTtsVoiceDataForSts",
+      base::BindRepeating(
+          &SettingsWithTtsPreviewHandler::HandleGetAllTtsVoiceData,
+          base::Unretained(this)));
+  web_ui()->RegisterMessageCallback(
+      "getAppLocale",
+      base::BindRepeating(&SelectToSpeakHandler::HandleGetAppLocale,
+                          base::Unretained(this)));
+  web_ui()->RegisterMessageCallback(
+      "previewTtsVoiceForSts",
+      base::BindRepeating(&SettingsWithTtsPreviewHandler::HandlePreviewTtsVoice,
+                          base::Unretained(this)));
+}
+
+GURL SelectToSpeakHandler::GetSourceURL() const {
+  return GURL(chrome::GetOSSettingsUrl(
+      chromeos::settings::mojom::kSelectToSpeakSubpagePath));
+}
+
+}  // namespace ash::settings
diff --git a/chrome/browser/ui/webui/settings/ash/select_to_speak_handler.h b/chrome/browser/ui/webui/settings/ash/select_to_speak_handler.h
new file mode 100644
index 0000000..b2a1c34e
--- /dev/null
+++ b/chrome/browser/ui/webui/settings/ash/select_to_speak_handler.h
@@ -0,0 +1,42 @@
+// 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_UI_WEBUI_SETTINGS_ASH_SELECT_TO_SPEAK_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_SETTINGS_ASH_SELECT_TO_SPEAK_HANDLER_H_
+
+#include "base/memory/weak_ptr.h"
+#include "chrome/browser/ui/webui/settings/ash/settings_with_tts_preview_handler.h"
+
+namespace ash::settings {
+
+// ChromeOS "/textToSpeech/selectToSpeak/*" settings page UI handler.
+class SelectToSpeakHandler : public SettingsWithTtsPreviewHandler {
+ public:
+  SelectToSpeakHandler();
+
+  SelectToSpeakHandler(const SelectToSpeakHandler&) = delete;
+  SelectToSpeakHandler& operator=(const SelectToSpeakHandler&) = delete;
+
+  ~SelectToSpeakHandler() override;
+
+  void HandleGetAllTtsVoiceData(const base::Value::List& args);
+  void HandleGetAppLocale(const base::Value::List& args);
+  void HandlePreviewTtsVoice(const base::Value::List& args);
+
+  // SettingsPageUIHandler implementation.
+  void RegisterMessages() override;
+
+  // VoicesChangedDelegate implementation.
+  void OnVoicesChanged() override;
+
+  // SettingsWithTtsPreviewHandler implementation.
+  GURL GetSourceURL() const override;
+
+ private:
+  base::WeakPtrFactory<SelectToSpeakHandler> weak_factory_{this};
+};
+
+}  // namespace ash::settings
+
+#endif  // CHROME_BROWSER_UI_WEBUI_SETTINGS_ASH_SELECT_TO_SPEAK_HANDLER_H_
diff --git a/chrome/browser/ui/webui/settings/ash/settings_with_tts_preview_handler.cc b/chrome/browser/ui/webui/settings/ash/settings_with_tts_preview_handler.cc
new file mode 100644
index 0000000..2c8680fd
--- /dev/null
+++ b/chrome/browser/ui/webui/settings/ash/settings_with_tts_preview_handler.cc
@@ -0,0 +1,115 @@
+// 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/ui/webui/settings/ash/settings_with_tts_preview_handler.h"
+
+#include "base/bind.h"
+#include "base/json/json_reader.h"
+#include "base/values.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/speech/extension_api/tts_engine_extension_api.h"
+#include "chrome/browser/speech/extension_api/tts_engine_extension_observer_chromeos.h"
+#include "chrome/browser/ui/chrome_pages.h"
+#include "chrome/common/extensions/extension_constants.h"
+#include "chrome/grit/generated_resources.h"
+#include "content/public/browser/tts_controller.h"
+#include "content/public/browser/web_ui.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/process_manager.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_set.h"
+#include "extensions/common/manifest_handlers/options_page_info.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace ash::settings {
+
+SettingsWithTtsPreviewHandler::SettingsWithTtsPreviewHandler() = default;
+
+SettingsWithTtsPreviewHandler::~SettingsWithTtsPreviewHandler() {
+  RemoveTtsControllerDelegates();
+}
+
+void SettingsWithTtsPreviewHandler::HandleGetAllTtsVoiceData(
+    const base::Value::List& args) {
+  OnVoicesChanged();
+}
+
+void SettingsWithTtsPreviewHandler::OnTtsEvent(
+    content::TtsUtterance* utterance,
+    content::TtsEventType event_type,
+    int char_index,
+    int length,
+    const std::string& error_message) {
+  if (event_type == content::TTS_EVENT_END ||
+      event_type == content::TTS_EVENT_INTERRUPTED ||
+      event_type == content::TTS_EVENT_ERROR) {
+    base::Value result(false /* preview stopped */);
+    FireWebUIListener("tts-preview-state-changed", result);
+  }
+}
+
+void SettingsWithTtsPreviewHandler::HandlePreviewTtsVoice(
+    const base::Value::List& args) {
+  DCHECK_EQ(2U, args.size());
+  const std::string& text = args[0].GetString();
+  const std::string& voice_id = args[1].GetString();
+
+  if (text.empty() || voice_id.empty())
+    return;
+
+  absl::optional<base::Value> json = base::JSONReader::Read(voice_id);
+  std::string name;
+  std::string extension_id;
+  if (const std::string* ptr = json->FindStringKey("name"))
+    name = *ptr;
+  if (const std::string* ptr = json->FindStringKey("extension"))
+    extension_id = *ptr;
+
+  std::unique_ptr<content::TtsUtterance> utterance =
+      content::TtsUtterance::Create(web_ui()->GetWebContents());
+  utterance->SetText(text);
+  utterance->SetVoiceName(name);
+  utterance->SetEngineId(extension_id);
+  utterance->SetSrcUrl(GetSourceURL());
+  utterance->SetEventDelegate(this);
+  content::TtsController::GetInstance()->Stop();
+
+  base::Value result(true /* preview started */);
+  FireWebUIListener("tts-preview-state-changed", result);
+  content::TtsController::GetInstance()->SpeakOrEnqueue(std::move(utterance));
+}
+
+void SettingsWithTtsPreviewHandler::RegisterMessages() {
+  web_ui()->RegisterMessageCallback(
+      "getAllTtsVoiceData",
+      base::BindRepeating(
+          &SettingsWithTtsPreviewHandler::HandleGetAllTtsVoiceData,
+          base::Unretained(this)));
+  web_ui()->RegisterMessageCallback(
+      "refreshTtsVoices",
+      base::BindRepeating(&SettingsWithTtsPreviewHandler::RefreshTtsVoices,
+                          base::Unretained(this)));
+}
+
+void SettingsWithTtsPreviewHandler::OnJavascriptAllowed() {
+  content::TtsController::GetInstance()->AddVoicesChangedDelegate(this);
+}
+
+void SettingsWithTtsPreviewHandler::OnJavascriptDisallowed() {
+  RemoveTtsControllerDelegates();
+}
+
+void SettingsWithTtsPreviewHandler::RefreshTtsVoices(
+    const base::Value::List& args) {
+  content::TtsController::GetInstance()->RefreshVoices();
+}
+
+void SettingsWithTtsPreviewHandler::RemoveTtsControllerDelegates() {
+  content::TtsController::GetInstance()->RemoveVoicesChangedDelegate(this);
+  content::TtsController::GetInstance()->RemoveUtteranceEventDelegate(this);
+}
+
+}  // namespace ash::settings
diff --git a/chrome/browser/ui/webui/settings/ash/settings_with_tts_preview_handler.h b/chrome/browser/ui/webui/settings/ash/settings_with_tts_preview_handler.h
new file mode 100644
index 0000000..7624f6aad
--- /dev/null
+++ b/chrome/browser/ui/webui/settings/ash/settings_with_tts_preview_handler.h
@@ -0,0 +1,52 @@
+// 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_UI_WEBUI_SETTINGS_ASH_SETTINGS_WITH_TTS_PREVIEW_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_SETTINGS_ASH_SETTINGS_WITH_TTS_PREVIEW_HANDLER_H_
+
+#include "base/memory/weak_ptr.h"
+#include "chrome/browser/ui/webui/settings/settings_page_ui_handler.h"
+#include "content/public/browser/tts_controller.h"
+
+namespace ash::settings {
+
+// Parent Chrome OS TTS-related settings page handler.
+class SettingsWithTtsPreviewHandler : public ::settings::SettingsPageUIHandler,
+                                      public content::VoicesChangedDelegate,
+                                      public content::UtteranceEventDelegate {
+ public:
+  SettingsWithTtsPreviewHandler();
+
+  SettingsWithTtsPreviewHandler(const SettingsWithTtsPreviewHandler&) = delete;
+  SettingsWithTtsPreviewHandler& operator=(
+      const SettingsWithTtsPreviewHandler&) = delete;
+
+  ~SettingsWithTtsPreviewHandler() override;
+
+  void HandleGetAllTtsVoiceData(const base::Value::List& args);
+  void HandlePreviewTtsVoice(const base::Value::List& args);
+
+  // SettingsPageUIHandler implementation.
+  void RegisterMessages() override;
+  void OnJavascriptAllowed() override;
+  void OnJavascriptDisallowed() override;
+
+  // UtteranceEventDelegate implementation.
+  void OnTtsEvent(content::TtsUtterance* utterance,
+                  content::TtsEventType event_type,
+                  int char_index,
+                  int length,
+                  const std::string& error_message) override;
+
+  void RefreshTtsVoices(const base::Value::List& args);
+  void RemoveTtsControllerDelegates();
+  virtual GURL GetSourceURL() const = 0;
+
+ private:
+  base::WeakPtrFactory<SettingsWithTtsPreviewHandler> weak_factory_{this};
+};
+
+}  // namespace ash::settings
+
+#endif  // CHROME_BROWSER_UI_WEBUI_SETTINGS_ASH_SETTINGS_WITH_TTS_PREVIEW_HANDLER_H_
diff --git a/chrome/browser/ui/webui/settings/ash/tts_handler.cc b/chrome/browser/ui/webui/settings/ash/tts_handler.cc
index edc49009..ded0a79 100644
--- a/chrome/browser/ui/webui/settings/ash/tts_handler.cc
+++ b/chrome/browser/ui/webui/settings/ash/tts_handler.cc
@@ -12,6 +12,7 @@
 #include "chrome/browser/speech/extension_api/tts_engine_extension_api.h"
 #include "chrome/browser/speech/extension_api/tts_engine_extension_observer_chromeos.h"
 #include "chrome/browser/ui/chrome_pages.h"
+#include "chrome/browser/ui/webui/settings/chromeos/constants/routes.mojom.h"
 #include "chrome/common/extensions/extension_constants.h"
 #include "chrome/grit/generated_resources.h"
 #include "content/public/browser/tts_controller.h"
@@ -28,13 +29,7 @@
 
 TtsHandler::TtsHandler() = default;
 
-TtsHandler::~TtsHandler() {
-  RemoveTtsControllerDelegates();
-}
-
-void TtsHandler::HandleGetAllTtsVoiceData(const base::Value::List& args) {
-  OnVoicesChanged();
-}
+TtsHandler::~TtsHandler() = default;
 
 void TtsHandler::HandleGetTtsExtensions(const base::Value::List& args) {
   // Ensure the built in tts engine is loaded to be able to respond to messages.
@@ -110,77 +105,19 @@
   HandleGetTtsExtensions(base::Value::List());
 }
 
-void TtsHandler::OnTtsEvent(content::TtsUtterance* utterance,
-                            content::TtsEventType event_type,
-                            int char_index,
-                            int length,
-                            const std::string& error_message) {
-  if (event_type == content::TTS_EVENT_END ||
-      event_type == content::TTS_EVENT_INTERRUPTED ||
-      event_type == content::TTS_EVENT_ERROR) {
-    base::Value result(false /* preview stopped */);
-    FireWebUIListener("tts-preview-state-changed", result);
-  }
-}
-
-void TtsHandler::HandlePreviewTtsVoice(const base::Value::List& args) {
-  DCHECK_EQ(2U, args.size());
-  const std::string& text = args[0].GetString();
-  const std::string& voice_id = args[1].GetString();
-
-  if (text.empty() || voice_id.empty())
-    return;
-
-  std::unique_ptr<base::DictionaryValue> json =
-      base::DictionaryValue::From(base::JSONReader::ReadDeprecated(voice_id));
-  std::string name;
-  std::string extension_id;
-  if (const std::string* ptr = json->FindStringKey("name"))
-    name = *ptr;
-  if (const std::string* ptr = json->FindStringKey("extension"))
-    extension_id = *ptr;
-
-  std::unique_ptr<content::TtsUtterance> utterance =
-      content::TtsUtterance::Create(web_ui()->GetWebContents());
-  utterance->SetText(text);
-  utterance->SetVoiceName(name);
-  utterance->SetEngineId(extension_id);
-  utterance->SetSrcUrl(
-      GURL(chrome::GetOSSettingsUrl("manageAccessibility/tts")));
-  utterance->SetEventDelegate(this);
-  content::TtsController::GetInstance()->Stop();
-
-  base::Value result(true /* preview started */);
-  FireWebUIListener("tts-preview-state-changed", result);
-  content::TtsController::GetInstance()->SpeakOrEnqueue(std::move(utterance));
-}
-
 void TtsHandler::RegisterMessages() {
-  web_ui()->RegisterMessageCallback(
-      "getAllTtsVoiceData",
-      base::BindRepeating(&TtsHandler::HandleGetAllTtsVoiceData,
-                          base::Unretained(this)));
+  SettingsWithTtsPreviewHandler::RegisterMessages();
   web_ui()->RegisterMessageCallback(
       "getTtsExtensions",
       base::BindRepeating(&TtsHandler::HandleGetTtsExtensions,
                           base::Unretained(this)));
   web_ui()->RegisterMessageCallback(
-      "previewTtsVoice", base::BindRepeating(&TtsHandler::HandlePreviewTtsVoice,
-                                             base::Unretained(this)));
+      "previewTtsVoice",
+      base::BindRepeating(&SettingsWithTtsPreviewHandler::HandlePreviewTtsVoice,
+                          base::Unretained(this)));
   web_ui()->RegisterMessageCallback(
       "wakeTtsEngine",
       base::BindRepeating(&TtsHandler::WakeTtsEngine, base::Unretained(this)));
-  web_ui()->RegisterMessageCallback(
-      "refreshTtsVoices", base::BindRepeating(&TtsHandler::RefreshTtsVoices,
-                                              base::Unretained(this)));
-}
-
-void TtsHandler::OnJavascriptAllowed() {
-  content::TtsController::GetInstance()->AddVoicesChangedDelegate(this);
-}
-
-void TtsHandler::OnJavascriptDisallowed() {
-  RemoveTtsControllerDelegates();
 }
 
 int TtsHandler::GetVoiceLangMatchScore(const content::VoiceData* voice,
@@ -208,13 +145,9 @@
   OnVoicesChanged();
 }
 
-void TtsHandler::RefreshTtsVoices(const base::Value::List& args) {
-  content::TtsController::GetInstance()->RefreshVoices();
-}
-
-void TtsHandler::RemoveTtsControllerDelegates() {
-  content::TtsController::GetInstance()->RemoveVoicesChangedDelegate(this);
-  content::TtsController::GetInstance()->RemoveUtteranceEventDelegate(this);
+GURL TtsHandler::GetSourceURL() const {
+  return GURL(chrome::GetOSSettingsUrl(
+      chromeos::settings::mojom::kTextToSpeechSubpagePath));
 }
 
 }  // namespace ash::settings
diff --git a/chrome/browser/ui/webui/settings/ash/tts_handler.h b/chrome/browser/ui/webui/settings/ash/tts_handler.h
index 21cc6fa..27f7bf0 100644
--- a/chrome/browser/ui/webui/settings/ash/tts_handler.h
+++ b/chrome/browser/ui/webui/settings/ash/tts_handler.h
@@ -6,15 +6,13 @@
 #define CHROME_BROWSER_UI_WEBUI_SETTINGS_ASH_TTS_HANDLER_H_
 
 #include "base/memory/weak_ptr.h"
-#include "chrome/browser/ui/webui/settings/settings_page_ui_handler.h"
+#include "chrome/browser/ui/webui/settings/ash/settings_with_tts_preview_handler.h"
 #include "content/public/browser/tts_controller.h"
 
 namespace ash::settings {
 
-// Chrome "/manageAccessibility/tts/*" settings page UI handler.
-class TtsHandler : public ::settings::SettingsPageUIHandler,
-                   public content::VoicesChangedDelegate,
-                   public content::UtteranceEventDelegate {
+// ChromeOS "/manageAccessibility/tts/*" settings page UI handler.
+class TtsHandler : public SettingsWithTtsPreviewHandler {
  public:
   TtsHandler();
 
@@ -25,30 +23,21 @@
 
   void HandleGetAllTtsVoiceData(const base::Value::List& args);
   void HandleGetTtsExtensions(const base::Value::List& args);
-  void HandlePreviewTtsVoice(const base::Value::List& args);
 
   // SettingsPageUIHandler implementation.
   void RegisterMessages() override;
-  void OnJavascriptAllowed() override;
-  void OnJavascriptDisallowed() override;
 
   // VoicesChangedDelegate implementation.
   void OnVoicesChanged() override;
 
-  // UtteranceEventDelegate implementation.
-  void OnTtsEvent(content::TtsUtterance* utterance,
-                  content::TtsEventType event_type,
-                  int char_index,
-                  int length,
-                  const std::string& error_message) override;
+  // SettingsWithTtsPreviewHandler implementation.
+  GURL GetSourceURL() const override;
 
  private:
   void WakeTtsEngine(const base::Value::List& args);
   void OnTtsEngineAwake(bool success);
-  void RefreshTtsVoices(const base::Value::List& args);
   int GetVoiceLangMatchScore(const content::VoiceData* voice,
                              const std::string& app_locale);
-  void RemoveTtsControllerDelegates();
 
   base::WeakPtrFactory<TtsHandler> weak_factory_{this};
 };
diff --git a/chrome/browser/ui/webui/settings/protocol_handlers_handler.cc b/chrome/browser/ui/webui/settings/protocol_handlers_handler.cc
index 50fe112..c605911 100644
--- a/chrome/browser/ui/webui/settings/protocol_handlers_handler.cc
+++ b/chrome/browser/ui/webui/settings/protocol_handlers_handler.cc
@@ -74,7 +74,7 @@
 void ProtocolHandlersHandler::OnJavascriptAllowed() {
   registry_observation_.Observe(GetProtocolHandlerRegistry());
   if (web_app_provider_) {
-    app_observation_.Observe(&web_app_provider_->registrar());
+    app_observation_.Observe(&web_app_provider_->registrar_unsafe());
     install_manager_observation_.Observe(&web_app_provider_->install_manager());
   }
 }
@@ -274,7 +274,7 @@
     return;
 
   base::flat_set<std::string> protocols(
-      web_app_provider_->registrar().GetAllAllowedLaunchProtocols());
+      web_app_provider_->registrar_unsafe().GetAllAllowedLaunchProtocols());
   web_app::OsIntegrationManager& os_integration_manager =
       web_app_provider_->os_integration_manager();
 
@@ -295,7 +295,7 @@
     return;
 
   base::flat_set<std::string> protocols(
-      web_app_provider_->registrar().GetAllDisallowedLaunchProtocols());
+      web_app_provider_->registrar_unsafe().GetAllDisallowedLaunchProtocols());
   web_app::OsIntegrationManager& os_integration_manager =
       web_app_provider_->os_integration_manager();
 
diff --git a/chrome/browser/ui/webui/settings/settings_ui.cc b/chrome/browser/ui/webui/settings/settings_ui.cc
index 0cb3166..8319194b 100644
--- a/chrome/browser/ui/webui/settings/settings_ui.cc
+++ b/chrome/browser/ui/webui/settings/settings_ui.cc
@@ -182,7 +182,8 @@
   content::WebUIDataSource* html_source =
       content::WebUIDataSource::Create(chrome::kChromeUISettingsHost);
   html_source->OverrideContentSecurityPolicy(
-      network::mojom::CSPDirectiveName::WorkerSrc, "worker-src blob: 'self';");
+      network::mojom::CSPDirectiveName::WorkerSrc,
+      "worker-src blob: chrome://resources 'self';");
 
   AddSettingsPageUIHandler(std::make_unique<AppearanceHandler>(web_ui));
 
diff --git a/chrome/browser/ui/webui/settings/shared_settings_localized_strings_provider.cc b/chrome/browser/ui/webui/settings/shared_settings_localized_strings_provider.cc
index 651dbc13..58f92ce 100644
--- a/chrome/browser/ui/webui/settings/shared_settings_localized_strings_provider.cc
+++ b/chrome/browser/ui/webui/settings/shared_settings_localized_strings_provider.cc
@@ -447,7 +447,8 @@
   // increases, set this as the default so manual override is no longer
   // required.
   html_source->OverrideContentSecurityPolicy(
-      network::mojom::CSPDirectiveName::WorkerSrc, "worker-src blob: 'self';");
+      network::mojom::CSPDirectiveName::WorkerSrc,
+      "worker-src blob: chrome://resources 'self';");
 }
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
diff --git a/chrome/browser/ui/webui/settings/site_settings_helper.cc b/chrome/browser/ui/webui/settings/site_settings_helper.cc
index 7bc61c3..f73d0fc 100644
--- a/chrome/browser/ui/webui/settings/site_settings_helper.cc
+++ b/chrome/browser/ui/webui/settings/site_settings_helper.cc
@@ -1049,11 +1049,11 @@
   absl::optional<std::string> app_name;
   if (auto* provider = web_app::WebAppProvider::GetForWebApps(profile)) {
     if (absl::optional<web_app::AppId> app_id =
-            provider->registrar().FindAppWithUrlInScope(origin)) {
-      if (!provider->registrar().IsIsolated(*app_id)) {
+            provider->registrar_unsafe().FindAppWithUrlInScope(origin)) {
+      if (!provider->registrar_unsafe().IsIsolated(*app_id)) {
         return app_name;
       }
-      app_name = provider->registrar().GetAppShortName(*app_id);
+      app_name = provider->registrar_unsafe().GetAppShortName(*app_id);
     }
   }
   return app_name;
diff --git a/chrome/browser/ui/webui/side_panel/bookmarks/bookmarks.mojom b/chrome/browser/ui/webui/side_panel/bookmarks/bookmarks.mojom
index edfbb8e..e5eec242 100644
--- a/chrome/browser/ui/webui/side_panel/bookmarks/bookmarks.mojom
+++ b/chrome/browser/ui/webui/side_panel/bookmarks/bookmarks.mojom
@@ -22,8 +22,8 @@
 
 // Browser-side handler for requests from WebUI page.
 interface BookmarksPageHandler {
-  // Bookmarks the current active tab.
-  BookmarkCurrentTab();
+  // Bookmarks the current active tab in the given folder.
+  BookmarkCurrentTabInFolder(int64 folder_id);
 
   // Opens the bookmark specified by node_id. Passes the parent folder
   // depth for metrics collection and the action source to identify
diff --git a/chrome/browser/ui/webui/side_panel/bookmarks/bookmarks_page_handler.cc b/chrome/browser/ui/webui/side_panel/bookmarks/bookmarks_page_handler.cc
index 8fa0b52..82b573d 100644
--- a/chrome/browser/ui/webui/side_panel/bookmarks/bookmarks_page_handler.cc
+++ b/chrome/browser/ui/webui/side_panel/bookmarks/bookmarks_page_handler.cc
@@ -147,12 +147,12 @@
 
 BookmarksPageHandler::~BookmarksPageHandler() = default;
 
-void BookmarksPageHandler::BookmarkCurrentTab() {
+void BookmarksPageHandler::BookmarkCurrentTabInFolder(int64_t folder_id) {
   Browser* browser = chrome::FindLastActive();
   if (!browser)
     return;
 
-  chrome::BookmarkCurrentTab(browser);
+  chrome::BookmarkCurrentTabInFolder(browser, folder_id);
 }
 
 void BookmarksPageHandler::OpenBookmark(
diff --git a/chrome/browser/ui/webui/side_panel/bookmarks/bookmarks_page_handler.h b/chrome/browser/ui/webui/side_panel/bookmarks/bookmarks_page_handler.h
index c9488e1..6779e16a 100644
--- a/chrome/browser/ui/webui/side_panel/bookmarks/bookmarks_page_handler.h
+++ b/chrome/browser/ui/webui/side_panel/bookmarks/bookmarks_page_handler.h
@@ -26,7 +26,7 @@
   ~BookmarksPageHandler() override;
 
   // side_panel::mojom::BookmarksPageHandler:
-  void BookmarkCurrentTab() override;
+  void BookmarkCurrentTabInFolder(int64_t folder_id) override;
   void OpenBookmark(int64_t node_id,
                     int32_t parent_folder_depth,
                     ui::mojom::ClickModifiersPtr click_modifiers,
diff --git a/chrome/browser/ui/webui/side_panel/bookmarks/bookmarks_side_panel_ui.cc b/chrome/browser/ui/webui/side_panel/bookmarks/bookmarks_side_panel_ui.cc
index 9ae8d9fb..3ada2ed0 100644
--- a/chrome/browser/ui/webui/side_panel/bookmarks/bookmarks_side_panel_ui.cc
+++ b/chrome/browser/ui/webui/side_panel/bookmarks/bookmarks_side_panel_ui.cc
@@ -94,6 +94,13 @@
   source->AddBoolean("canModifyBookmarks", !profile->IsGuestSession() &&
                                                !profile->IsIncognitoProfile());
 
+  bookmarks::BookmarkModel* bookmark_model =
+      BookmarkModelFactory::GetForBrowserContext(profile);
+  source->AddString(
+      "otherBookmarksId",
+      base::NumberToString(bookmark_model ? bookmark_model->other_node()->id()
+                                          : -1));
+
   content::URLDataSource::Add(
       profile, std::make_unique<FaviconSource>(
                    profile, chrome::FaviconUrlFormat::kFavicon2));
diff --git a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome.mojom b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome.mojom
index fb3c61c69..6682694 100644
--- a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome.mojom
+++ b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome.mojom
@@ -11,12 +11,18 @@
 struct BackgroundImage {
   // URL to the background image. Can point to untrusted content.
   url.mojom.Url url;
+  // Title of the background image.
+  string title;
 };
 
 // A generic theme.
 struct Theme {
   // The background image.
   BackgroundImage? background_image;
+  // Whether the OS is in dark mode.
+  bool system_dark_mode;
+  // The current theme color. If not set, we use the default theme.
+  skia.mojom.SkColor? foreground_color;
 };
 
 struct ChromeColor {
@@ -59,8 +65,15 @@
 
   // Returns the collections of background images.
   GetBackgroundCollections() => (array<BackgroundCollection> collections);
+
   // Triggers a call to |CustomizeChromePage.SetTheme()|.
   UpdateTheme();
+
+  // Sets Chrome's theme according to the default color.
+  SetDefaultColor();
+
+  // Sets Chrome's theme according to |foreground_color|.
+  SetForegroundColor(skia.mojom.SkColor foreground_color);
 };
 
 // WebUI-side handler for requests from the browser.
diff --git a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler.cc b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler.cc
index e4d27ce4..3d092a3 100644
--- a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler.cc
+++ b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler.cc
@@ -6,9 +6,15 @@
 
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
+#include "chrome/browser/new_tab_page/chrome_colors/chrome_colors_factory.h"
+#include "chrome/browser/new_tab_page/chrome_colors/chrome_colors_service.h"
 #include "chrome/browser/new_tab_page/chrome_colors/selected_colors_info.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/search/background/ntp_background_service_factory.h"
+#include "chrome/browser/themes/theme_service.h"
+#include "chrome/browser/themes/theme_service_factory.h"
+#include "chrome/browser/ui/browser_window.h"
+#include "chrome/browser/ui/color/chrome_color_id.h"
 #include "chrome/browser/ui/webui/new_tab_page/ntp_pref_names.h"
 #include "chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_colors.h"
 #include "chrome/common/themes/autogenerated_theme_util.h"
@@ -16,6 +22,8 @@
 #include "content/public/browser/web_contents.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/color/color_provider.h"
+#include "ui/native_theme/native_theme.h"
 
 CustomizeChromePageHandler::CustomizeChromePageHandler(
     mojo::PendingReceiver<side_panel::mojom::CustomizeChromePageHandler>
@@ -28,11 +36,18 @@
       web_contents_(web_contents),
       ntp_background_service_(
           NtpBackgroundServiceFactory::GetForProfile(profile_)),
+      theme_service_(ThemeServiceFactory::GetForProfile(profile_)),
+      chrome_colors_service_(
+          chrome_colors::ChromeColorsFactory::GetForProfile(profile_)),
       page_(std::move(pending_page)),
       receiver_(this, std::move(pending_page_handler)) {
   CHECK(ntp_custom_background_service_);
+  CHECK(theme_service_);
   CHECK(ntp_background_service_);
+  CHECK(chrome_colors_service_);
   ntp_background_service_->AddObserver(this);
+  native_theme_observation_.Observe(ui::NativeTheme::GetInstanceForNativeUi());
+  theme_service_observation_.Observe(theme_service_);
 }
 
 CustomizeChromePageHandler::~CustomizeChromePageHandler() {
@@ -91,13 +106,49 @@
   auto background_image = side_panel::mojom::BackgroundImage::New();
   if (custom_background.has_value()) {
     background_image->url = custom_background->custom_background_url;
+    background_image->title =
+        custom_background->custom_background_attribution_line_1;
   } else {
     background_image = nullptr;
   }
   theme->background_image = std::move(background_image);
+  if (!theme_service_->UsingDefaultTheme() &&
+      !theme_service_->UsingSystemTheme()) {
+    theme->foreground_color =
+        web_contents_->GetColorProvider().GetColor(ui::kColorFrameActive);
+  }
+  auto* native_theme = ui::NativeTheme::GetInstanceForNativeUi();
+  CHECK(native_theme);
+  theme->system_dark_mode = native_theme->ShouldUseDarkColors();
   page_->SetTheme(std::move(theme));
 }
 
+void CustomizeChromePageHandler::SetDefaultColor() {
+  auto* chrome_colors_service =
+      chrome_colors::ChromeColorsFactory::GetForProfile(profile_);
+  chrome_colors_service->ApplyDefaultTheme(web_contents_);
+  // Side panel settings are final.
+  chrome_colors_service->ConfirmThemeChanges();
+}
+
+void CustomizeChromePageHandler::SetForegroundColor(SkColor foreground_color) {
+  auto* chrome_colors_service =
+      chrome_colors::ChromeColorsFactory::GetForProfile(profile_);
+  chrome_colors_service->ApplyAutogeneratedTheme(foreground_color,
+                                                 web_contents_);
+  // Side panel settings are final.
+  chrome_colors_service->ConfirmThemeChanges();
+}
+
+void CustomizeChromePageHandler::OnNativeThemeUpdated(
+    ui::NativeTheme* observed_theme) {
+  UpdateTheme();
+}
+
+void CustomizeChromePageHandler::OnThemeChanged() {
+  UpdateTheme();
+}
+
 bool CustomizeChromePageHandler::IsCustomLinksEnabled() const {
   return !profile_->GetPrefs()->GetBoolean(ntp_prefs::kNtpUseMostVisitedTiles);
 }
diff --git a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler.h b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler.h
index 7f32253..21bb4ee 100644
--- a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler.h
+++ b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler.h
@@ -6,13 +6,22 @@
 #define CHROME_BROWSER_UI_WEBUI_SIDE_PANEL_CUSTOMIZE_CHROME_CUSTOMIZE_CHROME_PAGE_HANDLER_H_
 
 #include "base/memory/raw_ptr.h"
+#include "base/scoped_observation.h"
 #include "chrome/browser/search/background/ntp_background_service.h"
 #include "chrome/browser/search/background/ntp_background_service_observer.h"
 #include "chrome/browser/search/background/ntp_custom_background_service.h"
+#include "chrome/browser/themes/theme_service.h"
+#include "chrome/browser/themes/theme_service_observer.h"
 #include "chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome.mojom.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/remote.h"
+#include "ui/native_theme/native_theme.h"
+#include "ui/native_theme/native_theme_observer.h"
+
+namespace chrome_colors {
+class ChromeColorsService;
+}  // namespace chrome_colors
 
 namespace content {
 class WebContents;
@@ -22,7 +31,9 @@
 
 class CustomizeChromePageHandler
     : public side_panel::mojom::CustomizeChromePageHandler,
-      public NtpBackgroundServiceObserver {
+      public NtpBackgroundServiceObserver,
+      public ui::NativeThemeObserver,
+      public ThemeServiceObserver {
  public:
   CustomizeChromePageHandler(
       mojo::PendingReceiver<side_panel::mojom::CustomizeChromePageHandler>
@@ -44,8 +55,16 @@
   void GetBackgroundCollections(
       GetBackgroundCollectionsCallback callback) override;
   void UpdateTheme() override;
+  void SetDefaultColor() override;
+  void SetForegroundColor(SkColor foreground_color) override;
 
  private:
+  // ui::NativeThemeObserver:
+  void OnNativeThemeUpdated(ui::NativeTheme* observed_theme) override;
+
+  // ThemeServiceObserver:
+  void OnThemeChanged() override;
+
   bool IsCustomLinksEnabled() const;
   bool IsShortcutsVisible() const;
 
@@ -61,6 +80,13 @@
   raw_ptr<NtpBackgroundService> ntp_background_service_;
   GetBackgroundCollectionsCallback background_collections_callback_;
   base::TimeTicks background_collections_request_start_time_;
+  raw_ptr<ThemeService> theme_service_;
+  raw_ptr<chrome_colors::ChromeColorsService> chrome_colors_service_;
+
+  base::ScopedObservation<ui::NativeTheme, ui::NativeThemeObserver>
+      native_theme_observation_{this};
+  base::ScopedObservation<ThemeService, ThemeServiceObserver>
+      theme_service_observation_{this};
 
   mojo::Remote<side_panel::mojom::CustomizeChromePage> page_;
   mojo::Receiver<side_panel::mojom::CustomizeChromePageHandler> receiver_;
diff --git a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler_unittest.cc b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler_unittest.cc
index 6172eef4..545f401 100644
--- a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler_unittest.cc
+++ b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler_unittest.cc
@@ -4,20 +4,28 @@
 
 #include "chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler.h"
 
+#include <functional>
 #include <memory>
 
 #include "base/test/mock_callback.h"
+#include "chrome/browser/new_tab_page/chrome_colors/chrome_colors_factory.h"
+#include "chrome/browser/new_tab_page/chrome_colors/chrome_colors_service.h"
 #include "chrome/browser/new_tab_page/chrome_colors/selected_colors_info.h"
 #include "chrome/browser/search/background/ntp_background_data.h"
 #include "chrome/browser/search/background/ntp_background_service_factory.h"
 #include "chrome/browser/search/background/ntp_custom_background_service.h"
 #include "chrome/browser/search_engines/template_url_service_factory.h"
+#include "chrome/browser/themes/theme_service.h"
+#include "chrome/browser/themes/theme_service_factory.h"
+#include "chrome/browser/ui/color/chrome_color_id.h"
 #include "chrome/browser/ui/webui/new_tab_page/ntp_pref_names.h"
 #include "chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome.mojom.h"
 #include "chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_colors.h"
 #include "chrome/common/themes/autogenerated_theme_util.h"
 #include "chrome/test/base/testing_profile.h"
+#include "components/keyed_service/core/keyed_service.h"
 #include "components/prefs/pref_service.h"
+#include "content/public/browser/web_contents.h"
 #include "content/public/test/browser_task_environment.h"
 #include "content/public/test/test_web_contents_factory.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
@@ -26,7 +34,14 @@
 #include "services/network/test/test_url_loader_factory.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/skia/include/core/SkColor.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/color/color_provider.h"
+#include "ui/native_theme/native_theme.h"
+
+namespace content {
+class BrowserContext;
+}  // namespace content
 
 namespace {
 
@@ -67,6 +82,26 @@
   MOCK_METHOD1(AddObserver, void(NtpBackgroundServiceObserver*));
 };
 
+class MockThemeService : public ThemeService {
+ public:
+  MockThemeService() : ThemeService(nullptr, theme_helper_) { set_ready(); }
+  using ThemeService::NotifyThemeChanged;
+  MOCK_CONST_METHOD0(UsingDefaultTheme, bool());
+  MOCK_CONST_METHOD0(UsingSystemTheme, bool());
+
+ private:
+  ThemeHelper theme_helper_;
+};
+
+class MockChromeColorsService : public chrome_colors::ChromeColorsService {
+ public:
+  explicit MockChromeColorsService(Profile* profile)
+      : chrome_colors::ChromeColorsService(profile) {}
+  MOCK_METHOD1(ApplyDefaultTheme, void(content::WebContents*));
+  MOCK_METHOD2(ApplyAutogeneratedTheme, void(SkColor, content::WebContents*));
+  MOCK_METHOD0(ConfirmThemeChanges, void());
+};
+
 std::unique_ptr<TestingProfile> MakeTestingProfile(
     scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory) {
   TestingProfile::Builder profile_builder;
@@ -81,6 +116,19 @@
                 url_loader_factory);
           },
           url_loader_factory));
+  profile_builder.AddTestingFactory(
+      ThemeServiceFactory::GetInstance(),
+      base::BindRepeating([](content::BrowserContext* context)
+                              -> std::unique_ptr<KeyedService> {
+        return std::make_unique<testing::NiceMock<MockThemeService>>();
+      }));
+  profile_builder.AddTestingFactory(
+      chrome_colors::ChromeColorsFactory::GetInstance(),
+      base::BindRepeating([](content::BrowserContext* context)
+                              -> std::unique_ptr<KeyedService> {
+        return std::make_unique<testing::NiceMock<MockChromeColorsService>>(
+            Profile::FromBrowserContext(context));
+      }));
   profile_builder.SetSharedURLLoaderFactory(url_loader_factory);
   auto profile = profile_builder.Build();
   return profile;
@@ -96,7 +144,13 @@
                 &test_url_loader_factory_))),
         mock_ntp_custom_background_service_(profile_.get()),
         mock_ntp_background_service_(static_cast<MockNtpBackgroundService*>(
-            NtpBackgroundServiceFactory::GetForProfile(profile_.get()))) {}
+            NtpBackgroundServiceFactory::GetForProfile(profile_.get()))),
+        web_contents_(web_contents_factory_.CreateWebContents(profile_.get())),
+        mock_theme_service_(static_cast<MockThemeService*>(
+            ThemeServiceFactory::GetForProfile(profile_.get()))),
+        mock_chrome_colors_service_(static_cast<MockChromeColorsService*>(
+            chrome_colors::ChromeColorsFactory::GetForProfile(
+                profile_.get()))) {}
 
   void SetUp() override {
     EXPECT_CALL(mock_ntp_background_service(), AddObserver)
@@ -105,12 +159,13 @@
     handler_ = std::make_unique<CustomizeChromePageHandler>(
         mojo::PendingReceiver<side_panel::mojom::CustomizeChromePageHandler>(),
         mock_page_.BindAndGetRemote(), &mock_ntp_custom_background_service_,
-        web_contents_factory_.CreateWebContents(profile_.get()));
+        web_contents_);
     mock_page_.FlushForTesting();
     EXPECT_EQ(handler_.get(), ntp_background_service_observer_);
   }
 
   TestingProfile& profile() { return *profile_; }
+  content::WebContents& web_contents() { return *web_contents_; }
   CustomizeChromePageHandler& handler() { return *handler_; }
   MockNtpBackgroundService& mock_ntp_background_service() {
     return *mock_ntp_background_service_;
@@ -118,6 +173,10 @@
   NtpBackgroundServiceObserver& ntp_background_service_observer() {
     return *ntp_background_service_observer_;
   }
+  MockThemeService& mock_theme_service() { return *mock_theme_service_; }
+  MockChromeColorsService& mock_chrome_colors_service() {
+    return *mock_chrome_colors_service_;
+  }
 
  protected:
   // NOTE: The initialization order of these members matters.
@@ -128,8 +187,11 @@
   network::TestURLLoaderFactory test_url_loader_factory_;
   raw_ptr<MockNtpBackgroundService> mock_ntp_background_service_;
   content::TestWebContentsFactory web_contents_factory_;
+  content::WebContents* web_contents_;
   testing::NiceMock<MockPage> mock_page_;
   NtpBackgroundServiceObserver* ntp_background_service_observer_;
+  MockThemeService* mock_theme_service_;
+  MockChromeColorsService* mock_chrome_colors_service_;
   std::unique_ptr<CustomizeChromePageHandler> handler_;
 };
 
@@ -193,7 +255,32 @@
   }
 }
 
-TEST_F(CustomizeChromePageHandlerTest, SetTheme) {
+enum class ThemeUpdateSource {
+  kMojo,
+  kThemeService,
+  kNativeTheme,
+};
+
+class CustomizeChromePageHandlerSetThemeTest
+    : public CustomizeChromePageHandlerTest,
+      public ::testing::WithParamInterface<ThemeUpdateSource> {
+ public:
+  void UpdateTheme() {
+    switch (GetParam()) {
+      case ThemeUpdateSource::kMojo:
+        handler().UpdateTheme();
+        break;
+      case ThemeUpdateSource::kThemeService:
+        mock_theme_service().NotifyThemeChanged();
+        break;
+      case ThemeUpdateSource::kNativeTheme:
+        ui::NativeTheme::GetInstanceForNativeUi()->NotifyOnNativeThemeUpdated();
+        break;
+    }
+  }
+};
+
+TEST_P(CustomizeChromePageHandlerSetThemeTest, SetTheme) {
   side_panel::mojom::ThemePtr theme;
   EXPECT_CALL(mock_page_, SetTheme)
       .Times(1)
@@ -202,17 +289,33 @@
       }));
   CustomBackground custom_background;
   custom_background.custom_background_url = GURL("https://foo.com/img.png");
+  custom_background.custom_background_attribution_line_1 = "foo line";
   ON_CALL(mock_ntp_custom_background_service_, GetCustomBackground())
       .WillByDefault(testing::Return(absl::make_optional(custom_background)));
+  ON_CALL(mock_theme_service(), UsingDefaultTheme())
+      .WillByDefault(testing::Return(false));
+  ON_CALL(mock_theme_service(), UsingSystemTheme())
+      .WillByDefault(testing::Return(false));
+  ui::NativeTheme::GetInstanceForNativeUi()->set_use_dark_colors(true);
 
-  handler().UpdateTheme();
+  UpdateTheme();
   mock_page_.FlushForTesting();
 
   ASSERT_TRUE(theme);
   ASSERT_TRUE(theme->background_image);
   EXPECT_EQ("https://foo.com/img.png", theme->background_image->url);
+  EXPECT_EQ("foo line", theme->background_image->title);
+  EXPECT_TRUE(theme->system_dark_mode);
+  EXPECT_EQ(web_contents().GetColorProvider().GetColor(ui::kColorFrameActive),
+            theme->foreground_color);
 }
 
+INSTANTIATE_TEST_SUITE_P(All,
+                         CustomizeChromePageHandlerSetThemeTest,
+                         ::testing::Values(ThemeUpdateSource::kMojo,
+                                           ThemeUpdateSource::kThemeService,
+                                           ThemeUpdateSource::kNativeTheme));
+
 TEST_F(CustomizeChromePageHandlerTest, GetBackgroundCollections) {
   std::vector<CollectionInfo> test_collection_info;
   CollectionInfo test_collection;
@@ -244,3 +347,22 @@
   EXPECT_EQ(test_collection_info[0].preview_image_url,
             collections[0]->preview_image_url);
 }
+
+TEST_F(CustomizeChromePageHandlerTest, SetDefaultColor) {
+  EXPECT_CALL(mock_chrome_colors_service(), ApplyDefaultTheme).Times(1);
+  EXPECT_CALL(mock_chrome_colors_service(), ConfirmThemeChanges).Times(1);
+
+  handler().SetDefaultColor();
+}
+
+TEST_F(CustomizeChromePageHandlerTest, SetForegroundColor) {
+  SkColor color = SK_ColorWHITE;
+  EXPECT_CALL(mock_chrome_colors_service(), ApplyAutogeneratedTheme)
+      .Times(1)
+      .WillOnce(testing::SaveArg<0>(&color));
+  EXPECT_CALL(mock_chrome_colors_service(), ConfirmThemeChanges).Times(1);
+
+  handler().SetForegroundColor(SK_ColorBLUE);
+
+  EXPECT_EQ(SK_ColorBLUE, color);
+}
diff --git a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_ui.cc b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_ui.cc
index 5e71aed..066a3c5 100644
--- a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_ui.cc
+++ b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_ui.cc
@@ -31,6 +31,7 @@
   static constexpr webui::LocalizedString kLocalizedStrings[] = {
       {"customizeThisPage", IDS_NTP_CUSTOM_BG_CUSTOMIZE_NTP_LABEL},
       {"appearanceHeader", IDS_NTP_CUSTOMIZE_APPEARANCE_LABEL},
+      {"defaultColorName", IDS_NTP_CUSTOMIZE_DEFAULT_LABEL},
       {"mostVisited", IDS_NTP_CUSTOMIZE_MOST_VISITED_LABEL},
       {"myShortcuts", IDS_NTP_CUSTOMIZE_MY_SHORTCUTS_LABEL},
       {"shortcutsCurated", IDS_NTP_CUSTOMIZE_MY_SHORTCUTS_DESC},
diff --git a/chrome/browser/ui/webui/side_panel/reading_list/reading_list_ui.cc b/chrome/browser/ui/webui/side_panel/reading_list/reading_list_ui.cc
index b88812e..94795721 100644
--- a/chrome/browser/ui/webui/side_panel/reading_list/reading_list_ui.cc
+++ b/chrome/browser/ui/webui/side_panel/reading_list/reading_list_ui.cc
@@ -106,6 +106,13 @@
       "bookmarksDragAndDropEnabled",
       prefs->GetBoolean(bookmarks::prefs::kEditBookmarksEnabled));
 
+  bookmarks::BookmarkModel* bookmark_model =
+      BookmarkModelFactory::GetForBrowserContext(profile);
+  source->AddString(
+      "otherBookmarksId",
+      base::NumberToString(bookmark_model ? bookmark_model->other_node()->id()
+                                          : -1));
+
   ReadingListModel* const reading_list_model =
       ReadingListModelFactory::GetForBrowserContext(profile);
   source->AddBoolean(
diff --git a/chrome/browser/ui/webui/test_data_source.cc b/chrome/browser/ui/webui/test_data_source.cc
index 0adcf4f3..047b8343 100644
--- a/chrome/browser/ui/webui/test_data_source.cc
+++ b/chrome/browser/ui/webui/test_data_source.cc
@@ -90,7 +90,7 @@
   if (directive == network::mojom::CSPDirectiveName::ScriptSrc) {
     return "script-src chrome://* 'self';";
   } else if (directive == network::mojom::CSPDirectiveName::WorkerSrc) {
-    return "worker-src blob: 'self';";
+    return "worker-src blob: chrome://resources 'self';";
   } else if (directive ==
                  network::mojom::CSPDirectiveName::RequireTrustedTypesFor ||
              directive == network::mojom::CSPDirectiveName::TrustedTypes) {
diff --git a/chrome/browser/user_bypass/DIR_METADATA b/chrome/browser/user_bypass/DIR_METADATA
new file mode 100644
index 0000000..ef53ddb
--- /dev/null
+++ b/chrome/browser/user_bypass/DIR_METADATA
@@ -0,0 +1,8 @@
+# Metadata information for this directory.
+# For the schema of this file, see Metadata message: https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/proto/dir_metadata.proto
+
+monorail: {
+  project: "chromium";
+  component: "Internals>Services>Content";
+}
+team_email: "dc-komics@google.com";
diff --git a/chrome/browser/user_bypass/OWNERS b/chrome/browser/user_bypass/OWNERS
new file mode 100644
index 0000000..dcb5655
--- /dev/null
+++ b/chrome/browser/user_bypass/OWNERS
@@ -0,0 +1,5 @@
+bcb@chromium.org
+bcl@chromium.org
+bingler@chromium.org
+njeunje@chromium.org
+wanderview@chromium.org
diff --git a/chrome/browser/web_applications/BUILD.gn b/chrome/browser/web_applications/BUILD.gn
index 9d4ad8b..47cc828 100644
--- a/chrome/browser/web_applications/BUILD.gn
+++ b/chrome/browser/web_applications/BUILD.gn
@@ -599,6 +599,7 @@
     "web_app_registrar_unittest.cc",
     "web_app_sync_bridge_unittest.cc",
     "web_app_translation_manager_unittest.cc",
+    "web_app_ui_manager_unittest.cc",
     "web_app_unittest.cc",
     "web_app_url_loader_unittest.cc",
     "web_app_utils_unittest.cc",
@@ -717,7 +718,6 @@
   deps = [
     ":web_applications_unit_tests",
     "//chrome/browser/web_applications/adjustments:unit_tests",
-    "//chrome/browser/web_applications/app_service:unit_tests",
     "//chrome/browser/web_applications/extensions:unit_tests",
   ]
 }
diff --git a/chrome/browser/web_applications/app_service/web_apps.cc b/chrome/browser/web_applications/app_service/web_apps.cc
index fead2625f..6ff66aa 100644
--- a/chrome/browser/web_applications/app_service/web_apps.cc
+++ b/chrome/browser/web_applications/app_service/web_apps.cc
@@ -16,7 +16,6 @@
 #include "chrome/browser/web_applications/web_app_provider.h"
 #include "chrome/browser/web_applications/web_app_registrar.h"
 #include "chrome/browser/web_applications/web_app_utils.h"
-#include "components/services/app_service/public/cpp/features.h"
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "ash/public/cpp/app_menu_constants.h"
@@ -339,16 +338,8 @@
     const std::string& app_id,
     absl::optional<bool> accessing_camera,
     absl::optional<bool> accessing_microphone) {
-  if (base::FeatureList::IsEnabled(
-          apps::kAppServiceCapabilityAccessWithoutMojom)) {
-    apps::AppPublisher::ModifyCapabilityAccess(
-        app_id, std::move(accessing_camera), std::move(accessing_microphone));
-    return;
-  }
-
-  PublisherBase::ModifyCapabilityAccess(subscribers_, app_id,
-                                        std::move(accessing_camera),
-                                        std::move(accessing_microphone));
+  apps::AppPublisher::ModifyCapabilityAccess(
+      app_id, std::move(accessing_camera), std::move(accessing_microphone));
 }
 
 std::vector<apps::AppPtr> WebApps::CreateWebApps() {
diff --git a/chrome/browser/web_applications/isolated_web_apps/isolated_web_app_url_loader_factory_browsertest.cc b/chrome/browser/web_applications/isolated_web_apps/isolated_web_app_url_loader_factory_browsertest.cc
index d14bf75f..8486148 100644
--- a/chrome/browser/web_applications/isolated_web_apps/isolated_web_app_url_loader_factory_browsertest.cc
+++ b/chrome/browser/web_applications/isolated_web_apps/isolated_web_app_url_loader_factory_browsertest.cc
@@ -293,5 +293,85 @@
       "non-existing");
 }
 
+IN_PROC_BROWSER_TEST_F(IsolatedWebAppURLLoaderFactoryBrowserTest,
+                       UrlLoaderFactoryCanUseServiceWorker) {
+  web_package::WebBundleBuilder builder;
+  builder.AddExchange(kUrl, {{":status", "200"}, {"content-type", "text/html"}},
+                      R"html(
+<html>
+  <head>
+    <script type="text/javascript" src="/script.js"></script>
+  </head>
+</html>
+)html");
+  builder.AddExchange(kUrl.Resolve("/title.txt"),
+                      {{":status", "200"}, {"content-type", "text/plain"}},
+                      "data from web bundle");
+  builder.AddExchange(kUrl.Resolve("/script.js"),
+                      {{":status", "200"}, {"content-type", "text/javascript"}},
+                      R"js(
+const policy = trustedTypes.createPolicy('default', {
+  createScriptURL(url) {
+    return new URL(url, document.baseURI);
+  },
+});
+
+const wait_for_activated = async (registration) => {
+  const worker = registration.active;
+  if (worker.state == 'activated') {
+    return;
+  }
+
+  await new Promise(resolve => {
+    worker.addEventListener('statechange', () => {
+      if (worker.state = 'activated') {
+        resolve();
+      }
+    });
+  });
+};
+
+const register_service_worker = async () => {
+  const registration = await navigator.serviceWorker.register(
+    policy.createScriptURL('service_worker.js'), {
+      scope: '/',
+    }
+  );
+
+  await wait_for_activated(await navigator.serviceWorker.ready);
+
+  return registration;
+};
+
+window.addEventListener('load', (async () => {
+  const registration = await register_service_worker();
+  const request = await fetch('title.txt');
+  document.title = await request.text();
+}));
+)js");
+  builder.AddExchange(kUrl.Resolve("/service_worker.js"),
+                      {{":status", "200"}, {"content-type", "text/javascript"}},
+                      R"js(
+addEventListener('fetch', (event) => {
+  event.respondWith((async () => {
+    response = await fetch(event.request);
+    text = await response.text();
+    return new Response(text + ' data from service worker');
+  })());
+});
+
+self.addEventListener('activate', (event) => {
+  event.waitUntil(clients.claim());
+});
+)js");
+  RegisterWebApp(CreateIsolatedWebApp(
+      GURL(kUrl),
+      IsolationData{IsolationData::InstalledBundle{
+          .path = SignAndWriteBundleToDisk(builder.CreateBundle())}}));
+
+  NavigateAndWaitForTitle(GURL(kUrl),
+                          u"data from web bundle data from service worker");
+}
+
 }  // namespace
 }  // namespace web_app
diff --git a/chrome/browser/web_applications/test/fake_web_app_ui_manager.cc b/chrome/browser/web_applications/test/fake_web_app_ui_manager.cc
index 194d049f..82e7f47 100644
--- a/chrome/browser/web_applications/test/fake_web_app_ui_manager.cc
+++ b/chrome/browser/web_applications/test/fake_web_app_ui_manager.cc
@@ -11,6 +11,7 @@
 #include "base/task/single_thread_task_runner.h"
 #include "base/test/bind.h"
 #include "chrome/browser/web_applications/web_app_callback_app_identity.h"
+#include "components/services/app_service/public/cpp/app_launch_util.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace web_app {
@@ -112,4 +113,15 @@
   std::move(callback).Run(identity_update_dialog_action_for_testing.value());
 }
 
+base::Value FakeWebAppUiManager::LaunchWebApp(
+    apps::AppLaunchParams params,
+    LaunchWebAppWindowSetting launch_setting,
+    Profile& profile,
+    LaunchWebAppCallback callback,
+    AppLock& lock) {
+  std::move(callback).Run(nullptr, nullptr,
+                          apps::LaunchContainer::kLaunchContainerNone);
+  return base::Value("FakeWebAppUiManager::LaunchWebApp");
+}
+
 }  // namespace web_app
diff --git a/chrome/browser/web_applications/test/fake_web_app_ui_manager.h b/chrome/browser/web_applications/test/fake_web_app_ui_manager.h
index 8b07a3a8..4f823f1 100644
--- a/chrome/browser/web_applications/test/fake_web_app_ui_manager.h
+++ b/chrome/browser/web_applications/test/fake_web_app_ui_manager.h
@@ -60,6 +60,12 @@
       content::WebContents* web_contents,
       AppIdentityDialogCallback callback) override;
 
+  base::Value LaunchWebApp(apps::AppLaunchParams params,
+                           LaunchWebAppWindowSetting launch_setting,
+                           Profile& profile,
+                           LaunchWebAppCallback callback,
+                           AppLock& lock) override;
+
  private:
   std::map<AppId, size_t> app_id_to_num_windows_map_;
   std::map<AppId, AppId> uninstall_and_replace_map_;
diff --git a/chrome/browser/web_applications/web_app_command_scheduler.cc b/chrome/browser/web_applications/web_app_command_scheduler.cc
index 4d5692d..ad60f4c 100644
--- a/chrome/browser/web_applications/web_app_command_scheduler.cc
+++ b/chrome/browser/web_applications/web_app_command_scheduler.cc
@@ -11,6 +11,7 @@
 #include "base/threading/sequenced_task_runner_handle.h"
 #include "base/types/expected.h"
 #include "base/values.h"
+#include "chrome/browser/profiles/keep_alive/profile_keep_alive_types.h"
 #include "chrome/browser/profiles/keep_alive/scoped_profile_keep_alive.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/web_applications/commands/callback_command.h"
@@ -38,7 +39,9 @@
 #include "chrome/browser/web_applications/web_app_data_retriever.h"
 #include "chrome/browser/web_applications/web_app_provider.h"
 #include "chrome/browser/web_applications/web_app_sync_bridge.h"
+#include "chrome/browser/web_applications/web_app_ui_manager.h"
 #include "components/keep_alive_registry/keep_alive_registry.h"
+#include "components/keep_alive_registry/keep_alive_types.h"
 #include "components/keep_alive_registry/scoped_keep_alive.h"
 #include "components/webapps/browser/installable/installable_manager.h"
 #include "content/public/browser/web_contents.h"
@@ -400,6 +403,92 @@
           operation_name, std::move(lock_description), std::move(callback)));
 }
 
+void WebAppCommandScheduler::LaunchApp(
+    const AppId& app_id,
+    const base::CommandLine& command_line,
+    const base::FilePath& current_directory,
+    const absl::optional<GURL>& url_handler_launch_url,
+    const absl::optional<GURL>& protocol_handler_launch_url,
+    const absl::optional<GURL>& file_launch_url,
+    const std::vector<base::FilePath>& launch_files,
+    LaunchWebAppCallback callback) {
+  LaunchApp(WebAppUiManager::CreateAppLaunchParamsWithoutWindowConfig(
+                app_id, command_line, current_directory, url_handler_launch_url,
+                protocol_handler_launch_url, file_launch_url, launch_files),
+            LaunchWebAppWindowSetting::kOverrideWithWebAppConfig,
+            std::move(callback));
+}
+
+void WebAppCommandScheduler::LaunchAppWithCustomParams(
+    apps::AppLaunchParams params,
+    LaunchWebAppCallback callback) {
+  LaunchApp(std::move(params), LaunchWebAppWindowSetting::kUseLaunchParams,
+            std::move(callback));
+}
+
+void WebAppCommandScheduler::LaunchApp(apps::AppLaunchParams params,
+                                       LaunchWebAppWindowSetting option,
+                                       LaunchWebAppCallback callback) {
+  if (IsShuttingDown()) {
+    base::SequencedTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE, base::BindOnce(std::move(callback), nullptr, nullptr,
+                                  apps::LaunchContainer::kLaunchContainerNone));
+    return;
+  }
+  // Off the record profiles cannot be 'kept alive'.
+  std::unique_ptr<ScopedProfileKeepAlive> profile_keep_alive =
+      profile_->IsOffTheRecord()
+          ? nullptr
+          : std::make_unique<ScopedProfileKeepAlive>(
+                &profile_.get(), ProfileKeepAliveOrigin::kAppWindow);
+  std::unique_ptr<ScopedKeepAlive> browser_keep_alive =
+      std::make_unique<ScopedKeepAlive>(KeepAliveOrigin::WEB_APP_LAUNCH,
+                                        KeepAliveRestartOption::ENABLED);
+
+  auto launch_with_keep_alives = base::BindOnce(
+      &WebAppCommandScheduler::LaunchAppWithKeepAlives,
+      weak_ptr_factory_.GetWeakPtr(), std::move(params), std::move(option),
+      std::move(callback), std::move(profile_keep_alive),
+      std::move(browser_keep_alive));
+  // Because we are accessing the WebAppUiManager, we should wait until the
+  // provider has started to actually create the command.
+  if (!provider_->is_registry_ready()) {
+    provider_->on_registry_ready().Post(FROM_HERE,
+                                        std::move(launch_with_keep_alives));
+    return;
+  }
+  std::move(launch_with_keep_alives).Run();
+}
+
+void WebAppCommandScheduler::LaunchAppWithKeepAlives(
+    apps::AppLaunchParams params,
+    LaunchWebAppWindowSetting launch_setting,
+    LaunchWebAppCallback callback,
+    std::unique_ptr<ScopedProfileKeepAlive> profile_keep_alive,
+    std::unique_ptr<ScopedKeepAlive> browser_keep_alive) {
+  DCHECK(provider_->is_registry_ready());
+
+  // Decorate the callback to ensure the keep alives are kept alive during the
+  // execution of the launch.
+  callback = std::move(callback).Then(base::BindOnce(
+      [](std::unique_ptr<ScopedProfileKeepAlive> profile_keep_alive,
+         std::unique_ptr<ScopedKeepAlive> browser_keep_alive) {},
+      std::move(profile_keep_alive), std::move(browser_keep_alive)));
+
+  // Unretained is safe because this callback is lives on the WebAppProvider
+  // (via the WebAppCommandManager), which is a Profile KeyedService. It is
+  // destructed when the profile is shutting down as well. So it is impossible
+  // for this callback to be run with the WebAppUiManager being destructed.
+  AppId app_id = params.app_id;
+  ScheduleCallbackWithLock(
+      "LaunchApp",
+      std::make_unique<AppLockDescription, base::flat_set<AppId>>({app_id}),
+      base::BindOnce(&WebAppUiManager::LaunchWebApp,
+                     base::Unretained(&provider_->ui_manager()),
+                     std::move(params), launch_setting, std::ref(*profile_),
+                     std::move(callback)));
+}
+
 bool WebAppCommandScheduler::IsShuttingDown() const {
   return is_in_shutdown_ ||
          KeepAliveRegistry::GetInstance()->IsShuttingDown() ||
diff --git a/chrome/browser/web_applications/web_app_command_scheduler.h b/chrome/browser/web_applications/web_app_command_scheduler.h
index 2c3019c..844ba1d6 100644
--- a/chrome/browser/web_applications/web_app_command_scheduler.h
+++ b/chrome/browser/web_applications/web_app_command_scheduler.h
@@ -15,6 +15,7 @@
 #include "chrome/browser/web_applications/commands/manifest_update_finalize_command.h"
 #include "chrome/browser/web_applications/external_install_options.h"
 #include "chrome/browser/web_applications/web_app_install_params.h"
+#include "chrome/browser/web_applications/web_app_ui_manager.h"
 #include "components/webapps/browser/installable/installable_metrics.h"
 
 class GURL;
@@ -186,10 +187,37 @@
                                const base::Time& end_time,
                                base::OnceClosure done);
 
+  // Launches the given app. This call also uses keep-alives to guarantee that
+  // the browser and profile will not destruct before the launch is complete.
+  void LaunchApp(const AppId& app_id,
+                 const base::CommandLine& command_line,
+                 const base::FilePath& current_directory,
+                 const absl::optional<GURL>& url_handler_launch_url,
+                 const absl::optional<GURL>& protocol_handler_launch_url,
+                 const absl::optional<GURL>& file_launch_url,
+                 const std::vector<base::FilePath>& launch_files,
+                 LaunchWebAppCallback callback);
+
+  // Used to launch apps with a custom launch params. This does not respect the
+  // configuration of the app, and will respect whatever the params say.
+  void LaunchAppWithCustomParams(apps::AppLaunchParams params,
+                                 LaunchWebAppCallback callback);
+
   // TODO(https://crbug.com/1298130): expose all commands for web app
   // operations.
 
  private:
+  void LaunchApp(apps::AppLaunchParams params,
+                 LaunchWebAppWindowSetting option,
+                 LaunchWebAppCallback callback);
+
+  void LaunchAppWithKeepAlives(
+      apps::AppLaunchParams params,
+      LaunchWebAppWindowSetting option,
+      LaunchWebAppCallback callback,
+      std::unique_ptr<ScopedProfileKeepAlive> profile_keep_alive,
+      std::unique_ptr<ScopedKeepAlive> browser_keep_alive);
+
   bool IsShuttingDown() const;
 
   const raw_ref<Profile> profile_;
diff --git a/chrome/browser/web_applications/web_app_ui_manager.cc b/chrome/browser/web_applications/web_app_ui_manager.cc
index b1284e4d..de2c5f39 100644
--- a/chrome/browser/web_applications/web_app_ui_manager.cc
+++ b/chrome/browser/web_applications/web_app_ui_manager.cc
@@ -5,7 +5,10 @@
 #include "chrome/browser/web_applications/web_app_ui_manager.h"
 
 #include "base/auto_reset.h"
+#include "base/feature_list.h"
 #include "chrome/browser/web_applications/web_app_callback_app_identity.h"
+#include "chrome/common/chrome_features.h"
+#include "chrome/common/chrome_switches.h"
 
 namespace web_app {
 
@@ -33,6 +36,54 @@
   return g_auto_resolve_app_identity_update_dialog_for_testing;
 }
 
+// static
+apps::AppLaunchParams WebAppUiManager::CreateAppLaunchParamsWithoutWindowConfig(
+    const AppId& app_id,
+    const base::CommandLine& command_line,
+    const base::FilePath& current_directory,
+    const absl::optional<GURL>& url_handler_launch_url,
+    const absl::optional<GURL>& protocol_handler_launch_url,
+    const absl::optional<GURL>& file_launch_url,
+    const std::vector<base::FilePath>& launch_files) {
+  // At most one of these parameters should be non-empty.
+  DCHECK_LE(url_handler_launch_url.has_value() +
+                protocol_handler_launch_url.has_value() + !launch_files.empty(),
+            1);
+
+  apps::LaunchSource launch_source = apps::LaunchSource::kFromCommandLine;
+
+  if (url_handler_launch_url.has_value()) {
+    launch_source = apps::LaunchSource::kFromUrlHandler;
+  } else if (!launch_files.empty()) {
+    DCHECK(file_launch_url.has_value());
+    launch_source = apps::LaunchSource::kFromFileManager;
+  }
+
+  if (base::FeatureList::IsEnabled(features::kDesktopPWAsRunOnOsLogin) &&
+      command_line.HasSwitch(switches::kAppRunOnOsLoginMode)) {
+    launch_source = apps::LaunchSource::kFromOsLogin;
+  } else if (protocol_handler_launch_url.has_value()) {
+    launch_source = apps::LaunchSource::kFromProtocolHandler;
+  }
+
+  apps::AppLaunchParams params(app_id,
+                               apps::LaunchContainer::kLaunchContainerNone,
+                               WindowOpenDisposition::UNKNOWN, launch_source);
+  params.command_line = command_line;
+  params.current_directory = current_directory;
+  params.launch_files = launch_files;
+  params.url_handler_launch_url = url_handler_launch_url;
+  params.protocol_handler_launch_url = protocol_handler_launch_url;
+  if (file_launch_url) {
+    params.override_url = *file_launch_url;
+  } else {
+    params.override_url = GURL(command_line.GetSwitchValueASCII(
+        switches::kAppLaunchUrlForShortcutsMenuItem));
+  }
+
+  return params;
+}
+
 WebAppUiManager::WebAppUiManager() = default;
 
 WebAppUiManager::~WebAppUiManager() {
@@ -40,6 +91,10 @@
     observer.OnWebAppUiManagerDestroyed();
 }
 
+base::WeakPtr<WebAppUiManager> WebAppUiManager::GetWeakPtr() {
+  return weak_ptr_factory_.GetWeakPtr();
+}
+
 void WebAppUiManager::AddObserver(WebAppUiManagerObserver* observer) {
   observers_.AddObserver(observer);
 }
diff --git a/chrome/browser/web_applications/web_app_ui_manager.h b/chrome/browser/web_applications/web_app_ui_manager.h
index 0d5ce7c..71375475 100644
--- a/chrome/browser/web_applications/web_app_ui_manager.h
+++ b/chrome/browser/web_applications/web_app_ui_manager.h
@@ -10,13 +10,17 @@
 
 #include "base/auto_reset.h"
 #include "base/callback_forward.h"
+#include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
 #include "base/observer_list_types.h"
+#include "chrome/browser/apps/app_service/app_launch_params.h"
 #include "chrome/browser/web_applications/os_integration/os_integration_manager.h"
 #include "chrome/browser/web_applications/os_integration/web_app_shortcut.h"
 #include "chrome/browser/web_applications/web_app_callback_app_identity.h"
 #include "chrome/browser/web_applications/web_app_id.h"
+#include "components/services/app_service/public/cpp/app_launch_util.h"
 
+class Browser;
 class Profile;
 
 namespace content {
@@ -26,6 +30,7 @@
 
 namespace web_app {
 
+class AppLock;
 class WebAppSyncBridge;
 // WebAppUiManagerImpl can be used only in UI code.
 class WebAppUiManagerImpl;
@@ -51,6 +56,21 @@
   virtual void OnWebAppUiManagerDestroyed() {}
 };
 
+using LaunchWebAppCallback =
+    base::OnceCallback<void(Browser* browser,
+                            content::WebContents* web_contents,
+                            apps::LaunchContainer container)>;
+
+enum class LaunchWebAppWindowSetting {
+  // The window container and disposition from the launch params are used,
+  // despite the configuration of the web app.
+  kUseLaunchParams,
+  // The container and disposition of the launch are overridden with the
+  // configuration of the web app, which include the user preference as well as
+  // configuration in the web app's manifest.
+  kOverrideWithWebAppConfig,
+};
+
 // A chrome/browser/ representation of the chrome/browser/ui/ UI manager to
 // perform Web App UI operations or listen to Web App UI events, including
 // events from WebAppTabHelpers.
@@ -58,9 +78,23 @@
  public:
   static std::unique_ptr<WebAppUiManager> Create(Profile* profile);
 
+  // The returned params are populated except for the disposition and container,
+  // which is expected to be populated later when using `LaunchWebApp`
+  // with `kOverrideWithWebAppConfig`.
+  static apps::AppLaunchParams CreateAppLaunchParamsWithoutWindowConfig(
+      const AppId& app_id,
+      const base::CommandLine& command_line,
+      const base::FilePath& current_directory,
+      const absl::optional<GURL>& url_handler_launch_url,
+      const absl::optional<GURL>& protocol_handler_launch_url,
+      const absl::optional<GURL>& file_launch_url,
+      const std::vector<base::FilePath>& launch_files);
+
   WebAppUiManager();
   virtual ~WebAppUiManager();
 
+  base::WeakPtr<WebAppUiManager> GetWeakPtr();
+
   virtual void SetSubsystems(WebAppSyncBridge* sync_bridge,
                              OsIntegrationManager* os_integration_manager) = 0;
   virtual void Start() = 0;
@@ -117,8 +151,22 @@
       content::WebContents* web_contents,
       AppIdentityDialogCallback callback) = 0;
 
+  // This launches the web app in the appropriate configuration, the behavior of
+  // which depends on the given configuration here and the configuration of the
+  // web app. E.g. attaching file handles to the launch queue, focusing existing
+  // windows if configured by the launch handlers, etc. See
+  // `web_app::LaunchWebApp` and `WebAppLaunchProcess` for more info.
+  // If the app_id is invalid, an empty browser window is opened.
+  virtual base::Value LaunchWebApp(apps::AppLaunchParams params,
+                                   LaunchWebAppWindowSetting launch_setting,
+                                   Profile& profile,
+                                   LaunchWebAppCallback callback,
+                                   AppLock& lock) = 0;
+
  private:
   base::ObserverList<WebAppUiManagerObserver> observers_;
+
+  base::WeakPtrFactory<WebAppUiManager> weak_ptr_factory_{this};
 };
 
 }  // namespace web_app
diff --git a/chrome/browser/web_applications/web_app_ui_manager_unittest.cc b/chrome/browser/web_applications/web_app_ui_manager_unittest.cc
new file mode 100644
index 0000000..3a25637
--- /dev/null
+++ b/chrome/browser/web_applications/web_app_ui_manager_unittest.cc
@@ -0,0 +1,205 @@
+// 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/web_applications/web_app_ui_manager.h"
+
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
+#include "chrome/browser/apps/app_service/app_launch_params.h"
+#include "chrome/browser/web_applications/test/web_app_test_utils.h"
+#include "chrome/browser/web_applications/user_display_mode.h"
+#include "chrome/browser/web_applications/web_app_constants.h"
+#include "chrome/browser/web_applications/web_app_registrar.h"
+#include "chrome/browser/web_applications/web_app_ui_manager.h"
+#include "chrome/common/chrome_switches.h"
+#include "components/services/app_service/public/cpp/app_launch_util.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "url/gurl.h"
+
+namespace web_app {
+namespace {
+
+#if BUILDFLAG(IS_WIN)
+const base::FilePath::CharType kCurrentDirectory[] =
+    FILE_PATH_LITERAL("\\path");
+#else
+const base::FilePath::CharType kCurrentDirectory[] = FILE_PATH_LITERAL("/path");
+#endif  // BUILDFLAG(IS_WIN)
+
+const char kTestAppId[] = "test_app_id";
+
+class WebAppUiManagerTest : public testing::Test {
+ public:
+  WebAppUiManagerTest() = default;
+  ~WebAppUiManagerTest() override = default;
+
+ protected:
+  void InitAppWithDisplayMode(DisplayMode display_mode) {
+    auto web_app = std::make_unique<WebApp>(kTestAppId);
+    web_app->SetDisplayMode(display_mode);
+    if (display_mode == DisplayMode::kBrowser) {
+      web_app->SetUserDisplayMode(UserDisplayMode::kBrowser);
+    } else {
+      web_app->SetUserDisplayMode(UserDisplayMode::kStandalone);
+    }
+    Registry map;
+    map[kTestAppId] = std::move(web_app);
+    registrar_.InitRegistry(std::move(map));
+  }
+
+  apps::AppLaunchParams CreateLaunchParams(
+      const base::CommandLine& command_line,
+      const std::vector<base::FilePath>& launch_files,
+      const absl::optional<GURL>& url_handler_launch_url,
+      const absl::optional<GURL>& protocol_handler_launch_url) {
+    apps::AppLaunchParams params(
+        kTestAppId, apps::LaunchContainer::kLaunchContainerNone,
+        WindowOpenDisposition::UNKNOWN, apps::LaunchSource::kFromCommandLine);
+
+    params.current_directory = base::FilePath(kCurrentDirectory);
+    params.command_line = command_line;
+    params.launch_files = launch_files;
+    params.url_handler_launch_url = url_handler_launch_url;
+    params.protocol_handler_launch_url = protocol_handler_launch_url;
+
+    return params;
+  }
+
+  base::CommandLine CreateCommandLine() {
+    base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
+    command_line.AppendSwitchASCII(switches::kAppId, kTestAppId);
+    return command_line;
+  }
+
+  void ValidateOptionalGURL(const absl::optional<GURL>& actual,
+                            const absl::optional<GURL>& expected) {
+    ASSERT_EQ(actual.has_value(), expected.has_value());
+    if (actual.has_value()) {
+      EXPECT_EQ(actual.value(), expected.value());
+    }
+  }
+
+  void ValidateLaunchParams(const apps::AppLaunchParams& actual_results,
+                            const apps::AppLaunchParams& expected_results) {
+    EXPECT_EQ(actual_results.app_id, expected_results.app_id);
+    EXPECT_EQ(actual_results.command_line.GetArgs(),
+              expected_results.command_line.GetArgs());
+    EXPECT_EQ(actual_results.current_directory,
+              expected_results.current_directory);
+    EXPECT_EQ(actual_results.launch_source, expected_results.launch_source);
+    EXPECT_EQ(actual_results.launch_files, expected_results.launch_files);
+    EXPECT_EQ(actual_results.url_handler_launch_url,
+              expected_results.url_handler_launch_url);
+    ValidateOptionalGURL(actual_results.url_handler_launch_url,
+                         expected_results.url_handler_launch_url);
+    ValidateOptionalGURL(actual_results.protocol_handler_launch_url,
+                         expected_results.protocol_handler_launch_url);
+
+    EXPECT_EQ(actual_results.protocol_handler_launch_url,
+              expected_results.protocol_handler_launch_url);
+  }
+
+  WebAppRegistrarMutable registrar_{nullptr};
+};
+
+TEST_F(WebAppUiManagerTest, DefaultParamsTab) {
+  base::CommandLine command_line = CreateCommandLine();
+
+  InitAppWithDisplayMode(DisplayMode::kBrowser);
+
+  apps::AppLaunchParams expected_results =
+      CreateLaunchParams(command_line, std::vector<base::FilePath>(),
+                         /*url_handler_launch_url=*/absl::nullopt,
+                         /*protocol_handler_launch_url=*/absl::nullopt);
+
+  ValidateLaunchParams(
+      WebAppUiManager::CreateAppLaunchParamsWithoutWindowConfig(
+          kTestAppId, command_line, base::FilePath(kCurrentDirectory),
+          /*url_handler_launch_url=*/absl::nullopt,
+          /*protocol_handler_launch_url=*/absl::nullopt,
+          /*file_launch_url=*/absl::nullopt, /*launch_files=*/{}),
+      expected_results);
+}
+
+TEST_F(WebAppUiManagerTest, DefaultParamsStandalone) {
+  InitAppWithDisplayMode(DisplayMode::kStandalone);
+  base::CommandLine command_line = CreateCommandLine();
+
+  apps::AppLaunchParams expected_results =
+      CreateLaunchParams(command_line, std::vector<base::FilePath>(),
+                         /*url_handler_launch_url=*/absl::nullopt,
+                         /*protocol_handler_launch_url=*/absl::nullopt);
+
+  ValidateLaunchParams(
+      WebAppUiManager::CreateAppLaunchParamsWithoutWindowConfig(
+          kTestAppId, command_line, base::FilePath(kCurrentDirectory),
+          /*url_handler_launch_url=*/absl::nullopt,
+          /*protocol_handler_launch_url=*/absl::nullopt,
+          /*file_launch_url=*/absl::nullopt, /*launch_files=*/{}),
+      expected_results);
+}
+
+TEST_F(WebAppUiManagerTest, ProtocolHandlerUrl) {
+  InitAppWithDisplayMode(DisplayMode::kStandalone);
+  const absl::optional<GURL> protocol_handler_launch_url(
+      GURL("web+test://test"));
+  base::CommandLine command_line = CreateCommandLine();
+  command_line.AppendArg(protocol_handler_launch_url.value().spec());
+
+  apps::AppLaunchParams expected_results =
+      CreateLaunchParams(command_line, std::vector<base::FilePath>(),
+                         absl::nullopt, protocol_handler_launch_url);
+  expected_results.launch_source = apps::LaunchSource::kFromProtocolHandler;
+
+  ValidateLaunchParams(
+      WebAppUiManager::CreateAppLaunchParamsWithoutWindowConfig(
+          kTestAppId, command_line, base::FilePath(kCurrentDirectory),
+          /*url_handler_launch_url=*/absl::nullopt, protocol_handler_launch_url,
+          /*file_launch_url=*/absl::nullopt, /*launch_files=*/{}),
+      expected_results);
+}
+
+TEST_F(WebAppUiManagerTest, LaunchApplication_ProtocolMailTo) {
+  InitAppWithDisplayMode(DisplayMode::kStandalone);
+  const absl::optional<GURL> protocol_handler_launch_url(
+      GURL("mailto://test@test.com"));
+  base::CommandLine command_line = CreateCommandLine();
+
+  command_line.AppendArg(protocol_handler_launch_url.value().spec());
+
+  apps::AppLaunchParams expected_results =
+      CreateLaunchParams(command_line, std::vector<base::FilePath>(),
+                         absl::nullopt, protocol_handler_launch_url);
+  expected_results.launch_source = apps::LaunchSource::kFromProtocolHandler;
+
+  ValidateLaunchParams(
+      WebAppUiManager::CreateAppLaunchParamsWithoutWindowConfig(
+          kTestAppId, command_line, base::FilePath(kCurrentDirectory),
+          absl::nullopt, protocol_handler_launch_url, absl::nullopt, {}),
+      expected_results);
+}
+
+// Apps are not allowed to handle https:// either as protocols or as file paths.
+TEST_F(WebAppUiManagerTest, LaunchApplication_ProtocolDisallowed) {
+  InitAppWithDisplayMode(DisplayMode::kStandalone);
+  base::CommandLine command_line = CreateCommandLine();
+
+  command_line.AppendArg("https://www.test.com/");
+
+  apps::AppLaunchParams expected_results =
+      CreateLaunchParams(command_line, {}, absl::nullopt, absl::nullopt);
+
+  ValidateLaunchParams(
+      WebAppUiManager::CreateAppLaunchParamsWithoutWindowConfig(
+          kTestAppId, command_line, base::FilePath(kCurrentDirectory),
+          absl::nullopt, absl::nullopt, absl::nullopt, {}),
+      expected_results);
+}
+
+}  // namespace
+}  // namespace web_app
diff --git a/chrome/browser/webauthn/android/chrome_webauthn_client_android.cc b/chrome/browser/webauthn/android/chrome_webauthn_client_android.cc
index 31e2732c..974d9d0 100644
--- a/chrome/browser/webauthn/android/chrome_webauthn_client_android.cc
+++ b/chrome/browser/webauthn/android/chrome_webauthn_client_android.cc
@@ -4,7 +4,7 @@
 
 #include "chrome/browser/webauthn/android/chrome_webauthn_client_android.h"
 
-#include "chrome/browser/webauthn/android/conditional_ui_delegate_android.h"
+#include "chrome/browser/webauthn/android/webauthn_request_delegate_android.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/web_contents.h"
 
@@ -16,7 +16,7 @@
     content::RenderFrameHost* frame_host,
     const std::vector<device::DiscoverableCredentialMetadata>& credentials,
     base::OnceCallback<void(const std::vector<uint8_t>& id)> callback) {
-  auto* delegate = ConditionalUiDelegateAndroid::GetConditionalUiDelegate(
+  auto* delegate = WebAuthnRequestDelegateAndroid::GetRequestDelegate(
       content::WebContents::FromRenderFrameHost(frame_host));
 
   delegate->OnWebAuthnRequestPending(frame_host, credentials,
@@ -25,7 +25,7 @@
 
 void ChromeWebAuthnClientAndroid::CancelWebAuthnRequest(
     content::RenderFrameHost* frame_host) {
-  auto* delegate = ConditionalUiDelegateAndroid::GetConditionalUiDelegate(
+  auto* delegate = WebAuthnRequestDelegateAndroid::GetRequestDelegate(
       content::WebContents::FromRenderFrameHost(frame_host));
   delegate->CancelWebAuthnRequest(frame_host);
 }
diff --git a/chrome/browser/webauthn/android/conditional_ui_delegate_android.cc b/chrome/browser/webauthn/android/webauthn_request_delegate_android.cc
similarity index 68%
rename from chrome/browser/webauthn/android/conditional_ui_delegate_android.cc
rename to chrome/browser/webauthn/android/webauthn_request_delegate_android.cc
index 27b71a08..ac69db40 100644
--- a/chrome/browser/webauthn/android/conditional_ui_delegate_android.cc
+++ b/chrome/browser/webauthn/android/webauthn_request_delegate_android.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/webauthn/android/conditional_ui_delegate_android.h"
+#include "chrome/browser/webauthn/android/webauthn_request_delegate_android.h"
 
 #include <memory>
 
@@ -14,28 +14,28 @@
 #include "device/fido/discoverable_credential_metadata.h"
 
 // static
-ConditionalUiDelegateAndroid*
-ConditionalUiDelegateAndroid::GetConditionalUiDelegate(
+WebAuthnRequestDelegateAndroid*
+WebAuthnRequestDelegateAndroid::GetRequestDelegate(
     content::WebContents* web_contents) {
-  static constexpr char kConditionalUiDelegateKey[] =
+  static constexpr char kWebAuthnRequestDelegateKey[] =
       "ConditionalUiDelegateKey";
-  auto* delegate = static_cast<ConditionalUiDelegateAndroid*>(
-      web_contents->GetUserData(kConditionalUiDelegateKey));
+  auto* delegate = static_cast<WebAuthnRequestDelegateAndroid*>(
+      web_contents->GetUserData(kWebAuthnRequestDelegateKey));
   if (!delegate) {
-    auto new_user_data = std::make_unique<ConditionalUiDelegateAndroid>();
+    auto new_user_data = std::make_unique<WebAuthnRequestDelegateAndroid>();
     delegate = new_user_data.get();
-    web_contents->SetUserData(kConditionalUiDelegateKey,
+    web_contents->SetUserData(kWebAuthnRequestDelegateKey,
                               std::move(new_user_data));
   }
 
   return delegate;
 }
 
-ConditionalUiDelegateAndroid::ConditionalUiDelegateAndroid() = default;
+WebAuthnRequestDelegateAndroid::WebAuthnRequestDelegateAndroid() = default;
 
-ConditionalUiDelegateAndroid::~ConditionalUiDelegateAndroid() = default;
+WebAuthnRequestDelegateAndroid::~WebAuthnRequestDelegateAndroid() = default;
 
-void ConditionalUiDelegateAndroid::OnWebAuthnRequestPending(
+void WebAuthnRequestDelegateAndroid::OnWebAuthnRequestPending(
     content::RenderFrameHost* frame_host,
     const std::vector<device::DiscoverableCredentialMetadata>& credentials,
     base::OnceCallback<void(const std::vector<uint8_t>& id)> callback) {
@@ -49,7 +49,7 @@
       ->OnCredentialsReceived(credentials);
 }
 
-void ConditionalUiDelegateAndroid::CancelWebAuthnRequest(
+void WebAuthnRequestDelegateAndroid::CancelWebAuthnRequest(
     content::RenderFrameHost* frame_host) {
   // Prevent autofill from offering WebAuthn credentials in the popup.
   ChromeWebAuthnCredentialsDelegateFactory::GetFactory(
@@ -59,7 +59,7 @@
   std::move(webauthn_account_selection_callback_).Run(std::vector<uint8_t>());
 }
 
-void ConditionalUiDelegateAndroid::OnWebAuthnAccountSelected(
+void WebAuthnRequestDelegateAndroid::OnWebAuthnAccountSelected(
     const std::vector<uint8_t>& user_id) {
   std::move(webauthn_account_selection_callback_).Run(user_id);
 }
diff --git a/chrome/browser/webauthn/android/conditional_ui_delegate_android.h b/chrome/browser/webauthn/android/webauthn_request_delegate_android.h
similarity index 74%
rename from chrome/browser/webauthn/android/conditional_ui_delegate_android.h
rename to chrome/browser/webauthn/android/webauthn_request_delegate_android.h
index db312947..87fb23fa 100644
--- a/chrome/browser/webauthn/android/conditional_ui_delegate_android.h
+++ b/chrome/browser/webauthn/android/webauthn_request_delegate_android.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_WEBAUTHN_ANDROID_CONDITIONAL_UI_DELEGATE_ANDROID_H_
-#define CHROME_BROWSER_WEBAUTHN_ANDROID_CONDITIONAL_UI_DELEGATE_ANDROID_H_
+#ifndef CHROME_BROWSER_WEBAUTHN_ANDROID_WEBAUTHN_REQUEST_DELEGATE_ANDROID_H_
+#define CHROME_BROWSER_WEBAUTHN_ANDROID_WEBAUTHN_REQUEST_DELEGATE_ANDROID_H_
 
 #include <vector>
 
@@ -13,7 +13,7 @@
 namespace content {
 class RenderFrameHost;
 class WebContents;
-}
+}  // namespace content
 
 namespace device {
 class DiscoverableCredentialMetadata;
@@ -23,15 +23,16 @@
 // request handling for Conditional UI on Android. This is attached to a
 // WebContents via SetUserData. It caches a callback that will complete the
 // WebAuthn 'get' request when a user selects a credential.
-class ConditionalUiDelegateAndroid : public base::SupportsUserData::Data {
+class WebAuthnRequestDelegateAndroid : public base::SupportsUserData::Data {
  public:
-  ConditionalUiDelegateAndroid();
+  WebAuthnRequestDelegateAndroid();
 
-  ConditionalUiDelegateAndroid(const ConditionalUiDelegateAndroid&) = delete;
-  ConditionalUiDelegateAndroid& operator=(const ConditionalUiDelegateAndroid&) =
+  WebAuthnRequestDelegateAndroid(const WebAuthnRequestDelegateAndroid&) =
       delete;
+  WebAuthnRequestDelegateAndroid& operator=(
+      const WebAuthnRequestDelegateAndroid&) = delete;
 
-  ~ConditionalUiDelegateAndroid() override;
+  ~WebAuthnRequestDelegateAndroid() override;
 
   // Called when a Web Authentication Conditional UI request is received. This
   // provides the callback that will complete the request if and when a user
@@ -54,7 +55,7 @@
   // one does not already exist.
   // The delegate is destroyed along with the WebContents and so should not be
   // cached.
-  static ConditionalUiDelegateAndroid* GetConditionalUiDelegate(
+  static WebAuthnRequestDelegateAndroid* GetRequestDelegate(
       content::WebContents* web_contents);
 
  private:
@@ -62,4 +63,4 @@
       webauthn_account_selection_callback_;
 };
 
-#endif  // CHROME_BROWSER_WEBAUTHN_ANDROID_CONDITIONAL_UI_DELEGATE_ANDROID_H_
+#endif  // CHROME_BROWSER_WEBAUTHN_ANDROID_WEBAUTHN_REQUEST_DELEGATE_ANDROID_H_
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index 7d33096b..3954657 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-main-1670500610-9f34e513062fcbcb71398999bc0a1babdfa65162.profdata
+chrome-linux-main-1670565010-21ad6b059fdb1448e007b9562942f88bd110f296.profdata
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt
index 429cfbe..3ca7746 100644
--- a/chrome/build/mac-arm.pgo.txt
+++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@
-chrome-mac-arm-main-1670500610-86d03ea6e432ee5ea1515bea96ba3e2eaba3d6a7.profdata
+chrome-mac-arm-main-1670565010-1d55baa72fe0ca26a6554c8504dddb4c11cd6377.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index 79ab861..d7aaead 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-main-1670500610-5cab349354031406441977f478a8a918234d5056.profdata
+chrome-mac-main-1670565010-122429676482b3d8ffe1692f15e5b564d9ba0338.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index 991295f..9e29524 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1670500610-b137e54ba26b7335522031808273911e703b2957.profdata
+chrome-win32-main-1670565010-8e48bc4a5a823c928f9634374857ec9474e902c3.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index 807d502c..50fabe1 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1670500610-c410d81abe286e62fa959a0e1f762b2dec4de3d9.profdata
+chrome-win64-main-1670554292-a806b0303e0870c943edbc6d114035e9f1b0fac2.profdata
diff --git a/chrome/common/chromeos/extensions/api/diagnostics.idl b/chrome/common/chromeos/extensions/api/diagnostics.idl
index ef06e55e..f78b88c3 100644
--- a/chrome/common/chromeos/extensions/api/diagnostics.idl
+++ b/chrome/common/chromeos/extensions/api/diagnostics.idl
@@ -29,7 +29,8 @@
     gateway_can_be_pinged,
     sensitive_sensor,
     nvme_self_test,
-    fingerprint_alive
+    fingerprint_alive,
+    smartctl_check_with_percentage_used
   };
 
   enum RoutineStatus {
@@ -131,6 +132,10 @@
     long wear_level_threshold;
   };
 
+  dictionary RunSmartctlCheckRequest {
+    long? percentage_used_threshold;
+  };
+
   dictionary RunRoutineResponse {
     long id;
 
@@ -198,6 +203,6 @@
 
     [supportsPromises] static void runSignalStrengthRoutine(RunRoutineCallback callback);
 
-    [supportsPromises] static void runSmartctlCheckRoutine(RunRoutineCallback callback);
+    [supportsPromises] static void runSmartctlCheckRoutine(optional RunSmartctlCheckRequest request, RunRoutineCallback callback);
   };
 };
diff --git a/chrome/common/extensions/api/file_system_provider_internal.idl b/chrome/common/extensions/api/file_system_provider_internal.idl
index 0044234..1390d1b 100644
--- a/chrome/common/extensions/api/file_system_provider_internal.idl
+++ b/chrome/common/extensions/api/file_system_provider_internal.idl
@@ -8,6 +8,12 @@
  nodoc]
 namespace fileSystemProviderInternal {
   interface Functions {
+    // Internal. Callback for mount requests.
+    static void respondToMountRequest(
+        long requestId,
+        fileSystemProvider.ProviderError error,
+        long executionTime);
+
     // Internal. Success callback of the <code>onUnmountRequested</code>
     // event. Must be called when unmounting is completed.
     static void unmountRequestedSuccess(
diff --git a/chrome/common/extensions/api/passwords_private.idl b/chrome/common/extensions/api/passwords_private.idl
index 8341f719..1ed29b58 100644
--- a/chrome/common/extensions/api/passwords_private.idl
+++ b/chrome/common/extensions/api/passwords_private.idl
@@ -166,10 +166,23 @@
     boolean isMuted;
   };
 
+  // Structure which hold required information to display a link.
+  dictionary DomainInfo {
+    // A human readable version of the URL of the credential's origin. For
+    // android credentials this is usually the app name.
+    DOMString name;
+
+    // The URL that will be linked to when an entry is clicked.
+    DOMString url;
+  };
+
   // Entry used to display a password in the settings UI.
   dictionary PasswordUiEntry {
     // The URL collection corresponding to this saved password entry.
+    // TODO(crbug.com/1383972): remove |urls| when old Password Manager UI is
+    // deprecated.
     UrlCollection urls;
+    DomainInfo[]? affiliatedDomains;
 
     // The username used in conjunction with the saved password.
     DOMString username;
diff --git a/chrome/renderer/accessibility/read_anything_app_controller.cc b/chrome/renderer/accessibility/read_anything_app_controller.cc
index b3fd2bf..37e1107 100644
--- a/chrome/renderer/accessibility/read_anything_app_controller.cc
+++ b/chrome/renderer/accessibility/read_anything_app_controller.cc
@@ -270,7 +270,6 @@
     const ui::AXTreeUpdate& snapshot,
     const std::vector<ui::AXNodeID>& content_node_ids) {
   // Reset state.
-  display_root_id_ = ui::kInvalidAXNodeID;
   display_node_ids_.clear();
   start_node_ = nullptr;
   end_node_ = nullptr;
@@ -340,25 +339,17 @@
     end_offset_ = 0;
   }
 
-  // The display root is the lowest common ancestor between the start and end
-  // nodes. This is the lowest node in the tree which entirely contains the
-  // selection.
-  display_root_id_ = start_node_->GetLowestCommonAncestor(*end_node_)->id();
-
   // Display nodes are the nodes which will be displayed by the rendering
-  // algorithm of Read Anything app.ts. We wish to create a subtree with display
-  // root as the root which stretches from start node to end node.
+  // algorithm of Read Anything app.ts. We wish to create a subtree which
+  // stretches from start node to end node with tree root as the root.
 
-  // Add all ancestor ids of start node up to the display root. The first
-  // ancestor is the content node, which should also be added. This does a first
-  // walk from display root down to start node.
+  // Add all ancestor ids of start node, including the start node itself. This
+  // does a first walk down to start node.
   base::queue<ui::AXNode*> ancestors =
       start_node_->GetAncestorsCrossingTreeBoundaryAsQueue();
   while (!ancestors.empty()) {
     ui::AXNodeID ancestor_id = ancestors.front()->id();
     display_node_ids_.insert(ancestor_id);
-    if (ancestor_id == display_root_id_)
-      break;
     ancestors.pop();
   }
 
@@ -376,21 +367,18 @@
 void ReadAnythingAppController::PostProcessDistillableAXTree() {
   DCHECK(!content_node_ids_.empty());
 
-  // The display root is the lowest common ancestor between all of the content
-  // node IDs. This is the lowest node in the tree which entirely contains the
-  // distilled content.
-  display_root_id_ = GetLowestCommonAncestorOfContentNodes()->id();
-
   // Display nodes are the nodes which will be displayed by the rendering
-  // algorithm of Read Anything app.ts. We wish to create a subtree with display
-  // root as the root which stretches down to every content node and includes
-  // the descendants of each content node.
+  // algorithm of Read Anything app.ts. We wish to create a subtree which
+  // stretches down from tree root to every content node and includes the
+  // descendants of each content node.
   for (auto content_node_id : content_node_ids_) {
     ui::AXNode* content_node = GetAXNode(content_node_id);
     DCHECK(content_node);
 
-    // Add all ancestor ids in the queue up to the display root. The first
-    // ancestor is the content node, which should also be added.
+    // Add all ancestor ids, including the content node itself, which is the
+    // first ancestor in the queue. Exit the loop early if an ancestor is
+    // already in display_node_ids_; this means that all of the remaining
+    // ancestors in the queue are also already in display_node_ids.
     base::queue<ui::AXNode*> ancestors =
         content_node->GetAncestorsCrossingTreeBoundaryAsQueue();
     while (!ancestors.empty()) {
@@ -398,8 +386,6 @@
       if (base::Contains(display_node_ids_, ancestor_id))
         break;
       display_node_ids_.insert(ancestor_id);
-      if (ancestor_id == display_root_id_)
-        break;
       ancestors.pop();
     }
 
@@ -436,7 +422,7 @@
              isolate)
       .SetProperty("backgroundColor",
                    &ReadAnythingAppController::BackgroundColor)
-      .SetProperty("displayRootId", &ReadAnythingAppController::DisplayRootId)
+      .SetProperty("rootId", &ReadAnythingAppController::RootId)
       .SetProperty("fontName", &ReadAnythingAppController::FontName)
       .SetProperty("fontSize", &ReadAnythingAppController::FontSize)
       .SetProperty("foregroundColor",
@@ -458,8 +444,8 @@
                  &ReadAnythingAppController::SetThemeForTesting);
 }
 
-ui::AXNodeID ReadAnythingAppController::DisplayRootId() const {
-  return display_root_id_;
+ui::AXNodeID ReadAnythingAppController::RootId() const {
+  return tree_->root()->id();
 }
 
 SkColor ReadAnythingAppController::BackgroundColor() const {
@@ -645,21 +631,6 @@
   return tree_->GetFromId(ax_node_id);
 }
 
-ui::AXNode* ReadAnythingAppController::GetLowestCommonAncestorOfContentNodes()
-    const {
-  ui::AXNode* common_ancestor = nullptr;
-  for (auto content_node_id : content_node_ids_) {
-    ui::AXNode* content_node = GetAXNode(content_node_id);
-    DCHECK(content_node);
-    if (common_ancestor) {
-      common_ancestor = content_node->GetLowestCommonAncestor(*common_ancestor);
-    } else {
-      common_ancestor = content_node;
-    }
-  }
-  return common_ancestor;
-}
-
 bool ReadAnythingAppController::NodeIsContentNode(
     ui::AXNodeID ax_node_id) const {
   return base::Contains(content_node_ids_, ax_node_id);
diff --git a/chrome/renderer/accessibility/read_anything_app_controller.h b/chrome/renderer/accessibility/read_anything_app_controller.h
index 5a1138f..38c1aac0 100644
--- a/chrome/renderer/accessibility/read_anything_app_controller.h
+++ b/chrome/renderer/accessibility/read_anything_app_controller.h
@@ -45,11 +45,9 @@
 //  from the provided AXTreeUpdate and content nodes. There are two rendering
 //  algorithms:
 //  1. If the AXTreeUpdate has a selection, display a subtree containing all of
-//     the nodes between the selection start and end, with the least common
-//     ancestor of the start and end nodes as the subtree root.
+//     the nodes between the selection start and end.
 //  2. If the AXTreeUpdate has no selection, display a subtree containing all of
-//     the content nodes, their descendants, and their ancestors up to the
-//     least common ancestor of the content nodes.
+//     the content nodes, their descendants, and their ancestors.
 //
 class ReadAnythingAppController
     : public gin::Wrappable<ReadAnythingAppController>,
@@ -83,7 +81,7 @@
       read_anything::mojom::ReadAnythingThemePtr new_theme) override;
 
   // gin templates:
-  ui::AXNodeID DisplayRootId() const;
+  ui::AXNodeID RootId() const;
   SkColor BackgroundColor() const;
   std::string FontName() const;
   float FontSize() const;
@@ -104,9 +102,7 @@
   // Anything app.ts. These functions:
   // 1. Save state related to selection (start_node_, end_node_, start_offset_,
   //    end_offset_).
-  // 2. Save the display_root_id_, which is to be the root node of the tree
-  //    to be displayed by Read Anything app.ts.
-  // 3. Save the display_node_ids_, which is a set of all nodes to be displayed
+  // 2. Save the display_node_ids_, which is a set of all nodes to be displayed
   //    in Read Anything app.ts.
   void PostProcessAXTreeWithSelection(const ui::AXTreeData& tree_data);
   void PostProcessDistillableAXTree();
@@ -142,9 +138,6 @@
 
   ui::AXNode* GetAXNode(ui::AXNodeID ax_node_id) const;
 
-  // Returns the lowest common ancestor of all content nodes.
-  ui::AXNode* GetLowestCommonAncestorOfContentNodes() const;
-
   bool NodeIsContentNode(ui::AXNodeID ax_node_id) const;
 
   content::RenderFrame* render_frame_;
@@ -156,7 +149,6 @@
   std::unique_ptr<ui::AXTree> tree_;
   std::vector<ui::AXNodeID> content_node_ids_;
   std::set<ui::AXNodeID> display_node_ids_;
-  ui::AXNodeID display_root_id_;
   bool has_selection_ = false;
   ui::AXNode* start_node_ = nullptr;
   ui::AXNode* end_node_ = nullptr;
diff --git a/chrome/renderer/accessibility/read_anything_app_controller_browsertest.cc b/chrome/renderer/accessibility/read_anything_app_controller_browsertest.cc
index 40ccf87..ae36daf 100644
--- a/chrome/renderer/accessibility/read_anything_app_controller_browsertest.cc
+++ b/chrome/renderer/accessibility/read_anything_app_controller_browsertest.cc
@@ -56,7 +56,7 @@
     controller_->OnAXTreeDistilled(snapshot, content_node_ids);
   }
 
-  ui::AXNodeID DisplayRootId() { return controller_->DisplayRootId(); }
+  ui::AXNodeID RootId() { return controller_->RootId(); }
 
   bool DisplayNodeIdsContains(ui::AXNodeID ax_node_id) {
     return base::Contains(controller_->display_node_ids_, ax_node_id);
@@ -90,10 +90,6 @@
     return controller_->GetUrl(ax_node_id);
   }
 
-  ui::AXNode* GetLowestCommonAncestorOfContentNodes() {
-    return controller_->GetLowestCommonAncestorOfContentNodes();
-  }
-
   ui::AXTreeUpdate basic_snapshot_;
   ui::AXTreeData basic_tree_data_with_selection_;
 
@@ -122,48 +118,15 @@
   EXPECT_EQ(letter_spacing_value, LetterSpacing());
 }
 
-TEST_F(ReadAnythingAppControllerTest, DisplayRootId_NoSelection) {
-  basic_snapshot_.nodes.resize(6);
-  basic_snapshot_.nodes[3].child_ids = {5, 6};
-  basic_snapshot_.nodes[4].id = 5;
-  basic_snapshot_.nodes[5].id = 6;
-
-  // The lowest common ancestor of nodes 5 and 6 is node 4.
-  std::vector<ui::AXNodeID> content_node_ids_1 = {5, 6};
-  OnAXTreeDistilled(basic_snapshot_, content_node_ids_1);
-  EXPECT_EQ(4, DisplayRootId());
-
-  // The lowest common ancestor of nodes 4, 5, and 6 is the root node.
-  std::vector<ui::AXNodeID> content_node_ids_2 = {3, 5, 6};
-  OnAXTreeDistilled(basic_snapshot_, content_node_ids_2);
-  EXPECT_EQ(basic_snapshot_.root_id, DisplayRootId());
-
-  // The lowest common ancestor of node 5 is node 5.
-  std::vector<ui::AXNodeID> content_node_ids_3 = {5};
-  OnAXTreeDistilled(basic_snapshot_, content_node_ids_3);
-  EXPECT_EQ(5, DisplayRootId());
-
-  // The lowest common ancestor when there are no content nodes is
-  // kInvalidAXNodeID.
-  std::vector<ui::AXNodeID> content_node_ids_4 = {};
-  OnAXTreeDistilled(basic_snapshot_, content_node_ids_4);
-  EXPECT_EQ(ui::kInvalidAXNodeID, DisplayRootId());
-}
-
-TEST_F(ReadAnythingAppControllerTest,
-       DisplayRootId_WithSelectionAndContentNodeIds) {
-  basic_snapshot_.has_tree_data = true;
-  basic_snapshot_.tree_data = basic_tree_data_with_selection_;
+TEST_F(ReadAnythingAppControllerTest, RootIdIsSnapshotRootId) {
+  OnAXTreeDistilled(basic_snapshot_, {1});
+  EXPECT_EQ(1, RootId());
+  OnAXTreeDistilled(basic_snapshot_, {2});
+  EXPECT_EQ(1, RootId());
+  OnAXTreeDistilled(basic_snapshot_, {3});
+  EXPECT_EQ(1, RootId());
   OnAXTreeDistilled(basic_snapshot_, {4});
-  EXPECT_EQ(4, DisplayRootId());
-}
-
-TEST_F(ReadAnythingAppControllerTest,
-       DisplayRootId_WithSelectionButNoContentNodeIds) {
-  basic_snapshot_.has_tree_data = true;
-  basic_snapshot_.tree_data = basic_tree_data_with_selection_;
-  OnAXTreeDistilled(basic_snapshot_, {});
-  EXPECT_EQ(basic_snapshot_.root_id, DisplayRootId());
+  EXPECT_EQ(1, RootId());
 }
 
 TEST_F(ReadAnythingAppControllerTest, GetChildren_NoSelectionOrContentNodes) {
@@ -401,25 +364,3 @@
   EXPECT_FALSE(DisplayNodeIdsContains(3));
   EXPECT_FALSE(DisplayNodeIdsContains(4));
 }
-
-TEST_F(ReadAnythingAppControllerTest, GetLowestCommonAncestorOfContentNodes) {
-  basic_snapshot_.nodes.resize(6);
-  basic_snapshot_.nodes[3].child_ids = {5, 6};
-  basic_snapshot_.nodes[4].id = 5;
-  basic_snapshot_.nodes[5].id = 6;
-
-  OnAXTreeDistilled(basic_snapshot_, {5, 6});
-  EXPECT_EQ(4, GetLowestCommonAncestorOfContentNodes()->id());
-
-  OnAXTreeDistilled(basic_snapshot_, {4, 6});
-  EXPECT_EQ(4, GetLowestCommonAncestorOfContentNodes()->id());
-
-  OnAXTreeDistilled(basic_snapshot_, {3, 6});
-  EXPECT_EQ(1, GetLowestCommonAncestorOfContentNodes()->id());
-
-  OnAXTreeDistilled(basic_snapshot_, {2});
-  EXPECT_EQ(2, GetLowestCommonAncestorOfContentNodes()->id());
-
-  OnAXTreeDistilled(basic_snapshot_, {});
-  EXPECT_EQ(nullptr, GetLowestCommonAncestorOfContentNodes());
-}
diff --git a/chrome/renderer/resources/extensions/file_system_provider_custom_bindings.js b/chrome/renderer/resources/extensions/file_system_provider_custom_bindings.js
index 487514ff..d5bba3a 100644
--- a/chrome/renderer/resources/extensions/file_system_provider_custom_bindings.js
+++ b/chrome/renderer/resources/extensions/file_system_provider_custom_bindings.js
@@ -357,19 +357,18 @@
     massageArgumentsDefault);
 
 bindingUtil.registerEventArgumentMassager(
-    'fileSystemProvider.onMountRequested',
-    function(args, dispatch) {
+    'fileSystemProvider.onMountRequested', function(args, dispatch) {
+      var executionStart = Date.now();
+      var requestId = args[0];
       var onSuccessCallback = function() {
-        // chrome.fileManagerPrivate.addProvidedFileSystem doesn't accept
-        // any callbacks, so ignore the callback calls here.
-        // The callbacks exist for consistency with other on*Requested events.
+        fileSystemProviderInternal.respondToMountRequest(
+            requestId, 'OK', Date.now() - executionStart);
       };
       var onErrorCallback = function(error) {
         if (!verifyErrorForFailure(error))
           return;
-        // chrome.fileManagerPrivate.addProvidedFileSystem doesn't accept
-        // any callbacks, so ignore the callback calls here.
-        // The callbacks exist for consistency with other on*Requested events.
+        fileSystemProviderInternal.respondToMountRequest(
+            requestId, error, Date.now() - executionStart);
       }
       dispatch([onSuccessCallback, onErrorCallback]);
     });
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 42912fcb..94fafde 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -3628,6 +3628,7 @@
         "../browser/ash/accessibility/accessibility_common_browsertest.cc",
         "../browser/ash/accessibility/accessibility_extension_api_browsertest.cc",
         "../browser/ash/accessibility/accessibility_extension_channel_browsertest.cc",
+        "../browser/ash/accessibility/accessibility_highlights_browsertest.cc",
         "../browser/ash/accessibility/accessibility_manager_browsertest.cc",
         "../browser/ash/accessibility/accessibility_test_utils.cc",
         "../browser/ash/accessibility/accessibility_test_utils.h",
@@ -3653,6 +3654,7 @@
         "../browser/ash/app_list/arc/arc_usb_host_permission_browsertest.cc",
         "../browser/ash/app_list/search/files/drive_search_browsertest.cc",
         "../browser/ash/app_list/search/files/file_suggest_keyed_service_browsertest.cc",
+        "../browser/ash/app_list/search/help_app_search_browsertest.cc",
         "../browser/ash/app_list/search/test/app_list_search_test_helper.cc",
         "../browser/ash/app_list/search/test/app_list_search_test_helper.h",
         "../browser/ash/app_list/search/test/search_results_changed_waiter.cc",
@@ -4057,7 +4059,6 @@
         "../browser/ui/app_list/app_list_sort_browsertest.cc",
         "../browser/ui/app_list/chrome_app_list_item_browsertest.cc",
         "../browser/ui/app_list/chrome_app_list_model_updater_browsertest.cc",
-        "../browser/ui/app_list/search/help_app_search_browsertest.cc",
         "../browser/ui/ash/accelerator_commands_browsertest.cc",
         "../browser/ui/ash/app_list/app_list_with_recent_apps_browsertest.cc",
         "../browser/ui/ash/app_list/apps_grid_drag_browsertest.cc",
@@ -6915,6 +6916,7 @@
       "../browser/ui/tabs/existing_window_sub_menu_model_unittest.cc",
       "../browser/ui/tabs/pinned_tab_codec_unittest.cc",
       "../browser/ui/tabs/pinned_tab_service_unittest.cc",
+      "../browser/ui/tabs/saved_tab_groups/saved_tab_group_model_listener_unittest.cc",
       "../browser/ui/tabs/tab_menu_model_unittest.cc",
       "../browser/ui/tabs/tab_strip_model_stats_recorder_unittest.cc",
       "../browser/ui/tabs/tab_strip_model_unittest.cc",
@@ -7215,7 +7217,6 @@
       "//components/safe_browsing/core/common:safe_browsing_policy_handler",
       "//components/safety_check:test_support",
       "//components/send_tab_to_self:test_support",
-      "//components/services/app_service:unit_tests",
       "//components/services/app_service/public/cpp:icon_loader",
       "//components/services/app_service/public/cpp:icon_loader_test_support",
       "//components/services/app_service/public/cpp:preferred_app",
@@ -7333,9 +7334,15 @@
       "../browser/ash/app_list/arc/mock_arc_app_list_prefs_observer.h",
       "../browser/ash/app_list/reorder/app_list_reorder_core_unittest.cc",
       "../browser/ash/app_list/reorder/app_list_reorder_util_unittest.cc",
+      "../browser/ash/app_list/search/app_search_provider_test_base.cc",
+      "../browser/ash/app_list/search/app_search_provider_test_base.h",
+      "../browser/ash/app_list/search/app_search_provider_unittest.cc",
+      "../browser/ash/app_list/search/app_zero_state_provider_unittest.cc",
       "../browser/ash/app_list/search/arc/arc_app_shortcuts_search_provider_unittest.cc",
       "../browser/ash/app_list/search/arc/arc_playstore_search_provider_unittest.cc",
       "../browser/ash/app_list/search/arc/recommend_apps_fetcher_impl_unittest.cc",
+      "../browser/ash/app_list/search/assistant_text_search_provider_unittest.cc",
+      "../browser/ash/app_list/search/chrome_search_result_unittest.cc",
       "../browser/ash/app_list/search/common/string_util_unittest.cc",
       "../browser/ash/app_list/search/cros_action_history/cros_action_recorder_tab_tracker_unittest.cc",
       "../browser/ash/app_list/search/cros_action_history/cros_action_recorder_unittest.cc",
@@ -7349,11 +7356,17 @@
       "../browser/ash/app_list/search/files/zero_state_file_provider_unittest.cc",
       "../browser/ash/app_list/search/games/game_provider_unittest.cc",
       "../browser/ash/app_list/search/games/game_result_unittest.cc",
+      "../browser/ash/app_list/search/help_app_provider_unittest.cc",
+      "../browser/ash/app_list/search/help_app_zero_state_provider_unittest.cc",
+      "../browser/ash/app_list/search/keyboard_shortcut_provider_unittest.cc",
+      "../browser/ash/app_list/search/keyboard_shortcut_result_unittest.cc",
       "../browser/ash/app_list/search/omnibox/omnibox_answer_result_unittest.cc",
       "../browser/ash/app_list/search/omnibox/omnibox_lacros_provider_unittest.cc",
       "../browser/ash/app_list/search/omnibox/omnibox_provider_unittest.cc",
       "../browser/ash/app_list/search/omnibox/omnibox_result_unittest.cc",
       "../browser/ash/app_list/search/omnibox/open_tab_result_unittest.cc",
+      "../browser/ash/app_list/search/os_settings_provider_unittest.cc",
+      "../browser/ash/app_list/search/personalization_provider_unittest.cc",
       "../browser/ash/app_list/search/ranking/answer_ranker_unittest.cc",
       "../browser/ash/app_list/search/ranking/best_match_ranker_unittest.cc",
       "../browser/ash/app_list/search/ranking/filtering_ranker_unittest.cc",
@@ -7363,6 +7376,8 @@
       "../browser/ash/app_list/search/ranking/removed_results_proto_unittest.cc",
       "../browser/ash/app_list/search/ranking/removed_results_ranker_unittest.cc",
       "../browser/ash/app_list/search/ranking/score_normalizing_ranker_unittest.cc",
+      "../browser/ash/app_list/search/search_controller_impl_unittest.cc",
+      "../browser/ash/app_list/search/search_metrics_manager_unittest.cc",
       "../browser/ash/app_list/search/test/ranking_test_util.cc",
       "../browser/ash/app_list/search/test/ranking_test_util.h",
       "../browser/ash/app_list/search/test/search_controller_test_util.cc",
@@ -7471,20 +7486,6 @@
       "../browser/ui/app_list/app_list_test_util.h",
       "../browser/ui/app_list/chrome_app_list_item_manager_unittest.cc",
       "../browser/ui/app_list/md_icon_normalizer_unittest.cc",
-      "../browser/ui/app_list/search/app_search_provider_test_base.cc",
-      "../browser/ui/app_list/search/app_search_provider_test_base.h",
-      "../browser/ui/app_list/search/app_search_provider_unittest.cc",
-      "../browser/ui/app_list/search/app_zero_state_provider_unittest.cc",
-      "../browser/ui/app_list/search/assistant_text_search_provider_unittest.cc",
-      "../browser/ui/app_list/search/chrome_search_result_unittest.cc",
-      "../browser/ui/app_list/search/help_app_provider_unittest.cc",
-      "../browser/ui/app_list/search/help_app_zero_state_provider_unittest.cc",
-      "../browser/ui/app_list/search/keyboard_shortcut_provider_unittest.cc",
-      "../browser/ui/app_list/search/keyboard_shortcut_result_unittest.cc",
-      "../browser/ui/app_list/search/os_settings_provider_unittest.cc",
-      "../browser/ui/app_list/search/personalization_provider_unittest.cc",
-      "../browser/ui/app_list/search/search_controller_impl_unittest.cc",
-      "../browser/ui/app_list/search/search_metrics_manager_unittest.cc",
       "../browser/ui/ash/accessibility/accessibility_controller_client_unittest.cc",
       "../browser/ui/ash/accessibility/ax_tree_source_aura_unittest.cc",
       "../browser/ui/ash/ambient/ambient_client_impl_unittest.cc",
@@ -8917,7 +8918,6 @@
       "../browser/ui/views/tabs/tab_group_views_unittest.cc",
       "../browser/ui/views/tabs/tab_hover_card_bubble_view_unittest.cc",
       "../browser/ui/views/tabs/tab_hover_card_controller_unittest.cc",
-      "../browser/ui/views/tabs/tab_hover_card_metrics_unittest.cc",
       "../browser/ui/views/tabs/tab_strip_layout_unittest.cc",
       "../browser/ui/views/tabs/tab_strip_scroll_container_unittest.cc",
       "../browser/ui/views/tabs/tab_strip_scroll_session_unittest.cc",
@@ -10031,6 +10031,7 @@
 
     if (is_mac) {
       sources += [
+        "../browser/app_controller_mac_interactive_uitest.mm",
         "../browser/apps/platform_apps/app_shim_interactive_uitest_mac.mm",
         "../browser/apps/platform_apps/app_shim_quit_interactive_uitest_mac.mm",
         "../browser/focus_ring_browsertest_mac.h",
diff --git a/chrome/test/base/perf/performance_test.cc b/chrome/test/base/perf/performance_test.cc
index d22ad38..ffd3188 100644
--- a/chrome/test/base/perf/performance_test.cc
+++ b/chrome/test/base/perf/performance_test.cc
@@ -22,6 +22,7 @@
 #include "ash/public/cpp/wallpaper/wallpaper_types.h"
 #include "chrome/browser/ui/ash/wallpaper_controller_client_impl.h"
 #include "components/user_manager/user_names.h"
+#include "third_party/skia/include/core/SkCanvas.h"
 #include "ui/display/display.h"
 #include "ui/display/screen.h"
 #include "ui/gfx/image/image_skia.h"
diff --git a/chrome/test/base/web_ui_test_data_source.cc b/chrome/test/base/web_ui_test_data_source.cc
index d343f9c6..7da6915f 100644
--- a/chrome/test/base/web_ui_test_data_source.cc
+++ b/chrome/test/base/web_ui_test_data_source.cc
@@ -22,7 +22,8 @@
       network::mojom::CSPDirectiveName::ScriptSrc,
       "script-src chrome://* 'self';");
   source->OverrideContentSecurityPolicy(
-      network::mojom::CSPDirectiveName::WorkerSrc, "worker-src blob: 'self';");
+      network::mojom::CSPDirectiveName::WorkerSrc,
+      "worker-src blob: chrome://* 'self';");
   source->OverrideContentSecurityPolicy(
       network::mojom::CSPDirectiveName::FrameAncestors,
       "frame-ancestors chrome://* 'self';");
diff --git a/chrome/test/chromedriver/chrome/devtools_client_impl.cc b/chrome/test/chromedriver/chrome/devtools_client_impl.cc
index 609be68b..189a2206 100644
--- a/chrome/test/chromedriver/chrome/devtools_client_impl.cc
+++ b/chrome/test/chromedriver/chrome/devtools_client_impl.cc
@@ -18,6 +18,8 @@
 #include "base/memory/raw_ptr.h"
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
+#include "base/threading/platform_thread.h"
+#include "base/time/time.h"
 #include "chrome/test/chromedriver/chrome/devtools_event_listener.h"
 #include "chrome/test/chromedriver/chrome/javascript_dialog_manager.h"
 #include "chrome/test/chromedriver/chrome/log.h"
@@ -296,6 +298,9 @@
                   "BiDi tunnel is already set up in this client"};
   }
   Status status{kOk};
+  // TODO(https://crbug.com/chromedriver/4295#c1): implement the proper solution
+  // by waiting for the initial page navigation to be finished.
+  base::PlatformThread::Sleep(base::Milliseconds(200));
   // Page clients have target_id coinciding with id
   std::string target_id = id_;
   {
diff --git a/chrome/test/chromedriver/chrome/devtools_http_client.cc b/chrome/test/chromedriver/chrome/devtools_http_client.cc
index 41466539..9bc97985 100644
--- a/chrome/test/chromedriver/chrome/devtools_http_client.cc
+++ b/chrome/test/chromedriver/chrome/devtools_http_client.cc
@@ -170,24 +170,23 @@
   for (const base::Value& info_value : value->GetList()) {
     if (!info_value.is_dict())
       return Status(kUnknownError, "DevTools contains non-dictionary item");
-    const base::DictionaryValue& info =
-        base::Value::AsDictionaryValue(info_value);
-    std::string id;
-    if (!info.GetString("id", &id))
+    const base::Value::Dict& info = info_value.GetDict();
+    const std::string* id = info.FindString("id");
+    if (!id)
       return Status(kUnknownError, "DevTools did not include id");
-    std::string type_as_string;
-    if (!info.GetString("type", &type_as_string))
+    const std::string* type_as_string = info.FindString("type");
+    if (!type_as_string)
       return Status(kUnknownError, "DevTools did not include type");
-    std::string url;
-    if (!info.GetString("url", &url))
+    const std::string* url = info.FindString("url");
+    if (!url)
       return Status(kUnknownError, "DevTools did not include url");
-    std::string debugger_url;
-    info.GetString("webSocketDebuggerUrl", &debugger_url);
+    const std::string* debugger_url = info.FindString("webSocketDebuggerUrl");
     WebViewInfo::Type type;
-    Status status = ParseType(type_as_string, &type);
+    Status status = ParseType(*type_as_string, &type);
     if (status.IsError())
       return status;
-    temp_views_info.push_back(WebViewInfo(id, debugger_url, url, type));
+    temp_views_info.push_back(
+        WebViewInfo(*id, debugger_url ? *debugger_url : "", *url, type));
   }
   *views_info = WebViewsInfo(temp_views_info);
   return Status(kOk);
diff --git a/chrome/test/data/extensions/api_test/file_browser/file_watcher_test/test.js b/chrome/test/data/extensions/api_test/file_browser/file_watcher_test/test.js
index bb5e871d..80a9003c 100644
--- a/chrome/test/data/extensions/api_test/file_browser/file_watcher_test/test.js
+++ b/chrome/test/data/extensions/api_test/file_browser/file_watcher_test/test.js
@@ -18,79 +18,78 @@
  * after the operation. chrome.test.succeed is called when all the expected
  * events are received and verified.
  *
- * @constructor
  */
-function TestEventListener() {
-  /**
-   * Maps expectedEvent.entry.toURL() ->
-   *     {expectedEvent.eventType, expectedEvent.changeType}
-   *
-   * Set of events that are expected to be triggered during the test. Each
-   * object property represents one expected event.
-   *
-   * @type {Object<string, Object>}
-   * @private
-   */
-  this.expectedEvents_ = {};
+class TestEventListener {
+  constructor(id) {
+    /** @type {string} */
+    this.id = id;
 
-  /**
-   * List of fileManagerPrivate.onDirectoryChanged events received before file
-   * system operation was done.
-   *
-   * @type {Array<Object>}
-   * @private
-   */
-  this.eventQueue_ = [];
+    /**
+     * Maps expectedEvent.entry.toURL() ->
+     *     {expectedEvent.eventType, expectedEvent.changeType}
+     *
+     * Set of events that are expected to be triggered during the test. Each
+     * object property represents one expected event.
+     *
+     * @type {Object<string, Object>}
+     * @private
+     */
+    this.expectedEvents_ = {};
 
-  /**
-   * Whether the test listener is done. When set, all further |onSuccess_| and
-   * |onError| calls are ignored.
-   *
-   * @type {boolean}
-   * @private
-   */
-  this.done_ = false;
+    /**
+     * List of fileManagerPrivate.onDirectoryChanged events received before file
+     * system operation was done.
+     *
+     * @type {Array<Object>}
+     * @private
+     */
+    this.eventQueue_ = [];
 
-  /**
-   * An entry returned by the test file system operation.
-   *
-   * @type {Entry}
-   * @private
-   */
-  this.receivedEntry_ = null;
+    /**
+     * Whether the test listener is done. When set, all further |onSuccess_| and
+     * |onError| calls are ignored.
+     *
+     * @type {boolean}
+     * @private
+     */
+    this.done_ = false;
 
-  /**
-   * The listener to the fileManagerPrivate.onDirectoryChanged.
-   *
-   * @type {function(Object)}
-   * @private
-   */
-  this.eventListener_ = this.onDirectoryChanged_.bind(this);
-}
+    /**
+     * An entry returned by the test file system operation.
+     *
+     * @type {Entry}
+     * @private
+     */
+    this.receivedEntry_ = null;
 
-TestEventListener.prototype = {
+    /**
+     * The listener to the fileManagerPrivate.onDirectoryChanged.
+     *
+     * @type {function(Object)}
+     * @private
+     */
+    this.eventListener_ = this.onDirectoryChanged_.bind(this);
+  }
+
   /**
    * Starts listening for the onDirectoryChanged events.
    */
-  start: function() {
+  start() {
     chrome.fileManagerPrivate.onDirectoryChanged.addListener(
         this.eventListener_);
-  },
+  }
 
   /**
-   * Adds expectation for an event that should be encountered during the test.
+   * Adds expectation for an event that should be encountered during the
+   * test.
    *
    * @param {Entry} entry The event's entry argument.
-   * @param {string} eventType The event't type.
-   * @param {string} changeType The change type for the entry specified in
-   *     event.changedEntries[0].
    */
-  addExpectedEvent: function(entry, eventType, changeType) {
+  addExpectedEvent(entry) {
     this.expectedEvents_[entry.toURL()] = {
-        eventType: eventType,
-        changeType: changeType,
+      eventType: 'changed',
     };
-  },
+  }
 
   /**
    * Called by a test when the file system operation performed in the test
@@ -98,99 +97,118 @@
    *
    * @param {Entry} entry The entry returned by the file system operation.
    */
-  onFileSystemOperation: function(entry) {
+  onFileSystemOperation(entry) {
     this.receivedEntry_ = entry;
     this.eventQueue_.forEach(function(event) {
+      // When done the `onError` ignores any error, so returning early here.
+      if (this.done_) {
+        return;
+      }
+      console.log('*** Checking queued events');
       this.verifyReceivedEvent_(event);
     }.bind(this));
-  },
+  }
 
   /**
-   * Called when the test encounters an error. Does cleanup and ends the test
-   * with failure. Further |onError| and |onSuccess| calls will be ignored.
+   * Called when the test encounters an error. Does cleanup and ends the
+   * test with failure. Further |onError| and |onSuccess| calls will be
+   * ignored.
    *
    * @param {string} message An error message.
    */
-  onError: function(message) {
-    if (this.done_)
+  onError(message) {
+    if (this.done_) {
       return;
+    }
     this.done_ = true;
 
     chrome.fileManagerPrivate.onDirectoryChanged.removeListener(
         this.eventListener_);
     chrome.test.fail(message);
-  },
+  }
 
   /**
-   * Called when the test succeeds. Does cleanup and calls chrome.test.succeed.
-   * Further |onError| and |onSuccess| calls will be ignored.
+   * Called when the test succeeds. Does cleanup and calls
+   * chrome.test.succeed. Further |onError| and |onSuccess| calls will be
+   * ignored.
    *
    * @private
    */
-  onSuccess_: function() {
-    if (this.done_)
+  onSuccess_() {
+    if (this.done_) {
       return;
+    }
     this.done_ = true;
 
     chrome.fileManagerPrivate.onDirectoryChanged.removeListener(
         this.eventListener_);
     chrome.test.succeed();
-  },
+  }
 
   /**
    * onDirectoryChanged event listener.
-   * If the test file system operation is done, verifies the event, otherwise
-   * it adds the event to |eventQueue_|. The events from |eventQueue_| will be
-   * verified once the file system operation is done.
+   * If the test file system operation is done, verifies the event,
+   * otherwise it adds the event to |eventQueue_|. The events from
+   * |eventQueue_| will be verified once the file system operation is done.
    *
-   * @param {Object} event chrome.fileManagerPrivate.onDirectoryChanged event.
+   * @param {Object} event chrome.fileManagerPrivate.onDirectoryChanged
+   *     event.
    * @private
    */
-  onDirectoryChanged_: function(event) {
+  onDirectoryChanged_(event) {
     if (this.receivedEntry_) {
       this.verifyReceivedEvent_(event);
     } else {
+      console.log(`*** Queued event for ${event.entry.toURL()}`);
       this.eventQueue_.push(event);
     }
-  },
+  }
 
   /**
    * Verifies a received event.
    * It checks that there is an expected event for |event.entry.toURL()|.
    * If there is, the event is removed from the set of expected events.
-   * It verifies that the recived event matches the expected event parameters.
-   * If the received event was the last expected event, onSuccess_ is called.
+   * It verifies that the received event matches the expected event
+   * parameters. If the received event was the last expected event,
+   * onSuccess_ is called.
    *
-   * @param {Object} event chrome.fileManagerPrivate.onDirectoryChanged event.
+   * @param {Object} event chrome.fileManagerPrivate.onDirectoryChanged
+   *     event.
    * @private
    */
-  verifyReceivedEvent_: function(event) {
-    var entryURL = event.entry.toURL();
-    var expectedEvent = this.expectedEvents_[entryURL];
+  verifyReceivedEvent_(event) {
+    const entryURL = event.entry.toURL();
+    const expectedEvent = this.expectedEvents_[entryURL];
 
-    console.log('verifyReceivedEvent_: ' + event.eventType);
+    console.log(`${this.id} verifyReceivedEvent_: ${event.eventType} ${
+        event.entry.path}`);
     const state = JSON.stringify(this.expectedEvents_[entryURL]);
-    console.log('verifyReceivedEvent_: state ' + entryURL + ' ' + state);
+    console.log(`${this.id} verifyReceivedEvent_: state ${entryURL} ${state}`);
 
     if (!expectedEvent) {
-      this.onError('Event with unexpected entryURL: ' + entryURL + '\n' +
-                   ' Event type: ' + event.eventType + '\n');
+      this.onError(
+          `${this.id} Event with unexpected entryURL: ${entryURL} \n` +
+          `Event type: ${event.eventType} \n`);
       return;
     }
 
-
-    console.log('verifyReceivedEvent_: delete expectedEvents_ ' + entryURL);
+    console.log(
+        `${this.id} verifyReceivedEvent_: delete expectedEvents_ ${entryURL}`);
     delete this.expectedEvents_[entryURL];
 
-    if (expectedEvent.eventType != event.eventType) {
-      this.onError('Unexpected event type for entryURL: ' + entryURL + '\n' +
-                   ' Expected type: ' + expectedEvent.eventType + '\n' +
-                   ' Actual type: ' + event.eventType + '\n');
+    if (expectedEvent.eventType !== event.eventType) {
+      console.log(`Marking ${this.id} as error`);
+      this.onError(
+          'Unexpected event type for entryURL: ' + entryURL + '\n' +
+          ' Expected type: ' + expectedEvent.eventType + '\n' +
+          ' Actual type: ' + event.eventType + '\n');
       return;
     }
 
-    if (Object.keys(this.expectedEvents_).length == 0)
+    if (Object.keys(this.expectedEvents_).length == 0) {
+      console.log(`Marking ${this.id} as success`);
       this.onSuccess_();
+    }
   }
 }
 
@@ -212,7 +230,7 @@
  *    valid.
  */
 function initTests(callback) {
-  var testParams = {
+  const testParams = {
     /**
      * Whether the test parameters are valid.
      * @type {boolean}
@@ -221,7 +239,7 @@
     /**
      * TODO(tbarzic) : We should not need to have this. The watch api should
      * have the same behavior for local and drive file system.
-     * @type {boolean}}
+     * @type {boolean}
      */
     isOnDrive: false,
     /**
@@ -237,14 +255,17 @@
   };
 
   chrome.fileManagerPrivate.getVolumeMetadataList(function(volumeMetadataList) {
-    var possibleVolumeTypes = ['testing', 'drive'];
+    const possibleVolumeTypes = ['testing', 'drive'];
 
-    var sortedVolumeMetadataList = volumeMetadataList.filter(function(volume) {
-      return possibleVolumeTypes.indexOf(volume.volumeType) != -1;
-    }).sort(function(volumeA, volumeB) {
-      return possibleVolumeTypes.indexOf(volumeA.volumeType) -
-             possibleVolumeTypes.indexOf(volumeB.volumeType);
-    });
+    const sortedVolumeMetadataList =
+        volumeMetadataList
+            .filter(function(volume) {
+              return possibleVolumeTypes.indexOf(volume.volumeType) != -1;
+            })
+            .sort(function(volumeA, volumeB) {
+              return possibleVolumeTypes.indexOf(volumeA.volumeType) -
+                  possibleVolumeTypes.indexOf(volumeB.volumeType);
+            });
 
     if (sortedVolumeMetadataList.length == 0) {
       callback(
@@ -264,19 +285,26 @@
           testParams.isOnDrive =
               sortedVolumeMetadataList[0].volumeType == 'drive';
 
-          var testWatchEntries = [
-            {name: 'file',
-             path: getPath('test_dir/test_file.xul', testParams.isOnDrive),
-             type: 'file'},
-            {name: 'dir', path: getPath('test_dir/', testParams.isOnDrive),
-             type: 'dir'},
-            {name: 'subdir',
-             path: getPath('test_dir/subdir', testParams.isOnDrive),
-             type: 'dir'},
+          const testWatchEntries = [
+            {
+              name: 'file',
+              path: getPath('test_dir/test_file.xul', testParams.isOnDrive),
+              type: 'file'
+            },
+            {
+              name: 'dir',
+              path: getPath('test_dir/', testParams.isOnDrive),
+              type: 'dir'
+            },
+            {
+              name: 'subdir',
+              path: getPath('test_dir/subdir', testParams.isOnDrive),
+              type: 'dir'
+            },
           ];
 
           // Gets the first entry in |testWatchEntries| list.
-          var getNextEntry = function() {
+          const getNextEntry = function() {
             // If the list is empty, the test has been successfully
             // initialized, so call callback.
             if (testWatchEntries.length == 0) {
@@ -285,9 +313,9 @@
               return;
             }
 
-            var testEntry = testWatchEntries.shift();
+            const testEntry = testWatchEntries.shift();
 
-            var getFunction = null;
+            let getFunction = null;
             if (testEntry.type == 'file') {
               getFunction = fileSystem.root.getFile.bind(fileSystem.root);
             } else {
@@ -296,7 +324,7 @@
 
             // TODO(mtomasz): Remove this hack after migrating watchers to
             // chrome.fileSystem.
-            var getFunctionAndConvert = function(path, options, callback) {
+            const getFunctionAndConvert = function(path, options, callback) {
               getFunction(path, options, function(isolatedEntry) {
                 chrome.fileManagerPrivate.resolveIsolatedEntries(
                     [isolatedEntry],
@@ -353,137 +381,128 @@
           }));
     },
 
-    // Test that onDirectoryChanged is triggerred when a directory in a watched
+    // Test that onDirectoryChanged is triggered when a directory in a watched
     // directory is created.
     function onCreateDir() {
-      var testEventListener = new TestEventListener();
-      testEventListener.addExpectedEvent(testParams.entries.subdir,
-                                         'changed', 'added');
+      const testEventListener = new TestEventListener('onCreateDir');
+      testEventListener.addExpectedEvent(testParams.entries.subdir);
       testEventListener.start();
 
       testParams.fileSystem.root.getDirectory(
           getPath('test_dir/subdir/subsubdir', testParams.isOnDrive),
           {create: true, exclusive: true},
           testEventListener.onFileSystemOperation.bind(testEventListener),
-          testEventListener.onError.bind(testEventListener,
-                                         'Failed to create directory.'));
+          testEventListener.onError.bind(
+              testEventListener, 'Failed to create directory.'));
     },
 
-    // Test that onDirectoryChanged is triggerred when a file in a watched
+    // Test that onDirectoryChanged is triggered when a file in a watched
     // directory is created.
     function onCreateFile() {
-      var testEventListener = new TestEventListener();
-      testEventListener.addExpectedEvent(testParams.entries.subdir,
-                                         'changed', 'added');
+      const testEventListener = new TestEventListener('onCreateFile');
+      testEventListener.addExpectedEvent(testParams.entries.subdir);
       testEventListener.start();
 
       testParams.fileSystem.root.getFile(
           getPath('test_dir/subdir/file', testParams.isOnDrive),
           {create: true, exclusive: true},
           testEventListener.onFileSystemOperation.bind(testEventListener),
-          testEventListener.onError.bind(testEventListener,
-                                         'Failed to create file.'));
+          testEventListener.onError.bind(
+              testEventListener, 'Failed to create file.'));
     },
 
-    // Test that onDirectoryChanged is triggerred when a file in a watched
+    // Test that onDirectoryChanged is triggered when a file in a watched
     // directory is renamed.
     function onFileUpdated() {
-      var testEventListener = new TestEventListener();
-      testEventListener.addExpectedEvent(testParams.entries.subdir,
-                                         'changed', 'updated');
+      const testEventListener = new TestEventListener('onFileUpdated');
+      testEventListener.addExpectedEvent(testParams.entries.subdir);
 
       testEventListener.start();
 
       testParams.fileSystem.root.getFile(
-          getPath('test_dir/subdir/file', testParams.isOnDrive),
-          {},
+          getPath('test_dir/subdir/file', testParams.isOnDrive), {},
           function(entry) {
-            entry.moveTo(testParams.entries.subdir, 'renamed',
+            entry.moveTo(
+                testParams.entries.subdir, 'renamed',
                 testEventListener.onFileSystemOperation.bind(testEventListener),
-                testEventListener.onError.bind(testEventListener,
-                                               'Failed to rename the file.'));
+                testEventListener.onError.bind(
+                    testEventListener, 'Failed to rename the file.'));
           },
-          testEventListener.onError.bind(testEventListener,
-                                         'Failed to get file.'));
+          testEventListener.onError.bind(
+              testEventListener, 'Failed to get file.'));
     },
 
-    // Test that onDirectoryChanged is triggerred when a file in a watched
+    // Test that onDirectoryChanged is triggered when a file in a watched
     // directory is deleted.
     function onDeleteFile() {
-      var testEventListener = new TestEventListener();
-      testEventListener.addExpectedEvent(testParams.entries.subdir,
-                                         'changed', 'deleted');
+      const testEventListener = new TestEventListener('onDeleteFile');
+      testEventListener.addExpectedEvent(testParams.entries.subdir);
       testEventListener.start();
 
       testParams.fileSystem.root.getFile(
           getPath('test_dir/subdir/renamed', testParams.isOnDrive), {},
           function(entry) {
             entry.remove(
-                testEventListener.onFileSystemOperation.bind(testEventListener,
-                                                             entry),
-                testEventListener.onError.bind(testEventListener,
-                                               'Failed to remove the file.'));
+                testEventListener.onFileSystemOperation.bind(
+                    testEventListener, entry),
+                testEventListener.onError.bind(
+                    testEventListener, 'Failed to remove the file.'));
           },
-          testEventListener.onError.bind(testEventListener,
-                                         'Failed to get the file.'));
+          testEventListener.onError.bind(
+              testEventListener, 'Failed to get the file.'));
     },
 
-    // Test that onDirectoryChanged is triggerred when a watched file in a
+    // Test that onDirectoryChanged is triggered when a watched file in a
     // watched directory is deleted.
     // The behaviour is different for drive and local mount points. On drive,
     // there will be no event for the watched file.
     function onDeleteWatchedFile() {
-      var testEventListener = new TestEventListener();
-       testEventListener.addExpectedEvent(testParams.entries.dir,
-                                          'changed', 'deleted');
-       testEventListener.addExpectedEvent(
-           testParams.entries.file, 'changed', 'deleted');
+      const testEventListener = new TestEventListener('onDeleteWatchedFile');
+      testEventListener.addExpectedEvent(testParams.entries.dir);
+      testEventListener.addExpectedEvent(testParams.entries.file);
       testEventListener.start();
 
       testParams.fileSystem.root.getFile(
           getPath('test_dir/test_file.xul', testParams.isOnDrive), {},
           function(entry) {
             entry.remove(
-                testEventListener.onFileSystemOperation.bind(testEventListener,
-                                                             entry),
-                testEventListener.onError.bind(testEventListener,
-                                               'Failed to remove the file.'));
+                testEventListener.onFileSystemOperation.bind(
+                    testEventListener, entry),
+                testEventListener.onError.bind(
+                    testEventListener, 'Failed to remove the file.'));
           },
-          testEventListener.onError.bind(testEventListener,
-                                         'Failed to get the file.'));
+          testEventListener.onError.bind(
+              testEventListener, 'Failed to get the file.'));
     },
 
-    // Test that onDirectoryChanged is triggerred when a directory in a
+    // Test that onDirectoryChanged is triggered when a directory in a
     // watched directory is deleted.
     function onDeleteDir() {
-      var testEventListener = new TestEventListener();
-      testEventListener.addExpectedEvent(testParams.entries.subdir,
-                                         'changed', 'deleted');
+      const testEventListener = new TestEventListener('onDeleteDir');
+      testEventListener.addExpectedEvent(testParams.entries.subdir);
       testEventListener.start();
 
       testParams.fileSystem.root.getDirectory(
           getPath('test_dir/subdir/subsubdir', testParams.isOnDrive), {},
           function(entry) {
             entry.removeRecursively(
-                testEventListener.onFileSystemOperation.bind(testEventListener,
-                                                             entry),
-                testEventListener.onError.bind(testEventListener,
-                                               'Failed to remove the dir.'));
+                testEventListener.onFileSystemOperation.bind(
+                    testEventListener, entry),
+                testEventListener.onError.bind(
+                    testEventListener, 'Failed to remove the dir.'));
           },
-          testEventListener.onError.bind(testEventListener,
-                                         'Failed to get the dir.'));
+          testEventListener.onError.bind(
+              testEventListener, 'Failed to get the dir.'));
     },
 
-    // Test that onDirectoryChanged is triggerred when a watched directory in a
+    // Test that onDirectoryChanged is triggered when a watched directory in a
     // watched directory is deleted.
     // The behaviour is different for drive and local mount points. On drive,
     // there will be no event for the deleted directory.
     function onDeleteWatchedDir() {
-      var testEventListener = new TestEventListener();
-      testEventListener.addExpectedEvent(
-          testParams.entries.subdir, 'changed', 'deleted');
-      testEventListener.addExpectedEvent(testParams.entries.dir,
-                                         'changed', 'deleted');
+      const testEventListener = new TestEventListener('onDeleteWatchedDir');
+      testEventListener.addExpectedEvent(testParams.entries.subdir);
+      testEventListener.addExpectedEvent(testParams.entries.dir);
       testEventListener.start();
 
       testParams.fileSystem.root.getDirectory(
diff --git a/chrome/test/data/optimization_guide/no_og_image.html b/chrome/test/data/optimization_guide/no_og_image.html
deleted file mode 100644
index 8ae1e26..0000000
--- a/chrome/test/data/optimization_guide/no_og_image.html
+++ /dev/null
@@ -1,9 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-  <title>Test Page</title>
-</head>
-<body>
-  <p>hello</p>
-</body>
-</html>
\ No newline at end of file
diff --git a/chrome/test/data/optimization_guide/og_image.html b/chrome/test/data/optimization_guide/og_image.html
deleted file mode 100644
index 4b16606..0000000
--- a/chrome/test/data/optimization_guide/og_image.html
+++ /dev/null
@@ -1,11 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-  <meta property="og:image" content="https://google.com/image.png" />
-
-  <title>Test Page</title>
-</head>
-<body>
-  <p>hello</p>
-</body>
-</html>
\ No newline at end of file
diff --git a/chrome/test/data/optimization_guide/og_image_malformed.html b/chrome/test/data/optimization_guide/og_image_malformed.html
deleted file mode 100644
index aa3012e..0000000
--- a/chrome/test/data/optimization_guide/og_image_malformed.html
+++ /dev/null
@@ -1,11 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-  <meta property="og:image" content="httpx://image.png" />
-
-  <title>Test Page</title>
-</head>
-<body>
-  <p>hello</p>
-</body>
-</html>
\ No newline at end of file
diff --git a/chrome/test/data/policy/policy_test_cases.json b/chrome/test/data/policy/policy_test_cases.json
index 9b8addae..929d049b 100644
--- a/chrome/test/data/policy/policy_test_cases.json
+++ b/chrome/test/data/policy/policy_test_cases.json
@@ -21204,57 +21204,6 @@
       }
     ]
   },
-  "DeviceKeyboardBrightness": {
-    "os": [
-      "chromeos_ash"
-    ],
-    "policy_pref_mapping_tests": [
-      {
-        "policies": {},
-        "prefs": {
-          "ash.personalization.keyboard_brightness": {
-            "location": "local_state",
-            "default_value": 60,
-            "check_for_recommended": true
-          }
-        }
-      },
-      {
-        "policies": {},
-        "prefs": {
-          "ash.personalization.keyboard_brightness": {
-            "location": "user_profile",
-            "default_value": 60,
-            "check_for_recommended": true
-          }
-        }
-      },
-      {
-        "policies": {
-          "DeviceKeyboardBrightness": 40
-        },
-        "prefs": {
-          "ash.personalization.keyboard_brightness": {
-            "location": "local_state",
-            "value": 40,
-            "check_for_recommended": true
-          }
-        }
-      },
-      {
-        "policies": {
-          "DeviceKeyboardBrightness": 40
-        },
-        "prefs": {
-          "ash.personalization.keyboard_brightness": {
-            "location": "user_profile",
-            "value": 40,
-            "check_for_recommended": true
-          }
-        }
-      }
-    ]
-  },
   "ShowCastSessionsStartedByOtherDevices": {
     "os": [
       "win",
diff --git a/chrome/test/data/webui/bookmarks/BUILD.gn b/chrome/test/data/webui/bookmarks/BUILD.gn
index 383c543..b295b8f9 100644
--- a/chrome/test/data/webui/bookmarks/BUILD.gn
+++ b/chrome/test/data/webui/bookmarks/BUILD.gn
@@ -36,6 +36,7 @@
     "reducers_test.ts",
     "router_test.js",
     "store_test.js",
+    "test_bookmarks_api_proxy.ts",
     "test_browser_proxy.ts",
     "test_command_manager.ts",
     "test_store.ts",
diff --git a/chrome/test/data/webui/bookmarks/app_test.js b/chrome/test/data/webui/bookmarks/app_test.js
index c173fe1..c16c078 100644
--- a/chrome/test/data/webui/bookmarks/app_test.js
+++ b/chrome/test/data/webui/bookmarks/app_test.js
@@ -2,48 +2,50 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {HIDE_FOCUS_RING_ATTRIBUTE, LOCAL_STORAGE_FOLDER_STATE_KEY, LOCAL_STORAGE_TREE_WIDTH_KEY} from 'chrome://bookmarks/bookmarks.js';
+import {BookmarksApiProxyImpl, HIDE_FOCUS_RING_ATTRIBUTE, LOCAL_STORAGE_FOLDER_STATE_KEY, LOCAL_STORAGE_TREE_WIDTH_KEY} from 'chrome://bookmarks/bookmarks.js';
 import {isMac} from 'chrome://resources/js/platform.js';
 import {getDeepActiveElement} from 'chrome://resources/js/util_ts.js';
 import {down, keyDownOn, pressAndReleaseKeyOn} from 'chrome://resources/polymer/v3_0/iron-test-helpers/mock-interactions.js';
 import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
 
+import {TestBookmarksApiProxy} from './test_bookmarks_api_proxy.js';
 import {TestStore} from './test_store.js';
 import {createFolder, normalizeIterable, replaceBody} from './test_util.js';
 
 suite('<bookmarks-app>', function() {
   let app;
   let store;
+  let testBookmarksApiProxy;
 
   function resetStore() {
     store = new TestStore({});
     store.acceptInitOnce();
     store.replaceSingleton();
-
-    chrome.bookmarks.getTree = function(fn) {
-      fn([
-        createFolder(
-            '0',
-            [
-              createFolder(
-                  '1',
-                  [
-                    createFolder('11', []),
-                  ]),
-            ]),
-      ]);
-    };
+    testBookmarksApiProxy.setGetTree([
+      createFolder(
+          '0',
+          [
+            createFolder(
+                '1',
+                [
+                  createFolder('11', []),
+                ]),
+          ]),
+    ]);
   }
 
   setup(function() {
     window.localStorage.clear();
+    testBookmarksApiProxy = new TestBookmarksApiProxy();
+    BookmarksApiProxyImpl.setInstance(testBookmarksApiProxy);
     resetStore();
 
     app = document.createElement('bookmarks-app');
     replaceBody(app);
+    return flushTasks();
   });
 
-  test('write and load closed folder state', function() {
+  test('write and load closed folder state', async function() {
     const folderOpenStateList = [['1', true]];
     const folderOpenState = new Map(folderOpenStateList);
     store.data.folderOpenState = folderOpenState;
@@ -57,13 +59,14 @@
     resetStore();
     app = document.createElement('bookmarks-app');
     replaceBody(app);
+    await flushTasks();
 
     // Ensure closed folders are read from local storage.
     assertDeepEquals(
         folderOpenStateList, normalizeIterable(store.data.folderOpenState));
   });
 
-  test('write and load sidebar width', function() {
+  test('write and load sidebar width', async function() {
     assertEquals(getComputedStyle(app.$.sidebar).width, app.sidebarWidth_);
 
     const sidebarWidth = '500px';
@@ -74,13 +77,14 @@
 
     app = document.createElement('bookmarks-app');
     replaceBody(app);
+    await flushTasks();
 
     assertEquals(sidebarWidth, app.$.sidebar.style.width);
   });
 
   test('focus ring hides and restores', async function() {
-    await flushTasks();
     const list = app.shadowRoot.querySelector('bookmarks-list');
+    await flushTasks();
     const item = list.root.querySelectorAll('bookmarks-item')[0];
     const getFocusAttribute = () => app.getAttribute(HIDE_FOCUS_RING_ATTRIBUTE);
 
diff --git a/chrome/test/data/webui/bookmarks/dnd_manager_test.js b/chrome/test/data/webui/bookmarks/dnd_manager_test.js
index b7e56546..72b55e7 100644
--- a/chrome/test/data/webui/bookmarks/dnd_manager_test.js
+++ b/chrome/test/data/webui/bookmarks/dnd_manager_test.js
@@ -117,7 +117,6 @@
     });
     store.replaceSingleton();
 
-    chrome.bookmarks.move = function(id, details) {};
     chrome.bookmarkManagerPrivate.startDrag = function(
         idList, dragNodeIndex, isFromTouch) {
       dndManager.dragInfo_.setNativeDragData(createDragData(idList));
diff --git a/chrome/test/data/webui/bookmarks/router_test.js b/chrome/test/data/webui/bookmarks/router_test.js
index ef9feb5..43b34941 100644
--- a/chrome/test/data/webui/bookmarks/router_test.js
+++ b/chrome/test/data/webui/bookmarks/router_test.js
@@ -2,9 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {getDisplayedList, Store} from 'chrome://bookmarks/bookmarks.js';
+import {BookmarksApiProxyImpl, getDisplayedList, Store} from 'chrome://bookmarks/bookmarks.js';
 import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
 
+import {TestBookmarksApiProxy} from './test_bookmarks_api_proxy.js';
 import {TestStore} from './test_store.js';
 import {createFolder, createItem, getAllFoldersOpenState, replaceBody, testTree} from './test_util.js';
 
@@ -31,6 +32,7 @@
 
     router = document.createElement('bookmarks-router');
     replaceBody(router);
+    return flushTasks();
   });
 
   test('search updates from route', function() {
@@ -81,6 +83,13 @@
 });
 
 suite('URL preload', function() {
+  let testBookmarksApiProxy;
+
+  setup(function() {
+    testBookmarksApiProxy = new TestBookmarksApiProxy();
+    BookmarksApiProxyImpl.setInstance(testBookmarksApiProxy);
+  });
+
   /**
    * Reset the page state with a <bookmarks-app> and a clean Store, with the
    * given |url| to trigger routing initialization code.
@@ -90,53 +99,50 @@
     Store.setInstance(undefined);
     window.history.replaceState({}, '', url);
 
-    chrome.bookmarks.getTree = function(callback) {
-      callback([
-        createFolder(
-            '0',
-            [
-              createFolder(
-                  '1',
-                  [
-                    createFolder('11', []),
-                  ]),
-              createFolder(
-                  '2',
-                  [
-                    createItem('21'),
-                  ]),
-            ]),
-      ]);
-    };
+    testBookmarksApiProxy.setGetTree([
+      createFolder(
+          '0',
+          [
+            createFolder(
+                '1',
+                [
+                  createFolder('11', []),
+                ]),
+            createFolder(
+                '2',
+                [
+                  createItem('21'),
+                ]),
+          ]),
+    ]);
 
     const app = document.createElement('bookmarks-app');
     document.body.appendChild(app);
+    return flushTasks();
   }
 
-  test('loading a search URL performs a search', function() {
-    let lastQuery;
-    chrome.bookmarks.search = function(query) {
-      lastQuery = query;
-      return ['11'];
-    };
-
-    setupWithUrl('/?q=testQuery');
+  test('loading a search URL performs a search', async function() {
+    testBookmarksApiProxy.setSearchResponse(['11']);
+    await setupWithUrl('/?q=testQuery');
+    const lastQuery = await testBookmarksApiProxy.whenCalled('search');
     assertEquals('testQuery', lastQuery);
   });
 
-  test('loading a folder URL selects that folder', function() {
-    setupWithUrl('/?id=2');
+  test('loading a folder URL selects that folder', async function() {
+    await setupWithUrl('/?id=2');
     const state = Store.getInstance().data;
     assertEquals('2', state.selectedFolder);
     assertDeepEquals(['21'], getDisplayedList(state));
   });
 
-  test('loading an invalid folder URL selects the Bookmarks Bar', function() {
-    setupWithUrl('/?id=42');
-    const state = Store.getInstance().data;
-    assertEquals('1', state.selectedFolder);
-    return Promise.resolve().then(function() {
-      assertEquals('chrome://bookmarks/', window.location.href);
-    });
-  });
+  test(
+      'loading an invalid folder URL selects the Bookmarks Bar',
+      async function() {
+        await setupWithUrl('/?id=42');
+        const state = Store.getInstance().data;
+        assertEquals('1', state.selectedFolder);
+        return Promise.resolve().then(function() {
+          assertEquals('chrome://bookmarks/', window.location.href);
+        });
+      });
 });
diff --git a/chrome/test/data/webui/bookmarks/test_bookmarks_api_proxy.ts b/chrome/test/data/webui/bookmarks/test_bookmarks_api_proxy.ts
new file mode 100644
index 0000000..44cf4b9
--- /dev/null
+++ b/chrome/test/data/webui/bookmarks/test_bookmarks_api_proxy.ts
@@ -0,0 +1,49 @@
+// 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.
+
+import {BookmarksApiProxy, Query} from 'chrome://bookmarks/bookmarks.js';
+import {TestBrowserProxy} from 'chrome://webui-test/test_browser_proxy.js';
+
+export class TestBookmarksApiProxy extends TestBrowserProxy implements
+    BookmarksApiProxy {
+  private searchResponse_: chrome.bookmarks.BookmarkTreeNode[] = [];
+  private getTreeResponse_: chrome.bookmarks.BookmarkTreeNode[] = [];
+
+  constructor() {
+    super([
+      'create',
+      'getTree',
+      'search',
+      'update',
+    ]);
+  }
+
+  getTree() {
+    this.methodCalled('getTree');
+    return Promise.resolve(this.getTreeResponse_);
+  }
+
+  setGetTree(nodes: chrome.bookmarks.BookmarkTreeNode[]) {
+    this.getTreeResponse_ = nodes;
+  }
+
+  search(query: Query) {
+    this.methodCalled('search', query);
+    return Promise.resolve(this.searchResponse_);
+  }
+
+  setSearchResponse(response: chrome.bookmarks.BookmarkTreeNode[]) {
+    this.searchResponse_ = response;
+  }
+
+  update(id: string, changes: {title?: string, url?: string}) {
+    this.methodCalled('update', [id, changes]);
+    return Promise.resolve({id: '', title: ''});
+  }
+
+  create(bookmark: chrome.bookmarks.CreateDetails) {
+    this.methodCalled('create', bookmark);
+    return Promise.resolve({id: '', title: ''});
+  }
+}
diff --git a/chrome/test/data/webui/chromeos/cloud_upload/cloud_upload_app_test.ts b/chrome/test/data/webui/chromeos/cloud_upload/cloud_upload_app_test.ts
index 3e930b5..20a4e45 100644
--- a/chrome/test/data/webui/chromeos/cloud_upload/cloud_upload_app_test.ts
+++ b/chrome/test/data/webui/chromeos/cloud_upload/cloud_upload_app_test.ts
@@ -12,13 +12,14 @@
 import {SetupCancelDialogElement} from 'chrome://cloud-upload/setup_cancel_dialog.js';
 import {SignInPageElement} from 'chrome://cloud-upload/sign_in_page.js';
 import {WelcomePageElement} from 'chrome://cloud-upload/welcome_page.js';
-import {assertDeepEquals, assertEquals, assertFalse, assertNotEquals, assertTrue} from 'chrome://webui-test/chai_assert.js';
+import {assertDeepEquals, assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
 import {TestBrowserProxy} from 'chrome://webui-test/test_browser_proxy.js';
 
 interface ProxyOptions {
   fileName?: string|null;
   officeWebAppInstalled: boolean;
   installOfficeWebAppResult: boolean;
+  odfsMounted: boolean;
 }
 
 /**
@@ -42,6 +43,8 @@
         'isOfficeWebAppInstalled', {installed: options.officeWebAppInstalled});
     this.handler.setResultFor(
         'installOfficeWebApp', {installed: options.installOfficeWebAppResult});
+    this.handler.setResultFor('isODFSMounted', {mounted: options.odfsMounted});
+    this.handler.setResultFor('signInToOneDrive', {success: true});
   }
 
   isTest() {
@@ -149,6 +152,7 @@
       fileName: 'file.docx',
       officeWebAppInstalled: false,
       installOfficeWebAppResult: true,
+      odfsMounted: false,
     });
 
     // Go to the OneDrive upload page.
@@ -169,6 +173,7 @@
     await setUp({
       officeWebAppInstalled: false,
       installOfficeWebAppResult: true,
+      odfsMounted: false,
     });
 
     // Go to the OneDrive upload page.
@@ -181,27 +186,59 @@
     assertTrue(fileContainer.hidden);
   });
 
+  /**
+   * Tests that there is no PWA install page when the Office PWA is already
+   * installed.
+   */
   test('Set up OneDrive with Office PWA already installed', async () => {
     await setUp({
       officeWebAppInstalled: true,
       installOfficeWebAppResult: true,
+      odfsMounted: false,
     });
 
     await doWelcomePage();
-
-    // Make the setup skips the PWA install page and goes to the upload page.
-    // TODO(b/251046341): Once the sign in page is ready, this should check for
-    // that page instead.
-    assertEquals(null, cloudUploadApp.$('office-pwa-install-page'));
-
-    // Go to the OneDrive upload page.
     await doSignInPage();
 
     checkIsOneDriveUploadPage();
-    assertNotEquals(null, cloudUploadApp.$('upload-page'));
   });
 
   /**
+   * Tests that there is no sign in page when ODFS is already mounted.
+   */
+  test('Set up OneDrive with user already signed in', async () => {
+    await setUp({
+      fileName: 'file.docx',
+      officeWebAppInstalled: false,
+      installOfficeWebAppResult: true,
+      odfsMounted: true,
+    });
+
+    await doWelcomePage();
+    await doPWAInstallPage();
+
+    checkIsOneDriveUploadPage();
+  });
+
+  /**
+   * Tests that there is no Office PWA install page or sign in page when the
+   * Office PWA is already installed and ODFS is already mounted.
+   */
+  test(
+      'Set up OneDrive with Office PWA already installed and already signed in',
+      async () => {
+        await setUp({
+          officeWebAppInstalled: true,
+          installOfficeWebAppResult: true,
+          odfsMounted: true,
+        });
+
+        await doWelcomePage();
+
+        checkIsOneDriveUploadPage();
+      });
+
+  /**
    * Tests that clicking the open file button triggers the right
    * `respondAndClose` mojo request.
    */
@@ -210,6 +247,7 @@
       fileName: 'file.docx',
       officeWebAppInstalled: false,
       installOfficeWebAppResult: true,
+      odfsMounted: false,
     });
     checkIsWelcomePage();
 
@@ -237,6 +275,7 @@
       fileName: 'file.docx',
       officeWebAppInstalled: false,
       installOfficeWebAppResult: true,
+      odfsMounted: false,
     });
 
     // Go to the OneDrive upload page.
@@ -261,6 +300,7 @@
         await setUp({
           officeWebAppInstalled: false,
           installOfficeWebAppResult: true,
+          odfsMounted: false,
         });
 
         // Go to the specified page.
diff --git a/chrome/test/data/webui/chromeos/os_feedback_ui/search_page_test.js b/chrome/test/data/webui/chromeos/os_feedback_ui/search_page_test.js
index c21cd3d..1e24ad2 100644
--- a/chrome/test/data/webui/chromeos/os_feedback_ui/search_page_test.js
+++ b/chrome/test/data/webui/chromeos/os_feedback_ui/search_page_test.js
@@ -5,13 +5,14 @@
 import {fakeEmptySearchResponse, fakeFeedbackContext, fakeInternalUserFeedbackContext, fakeSearchResponse} from 'chrome://os-feedback/fake_data.js';
 import {FakeHelpContentProvider} from 'chrome://os-feedback/fake_help_content_provider.js';
 import {FeedbackFlowState} from 'chrome://os-feedback/feedback_flow.js';
+import {SearchResponse} from 'chrome://os-feedback/feedback_types.js';
 import {setHelpContentProviderForTesting} from 'chrome://os-feedback/mojo_interface_provider.js';
 import {domainQuestions, questionnaireBegin} from 'chrome://os-feedback/questionnaire.js';
 import {OS_FEEDBACK_UNTRUSTED_ORIGIN, SearchPageElement} from 'chrome://os-feedback/search_page.js';
 import {getDeepActiveElement} from 'chrome://resources/ash/common/util.js';
+import {assertEquals, assertFalse, assertNotEquals, assertTrue} from 'chrome://webui-test/chromeos/chai_assert.js';
 import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
 
-import {assertEquals, assertFalse, assertNotEquals, assertTrue} from 'chrome://webui-test/chromeos/chai_assert.js';
 import {eventToPromise} from '../test_util.js';
 
 export function searchPageTestSuite() {
@@ -223,6 +224,39 @@
     assertEquals(longTextNoResult, provider.lastQuery);
     // Search result count should be 0.
     assertEquals(0, page.getSearchResultCountForTesting());
+    // Popular content should be displayed (i.e. isPopularContent = true).
+    assertTrue(page.getIsPopularContentForTesting_());
+  });
+
+  /**
+   * Test that popular help content is displayed when no match is returned after
+   * filtering but there were matches.
+   */
+  test('NoItemsReturnedButThereWereMatches', async () => {
+    /** {?Element} */
+    let textAreaElement = null;
+
+    await initializePage();
+    page.feedbackContext = fakeFeedbackContext;
+    textAreaElement = getElement('#descriptionText');
+
+    /** @type {!SearchResponse} */
+    const fakeResponse = {
+      results: [],  // None items returned after filtering out other languages.
+      totalResults: 15,  // 15 matches.
+    };
+
+    provider.setFakeSearchResponse(fakeResponse);
+    textAreaElement.value = 'abc';
+
+    textAreaElement.dispatchEvent(new Event('input'));
+    await flushTasks();
+    // Verify that getHelpContent() has been called with query 'abc'.
+    assertEquals('abc', provider.lastQuery);
+    // Search result count should be 0.
+    assertEquals(0, page.getSearchResultCountForTesting());
+    // Popular content should be displayed (i.e. isPopularContent = true).
+    assertTrue(page.getIsPopularContentForTesting_());
   });
 
   /**
diff --git a/chrome/test/data/webui/chromeos/personalization_app/dynamic_color_element_test.ts b/chrome/test/data/webui/chromeos/personalization_app/dynamic_color_element_test.ts
index ddb8a59..ce1a4fa 100644
--- a/chrome/test/data/webui/chromeos/personalization_app/dynamic_color_element_test.ts
+++ b/chrome/test/data/webui/chromeos/personalization_app/dynamic_color_element_test.ts
@@ -7,10 +7,11 @@
 import 'chrome://personalization/strings.m.js';
 import 'chrome://webui-test/mojo_webui_test_support.js';
 
-import {DynamicColorElement, SetColorSchemePrefAction, SetStaticColorPrefAction, ThemeActionName} from 'chrome://personalization/js/personalization_app.js';
+import {DynamicColorElement, SetColorSchemePrefAction, SetStaticColorPrefAction, ThemeActionName, ThemeObserver} from 'chrome://personalization/js/personalization_app.js';
 import {CrButtonElement} from 'chrome://resources/cr_elements/cr_button/cr_button.js';
 import {CrToggleElement} from 'chrome://resources/cr_elements/cr_toggle/cr_toggle.js';
-import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
+import {hexColorToSkColor} from 'chrome://resources/js/color_utils.js';
+import {assertDeepEquals, assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
 import {waitAfterNextRender} from 'chrome://webui-test/polymer_test_util.js';
 
 import {baseSetup, dispatchKeydown, getActiveElement, initElement, teardownElement, waitForActiveElement} from './personalization_app_test_utils.js';
@@ -64,6 +65,7 @@
     const mocks = baseSetup();
     personalizationStore = mocks.personalizationStore;
     themeProvider = mocks.themeProvider;
+    ThemeObserver.initThemeObserverIfNeeded();
 
     dynamicColorElement = initElement(DynamicColorElement)!;
     await waitAfterNextRender(dynamicColorElement);
@@ -71,6 +73,7 @@
 
   teardown(async () => {
     teardownElement(dynamicColorElement);
+    ThemeObserver.shutdown();
   });
 
   test('displays content', async () => {
@@ -176,13 +179,20 @@
   test('set static color', async () => {
     personalizationStore.expectAction(ThemeActionName.SET_STATIC_COLOR);
     showStaticColorButtons();
+    const button = getStaticColorButtons()[1]!;
+    assertEquals(button.getAttribute('aria-checked'), 'false');
+    personalizationStore.setReducersEnabled(true);
 
-    getStaticColorButtons()[1]!.click();
+    button.click();
     await themeProvider.whenCalled('setStaticColor');
 
     const action =
         await personalizationStore.waitForAction(
             ThemeActionName.SET_STATIC_COLOR) as SetStaticColorPrefAction;
     assertTrue(!!action.staticColor);
+    assertDeepEquals(
+        hexColorToSkColor(button.dataset['staticColor']!),
+        personalizationStore.data.theme.staticColorSelected);
+    assertEquals(button.getAttribute('aria-checked'), 'true');
   });
 });
diff --git a/chrome/test/data/webui/chromeos/personalization_app/test_theme_interface_provider.ts b/chrome/test/data/webui/chromeos/personalization_app/test_theme_interface_provider.ts
index 253f709..1c1c29b 100644
--- a/chrome/test/data/webui/chromeos/personalization_app/test_theme_interface_provider.ts
+++ b/chrome/test/data/webui/chromeos/personalization_app/test_theme_interface_provider.ts
@@ -15,13 +15,16 @@
       'setColorModeAutoScheduleEnabled',
       'setColorScheme',
       'setStaticColor',
+      'getStaticColor',
       'isDarkModeEnabled',
       'isColorModeAutoScheduleEnabled',
     ]);
+    this.staticColor = null;
   }
 
   isDarkModeEnabledResponse = true;
   isColorModeAutoScheduleEnabledResponse = true;
+  staticColor: SkColor|null;
 
   themeObserverRemote: ThemeObserverInterface|null = null;
 
@@ -43,10 +46,17 @@
 
   setColorScheme(colorScheme: ColorScheme) {
     this.methodCalled('setColorScheme', colorScheme);
+    this.staticColor = null;
   }
 
   setStaticColor(color: SkColor) {
     this.methodCalled('setStaticColor', color);
+    this.staticColor = color;
+  }
+
+  getStaticColor() {
+    this.methodCalled('getStaticColor');
+    return Promise.resolve({staticColor: this.staticColor});
   }
 
   isDarkModeEnabled() {
diff --git a/chrome/test/data/webui/cr_components/chromeos/network/apn_list_item_test.js b/chrome/test/data/webui/cr_components/chromeos/network/apn_list_item_test.js
index 08fa0c5..0f4d351d 100644
--- a/chrome/test/data/webui/cr_components/chromeos/network/apn_list_item_test.js
+++ b/chrome/test/data/webui/cr_components/chromeos/network/apn_list_item_test.js
@@ -212,15 +212,25 @@
       async function() {
         apnListItem.apn = TEST_APN_EVENT_DATA.apn;
         apnListItem.guid = TEST_APN_EVENT_DATA.guid;
-
-        const apnDetailsClickedEvent =
+        apnListItem.isAutoDetected = true;
+        let apnDetailsClickedEvent =
             eventToPromise('show-apn-detail-dialog', window);
         assertTrue(!!apnListItem.$.detailsButton);
         apnListItem.$.detailsButton.click();
-        const eventData = await apnDetailsClickedEvent;
+        let eventData = await apnDetailsClickedEvent;
 
         assertEquals(TEST_APN_EVENT_DATA.apn.name, eventData.detail.apn.name);
         assertEquals(TEST_APN_EVENT_DATA.guid, eventData.detail.guid);
         assertEquals(TEST_APN_EVENT_DATA.mode, eventData.detail.mode);
+
+        // Case: the apn list item is not auto detected
+        apnDetailsClickedEvent =
+            eventToPromise('show-apn-detail-dialog', window);
+        apnListItem.isAutoDetected = false;
+        apnListItem.$.detailsButton.click();
+        eventData = await apnDetailsClickedEvent;
+        assertEquals(TEST_APN_EVENT_DATA.apn.name, eventData.detail.apn.name);
+        assertEquals(TEST_APN_EVENT_DATA.guid, eventData.detail.guid);
+        assertEquals(ApnDetailDialogMode.EDIT, eventData.detail.mode);
       });
 });
diff --git a/chrome/test/data/webui/password_manager/password_details_card_test.ts b/chrome/test/data/webui/password_manager/password_details_card_test.ts
index 27270caa..2d4b18f 100644
--- a/chrome/test/data/webui/password_manager/password_details_card_test.ts
+++ b/chrome/test/data/webui/password_manager/password_details_card_test.ts
@@ -4,12 +4,11 @@
 
 import 'chrome://password-manager/password_manager.js';
 
-import {CrInputElement} from 'chrome://resources/cr_elements/cr_input/cr_input.js';
-import {Page, PasswordDetailsCardElement, PasswordManagerImpl, Router} from 'chrome://password-manager/password_manager.js';
+import {CrInputElement, Page, PasswordDetailsCardElement, PasswordManagerImpl, Router} from 'chrome://password-manager/password_manager.js';
+import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
 import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
 import {isVisible} from 'chrome://webui-test/test_util.js';
-import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 
 import {TestPasswordManagerProxy} from './test_password_manager_proxy.js';
 import {createPasswordEntry} from './test_util.js';
@@ -37,8 +36,6 @@
     assertEquals(password.username, card.$.usernameValue.value);
     assertEquals(password.password, card.$.passwordValue.value);
     assertEquals('password', card.$.passwordValue.type);
-    assertEquals(password.urls.shown, card.$.linkValue.textContent!.trim());
-    assertEquals(password.urls.link, card.$.linkValue.getAttribute('href'));
     const note: CrInputElement|null =
         card.shadowRoot!.querySelector('#noteValue');
     assertTrue(!!note);
@@ -63,8 +60,6 @@
     assertEquals(password.username, card.$.usernameValue.value);
     assertEquals(password.federationText, card.$.passwordValue.value);
     assertEquals('text', card.$.passwordValue.type);
-    assertEquals(password.urls.shown, card.$.linkValue.textContent!.trim());
-    assertEquals(password.urls.link, card.$.linkValue.getAttribute('href'));
     const note: CrInputElement|null =
         card.shadowRoot!.querySelector('#noteValue');
     assertFalse(!!note);
@@ -118,4 +113,30 @@
         loadTimeData.getString('passwordCopiedToClipboard'),
         card.$.toast.textContent!.trim());
   });
+
+  test('Links properly displayed', async function() {
+    const password = createPasswordEntry(
+        {url: 'test.com', username: 'vik', password: 'password69'});
+    password.affiliatedDomains = [
+      {name: 'test.com', url: 'https://test.com/'},
+      {name: 'Test App', url: 'https://m.test.com/'},
+    ];
+
+    const card = document.createElement('password-details-card');
+    card.password = password;
+    document.body.appendChild(card);
+    await flushTasks();
+
+    const listItemElements =
+        card.shadowRoot!.querySelectorAll<HTMLAnchorElement>('a.site-link');
+    assertEquals(listItemElements.length, password.affiliatedDomains.length);
+
+    password.affiliatedDomains.forEach((expectedDomain, i) => {
+      const listItemElement = listItemElements[i];
+
+      assertTrue(!!listItemElement);
+      assertEquals(expectedDomain.name, listItemElement.textContent!.trim());
+      assertEquals(expectedDomain.url, listItemElement.href);
+    });
+  });
 });
diff --git a/chrome/test/data/webui/settings/about_page_tests.ts b/chrome/test/data/webui/settings/about_page_tests.ts
index 65de0c7..26e55617 100644
--- a/chrome/test/data/webui/settings/about_page_tests.ts
+++ b/chrome/test/data/webui/settings/about_page_tests.ts
@@ -5,7 +5,7 @@
 // clang-format off
 import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
 
-import {AboutPageBrowserProxyImpl, LifetimeBrowserProxyImpl, MinimumRoutes, Route, Router, SettingsAboutPageElement} from 'chrome://settings/settings.js';
+import {AboutPageBrowserProxyImpl, LifetimeBrowserProxyImpl, Route, Router, SettingsAboutPageElement, SettingsRoutes} from 'chrome://settings/settings.js';
 import {assertTrue} from 'chrome://webui-test/chai_assert.js';
 import {TestAboutPageBrowserProxy} from './test_about_page_browser_proxy.js';
 import {TestLifetimeBrowserProxy} from './test_lifetime_browser_proxy.js';
@@ -23,12 +23,12 @@
 
 // clang-format on
 
-function setupRouter(): MinimumRoutes {
+function setupRouter(): SettingsRoutes {
   const routes = {
     ABOUT: new Route('/help'),
     ADVANCED: new Route('/advanced'),
     BASIC: new Route('/'),
-  };
+  } as unknown as SettingsRoutes;
   Router.resetInstanceForTesting(new Router(routes));
   return routes;
 }
@@ -49,7 +49,7 @@
   let aboutBrowserProxy: TestAboutPageBrowserProxy;
   let lifetimeBrowserProxy: TestLifetimeBrowserProxy;
 
-  let testRoutes: MinimumRoutes;
+  let testRoutes: SettingsRoutes;
 
   setup(function() {
     loadTimeData.overrideValues({
diff --git a/chrome/test/data/webui/settings/autofill_page_test.ts b/chrome/test/data/webui/settings/autofill_page_test.ts
index 7704de7..ccecb18 100644
--- a/chrome/test/data/webui/settings/autofill_page_test.ts
+++ b/chrome/test/data/webui/settings/autofill_page_test.ts
@@ -7,8 +7,7 @@
 import {DomIf, flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 import {AutofillManagerImpl, PasswordsSectionElement, PasswordListItemElement, PaymentsManagerImpl, SettingsAutofillSectionElement, SettingsPaymentsSectionElement} from 'chrome://settings/lazy_load.js';
 import {buildRouter, Router, routes} from 'chrome://settings/settings.js';
-import {CrSettingsPrefs, OpenWindowProxyImpl, PasswordManagerImpl, SettingsAutofillPageElement, SettingsPluralStringProxyImpl, SettingsPrefsElement} from 'chrome://settings/settings.js';
-import {SettingsRoutes} from 'chrome://settings/settings_routes.js';
+import {CrSettingsPrefs, OpenWindowProxyImpl, PasswordManagerImpl, SettingsAutofillPageElement, SettingsPluralStringProxyImpl, SettingsPrefsElement, SettingsRoutes} from 'chrome://settings/settings.js';
 import {assertDeepEquals, assertEquals, assertFalse, assertNotEquals, assertTrue} from 'chrome://webui-test/chai_assert.js';
 import {TestPluralStringProxy} from 'chrome://webui-test/test_plural_string_proxy.js';
 import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
diff --git a/chrome/test/data/webui/settings/chromeos/BUILD.gn b/chrome/test/data/webui/settings/chromeos/BUILD.gn
index 0a142c72..3e64500 100644
--- a/chrome/test/data/webui/settings/chromeos/BUILD.gn
+++ b/chrome/test/data/webui/settings/chromeos/BUILD.gn
@@ -180,6 +180,7 @@
   "test_personalization_hub_browser_proxy.js",
   "test_privacy_hub_browser_proxy.js",
   "test_profile_info_browser_proxy.js",
+  "test_select_to_speak_subpage_browser_proxy.js",
   "tether_connection_dialog_test.js",
   "text_to_speech_page_tests.js",
   "text_to_speech_subpage_tests.js",
diff --git a/chrome/test/data/webui/settings/chromeos/apn_detail_dialog_tests.js b/chrome/test/data/webui/settings/chromeos/apn_detail_dialog_tests.js
index 03675790..1f1b106 100644
--- a/chrome/test/data/webui/settings/chromeos/apn_detail_dialog_tests.js
+++ b/chrome/test/data/webui/settings/chromeos/apn_detail_dialog_tests.js
@@ -9,7 +9,7 @@
 import {ApnDetailDialogMode} from 'chrome://resources/ash/common/network/cellular_utils.js';
 import {MojoInterfaceProviderImpl} from 'chrome://resources/ash/common/network/mojo_interface_provider.js';
 import {OncMojo} from 'chrome://resources/ash/common/network/onc_mojo.js';
-import {ApnAuthenticationType, ApnIpType, ApnProperties, ApnType} from 'chrome://resources/mojo/chromeos/services/network_config/public/mojom/cros_network_config.mojom-webui.js';
+import {ApnAuthenticationType, ApnIpType, ApnProperties, ApnType, ManagedProperties} from 'chrome://resources/mojo/chromeos/services/network_config/public/mojom/cros_network_config.mojom-webui.js';
 import {NetworkType} from 'chrome://resources/mojo/chromeos/services/network_config/public/mojom/network_types.mojom-webui.js';
 import {FakeNetworkConfig} from 'chrome://test/chromeos/fake_network_config_mojom.js';
 import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
@@ -39,6 +39,25 @@
     advancedSettingsBtn.click();
   }
 
+  /**
+   * @param {string} selector
+   */
+  function assertElementEnabled(selector) {
+    const element = apnDetailDialog.shadowRoot.querySelector(selector);
+    assertTrue(!!element);
+    assertFalse(element.disabled);
+  }
+
+  function assertAllInputsEnabled() {
+    assertElementEnabled('#apnInput');
+    assertElementEnabled('#usernameInput');
+    assertElementEnabled('#passwordInput');
+    assertElementEnabled('#authTypeDropDown');
+    assertElementEnabled('#apnDefaultTypeCheckbox');
+    assertElementEnabled('#apnAttachTypeCheckbox');
+    assertElementEnabled('#ipTypeDropDown');
+  }
+
   setup(async function() {
     testing.Test.disableAnimationsAndTransitions();
     PolymerTest.clearBody();
@@ -79,7 +98,8 @@
     assertTrue(!!apnDetailDialog.shadowRoot.querySelector('#ipTypeDropDown'));
     assertTrue(
         !!apnDetailDialog.shadowRoot.querySelector('#apnDetailCancelBtn'));
-    assertTrue(!!apnDetailDialog.shadowRoot.querySelector('#apnDetailAddBtn'));
+    assertTrue(
+        !!apnDetailDialog.shadowRoot.querySelector('#apnDetailActionBtn'));
     assertFalse(!!apnDetailDialog.shadowRoot.querySelector('#apnDoneBtn'));
   });
 
@@ -143,24 +163,17 @@
       });
 
   test('Clicking on the add button calls createCustomApn', async () => {
-    const assertFieldEnabled = (selector) => {
-      const element = apnDetailDialog.shadowRoot.querySelector(selector);
-      assertTrue(!!element);
-      assertFalse(element.disabled);
-    };
     apnDetailDialog.$.apnInput.value = TEST_APN.accessPointName;
     apnDetailDialog.$.usernameInput.value = TEST_APN.username;
     apnDetailDialog.$.passwordInput.value = TEST_APN.password;
 
-    assertFieldEnabled('#apnInput');
-    assertFieldEnabled('#usernameInput');
-    assertFieldEnabled('#passwordInput');
-    assertFieldEnabled('#authTypeDropDown');
-    assertFieldEnabled('#apnDefaultTypeCheckbox');
-    assertFieldEnabled('#apnAttachTypeCheckbox');
-    assertFieldEnabled('#ipTypeDropDown');
-    assertFieldEnabled('#apnDetailCancelBtn');
-    assertFieldEnabled('#apnDetailAddBtn');
+    assertAllInputsEnabled();
+    assertElementEnabled('#apnDetailCancelBtn');
+    assertElementEnabled('#apnDetailActionBtn');
+    assertEquals(
+        apnDetailDialog.i18n('add'),
+        apnDetailDialog.shadowRoot.querySelector('#apnDetailActionBtn')
+            .innerText);
 
     // Add a network.
     const network = OncMojo.getDefaultManagedProperties(
@@ -169,16 +182,17 @@
     await flushTasks();
 
     /**
-     * @type {!ApnProperties}
+     * @type {!ManagedProperties}
      */
     const managedProp =
         await mojoApi_.getManagedProperties(apnDetailDialog.guid);
     assertTrue(!!managedProp);
     assertFalse(!!managedProp.result.typeProperties.cellular.customApnList);
 
-    const addBtn = apnDetailDialog.shadowRoot.querySelector('#apnDetailAddBtn');
-    assertTrue(!!addBtn);
-    addBtn.click();
+    const actionBtn =
+        apnDetailDialog.shadowRoot.querySelector('#apnDetailActionBtn');
+    assertTrue(!!actionBtn);
+    actionBtn.click();
     await flushTasks();
     await mojoApi_.whenCalled('createCustomApn');
 
@@ -210,7 +224,8 @@
             .innerText);
     assertFalse(
         !!apnDetailDialog.shadowRoot.querySelector('#apnDetailCancelBtn'));
-    assertFalse(!!apnDetailDialog.shadowRoot.querySelector('#apnDetailAddBtn'));
+    assertFalse(
+        !!apnDetailDialog.shadowRoot.querySelector('#apnDetailActionBtn'));
     const doneBtn = apnDetailDialog.shadowRoot.querySelector('#apnDoneBtn');
     assertTrue(!!doneBtn);
     assertFalse(doneBtn.disabled);
@@ -225,40 +240,62 @@
 
   test('Dialog input fields are validated', async () => {
     const apnInputField = apnDetailDialog.$.apnInput;
-    const addBtn = apnDetailDialog.shadowRoot.querySelector('#apnDetailAddBtn');
-    assertTrue(!!addBtn);
+    const actionButton =
+        apnDetailDialog.shadowRoot.querySelector('#apnDetailActionBtn');
+    assertTrue(!!actionButton);
     // Case: After opening dialog before user input
     assertFalse(!!apnInputField.invalid);
-    assertTrue(!!addBtn.disabled);
+    assertTrue(!!actionButton.disabled);
 
     // Case : After valid user input
     apnInputField.value = 'test';
     assertFalse(!!apnInputField.invalid);
-    assertFalse(!!addBtn.disabled);
+    assertFalse(!!actionButton.disabled);
 
     // Case : After Removing all user input no error state but button disabled
     apnInputField.value = '';
     assertFalse(!!apnInputField.invalid);
-    assertTrue(!!addBtn.disabled);
+    assertTrue(!!actionButton.disabled);
 
     // Case : Non ascii user input
     apnInputField.value = 'testμ';
     assertTrue(!!apnInputField.invalid);
-    assertTrue(!!addBtn.disabled);
+    assertTrue(!!actionButton.disabled);
     assertTrue(apnInputField.value.includes('μ'));
 
     // Case : longer than 63 characters then removing one character
     apnInputField.value = 'a'.repeat(64);
     assertTrue(!!apnInputField.invalid);
-    assertTrue(!!addBtn.disabled);
+    assertTrue(!!actionButton.disabled);
     assertFalse(apnInputField.value.length > 63);
     apnInputField.value = apnInputField.value.slice(0, -1);
     assertFalse(!!apnInputField.invalid);
-    assertFalse(!!addBtn.disabled);
+    assertFalse(!!actionButton.disabled);
 
     // Case : longer than 63 non-ASCII characters
     apnInputField.value = 'μ'.repeat(64);
     assertTrue(!!apnInputField.invalid);
-    assertTrue(!!addBtn.disabled);
+    assertTrue(!!actionButton.disabled);
   });
+
+  test('Setting mode to edit changes buttons and fields', async () => {
+    const apnWithId = TEST_APN;
+    apnWithId.id = '1';
+    apnDetailDialog.mode = ApnDetailDialogMode.EDIT;
+    apnDetailDialog.apnProperties = apnWithId;
+    await flushTasks();
+    assertEquals(
+        apnDetailDialog.i18n('apnDetailEditApnDialogTitle'),
+        apnDetailDialog.shadowRoot.querySelector('#apnDetailDialogTitle')
+            .innerText);
+    assertElementEnabled('#apnDetailCancelBtn');
+    assertElementEnabled('#apnDetailActionBtn');
+    assertEquals(
+        apnDetailDialog.i18n('save'),
+        apnDetailDialog.shadowRoot.querySelector('#apnDetailActionBtn')
+            .innerText);
+    assertFalse(!!apnDetailDialog.shadowRoot.querySelector('#apnDoneBtn'));
+    assertAllInputsEnabled();
+  });
+
 });
diff --git a/chrome/test/data/webui/settings/chromeos/cursor_and_touchpad_page_tests.js b/chrome/test/data/webui/settings/chromeos/cursor_and_touchpad_page_tests.js
index d8ed0684..5284d53 100644
--- a/chrome/test/data/webui/settings/chromeos/cursor_and_touchpad_page_tests.js
+++ b/chrome/test/data/webui/settings/chromeos/cursor_and_touchpad_page_tests.js
@@ -15,6 +15,15 @@
 const DEFAULT_BLACK_CURSOR_COLOR = 0;
 const RED_CURSOR_COLOR = 0xd93025;
 
+/**
+ * Possible control types for settings.
+ * @enum {string}
+ */
+export const ControlType = {
+  DROPDOWN: 'dropdown',
+  TOGGLE: 'toggle',
+};
+
 suite('CursorAndTouchpadPageTests', function() {
   let page = null;
   let deviceBrowserProxy = null;
@@ -262,4 +271,74 @@
     assertTrue(isVisible(largeCursorSizeSlider));
   });
 
+  const settingsControls = [
+    {
+      id: 'autoClickToggle',
+      prefKey: 'settings.a11y.autoclick',
+      defaultValue: false,
+      alternateValue: true,
+      type: ControlType.TOGGLE,
+    },
+    {
+      id: 'delayBeforeClickDropdown',
+      prefKey: 'settings.a11y.autoclick_delay_ms',
+      defaultValue: 1000,
+      alternateValue: 2000,
+      type: ControlType.DROPDOWN,
+    },
+    {
+      id: 'autoClickStabilizePositionToggle',
+      prefKey: 'settings.a11y.autoclick_stabilize_position',
+      defaultValue: false,
+      alternateValue: true,
+      type: ControlType.TOGGLE,
+    },
+    {
+      id: 'autoclickMovementThresholdDropdown',
+      prefKey: 'settings.a11y.autoclick_movement_threshold',
+      defaultValue: 20,
+      alternateValue: 5,
+      type: ControlType.DROPDOWN,
+    },
+    {
+      id: 'autoClickRevertToLeftClickToggle',
+      prefKey: 'settings.a11y.autoclick_revert_to_left_click',
+      defaultValue: true,
+      alternateValue: false,
+      type: ControlType.TOGGLE,
+    },
+  ];
+
+  settingsControls.forEach(control => {
+    const {id, prefKey, defaultValue, alternateValue, type} = control;
+
+    test(`Autoclick ${type} ${id} syncs to Pref: ${prefKey}`, async () => {
+      await initPage();
+
+      // Ensure control exists.
+      const control = page.shadowRoot.querySelector(`#${id}`);
+      assertTrue(!!control);
+
+      // Ensure pref is set to the default value.
+      let pref = page.getPref(prefKey);
+      assertEquals(defaultValue, pref.value);
+
+      // Update control to alternate value.
+      switch (type) {
+        case ControlType.TOGGLE:
+          control.click();
+          break;
+        case ControlType.DROPDOWN:
+          await waitAfterNextRender(control);
+          const controlElement = control.shadowRoot.querySelector('select');
+          controlElement.value = alternateValue;
+          controlElement.dispatchEvent(new CustomEvent('change'));
+          break;
+      }
+
+      // Ensure pref is set to the alternate value.
+      pref = page.getPref(prefKey);
+      assertEquals(alternateValue, pref.value);
+    });
+  });
 });
diff --git a/chrome/test/data/webui/settings/chromeos/device_page_tests.js b/chrome/test/data/webui/settings/chromeos/device_page_tests.js
index 38e9bad7..13b3aa5 100644
--- a/chrome/test/data/webui/settings/chromeos/device_page_tests.js
+++ b/chrome/test/data/webui/settings/chromeos/device_page_tests.js
@@ -553,6 +553,16 @@
       const deviceSubsectionDropdown =
           audioPage.shadowRoot.querySelector('#audioInputDeviceDropdown');
       assertTrue(isVisible(deviceSubsectionDropdown));
+      const inputGainSubsectionHeader =
+          audioPage.shadowRoot.querySelector('#audioInputGainLabel');
+      assertTrue(isVisible(inputGainSubsectionHeader), 'audioInputGainLabel');
+      assertEquals('Volume', inputGainSubsectionHeader.textContent);
+      const inputVolumeButton =
+          audioPage.shadowRoot.querySelector('#audioInputGainMuteButton');
+      assertTrue(isVisible(inputVolumeButton), 'audioInputGainMuteButton');
+      const inputVolumeSlider =
+          audioPage.shadowRoot.querySelector('#audioInputGainVolumeSlider');
+      assertTrue(isVisible(inputVolumeSlider), 'audioInputGainVolumeSlider');
     });
   });
 
diff --git a/chrome/test/data/webui/settings/chromeos/fake_cros_audio_config_test.js b/chrome/test/data/webui/settings/chromeos/fake_cros_audio_config_test.js
index f69d7b30..7177b4d4 100644
--- a/chrome/test/data/webui/settings/chromeos/fake_cros_audio_config_test.js
+++ b/chrome/test/data/webui/settings/chromeos/fake_cros_audio_config_test.js
@@ -142,4 +142,28 @@
     crosAudioConfig.setOutputMuted(/*muted=*/ false);
     assertDeepEquals(propertiesOutputUnmute, onPropertiesUpdated.calls_[2][0]);
   });
+
+  test('VerifySetInputMutedTriggersMatchingPropertyUpdate', () => {
+    assertEquals(
+        crosAudioConfigMojomWebui.MuteState.kNotMuted,
+        onPropertiesUpdated.calls_[0][0].inputMuteState);
+
+    /** @type {AudioSystemProperties} */
+    const updateInputGainMuted = {
+      ...defaultProperties,
+      inputMuteState: crosAudioConfigMojomWebui.MuteState.kMutedByUser,
+    };
+    onPropertiesUpdated.addExpectation(updateInputGainMuted);
+    crosAudioConfig.setInputMuted(/*muted=*/ true);
+
+    assertEquals(
+        crosAudioConfigMojomWebui.MuteState.kMutedByUser,
+        onPropertiesUpdated.calls_[1][0].inputMuteState);
+    onPropertiesUpdated.addExpectation(defaultProperties);
+    crosAudioConfig.setInputMuted(/*muted=*/ false);
+
+    assertEquals(
+        crosAudioConfigMojomWebui.MuteState.kNotMuted,
+        onPropertiesUpdated.calls_[2][0].inputMuteState);
+  });
 });
diff --git a/chrome/test/data/webui/settings/chromeos/os_people_page_test.js b/chrome/test/data/webui/settings/chromeos/os_people_page_test.js
index 10060a8..504b0cb 100644
--- a/chrome/test/data/webui/settings/chromeos/os_people_page_test.js
+++ b/chrome/test/data/webui/settings/chromeos/os_people_page_test.js
@@ -207,7 +207,8 @@
     await waitAfterNextRender(peoplePage);
 
     // Make the sync page configurable.
-    const syncPage = peoplePage.shadowRoot.querySelector('settings-sync-page');
+    const syncPage =
+        peoplePage.shadowRoot.querySelector('os-settings-sync-page');
     assert(syncPage);
     syncPage.syncPrefs = {
       customPassphraseAllowed: true,
@@ -227,7 +228,7 @@
     // Flush to make sure the dropdown expands.
     flush();
     const deepLinkElement =
-        syncPage.shadowRoot.querySelector('settings-sync-encryption-options')
+        syncPage.shadowRoot.querySelector('os-settings-sync-encryption-options')
             .shadowRoot.querySelector('#encryptionRadioGroup')
             .buttons_[0]
             .shadowRoot.querySelector('#button');
diff --git a/chrome/test/data/webui/settings/chromeos/privacy_hub_subpage_tests.js b/chrome/test/data/webui/settings/chromeos/privacy_hub_subpage_tests.js
index bfc0fac..0e64f08 100644
--- a/chrome/test/data/webui/settings/chromeos/privacy_hub_subpage_tests.js
+++ b/chrome/test/data/webui/settings/chromeos/privacy_hub_subpage_tests.js
@@ -125,6 +125,9 @@
     const getMicrophoneCrToggle = () =>
         privacyHubSubpage.shadowRoot.querySelector('#microphoneToggle')
             .shadowRoot.querySelector('cr-toggle');
+    const getMicrophoneTooltip = () =>
+        privacyHubSubpage.shadowRoot.querySelector('#microphoneToggle')
+            .querySelector('cr-tooltip-icon');
 
     privacyHubBrowserProxy.microphoneToggleIsEnabled = false;
     await privacyHubBrowserProxy.whenCalled(
@@ -135,6 +138,9 @@
     // should be disabled as no microphone is connected.
     assertFalse(!!getMicrophoneList());
     assertTrue(getMicrophoneCrToggle().disabled);
+    // TODO(b/259553116) Check how banshee handles the microphone hardware
+    // switch.
+    assertTrue(getMicrophoneTooltip().hidden);
 
     // Add a microphone.
     mediaDevices.addDevice('audioinput', 'Fake Microphone');
@@ -143,6 +149,8 @@
     // Microphone toggle should be enabled to click now as there is a microphone
     // connected and the hw toggle is inactive.
     assertFalse(getMicrophoneCrToggle().disabled);
+    // The tooltip should only show when the HW switch is engaged.
+    assertTrue(getMicrophoneTooltip().hidden);
 
     // Activate the hw toggle.
     webUIListenerCallback('microphone-hardware-toggle-changed', true);
@@ -150,6 +158,8 @@
     // Microphone toggle should be disabled again due to the hw switch being
     // active.
     assertTrue(getMicrophoneCrToggle().disabled);
+    // With the HW switch being active the tooltip should be visible.
+    assertFalse(getMicrophoneTooltip().hidden);
 
     mediaDevices.popDevice();
   });
diff --git a/chrome/test/data/webui/settings/chromeos/select_to_speak_subpage_tests.js b/chrome/test/data/webui/settings/chromeos/select_to_speak_subpage_tests.js
index 8869ea7..e093720 100644
--- a/chrome/test/data/webui/settings/chromeos/select_to_speak_subpage_tests.js
+++ b/chrome/test/data/webui/settings/chromeos/select_to_speak_subpage_tests.js
@@ -4,15 +4,22 @@
 
 import 'chrome://os-settings/chromeos/lazy_load.js';
 
-import {CrSettingsPrefs, Router, routes} from 'chrome://os-settings/chromeos/os_settings.js';
+import {CrSettingsPrefs, Router, routes, SelectToSpeakSubpageBrowserProxyImpl} from 'chrome://os-settings/chromeos/os_settings.js';
+import {addWebUiListener, webUIListenerCallback} from 'chrome://resources/js/cr.js';
+import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 import {assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
 import {waitAfterNextRender} from 'chrome://webui-test/polymer_test_util.js';
 
+import {TestSelectToSpeakSubpageBrowserProxy} from './test_select_to_speak_subpage_browser_proxy.js';
+
 suite('SelectToSpeakSubpageTests', function() {
   /** @type {SettingsSelectToSpeakSubpageElement} */
   let page = null;
 
   setup(async function() {
+    SelectToSpeakSubpageBrowserProxyImpl.setInstanceForTesting(
+        new TestSelectToSpeakSubpageBrowserProxy());
+
     loadTimeData.overrideValues(
         {isExperimentalAccessibilitySelectToSpeakVoiceSwitchingEnabled: true});
 
@@ -25,12 +32,31 @@
     page = document.createElement('settings-select-to-speak-subpage');
     page.prefs = prefElement.prefs;
     document.body.appendChild(page);
+    flush();
   });
 
   teardown(function() {
     page.remove();
   });
 
+  // TODO(crbug.com/1354821): Add tests that the language filter works for
+  // enhanced and device voices.
+
+  test('voice pref and dropdown synced', async function() {
+    // Make sure voice dropdown is system voice, matching default pref state.
+    const voiceDropdown = page.shadowRoot.querySelector('#voiceDropdown');
+    await waitAfterNextRender(voiceDropdown);
+    const voiceSelectElement = voiceDropdown.shadowRoot.querySelector('select');
+    assertEquals('select_to_speak_system_voice', voiceSelectElement.value);
+
+    // Change voice to Chrome OS US English, and verify pref is also changed.
+    voiceSelectElement.value = 'Chrome OS US English';
+    voiceSelectElement.dispatchEvent(new CustomEvent('change'));
+    flush();
+    const voicePref = page.getPref('settings.a11y.select_to_speak_voice_name');
+    assertEquals('Chrome OS US English', voicePref.value);
+  });
+
   test('voice switching pref and toggle synced', function() {
     // Make sure voice switching toggle is off, matching default pref state.
     const voiceSwitchingToggle =
@@ -51,7 +77,7 @@
         page.shadowRoot.querySelector('#enhancedNetworkVoicesToggle');
     assertFalse(enhancedNetworkVoicesToggle.checked);
 
-    // Toggle enhanced network voices off, and verify voice_switching pref is
+    // Toggle enhanced network voices on, and verify voice_switching pref is
     // enabled.
     enhancedNetworkVoicesToggle.click();
     const enhancedNetworkVoicesPref =
@@ -59,6 +85,30 @@
     assertTrue(enhancedNetworkVoicesPref.value);
   });
 
+  test('enhanced network voice pref and dropdown synced', async function() {
+    // Turn on enhanced network voices.
+    const enhancedNetworkVoicesToggle =
+        page.shadowRoot.querySelector('#enhancedNetworkVoicesToggle');
+    enhancedNetworkVoicesToggle.click();
+    flush();
+
+    // Make sure enhanced network voice dropdown is default voice, matching
+    // default pref state.
+    const enhancedNetworkVoiceDropdown =
+        page.shadowRoot.querySelector('#enhancedNetworkVoiceDropdown');
+    await waitAfterNextRender(enhancedNetworkVoiceDropdown);
+    const enhancedNetworkVoiceSelectElement =
+        enhancedNetworkVoiceDropdown.shadowRoot.querySelector('select');
+    assertEquals('default-wavenet', enhancedNetworkVoiceSelectElement.value);
+
+    // Change voice to Bangla (India) 1, and verify pref is also changed.
+    enhancedNetworkVoiceSelectElement.value = 'bnm';
+    enhancedNetworkVoiceSelectElement.dispatchEvent(new CustomEvent('change'));
+    const enhancedNetworkVoicePref =
+        page.getPref('settings.a11y.select_to_speak_enhanced_voice_name');
+    assertEquals('bnm', enhancedNetworkVoicePref.value);
+  });
+
   test('word highlight pref and toggle synced', function() {
     // Make sure word highlight toggle is on, matching default pref state.
     const wordHighlightToggle =
diff --git a/chrome/test/data/webui/settings/chromeos/test_select_to_speak_subpage_browser_proxy.js b/chrome/test/data/webui/settings/chromeos/test_select_to_speak_subpage_browser_proxy.js
new file mode 100644
index 0000000..771aa7c
--- /dev/null
+++ b/chrome/test/data/webui/settings/chromeos/test_select_to_speak_subpage_browser_proxy.js
@@ -0,0 +1,95 @@
+// 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.
+
+import {webUIListenerCallback} from 'chrome://resources/js/cr.m.js';
+import {TestBrowserProxy} from 'chrome://webui-test/test_browser_proxy.js';
+
+/** @implements {SelectToSpeakSubpageBrowserProxy} */
+export class TestSelectToSpeakSubpageBrowserProxy extends TestBrowserProxy {
+  constructor() {
+    super([
+      'getAllTtsVoiceData',
+      'getAppLocale',
+      'previewTtsVoice',
+      'refreshTtsVoices',
+    ]);
+  }
+
+  getAllTtsVoiceData() {
+    const voices = [
+      {
+        displayLanguage: 'English',
+        displayLanguageAndCountry: 'English (United States)',
+        eventTypes:
+            ['start', 'end', 'word', 'interrupted', 'cancelled', 'error'],
+        extensionId: 'gjjabgpgjpampikjhjpfhneeoapjbjaf',
+        lang: 'en-US',
+        voiceName: 'Chrome OS US English',
+      },
+      {
+        displayLanguage: 'Indonesian',
+        displayLanguageAndCountry: 'Indonesian (Indonesia)',
+        eventTypes:
+            ['start', 'end', 'word', 'interrupted', 'cancelled', 'error'],
+        extensionId: 'gjjabgpgjpampikjhjpfhneeoapjbjaf',
+        lang: 'id-ID',
+        voiceName: 'Chrome OS हिन्दी',
+      },
+      {
+        eventTypes:
+            ['start', 'end', 'word', 'interrupted', 'cancelled', 'error'],
+        extensionId: 'jacnkoglebceckolkoapelihnglgaicd',
+        lang: '',
+        voiceName: 'default-wavenet',
+      },
+      {
+        displayLanguage: 'Bangla',
+        displayLanguageAndCountry: 'Bangla (India)',
+        eventTypes:
+            ['start', 'end', 'word', 'interrupted', 'cancelled', 'error'],
+        extensionId: 'jacnkoglebceckolkoapelihnglgaicd',
+        lang: 'bn_in',
+        voiceName: 'bnm',
+      },
+      {
+        displayLanguage: 'Bangla',
+        displayLanguageAndCountry: 'Bangla (India)',
+        eventTypes:
+            ['start', 'end', 'word', 'interrupted', 'cancelled', 'error'],
+        extensionId: 'jacnkoglebceckolkoapelihnglgaicd',
+        lang: 'bn_in',
+        voiceName: 'bnx',
+      },
+      {
+        displayLanguage: 'Turkish',
+        displayLanguageAndCountry: 'Turkish',
+        eventTypes: [
+          'start',
+          'end',
+          'word',
+          'sentence',
+          'interrupted',
+          'cancelled',
+          'error',
+        ],
+        extensionId: 'dakbfdmgjiabojdgbiljlhgjbokobjpg',
+        lang: 'tr',
+        voiceName: 'eSpeak Turkish',
+      },
+    ];
+    webUIListenerCallback('all-sts-voice-data-updated', voices);
+  }
+
+  getAppLocale() {
+    this.methodCalled('getAppLocale');
+  }
+
+  previewTtsVoice() {
+    this.methodCalled('previewTtsVoice');
+  }
+
+  refreshTtsVoices() {
+    this.methodCalled('refreshTtsVoices');
+  }
+}
diff --git a/chrome/test/data/webui/settings/cr_settings_browsertest.js b/chrome/test/data/webui/settings/cr_settings_browsertest.js
index ca8729b..ed75304 100644
--- a/chrome/test/data/webui/settings/cr_settings_browsertest.js
+++ b/chrome/test/data/webui/settings/cr_settings_browsertest.js
@@ -498,9 +498,12 @@
 });
 
 GEN('#if BUILDFLAG(GOOGLE_CHROME_BRANDING)');
-TEST_F('CrSettingsPersonalizationOptionsTest', 'OfficialBuild', function() {
-  runMochaSuite('PersonalizationOptionsTests_OfficialBuild');
-});
+// TODO(crbug.com/1399684): failing on linux-chromeos-chrome
+TEST_F(
+    'CrSettingsPersonalizationOptionsTest', 'DISABLED_OfficialBuild',
+    function() {
+      runMochaSuite('PersonalizationOptionsTests_OfficialBuild');
+    });
 GEN('#endif');
 
 var CrSettingsPrivacyPageTest = class extends CrSettingsBrowserTest {
diff --git a/chrome/test/data/webui/settings/password_view_test.ts b/chrome/test/data/webui/settings/password_view_test.ts
index dd65996..7bc824b 100644
--- a/chrome/test/data/webui/settings/password_view_test.ts
+++ b/chrome/test/data/webui/settings/password_view_test.ts
@@ -10,8 +10,7 @@
 import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
 import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 import {PasswordViewElement} from 'chrome://settings/lazy_load.js';
-import {buildRouter, PasswordManagerImpl, Router, routes} from 'chrome://settings/settings.js';
-import {SettingsRoutes} from 'chrome://settings/settings_routes.js';
+import {buildRouter, PasswordManagerImpl, Router, routes, SettingsRoutes} from 'chrome://settings/settings.js';
 import {assertDeepEquals, assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
 import {isVisible} from 'chrome://webui-test/test_util.js';
 import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
diff --git a/chrome/test/data/webui/settings/passwords_section_test.ts b/chrome/test/data/webui/settings/passwords_section_test.ts
index 0a225fec1..5ae786c5 100644
--- a/chrome/test/data/webui/settings/passwords_section_test.ts
+++ b/chrome/test/data/webui/settings/passwords_section_test.ts
@@ -12,8 +12,7 @@
 import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
 import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 import {PasswordsSectionElement} from 'chrome://settings/lazy_load.js';
-import {buildRouter, HatsBrowserProxyImpl, PasswordCheckReferrer, PasswordManagerImpl, Router, routes, SettingsPluralStringProxyImpl,StatusAction, TrustedVaultBannerState, TrustSafetyInteraction} from 'chrome://settings/settings.js';
-import {SettingsRoutes} from 'chrome://settings/settings_routes.js';
+import {buildRouter, HatsBrowserProxyImpl, PasswordCheckReferrer, PasswordManagerImpl, Router, routes, SettingsPluralStringProxyImpl, SettingsRoutes, StatusAction, TrustedVaultBannerState, TrustSafetyInteraction} from 'chrome://settings/settings.js';
 import {SettingsToggleButtonElement} from 'chrome://settings/settings.js';
 import {assertDeepEquals, assertEquals, assertFalse, assertTrue, assertNotEquals} from 'chrome://webui-test/chai_assert.js';
 import {TestPluralStringProxy} from 'chrome://webui-test/test_plural_string_proxy.js';
diff --git a/chrome/test/data/webui/settings/settings_animated_pages_test.ts b/chrome/test/data/webui/settings/settings_animated_pages_test.ts
index ded187d..11545c5 100644
--- a/chrome/test/data/webui/settings/settings_animated_pages_test.ts
+++ b/chrome/test/data/webui/settings/settings_animated_pages_test.ts
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 // clang-format off
-import {Route, Router} from 'chrome://settings/settings.js';
+import {Route, Router, SettingsRoutes} from 'chrome://settings/settings.js';
 import {assertEquals, assertTrue} from 'chrome://webui-test/chai_assert.js';
 import {eventToPromise} from 'chrome://webui-test/test_util.js';
 import {getTrustedHtml} from 'chrome://webui-test/trusted_html.js';
@@ -40,7 +40,7 @@
       SITE_SETTINGS: privacyRoute.createChild('/content'),
     };
 
-    Router.resetInstanceForTesting(new Router(testRoutes));
+    Router.resetInstanceForTesting(new Router(testRoutes as SettingsRoutes));
     setupPopstateListener();
   });
 
diff --git a/chrome/test/data/webui/settings/settings_subpage_test.ts b/chrome/test/data/webui/settings/settings_subpage_test.ts
index 68d197c88..f6905446 100644
--- a/chrome/test/data/webui/settings/settings_subpage_test.ts
+++ b/chrome/test/data/webui/settings/settings_subpage_test.ts
@@ -4,7 +4,7 @@
 
 // clang-format off
 import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-import {loadTimeData, Route, Router} from 'chrome://settings/settings.js';
+import {loadTimeData, Route, Router, SettingsRoutes} from 'chrome://settings/settings.js';
 import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
 import {eventToPromise} from 'chrome://webui-test/test_util.js';
 import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
@@ -49,7 +49,8 @@
       SYNC: peopleRoute.createChild('/syncSetup'),
     };
 
-    Router.resetInstanceForTesting(new Router(testRoutes));
+    Router.resetInstanceForTesting(
+        new Router(testRoutes as unknown as SettingsRoutes));
 
     setupPopstateListener();
 
diff --git a/chrome/test/data/webui/settings/sync_test_util.ts b/chrome/test/data/webui/settings/sync_test_util.ts
index 16b06711..2964f31 100644
--- a/chrome/test/data/webui/settings/sync_test_util.ts
+++ b/chrome/test/data/webui/settings/sync_test_util.ts
@@ -5,7 +5,7 @@
 // clang-format off
 import {webUIListenerCallback} from 'chrome://resources/js/cr.js';
 import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-import {Route, Router, StoredAccount, SyncStatus} from 'chrome://settings/settings.js';
+import {Route, Router, SettingsRoutes, StoredAccount, SyncStatus} from 'chrome://settings/settings.js';
 // clang-format on
 
 interface SyncAllPrefs {
@@ -103,7 +103,8 @@
     ABOUT: new Route('/help'),
   };
 
-  Router.resetInstanceForTesting(new Router(routes));
+  Router.resetInstanceForTesting(
+      new Router(routes as unknown as SettingsRoutes));
 }
 
 export function simulateSyncStatus(status: SyncStatus|undefined) {
diff --git a/chrome/test/data/webui/side_panel/bookmarks/test_bookmarks_api_proxy.ts b/chrome/test/data/webui/side_panel/bookmarks/test_bookmarks_api_proxy.ts
index 8e9fce9..2afed3076 100644
--- a/chrome/test/data/webui/side_panel/bookmarks/test_bookmarks_api_proxy.ts
+++ b/chrome/test/data/webui/side_panel/bookmarks/test_bookmarks_api_proxy.ts
@@ -25,7 +25,7 @@
     super([
       'getActiveUrl',
       'getFolders',
-      'bookmarkCurrentTab',
+      'bookmarkCurrentTabInFolder',
       'openBookmark',
       'cutBookmark',
       'copyBookmark',
@@ -55,8 +55,8 @@
     return Promise.resolve(this.folders_);
   }
 
-  bookmarkCurrentTab() {
-    this.methodCalled('bookmarkCurrentTab');
+  bookmarkCurrentTabInFolder() {
+    this.methodCalled('bookmarkCurrentTabInFolder');
   }
 
   openBookmark(
diff --git a/chrome/test/data/webui/side_panel/customize_chrome/BUILD.gn b/chrome/test/data/webui/side_panel/customize_chrome/BUILD.gn
index 9b00659c..262df87 100644
--- a/chrome/test/data/webui/side_panel/customize_chrome/BUILD.gn
+++ b/chrome/test/data/webui/side_panel/customize_chrome/BUILD.gn
@@ -27,6 +27,7 @@
     "color_test.ts",
     "colors_test.ts",
     "themes_test.ts",
+    "theme_snapshot_test.ts",
   ]
   definitions = [ "//tools/typescript/definitions/metrics_private.d.ts" ]
   deps = [
diff --git a/chrome/test/data/webui/side_panel/customize_chrome/colors_test.ts b/chrome/test/data/webui/side_panel/customize_chrome/colors_test.ts
index 4987c22..aacdf324 100644
--- a/chrome/test/data/webui/side_panel/customize_chrome/colors_test.ts
+++ b/chrome/test/data/webui/side_panel/customize_chrome/colors_test.ts
@@ -4,9 +4,11 @@
 
 import 'chrome://webui-test/mojo_webui_test_support.js';
 
-import {ColorsElement} from 'chrome://customize-chrome-side-panel.top-chrome/colors.js';
-import {CustomizeChromePageCallbackRouter, CustomizeChromePageHandlerRemote} from 'chrome://customize-chrome-side-panel.top-chrome/customize_chrome.mojom-webui.js';
+import {ColorElement} from 'chrome://customize-chrome-side-panel.top-chrome/color.js';
+import {Color, ColorsElement, DARK_DEFAULT_COLOR, LIGHT_DEFAULT_COLOR} from 'chrome://customize-chrome-side-panel.top-chrome/colors.js';
+import {ChromeColor, CustomizeChromePageCallbackRouter, CustomizeChromePageHandlerRemote, CustomizeChromePageRemote, Theme} from 'chrome://customize-chrome-side-panel.top-chrome/customize_chrome.mojom-webui.js';
 import {CustomizeChromeApiProxy} from 'chrome://customize-chrome-side-panel.top-chrome/customize_chrome_api_proxy.js';
+import {PromiseResolver} from 'chrome://resources/js/promise_resolver.js';
 import {assertDeepEquals, assertEquals} from 'chrome://webui-test/chai_assert.js';
 import {waitAfterNextRender} from 'chrome://webui-test/polymer_test_util.js';
 import {TestBrowserProxy} from 'chrome://webui-test/test_browser_proxy.js';
@@ -14,7 +16,10 @@
 import {installMock} from './test_support.js';
 
 suite('ColorsTest', () => {
+  let colorsElement: ColorsElement;
   let handler: TestBrowserProxy<CustomizeChromePageHandlerRemote>;
+  let callbackRouter: CustomizeChromePageRemote;
+  let chromeColorsResolver: PromiseResolver<{colors: ChromeColor[]}>;
 
   setup(() => {
     document.body.innerHTML = window.trustedTypes!.emptyHTML;
@@ -23,23 +28,58 @@
         (mock: CustomizeChromePageHandlerRemote) =>
             CustomizeChromeApiProxy.setInstance(
                 mock, new CustomizeChromePageCallbackRouter()));
+    callbackRouter = CustomizeChromeApiProxy.getInstance()
+                         .callbackRouter.$.bindNewPipeAndPassRemote();
+    chromeColorsResolver = new PromiseResolver();
+    handler.setResultFor('getChromeColors', chromeColorsResolver.promise);
+    colorsElement = new ColorsElement();
+    document.body.appendChild(colorsElement);
+  });
+
+  ([
+    [true, DARK_DEFAULT_COLOR],
+    [false, LIGHT_DEFAULT_COLOR],
+  ] as Array<[boolean, Color]>)
+      .forEach(([systemDarkMode, defaultColor]) => {
+        test('renders default color', async () => {
+          const theme: Theme = {
+            backgroundImage: undefined,
+            systemDarkMode,
+            foregroundColor: undefined,
+          };
+
+          callbackRouter.setTheme(theme);
+          await callbackRouter.$.flushForTesting();
+
+          assertDeepEquals(
+              defaultColor.foreground,
+              colorsElement.$.defaultColor.foregroundColor);
+          assertDeepEquals(
+              defaultColor.background,
+              colorsElement.$.defaultColor.backgroundColor);
+        });
+      });
+
+  test('sets default color', () => {
+    colorsElement.$.defaultColor.click();
+
+    assertEquals(1, handler.getCallCount('setDefaultColor'));
   });
 
   test('renders chrome colors', async () => {
-    const colors = Promise.resolve({
+    const colors = {
       colors: [
         {id: 1, name: 'foo', background: {value: 1}, foreground: {value: 2}},
         {id: 2, name: 'bar', background: {value: 3}, foreground: {value: 4}},
       ],
-    });
-    handler.setResultFor('getChromeColors', colors);
+    };
 
-    const colorsElement = new ColorsElement();
-    document.body.appendChild(colorsElement);
+    chromeColorsResolver.resolve(colors);
     await waitAfterNextRender(colorsElement);
 
     const colorElements =
-        colorsElement.shadowRoot!.querySelectorAll('customize-chrome-color');
+        colorsElement.shadowRoot!.querySelectorAll<ColorElement>(
+            '.chrome-color');
     assertEquals(2, colorElements.length);
     assertDeepEquals({value: 1}, colorElements[0]!.backgroundColor);
     assertDeepEquals({value: 2}, colorElements[0]!.foregroundColor);
@@ -48,4 +88,20 @@
     assertDeepEquals({value: 4}, colorElements[1]!.foregroundColor);
     assertEquals('bar', colorElements[1]!.title);
   });
+
+  test('sets chrome color', async () => {
+    const colors = {
+      colors: [
+        {id: 1, name: 'foo', background: {value: 1}, foreground: {value: 2}},
+      ],
+    };
+
+    chromeColorsResolver.resolve(colors);
+    await waitAfterNextRender(colorsElement);
+    colorsElement.shadowRoot!.querySelector<ColorElement>(
+                                 '.chrome-color')!.click();
+
+    assertEquals(1, handler.getCallCount('setForegroundColor'));
+    assertEquals(2, handler.getArgs('setForegroundColor')[0].value);
+  });
 });
diff --git a/chrome/test/data/webui/side_panel/customize_chrome/side_panel_customize_chrome_browsertest.js b/chrome/test/data/webui/side_panel/customize_chrome/side_panel_customize_chrome_browsertest.js
index 66ce642..ac51ee0 100644
--- a/chrome/test/data/webui/side_panel/customize_chrome/side_panel_customize_chrome_browsertest.js
+++ b/chrome/test/data/webui/side_panel/customize_chrome/side_panel_customize_chrome_browsertest.js
@@ -98,6 +98,15 @@
   }
 };
 
+var SidePanelCustomizeChromeThemeSnapshotTest =
+    class extends SidePanelCustomizeChromeBrowserTest {
+  /** @override */
+  get browsePreload() {
+    return 'chrome://customize-chrome-side-panel.top-chrome/test_loader.html' +
+        '?module=side_panel_customize_chrome/theme_snapshot_test.js';
+  }
+};
+
 TEST_F('SidePanelCustomizeChromeShortcutsTest', 'All', function() {
   mocha.run();
 });
@@ -128,4 +137,8 @@
 
 TEST_F('SidePanelCustomizeChromeThemesTest', 'All', function() {
   mocha.run();
-});
\ No newline at end of file
+});
+
+TEST_F('SidePanelCustomizeChromeThemeSnapshotTest', 'All', function() {
+  mocha.run();
+});
diff --git a/chrome/test/data/webui/side_panel/customize_chrome/test_support.ts b/chrome/test/data/webui/side_panel/customize_chrome/test_support.ts
index 7eafe06..84b458c 100644
--- a/chrome/test/data/webui/side_panel/customize_chrome/test_support.ts
+++ b/chrome/test/data/webui/side_panel/customize_chrome/test_support.ts
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import {BackgroundImage, Theme} from 'chrome://customize-chrome-side-panel.top-chrome/customize_chrome.mojom-webui.js';
 import {assertEquals, assertNotEquals} from 'chrome://webui-test/chai_assert.js';
 import {TestBrowserProxy} from 'chrome://webui-test/test_browser_proxy.js';
 
@@ -26,3 +27,28 @@
   const actual = window.getComputedStyle(element).getPropertyValue(name).trim();
   assertNotEquals(not, actual);
 }
+
+/**
+ * Queries |selector| on |element|'s shadow root and returns the resulting
+ * element if there is any.
+ */
+export function $$<E extends Element = Element>(
+    element: Element, selector: string): E|null;
+export function $$(element: Element, selector: string) {
+  return element.shadowRoot!.querySelector(selector);
+}
+
+export function createBackgroundImage(url: string): BackgroundImage {
+  return {
+    url: {url},
+    title: '',
+  };
+}
+
+export function createTheme(): Theme {
+  return {
+    backgroundImage: undefined,
+    systemDarkMode: false,
+    foregroundColor: undefined,
+  };
+}
diff --git a/chrome/test/data/webui/side_panel/customize_chrome/theme_snapshot_test.ts b/chrome/test/data/webui/side_panel/customize_chrome/theme_snapshot_test.ts
new file mode 100644
index 0000000..6da6b1e4
--- /dev/null
+++ b/chrome/test/data/webui/side_panel/customize_chrome/theme_snapshot_test.ts
@@ -0,0 +1,59 @@
+// 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.
+
+import 'chrome://webui-test/mojo_webui_test_support.js';
+import 'chrome://customize-chrome-side-panel.top-chrome/theme_snapshot.js';
+
+import {CustomizeChromePageCallbackRouter, CustomizeChromePageHandlerRemote, CustomizeChromePageRemote} from 'chrome://customize-chrome-side-panel.top-chrome/customize_chrome.mojom-webui.js';
+import {CustomizeChromeApiProxy} from 'chrome://customize-chrome-side-panel.top-chrome/customize_chrome_api_proxy.js';
+import {ThemeSnapshotElement} from 'chrome://customize-chrome-side-panel.top-chrome/theme_snapshot.js';
+import {assertEquals} from 'chrome://webui-test/chai_assert.js';
+import {TestBrowserProxy} from 'chrome://webui-test/test_browser_proxy.js';
+
+import {$$, createBackgroundImage, createTheme, installMock} from './test_support.js';
+
+
+suite('ThemeSnapshotTest', () => {
+  let themeSnapshotElement: ThemeSnapshotElement;
+  let callbackRouterRemote: CustomizeChromePageRemote;
+  let handler: TestBrowserProxy<CustomizeChromePageHandlerRemote>;
+
+  setup(async () => {
+    document.body.innerHTML = window.trustedTypes!.emptyHTML;
+    handler = installMock(
+        CustomizeChromePageHandlerRemote,
+        (mock: CustomizeChromePageHandlerRemote) =>
+            CustomizeChromeApiProxy.setInstance(
+                mock, new CustomizeChromePageCallbackRouter()));
+    callbackRouterRemote = CustomizeChromeApiProxy.getInstance()
+                               .callbackRouter.$.bindNewPipeAndPassRemote();
+  });
+
+  async function createThemeSnapshotElement(): Promise<void> {
+    handler.setResultFor('updateTheme', Promise.resolve({
+      theme: null,
+    }));
+    themeSnapshotElement =
+        document.createElement('customize-chrome-theme-snapshot');
+    document.body.appendChild(themeSnapshotElement);
+    await handler.whenCalled('updateTheme');
+  }
+
+  test('setting theme updates theme snapshot', async () => {
+    // Arrange.
+    createThemeSnapshotElement();
+    const theme = createTheme();
+    theme.backgroundImage = createBackgroundImage('chrome://theme/foo');
+
+    // Act.
+    callbackRouterRemote.setTheme(theme);
+    await callbackRouterRemote.$.flushForTesting();
+
+    // Assert.
+    assertEquals(1, handler.getCallCount('updateTheme'));
+    assertEquals(
+        'chrome://theme/foo',
+        $$<HTMLImageElement>(themeSnapshotElement, '#themeSnapshot img')!.src);
+  });
+});
diff --git a/chrome/test/data/webui/side_panel/read_anything/read_anything_app_test.ts b/chrome/test/data/webui/side_panel/read_anything/read_anything_app_test.ts
index c9ccab98..aab5922 100644
--- a/chrome/test/data/webui/side_panel/read_anything/read_anything_app_test.ts
+++ b/chrome/test/data/webui/side_panel/read_anything/read_anything_app_test.ts
@@ -252,9 +252,9 @@
       ],
     };
     chrome.readAnything.setContentForTesting(axTree, [2]);
-    const expected =
-        '<p lang="en">This is in English<a href="http://www.google.com/">' +
-        'This link has no language set</a></p>';
+    const expected = '<div><p lang="en">This is in English' +
+        '<a href="http://www.google.com/">This link has no language set</a>' +
+        '</p></div>';
     assertContainerInnerHTML(expected);
   });
 
@@ -495,7 +495,8 @@
       ],
     };
     chrome.readAnything.setContentForTesting(axTree, [2]);
-    assertContainerInnerHTML('');
+    const expected = '<div></div>';
+    assertContainerInnerHTML(expected);
   });
 
   // The container clears its old content when it receives new content.
@@ -520,7 +521,7 @@
       ],
     };
     chrome.readAnything.setContentForTesting(axTree1, [2]);
-    const expected1 = 'First set of content.';
+    const expected1 = '<div>First set of content.</div>';
     assertContainerInnerHTML(expected1);
 
     // Fake chrome.readAnything methods for the following AXTree
@@ -543,7 +544,7 @@
       ],
     };
     chrome.readAnything.setContentForTesting(axTree2, [2]);
-    const expected2 = 'Second set of content.';
+    const expected2 = '<div>Second set of content.</div>';
     assertContainerInnerHTML(expected2);
   });
 
@@ -785,8 +786,9 @@
       ],
     };
     chrome.readAnything.setContentForTesting(axTree, [2]);
-    const expected = '<p dir="ltr">This is ltr' +
-        '<a dir="rtl" href="http://www.google.com/">This link is rtl</a></p>';
+    const expected = '<div><p dir="ltr">This is ltr' +
+        '<a dir="rtl" href="http://www.google.com/">' +
+        'This link is rtl</a></p></div>';
     assertContainerInnerHTML(expected);
   });
 
@@ -858,7 +860,7 @@
       ],
     };
     chrome.readAnything.setContentForTesting(axTree, []);
-    const expected = '';
+    const expected = '<div></div>';
     assertContainerInnerHTML(expected);
   });
 });
diff --git a/chrome/updater/BUILD.gn b/chrome/updater/BUILD.gn
index b1bcb6c6..02bc14b 100644
--- a/chrome/updater/BUILD.gn
+++ b/chrome/updater/BUILD.gn
@@ -342,6 +342,7 @@
     if (is_linux) {
       sources += [
         "activity_impl_util_linux.cc",
+        "app/app_install_linux.cc",
         "app/server/linux/server.cc",
         "app/server/linux/server.h",
         "app/server/linux/update_service_stub.cc",
diff --git a/chrome/updater/app/app_install.cc b/chrome/updater/app/app_install.cc
index cff89fb..f0efadd1 100644
--- a/chrome/updater/app/app_install.cc
+++ b/chrome/updater/app/app_install.cc
@@ -206,13 +206,6 @@
       update_service_internal, base::WrapRefCounted(this)));
 }
 
-#if BUILDFLAG(IS_LINUX)
-// TODO(crbug.com/1276114) - implement.
-void AppInstall::WakeCandidateDone() {
-  NOTIMPLEMENTED();
-}
-#endif  // BUILDFLAG(IS_LINUX)
-
 void AppInstall::FetchPolicies() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
diff --git a/chrome/updater/app/app_install_linux.cc b/chrome/updater/app/app_install_linux.cc
new file mode 100644
index 0000000..086bc67b
--- /dev/null
+++ b/chrome/updater/app/app_install_linux.cc
@@ -0,0 +1,13 @@
+// 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/updater/app/app_install.h"
+
+namespace updater {
+
+void AppInstall::WakeCandidateDone() {
+  FetchPolicies();
+}
+
+}  // namespace updater
diff --git a/chrome/updater/test/integration_tests.cc b/chrome/updater/test/integration_tests.cc
index 1046cb02..58f2a80 100644
--- a/chrome/updater/test/integration_tests.cc
+++ b/chrome/updater/test/integration_tests.cc
@@ -27,6 +27,7 @@
 #include "build/build_config.h"
 #include "build/buildflag.h"
 #include "chrome/updater/constants.h"
+#include "chrome/updater/ipc/ipc_support.h"
 #include "chrome/updater/persisted_data.h"
 #include "chrome/updater/prefs.h"
 #include "chrome/updater/registration_data.h"
@@ -52,9 +53,6 @@
 #include "chrome/updater/win/win_constants.h"
 #endif  // BUILDFLAG(IS_WIN)
 
-// TODO(1367437): Enable tests once updater is implemented for Linux
-#if !BUILDFLAG(IS_LINUX)
-
 namespace updater::test {
 namespace {
 
@@ -104,8 +102,9 @@
 
   void TearDown() override {
     ExitTestMode();
-    if (!HasFatalFailure())
+    if (!HasFatalFailure()) {
       ExpectClean();
+    }
     PrintLog();
 
     // TODO(crbug.com/1159189): Use a specific test output directory
@@ -354,6 +353,10 @@
 
  private:
   base::test::TaskEnvironment environment_;
+
+#if BUILDFLAG(IS_POSIX)
+  ScopedIPCSupportWrapper ipc_support_;
+#endif
 };
 
 // The project's position is that component builds are not portable outside of
@@ -380,6 +383,9 @@
   Uninstall();
 }
 
+// TODO(crbug.com/1398845) Enable test once SetupRealUpdaterLowerVersion
+// is implemented.
+#if !BUILDFLAG(IS_LINUX)
 TEST_F(IntegrationTest, OverinstallWorking) {
   ASSERT_NO_FATAL_FAILURE(SetupRealUpdaterLowerVersion());
   EXPECT_TRUE(WaitForUpdaterExit());
@@ -397,10 +403,6 @@
 TEST_F(IntegrationTest, OverinstallBroken) {
   ASSERT_NO_FATAL_FAILURE(SetupRealUpdaterLowerVersion());
   EXPECT_TRUE(WaitForUpdaterExit());
-
-  // TODO(crbug.com/1393788) - find a different way to break the CIPD build,
-  // maybe rename the directory then restore it back before uninstalling such
-  // that clean up is successful.
   DeleteUpdaterDirectory();
 
   // Since the old version is not working, the new version should install and
@@ -411,6 +413,7 @@
 
   Uninstall();
 }
+#endif  // !BUILDFLAG(IS_LINUX)
 
 TEST_F(IntegrationTest, SelfUninstallOutdatedUpdater) {
   Install();
@@ -783,6 +786,9 @@
 
 #if BUILDFLAG(CHROMIUM_BRANDING) || BUILDFLAG(GOOGLE_CHROME_BRANDING)
 #if !defined(COMPONENT_BUILD)
+// TODO(crbug.com/1398845): Enable test once SetupRealUpdaterLowerVersion
+// is implemented.
+#if !BUILDFLAG(IS_LINUX)
 TEST_F(IntegrationTest, SelfUpdateFromOldReal) {
   ScopedServer test_server(test_commands_);
 
@@ -814,7 +820,6 @@
   ExpectVersionNotActive(kUpdaterVersion);
   Uninstall();
 
-  // TODO(crbug.com/1393788) - eliminate this special case of clean up.
 #if BUILDFLAG(IS_WIN)
   // This deletes a tree of empty subdirectories corresponding to the crash
   // handler of the lower version updater installed above. `Uninstall` runs
@@ -827,6 +832,7 @@
 #endif  // IS_WIN
 }
 
+#endif  // !BUILDFLAG(IS_LINUX)
 #endif
 #endif
 
@@ -960,5 +966,3 @@
 #endif  // BUILDFLAG(IS_WIN) || !defined(COMPONENT_BUILD)
 
 }  // namespace updater::test
-
-#endif  // !BUILDFLAG(IS_LINUX)
diff --git a/chrome/updater/test/integration_tests_impl.cc b/chrome/updater/test/integration_tests_impl.cc
index c551ae1d..0cf5453 100644
--- a/chrome/updater/test/integration_tests_impl.cc
+++ b/chrome/updater/test/integration_tests_impl.cc
@@ -151,15 +151,18 @@
     const bool is_match = [&scope, &request_body]() {
       const absl::optional<base::Value> doc =
           base::JSONReader::Read(request_body);
-      if (!doc || !doc->is_dict())
+      if (!doc || !doc->is_dict()) {
         return false;
+      }
       const base::Value::Dict* object_request =
           doc->GetDict().FindDict("request");
-      if (!object_request)
+      if (!object_request) {
         return false;
+      }
       absl::optional<bool> ismachine = object_request->FindBool("ismachine");
-      if (!ismachine.has_value())
+      if (!ismachine.has_value()) {
         return false;
+      }
       switch (scope) {
         case UpdaterScope::kSystem:
           return *ismachine;
@@ -200,8 +203,9 @@
 int CountDirectoryFiles(const base::FilePath& dir) {
   base::FileEnumerator it(dir, false, base::FileEnumerator::FILES);
   int res = 0;
-  for (base::FilePath name = it.Next(); !name.empty(); name = it.Next())
+  for (base::FilePath name = it.Next(); !name.empty(); name = it.Next()) {
     ++res;
+  }
   return res;
 }
 
@@ -459,8 +463,9 @@
   }
   VLOG(0) << " Run command: " << command_line.GetCommandLineString();
   base::Process process = base::LaunchProcess(command_line, {});
-  if (!process.IsValid())
+  if (!process.IsValid()) {
     return false;
+  }
 
   // macOS requires a larger timeout value for --install.
   return process.WaitForExitWithTimeout(2 * TestTimeouts::action_max_timeout(),
@@ -473,8 +478,9 @@
   auto notify_next = base::TimeTicks::Now() + kOutputInterval;
   const auto deadline = base::TimeTicks::Now() + TestTimeouts::action_timeout();
   while (base::TimeTicks::Now() < deadline) {
-    if (predicate.Run())
+    if (predicate.Run()) {
       return true;
+    }
     if (notify_next < base::TimeTicks::Now()) {
       still_waiting.Run();
       notify_next += kOutputInterval;
@@ -596,8 +602,9 @@
   // Runs on the main sequence.
   auto loop_closure = [&]() {
     LOG(ERROR) << __func__ << ": n: " << n;
-    if (--n)
+    if (--n) {
       return false;
+    }
     loop.Quit();
     return true;
   };
@@ -721,8 +728,7 @@
       }(),
   };
 #else
-  NOTREACHED();
-  return {};
+  return {GetExecutableRelativePath().BaseName().value()};
 #endif
 }
 
diff --git a/chrome/updater/test/integration_tests_linux.cc b/chrome/updater/test/integration_tests_linux.cc
index da56f91..0c8f390 100644
--- a/chrome/updater/test/integration_tests_linux.cc
+++ b/chrome/updater/test/integration_tests_linux.cc
@@ -4,110 +4,208 @@
 
 #include <string>
 
+#include "base/base_paths.h"
 #include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/functional/bind.h"
+#include "base/logging.h"
 #include "base/notreached.h"
+#include "base/path_service.h"
+#include "base/process/launch.h"
+#include "base/process/process_iterator.h"
+#include "base/run_loop.h"
+#include "base/test/bind.h"
+#include "base/test/test_timeouts.h"
+#include "chrome/updater/activity_impl_util_posix.h"
+#include "chrome/updater/constants.h"
+#include "chrome/updater/external_constants_builder.h"
+#include "chrome/updater/registration_data.h"
+#include "chrome/updater/service_proxy_factory.h"
 #include "chrome/updater/test/integration_tests_impl.h"
+#include "chrome/updater/update_service.h"
 #include "chrome/updater/updater_scope.h"
+#include "chrome/updater/util/linux_util.h"
+#include "chrome/updater/util/util.h"
+#include "components/crx_file/crx_verifier.h"
+#include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
+#include "url/gurl.h"
 
-// TODO(crbug.com/1276180) - implement these functions.
-
-namespace updater {
-namespace test {
+namespace updater::test {
+namespace {
+base::FilePath GetExecutablePath() {
+  base::FilePath out_dir;
+  if (!base::PathService::Get(base::DIR_EXE, &out_dir)) {
+    return base::FilePath();
+  }
+  return out_dir.Append(GetExecutableRelativePath());
+}
+}  // namespace
 
 absl::optional<base::FilePath> GetFakeUpdaterInstallFolderPath(
     UpdaterScope scope,
     const base::Version& version) {
-  NOTREACHED();
-  return absl::nullopt;
+  return GetVersionedInstallDirectory(scope, version);
 }
 
 base::FilePath GetSetupExecutablePath() {
-  NOTREACHED();
-  return base::FilePath();
+  // There is no metainstaller on Linux, use the main executable for setup.
+  return GetExecutablePath();
 }
 
 absl::optional<base::FilePath> GetInstalledExecutablePath(UpdaterScope scope) {
-  NOTREACHED();
-  return absl::nullopt;
+  absl::optional<base::FilePath> path = GetVersionedInstallDirectory(scope);
+  if (!path) {
+    return absl::nullopt;
+  }
+  return path->Append(GetExecutableRelativePath());
 }
 
-bool WaitForUpdaterExit(UpdaterScope scope) {
-  NOTREACHED();
-  return false;
+bool WaitForUpdaterExit(UpdaterScope /*scope*/) {
+  return WaitFor(base::BindRepeating([]() {
+                   return !base::NamedProcessIterator(kExecutableName, nullptr)
+                               .NextProcessEntry();
+                 }),
+                 base::BindLambdaForTesting([]() {
+                   VLOG(0) << "Still waiting for updater to exit...";
+                 }));
 }
 
 absl::optional<base::FilePath> GetDataDirPath(UpdaterScope scope) {
-  NOTREACHED();
-  return absl::nullopt;
+  return GetBaseDataDirectory(scope);
 }
 
 void Uninstall(UpdaterScope scope) {
-  NOTREACHED();
+  absl::optional<base::FilePath> path = GetExecutablePath();
+  ASSERT_TRUE(path);
+  base::CommandLine command_line(*path);
+  command_line.AppendSwitch(kUninstallSwitch);
+  int exit_code = -1;
+  ASSERT_TRUE(Run(scope, command_line, &exit_code));
+  EXPECT_EQ(exit_code, 0);
 }
 
 void ExpectActiveUpdater(UpdaterScope scope) {
-  NOTREACHED();
+  absl::optional<base::FilePath> path = GetInstalledExecutablePath(scope);
+  EXPECT_TRUE(path);
+  if (path) {
+    EXPECT_TRUE(base::PathExists(*path));
+  }
 }
 
 void ExpectCandidateUninstalled(UpdaterScope scope) {
-  NOTREACHED();
+  absl::optional<base::FilePath> path = GetVersionedInstallDirectory(scope);
+  EXPECT_TRUE(path);
+  if (path) {
+    EXPECT_FALSE(base::PathExists(*path));
+  }
 }
 
 void ExpectInstalled(UpdaterScope scope) {
-  NOTREACHED();
+  absl::optional<base::FilePath> path = GetInstalledExecutablePath(scope);
+  EXPECT_TRUE(path);
+  if (path) {
+    EXPECT_TRUE(base::PathExists(*path));
+  }
 }
 
 void Clean(UpdaterScope scope) {
-  NOTREACHED();
+  absl::optional<base::FilePath> path = GetBaseDataDirectory(scope);
+  EXPECT_TRUE(path);
+  if (path) {
+    EXPECT_TRUE(base::DeletePathRecursively(*path));
+  }
 }
 
 void ExpectClean(UpdaterScope scope) {
-  NOTREACHED();
+  ExpectCleanProcesses();
+
+  absl::optional<base::FilePath> path = GetBaseDataDirectory(scope);
+  EXPECT_TRUE(path);
+  if (path && base::PathExists(*path)) {
+    // If the path exists, then expect only the log file to be present.
+    int count = CountDirectoryFiles(*path);
+    EXPECT_LT(count, 2);
+    if (count == 1) {
+      EXPECT_TRUE(base::PathExists(path->AppendASCII("updater.log")));
+    }
+  }
 }
 
 void EnterTestMode(const GURL& url) {
-  NOTREACHED();
+  ASSERT_TRUE(ExternalConstantsBuilder()
+                  .SetUpdateURL({url.spec()})
+                  .SetUseCUP(false)
+                  .SetInitialDelay(0.1)
+                  .SetServerKeepAliveSeconds(1)
+                  .SetCrxVerifierFormat(crx_file::VerifierFormat::CRX3)
+                  .SetOverinstallTimeout(TestTimeouts::action_timeout())
+                  .Modify());
 }
 
 void SetActive(UpdaterScope scope, const std::string& app_id) {
-  NOTREACHED();
+  const absl::optional<base::FilePath> path =
+      GetActiveFile(base::GetHomeDir(), app_id);
+  ASSERT_TRUE(path);
+  base::File::Error err = base::File::FILE_OK;
+  EXPECT_TRUE(base::CreateDirectoryAndGetError(path->DirName(), &err))
+      << "Error: " << err;
+  EXPECT_TRUE(base::WriteFile(*path, ""));
 }
 
 void ExpectActive(UpdaterScope scope, const std::string& app_id) {
-  NOTREACHED();
+  const absl::optional<base::FilePath> path =
+      GetActiveFile(base::GetHomeDir(), app_id);
+  ASSERT_TRUE(path);
+  EXPECT_TRUE(base::PathExists(*path));
+  EXPECT_TRUE(base::PathIsWritable(*path));
 }
 
 void ExpectNotActive(UpdaterScope scope, const std::string& app_id) {
-  NOTREACHED();
+  const absl::optional<base::FilePath> path =
+      GetActiveFile(base::GetHomeDir(), app_id);
+  ASSERT_TRUE(path);
+  EXPECT_FALSE(base::PathExists(*path));
+  EXPECT_FALSE(base::PathIsWritable(*path));
 }
 
 void SetupRealUpdaterLowerVersion(UpdaterScope scope) {
-  NOTREACHED();
+  // TODO(crbug.com/1398845): Add CI for `old_updater`.
+  NOTIMPLEMENTED();
 }
 
 void SetupFakeLegacyUpdaterData(UpdaterScope scope) {
-  NOTREACHED();
+  // No legacy migration for Linux.
 }
 
 void ExpectLegacyUpdaterDataMigrated(UpdaterScope scope) {
-  NOTREACHED();
+  // No legacy migration for Linux.
 }
 
 void InstallApp(UpdaterScope scope, const std::string& app_id) {
-  NOTREACHED();
+  scoped_refptr<UpdateService> update_service = CreateUpdateServiceProxy(scope);
+  RegistrationRequest registration;
+  registration.app_id = app_id;
+  registration.version = base::Version("0.1");
+  base::RunLoop loop;
+  update_service->RegisterApp(registration,
+                              base::BindLambdaForTesting([&loop](int result) {
+                                EXPECT_EQ(result, 0);
+                                loop.Quit();
+                              }));
+  loop.Run();
 }
 
 void UninstallApp(UpdaterScope scope, const std::string& app_id) {
   // This can probably be combined with mac into integration_tests_posix.cc.
-  NOTREACHED();
+  SetExistenceCheckerPath(scope, app_id,
+                          base::FilePath(FILE_PATH_LITERAL("NONE")));
 }
 
 void RunOfflineInstall(UpdaterScope scope,
                        bool is_legacy_install,
                        bool is_silent_install) {
-  NOTREACHED();
+  // TODO(crbug.com/1286574).
 }
 
-}  // namespace test
-}  // namespace updater
+}  // namespace updater::test
diff --git a/chrome/updater/util/win_util.cc b/chrome/updater/util/win_util.cc
index 029e5bc7..d7031bb9 100644
--- a/chrome/updater/util/win_util.cc
+++ b/chrome/updater/util/win_util.cc
@@ -972,4 +972,46 @@
   return base::FilePath::FromASCII(kExecutableName);
 }
 
+std::wstring QuoteForCommandLineToArgvW(const std::wstring& input) {
+  if (input.empty())
+    return L"\"\"";
+
+  std::wstring output;
+  const bool contains_whitespace =
+      input.find_first_of(L" \t") != std::wstring::npos;
+  if (contains_whitespace)
+    output.push_back(L'"');
+
+  size_t slash_count = 0;
+  for (auto i = input.begin(); i != input.end(); ++i) {
+    if (*i == L'"') {
+      // Before a quote, output 2n backslashes.
+      while (slash_count > 0) {
+        output.append(L"\\\\");
+        --slash_count;
+      }
+      output.append(L"\\\"");
+    } else if (*i != L'\\' || i + 1 == input.end()) {
+      // At the end of the string, or before a regular character, output queued
+      // slashes.
+      while (slash_count > 0) {
+        output.push_back(L'\\');
+        --slash_count;
+      }
+      // If this is a slash, it's also the last character. Otherwise, it is just
+      // a regular non-quote/non-slash character.
+      output.push_back(*i);
+    } else if (*i == L'\\') {
+      // This is a slash, possibly followed by a quote, not the last character.
+      // Queue it up and output it later.
+      ++slash_count;
+    }
+  }
+
+  if (contains_whitespace)
+    output.push_back(L'"');
+
+  return output;
+}
+
 }  // namespace updater
diff --git a/chrome/updater/util/win_util.h b/chrome/updater/util/win_util.h
index 5da480d7..70fe9dd3 100644
--- a/chrome/updater/util/win_util.h
+++ b/chrome/updater/util/win_util.h
@@ -44,6 +44,13 @@
 
 namespace updater {
 
+struct LocalAllocTraits {
+  static HLOCAL InvalidValue() { return nullptr; }
+  static void Free(HLOCAL mem) { ::LocalFree(mem); }
+};
+
+using ScopedLocalAlloc = base::ScopedGeneric<HLOCAL, LocalAllocTraits>;
+
 // Helper for methods which perform system operations which may fail. The
 // failure reason is returned as an HRESULT.
 // TODO(crbug.com/1369769): Remove the following warning once resolved in
@@ -338,6 +345,30 @@
 // processes exited cleanly.
 bool StopGoogleUpdateProcesses(UpdaterScope scope);
 
+// Quotes `input` if necessary so that it will be interpreted as a single
+// command-line parameter according to the rules for ::CommandLineToArgvW.
+//
+// ::CommandLineToArgvW has a special interpretation of backslash characters
+// when they are followed by a quotation mark character ("). This interpretation
+// assumes that any preceding argument is a valid file system path, or else it
+// may behave unpredictably.
+//
+// This special interpretation controls the "in quotes" mode tracked by the
+// parser. When this mode is off, whitespace terminates the current argument.
+// When on, whitespace is added to the argument like all other characters.
+
+// * 2n backslashes followed by a quotation mark produce n backslashes followed
+// by begin/end quote. This does not become part of the parsed argument, but
+// toggles the "in quotes" mode.
+// * (2n) + 1 backslashes followed by a quotation mark again produce n
+// backslashes followed by a quotation mark literal ("). This does not toggle
+// the "in quotes" mode.
+// * n backslashes not followed by a quotation mark simply produce n
+// backslashes.
+//
+// See examples in the `WinUtil.QuoteForCommandLineToArgvW` unit test.
+std::wstring QuoteForCommandLineToArgvW(const std::wstring& input);
+
 }  // namespace updater
 
 #endif  // CHROME_UPDATER_UTIL_WIN_UTIL_H_
diff --git a/chrome/updater/util/win_util_unittest.cc b/chrome/updater/util/win_util_unittest.cc
index d5e66976..c3a39b1 100644
--- a/chrome/updater/util/win_util_unittest.cc
+++ b/chrome/updater/util/win_util_unittest.cc
@@ -346,4 +346,45 @@
   EXPECT_TRUE(StopGoogleUpdateProcesses(GetTestScope()));
 }
 
+TEST(WinUtil, QuoteForCommandLineToArgvW) {
+  const struct {
+    std::vector<std::wstring> input_args;
+    const wchar_t* expected_output;
+  } test_cases[] = {
+      // Unformatted parameters.
+      {{L"abc=1"}, L"abc=1"},
+      {{L"abc=1", L"xyz=2"}, L"abc=1 xyz=2"},
+      {{L"abc=1", L"xyz=2", L"q"}, L"abc=1 xyz=2 q"},
+      {{L" abc=1  ", L"  xyz=2", L"q "}, L"abc=1 xyz=2 q"},
+      {{L"\"abc = 1\""}, L"\"abc = 1\""},
+      {{L"abc\" = \"1", L"xyz=2"}, L"\"abc = 1\" xyz=2"},
+      {{L"\"abc = 1\""}, L"\"abc = 1\""},
+      {{L"abc\" = \"1"}, L"\"abc = 1\""},
+  };
+
+  for (const auto& test_case : test_cases) {
+    std::wstring input_command_line =
+        base::StrCat({L"c:\\test\\process.exe ",
+                      base::JoinString(test_case.input_args, L" ")});
+    int num_args = 0;
+    ScopedLocalAlloc args(
+        ::CommandLineToArgvW(&input_command_line[0], &num_args));
+    ASSERT_EQ(num_args - 1U, test_case.input_args.size());
+
+    const wchar_t** argv = reinterpret_cast<const wchar_t**>(args.get());
+    std::wstring recreated_command_line;
+    for (int i = 1; i < num_args; ++i) {
+      recreated_command_line.append(QuoteForCommandLineToArgvW(argv[i]));
+
+      if (i + 1 < num_args)
+        recreated_command_line.push_back(L' ');
+    }
+
+    EXPECT_EQ(recreated_command_line, test_case.expected_output)
+        << "recreated_command_line '" << recreated_command_line
+        << "' did not match test_case.expected_output '"
+        << test_case.expected_output;
+  }
+}
+
 }  // namespace updater
diff --git a/chrome/updater/win/app_command_runner.cc b/chrome/updater/win/app_command_runner.cc
index 5b170c4..2958359 100644
--- a/chrome/updater/win/app_command_runner.cc
+++ b/chrome/updater/win/app_command_runner.cc
@@ -146,70 +146,6 @@
   return formatted_parameter;
 }
 
-// Quotes `input` if necessary so that it will be interpreted as a single
-// command-line parameter according to the rules for ::CommandLineToArgvW.
-//
-// ::CommandLineToArgvW has a special interpretation of backslash characters
-// when they are followed by a quotation mark character ("). This interpretation
-// assumes that any preceding argument is a valid file system path, or else it
-// may behave unpredictably.
-//
-// This special interpretation controls the "in quotes" mode tracked by the
-// parser. When this mode is off, whitespace terminates the current argument.
-// When on, whitespace is added to the argument like all other characters.
-
-// * 2n backslashes followed by a quotation mark produce n backslashes followed
-// by begin/end quote. This does not become part of the parsed argument, but
-// toggles the "in quotes" mode.
-// * (2n) + 1 backslashes followed by a quotation mark again produce n
-// backslashes followed by a quotation mark literal ("). This does not toggle
-// the "in quotes" mode.
-// * n backslashes not followed by a quotation mark simply produce n
-// backslashes.
-//
-// See examples in the WinUtil*FormatAppCommandLine unit tests.
-std::wstring QuoteForCommandLineToArgvW(const std::wstring& input) {
-  if (input.empty())
-    return L"\"\"";
-
-  std::wstring output;
-  const bool contains_whitespace =
-      input.find_first_of(L" \t") != std::wstring::npos;
-  if (contains_whitespace)
-    output.push_back(L'"');
-
-  size_t slash_count = 0;
-  for (auto i = input.begin(); i != input.end(); ++i) {
-    if (*i == L'"') {
-      // Before a quote, output 2n backslashes.
-      while (slash_count > 0) {
-        output.append(L"\\\\");
-        --slash_count;
-      }
-      output.append(L"\\\"");
-    } else if (*i != L'\\' || i + 1 == input.end()) {
-      // At the end of the string, or before a regular character, output queued
-      // slashes.
-      while (slash_count > 0) {
-        output.push_back(L'\\');
-        --slash_count;
-      }
-      // If this is a slash, it's also the last character. Otherwise, it is just
-      // a regular non-quote/non-slash character.
-      output.push_back(*i);
-    } else if (*i == L'\\') {
-      // This is a slash, possibly followed by a quote, not the last character.
-      // Queue it up and output it later.
-      ++slash_count;
-    }
-  }
-
-  if (contains_whitespace)
-    output.push_back(L'"');
-
-  return output;
-}
-
 bool IsParentOf(int key, const base::FilePath& child) {
   base::FilePath path;
   return base::PathService::Get(key, &path) && path.IsParent(child);
diff --git a/chrome/updater/win/app_command_runner.h b/chrome/updater/win/app_command_runner.h
index f0a6938b..ed99592 100644
--- a/chrome/updater/win/app_command_runner.h
+++ b/chrome/updater/win/app_command_runner.h
@@ -19,13 +19,6 @@
 
 namespace updater {
 
-struct LocalAllocTraits {
-  static HLOCAL InvalidValue() { return nullptr; }
-  static void Free(HLOCAL mem) { ::LocalFree(mem); }
-};
-
-using ScopedLocalAlloc = base::ScopedGeneric<HLOCAL, LocalAllocTraits>;
-
 // AppCommandRunner loads and runs a pre-registered command line from the
 // registry.
 class AppCommandRunner {
diff --git a/chrome/updater/win/app_command_runner_unittest.cc b/chrome/updater/win/app_command_runner_unittest.cc
index a4f01fa..f1f29e1 100644
--- a/chrome/updater/win/app_command_runner_unittest.cc
+++ b/chrome/updater/win/app_command_runner_unittest.cc
@@ -26,6 +26,7 @@
 #include "chrome/updater/updater_branding.h"
 #include "chrome/updater/updater_version.h"
 #include "chrome/updater/util/unittest_util_win.h"
+#include "chrome/updater/util/win_util.h"
 #include "chrome/updater/win/test/test_executables.h"
 #include "chrome/updater/win/win_constants.h"
 #include "testing/gtest/include/gtest/gtest.h"
diff --git a/chrome/updater/win/installer/installer.cc b/chrome/updater/win/installer/installer.cc
index d321ff5..a59b5e1 100644
--- a/chrome/updater/win/installer/installer.cc
+++ b/chrome/updater/win/installer/installer.cc
@@ -193,9 +193,12 @@
 
   // Append the command line arguments in `cmd_line` first.
   int num_args = 0;
-  wchar_t** const arg_list = ::CommandLineToArgvW(cmd_line, &num_args);
+  ScopedLocalAlloc scoped_arg_list(::CommandLineToArgvW(cmd_line, &num_args));
+  wchar_t** const arg_list =
+      reinterpret_cast<wchar_t** const>(scoped_arg_list.get());
   for (int i = 1; i != num_args; ++i) {
-    if (!args.append(L" ") || !args.append(arg_list[i])) {
+    if (!args.append(L" ") ||
+        !args.append(QuoteForCommandLineToArgvW(arg_list[i]).c_str())) {
       return ProcessExitResult(COMMAND_STRING_OVERFLOW);
     }
   }
diff --git a/chrome/updater/win/task_scheduler.cc b/chrome/updater/win/task_scheduler.cc
index 5c57fc1..3abd3860 100644
--- a/chrome/updater/win/task_scheduler.cc
+++ b/chrome/updater/win/task_scheduler.cc
@@ -626,8 +626,11 @@
       return false;
     }
 
-    base::win::ScopedBstr path(run_command.GetProgram().value());
-    hr = exec_action->put_Path(path.Get());
+    // Quotes the command line before `put_Path`.
+    hr = exec_action->put_Path(
+        base::win::ScopedBstr(
+            base::CommandLine(run_command.GetProgram()).GetCommandLineString())
+            .Get());
     if (FAILED(hr)) {
       PLOG(ERROR) << "Can't set path of exec action. " << std::hex << hr;
       return false;
diff --git a/chrome/updater/win/task_scheduler_unittest.cc b/chrome/updater/win/task_scheduler_unittest.cc
index 4a631f5..a967d9a 100644
--- a/chrome/updater/win/task_scheduler_unittest.cc
+++ b/chrome/updater/win/task_scheduler_unittest.cc
@@ -312,7 +312,8 @@
   EXPECT_EQ(0UL, info.exec_actions.size());
   EXPECT_TRUE(task_scheduler_->GetTaskInfo(kTaskName1, &info));
   ASSERT_EQ(1UL, info.exec_actions.size());
-  EXPECT_EQ(command_line1.GetProgram(), info.exec_actions[0].application_path);
+  EXPECT_EQ(base::StrCat({L"\"", command_line1.GetProgram().value(), L"\""}),
+            info.exec_actions[0].application_path.value());
   EXPECT_EQ(command_line1.GetArgumentsString(), info.exec_actions[0].arguments);
 
   base::CommandLine command_line2 = GetTestProcessCommandLine(GetTestScope());
@@ -326,7 +327,8 @@
   // the previous contents of the struct.
   EXPECT_TRUE(task_scheduler_->GetTaskInfo(kTaskName2, &info));
   ASSERT_EQ(1UL, info.exec_actions.size());
-  EXPECT_EQ(command_line2.GetProgram(), info.exec_actions[0].application_path);
+  EXPECT_EQ(base::StrCat({L"\"", command_line2.GetProgram().value(), L"\""}),
+            info.exec_actions[0].application_path.value());
   EXPECT_EQ(command_line2.GetArgumentsString(), info.exec_actions[0].arguments);
 
   EXPECT_TRUE(task_scheduler_->DeleteTask(kTaskName1));
diff --git a/chromecast/base/BUILD.gn b/chromecast/base/BUILD.gn
index 90aee0c..543ca2b 100644
--- a/chromecast/base/BUILD.gn
+++ b/chromecast/base/BUILD.gn
@@ -382,6 +382,7 @@
       "$java_test_dir/org/chromium/chromecast/base/ObservableMapTest.java",
       "$java_test_dir/org/chromium/chromecast/base/ObservableMiscellaneousTest.java",
       "$java_test_dir/org/chromium/chromecast/base/ObservableNotTest.java",
+      "$java_test_dir/org/chromium/chromecast/base/ObservableOpaqueTest.java",
       "$java_test_dir/org/chromium/chromecast/base/ObservableOrTest.java",
       "$java_test_dir/org/chromium/chromecast/base/ObserversTest.java",
       "$java_test_dir/org/chromium/chromecast/base/OwnedScopeTest.java",
diff --git a/chromecast/base/java/src/org/chromium/chromecast/base/Observable.java b/chromecast/base/java/src/org/chromium/chromecast/base/Observable.java
index efd79501..f9ff850c 100644
--- a/chromecast/base/java/src/org/chromium/chromecast/base/Observable.java
+++ b/chromecast/base/java/src/org/chromium/chromecast/base/Observable.java
@@ -121,6 +121,13 @@
     }
 
     /**
+     * Returns an Observable with its type mapped to Unit.
+     */
+    public final Observable<Unit> opaque() {
+        return map(x -> Unit.unit());
+    }
+
+    /**
      * Returns an Observable that combines the state of all of this Observable's data into
      * a single activation of type A, where the state is combined by successively applying |acc|
      * when this Observable adds data, and |dim| when this Observable removes data.
diff --git a/chromecast/base/java/test/org/chromium/chromecast/base/ObservableOpaqueTest.java b/chromecast/base/java/test/org/chromium/chromecast/base/ObservableOpaqueTest.java
new file mode 100644
index 0000000..9dfca84
--- /dev/null
+++ b/chromecast/base/java/test/org/chromium/chromecast/base/ObservableOpaqueTest.java
@@ -0,0 +1,31 @@
+// 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.chromecast.base;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.BlockJUnit4ClassRunner;
+
+import org.chromium.base.test.util.Batch;
+
+/**
+ * Tests for Observable#opaque().
+ */
+@RunWith(BlockJUnit4ClassRunner.class)
+@Batch(Batch.UNIT_TESTS)
+public class ObservableOpaqueTest {
+    @Test
+    public void testOpaque() {
+        Controller<String> a = new Controller<>();
+        Observable<?> opaque = a.opaque();
+
+        ReactiveRecorder opaqueRecorder = ReactiveRecorder.record(opaque);
+        a.set("foo");
+        opaqueRecorder.verify().opened(Unit.unit()).end();
+
+        a.reset();
+        opaqueRecorder.verify().closed(Unit.unit()).end();
+    }
+}
diff --git a/chromecast/browser/android/BUILD.gn b/chromecast/browser/android/BUILD.gn
index b164b88..cec8563 100644
--- a/chromecast/browser/android/BUILD.gn
+++ b/chromecast/browser/android/BUILD.gn
@@ -110,6 +110,7 @@
   java_src_dir = "//chromecast/browser/android/apk/src"
   sources = [
     "$java_src_dir/org/chromium/chromecast/shell/AsyncTaskRunner.java",
+    "$java_src_dir/org/chromium/chromecast/shell/BroadcastReceiverScope.java",
     "$java_src_dir/org/chromium/chromecast/shell/HandlerScheduler.java",
     "$java_src_dir/org/chromium/chromecast/shell/LocalBroadcastReceiverScope.java",
   ]
@@ -224,6 +225,7 @@
 robolectric_binary("cast_shell_junit_tests") {
   sources = [
     "junit/src/org/chromium/chromecast/shell/AsyncTaskRunnerTest.java",
+    "junit/src/org/chromium/chromecast/shell/BroadcastReceiverScopeTest.java",
     "junit/src/org/chromium/chromecast/shell/CastWebContentsActivityTest.java",
     "junit/src/org/chromium/chromecast/shell/CastWebContentsComponentTest.java",
     "junit/src/org/chromium/chromecast/shell/CastWebContentsIntentUtilsTest.java",
@@ -251,6 +253,7 @@
     "//content/public/android:content_java",
     "//testing/android/junit:junit_test_support",
     "//third_party/androidx:androidx_localbroadcastmanager_localbroadcastmanager_java",
+    "//third_party/androidx:androidx_test_core_java",
     "//third_party/hamcrest:hamcrest_java",
   ]
 }
diff --git a/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/BroadcastReceiverScope.java b/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/BroadcastReceiverScope.java
new file mode 100644
index 0000000..e0a6b3d0
--- /dev/null
+++ b/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/BroadcastReceiverScope.java
@@ -0,0 +1,51 @@
+// 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.chromecast.shell;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+
+import org.chromium.base.ContextUtils;
+import org.chromium.chromecast.base.Scope;
+
+/**
+ * Registers a BroadcastReceiver in the constructor, and unregisters it in the close() method.
+ *
+ * This can be used to react to Observables to properly control the lifetimes of BroadcastReceivers.
+ */
+public class BroadcastReceiverScope implements Scope {
+    private final Context mContext;
+    private final BroadcastReceiver mReceiver;
+
+    public BroadcastReceiverScope(IntentFilter filter, IntentReceivedCallback callback) {
+        this(ContextUtils.getApplicationContext(), filter, callback);
+    }
+
+    public BroadcastReceiverScope(
+            Context context, IntentFilter filter, IntentReceivedCallback callback) {
+        mContext = context;
+        mReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                callback.onReceive(intent);
+            }
+        };
+        mContext.registerReceiver(mReceiver, filter);
+    }
+
+    @Override
+    public void close() {
+        mContext.unregisterReceiver(mReceiver);
+    }
+
+    /**
+     * Functional interface to handle received Intents.
+     */
+    public interface IntentReceivedCallback {
+        public void onReceive(Intent intent);
+    }
+}
diff --git a/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastContentWindowAndroid.java b/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastContentWindowAndroid.java
index 775f5522..809ee0e 100644
--- a/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastContentWindowAndroid.java
+++ b/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastContentWindowAndroid.java
@@ -102,6 +102,11 @@
         mComponent.setAllowPictureInPicture(allowPictureInPicture);
     }
 
+    @CalledByNative
+    private void setMediaPlaying(boolean mediaPlaying) {
+        mComponent.setMediaPlaying(mediaPlaying);
+    }
+
     @SuppressWarnings("unused")
     @CalledByNative
     private void onNativeDestroyed() {
diff --git a/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastWebContentsActivity.java b/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastWebContentsActivity.java
index 9d2bf50..a866342 100644
--- a/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastWebContentsActivity.java
+++ b/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastWebContentsActivity.java
@@ -151,6 +151,45 @@
             });
         });
 
+        final Controller<Unit> mediaPlaying = new Controller<>();
+        mCreatedState.subscribe(x -> {
+            IntentFilter filter = new IntentFilter(CastWebContentsIntentUtils.ACTION_MEDIA_PLAYING);
+            return new LocalBroadcastReceiverScope(filter, (Intent intent) -> {
+                if (CastWebContentsIntentUtils.isMediaPlaying(intent)) {
+                    mediaPlaying.set(Unit.unit());
+                } else {
+                    mediaPlaying.reset();
+                }
+            });
+        });
+
+        final Controller<Unit> isDocked = new Controller<>();
+        mCreatedState.subscribe(x -> {
+            IntentFilter filter = new IntentFilter(Intent.ACTION_DOCK_EVENT);
+            return new BroadcastReceiverScope(filter, (Intent intent) -> {
+                if (isDocked(intent)) {
+                    isDocked.set(Unit.unit());
+                } else {
+                    isDocked.reset();
+                }
+            });
+        });
+
+        Observable<Unit> shouldKeepScreenOn =
+                mGotIntentState.filter(CastWebContentsIntentUtils::shouldKeepScreenOn).opaque();
+
+        shouldKeepScreenOn.or(mediaPlaying.and(isDocked).opaque()).subscribe((x) -> {
+            getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+            return () -> getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+        });
+
+        isDocked.subscribe((x) -> {
+            getWindow().addFlags(WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON);
+            return () -> {
+                getWindow().clearFlags(WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON);
+            };
+        });
+
         mGotIntentState.map(Intent::getExtras)
                 .map(CastWebContentsIntentUtils::getSessionId)
                 .subscribe(Observers.onEnter(mSessionIdState::set));
@@ -171,13 +210,6 @@
                 .filter(CastWebContentsIntentUtils::shouldTurnOnScreen)
                 .subscribe(Observers.onEnter(x -> turnScreenOn()));
 
-        mCreatedState.and(mGotIntentState)
-                .map(Both::getSecond)
-                .filter(CastWebContentsIntentUtils::shouldKeepScreenOn)
-                .subscribe(Observers.onEnter(x -> {
-                    getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
-                }));
-
         // Handle each new Intent.
         Controller<CastWebContentsSurfaceHelper.StartParams> startParamsState = new Controller<>();
         mGotIntentState.and(Observable.not(mIsFinishingState))
@@ -355,4 +387,15 @@
     public void setSurfaceHelperForTesting(CastWebContentsSurfaceHelper surfaceHelper) {
         mSurfaceHelperState.set(surfaceHelper);
     }
+
+    private static boolean isDocked(Intent intent) {
+        if (intent == null || !Intent.ACTION_DOCK_EVENT.equals(intent.getAction())) {
+            Log.w(TAG, "Invalid dock intent:" + intent);
+            return false;
+        }
+        int dockState = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, -1);
+        boolean result = dockState != Intent.EXTRA_DOCK_STATE_UNDOCKED;
+        Log.d(TAG, "IsDocked: " + result);
+        return result;
+    }
 }
diff --git a/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastWebContentsComponent.java b/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastWebContentsComponent.java
index 991a073..0943775 100644
--- a/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastWebContentsComponent.java
+++ b/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastWebContentsComponent.java
@@ -254,10 +254,16 @@
     }
 
     public void setAllowPictureInPicture(boolean allowPictureInPicture) {
+        if (DEBUG) Log.d(TAG, "setAllowPictureInPicture: " + allowPictureInPicture);
         sendIntentSync(CastWebContentsIntentUtils.allowPictureInPicture(
                 mSessionId, allowPictureInPicture));
     }
 
+    public void setMediaPlaying(boolean mediaPlaying) {
+        if (DEBUG) Log.d(TAG, "setMediaPlaying: " + mediaPlaying);
+        sendIntentSync(CastWebContentsIntentUtils.mediaPlaying(mSessionId, mediaPlaying));
+    }
+
     public static void onComponentClosed(String sessionId) {
         if (DEBUG) Log.d(TAG, "onComponentClosed");
         sendIntentSync(CastWebContentsIntentUtils.onActivityStopped(sessionId));
diff --git a/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastWebContentsIntentUtils.java b/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastWebContentsIntentUtils.java
index 7b496a6..4a57ffb8 100644
--- a/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastWebContentsIntentUtils.java
+++ b/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastWebContentsIntentUtils.java
@@ -54,6 +54,9 @@
     public static final String ACTION_ALLOW_PICTURE_IN_PICTURE =
             "com.google.android.apps.castshell.intent.action.ALLOW_PICTURE_IN_PICTURE";
 
+    public static final String ACTION_MEDIA_PLAYING =
+            "com.google.android.apps.castshell.intent.action.MEDIA_PLAYING";
+
     /** Key of extra value in an intent, the value is a URI of cast://webcontents/<instanceId> */
     static final String INTENT_EXTRA_URI = "content_uri";
 
@@ -75,6 +78,12 @@
     static final String INTENT_EXTRA_ALLOW_PICTURE_IN_PICTURE =
             "com.google.android.apps.castshell.intent.extra.ALLOW_PICTURE_IN_PICTURE";
 
+    /**
+     * Key of extra indicating whether media is playing.
+     */
+    static final String INTENT_EXTRA_MEDIA_PLAYING =
+            "com.google.android.apps.castshell.intent.extra.MEDIA_PLAYING";
+
     /** Key of extra value of the intent to start a web content, value is true is if cast app is
      *  a remote control app.
      */
@@ -239,6 +248,11 @@
         return in.getExtras().getBoolean(INTENT_EXTRA_ALLOW_PICTURE_IN_PICTURE);
     }
 
+    // Used by ACTION_MEDIA_PLAYING
+    public static boolean isMediaPlaying(Intent in) {
+        return in.getExtras().getBoolean(INTENT_EXTRA_MEDIA_PLAYING);
+    }
+
     // Used by ACTION_VIEW
     public static boolean isRemoteControlMode(Bundle bundle) {
         return bundle.getBoolean(INTENT_EXTRA_REMOTE_CONTROL_MODE);
@@ -275,6 +289,13 @@
         return intent;
     }
 
+    public static Intent mediaPlaying(String instanceId, boolean mediaPlaying) {
+        Intent intent = new Intent(ACTION_MEDIA_PLAYING);
+        intent.putExtra(INTENT_EXTRA_URI, getInstanceUri(instanceId).toString());
+        intent.putExtra(INTENT_EXTRA_MEDIA_PLAYING, mediaPlaying);
+        return intent;
+    }
+
     // CastWebContentsSurfaceHelper -> CastWebContentsActivity
     public static Intent onWebContentStopped(Uri uri) {
         Intent intent = new Intent(CastIntents.ACTION_ON_WEB_CONTENT_STOPPED);
diff --git a/chromecast/browser/android/cast_content_window_android.cc b/chromecast/browser/android/cast_content_window_android.cc
index e72613b..7315c85 100644
--- a/chromecast/browser/android/cast_content_window_android.cc
+++ b/chromecast/browser/android/cast_content_window_android.cc
@@ -88,11 +88,13 @@
 void CastContentWindowAndroid::MediaStartedPlaying(
     const content::WebContentsObserver::MediaPlayerInfo& video_type,
     const content::MediaPlayerId& id) {
+  JNIEnv* env = base::android::AttachCurrentThread();
   if (video_type.has_video) {
-    JNIEnv* env = base::android::AttachCurrentThread();
     Java_CastContentWindowAndroid_setAllowPictureInPicture(
         env, java_window_, static_cast<jboolean>(true));
   }
+  Java_CastContentWindowAndroid_setMediaPlaying(env, java_window_,
+                                                static_cast<jboolean>(true));
 }
 
 void CastContentWindowAndroid::MediaStoppedPlaying(
@@ -102,6 +104,8 @@
   JNIEnv* env = base::android::AttachCurrentThread();
   Java_CastContentWindowAndroid_setAllowPictureInPicture(
       env, java_window_, static_cast<jboolean>(false));
+  Java_CastContentWindowAndroid_setMediaPlaying(env, java_window_,
+                                                static_cast<jboolean>(false));
 }
 
 void CastContentWindowAndroid::OnActivityStopped(
diff --git a/chromecast/browser/android/junit/src/org/chromium/chromecast/shell/BroadcastReceiverScopeTest.java b/chromecast/browser/android/junit/src/org/chromium/chromecast/shell/BroadcastReceiverScopeTest.java
new file mode 100644
index 0000000..09f001783
--- /dev/null
+++ b/chromecast/browser/android/junit/src/org/chromium/chromecast/shell/BroadcastReceiverScopeTest.java
@@ -0,0 +1,92 @@
+// 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.chromecast.shell;
+
+import static android.os.Looper.getMainLooper;
+
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.emptyIterable;
+import static org.junit.Assert.assertThat;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+import org.robolectric.annotation.LooperMode;
+import org.robolectric.annotation.LooperMode.Mode;
+
+import org.chromium.testing.local.LocalRobolectricTestRunner;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests for BroadcastReceiverScope.
+ */
+@RunWith(LocalRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+@LooperMode(Mode.PAUSED)
+public class BroadcastReceiverScopeTest {
+    private Context mContext;
+
+    @Before
+    public void setUp() {
+        mContext = ApplicationProvider.getApplicationContext();
+    }
+
+    @Test
+    public void testConstructorRegistersReceiver() {
+        String action = "org.chromium.chromecast.test.ACTION_HELLO";
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(action);
+        List<String> result = new ArrayList<>();
+        new BroadcastReceiverScope(mContext, filter,
+                (Intent intent) -> result.add("Intent received: " + intent.getAction()));
+        Intent intent = new Intent().setAction(action);
+        mContext.sendBroadcast(intent);
+        shadowOf(getMainLooper()).idle();
+        assertThat(result, contains("Intent received: org.chromium.chromecast.test.ACTION_HELLO"));
+    }
+
+    @Test
+    public void testCallbackNotCalledIfBroadcastDoesNotMeetFilterSpec() {
+        String helloAction = "org.chromium.chromecast.test.ACTION_HELLO";
+        String goodbyeAction = "org.chromium.chromecast.test.ACTION_GOODBYE";
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(helloAction);
+        List<String> result = new ArrayList<>();
+        new BroadcastReceiverScope(mContext, filter,
+                (Intent intent) -> result.add("Intent received: " + intent.getAction()));
+        Intent intent = new Intent().setAction(goodbyeAction);
+        mContext.sendBroadcast(intent);
+        shadowOf(getMainLooper()).idle();
+        assertThat(result, emptyIterable());
+    }
+
+    @Test
+    public void testCloseUnregistersReceiver() {
+        String action = "org.chromium.chromecast.test.ACTION_HELLO";
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(action);
+        List<String> result = new ArrayList<>();
+        // Wrap scope in try-with-resources to call close() on it.
+        try (AutoCloseable scope = new BroadcastReceiverScope(mContext, filter,
+                     (Intent intent) -> result.add("Intent received: " + intent.getAction()))) {
+        } catch (Exception e) {
+            result.add("Exception during lifetime of BroadcastReceiver scope: " + e);
+        }
+        Intent intent = new Intent().setAction(action);
+        mContext.sendBroadcast(intent);
+        shadowOf(getMainLooper()).idle();
+        assertThat(result, emptyIterable());
+    }
+}
diff --git a/chromecast/browser/android/junit/src/org/chromium/chromecast/shell/CastWebContentsActivityTest.java b/chromecast/browser/android/junit/src/org/chromium/chromecast/shell/CastWebContentsActivityTest.java
index 09be535..de3831e8 100644
--- a/chromecast/browser/android/junit/src/org/chromium/chromecast/shell/CastWebContentsActivityTest.java
+++ b/chromecast/browser/android/junit/src/org/chromium/chromecast/shell/CastWebContentsActivityTest.java
@@ -4,6 +4,8 @@
 
 package org.chromium.chromecast.shell;
 
+import static android.os.Looper.getMainLooper;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
@@ -49,6 +51,8 @@
 import org.robolectric.annotation.Config;
 import org.robolectric.annotation.Implementation;
 import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.LooperMode;
+import org.robolectric.annotation.LooperMode.Mode;
 import org.robolectric.shadow.api.Shadow;
 import org.robolectric.shadows.ShadowActivity;
 import org.robolectric.shadows.ShadowActivityManager;
@@ -68,6 +72,7 @@
  */
 @RunWith(LocalRobolectricTestRunner.class)
 @Config(manifest = Config.NONE)
+@LooperMode(Mode.PAUSED)
 public class CastWebContentsActivityTest {
     /**
      * ShadowActivity that allows us to intercept calls to setTurnScreenOn.
@@ -535,6 +540,95 @@
         verify(scope).close();
     }
 
+    @Test
+    public void testAddsRequiredFlagsForDifferentDockedAndMediaPlayingStateTransistions() {
+        mActivityLifecycle = Robolectric.buildActivity(CastWebContentsActivity.class,
+                CastWebContentsIntentUtils.requestStartCastActivity(RuntimeEnvironment.application,
+                        mWebContents, true, false, true, /*keepScreenOn=*/false, "0"));
+        mActivity = mActivityLifecycle.get();
+        mActivity.testingModeForTesting();
+        mActivityLifecycle.create();
+        // RuntimeEnvironment.application
+        updateDockState(false);
+        updateMediaState(false);
+        // State: Undocked & No Media Playing
+        assertWakeLockFlags(false, false);
+        // Media Starts playing
+        updateMediaState(true);
+        // State: Undocked & Media Playing
+        assertWakeLockFlags(false, false);
+        // Device docked
+        updateDockState(true);
+        // State: Docked & Media Playing
+        assertWakeLockFlags(true, true);
+        // Media Stops playing
+        updateMediaState(false);
+        // // State: Docked & No Media Playing
+        assertWakeLockFlags(false, true);
+        // Media Starts playing again
+        updateMediaState(true);
+        // State: Docked & Media Playing
+        assertWakeLockFlags(true, true);
+        // Undocks
+        updateDockState(false);
+        // State: Undocked & Media Playing
+        assertWakeLockFlags(false, false);
+        updateMediaState(false);
+        // State: Undocked & No Media Playing
+        assertWakeLockFlags(false, false);
+    }
+
+    @Test
+    public void testEnsureDockStateAndMediaStateDoNotImpactKeepScreenOnFlagIfAlwaysKeepScreenOn() {
+        mActivityLifecycle = Robolectric.buildActivity(CastWebContentsActivity.class,
+                CastWebContentsIntentUtils.requestStartCastActivity(RuntimeEnvironment.application,
+                        mWebContents, true, false, true, /*keepScreenOn=*/true, "0"));
+        mActivity = mActivityLifecycle.get();
+        mActivity.testingModeForTesting();
+        mActivityLifecycle.create();
+        updateDockState(false);
+        updateMediaState(false);
+        assertWakeLockFlags(true, false);
+        updateDockState(true);
+        updateMediaState(true);
+        assertWakeLockFlags(true, true);
+        updateDockState(false);
+        updateMediaState(false);
+        assertWakeLockFlags(false, false);
+    }
+
+    private void assertWakeLockFlags(boolean keepScreenOn, boolean allowLockWhileScreenOn) {
+        if (keepScreenOn) {
+            Assert.assertTrue(Shadows.shadowOf(mActivity.getWindow())
+                                      .getFlag(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON));
+        } else {
+            Assert.assertFalse(Shadows.shadowOf(mActivity.getWindow())
+                                       .getFlag(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON));
+        }
+        if (allowLockWhileScreenOn) {
+            Assert.assertTrue(
+                    Shadows.shadowOf(mActivity.getWindow())
+                            .getFlag(WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON));
+        } else {
+            Assert.assertFalse(
+                    Shadows.shadowOf(mActivity.getWindow())
+                            .getFlag(WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON));
+        }
+    }
+
+    private void updateDockState(boolean docked) {
+        Intent intent = new Intent(Intent.ACTION_DOCK_EVENT);
+        intent.putExtra(
+                Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED + (docked ? 1 : 0));
+        RuntimeEnvironment.application.sendBroadcast(intent);
+        Shadows.shadowOf(getMainLooper()).idle();
+    }
+
+    private void updateMediaState(boolean playingMedia) {
+        CastWebContentsIntentUtils.getLocalBroadcastManager().sendBroadcastSync(
+                CastWebContentsIntentUtils.mediaPlaying(mSessionId, playingMedia));
+    }
+
     private IntentFilter filterFor(String action) {
         IntentFilter filter = new IntentFilter();
         Uri instanceUri = CastWebContentsIntentUtils.getInstanceUri(mSessionId);
diff --git a/chromecast/media/audio/cast_audio_mixer_unittest.cc b/chromecast/media/audio/cast_audio_mixer_unittest.cc
index e25aed0a0..51668d9a7 100644
--- a/chromecast/media/audio/cast_audio_mixer_unittest.cc
+++ b/chromecast/media/audio/cast_audio_mixer_unittest.cc
@@ -22,6 +22,7 @@
 #include "chromecast/media/audio/mock_cast_audio_manager_helper_delegate.h"
 #include "media/audio/audio_io.h"
 #include "media/audio/test_audio_thread.h"
+#include "media/base/audio_glitch_info.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
diff --git a/chromecast/media/audio/cast_audio_output_device.cc b/chromecast/media/audio/cast_audio_output_device.cc
index 0b2b74b7..8013b5a 100644
--- a/chromecast/media/audio/cast_audio_output_device.cc
+++ b/chromecast/media/audio/cast_audio_output_device.cc
@@ -20,6 +20,7 @@
 #include "chromecast/media/audio/audio_output_service/output_stream_connection.h"
 #include "chromecast/media/base/default_monotonic_clock.h"
 #include "media/base/audio_bus.h"
+#include "media/base/audio_glitch_info.h"
 #include "media/base/audio_parameters.h"
 #include "media/base/audio_timestamp_helper.h"
 #include "media/base/bind_to_current_loop.h"
@@ -235,7 +236,7 @@
     // No frames filled, schedule read immediately with a small delay.
     push_timer_.Start(FROM_HERE, base::TimeTicks::Now() + kNoBufferReadDelay,
                       this, &Internal::TryPushBuffer,
-                      base::ExactDeadline(true));
+                      base::subtle::DelayPolicy::kPrecise);
   }
 
   scoped_refptr<CastAudioOutputDevice> output_device_;
@@ -364,7 +365,7 @@
     return 0;
   }
   return active_render_callback_->Render(delay, base::TimeTicks(),
-                                         /*frames_skipped=*/0, audio_bus);
+                                         /*glitch_info=*/{}, audio_bus);
 }
 
 }  // namespace media
diff --git a/chromeos/CHROMEOS_LKGM b/chromeos/CHROMEOS_LKGM
index e0d57e7..9a0dd0b 100644
--- a/chromeos/CHROMEOS_LKGM
+++ b/chromeos/CHROMEOS_LKGM
@@ -1 +1 @@
-15265.0.0
\ No newline at end of file
+15270.0.0
\ No newline at end of file
diff --git a/chromeos/ash/components/dbus/shill/fake_shill_manager_client.cc b/chromeos/ash/components/dbus/shill/fake_shill_manager_client.cc
index b34c875..8ae9093 100644
--- a/chromeos/ash/components/dbus/shill/fake_shill_manager_client.cc
+++ b/chromeos/ash/components/dbus/shill/fake_shill_manager_client.cc
@@ -286,7 +286,7 @@
 }
 
 void FakeShillManagerClient::GetNetworksForGeolocation(
-    chromeos::DBusMethodCallback<base::Value> callback) {
+    chromeos::DBusMethodCallback<base::Value::Dict> callback) {
   base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE,
       base::BindOnce(&FakeShillManagerClient::PassStubGeoNetworks,
@@ -660,14 +660,8 @@
 }
 
 void FakeShillManagerClient::AddGeoNetwork(const std::string& technology,
-                                           const base::Value& network) {
-  base::Value* list_value =
-      stub_geo_networks_.FindKeyOfType(technology, base::Value::Type::LIST);
-  if (!list_value) {
-    list_value = stub_geo_networks_.SetKey(
-        technology, base::Value(base::Value::Type::LIST));
-  }
-  list_value->Append(network.Clone());
+                                           const base::Value::Dict& network) {
+  stub_geo_networks_.EnsureList(technology)->Append(network.Clone());
 }
 
 void FakeShillManagerClient::AddProfile(const std::string& profile_path) {
@@ -1175,7 +1169,7 @@
 }
 
 void FakeShillManagerClient::PassStubGeoNetworks(
-    chromeos::DBusMethodCallback<base::Value> callback) const {
+    chromeos::DBusMethodCallback<base::Value::Dict> callback) const {
   std::move(callback).Run(stub_geo_networks_.Clone());
 }
 
diff --git a/chromeos/ash/components/dbus/shill/fake_shill_manager_client.h b/chromeos/ash/components/dbus/shill/fake_shill_manager_client.h
index 7f3ab5d1..65b664ff 100644
--- a/chromeos/ash/components/dbus/shill/fake_shill_manager_client.h
+++ b/chromeos/ash/components/dbus/shill/fake_shill_manager_client.h
@@ -39,7 +39,7 @@
   void GetProperties(
       chromeos::DBusMethodCallback<base::Value> callback) override;
   void GetNetworksForGeolocation(
-      chromeos::DBusMethodCallback<base::Value> callback) override;
+      chromeos::DBusMethodCallback<base::Value::Dict> callback) override;
   void SetProperty(const std::string& name,
                    const base::Value& value,
                    base::OnceClosure callback,
@@ -98,7 +98,7 @@
                             base::OnceClosure callback,
                             bool enabled) override;
   void AddGeoNetwork(const std::string& technology,
-                     const base::Value& network) override;
+                     const base::Value::Dict& network) override;
   void AddProfile(const std::string& profile_path) override;
   void ClearProperties() override;
   void SetManagerProperty(const std::string& key,
@@ -137,7 +137,7 @@
   void PassStubProperties(
       chromeos::DBusMethodCallback<base::Value> callback) const;
   void PassStubGeoNetworks(
-      chromeos::DBusMethodCallback<base::Value> callback) const;
+      chromeos::DBusMethodCallback<base::Value::Dict> callback) const;
   void CallNotifyObserversPropertyChanged(const std::string& property);
   void NotifyObserversPropertyChanged(const std::string& property);
   base::Value::List& GetListProperty(const std::string& property);
@@ -158,7 +158,7 @@
   base::Value stub_properties_{base::Value::Type::DICTIONARY};
 
   // Dictionary of technology -> list of property dictionaries
-  base::Value stub_geo_networks_{base::Value::Type::DICTIONARY};
+  base::Value::Dict stub_geo_networks_;
 
   // Delay for interactive actions
   base::TimeDelta interactive_delay_;
diff --git a/chromeos/ash/components/dbus/shill/shill_client_helper.cc b/chromeos/ash/components/dbus/shill/shill_client_helper.cc
index 66fe3b1c..b063059 100644
--- a/chromeos/ash/components/dbus/shill/shill_client_helper.cc
+++ b/chromeos/ash/components/dbus/shill/shill_client_helper.cc
@@ -12,7 +12,6 @@
 #include "base/callback_helpers.h"
 #include "base/location.h"
 #include "base/task/single_thread_task_runner.h"
-#include "base/values.h"
 #include "components/device_event_log/device_event_log.h"
 #include "dbus/message.h"
 #include "dbus/object_proxy.h"
@@ -153,6 +152,34 @@
   std::move(callback).Run(std::move(value));
 }
 
+// Handles responses for methods with base::Value::Dict results.
+void OnDictValueMethod(ShillClientHelper::RefHolder* ref_holder,
+                       chromeos::DBusMethodCallback<base::Value::Dict> callback,
+                       dbus::Response* response,
+                       dbus::ErrorResponse* error_response) {
+  if (!response) {
+    if (error_response) {
+      dbus::MessageReader reader(error_response);
+      std::string error_message;
+      reader.PopString(&error_message);
+      NET_LOG(ERROR) << "DBus call failed. Error: "
+                     << error_response->GetErrorName()
+                     << " Message: " << error_message;
+    } else {
+      NET_LOG(ERROR) << "DBus call failed with no error.";
+    }
+    std::move(callback).Run(absl::nullopt);
+    return;
+  }
+  dbus::MessageReader reader(response);
+  base::Value value(dbus::PopDataAsValue(&reader));
+  if (value.is_none()) {
+    std::move(callback).Run(absl::nullopt);
+    return;
+  }
+  std::move(callback).Run(std::move(value).TakeDict());
+}
+
 // Handles responses for methods without results.
 void OnVoidMethodWithErrorCallback(ShillClientHelper::RefHolder* ref_holder,
                                    base::OnceClosure callback,
@@ -304,6 +331,17 @@
                      std::move(callback)));
 }
 
+void ShillClientHelper::CallDictValueMethod(
+    dbus::MethodCall* method_call,
+    chromeos::DBusMethodCallback<base::Value::Dict> callback) {
+  DCHECK(!callback.is_null());
+  proxy_->CallMethodWithErrorResponse(
+      method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
+      base::BindOnce(&OnDictValueMethod,
+                     base::Owned(new RefHolder(weak_ptr_factory_.GetWeakPtr())),
+                     std::move(callback)));
+}
+
 void ShillClientHelper::CallVoidMethodWithErrorCallback(
     dbus::MethodCall* method_call,
     base::OnceClosure callback,
diff --git a/chromeos/ash/components/dbus/shill/shill_client_helper.h b/chromeos/ash/components/dbus/shill/shill_client_helper.h
index c273b5d..4dbaacb0 100644
--- a/chromeos/ash/components/dbus/shill/shill_client_helper.h
+++ b/chromeos/ash/components/dbus/shill/shill_client_helper.h
@@ -87,6 +87,11 @@
   void CallValueMethod(dbus::MethodCall* method_call,
                        chromeos::DBusMethodCallback<base::Value> callback);
 
+  // Calls a method with a base::Value::Dict result.
+  void CallDictValueMethod(
+      dbus::MethodCall* method_call,
+      chromeos::DBusMethodCallback<base::Value::Dict> callback);
+
   // Calls a method without results with error callback.
   void CallVoidMethodWithErrorCallback(dbus::MethodCall* method_call,
                                        base::OnceClosure callback,
diff --git a/chromeos/ash/components/dbus/shill/shill_client_unittest_base.cc b/chromeos/ash/components/dbus/shill/shill_client_unittest_base.cc
index 8b1dcc8b..9e4d03e 100644
--- a/chromeos/ash/components/dbus/shill/shill_client_unittest_base.cc
+++ b/chromeos/ash/components/dbus/shill/shill_client_unittest_base.cc
@@ -14,7 +14,6 @@
 #include "base/location.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/task/single_thread_task_runner.h"
-#include "base/values.h"
 #include "dbus/message.h"
 #include "dbus/object_path.h"
 #include "dbus/values_util.h"
@@ -357,6 +356,17 @@
 }
 
 // static
+void ShillClientUnittestBase::ExpectDictValueResultWithoutStatus(
+    const base::Value::Dict* expected_result,
+    base::Value::Dict result) {
+  std::string expected_result_string;
+  base::JSONWriter::Write(*expected_result, &expected_result_string);
+  std::string result_string;
+  base::JSONWriter::Write(result, &result_string);
+  EXPECT_EQ(expected_result_string, result_string);
+}
+
+// static
 void ShillClientUnittestBase::ExpectValueResult(
     const base::Value* expected_result,
     absl::optional<base::Value> result) {
@@ -365,6 +375,15 @@
                                  std::move(result).value_or(base::Value()));
 }
 
+// static
+void ShillClientUnittestBase::ExpectDictValueResult(
+    const base::Value::Dict* expected_result,
+    absl::optional<base::Value::Dict> result) {
+  EXPECT_TRUE(result);
+  ExpectDictValueResultWithoutStatus(
+      expected_result, std::move(result).value_or(base::Value::Dict()));
+}
+
 void ShillClientUnittestBase::OnConnectToPlatformMessage(
     const std::string& interface_name,
     const std::string& signal_name,
diff --git a/chromeos/ash/components/dbus/shill/shill_client_unittest_base.h b/chromeos/ash/components/dbus/shill/shill_client_unittest_base.h
index fee39fa3..d6dbd9a 100644
--- a/chromeos/ash/components/dbus/shill/shill_client_unittest_base.h
+++ b/chromeos/ash/components/dbus/shill/shill_client_unittest_base.h
@@ -13,6 +13,7 @@
 
 #include "base/memory/ref_counted.h"
 #include "base/test/task_environment.h"
+#include "base/values.h"
 #include "chromeos/ash/components/dbus/shill/shill_client_helper.h"
 #include "chromeos/ash/components/dbus/shill/shill_property_changed_observer.h"
 #include "chromeos/ash/components/dbus/shill/shill_third_party_vpn_observer.h"
@@ -28,10 +29,6 @@
 using ::testing::MatcherInterface;
 using ::testing::MatchResultListener;
 
-namespace base {
-class Value;
-}
-
 namespace dbus {
 
 class MessageReader;
@@ -158,10 +155,19 @@
   static void ExpectValueResult(const base::Value* expected_result,
                                 absl::optional<base::Value> result);
 
+  // Checks the result and expects the call status to be SUCCESS.
+  static void ExpectDictValueResult(const base::Value::Dict* expected_result,
+                                    absl::optional<base::Value::Dict> result);
+
   // Expects the |expected_result| to match the |result|.
   static void ExpectValueResultWithoutStatus(const base::Value* expected_result,
                                              base::Value result);
 
+  // Expects the |expected_result| to match the |result|.
+  static void ExpectDictValueResultWithoutStatus(
+      const base::Value::Dict* expected_result,
+      base::Value::Dict result);
+
   // A message loop to emulate asynchronous behavior.
   base::test::SingleThreadTaskEnvironment task_environment_;
   // The mock bus.
diff --git a/chromeos/ash/components/dbus/shill/shill_manager_client.cc b/chromeos/ash/components/dbus/shill/shill_manager_client.cc
index 46af4dd..6dc44da7 100644
--- a/chromeos/ash/components/dbus/shill/shill_manager_client.cc
+++ b/chromeos/ash/components/dbus/shill/shill_manager_client.cc
@@ -9,7 +9,6 @@
 
 #include "base/bind.h"
 #include "base/check_op.h"
-#include "base/values.h"
 #include "chromeos/ash/components/dbus/shill/fake_shill_manager_client.h"
 #include "chromeos/ash/components/dbus/shill/shill_property_changed_observer.h"
 #include "dbus/bus.h"
@@ -60,10 +59,10 @@
   }
 
   void GetNetworksForGeolocation(
-      chromeos::DBusMethodCallback<base::Value> callback) override {
+      chromeos::DBusMethodCallback<base::Value::Dict> callback) override {
     dbus::MethodCall method_call(shill::kFlimflamManagerInterface,
                                  shill::kGetNetworksForGeolocation);
-    helper_->CallValueMethod(&method_call, std::move(callback));
+    helper_->CallDictValueMethod(&method_call, std::move(callback));
   }
 
   void SetProperty(const std::string& name,
diff --git a/chromeos/ash/components/dbus/shill/shill_manager_client.h b/chromeos/ash/components/dbus/shill/shill_manager_client.h
index 59c9ecc2..e99e5cc9 100644
--- a/chromeos/ash/components/dbus/shill/shill_manager_client.h
+++ b/chromeos/ash/components/dbus/shill/shill_manager_client.h
@@ -9,6 +9,7 @@
 
 #include "base/component_export.h"
 #include "base/time/time.h"
+#include "base/values.h"
 #include "chromeos/ash/components/dbus/shill/fake_shill_simulated_result.h"
 #include "chromeos/ash/components/dbus/shill/shill_client_helper.h"
 #include "chromeos/dbus/common/dbus_method_call_status.h"
@@ -65,7 +66,7 @@
     // which will be appended to the results returned from
     // GetNetworksForGeolocation().
     virtual void AddGeoNetwork(const std::string& technology,
-                               const base::Value& network) = 0;
+                               const base::Value::Dict& network) = 0;
 
     // Does not create an actual profile in the ProfileClient but update the
     // profiles list and sends a notification to observers. This should only be
@@ -176,10 +177,10 @@
       chromeos::DBusMethodCallback<base::Value> callback) = 0;
 
   // Calls the GetNetworksForGeolocation DBus method and invokes |callback| when
-  // complete. |callback| receives a dictionary Value containing an entry for
+  // complete. |callback| receives a base::Value::Dict containing an entry for
   // available network types. See Shill manager-api documentation for details.
   virtual void GetNetworksForGeolocation(
-      chromeos::DBusMethodCallback<base::Value> callback) = 0;
+      chromeos::DBusMethodCallback<base::Value::Dict> callback) = 0;
 
   // Calls SetProperty method.
   virtual void SetProperty(const std::string& name,
diff --git a/chromeos/ash/components/dbus/shill/shill_manager_client_unittest.cc b/chromeos/ash/components/dbus/shill/shill_manager_client_unittest.cc
index 355367a7..6b89bd5 100644
--- a/chromeos/ash/components/dbus/shill/shill_manager_client_unittest.cc
+++ b/chromeos/ash/components/dbus/shill/shill_manager_client_unittest.cc
@@ -154,20 +154,19 @@
   writer.CloseContainer(&type_dict_writer);
 
   // Create the expected value.
-  base::Value property_dict_value(base::Value::Type::DICTIONARY);
-  property_dict_value.SetKey(shill::kGeoMacAddressProperty,
-                             base::Value("01:23:45:67:89:AB"));
-  base::Value type_entry_value(base::Value::Type::LIST);
+  base::Value::Dict property_dict_value;
+  property_dict_value.Set(shill::kGeoMacAddressProperty, "01:23:45:67:89:AB");
+  base::Value::List type_entry_value;
   type_entry_value.Append(std::move(property_dict_value));
-  base::Value type_dict_value(base::Value::Type::DICTIONARY);
-  type_dict_value.SetKey("wifi", std::move(type_entry_value));
+  base::Value::Dict type_dict_value;
+  type_dict_value.Set("wifi", std::move(type_entry_value));
 
   // Set expectations.
   PrepareForMethodCall(shill::kGetNetworksForGeolocation,
                        base::BindRepeating(&ExpectNoArgument), response.get());
   // Call method.
   client_->GetNetworksForGeolocation(
-      base::BindOnce(&ExpectValueResult, &type_dict_value));
+      base::BindOnce(&ExpectDictValueResult, &type_dict_value));
 
   // Run the message loop.
   base::RunLoop().RunUntilIdle();
diff --git a/chromeos/ash/components/drivefs/drivefs_pin_manager.cc b/chromeos/ash/components/drivefs/drivefs_pin_manager.cc
index 6e1d89d..3f78f55 100644
--- a/chromeos/ash/components/drivefs/drivefs_pin_manager.cc
+++ b/chromeos/ash/components/drivefs/drivefs_pin_manager.cc
@@ -54,6 +54,11 @@
 
 }  // namespace
 
+// TODO(b/261530666): This was chosen arbitrarily, this should be experimented
+// with and potentially made dynamic depending on feedback of the in progress
+// queue.
+constexpr base::TimeDelta kPeriodicRemovalInterval = base::Seconds(10);
+
 constexpr char kGCacheFolderName[] = "GCache";
 
 DriveFsPinManager::InProgressSyncingItems::InProgressSyncingItems() = default;
@@ -94,6 +99,19 @@
   return in_progress_items_.size();
 }
 
+std::vector<std::string>
+DriveFsPinManager::InProgressSyncingItems::GetUnstartedItems() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  std::vector<std::string> unstarted_items;
+  for (const auto& item : in_progress_items_) {
+    if (item.second.second > 0) {
+      continue;
+    }
+    unstarted_items.emplace_back(item.first);
+  }
+  return unstarted_items;
+}
+
 DriveFsPinManager::DriveFsPinManager(bool enabled,
                                      const base::FilePath& profile_path,
                                      mojom::DriveFs* drivefs_interface)
@@ -130,6 +148,7 @@
   VLOG(1) << "Caculating free disk space";
   timer_.Begin();
   complete_callback_ = std::move(complete_callback);
+  setup_complete_ = false;
 
   base::FilePath gcache_path(profile_path_.AppendASCII(kGCacheFolderName));
 
@@ -138,6 +157,10 @@
                                   weak_ptr_factory_.GetWeakPtr()));
 }
 
+void DriveFsPinManager::Stop() {
+  Complete(PinError::kErrorManagerStopped);
+}
+
 void DriveFsPinManager::OnFreeDiskSpaceRetrieved(int64_t free_space) {
   if (free_space == -1) {
     LOG(ERROR) << "Error calculating free disk space";
@@ -174,7 +197,7 @@
   }
 
   if (items.value().size() == 0) {
-    VLOG(2) << "Iterated all files and calculated " << size_required_
+    VLOG(1) << "Iterated all files and calculated " << size_required_
             << " bytes required with " << free_space_ << " bytes available in "
             << timer_.Elapsed().InMilliseconds() << "ms";
     StartBatchPinning();
@@ -185,7 +208,7 @@
           << " for space calculation";
   for (const auto& item : items.value()) {
     if (item->metadata->pinned) {
-      VLOG(1) << "Item is already pinned, ignoring in space calculation";
+      VLOG(2) << "Item is already pinned, ignoring in space calculation";
       continue;
     }
     size_required_ += item->metadata->size;
@@ -202,6 +225,11 @@
     return;
   }
 
+  if (!search_query_.is_bound()) {
+    Complete(PinError::kErrorSearchQueryNotBound);
+    return;
+  }
+
   search_query_->GetNextPage(
       base::BindOnce(&DriveFsPinManager::OnSearchResultForSizeCalculation,
                      weak_ptr_factory_.GetWeakPtr()));
@@ -212,7 +240,9 @@
   search_query_.reset();
   free_space_ = 0;
   size_required_ = 0;
-  std::move(complete_callback_).Run(status);
+  if (complete_callback_) {
+    std::move(complete_callback_).Run(status);
+  }
 }
 
 void DriveFsPinManager::StartBatchPinning() {
@@ -225,6 +255,14 @@
   search_query_->GetNextPage(
       base::BindOnce(&DriveFsPinManager::OnSearchResultsForPinning,
                      weak_ptr_factory_.GetWeakPtr()));
+
+  // Start a periodic task that removes any files that are already available
+  // offline from the `in_progress_items_` map.
+  base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
+      FROM_HERE,
+      base::BindOnce(&DriveFsPinManager::PeriodicallyRemovePinnedItems,
+                     weak_ptr_factory_.GetWeakPtr()),
+      kPeriodicRemovalInterval);
 }
 
 void DriveFsPinManager::OnSearchResultsForPinning(
@@ -245,6 +283,7 @@
   if (items.value().size() == 0) {
     VLOG(1) << "Finished pinning all files in "
             << timer_.Elapsed().InMilliseconds() << "ms";
+    setup_complete_ = true;
     Complete(PinError::kSuccess);
     return;
   }
@@ -260,6 +299,10 @@
                              });
 
   if (unpinned_items == 0) {
+    if (!search_query_.is_bound()) {
+      Complete(PinError::kErrorSearchQueryNotBound);
+      return;
+    }
     VLOG(1) << "All items in current batch are already pinned";
     search_query_->GetNextPage(
         base::BindOnce(&DriveFsPinManager::OnSearchResultsForPinning,
@@ -269,7 +312,7 @@
 
   for (const auto& item : items.value()) {
     if (item->metadata->pinned) {
-      VLOG(1) << "Item is already pinned, ignoring when batch pinning";
+      VLOG(2) << "Item is already pinned, ignoring when batch pinning";
       continue;
     }
     base::FilePath path(item->path);
@@ -284,7 +327,7 @@
                                      drive::FileError status) {
   if (status != drive::FILE_ERROR_OK) {
     LOG(ERROR) << "Failed pinning an item: " << status;
-    VLOG(2) << "Path that failed to pin: " << path << " with error "
+    VLOG(1) << "Path that failed to pin: " << path << " with error "
             << drive::FileErrorToString(status);
     Complete(PinError::kErrorFailedToPinItem);
     return;
@@ -295,6 +338,10 @@
 
 void DriveFsPinManager::OnSyncingStatusUpdate(
     const mojom::SyncingStatus& status) {
+  if (!enabled_ || setup_complete_) {
+    return;
+  }
+
   for (const auto& item : status.item_events) {
     auto cloned_item = item.Clone();
     // TODO(b/259454320): Hosted files (e.g. gdoc) do not send an update via the
@@ -317,6 +364,11 @@
 }
 
 void DriveFsPinManager::MaybeStartSearch(size_t remaining_items) {
+  if (!search_query_.is_bound()) {
+    Complete(PinError::kErrorSearchQueryNotBound);
+    return;
+  }
+
   if (remaining_items == 0) {
     search_query_->GetNextPage(
         base::BindOnce(&DriveFsPinManager::OnSearchResultsForPinning,
@@ -324,4 +376,50 @@
   }
 }
 
+void DriveFsPinManager::PeriodicallyRemovePinnedItems() {
+  VLOG(1) << "Periodically removing pinned items";
+
+  syncing_items_.AsyncCall(&InProgressSyncingItems::GetUnstartedItems)
+      .Then(base::BindOnce(&DriveFsPinManager::GetMetadata,
+                           weak_ptr_factory_.GetWeakPtr()));
+
+  base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
+      FROM_HERE,
+      base::BindOnce(&DriveFsPinManager::PeriodicallyRemovePinnedItems,
+                     weak_ptr_factory_.GetWeakPtr()),
+      kPeriodicRemovalInterval);
+}
+
+void DriveFsPinManager::GetMetadata(
+    const std::vector<std::string> unstarted_paths) {
+  for (const auto& path : unstarted_paths) {
+    base::FilePath file_path(path);
+    drivefs_interface_->GetMetadata(
+        file_path,
+        base::BindOnce(&DriveFsPinManager::OnMetadataRetrieved,
+                       weak_ptr_factory_.GetWeakPtr(), file_path.value()));
+  }
+
+  syncing_items_.AsyncCall(&InProgressSyncingItems::GetItemCount)
+      .Then(base::BindOnce(&DriveFsPinManager::MaybeStartSearch,
+                           weak_ptr_factory_.GetWeakPtr()));
+}
+
+void DriveFsPinManager::OnMetadataRetrieved(const std::string path,
+                                            drive::FileError error,
+                                            mojom::FileMetadataPtr metadata) {
+  if (error != drive::FILE_ERROR_OK) {
+    LOG(ERROR) << "Failed to retrieve metadata: " << error;
+    return;
+  }
+
+  if (metadata->available_offline || metadata->size == 0) {
+    VLOG(2) << "File " << path
+            << " has already been pinned or is a 0 byte file, removing from in "
+               "progress items";
+    syncing_items_.AsyncCall(&InProgressSyncingItems::RemoveItem)
+        .WithArgs(std::move(path));
+  }
+}
+
 }  // namespace drivefs::pinning
diff --git a/chromeos/ash/components/drivefs/drivefs_pin_manager.h b/chromeos/ash/components/drivefs/drivefs_pin_manager.h
index e5cd0ef..69621ee 100644
--- a/chromeos/ash/components/drivefs/drivefs_pin_manager.h
+++ b/chromeos/ash/components/drivefs/drivefs_pin_manager.h
@@ -13,6 +13,7 @@
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/sequence_checker.h"
+#include "base/task/sequenced_task_runner.h"
 #include "base/thread_annotations.h"
 #include "base/threading/sequence_bound.h"
 #include "base/timer/elapsed_timer.h"
@@ -27,6 +28,10 @@
 // Constant representing the GCache folder name.
 extern const char kGCacheFolderName[];
 
+// The periodic removal task is ran to ensure any leftover items in the syncing
+// map are identified as being `available_offline` or 0 byte files.
+extern const base::TimeDelta kPeriodicRemovalInterval;
+
 // Errors that are returned via the completion callback that indicate either
 // which stage the failure was at or whether the initial setup was a success.
 enum class PinError {
@@ -39,6 +44,8 @@
   kErrorRetrievingSearchResultsForPinning = 6,
   kErrorResultsReturnedInvalidForPinning = 7,
   kErrorFailedToPinItem = 8,
+  kErrorSearchQueryNotBound = 9,
+  kErrorManagerStopped = 10,
 };
 
 // A delegate to aid in mocking the free disk scenarios for testing, in non-test
@@ -91,6 +98,9 @@
   // pinning has completed.
   void Start(base::OnceCallback<void(PinError)> complete_callback);
 
+  // Stop the syncing setup.
+  void Stop();
+
   // drivefs::DriveFsHostObserver
   void OnSyncingStatusUpdate(const mojom::SyncingStatus& status) override;
 
@@ -122,6 +132,10 @@
     // Return the number of items currently being tracked as in progress.
     size_t GetItemCount();
 
+    // Returns any items that have 0 `bytes_to_transfer` which corresponds to
+    // items that haven't received a syncing status update.
+    std::vector<std::string> GetUnstartedItems();
+
    private:
     SEQUENCE_CHECKER(sequence_checker_);
     // A map that tracks the in progress items by their key to a pair of
@@ -160,10 +174,30 @@
   // `OnSyncingStatusUpdate`.
   void OnFilePinned(const std::string& path, drive::FileError status);
 
+  // Invoked at a regular interval to look at the map of in progress items and
+  // ensure they are all still not available offline (i.e. still syncing). In
+  // certain cases (e.g. hosted docs like gdocs) they will not emit a syncing
+  // status update but will get pinned.
+  void PeriodicallyRemovePinnedItems();
+
+  // For any paths that are in the unstarted phase (i.e. no `bytes_to_transfer`
+  // registered), the metadata must be retrieved to verify their
+  // `available_offline` boolean is true OR the size is 0.
+  void GetMetadata(const std::vector<std::string> unstarted_paths);
+  void OnMetadataRetrieved(const std::string path,
+                           drive::FileError error,
+                           mojom::FileMetadataPtr metadata);
+
   // If there are no remaining items left, get the next search query page.
   void MaybeStartSearch(size_t remaining_items);
 
+  // Denotes whether the feature is enabled. If the feature is disabled no setup
+  // nor monitoring occurs.
   bool enabled_ = false;
+
+  // Denotes whether the initial setup has finished. The feature must be enabled
+  // for this to be used.
+  bool setup_complete_ = false;
   int64_t size_required_ = 0;
   int64_t free_space_ = 0;
   base::OnceCallback<void(PinError)> complete_callback_;
diff --git a/chromeos/ash/components/drivefs/drivefs_pin_manager_unittest.cc b/chromeos/ash/components/drivefs/drivefs_pin_manager_unittest.cc
index 07d44ad..2c326b5c 100644
--- a/chromeos/ash/components/drivefs/drivefs_pin_manager_unittest.cc
+++ b/chromeos/ash/components/drivefs/drivefs_pin_manager_unittest.cc
@@ -13,8 +13,8 @@
 #include "base/test/gmock_callback_support.h"
 #include "base/test/mock_callback.h"
 #include "base/test/task_environment.h"
-#include "chromeos/ash/components/drivefs/mojom/drivefs.mojom-forward.h"
 #include "chromeos/ash/components/drivefs/mojom/drivefs.mojom-test-utils.h"
+#include "chromeos/ash/components/drivefs/mojom/drivefs.mojom.h"
 #include "components/drive/file_errors.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -27,6 +27,7 @@
 using ::base::test::RunClosure;
 using ::base::test::RunOnceCallback;
 using ::testing::_;
+using ::testing::AnyNumber;
 using ::testing::DoAll;
 using ::testing::Return;
 
@@ -36,6 +37,9 @@
   int64_t size;
   base::FilePath path;
   bool pinned;
+  // Whether to send a status update for this drive item, if false this will get
+  // filtered out when converting `DriveItem` in `CreateSyncingStatusUpdate`.
+  bool status_update = true;
 };
 
 // An action that takes a `std::vector<DriveItem>` and is used to update the
@@ -103,6 +107,13 @@
                base::OnceCallback<void(drive::FileError)>),
               (override));
 
+  MOCK_METHOD(
+      void,
+      GetMetadata,
+      (const base::FilePath&,
+       base::OnceCallback<void(drive::FileError, mojom::FileMetadataPtr)>),
+      (override));
+
  private:
   mojo::Receiver<mojom::SearchQuery> search_receiver_{this};
 };
@@ -142,7 +153,7 @@
 
     std::vector<mojom::ItemEventPtr> item_events;
     for (const auto& item : items) {
-      if (item.pinned) {
+      if (item.pinned || !item.status_update) {
         continue;
       }
       mojom::ItemEventPtr item_event = mojom::ItemEvent::New();
@@ -163,7 +174,16 @@
     }
   }
 
-  base::test::TaskEnvironment task_environment_;
+  mojom::FileMetadataPtr CreateFileMetadataItem(bool available_offline,
+                                                int64_t size) {
+    auto metadata_item = mojom::FileMetadata::New();
+    metadata_item->available_offline = available_offline;
+    metadata_item->size = size;
+    return metadata_item;
+  }
+
+  base::test::TaskEnvironment task_environment_{
+      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
   base::ScopedTempDir temp_dir_;
   base::FilePath gcache_dir_;
   MockDriveFs mock_drivefs_;
@@ -325,6 +345,14 @@
       {.size = 128, .path = base::FilePath("/b")},
       {.size = 128, .path = base::FilePath("/c"), .pinned = true}};
 
+  // The `PeriodicallyRemoveUnpinnedItems` will get ran when the task queue is
+  // idle so ensure the `GetMetadata` call returns values that enable the flow
+  // to continue.
+  EXPECT_CALL(mock_drivefs_, GetMetadata(_, _))
+      .Times(AnyNumber())
+      .WillRepeatedly(RunOnceCallback<1>(drive::FILE_ERROR_OK,
+                                         CreateFileMetadataItem(false, 128)));
+
   EXPECT_CALL(mock_drivefs_, OnStartSearchQuery(_)).Times(2);
   EXPECT_CALL(mock_drivefs_, OnGetNextPage(_))
       // Results returned whilst calculating free disk space.
@@ -383,6 +411,80 @@
   new_run_loop.Run();
 }
 
+TEST_F(
+    DriveFsPinManagerTest,
+    ZeroByteItemsAndHostedItemsShouldBePeriodicallyCleanedFromTheInProgressMap) {
+  base::MockOnceCallback<void(PinError)> mock_callback;
+  auto mock_free_disk_space = std::make_unique<MockFreeDiskSpaceImpl>();
+
+  base::RunLoop run_loop;
+
+  base::FilePath gdoc_path("/a.gdoc");
+  std::vector<DriveItem> expected_drive_items = {
+      // The `a.gdoc` file will never receive an `OnSyncingStatusUpdate` and
+      // thus needs to be removed via the periodic removal task.
+      {.size = 0, .path = gdoc_path, .status_update = false},
+      {.size = 128, .path = base::FilePath("/b")}};
+
+  EXPECT_CALL(mock_drivefs_, OnStartSearchQuery(_)).Times(2);
+  EXPECT_CALL(mock_drivefs_, OnGetNextPage(_))
+      // Results returned whilst calculating free disk space.
+      .WillOnce(DoAll(PopulateSearchItems(expected_drive_items),
+                      Return(drive::FileError::FILE_ERROR_OK)))
+      .WillOnce(DoAll(PopulateNoSearchItems(),
+                      Return(drive::FileError::FILE_ERROR_OK)))
+      // Results returned when actually performing the pinning, the final
+      // response (i.e. PopulateNoSearchItems()) happens after the
+      // `OnSyncingStatusUpdate` instead.
+      .WillOnce(DoAll(PopulateSearchItems(expected_drive_items),
+                      Return(drive::FileError::FILE_ERROR_OK)));
+  EXPECT_CALL(*mock_free_disk_space, AmountOfFreeDiskSpace(gcache_dir_, _))
+      .WillOnce(RunOnceCallback<1>(1024));  // 1 MB.
+  EXPECT_CALL(mock_drivefs_, SetPinned(_, true, _))
+      .Times(2)
+      .WillOnce(RunOnceCallback<2>(drive::FILE_ERROR_OK))
+      // `RunOnceCallback` can't be chained together in a `DoAll` action
+      // combinator, so use an inline lambda instead.
+      .WillOnce(
+          [&run_loop](const base::FilePath& path, bool pinned,
+                      base::OnceCallback<void(drive::FileError)> callback) {
+            std::move(callback).Run(drive::FILE_ERROR_OK);
+            run_loop.QuitClosure().Run();
+          });
+
+  auto manager = std::make_unique<DriveFsPinManager>(
+      /*enabled=*/true, temp_dir_.GetPath(), &mock_drivefs_,
+      std::move(mock_free_disk_space));
+  manager->Start(mock_callback.Get());
+  run_loop.Run();
+
+  // Create the syncing status update and emit the update to the manager.
+  mojom::SyncingStatusPtr status =
+      CreateSyncingStatusUpdate(expected_drive_items);
+  manager->OnSyncingStatusUpdate(*status);
+
+  // Flipping all the events to `kCompleted` will not start the next search
+  // query as the `a.gdoc` file is still remaining in the syncing items. As the
+  // task environment was started with a mock time, the `base::Runloop` will
+  // execute all tasks then automatically advance the clock until the periodic
+  // removal task is executed, cleaning the "a.gdoc" file.
+  base::RunLoop new_run_loop;
+  EXPECT_CALL(mock_drivefs_, GetMetadata(gdoc_path, _))
+      // Mock the first file to be available offline with a 0 size.
+      .WillOnce(RunOnceCallback<1>(
+          drive::FILE_ERROR_OK,
+          CreateFileMetadataItem(/*available_offline=*/true, /*size=*/0)));
+  EXPECT_CALL(mock_drivefs_, OnGetNextPage(_))
+      .WillOnce(DoAll(PopulateNoSearchItems(),
+                      Return(drive::FileError::FILE_ERROR_OK)));
+  EXPECT_CALL(mock_callback, Run(PinError::kSuccess))
+      .WillOnce(RunClosure(new_run_loop.QuitClosure()));
+  ChangeAllItemEventsToState(status->item_events,
+                             mojom::ItemEvent::State::kCompleted);
+  manager->OnSyncingStatusUpdate(*status);
+  new_run_loop.Run();
+}
+
 }  // namespace
 
 }  // namespace drivefs::pinning
diff --git a/chromeos/ash/components/geolocation/simple_geolocation_unittest.cc b/chromeos/ash/components/geolocation/simple_geolocation_unittest.cc
index e4307dc..84a813c 100644
--- a/chromeos/ash/components/geolocation/simple_geolocation_unittest.cc
+++ b/chromeos/ash/components/geolocation/simple_geolocation_unittest.cc
@@ -12,6 +12,7 @@
 #include "base/strings/stringprintf.h"
 #include "base/test/task_environment.h"
 #include "base/time/time.h"
+#include "base/values.h"
 #include "chromeos/ash/components/dbus/shill/shill_manager_client.h"
 #include "chromeos/ash/components/geolocation/simple_geolocation_provider.h"
 #include "chromeos/ash/components/geolocation/simple_geolocation_request_test_monitor.h"
@@ -343,14 +344,14 @@
 
   // This should remain in sync with the format of shill (chromeos) dict entries
   void AddAccessPoint(int idx) {
-    base::DictionaryValue properties;
     std::string mac_address =
         base::StringPrintf("%02X:%02X:%02X:%02X:%02X:%02X", idx, 0, 0, 0, 0, 0);
     std::string channel = base::NumberToString(idx);
     std::string strength = base::NumberToString(idx * 10);
-    properties.SetKey(shill::kGeoMacAddressProperty, base::Value(mac_address));
-    properties.SetKey(shill::kGeoChannelProperty, base::Value(channel));
-    properties.SetKey(shill::kGeoSignalStrengthProperty, base::Value(strength));
+    base::Value::Dict properties;
+    properties.Set(shill::kGeoMacAddressProperty, mac_address);
+    properties.Set(shill::kGeoChannelProperty, channel);
+    properties.Set(shill::kGeoSignalStrengthProperty, strength);
     manager_test_->AddGeoNetwork(shill::kGeoWifiAccessPointsProperty,
                                  properties);
     base::RunLoop().RunUntilIdle();
@@ -358,16 +359,16 @@
 
   // This should remain in sync with the format of shill (chromeos) dict entries
   void AddCellTower(int idx) {
-    base::DictionaryValue properties;
     std::string ci = base::NumberToString(idx);
     std::string lac = base::NumberToString(idx * 3);
     std::string mcc = base::NumberToString(idx * 100);
     std::string mnc = base::NumberToString(idx * 100 + 1);
 
-    properties.SetKey(shill::kGeoCellIdProperty, base::Value(ci));
-    properties.SetKey(shill::kGeoLocationAreaCodeProperty, base::Value(lac));
-    properties.SetKey(shill::kGeoMobileCountryCodeProperty, base::Value(mcc));
-    properties.SetKey(shill::kGeoMobileNetworkCodeProperty, base::Value(mnc));
+    base::Value::Dict properties;
+    properties.Set(shill::kGeoCellIdProperty, ci);
+    properties.Set(shill::kGeoLocationAreaCodeProperty, lac);
+    properties.Set(shill::kGeoMobileCountryCodeProperty, mcc);
+    properties.Set(shill::kGeoMobileNetworkCodeProperty, mnc);
 
     manager_test_->AddGeoNetwork(shill::kGeoCellTowersProperty, properties);
     base::RunLoop().RunUntilIdle();
diff --git a/chromeos/ash/components/hid_detection/hid_detection_manager_impl.cc b/chromeos/ash/components/hid_detection/hid_detection_manager_impl.cc
index adc271f..96dccb95 100644
--- a/chromeos/ash/components/hid_detection/hid_detection_manager_impl.cc
+++ b/chromeos/ash/components/hid_detection/hid_detection_manager_impl.cc
@@ -15,15 +15,20 @@
 using BluetoothHidType = BluetoothHidDetector::BluetoothHidType;
 using InputState = HidDetectionManager::InputState;
 
-// Global InputDeviceManagerBinder instance that can be overridden in tests.
-base::NoDestructor<HidDetectionManagerImpl::InputDeviceManagerBinder>
-    g_input_device_manager_binder;
+HidDetectionManagerImpl::InputDeviceManagerBinder&
+GetInputDeviceManagerBinderOverride() {
+  // InputDeviceManagerBinder instance that can be overridden in tests.
+  static base::NoDestructor<HidDetectionManagerImpl::InputDeviceManagerBinder>
+      binder;
+  return *binder;
+}
+
 }  // namespace
 
 // static
 void HidDetectionManagerImpl::SetInputDeviceManagerBinderForTest(
     InputDeviceManagerBinder binder) {
-  *g_input_device_manager_binder = std::move(binder);
+  GetInputDeviceManagerBinderOverride() = std::move(binder);
 }
 
 HidDetectionManagerImpl::HidDetectionManagerImpl(
@@ -144,8 +149,9 @@
 
   mojo::PendingReceiver<device::mojom::InputDeviceManager> receiver =
       input_device_manager_.BindNewPipeAndPassReceiver();
-  if (*g_input_device_manager_binder) {
-    g_input_device_manager_binder->Run(std::move(receiver));
+  const auto& binder = GetInputDeviceManagerBinderOverride();
+  if (binder) {
+    binder.Run(std::move(receiver));
     return;
   }
 
diff --git a/chromeos/ash/components/network/geolocation_handler.cc b/chromeos/ash/components/network/geolocation_handler.cc
index d342c9f..d96cd276 100644
--- a/chromeos/ash/components/network/geolocation_handler.cc
+++ b/chromeos/ash/components/network/geolocation_handler.cc
@@ -10,7 +10,6 @@
 #include "base/bind.h"
 #include "base/logging.h"
 #include "base/strings/string_number_conversions.h"
-#include "base/values.h"
 #include "chromeos/ash/components/dbus/shill/shill_manager_client.h"
 #include "third_party/cros_system_api/dbus/service_constants.h"
 
@@ -145,14 +144,14 @@
 }
 
 void GeolocationHandler::GeolocationCallback(
-    absl::optional<base::Value> properties) {
-  if (!properties || !properties->is_dict()) {
+    absl::optional<base::Value::Dict> properties) {
+  if (!properties) {
     LOG(ERROR) << "Failed to get Geolocation data";
     return;
   }
   wifi_access_points_.clear();
   cell_towers_.clear();
-  if (properties->DictEmpty())
+  if (properties->empty())
     return;  // No enabled devices, don't update received time.
 
   // Dictionary<device_type, entry_list>
@@ -164,7 +163,7 @@
   //   kGeoCellTowersProperty: [ {kGeoCellIdProperty: cell_id_value, ...}, ... ]
   // }
   for (auto* device_type : kDevicePropertyNames) {
-    const base::Value* entry_list = properties->FindKey(device_type);
+    const base::Value* entry_list = properties->Find(device_type);
     if (!entry_list) {
       continue;
     }
diff --git a/chromeos/ash/components/network/geolocation_handler.h b/chromeos/ash/components/network/geolocation_handler.h
index 6bd8c966..dc1bafa6 100644
--- a/chromeos/ash/components/network/geolocation_handler.h
+++ b/chromeos/ash/components/network/geolocation_handler.h
@@ -78,7 +78,7 @@
   void RequestGeolocationObjects();
 
   // Callback for receiving Geolocation data.
-  void GeolocationCallback(absl::optional<base::Value> properties);
+  void GeolocationCallback(absl::optional<base::Value::Dict> properties);
 
   bool cellular_enabled_;
   bool wifi_enabled_;
diff --git a/chromeos/ash/components/network/geolocation_handler_unittest.cc b/chromeos/ash/components/network/geolocation_handler_unittest.cc
index beb3293d..7de2899 100644
--- a/chromeos/ash/components/network/geolocation_handler_unittest.cc
+++ b/chromeos/ash/components/network/geolocation_handler_unittest.cc
@@ -56,15 +56,15 @@
   // This should remain in sync with the format of shill (chromeos) dict entries
   // Shill provides us Cell ID and LAC in hex, but all other fields in decimal.
   void AddAccessPoint(int idx) {
-    base::Value properties(base::Value::Type::DICTIONARY);
     std::string mac_address =
         base::StringPrintf("%02X:%02X:%02X:%02X:%02X:%02X",
                            idx, 0, 0, 0, 0, 0);
     std::string channel = base::NumberToString(idx);
     std::string strength = base::NumberToString(idx * 10);
-    properties.SetKey(shill::kGeoMacAddressProperty, base::Value(mac_address));
-    properties.SetKey(shill::kGeoChannelProperty, base::Value(channel));
-    properties.SetKey(shill::kGeoSignalStrengthProperty, base::Value(strength));
+    base::Value::Dict properties;
+    properties.Set(shill::kGeoMacAddressProperty, mac_address);
+    properties.Set(shill::kGeoChannelProperty, channel);
+    properties.Set(shill::kGeoSignalStrengthProperty, strength);
     manager_test_->AddGeoNetwork(shill::kGeoWifiAccessPointsProperty,
                                  properties);
     base::RunLoop().RunUntilIdle();
@@ -72,7 +72,6 @@
 
   // This should remain in sync with the format of shill (chromeos) dict entries
   void AddCellTower(int idx) {
-    base::Value properties(base::Value::Type::DICTIONARY);
     // Multiplications, additions, and string concatenations
     // are intended solely to differentiate the various fields
     // in a predictable way, while preserving 3 digits for MCC and MNC.
@@ -81,10 +80,11 @@
     std::string mcc = base::NumberToString(idx * 100);
     std::string mnc = base::NumberToString(idx * 100 + 1);
 
-    properties.SetKey(shill::kGeoCellIdProperty, base::Value(ci));
-    properties.SetKey(shill::kGeoLocationAreaCodeProperty, base::Value(lac));
-    properties.SetKey(shill::kGeoMobileCountryCodeProperty, base::Value(mcc));
-    properties.SetKey(shill::kGeoMobileNetworkCodeProperty, base::Value(mnc));
+    base::Value::Dict properties;
+    properties.Set(shill::kGeoCellIdProperty, ci);
+    properties.Set(shill::kGeoLocationAreaCodeProperty, lac);
+    properties.Set(shill::kGeoMobileCountryCodeProperty, mcc);
+    properties.Set(shill::kGeoMobileNetworkCodeProperty, mnc);
 
     manager_test_->AddGeoNetwork(shill::kGeoCellTowersProperty, properties);
     base::RunLoop().RunUntilIdle();
diff --git a/chromeos/ash/components/string_matching/README.md b/chromeos/ash/components/string_matching/README.md
index 884b88b..9a10bda 100644
--- a/chromeos/ash/components/string_matching/README.md
+++ b/chromeos/ash/components/string_matching/README.md
@@ -4,7 +4,7 @@
 similarity scores between two given strings.
 
 This library's main use is within the launcher backend ranking system
-(`chrome/browser/ui/app_list/search/`).
+(`chrome/browser/ash/app_list/search/`).
 
 The entry points to this library are via either:
 
diff --git a/chromeos/ash/services/auth_factor_config/BUILD.gn b/chromeos/ash/services/auth_factor_config/BUILD.gn
index de2f707..1fc52c59 100644
--- a/chromeos/ash/services/auth_factor_config/BUILD.gn
+++ b/chromeos/ash/services/auth_factor_config/BUILD.gn
@@ -12,13 +12,16 @@
     "auth_factor_config.h",
     "in_process_instances.cc",
     "in_process_instances.h",
+    "quick_unlock_storage_delegate.h",
     "recovery_factor_editor.cc",
     "recovery_factor_editor.h",
   ]
 
   deps = [
     "//ash/constants",
-    "//components/prefs:prefs",
+    "//chromeos/ash/components/login/auth",
+    "//components/prefs",
+    "//components/user_manager",
   ]
 
   public_deps = [
diff --git a/chromeos/ash/services/auth_factor_config/DEPS b/chromeos/ash/services/auth_factor_config/DEPS
new file mode 100644
index 0000000..455e2dd
--- /dev/null
+++ b/chromeos/ash/services/auth_factor_config/DEPS
@@ -0,0 +1,5 @@
+include_rules = [
+  "+components/user_manager",
+  "+components/user_manager",
+  "+chromeos/ash/components/login/auth",
+]
diff --git a/chromeos/ash/services/auth_factor_config/auth_factor_config.cc b/chromeos/ash/services/auth_factor_config/auth_factor_config.cc
index 3f2a38a..ee5bbf39 100644
--- a/chromeos/ash/services/auth_factor_config/auth_factor_config.cc
+++ b/chromeos/ash/services/auth_factor_config/auth_factor_config.cc
@@ -6,11 +6,15 @@
 
 #include "ash/constants/ash_features.h"
 #include "ash/constants/ash_pref_names.h"
-#include "components/prefs/pref_registry_simple.h"
+#include "components/user_manager/user_manager.h"
 
 namespace ash::auth {
 
-AuthFactorConfig::AuthFactorConfig() = default;
+AuthFactorConfig::AuthFactorConfig(
+    QuickUnlockStorageDelegate* quick_unlock_storage)
+    : quick_unlock_storage_(quick_unlock_storage) {
+  DCHECK(quick_unlock_storage_);
+}
 
 AuthFactorConfig::~AuthFactorConfig() = default;
 
@@ -44,15 +48,27 @@
 void AuthFactorConfig::IsConfigured(const std::string& auth_token,
                                     mojom::AuthFactor factor,
                                     base::OnceCallback<void(bool)> callback) {
-  if (!features::IsCryptohomeRecoverySetupEnabled()) {
-    // Log always, crash on debug builds.
-    LOG(ERROR) << "AuthFactorConfig::IsConfigured is a fake";
+  DCHECK(features::IsCryptohomeRecoverySetupEnabled());
+
+  if (factor != mojom::AuthFactor::kRecovery) {
+    LOG(ERROR) << "AuthFactorConfig::IsConfigured supports recovery only";
     NOTIMPLEMENTED();
     std::move(callback).Run(false);
     return;
   }
 
-  std::move(callback).Run(recovery_configured_);
+  const auto* user = ::user_manager::UserManager::Get()->GetPrimaryUser();
+  auto* user_context = quick_unlock_storage_->GetUserContext(user, auth_token);
+  if (!user_context) {
+    LOG(ERROR) << "Invalid auth token";
+    std::move(callback).Run(false);
+    return;
+  }
+
+  const auto& config = user_context->GetAuthFactorsConfiguration();
+  const bool is_configured =
+      config.HasConfiguredFactor(cryptohome::AuthFactorType::kRecovery);
+  std::move(callback).Run(is_configured);
 }
 
 void AuthFactorConfig::GetManagementType(
diff --git a/chromeos/ash/services/auth_factor_config/auth_factor_config.h b/chromeos/ash/services/auth_factor_config/auth_factor_config.h
index d1734c2f..5ffb625b 100644
--- a/chromeos/ash/services/auth_factor_config/auth_factor_config.h
+++ b/chromeos/ash/services/auth_factor_config/auth_factor_config.h
@@ -6,22 +6,19 @@
 #define CHROMEOS_ASH_SERVICES_AUTH_FACTOR_CONFIG_AUTH_FACTOR_CONFIG_H_
 
 #include "chromeos/ash/services/auth_factor_config/public/mojom/auth_factor_config.mojom.h"
+#include "chromeos/ash/services/auth_factor_config/quick_unlock_storage_delegate.h"
 #include "mojo/public/cpp/bindings/receiver_set.h"
 #include "mojo/public/cpp/bindings/remote_set.h"
 
-class PrefRegistrySimple;
+#include "components/prefs/pref_registry_simple.h"
+#include "components/user_manager/user.h"
 
 namespace ash::auth {
 
 // The implementation of the AuthFactorConfig service.
-// TODO(crbug.com/1327627): This will eventually communicate with cryptohome,
-// but it is currently a fake and only maintains a boolean corresponding to the
-// current state. No changes are sent to cryptohome. The fake reports that
-// cryptohome recovery is supported only if the CryptohomeRecoverySetup feature
-// flag is enabled.
 class AuthFactorConfig : public mojom::AuthFactorConfig {
  public:
-  AuthFactorConfig();
+  explicit AuthFactorConfig(QuickUnlockStorageDelegate*);
   ~AuthFactorConfig() override;
 
   AuthFactorConfig(const AuthFactorConfig&) = delete;
@@ -47,13 +44,12 @@
                   mojom::AuthFactor factor,
                   base::OnceCallback<void(bool)>) override;
 
- private:
-  friend class RecoveryFactorEditor;
-
+  // Reload auth factor data from cryptohome and notify factor change
+  // observers of the change.
   void NotifyFactorObservers(mojom::AuthFactor changed_factor);
 
-  bool recovery_configured_ = false;
-
+ private:
+  raw_ptr<QuickUnlockStorageDelegate> quick_unlock_storage_;
   mojo::ReceiverSet<mojom::AuthFactorConfig> receivers_;
   mojo::RemoteSet<mojom::FactorObserver> observers_;
 };
diff --git a/chromeos/ash/services/auth_factor_config/in_process_instances.cc b/chromeos/ash/services/auth_factor_config/in_process_instances.cc
index 7fefc85..686bdaa 100644
--- a/chromeos/ash/services/auth_factor_config/in_process_instances.cc
+++ b/chromeos/ash/services/auth_factor_config/in_process_instances.cc
@@ -15,36 +15,41 @@
 
 namespace {
 
-// TODO(crbug.com/1327627): We probably want to initialize these later: Not on
-// chrome startup but when a user logs in. Optionally we could delay
-// initialization to first use (static member of a getter) or even create new
-// instances of the services for each webui that consumes them.
-base::NoDestructor<AuthFactorConfig> auth_factor_config;
-base::NoDestructor<RecoveryFactorEditor> recovery_factor_editor(
-    auth_factor_config.get());
+AuthFactorConfig& GetAuthFactorConfigImpl(
+    QuickUnlockStorageDelegate& delegate) {
+  static base::NoDestructor<AuthFactorConfig> auth_factor_config{&delegate};
+  return *auth_factor_config;
+}
+
+RecoveryFactorEditor& GetRecoveryFactorEditorImpl(
+    QuickUnlockStorageDelegate& delegate) {
+  static base::NoDestructor<RecoveryFactorEditor> recovery_factor_editor(
+      &GetAuthFactorConfigImpl(delegate), &delegate);
+  return *recovery_factor_editor;
+}
 
 }  // namespace
 
 void BindToAuthFactorConfig(
-    mojo::PendingReceiver<mojom::AuthFactorConfig> receiver) {
-  auth_factor_config->BindReceiver(std::move(receiver));
+    mojo::PendingReceiver<mojom::AuthFactorConfig> receiver,
+    QuickUnlockStorageDelegate& delegate) {
+  GetAuthFactorConfigImpl(delegate).BindReceiver(std::move(receiver));
 }
 
-mojom::AuthFactorConfigAsyncWaiter GetAuthFactorConfigForTesting() {
-  return mojom::AuthFactorConfigAsyncWaiter(auth_factor_config.get());
+mojom::AuthFactorConfig& GetAuthFactorConfig(
+    QuickUnlockStorageDelegate& delegate) {
+  return GetAuthFactorConfigImpl(delegate);
 }
 
 void BindToRecoveryFactorEditor(
-    mojo::PendingReceiver<mojom::RecoveryFactorEditor> receiver) {
-  recovery_factor_editor->BindReceiver(std::move(receiver));
+    mojo::PendingReceiver<mojom::RecoveryFactorEditor> receiver,
+    QuickUnlockStorageDelegate& delegate) {
+  GetRecoveryFactorEditorImpl(delegate).BindReceiver(std::move(receiver));
 }
 
-mojom::RecoveryFactorEditorAsyncWaiter GetRecoveryFactorEditorForTesting() {
-  return mojom::RecoveryFactorEditorAsyncWaiter(recovery_factor_editor.get());
-}
-
-mojom::RecoveryFactorEditor& GetRecoveryFactorEditor() {
-  return *recovery_factor_editor.get();
+mojom::RecoveryFactorEditor& GetRecoveryFactorEditor(
+    QuickUnlockStorageDelegate& delegate) {
+  return GetRecoveryFactorEditorImpl(delegate);
 }
 
 }  // namespace ash::auth
diff --git a/chromeos/ash/services/auth_factor_config/in_process_instances.h b/chromeos/ash/services/auth_factor_config/in_process_instances.h
index 5bae0d56..d90fe86 100644
--- a/chromeos/ash/services/auth_factor_config/in_process_instances.h
+++ b/chromeos/ash/services/auth_factor_config/in_process_instances.h
@@ -6,28 +6,31 @@
 #define CHROMEOS_ASH_SERVICES_AUTH_FACTOR_CONFIG_IN_PROCESS_INSTANCES_H_
 
 #include "chromeos/ash/services/auth_factor_config/public/mojom/auth_factor_config.mojom-forward.h"
+#include "chromeos/ash/services/auth_factor_config/quick_unlock_storage_delegate.h"
 #include "chromeos/ash/services/auth_factor_config/recovery_factor_editor.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 
 // This file contains functions to bind mojo clients for the auth factor config
 // related services to server implementations. The server implementations are
 // singletons defined in the .cc file.
+//
+// Until the QuickUnlockPrivate API is removed, the mojo services provided here
+// need to interact nicely with QuickUnlockPrivate internals. Because of DEPS
+// issues, a delegate to these internals must be provided whenever one of the
+// global mojo service instances is accessed.
 
 namespace ash::auth {
 
-namespace mojom {
-class AuthFactorConfigAsyncWaiter;
-class RecoveryFactorEditorAsyncWaiter;
-}  // namespace mojom
-
 void BindToAuthFactorConfig(
-    mojo::PendingReceiver<mojom::AuthFactorConfig> receiver);
-mojom::AuthFactorConfigAsyncWaiter GetAuthFactorConfigForTesting();
+    mojo::PendingReceiver<mojom::AuthFactorConfig> receiver,
+    QuickUnlockStorageDelegate&);
+mojom::AuthFactorConfig& GetAuthFactorConfig(QuickUnlockStorageDelegate&);
 
 void BindToRecoveryFactorEditor(
-    mojo::PendingReceiver<mojom::RecoveryFactorEditor> receiver);
-mojom::RecoveryFactorEditorAsyncWaiter GetRecoveryFactorEditorForTesting();
-mojom::RecoveryFactorEditor& GetRecoveryFactorEditor();
+    mojo::PendingReceiver<mojom::RecoveryFactorEditor> receiver,
+    QuickUnlockStorageDelegate&);
+mojom::RecoveryFactorEditor& GetRecoveryFactorEditor(
+    QuickUnlockStorageDelegate&);
 
 }  // namespace ash::auth
 
diff --git a/chromeos/ash/services/auth_factor_config/quick_unlock_storage_delegate.h b/chromeos/ash/services/auth_factor_config/quick_unlock_storage_delegate.h
new file mode 100644
index 0000000..580b032f
--- /dev/null
+++ b/chromeos/ash/services/auth_factor_config/quick_unlock_storage_delegate.h
@@ -0,0 +1,31 @@
+// 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 CHROMEOS_ASH_SERVICES_AUTH_FACTOR_CONFIG_QUICK_UNLOCK_STORAGE_DELEGATE_H_
+#define CHROMEOS_ASH_SERVICES_AUTH_FACTOR_CONFIG_QUICK_UNLOCK_STORAGE_DELEGATE_H_
+
+#include <memory>
+#include <string>
+#include "chromeos/ash/components/login/auth/public/user_context.h"
+#include "components/user_manager/user.h"
+
+namespace ash::auth {
+
+class QuickUnlockStorageDelegate {
+ public:
+  QuickUnlockStorageDelegate() = default;
+  QuickUnlockStorageDelegate(const QuickUnlockStorageDelegate&) = delete;
+  QuickUnlockStorageDelegate& operator=(const QuickUnlockStorageDelegate&) =
+      delete;
+  virtual ~QuickUnlockStorageDelegate() = default;
+
+  virtual UserContext* GetUserContext(const ::user_manager::User* user,
+                                      const std::string& token) = 0;
+  virtual void SetUserContext(const ::user_manager::User* user,
+                              std::unique_ptr<UserContext> user_context) = 0;
+};
+
+}  // namespace ash::auth
+
+#endif  // CHROMEOS_ASH_SERVICES_AUTH_FACTOR_CONFIG_QUICK_UNLOCK_STORAGE_DELEGATE_H_
diff --git a/chromeos/ash/services/auth_factor_config/recovery_factor_editor.cc b/chromeos/ash/services/auth_factor_config/recovery_factor_editor.cc
index b109b6c5..73f190a 100644
--- a/chromeos/ash/services/auth_factor_config/recovery_factor_editor.cc
+++ b/chromeos/ash/services/auth_factor_config/recovery_factor_editor.cc
@@ -5,11 +5,18 @@
 #include "chromeos/ash/services/auth_factor_config/recovery_factor_editor.h"
 #include "ash/constants/ash_features.h"
 #include "chromeos/ash/services/auth_factor_config/auth_factor_config.h"
+#include "components/user_manager/user_manager.h"
 
 namespace ash::auth {
 
-RecoveryFactorEditor::RecoveryFactorEditor(AuthFactorConfig* auth_factor_config)
-    : auth_factor_config_(auth_factor_config) {}
+RecoveryFactorEditor::RecoveryFactorEditor(
+    AuthFactorConfig* auth_factor_config,
+    QuickUnlockStorageDelegate* quick_unlock_storage)
+    : auth_factor_config_(auth_factor_config),
+      quick_unlock_storage_(quick_unlock_storage) {
+  DCHECK(auth_factor_config_);
+  DCHECK(quick_unlock_storage_);
+}
 RecoveryFactorEditor::~RecoveryFactorEditor() = default;
 
 void RecoveryFactorEditor::BindReceiver(
@@ -21,15 +28,72 @@
     const std::string& auth_token,
     bool enabled,
     base::OnceCallback<void(ConfigureResult)> callback) {
-  if (!features::IsCryptohomeRecoverySetupEnabled()) {
-    // Log always, crash on debug builds.
-    LOG(ERROR) << "AuthFactorConfig::Configure is a fake";
-    NOTIMPLEMENTED();
+  DCHECK(features::IsCryptohomeRecoverySetupEnabled());
+
+  const auto* user = ::user_manager::UserManager::Get()->GetPrimaryUser();
+  auto* user_context_ptr =
+      quick_unlock_storage_->GetUserContext(user, auth_token);
+  if (user_context_ptr == nullptr) {
+    LOG(ERROR) << "Invalid auth token";
+    std::move(callback).Run(ConfigureResult::kInvalidTokenError);
+    return;
+  }
+
+  const bool currently_enabled =
+      user_context_ptr->GetAuthFactorsConfiguration().HasConfiguredFactor(
+          cryptohome::AuthFactorType::kRecovery);
+
+  if (enabled == currently_enabled) {
+    std::move(callback).Run(ConfigureResult::kSuccess);
+    return;
+  }
+
+  auto user_context = std::make_unique<UserContext>(*user_context_ptr);
+
+  auto on_configured_callback =
+      base::BindOnce(&RecoveryFactorEditor::OnRecoveryFactorConfigured,
+                     weak_factory_.GetWeakPtr(), std::move(callback));
+
+  if (enabled) {
+    auth_factor_editor_.AddRecoveryFactor(std::move(user_context),
+                                          std::move(on_configured_callback));
+  } else {
+    auth_factor_editor_.RemoveRecoveryFactor(std::move(user_context),
+                                             std::move(on_configured_callback));
+  }
+}
+
+void RecoveryFactorEditor::OnRecoveryFactorConfigured(
+    base::OnceCallback<void(ConfigureResult)> callback,
+    std::unique_ptr<UserContext> context,
+    absl::optional<AuthenticationError> error) {
+  if (error.has_value()) {
+    LOG(ERROR) << "Configuring recovery factor failed, code "
+               << error->get_cryptohome_code();
     std::move(callback).Run(ConfigureResult::kClientError);
     return;
   }
 
-  auth_factor_config_->recovery_configured_ = enabled;
+  auth_factor_editor_.GetAuthFactorsConfiguration(
+      std::move(context),
+      base::BindOnce(&RecoveryFactorEditor::OnGetAuthFactorsConfiguration,
+                     weak_factory_.GetWeakPtr(), std::move(callback)));
+}
+
+void RecoveryFactorEditor::OnGetAuthFactorsConfiguration(
+    base::OnceCallback<void(ConfigureResult)> callback,
+    std::unique_ptr<UserContext> context,
+    absl::optional<AuthenticationError> error) {
+  if (error.has_value()) {
+    LOG(ERROR) << "Refreshing list of configured auth factors failed, code "
+               << error->get_cryptohome_code();
+    std::move(callback).Run(ConfigureResult::kClientError);
+    return;
+  }
+
+  const auto* user = ::user_manager::UserManager::Get()->GetPrimaryUser();
+  quick_unlock_storage_->SetUserContext(user, std::move(context));
+
   std::move(callback).Run(ConfigureResult::kSuccess);
   auth_factor_config_->NotifyFactorObservers(mojom::AuthFactor::kRecovery);
 }
diff --git a/chromeos/ash/services/auth_factor_config/recovery_factor_editor.h b/chromeos/ash/services/auth_factor_config/recovery_factor_editor.h
index fb1a72b55..6576129 100644
--- a/chromeos/ash/services/auth_factor_config/recovery_factor_editor.h
+++ b/chromeos/ash/services/auth_factor_config/recovery_factor_editor.h
@@ -5,7 +5,10 @@
 #ifndef CHROMEOS_ASH_SERVICES_AUTH_FACTOR_CONFIG_RECOVERY_FACTOR_EDITOR_H_
 #define CHROMEOS_ASH_SERVICES_AUTH_FACTOR_CONFIG_RECOVERY_FACTOR_EDITOR_H_
 
+#include "chromeos/ash/components/login/auth/auth_factor_editor.h"
+#include "chromeos/ash/components/login/auth/public/authentication_error.h"
 #include "chromeos/ash/services/auth_factor_config/public/mojom/auth_factor_config.mojom.h"
+#include "chromeos/ash/services/auth_factor_config/quick_unlock_storage_delegate.h"
 #include "mojo/public/cpp/bindings/receiver_set.h"
 
 namespace ash::auth {
@@ -13,17 +16,12 @@
 class AuthFactorConfig;
 
 // The implementation of the RecoveryFactorEditor service.
-// TODO(crbug.com/1327627): This is currently a fake and only flips a boolean
-// corresponding to the current state. No changes are sent to cryptohome.
-// Clients may use this API only if the CryptohomeRecoverySetup feature flag is
-// enabled.
 class RecoveryFactorEditor : public mojom::RecoveryFactorEditor {
  public:
-  explicit RecoveryFactorEditor(AuthFactorConfig*);
-  ~RecoveryFactorEditor() override;
-
+  explicit RecoveryFactorEditor(AuthFactorConfig*, QuickUnlockStorageDelegate*);
   RecoveryFactorEditor(const RecoveryFactorEditor&) = delete;
   RecoveryFactorEditor& operator=(const RecoveryFactorEditor&) = delete;
+  ~RecoveryFactorEditor() override;
 
   void BindReceiver(
       mojo::PendingReceiver<mojom::RecoveryFactorEditor> receiver);
@@ -33,8 +31,21 @@
                  base::OnceCallback<void(ConfigureResult)>) override;
 
  private:
+  void OnRecoveryFactorConfigured(
+      base::OnceCallback<void(ConfigureResult)> callback,
+      std::unique_ptr<UserContext> context,
+      absl::optional<AuthenticationError> error);
+
+  void OnGetAuthFactorsConfiguration(
+      base::OnceCallback<void(ConfigureResult)> callback,
+      std::unique_ptr<UserContext> context,
+      absl::optional<AuthenticationError> error);
+
   raw_ptr<AuthFactorConfig> auth_factor_config_;
+  raw_ptr<QuickUnlockStorageDelegate> quick_unlock_storage_;
+  AuthFactorEditor auth_factor_editor_;
   mojo::ReceiverSet<mojom::RecoveryFactorEditor> receivers_;
+  base::WeakPtrFactory<RecoveryFactorEditor> weak_factory_{this};
 };
 
 }  // namespace ash::auth
diff --git a/chromeos/ash/services/cros_healthd/public/cpp/fake_cros_healthd.cc b/chromeos/ash/services/cros_healthd/public/cpp/fake_cros_healthd.cc
index 8ea30787..d963b1f 100644
--- a/chromeos/ash/services/cros_healthd/public/cpp/fake_cros_healthd.cc
+++ b/chromeos/ash/services/cros_healthd/public/cpp/fake_cros_healthd.cc
@@ -538,8 +538,10 @@
 }
 
 void FakeCrosHealthd::RunSmartctlCheckRoutine(
+    mojom::NullableUint32Ptr percentage_used_threshold,
     RunSmartctlCheckRoutineCallback callback) {
-  last_run_routine_ = mojom::DiagnosticRoutineEnum::kSmartctlCheck;
+  last_run_routine_ =
+      mojom::DiagnosticRoutineEnum::kSmartctlCheckWithPercentageUsed;
   base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
       FROM_HERE,
       base::BindOnce(std::move(callback), run_routine_response_.Clone()),
diff --git a/chromeos/ash/services/cros_healthd/public/cpp/fake_cros_healthd.h b/chromeos/ash/services/cros_healthd/public/cpp/fake_cros_healthd.h
index 32d0e43..fb3c020 100644
--- a/chromeos/ash/services/cros_healthd/public/cpp/fake_cros_healthd.h
+++ b/chromeos/ash/services/cros_healthd/public/cpp/fake_cros_healthd.h
@@ -259,6 +259,7 @@
   void RunBatteryHealthRoutine(
       RunBatteryHealthRoutineCallback callback) override;
   void RunSmartctlCheckRoutine(
+      mojom::NullableUint32Ptr percentage_used_threshold,
       RunSmartctlCheckRoutineCallback callback) override;
   void RunAcPowerRoutine(mojom::AcPowerStatusEnum expected_status,
                          const absl::optional<std::string>& expected_power_type,
diff --git a/chromeos/ash/services/cros_healthd/public/mojom/cros_healthd.mojom b/chromeos/ash/services/cros_healthd/public/mojom/cros_healthd.mojom
index 0fe38b5..6ac9255 100644
--- a/chromeos/ash/services/cros_healthd/public/mojom/cros_healthd.mojom
+++ b/chromeos/ash/services/cros_healthd/public/mojom/cros_healthd.mojom
@@ -54,7 +54,7 @@
 
 // Diagnostics interface exposed by the cros_healthd daemon.
 //
-// NextMinVersion: 6, NextIndex: 38
+// NextMinVersion: 7, NextIndex: 38
 [Stable]
 interface CrosHealthdDiagnosticsService {
   // Returns an array of all diagnostic routines that the platform supports.
@@ -127,15 +127,29 @@
   RunBatteryHealthRoutine@4() => (RunRoutineResponse response);
 
   // Requests that the SmartctlCheck routine is created and started on the
-  // platform. This routine checks available spare NVMe capacity against the
-  // threshold.
+  // platform. This routine passes iff a NVMe drive's:
+  // 1. Critical Warning == 0x00 (no warning)
+  // 2. Available Spare >= Available Spare Threshold
+  // 3. Percentage Used <= Percentage Used Threshold (from request)
+  // In addition, above values are also returned in the output.
   // The availability of this routine can be determined by checking that
-  // kSmartctlCheck is returned by GetAvailableRoutines.
+  // kSmartctlCheck (without |percentage_used_threshold|) or
+  // kSmartctlCheckWithPercentageUsed (with |percentage_used_threshold|) is
+  // returned by GetAvailableRoutines.
+  //
+  // The request:
+  // * |percentage_used_threshold| - an optional threshold number in percentage,
+  //                                 range [0, 255] inclusive, that the routine
+  //                                 examines `percentage_used` against. If not
+  //                                 specified, the routine will default to the
+  //                                 max allowed value (255).
   //
   // The response:
   // * |response| - contains a unique identifier and status for the created
   //                routine.
-  RunSmartctlCheckRoutine@5() => (RunRoutineResponse response);
+  RunSmartctlCheckRoutine@5(
+    [MinVersion=6] NullableUint32? percentage_used_threshold)
+        => (RunRoutineResponse response);
 
   // Requests that the AcPower routine is created and started on the
   // platform. This routine checks the status of the power supply, and if
diff --git a/chromeos/ash/services/cros_healthd/public/mojom/cros_healthd_diagnostics.mojom b/chromeos/ash/services/cros_healthd/public/mojom/cros_healthd_diagnostics.mojom
index 6f0b9b46..20b073d 100644
--- a/chromeos/ash/services/cros_healthd/public/mojom/cros_healthd_diagnostics.mojom
+++ b/chromeos/ash/services/cros_healthd/public/mojom/cros_healthd_diagnostics.mojom
@@ -15,7 +15,7 @@
 
 // Enumeration of each of the diagnostics routines the platform may support.
 //
-// NextMinVersion: 5, NextIndex: 36
+// NextMinVersion: 6, NextIndex: 37
 [Stable, Extensible]
 enum DiagnosticRoutineEnum {
   [Default] kUnknown = 30,
@@ -54,6 +54,7 @@
   [MinVersion=2] kFingerprintAlive = 33,
   [MinVersion=3] kPrivacyScreen = 34,
   [MinVersion=4] kLedLitUp = 35,
+  [MinVersion=5] kSmartctlCheckWithPercentageUsed = 36,
 };
 
 // Enumeration of the possible DiskRead routine's command type
diff --git a/chromeos/chromeos_strings.grd b/chromeos/chromeos_strings.grd
index bee591ca..9177ffe8 100644
--- a/chromeos/chromeos_strings.grd
+++ b/chromeos/chromeos_strings.grd
@@ -3708,6 +3708,9 @@
       <message name="IDS_SETTINGS_VIEW_APN_DIALOG_TITLE" desc="Title for the dialog for viewing an APN">
         APN details
       </message>
+      <message name="IDS_SETTINGS_EDIT_APN_DIALOG_TITLE" desc="Title for the dialog for viewing an APN">
+        View and edit details
+      </message>
       <message name="IDS_SETTINGS_APN_INPUT_LABEL" desc="Label for the input field where the APN is specified, the asterisk indicates that this is required">
         APN*
       </message>
diff --git a/chromeos/chromeos_strings_grd/IDS_SETTINGS_EDIT_APN_DIALOG_TITLE.png.sha1 b/chromeos/chromeos_strings_grd/IDS_SETTINGS_EDIT_APN_DIALOG_TITLE.png.sha1
new file mode 100644
index 0000000..fcbd805
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_SETTINGS_EDIT_APN_DIALOG_TITLE.png.sha1
@@ -0,0 +1 @@
+3f97842ac2063eb27cee4fb709ea86bab09c036b
\ No newline at end of file
diff --git a/chromeos/constants/chromeos_features.cc b/chromeos/constants/chromeos_features.cc
index 209dd604..3529d32 100644
--- a/chromeos/constants/chromeos_features.cc
+++ b/chromeos/constants/chromeos_features.cc
@@ -28,7 +28,7 @@
 BASE_FEATURE(kDarkLightMode, "DarkLightMode", base::FEATURE_ENABLED_BY_DEFAULT);
 
 // Enables Demo Mode System Web App migration
-BASE_FEATURE(kDemoModeSWA, "DemoModeSWA", base::FEATURE_DISABLED_BY_DEFAULT);
+BASE_FEATURE(kDemoModeSWA, "DemoModeSWA", base::FEATURE_ENABLED_BY_DEFAULT);
 
 // Disable idle sockets closing on memory pressure for NetworkContexts that
 // belong to Profiles. It only applies to Profiles because the goal is to
diff --git a/chromeos/crosapi/mojom/diagnostics_service.mojom b/chromeos/crosapi/mojom/diagnostics_service.mojom
index 6f443fb..f16cff7 100644
--- a/chromeos/crosapi/mojom/diagnostics_service.mojom
+++ b/chromeos/crosapi/mojom/diagnostics_service.mojom
@@ -14,8 +14,11 @@
 
 module crosapi.mojom;
 
+import "chromeos/crosapi/mojom/nullable_primitives.mojom";
+
 // Interface for exposing diagnostics service. Implemented in ash-chrome.
 //
+// Next version: 2
 // Next ID: 23
 [Stable, Uuid="14bc6194-c059-4048-9bea-ca6823eeda82",
 RenamedFrom="ash.health.mojom.DiagnosticsService"]
@@ -71,15 +74,28 @@
   RunBatteryHealthRoutine@3() => (DiagnosticsRunRoutineResponse response);
 
   // Requests that the SmartctlCheck routine is created and started on the
-  // platform. This routine checks available spare NVMe capacity against the
-  // threshold.
+  // platform. This routine passes iff a NVMe drive's:
+  // 1. Critical Warning == 0x00 (no warning)
+  // 2. Available Spare >= Available Spare Threshold
+  // 3. Percentage Used <= Percentage Used Threshold (from request)
+  // In addition, above values are also returned in the output.
   // The availability of this routine can be determined by checking that
-  // kSmartctlCheck is returned by GetAvailableRoutines.
+  // kSmartctlCheck (without |percentage_used_threshold|) or
+  // kSmartctlCheckWithPercentageUsed (with |percentage_used_threshold|) is
+  // returned by GetAvailableRoutines.
+  //
+  // The request:
+  // * |percentage_used_threshold| - an optional threshold number in percentage,
+  //                                 range [0, 255] inclusive, that the routine
+  //                                 examines `percentage_used` against. If not
+  //                                 specified, the routine will default to the
+  //                                 max allowed value (255).
   //
   // The response:
   // * |response| - contains a unique identifier and status for the created
   //                routine.
-  RunSmartctlCheckRoutine@4() => (DiagnosticsRunRoutineResponse response);
+  RunSmartctlCheckRoutine@4([MinVersion=1] UInt32Value?
+      percentage_used_threshold) => (DiagnosticsRunRoutineResponse response);
 
   // Requests that the AcPower routine is created and started on the
   // platform. This routine checks the status of the power supply, and if
@@ -360,7 +376,7 @@
 
 // Enumeration of each of the diagnostics routines the platform may support.
 //
-// Next ID: 22
+// Next ID: 23
 [Stable, Extensible, RenamedFrom="ash.health.mojom.DiagnosticRoutineEnum"]
 enum DiagnosticsRoutineEnum {
   [Default] kUnknown = 15,
@@ -385,6 +401,7 @@
   kDnsResolverPresent = 19,
   kSensitiveSensor = 20,
   kFingerprintAlive = 21,
+  kSmartctlCheckWithPercentageUsed = 22,
 };
 
 // Enumeration of each of the possible statuses for a diagnostics routine.
diff --git a/chromeos/crosapi/mojom/file_system_provider.mojom b/chromeos/crosapi/mojom/file_system_provider.mojom
index 494cc85b..afdd1703 100644
--- a/chromeos/crosapi/mojom/file_system_provider.mojom
+++ b/chromeos/crosapi/mojom/file_system_provider.mojom
@@ -122,8 +122,7 @@
 // Implemented by Lacros.
 [Stable, Uuid="8cbcf151-c189-4fe9-90ff-892c7f5637a9"]
 interface FileSystemProvider {
-  // Allows Ash file system to forward an operation to Lacros extension
-  // providers.
+  // Allows Ash to forward requests to Lacros extension providers.
   // We use mojo_base.mojom.Value as the type because:
   //   * The extension API is stable.
   //   * Both the supplier and consumer of this information uses a type
@@ -210,6 +209,16 @@
       FileSystemId file_system_id, int64 request_id,
       mojo_base.mojom.ListValue args) => (string error);
 
+  // The following function represents responses from the Extension after
+  // handling a mount request. As opposed to `OperationFinished`, these
+  // requests are handled per-provider instead of per provided file system
+  // (no `FileSystemId).
+  // |request_id| corresponds to ids passed in via methods on interface
+  // FileSystemProvider.
+  [MinVersion=4]
+  MountFinished@10(string extension_id, int64 request_id,
+      mojo_base.mojom.ListValue args) => (string error);
+
   // Called by Lacros when a filesystem providing extension is loaded.
   [MinVersion=2]
   ExtensionLoaded@7(bool configurable, bool watchable, bool multiple_mounts,
diff --git a/chromeos/process_proxy/process_output_watcher_unittest.cc b/chromeos/process_proxy/process_output_watcher_unittest.cc
index 4c9bad23..d9e62331 100644
--- a/chromeos/process_proxy/process_output_watcher_unittest.cc
+++ b/chromeos/process_proxy/process_output_watcher_unittest.cc
@@ -295,7 +295,8 @@
   RunTest(test_cases);
 }
 
-TEST_F(ProcessOutputWatcherTest, InvalidUTF8SeriesOfTrailingBytes) {
+// TODO(crbug.com/1399698): Re-enable this test
+TEST_F(ProcessOutputWatcherTest, DISABLED_InvalidUTF8SeriesOfTrailingBytes) {
   std::vector<TestCase> test_cases;
   test_cases.push_back(TestCase("\x82\x82\x82", false, "\x82\x82\x82"));
   test_cases.push_back(TestCase("\x82\x82\x82", false, "\x82\x82\x82"));
diff --git a/chromeos/profiles/arm.afdo.newest.txt b/chromeos/profiles/arm.afdo.newest.txt
index f1be48e9..8613382 100644
--- a/chromeos/profiles/arm.afdo.newest.txt
+++ b/chromeos/profiles/arm.afdo.newest.txt
@@ -1 +1 @@
-chromeos-chrome-arm-none-110-5447.0-1670239482-benchmark-110.0.5460.0-r1-redacted.afdo.xz
+chromeos-chrome-arm-none-110-5447.0-1670239482-benchmark-110.0.5464.0-r1-redacted.afdo.xz
diff --git a/chromeos/services/tts/tts_player.cc b/chromeos/services/tts/tts_player.cc
index a6c1efca..4a9650d 100644
--- a/chromeos/services/tts/tts_player.cc
+++ b/chromeos/services/tts/tts_player.cc
@@ -55,7 +55,7 @@
 
 int TtsPlayer::Render(base::TimeDelta delay,
                       base::TimeTicks delay_timestamp,
-                      int prior_frames_skipped,
+                      const media::AudioGlitchInfo& glitch_info,
                       media::AudioBus* dest) {
   size_t frames_in_buf = 0;
   {
diff --git a/chromeos/services/tts/tts_player.h b/chromeos/services/tts/tts_player.h
index 38ed860b..6bd7e19 100644
--- a/chromeos/services/tts/tts_player.h
+++ b/chromeos/services/tts/tts_player.h
@@ -55,7 +55,7 @@
   // media::AudioRendererSink::RenderCallback:
   int Render(base::TimeDelta delay,
              base::TimeTicks delay_timestamp,
-             int prior_frames_skipped,
+             const media::AudioGlitchInfo& glitch_info,
              media::AudioBus* dest) override;
   void OnRenderError() override;
 
diff --git a/chromeos/services/tts/tts_service_unittest.cc b/chromeos/services/tts/tts_service_unittest.cc
index 204ffc7..f1b201f 100644
--- a/chromeos/services/tts/tts_service_unittest.cc
+++ b/chromeos/services/tts/tts_service_unittest.cc
@@ -8,6 +8,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/task_environment.h"
 #include "chromeos/services/tts/public/mojom/tts_service.mojom.h"
+#include "media/base/audio_glitch_info.h"
 #include "media/mojo/mojom/audio_data_pipe.mojom.h"
 #include "media/mojo/mojom/audio_stream_factory.mojom.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
@@ -165,7 +166,7 @@
 
   auto bus = media::AudioBus::Create(1 /* channels */, 512 /* frames */);
   service_.playback_tts_stream_for_testing()->tts_player_for_testing()->Render(
-      base::Seconds(0), base::TimeTicks::Now(), 0 /* prior frames skipped */,
+      base::Seconds(0), base::TimeTicks::Now(), {} /* glitch info */,
       bus.get());
   observer.FlushForTesting();
 
@@ -178,7 +179,7 @@
       std::vector<float>(), 100 /* char_index */, false /* last buffer */);
   playback_tts_stream.FlushForTesting();
   service_.playback_tts_stream_for_testing()->tts_player_for_testing()->Render(
-      base::Seconds(0), base::TimeTicks::Now(), 0 /* prior frames skipped */,
+      base::Seconds(0), base::TimeTicks::Now(), {} /* glitch info */,
       bus.get());
   observer.FlushForTesting();
   EXPECT_EQ(1, backing_observer.start_count);
@@ -192,7 +193,7 @@
       std::vector<float>(), 9999 /* char_index */, true /* last buffer */);
   playback_tts_stream.FlushForTesting();
   service_.playback_tts_stream_for_testing()->tts_player_for_testing()->Render(
-      base::Seconds(0), base::TimeTicks::Now(), 0 /* prior frames skipped */,
+      base::Seconds(0), base::TimeTicks::Now(), {} /* glitch info */,
       bus.get());
   observer.FlushForTesting();
   EXPECT_EQ(1, backing_observer.start_count);
@@ -216,7 +217,7 @@
 
   auto bus = media::AudioBus::Create(1 /* channels */, 512 /* frames */);
   service_.playback_tts_stream_for_testing()->tts_player_for_testing()->Render(
-      base::Seconds(0), base::TimeTicks::Now(), 0 /* prior frames skipped */,
+      base::Seconds(0), base::TimeTicks::Now(), {} /* glitch info */,
       bus.get());
   observer.FlushForTesting();
 
@@ -229,7 +230,7 @@
       std::vector<float>(), -1 /* char_index */, false /* last buffer */);
   playback_tts_stream.FlushForTesting();
   service_.playback_tts_stream_for_testing()->tts_player_for_testing()->Render(
-      base::Seconds(0), base::TimeTicks::Now(), 0 /* prior frames skipped */,
+      base::Seconds(0), base::TimeTicks::Now(), {} /* glitch info */,
       bus.get());
   observer.FlushForTesting();
   EXPECT_EQ(1, backing_observer.start_count);
@@ -249,7 +250,7 @@
       ->AddExplicitTimepoint(300, base::Seconds(0));
   playback_tts_stream.FlushForTesting();
   service_.playback_tts_stream_for_testing()->tts_player_for_testing()->Render(
-      base::Seconds(0), base::TimeTicks::Now(), 0 /* prior frames skipped */,
+      base::Seconds(0), base::TimeTicks::Now(), {} /* glitch info */,
       bus.get());
   observer.FlushForTesting();
   EXPECT_EQ(1, backing_observer.start_count);
@@ -263,7 +264,7 @@
       std::vector<float>(), 9999 /* char_index */, true /* last buffer */);
   playback_tts_stream.FlushForTesting();
   service_.playback_tts_stream_for_testing()->tts_player_for_testing()->Render(
-      base::Seconds(0), base::TimeTicks::Now(), 0 /* prior frames skipped */,
+      base::Seconds(0), base::TimeTicks::Now(), {} /* glitch info */,
       bus.get());
   observer.FlushForTesting();
   EXPECT_EQ(1, backing_observer.start_count);
diff --git a/chromeos/system/factory_ping_embargo_check.cc b/chromeos/system/factory_ping_embargo_check.cc
index e0a62837d..7039c5a6 100644
--- a/chromeos/system/factory_ping_embargo_check.cc
+++ b/chromeos/system/factory_ping_embargo_check.cc
@@ -40,13 +40,13 @@
     StatisticsProvider* statistics_provider,
     const std::string& key_name,
     const char* uma_prefix) {
-  std::string ping_embargo_end_date;
-  if (!statistics_provider->GetMachineStatistic(key_name,
-                                                &ping_embargo_end_date)) {
+  const absl::optional<base::StringPiece> ping_embargo_end_date =
+      statistics_provider->GetMachineStatistic(key_name);
+  if (!ping_embargo_end_date) {
     return FactoryPingEmbargoState::kMissingOrMalformed;
   }
   base::Time parsed_time;
-  if (!base::Time::FromUTCString(ping_embargo_end_date.c_str(), &parsed_time)) {
+  if (!base::Time::FromUTCString(ping_embargo_end_date->data(), &parsed_time)) {
     LOG(ERROR) << key_name << " exists but cannot be parsed.";
     RecordEndDateValidity(uma_prefix,
                           EndDateValidityHistogramValue::kMalformed);
@@ -69,9 +69,8 @@
 
 FactoryPingEmbargoState GetEnterpriseManagementPingEmbargoState(
     StatisticsProvider* statistics_provider) {
-  std::string ping_embargo_end_date;
   if (statistics_provider->GetMachineStatistic(
-          kEnterpriseManagementEmbargoEndDateKey, &ping_embargo_end_date))
+          kEnterpriseManagementEmbargoEndDateKey))
     return GetPingEmbargoState(statistics_provider,
                                kEnterpriseManagementEmbargoEndDateKey,
                                "FactoryPingEmbargo");
diff --git a/chromeos/ui/frame/BUILD.gn b/chromeos/ui/frame/BUILD.gn
index 0af26efd..eb25fbe 100644
--- a/chromeos/ui/frame/BUILD.gn
+++ b/chromeos/ui/frame/BUILD.gn
@@ -56,6 +56,8 @@
     "multitask_menu/multitask_menu.cc",
     "multitask_menu/multitask_menu.h",
     "multitask_menu/multitask_menu_constants.h",
+    "multitask_menu/multitask_menu_metrics.cc",
+    "multitask_menu/multitask_menu_metrics.h",
     "multitask_menu/multitask_menu_view.cc",
     "multitask_menu/multitask_menu_view.h",
     "multitask_menu/split_button_view.cc",
diff --git a/chromeos/ui/frame/caption_buttons/frame_size_button.cc b/chromeos/ui/frame/caption_buttons/frame_size_button.cc
index 86da437..93c79df 100644
--- a/chromeos/ui/frame/caption_buttons/frame_size_button.cc
+++ b/chromeos/ui/frame/caption_buttons/frame_size_button.cc
@@ -8,12 +8,12 @@
 
 #include "base/i18n/rtl.h"
 #include "base/memory/raw_ptr.h"
-#include "base/metrics/histogram_functions.h"
 #include "base/metrics/user_metrics.h"
 #include "chromeos/ui/base/tablet_state.h"
 #include "chromeos/ui/base/window_properties.h"
 #include "chromeos/ui/frame/caption_buttons/snap_controller.h"
 #include "chromeos/ui/frame/frame_utils.h"
+#include "chromeos/ui/frame/multitask_menu/multitask_menu.h"
 #include "chromeos/ui/wm/features.h"
 #include "ui/aura/window.h"
 #include "ui/aura/window_observer.h"
@@ -87,11 +87,6 @@
   }
 }
 
-void RecordMultitaskMenuEntryType(MultitaskMenuEntryType entry_type) {
-  base::UmaHistogramEnumeration(MultitaskMenuView::GetEntryTypeHistogramName(),
-                                entry_type);
-}
-
 }  // namespace
 
 // This class controls animating a pie on a parent button which indicates when
diff --git a/chromeos/ui/frame/caption_buttons/frame_size_button.h b/chromeos/ui/frame/caption_buttons/frame_size_button.h
index 1b6a5a51..0747989 100644
--- a/chromeos/ui/frame/caption_buttons/frame_size_button.h
+++ b/chromeos/ui/frame/caption_buttons/frame_size_button.h
@@ -11,7 +11,7 @@
 #include "base/memory/raw_ptr.h"
 #include "base/timer/timer.h"
 #include "chromeos/ui/frame/caption_buttons/frame_size_button_delegate.h"
-#include "chromeos/ui/frame/multitask_menu/multitask_menu.h"
+#include "chromeos/ui/frame/multitask_menu/multitask_menu_metrics.h"
 #include "ui/base/metadata/metadata_header_macros.h"
 #include "ui/display/display_observer.h"
 #include "ui/views/window/frame_caption_button.h"
diff --git a/chromeos/ui/frame/multitask_menu/multitask_menu_metrics.cc b/chromeos/ui/frame/multitask_menu/multitask_menu_metrics.cc
new file mode 100644
index 0000000..ff1ff3bef
--- /dev/null
+++ b/chromeos/ui/frame/multitask_menu/multitask_menu_metrics.cc
@@ -0,0 +1,25 @@
+// 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 "chromeos/ui/frame/multitask_menu/multitask_menu_metrics.h"
+
+#include "base/metrics/histogram_functions.h"
+#include "chromeos/ui/base/tablet_state.h"
+
+namespace chromeos {
+
+constexpr char kEntryTypeHistogramNamePrefix[] =
+    "Ash.Float.MultitaskMenuEntryType";
+
+std::string GetEntryTypeHistogramName() {
+  return std::string(kEntryTypeHistogramNamePrefix)
+      .append(TabletState::Get()->InTabletMode() ? ".TabletMode"
+                                                 : ".ClamshellMode");
+}
+
+void RecordMultitaskMenuEntryType(MultitaskMenuEntryType entry_type) {
+  base::UmaHistogramEnumeration(GetEntryTypeHistogramName(), entry_type);
+}
+
+}  // namespace chromeos
diff --git a/chromeos/ui/frame/multitask_menu/multitask_menu_metrics.h b/chromeos/ui/frame/multitask_menu/multitask_menu_metrics.h
new file mode 100644
index 0000000..4aed3a3
--- /dev/null
+++ b/chromeos/ui/frame/multitask_menu/multitask_menu_metrics.h
@@ -0,0 +1,39 @@
+// 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 CHROMEOS_UI_FRAME_MULTITASK_MENU_MULTITASK_MENU_METRICS_H_
+#define CHROMEOS_UI_FRAME_MULTITASK_MENU_MULTITASK_MENU_METRICS_H_
+
+#include <string>
+
+namespace chromeos {
+
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused. Please keep in sync with
+// MultitaskMenuEntryType in /src/tools/metrics/histograms/enums.xml.
+enum class MultitaskMenuEntryType {
+  kFrameSizeButtonHover = 0,
+  kFrameSizeButtonLongPress = 1,
+  kFrameSizeButtonLongTouch = 2,
+  kMaxValue = kFrameSizeButtonLongTouch,
+};
+
+// Used to record when the user partial splits to one third.
+constexpr char kPartialSplitOneThirdUserAction[] =
+    "MultitaskMenu_PartialSplit_OneThird";
+
+// Used to record when the user partial splits to two thirds.
+constexpr char kPartialSplitTwoThirdsUserAction[] =
+    "MultitaskMenu_PartialSplit_TwoThirds";
+
+// Gets the proper histogram name based on whether the user is in tablet mode or
+// not.
+std::string GetEntryTypeHistogramName();
+
+// Records the method the user used to show the multitask menu.
+void RecordMultitaskMenuEntryType(MultitaskMenuEntryType entry_type);
+
+}  // namespace chromeos
+
+#endif  // CHROMEOS_UI_FRAME_MULTITASK_MENU_MULTITASK_MENU_METRICS_H_
diff --git a/chromeos/ui/frame/multitask_menu/multitask_menu_view.cc b/chromeos/ui/frame/multitask_menu/multitask_menu_view.cc
index 74bf08e..2b8d25d 100644
--- a/chromeos/ui/frame/multitask_menu/multitask_menu_view.cc
+++ b/chromeos/ui/frame/multitask_menu/multitask_menu_view.cc
@@ -8,15 +8,16 @@
 
 #include "base/callback_forward.h"
 #include "base/check.h"
+#include "base/metrics/user_metrics.h"
 #include "chromeos/strings/grit/chromeos_strings.h"
 #include "chromeos/ui/base/display_util.h"
-#include "chromeos/ui/base/tablet_state.h"
 #include "chromeos/ui/base/window_properties.h"
 #include "chromeos/ui/base/window_state_type.h"
 #include "chromeos/ui/frame/caption_buttons/snap_controller.h"
 #include "chromeos/ui/frame/frame_utils.h"
 #include "chromeos/ui/frame/multitask_menu/float_controller_base.h"
 #include "chromeos/ui/frame/multitask_menu/multitask_button.h"
+#include "chromeos/ui/frame/multitask_menu/multitask_menu_metrics.h"
 #include "chromeos/ui/frame/multitask_menu/split_button_view.h"
 #include "chromeos/ui/wm/features.h"
 #include "ui/aura/window.h"
@@ -132,13 +133,6 @@
 
 MultitaskMenuView::~MultitaskMenuView() = default;
 
-// static
-std::string MultitaskMenuView::GetEntryTypeHistogramName() {
-  return std::string(kMultitaskMenuEntryTypeHistogram)
-      .append(TabletState::Get()->InTabletMode() ? ".TabletMode"
-                                                 : ".ClamshellMode");
-}
-
 void MultitaskMenuView::SplitButtonPressed(bool left_top) {
   SnapController::Get()->CommitSnap(
       window_, GetSnapDirectionForWindow(window_, left_top), kDefaultSnapRatio);
@@ -155,6 +149,10 @@
                                         ? kTwoThirdSnapRatio
                                         : kOneThirdSnapRatio);
   on_any_button_pressed_.Run();
+
+  base::RecordAction(base::UserMetricsAction(
+      snap == SnapDirection::kPrimary ? kPartialSplitTwoThirdsUserAction
+                                      : kPartialSplitOneThirdUserAction));
 }
 
 void MultitaskMenuView::FullScreenButtonPressed() {
diff --git a/chromeos/ui/frame/multitask_menu/multitask_menu_view.h b/chromeos/ui/frame/multitask_menu/multitask_menu_view.h
index f8b0ef37..f22f56ecc 100644
--- a/chromeos/ui/frame/multitask_menu/multitask_menu_view.h
+++ b/chromeos/ui/frame/multitask_menu/multitask_menu_view.h
@@ -19,19 +19,6 @@
 class MultitaskButton;
 class SplitButtonView;
 
-constexpr char kMultitaskMenuEntryTypeHistogram[] =
-    "Ash.Float.MultitaskMenuEntryType";
-
-// These values are persisted to logs. Entries should not be renumbered and
-// numeric values should never be reused. Please keep in sync with
-// MultitaskMenuEntryType in /src/tools/metrics/histograms/enums.xml.
-enum class MultitaskMenuEntryType {
-  kFrameSizeButtonHover = 0,
-  kFrameSizeButtonLongPress = 1,
-  kFrameSizeButtonLongTouch = 2,
-  kMaxValue = kFrameSizeButtonLongTouch,
-};
-
 // Contains buttons which can fullscreen, snap, or float a window.
 class COMPONENT_EXPORT(CHROMEOS_UI_FRAME) MultitaskMenuView
     : public views::View {
@@ -55,8 +42,6 @@
 
   ~MultitaskMenuView() override;
 
-  static std::string GetEntryTypeHistogramName();
-
   // For testing.
   SplitButtonView* half_button_for_testing() {
     return half_button_for_testing_.get();
diff --git a/components/app_restore/full_restore_read_handler.cc b/components/app_restore/full_restore_read_handler.cc
index 338fc85..75c1dd5e 100644
--- a/components/app_restore/full_restore_read_handler.cc
+++ b/components/app_restore/full_restore_read_handler.cc
@@ -54,6 +54,10 @@
 void FullRestoreReadHandler::OnWindowInitialized(aura::Window* window) {
   int32_t window_id = window->GetProperty(app_restore::kRestoreWindowIdKey);
 
+  // Patch fix for (b/261765975).
+  if (window_id < app_restore::kParentToHiddenContainer)
+    return;
+
   if (app_restore::IsArcWindow(window)) {
     // If there isn't restore data for ARC apps, we don't need to handle ARC app
     // windows restoration.
diff --git a/components/attribution_reporting/source_registration.cc b/components/attribution_reporting/source_registration.cc
index c6fed01..de6e5fc 100644
--- a/components/attribution_reporting/source_registration.cc
+++ b/components/attribution_reporting/source_registration.cc
@@ -73,10 +73,8 @@
 
 SourceRegistration::SourceRegistration() = default;
 
-SourceRegistration::SourceRegistration(SuitableOrigin destination,
-                                       SuitableOrigin reporting_origin)
-    : destination(std::move(destination)),
-      reporting_origin(std::move(reporting_origin)) {}
+SourceRegistration::SourceRegistration(SuitableOrigin destination)
+    : destination(std::move(destination)) {}
 
 SourceRegistration::~SourceRegistration() = default;
 
@@ -92,14 +90,12 @@
 
 // static
 base::expected<SourceRegistration, SourceRegistrationError>
-SourceRegistration::Parse(base::Value::Dict registration,
-                          SuitableOrigin reporting_origin) {
+SourceRegistration::Parse(base::Value::Dict registration) {
   auto destination = ParseDestination(registration);
   if (!destination.has_value())
     return base::unexpected(destination.error());
 
-  SourceRegistration result(std::move(*destination),
-                            std::move(reporting_origin));
+  SourceRegistration result(std::move(*destination));
 
   base::expected<FilterData, SourceRegistrationError> filter_data =
       FilterData::FromJSON(registration.Find(kFilterData));
@@ -137,8 +133,7 @@
 
 // static
 base::expected<SourceRegistration, SourceRegistrationError>
-SourceRegistration::Parse(base::StringPiece json,
-                          SuitableOrigin reporting_origin) {
+SourceRegistration::Parse(base::StringPiece json) {
   absl::optional<base::Value> value =
       base::JSONReader::Read(json, base::JSON_PARSE_RFC);
   if (!value)
@@ -147,7 +142,7 @@
   if (!value->is_dict())
     return base::unexpected(SourceRegistrationError::kRootWrongType);
 
-  return Parse(std::move(*value).TakeDict(), std::move(reporting_origin));
+  return Parse(std::move(*value).TakeDict());
 }
 
 base::Value::Dict SourceRegistration::ToJson() const {
diff --git a/components/attribution_reporting/source_registration.h b/components/attribution_reporting/source_registration.h
index 881120ac..06e088b2 100644
--- a/components/attribution_reporting/source_registration.h
+++ b/components/attribution_reporting/source_registration.h
@@ -26,13 +26,12 @@
 
 struct COMPONENT_EXPORT(ATTRIBUTION_REPORTING) SourceRegistration {
   static base::expected<SourceRegistration, mojom::SourceRegistrationError>
-  Parse(base::Value::Dict, SuitableOrigin reporting_origin);
+      Parse(base::Value::Dict);
 
   static base::expected<SourceRegistration, mojom::SourceRegistrationError>
-  Parse(base::StringPiece json, SuitableOrigin reporting_origin);
+  Parse(base::StringPiece json);
 
-  SourceRegistration(SuitableOrigin destination,
-                     SuitableOrigin reporting_origin);
+  explicit SourceRegistration(SuitableOrigin destination);
 
   ~SourceRegistration();
 
@@ -46,7 +45,6 @@
 
   uint64_t source_event_id = 0;
   SuitableOrigin destination;
-  SuitableOrigin reporting_origin;
   absl::optional<base::TimeDelta> expiry;
   absl::optional<base::TimeDelta> event_report_window;
   absl::optional<base::TimeDelta> aggregatable_report_window;
diff --git a/components/attribution_reporting/source_registration_fuzzer.cc b/components/attribution_reporting/source_registration_fuzzer.cc
index 911e3d6..68c32ef 100644
--- a/components/attribution_reporting/source_registration_fuzzer.cc
+++ b/components/attribution_reporting/source_registration_fuzzer.cc
@@ -15,7 +15,6 @@
 #include "base/logging.h"
 #include "base/values.h"
 #include "components/attribution_reporting/source_registration.h"
-#include "components/attribution_reporting/suitable_origin.h"
 #include "testing/libfuzzer/proto/json.pb.h"
 #include "testing/libfuzzer/proto/json_proto_converter.h"
 #include "testing/libfuzzer/proto/lpm_interface.h"
@@ -49,10 +48,7 @@
   if (!input || !input->is_dict())
     return;
 
-  std::ignore = SourceRegistration::Parse(
-      std::move(*input).TakeDict(),
-      /*reporting_origin=*/
-      *SuitableOrigin::Deserialize("https://r.test/"));
+  std::ignore = SourceRegistration::Parse(std::move(*input).TakeDict());
 }
 
 }  // namespace attribution_reporting
diff --git a/components/attribution_reporting/source_registration_unittest.cc b/components/attribution_reporting/source_registration_unittest.cc
index f92cf2f..0edb88d 100644
--- a/components/attribution_reporting/source_registration_unittest.cc
+++ b/components/attribution_reporting/source_registration_unittest.cc
@@ -26,18 +26,13 @@
 using ::attribution_reporting::mojom::SourceRegistrationError;
 
 template <typename F>
-SourceRegistration SourceRegistrationWith(SuitableOrigin destination,
-                                          SuitableOrigin reporting_origin,
-                                          F&& f) {
-  SourceRegistration r(std::move(destination), std::move(reporting_origin));
+SourceRegistration SourceRegistrationWith(SuitableOrigin destination, F&& f) {
+  SourceRegistration r(std::move(destination));
   base::invoke<F, SourceRegistration&>(std::move(f), r);
   return r;
 }
 
 TEST(SourceRegistrationTest, Parse) {
-  const auto reporting_origin =
-      *SuitableOrigin::Deserialize("https://r.example");
-
   const auto destination_origin =
       *SuitableOrigin::Deserialize("https://d.example");
 
@@ -59,23 +54,23 @@
       {
           "required_fields_only",
           R"json({"destination":"https://d.example"})json",
-          SourceRegistration(destination_origin, reporting_origin),
+          SourceRegistration(destination_origin),
       },
       {
           "source_event_id_valid",
           R"json({"source_event_id":"1","destination":"https://d.example"})json",
-          SourceRegistrationWith(destination_origin, reporting_origin,
+          SourceRegistrationWith(destination_origin,
                                  [](auto& r) { r.source_event_id = 1; }),
       },
       {
           "source_event_id_wrong_type",
           R"json({"source_event_id":1,"destination":"https://d.example"})json",
-          SourceRegistration(destination_origin, reporting_origin),
+          SourceRegistration(destination_origin),
       },
       {
           "source_event_id_invalid_defaults_to_0",
           R"json({"source_event_id":"-1","destination":"https://d.example"})json",
-          SourceRegistration(destination_origin, reporting_origin),
+          SourceRegistration(destination_origin),
       },
       {
           "destination_missing",
@@ -95,41 +90,41 @@
       {
           "priority_valid",
           R"json({"priority":"-5","destination":"https://d.example"})json",
-          SourceRegistrationWith(destination_origin, reporting_origin,
+          SourceRegistrationWith(destination_origin,
                                  [](auto& r) { r.priority = -5; }),
       },
       {
           "priority_wrong_type_defaults_to_0",
           R"json({"priority":-5,"destination":"https://d.example"})json",
-          SourceRegistration(destination_origin, reporting_origin),
+          SourceRegistration(destination_origin),
       },
       {
           "priority_invalid_defaults_to_0",
           R"json({"priority":"abc","destination":"https://d.example"})json",
-          SourceRegistration(destination_origin, reporting_origin),
+          SourceRegistration(destination_origin),
       },
       {
           "expiry_valid",
           R"json({"expiry":"172801","destination":"https://d.example"})json",
           SourceRegistrationWith(
-              destination_origin, reporting_origin,
+              destination_origin,
               [](auto& r) { r.expiry = base::Seconds(172801); }),
       },
       {
           "expiry_wrong_type",
           R"json({"expiry":172800,"destination":"https://d.example"})json",
-          SourceRegistration(destination_origin, reporting_origin),
+          SourceRegistration(destination_origin),
       },
       {
           "expiry_invalid",
           R"json({"expiry":"abc","destination":"https://d.example"})json",
-          SourceRegistration(destination_origin, reporting_origin),
+          SourceRegistration(destination_origin),
       },
       {
           "event_report_window_valid",
           R"json({"expiry":"172801","event_report_window":"86401",
           "destination":"https://d.example"})json",
-          SourceRegistrationWith(destination_origin, reporting_origin,
+          SourceRegistrationWith(destination_origin,
                                  [](auto& r) {
                                    r.expiry = base::Seconds(172801);
                                    r.event_report_window = base::Seconds(86401);
@@ -140,7 +135,7 @@
           R"json({"expiry":"172801","event_report_window":86401,
           "destination":"https://d.example"})json",
           SourceRegistrationWith(
-              destination_origin, reporting_origin,
+              destination_origin,
               [](auto& r) { r.expiry = base::Seconds(172801); }),
       },
       {
@@ -148,14 +143,14 @@
           R"json({"expiry":"172801","event_report_window":"abc",
           "destination":"https://d.example"})json",
           SourceRegistrationWith(
-              destination_origin, reporting_origin,
+              destination_origin,
               [](auto& r) { r.expiry = base::Seconds(172801); }),
       },
       {
           "aggregatable_report_window_valid",
           R"json({"expiry":"172801","aggregatable_report_window":"86401",
           "destination":"https://d.example"})json",
-          SourceRegistrationWith(destination_origin, reporting_origin,
+          SourceRegistrationWith(destination_origin,
                                  [](auto& r) {
                                    r.expiry = base::Seconds(172801);
                                    r.aggregatable_report_window =
@@ -167,7 +162,7 @@
           R"json({"expiry":"172801","aggregatable_report_window":86401,
           "destination":"https://d.example"})json",
           SourceRegistrationWith(
-              destination_origin, reporting_origin,
+              destination_origin,
               [](auto& r) { r.expiry = base::Seconds(172801); }),
       },
       {
@@ -175,30 +170,30 @@
           R"json({"expiry":"172801","aggregatable_report_window":"abc",
           "destination":"https://d.example"})json",
           SourceRegistrationWith(
-              destination_origin, reporting_origin,
+              destination_origin,
               [](auto& r) { r.expiry = base::Seconds(172801); }),
       },
       {
           "debug_key_valid",
           R"json({"debug_key":"5","destination":"https://d.example"})json",
-          SourceRegistrationWith(destination_origin, reporting_origin,
+          SourceRegistrationWith(destination_origin,
                                  [](auto& r) { r.debug_key = 5; }),
       },
       {
           "debug_key_invalid",
           R"json({"debug_key":"-5","destination":"https://d.example"})json",
-          SourceRegistration(destination_origin, reporting_origin),
+          SourceRegistration(destination_origin),
       },
       {
           "debug_key_wrong_type",
           R"json({"debug_key":5,"destination":"https://d.example"})json",
-          SourceRegistration(destination_origin, reporting_origin),
+          SourceRegistration(destination_origin),
       },
       {
           "filter_data_valid",
           R"json({"filter_data":{"a":["b"]},"destination":"https://d.example"})json",
           SourceRegistrationWith(
-              destination_origin, reporting_origin,
+              destination_origin,
               [](auto& r) {
                 r.filter_data = *FilterData::Create({{"a", {"b"}}});
               }),
@@ -211,7 +206,7 @@
       {
           "aggregation_keys_valid",
           R"json({"aggregation_keys":{"a":"0x1"},"destination":"https://d.example"})json",
-          SourceRegistrationWith(destination_origin, reporting_origin,
+          SourceRegistrationWith(destination_origin,
                                  [](auto& r) {
                                    r.aggregation_keys =
                                        *AggregationKeys::FromKeys(
@@ -226,27 +221,23 @@
       {
           "debug_reporting_valid",
           R"json({"debug_reporting":true,"destination":"https://d.example"})json",
-          SourceRegistrationWith(destination_origin, reporting_origin,
+          SourceRegistrationWith(destination_origin,
                                  [](auto& r) { r.debug_reporting = true; }),
       },
       {
           "debug_reporting_wrong_type",
           R"json({"debug_reporting":"true","destination":"https://d.example"})json",
-          SourceRegistration(destination_origin, reporting_origin),
+          SourceRegistration(destination_origin),
       },
   };
 
   for (const auto& test_case : kTestCases) {
-    EXPECT_EQ(test_case.expected,
-              SourceRegistration::Parse(test_case.json, reporting_origin))
+    EXPECT_EQ(test_case.expected, SourceRegistration::Parse(test_case.json))
         << test_case.desc;
   }
 }
 
 TEST(SourceRegistrationTest, ToJson) {
-  const auto reporting_origin =
-      *SuitableOrigin::Deserialize("https://r.example");
-
   const auto destination_origin =
       *SuitableOrigin::Deserialize("https://d.example");
 
@@ -255,7 +246,7 @@
     const char* expected_json;
   } kTestCases[] = {
       {
-          SourceRegistration(destination_origin, reporting_origin),
+          SourceRegistration(destination_origin),
           R"json({
             "debug_reporting": false,
             "destination":"https://d.example",
@@ -265,7 +256,7 @@
       },
       {
           SourceRegistrationWith(
-              destination_origin, reporting_origin,
+              destination_origin,
               [](auto& r) {
                 r.aggregatable_report_window = base::Seconds(1);
                 r.aggregation_keys = *AggregationKeys::FromKeys({{"a", 2}});
diff --git a/components/attribution_reporting/test_utils.cc b/components/attribution_reporting/test_utils.cc
index 2ae5c8c1..55d7a31 100644
--- a/components/attribution_reporting/test_utils.cc
+++ b/components/attribution_reporting/test_utils.cc
@@ -48,17 +48,16 @@
 
 bool operator==(const SourceRegistration& a, const SourceRegistration& b) {
   auto tie = [](const SourceRegistration& s) {
-    return std::make_tuple(
-        s.source_event_id, s.destination, s.reporting_origin, s.expiry,
-        s.event_report_window, s.aggregatable_report_window, s.priority,
-        s.filter_data, s.debug_key, s.aggregation_keys, s.debug_reporting);
+    return std::make_tuple(s.source_event_id, s.destination, s.expiry,
+                           s.event_report_window, s.aggregatable_report_window,
+                           s.priority, s.filter_data, s.debug_key,
+                           s.aggregation_keys, s.debug_reporting);
   };
   return tie(a) == tie(b);
 }
 
 std::ostream& operator<<(std::ostream& out, const SourceRegistration& s) {
-  return out << "{reporting_origin=" << s.reporting_origin
-             << ",json=" << s.ToJson() << "}";
+  return out << s.ToJson();
 }
 
 bool operator==(const AggregatableValues& a, const AggregatableValues& b) {
diff --git a/components/autofill/README.md b/components/autofill/README.md
index 52ea042..ef1eb83 100644
--- a/components/autofill/README.md
+++ b/components/autofill/README.md
@@ -144,3 +144,156 @@
   - [`//chrome/android/java/src/org/chromium/chrome/browser/autofill`](https://source.chromium.org/chromium/chromium/src/+/main:chrome/android/java/src/org/chromium/chrome/browser/autofill/)
 - [`//chrome/browser/ui/autofill`](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/autofill)
 - [`//chrome/browser/ui/views/autofill`](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/views/autofill)
+
+# Autofill cheatsheet
+
+This is a cheatsheet to navigate Autofill. It is not necessarily exhaustive and
+may sacrifice a little bit of correctness in favor of simplicity.
+
+## What are the main classes that orchestrate Autofill?
+
+* Renderer:
+  * `AutofillAgent`
+    * One instance per `RenderFrame` (frame).
+    * Responsibilities:
+      * Observes forms in a frame and notifies the browser about changes.
+      * Executes preview and filling requests.
+    * Implements `blink::WebAutofillClient` to communicate with Blink.
+* Browser:
+  * `ContentAutofillDriver`
+    * One instance per `RenderFrameHost` (frame), owned by
+      `ContentAutofillDriverFactory`
+    * Responsibilities:
+      * Facilitates communication between the browser and the renderer logic.
+    * Implements interfaces `AutofillDriver` and `mojom::AutofillDriver`
+    * Has sibling `AutofillDriverIOS` for iOS
+  * `ContentAutofillDriverFactory`
+    * One instance per `WebContents` (tab)
+    * Responsibilities:
+      * Manages life-cycle of `ContentAutofillDriver` and ensures that there is
+        one Driver instance per renderer frame.
+  * `AutofillManager` and `BrowserAutofillManager`
+    * One instance per `RenderFrameHost` (frame), owned by
+      `AutofillDriver`.
+    * Responsibilities:
+      * Main orchestrator for Autofill logic.
+    * `BrowserAutofillManager` extends the `AutofillManager` base class.
+    * `BrowserAutofillManager` has sibling `AndroidAutofillManager` which is
+      responsible for Android Autofill for WebViews.
+  * `ChromeAutofillClient`
+    * One instance per `WebContents` (tab)
+    * Responsibilities:
+      * Serves as bridge from platform aganostic `BrowserAutofillManager` to the
+        OS specific logic.
+    * Implements `AutofillClient` interface.
+    * Has siblings `AwAutofillClient`, `ChromeAutofillClientIOS` and
+      `WebViewAutofillClientIOS`.
+
+## What's the difference between Autofill and Autocomplete?
+
+* Autofill is about structured and typed information (addresses, credit card
+  data, ...)
+* Autocomplete is about single-field, untyped information. Values are tied to a
+  field identifier. Autocomplete is largely implemented by the
+  `AutocompleteHistoryManager`.
+
+## What are the representations for Forms and Fields?
+
+* Between Renderer and Browser, we mostly exchange structural information
+  * `FormData`
+    * HTML attributes of a form
+    * Contains a list of fields (`FormFieldData`).
+    * 1:1 correspondence to a `blink::WebFormElement`
+  * `FormFieldData`
+    * HTML attributes of a field
+    * current value (or value that should be filled)
+    * `global_id()` gives a globally unique and non-changing identifier of a
+      field in the renderer process.
+    * 1:1 correspondence to a `blink::WebFormControlElement`
+* On the Browser side, we have augmented information:
+  * `FormStructure` - corresponds to a `FormData`
+    * Container for a series of `FormFieldData` objects
+  * `AutofillField` - corresponds to a `FormFieldData`
+    * Inherits from `FormFieldData` and extends it by
+      * Field type classifications
+      * Other Autofill metadata
+
+## How are forms and fields identified?
+* Per page load, in particular for distinguishing DOM elements:
+  * `FormGlobalId` is a pair of a `LocalFrameToken` and a `FormRendererId`,
+     which uniquely identify the frame and the form element in that frame.
+  * `FieldGlobalId` is a pair of a `LocalFrameToken` and a `FieldRendererId`.
+* Across page loads, in particular for crowdsourcing:
+  * `FormSignature` is a 64 bit hash value of the form URL (target URL or
+    location of the embedding website as a fallback) and normalized field
+    names.
+  * `FieldSignature` is a 32 bit hash value of the field name (name attribute,
+    falling back to id attribute of the form control) and type (text, search,
+    password, tel, ...)
+
+## How are field classified?
+* Local heuristics
+  * See `components/autofill/core/browser/form_parsing/`.
+  * `FormField::ParseFormFields` is the global entry point for parsing fields
+    with heuristics.
+  * Local heuristics are only applied if a form has at least 3 fields and at
+    least 3 fields are classified (after launching
+    AutofillMin3FieldTypesForLocalHeuristics, we require 3 different field
+    types). There are exceptions for a few field types (email addresses,
+    promo codes, IBANs, CVV fields).
+  * We perform local heuristics even for smaller forms but only for promo codes
+    and IBANs (see `ParseSingleFieldForms`).
+  * Regular expressions for parsing are provided via
+    `components/autofill/core/common/autofill_regex_constants.h` or
+    `components/autofill/core/browser/form_parsing/regex_patterns.h` if
+    `features::kAutofillParsingPatternProvider` is enabled.
+* Crowd sourcing
+  * `AutofillDownloadManager` is responsible for downloading field
+    classifications.
+  * Crowd sourcing is applied (for lookups and voting) for forms of any size but
+    the server can handle small forms differently, see
+    [`http://cs/IsSmallForm%20file:autofill`](http://cs/IsSmallForm%20file:autofill).
+  * Crowd sourcing trumps local heuristics.
+* Autocomplete attribute
+  * The autocomplete attribute is parsed in `ParseAutocompleteAttribute`.
+  * The autocomplete attribute trumps local heuristics and crowd sourcing
+    (except for `off`).
+* Rationalization
+  * Rationalization is the process of looking at the output of the previous
+    classification steps and doing a post processing for certain field
+    combinations that don't make sense (street-address followed by
+    address-line1).
+
+## How is data represented internally?
+* See `components/autofill/core/browser/data_model/`
+  * For addresses, see
+    `components/autofill/core/browser/data_model/autofill_structured_address.h`
+    and
+    `components/autofill/core/browser/data_model/autofill_structured_address_name.h`.
+  * Parsing = breaking a bigger concept (e.g. street address) into smaller
+    concepts (e.g. street name and house number). See
+    `AddressComponent::ParseValueAndAssignSubcomponents()`.
+      * Parsing goes through a chain until one method succeeds:
+        * Via `ParseValueAndAssignSubcomponentsByMethod()`
+        * Via `ParseValueAndAssignSubcomponentsByRegularExpressions()`
+        * Finally `ParseValueAndAssignSubcomponentsByFallbackMethod()`
+      * This is driven by the implementations of
+        `GetParseRegularExpressionsByRelevance()`.
+  * Formatting = combining the smaller concepts (e.g. street name and house
+    number) into a bigger one (street address). See
+    `AddressComponent::FormatValueFromSubcomponents()`.
+    * This is driven by the implementations of `GetBestFormatString()`,
+      in particular `StreetAddress::GetBestFormatString()`.
+  * Invariance: The children of a node cannot contain more information than the
+    parent node, or more more formally: every string in a node must be present
+    in its parent (at least in a normalized form). If a subtree contains too
+    much data, it is discarded via `AddressComponent::WipeInvalidStructure()`.
+
+## Where is Autofill data persisted?
+* See
+  [`../../components/autofill/core/browser/webdata/autofill_table.h`](https://source.chromium.org/chromium/chromium/src/+/main:components/autofill/core/browser/webdata/autofill_table.h)
+
+<!-- TODO:
+## How are addresses compared, updated or added?
+*
+-->
diff --git a/components/autofill/core/common/autofill_features.cc b/components/autofill/core/common/autofill_features.cc
index d77af35..ab109cfc 100644
--- a/components/autofill/core/common/autofill_features.cc
+++ b/components/autofill/core/common/autofill_features.cc
@@ -569,7 +569,7 @@
 // settled.
 BASE_FEATURE(kAutofillVoteForSelectOptionValues,
              "AutofillVoteForSelectOptionValues",
-             base::FEATURE_ENABLED_BY_DEFAULT);
+             base::FEATURE_DISABLED_BY_DEFAULT);
 
 #if BUILDFLAG(IS_ANDROID)
 // Controls whether the Autofill manual fallback for Addresses and Payments is
diff --git a/components/autofill/ios/form_util/form_activity_tab_helper.h b/components/autofill/ios/form_util/form_activity_tab_helper.h
index be155e2..2f18ac72 100644
--- a/components/autofill/ios/form_util/form_activity_tab_helper.h
+++ b/components/autofill/ios/form_util/form_activity_tab_helper.h
@@ -6,13 +6,10 @@
 #define COMPONENTS_AUTOFILL_IOS_FORM_UTIL_FORM_ACTIVITY_TAB_HELPER_H_
 
 #include "base/observer_list.h"
+#include "base/values.h"
 #include "ios/web/public/web_state_observer.h"
 #import "ios/web/public/web_state_user_data.h"
 
-namespace base {
-class DictionaryValue;
-}
-
 namespace web {
 class ScriptMessage;
 class WebState;
@@ -68,7 +65,7 @@
 
   bool GetBaseFormActivityParams(web::WebState* web_state,
                                  const web::ScriptMessage& message,
-                                 base::DictionaryValue** message_body,
+                                 const base::Value::Dict** message_body,
                                  BaseFormActivityParams* form_activity,
                                  web::WebFrame** sender_frame);
 
diff --git a/components/autofill/ios/form_util/form_activity_tab_helper.mm b/components/autofill/ios/form_util/form_activity_tab_helper.mm
index 2ef0604..abffd9c 100644
--- a/components/autofill/ios/form_util/form_activity_tab_helper.mm
+++ b/components/autofill/ios/form_util/form_activity_tab_helper.mm
@@ -55,21 +55,19 @@
 void FormActivityTabHelper::OnFormMessageReceived(
     web::WebState* web_state,
     const web::ScriptMessage& message) {
-  base::DictionaryValue* message_body;
-  if (!message.body() || !message.body()->is_dict() ||
-      !message.body()->GetAsDictionary(&message_body)) {
+  if (!message.body() || !message.body()->is_dict()) {
     // Ignore invalid message.
     return;
   }
 
-  std::string command;
-  if (!message_body->GetString("command", &command)) {
+  const std::string* command = message.body()->GetDict().FindString("command");
+  if (!command) {
     DLOG(WARNING) << "JS message parameter not found: command";
-  } else if (command == "form.submit") {
+  } else if (*command == "form.submit") {
     FormSubmissionHandler(web_state, message);
-  } else if (command == "form.activity") {
+  } else if (*command == "form.activity") {
     HandleFormActivity(web_state, message);
-  } else if (command == "form.removal") {
+  } else if (*command == "form.removal") {
     HandleFormRemoval(web_state, message);
   }
 }
@@ -77,7 +75,7 @@
 void FormActivityTabHelper::HandleFormActivity(
     web::WebState* web_state,
     const web::ScriptMessage& message) {
-  base::DictionaryValue* message_body = nullptr;
+  const base::Value::Dict* message_body = nullptr;
   web::WebFrame* sender_frame = nullptr;
   FormActivityParams params;
   if (!GetBaseFormActivityParams(web_state, message, &message_body, &params,
@@ -86,14 +84,14 @@
   }
 
   const std::string* field_identifier =
-      message_body->FindStringKey("fieldIdentifier");
+      message_body->FindString("fieldIdentifier");
   const std::string* unique_field_id =
-      message_body->FindStringKey("uniqueFieldID");
-  const std::string* field_type = message_body->FindStringKey("fieldType");
-  const std::string* type = message_body->FindStringKey("type");
-  const std::string* value = message_body->FindStringKey("value");
+      message_body->FindString("uniqueFieldID");
+  const std::string* field_type = message_body->FindString("fieldType");
+  const std::string* type = message_body->FindString("type");
+  const std::string* value = message_body->FindString("value");
   absl::optional<bool> has_user_gesture =
-      message_body->FindBoolKey("hasUserGesture");
+      message_body->FindBool("hasUserGesture");
   if (!field_identifier || !unique_field_id || !field_type || !type || !value ||
       !has_user_gesture) {
     params.input_missing = true;
@@ -119,7 +117,7 @@
 void FormActivityTabHelper::HandleFormRemoval(
     web::WebState* web_state,
     const web::ScriptMessage& message) {
-  base::DictionaryValue* message_body = nullptr;
+  const base::Value::Dict* message_body = nullptr;
   web::WebFrame* sender_frame = nullptr;
   FormRemovalParams params;
   if (!GetBaseFormActivityParams(web_state, message, &message_body, &params,
@@ -127,12 +125,13 @@
     return;
   }
 
-  std::string unique_field_ids;
-  if (!params.unique_form_id &&
-      (!message_body->GetString("uniqueFieldID", &unique_field_ids) ||
-       !ExtractIDs(SysUTF8ToNSString(unique_field_ids),
-                   &params.removed_unowned_fields))) {
-    params.input_missing = true;
+  if (!params.unique_form_id) {
+    const std::string* unique_field_ids =
+        message_body->FindString("uniqueFieldID");
+    if (!unique_field_ids || !ExtractIDs(SysUTF8ToNSString(*unique_field_ids),
+                                         &params.removed_unowned_fields)) {
+      params.input_missing = true;
+    }
   }
 
   for (auto& observer : observers_)
@@ -142,19 +141,18 @@
 void FormActivityTabHelper::FormSubmissionHandler(
     web::WebState* web_state,
     const web::ScriptMessage& message) {
-  base::DictionaryValue* message_body;
-  if (!message.body() || !message.body()->is_dict() ||
-      !message.body()->GetAsDictionary(&message_body)) {
+  if (!message.body() || !message.body()->is_dict()) {
     // Ignore invalid message.
     return;
   }
 
-  std::string frame_id;
-  if (!message_body->GetString("frameID", &frame_id)) {
+  const base::Value::Dict& message_body = message.body()->GetDict();
+  const std::string* frame_id = message_body.FindString("frameID");
+  if (!frame_id) {
     return;
   }
 
-  web::WebFrame* sender_frame = GetWebFrameWithId(web_state, frame_id);
+  web::WebFrame* sender_frame = GetWebFrameWithId(web_state, *frame_id);
   if (!sender_frame) {
     return;
   }
@@ -162,56 +160,73 @@
     return;
   }
 
-  std::string href;
-  if (!message_body->GetString("href", &href)) {
+  if (!message_body.FindString("href")) {
     DLOG(WARNING) << "JS message parameter not found: href";
     return;
   }
-  std::string form_name;
-  message_body->GetString("formName", &form_name);
+  const std::string* maybe_form_name = message_body.FindString("formName");
+  const std::string* maybe_form_data = message_body.FindString("formData");
 
-  std::string form_data;
-  message_body->GetString("formData", &form_data);
   // We decide the form is user-submitted if the user has interacted with
   // the main page (using logic from the popup blocker), or if the keyboard
   // is visible.
   BOOL submitted_by_user = message.is_user_interacting() ||
                            [web_state->GetWebViewProxy() keyboardAccessory];
 
-  for (auto& observer : observers_)
+  std::string form_name;
+  if (maybe_form_name) {
+    form_name = *maybe_form_name;
+  }
+  std::string form_data;
+  if (maybe_form_data) {
+    form_data = *maybe_form_data;
+  }
+  for (auto& observer : observers_) {
     observer.DocumentSubmitted(web_state, sender_frame, form_name, form_data,
                                submitted_by_user);
+  }
 }
 
 bool FormActivityTabHelper::GetBaseFormActivityParams(
     web::WebState* web_state,
     const web::ScriptMessage& message,
-    base::DictionaryValue** message_body,
+    const base::Value::Dict** message_body,
     BaseFormActivityParams* params,
     web::WebFrame** sender_frame) {
-  if (!message.body() || !message.body()->is_dict() ||
-      !message.body()->GetAsDictionary(message_body)) {
+  if (!message.body() || !message.body()->is_dict()) {
     // Ignore invalid message.
     return false;
   }
 
-  std::string frame_id;
-  if (!(*message_body)->GetString("frameID", &frame_id)) {
+  const auto& message_body_dict = message.body()->GetDict();
+  *message_body = &message_body_dict;
+  const std::string* frame_id = message_body_dict.FindString("frameID");
+  if (!frame_id) {
     return false;
   }
 
-  *sender_frame = GetWebFrameWithId(web_state, frame_id);
+  *sender_frame = GetWebFrameWithId(web_state, *frame_id);
   if (!*sender_frame) {
     return false;
   }
 
-  params->frame_id = frame_id;
-  std::string unique_form_id;
-  if (!(*message_body)->GetString("formName", &params->form_name) ||
-      !(*message_body)->GetString("uniqueFormID", &unique_form_id)) {
+  params->frame_id = *frame_id;
+  const std::string* form_name = message_body_dict.FindString("formName");
+  const std::string* unique_form_id =
+      message_body_dict.FindString("uniqueFormID");
+  if (!form_name || !unique_form_id) {
     params->input_missing = true;
   }
-  StringToUint(unique_form_id, &params->unique_form_id.value());
+
+  if (form_name) {
+    params->form_name = *form_name;
+  }
+
+  std::string unique_id;
+  if (unique_form_id) {
+    unique_id = *unique_form_id;
+  }
+  StringToUint(unique_id, &params->unique_form_id.value());
 
   params->is_main_frame = message.is_main_frame();
 
diff --git a/components/browser_ui/util/android/java/src/org/chromium/components/browser_ui/util/FirstDrawDetector.java b/components/browser_ui/util/android/java/src/org/chromium/components/browser_ui/util/FirstDrawDetector.java
index 013e284..1be9676b 100644
--- a/components/browser_ui/util/android/java/src/org/chromium/components/browser_ui/util/FirstDrawDetector.java
+++ b/components/browser_ui/util/android/java/src/org/chromium/components/browser_ui/util/FirstDrawDetector.java
@@ -32,32 +32,26 @@
      * @param callback Callback to trigger on first draw. Will be called on the UI thread.
      */
     public static void waitForFirstDraw(View view, Runnable callback) {
-        new FirstDrawDetector(view, callback).startWaiting();
+        new FirstDrawDetector(view, callback).startWaiting(/*strict=*/false);
     }
 
-    private void startWaiting() {
-        // We use a draw listener to detect when a view is first drawn. However, if the view
-        // doesn't get drawn for some reason (e.g. the screen is off), our listener will never
-        // get called. To work around this, we also schedule a callback for the next frame from
-        // a pre-draw listener (which will always get called). Whichever callback runs first
-        // will declare the view to have been drawn.
-        //
-        // Note that we cannot just use a pre-draw listener here, because it does not guarantee
-        // that the view has actually been drawn.
-        ViewTreeObserver.OnPreDrawListener firstPreDrawListener =
-                new ViewTreeObserver.OnPreDrawListener() {
-                    @Override
-                    public boolean onPreDraw() {
-                        // The pre-draw listener will run both when the screen is on or off, but the
-                        // view might not have been drawn yet at this point. Trigger the first paint
-                        // at the next frame.
-                        PostTask.postTask(TaskTraits.CHOREOGRAPHER_FRAME, () -> onFirstDraw());
-                        if (mView.get() != null) {
-                            mView.get().getViewTreeObserver().removeOnPreDrawListener(this);
-                        }
-                        return true;
-                    }
-                };
+    /**
+     * Waits for a view to be drawn on the screen for the first time. Unlike |#waitForFirstDraw()|,
+     * which can trigger the callback in |#onPreDraw()|, this method waits for |#onDraw()|. This is
+     * useful when the caller knows that the draw may be delayed because some
+     * {@link OnPreDrawListener}s return false.
+     * @param view View whose drawing to observe.
+     * @param callback Callback to trigger on first draw. Will be called on the UI thread.
+     */
+    public static void waitForFirstDrawStrict(View view, Runnable callback) {
+        new FirstDrawDetector(view, callback).startWaiting(/*strict=*/true);
+    }
+
+    /**
+     * Starts waiting for a draw to trigger the callback.
+     * @param strict Whether to wait for an |#onDraw| strictly. See |#waitForFirstDrawStrict()|.
+     */
+    private void startWaiting(boolean strict) {
         ViewTreeObserver.OnDrawListener firstDrawListener = new ViewTreeObserver.OnDrawListener() {
             @Override
             public void onDraw() {
@@ -73,8 +67,31 @@
                 });
             }
         };
-        mView.get().getViewTreeObserver().addOnPreDrawListener(firstPreDrawListener);
         mView.get().getViewTreeObserver().addOnDrawListener(firstDrawListener);
+        if (strict) return;
+        // We use a draw listener to detect when a view is first drawn. However, if the view
+        // doesn't get drawn for some reason (e.g. the screen is off), our listener will never
+        // get called. To work around this, we also schedule a callback for the next frame from
+        // a pre-draw listener (which will always get called). Whichever callback runs first
+        // will declare the view to have been drawn.
+        //
+        // Note that we cannot just use a pre-draw listener here, because it does not guarantee
+        // that the view has actually been drawn.
+        ViewTreeObserver.OnPreDrawListener firstPreDrawListener =
+                new ViewTreeObserver.OnPreDrawListener() {
+                    @Override
+                    public boolean onPreDraw() {
+                        // The pre-draw listener will run both when the screen is on or off, but the
+                        // view might not have been drawn yet at this point. Trigger the first paint
+                        // at the next frame.
+                        PostTask.postTask(TaskTraits.CHOREOGRAPHER_FRAME, () -> { onFirstDraw(); });
+                        if (mView.get() != null) {
+                            mView.get().getViewTreeObserver().removeOnPreDrawListener(this);
+                        }
+                        return true;
+                    }
+                };
+        mView.get().getViewTreeObserver().addOnPreDrawListener(firstPreDrawListener);
     }
 
     private void onFirstDraw() {
diff --git a/components/commerce/core/commerce_feature_list.cc b/components/commerce/core/commerce_feature_list.cc
index 5bea80a..c366e91 100644
--- a/components/commerce/core/commerce_feature_list.cc
+++ b/components/commerce/core/commerce_feature_list.cc
@@ -37,7 +37,7 @@
 const CountryLocaleMap& GetAllowedCountryToLocaleMap() {
   // Declaring the variable "static" means it isn't recreated each time this
   // function is called. This gets around the "static initializers" problem.
-  static const base::NoDestructor<CountryLocaleMap> map({{"us", {"en-us"}}});
+  static const base::NoDestructor<CountryLocaleMap> map{{{"us", {"en-us"}}}};
   return *map;
 }
 
diff --git a/components/content_settings/browser/page_specific_content_settings.h b/components/content_settings/browser/page_specific_content_settings.h
index 844e22b..3c0f72a 100644
--- a/components/content_settings/browser/page_specific_content_settings.h
+++ b/components/content_settings/browser/page_specific_content_settings.h
@@ -39,7 +39,7 @@
 namespace content {
 class WebContents;
 class WebContentsObserver;
-}
+}  // namespace content
 
 namespace url {
 class Origin;
diff --git a/components/content_settings/core/browser/content_settings_observable_provider.cc b/components/content_settings/core/browser/content_settings_observable_provider.cc
index 68fad74..9b40cf9 100644
--- a/components/content_settings/core/browser/content_settings_observable_provider.cc
+++ b/components/content_settings/core/browser/content_settings_observable_provider.cc
@@ -11,11 +11,9 @@
 // ObservableProvider
 //
 
-ObservableProvider::ObservableProvider() {
-}
+ObservableProvider::ObservableProvider() {}
 
-ObservableProvider::~ObservableProvider() {
-}
+ObservableProvider::~ObservableProvider() {}
 
 void ObservableProvider::AddObserver(Observer* observer) {
   observer_list_.AddObserver(observer);
diff --git a/components/content_settings/core/browser/content_settings_origin_identifier_value_map.cc b/components/content_settings/core/browser/content_settings_origin_identifier_value_map.cc
index 5e73a8c1..336a853 100644
--- a/components/content_settings/core/browser/content_settings_origin_identifier_value_map.cc
+++ b/components/content_settings/core/browser/content_settings_origin_identifier_value_map.cc
@@ -55,9 +55,7 @@
 OriginIdentifierValueMap::PatternPair::PatternPair(
     const ContentSettingsPattern& primary_pattern,
     const ContentSettingsPattern& secondary_pattern)
-    : primary_pattern(primary_pattern),
-      secondary_pattern(secondary_pattern) {
-}
+    : primary_pattern(primary_pattern), secondary_pattern(secondary_pattern) {}
 
 bool OriginIdentifierValueMap::PatternPair::operator<(
     const OriginIdentifierValueMap::PatternPair& other) const {
diff --git a/components/content_settings/core/browser/content_settings_origin_identifier_value_map.h b/components/content_settings/core/browser/content_settings_origin_identifier_value_map.h
index 9e57748..9b9e2fc 100644
--- a/components/content_settings/core/browser/content_settings_origin_identifier_value_map.h
+++ b/components/content_settings/core/browser/content_settings_origin_identifier_value_map.h
@@ -20,7 +20,7 @@
 namespace base {
 class Lock;
 class Value;
-}
+}  // namespace base
 
 namespace content_settings {
 
@@ -46,29 +46,19 @@
   typedef std::map<PatternPair, ValueEntry> Rules;
   typedef std::map<ContentSettingsType, Rules> EntryMap;
 
-  EntryMap::iterator begin() {
-    return entries_.begin();
-  }
+  EntryMap::iterator begin() { return entries_.begin(); }
 
-  EntryMap::iterator end() {
-    return entries_.end();
-  }
+  EntryMap::iterator end() { return entries_.end(); }
 
-  EntryMap::const_iterator begin() const {
-    return entries_.begin();
-  }
+  EntryMap::const_iterator begin() const { return entries_.begin(); }
 
-  EntryMap::const_iterator end() const {
-    return entries_.end();
-  }
+  EntryMap::const_iterator end() const { return entries_.end(); }
 
   EntryMap::iterator find(ContentSettingsType content_type) {
     return entries_.find(content_type);
   }
 
-  bool empty() const {
-    return size() == 0u;
-  }
+  bool empty() const { return size() == 0u; }
 
   size_t size() const;
 
diff --git a/components/content_settings/core/browser/content_settings_policy_provider.cc b/components/content_settings/core/browser/content_settings_policy_provider.cc
index e2daa79..2624e1d 100644
--- a/components/content_settings/core/browser/content_settings_policy_provider.cc
+++ b/components/content_settings/core/browser/content_settings_policy_provider.cc
@@ -460,7 +460,6 @@
   }
 }
 
-
 void PolicyProvider::ReadManagedContentSettings(bool overwrite) {
   base::AutoLock auto_lock(lock_);
   if (overwrite)
@@ -481,8 +480,7 @@
 }
 
 void PolicyProvider::ClearAllContentSettingsRules(
-    ContentSettingsType content_type) {
-}
+    ContentSettingsType content_type) {}
 
 void PolicyProvider::ShutdownOnUIThread() {
   DCHECK(CalledOnValidThread());
diff --git a/components/content_settings/core/browser/content_settings_pref.h b/components/content_settings/core/browser/content_settings_pref.h
index 83406c9..2bd9d96 100644
--- a/components/content_settings/core/browser/content_settings_pref.h
+++ b/components/content_settings/core/browser/content_settings_pref.h
@@ -50,8 +50,7 @@
   ~ContentSettingsPref();
 
   // Returns nullptr to indicate the RuleIterator is empty.
-  std::unique_ptr<RuleIterator> GetRuleIterator(
-      bool off_the_record) const;
+  std::unique_ptr<RuleIterator> GetRuleIterator(bool off_the_record) const;
 
   void SetWebsiteSetting(const ContentSettingsPattern& primary_pattern,
                          const ContentSettingsPattern& secondary_pattern,
diff --git a/components/content_settings/core/browser/content_settings_pref_provider.cc b/components/content_settings/core/browser/content_settings_pref_provider.cc
index 48204d5..c616ba9 100644
--- a/components/content_settings/core/browser/content_settings_pref_provider.cc
+++ b/components/content_settings/core/browser/content_settings_pref_provider.cc
@@ -282,7 +282,6 @@
 
   if (supports_type(content_type))
     GetPref(content_type)->ClearAllContentSettingsRules();
-
 }
 
 void PrefProvider::ShutdownOnUIThread() {
diff --git a/components/content_settings/core/browser/content_settings_utils.cc b/components/content_settings/core/browser/content_settings_utils.cc
index a1af4f3..41f7ef88 100644
--- a/components/content_settings/core/browser/content_settings_utils.cc
+++ b/components/content_settings/core/browser/content_settings_utils.cc
@@ -44,11 +44,13 @@
 // belong between ALLOW and ASK. DEFAULT should never be used and is therefore
 // not part of this array.
 const ContentSetting kContentSettingOrder[] = {
+    // clang-format off
     CONTENT_SETTING_ALLOW,
     CONTENT_SETTING_SESSION_ONLY,
     CONTENT_SETTING_DETECT_IMPORTANT_CONTENT,
     CONTENT_SETTING_ASK,
     CONTENT_SETTING_BLOCK
+    // clang-format on
 };
 
 static_assert(std::size(kContentSettingOrder) ==
@@ -83,15 +85,14 @@
 std::string CreatePatternString(
     const ContentSettingsPattern& item_pattern,
     const ContentSettingsPattern& top_level_frame_pattern) {
-  return item_pattern.ToString()
-         + std::string(kPatternSeparator)
-         + top_level_frame_pattern.ToString();
+  return item_pattern.ToString() + std::string(kPatternSeparator) +
+         top_level_frame_pattern.ToString();
 }
 
 PatternPair ParsePatternString(const std::string& pattern_str) {
-  std::vector<std::string> pattern_str_list = base::SplitString(
-      pattern_str, std::string(1, kPatternSeparator[0]),
-      base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+  std::vector<std::string> pattern_str_list =
+      base::SplitString(pattern_str, std::string(1, kPatternSeparator[0]),
+                        base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
 
   // If the |pattern_str| is an empty string then the |pattern_string_list|
   // contains a single empty string. In this case the empty string will be
@@ -106,10 +107,8 @@
     }
   }
 
-  if (pattern_str_list.size() > 2 ||
-      pattern_str_list.size() == 0) {
-    return PatternPair(ContentSettingsPattern(),
-                       ContentSettingsPattern());
+  if (pattern_str_list.size() > 2 || pattern_str_list.size() == 0) {
+    return PatternPair(ContentSettingsPattern(), ContentSettingsPattern());
   }
 
   PatternPair pattern_pair;
diff --git a/components/content_settings/core/browser/content_settings_utils_unittest.cc b/components/content_settings/core/browser/content_settings_utils_unittest.cc
index 5869d4f..711b762c 100644
--- a/components/content_settings/core/browser/content_settings_utils_unittest.cc
+++ b/components/content_settings/core/browser/content_settings_utils_unittest.cc
@@ -16,6 +16,7 @@
 
 namespace {
 
+// clang-format off
 const char* const kContentSettingNames[] = {
   "default",
   "allow",
@@ -24,6 +25,8 @@
   "session_only",
   "detect_important_content",
 };
+// clang-format on
+
 static_assert(std::size(kContentSettingNames) == CONTENT_SETTING_NUM_SETTINGS,
               "kContentSettingNames has an unexpected number of elements");
 
@@ -81,49 +84,44 @@
 }
 
 TEST(ContentSettingsUtilsTest, IsMorePermissive) {
-  EXPECT_TRUE(IsMorePermissive(
-      CONTENT_SETTING_ALLOW, CONTENT_SETTING_BLOCK));
-  EXPECT_TRUE(IsMorePermissive(
-      CONTENT_SETTING_ALLOW, CONTENT_SETTING_ASK));
-  EXPECT_TRUE(IsMorePermissive(
-      CONTENT_SETTING_ALLOW, CONTENT_SETTING_DETECT_IMPORTANT_CONTENT));
-  EXPECT_TRUE(IsMorePermissive(
-      CONTENT_SETTING_ALLOW, CONTENT_SETTING_SESSION_ONLY));
+  EXPECT_TRUE(IsMorePermissive(CONTENT_SETTING_ALLOW, CONTENT_SETTING_BLOCK));
+  EXPECT_TRUE(IsMorePermissive(CONTENT_SETTING_ALLOW, CONTENT_SETTING_ASK));
+  EXPECT_TRUE(IsMorePermissive(CONTENT_SETTING_ALLOW,
+                               CONTENT_SETTING_DETECT_IMPORTANT_CONTENT));
+  EXPECT_TRUE(
+      IsMorePermissive(CONTENT_SETTING_ALLOW, CONTENT_SETTING_SESSION_ONLY));
 
-  EXPECT_TRUE(IsMorePermissive(
-      CONTENT_SETTING_SESSION_ONLY, CONTENT_SETTING_ASK));
-  EXPECT_TRUE(IsMorePermissive(
-      CONTENT_SETTING_SESSION_ONLY, CONTENT_SETTING_BLOCK));
+  EXPECT_TRUE(
+      IsMorePermissive(CONTENT_SETTING_SESSION_ONLY, CONTENT_SETTING_ASK));
+  EXPECT_TRUE(
+      IsMorePermissive(CONTENT_SETTING_SESSION_ONLY, CONTENT_SETTING_BLOCK));
 
-  EXPECT_TRUE(IsMorePermissive(
-      CONTENT_SETTING_DETECT_IMPORTANT_CONTENT, CONTENT_SETTING_ASK));
-  EXPECT_TRUE(IsMorePermissive(
-      CONTENT_SETTING_DETECT_IMPORTANT_CONTENT, CONTENT_SETTING_BLOCK));
+  EXPECT_TRUE(IsMorePermissive(CONTENT_SETTING_DETECT_IMPORTANT_CONTENT,
+                               CONTENT_SETTING_ASK));
+  EXPECT_TRUE(IsMorePermissive(CONTENT_SETTING_DETECT_IMPORTANT_CONTENT,
+                               CONTENT_SETTING_BLOCK));
 
   EXPECT_TRUE(IsMorePermissive(CONTENT_SETTING_ASK, CONTENT_SETTING_BLOCK));
 
-  EXPECT_FALSE(IsMorePermissive(
-      CONTENT_SETTING_BLOCK, CONTENT_SETTING_ALLOW));
-  EXPECT_FALSE(IsMorePermissive(
-      CONTENT_SETTING_BLOCK, CONTENT_SETTING_DETECT_IMPORTANT_CONTENT));
-  EXPECT_FALSE(IsMorePermissive(
-      CONTENT_SETTING_BLOCK, CONTENT_SETTING_SESSION_ONLY));
+  EXPECT_FALSE(IsMorePermissive(CONTENT_SETTING_BLOCK, CONTENT_SETTING_ALLOW));
+  EXPECT_FALSE(IsMorePermissive(CONTENT_SETTING_BLOCK,
+                                CONTENT_SETTING_DETECT_IMPORTANT_CONTENT));
+  EXPECT_FALSE(
+      IsMorePermissive(CONTENT_SETTING_BLOCK, CONTENT_SETTING_SESSION_ONLY));
   EXPECT_FALSE(IsMorePermissive(CONTENT_SETTING_BLOCK, CONTENT_SETTING_ASK));
 
-  EXPECT_FALSE(IsMorePermissive(
-      CONTENT_SETTING_ASK, CONTENT_SETTING_ALLOW));
-  EXPECT_FALSE(IsMorePermissive(
-      CONTENT_SETTING_ASK, CONTENT_SETTING_SESSION_ONLY));
-  EXPECT_FALSE(IsMorePermissive(
-      CONTENT_SETTING_ASK, CONTENT_SETTING_DETECT_IMPORTANT_CONTENT));
+  EXPECT_FALSE(IsMorePermissive(CONTENT_SETTING_ASK, CONTENT_SETTING_ALLOW));
+  EXPECT_FALSE(
+      IsMorePermissive(CONTENT_SETTING_ASK, CONTENT_SETTING_SESSION_ONLY));
+  EXPECT_FALSE(IsMorePermissive(CONTENT_SETTING_ASK,
+                                CONTENT_SETTING_DETECT_IMPORTANT_CONTENT));
 
-  EXPECT_FALSE(IsMorePermissive(
-      CONTENT_SETTING_SESSION_ONLY, CONTENT_SETTING_ALLOW));
-  EXPECT_FALSE(IsMorePermissive(
-      CONTENT_SETTING_DETECT_IMPORTANT_CONTENT, CONTENT_SETTING_ALLOW));
+  EXPECT_FALSE(
+      IsMorePermissive(CONTENT_SETTING_SESSION_ONLY, CONTENT_SETTING_ALLOW));
+  EXPECT_FALSE(IsMorePermissive(CONTENT_SETTING_DETECT_IMPORTANT_CONTENT,
+                                CONTENT_SETTING_ALLOW));
 
-  EXPECT_FALSE(IsMorePermissive(
-      CONTENT_SETTING_ALLOW, CONTENT_SETTING_ALLOW));
+  EXPECT_FALSE(IsMorePermissive(CONTENT_SETTING_ALLOW, CONTENT_SETTING_ALLOW));
 
   // Check that all possible ContentSettings except CONTENT_SETTING_DEFAULT are
   // handled.
diff --git a/components/content_settings/core/browser/host_content_settings_map.cc b/components/content_settings/core/browser/host_content_settings_map.cc
index 3ebc46f0..f96c9ca 100644
--- a/components/content_settings/core/browser/host_content_settings_map.cc
+++ b/components/content_settings/core/browser/host_content_settings_map.cc
@@ -48,8 +48,8 @@
 #include "net/cookies/static_cookie_policy.h"
 #include "url/gurl.h"
 
-using content_settings::WebsiteSettingsInfo;
 using content_settings::ContentSettingsInfo;
+using content_settings::WebsiteSettingsInfo;
 
 namespace {
 
@@ -516,7 +516,7 @@
                                constraints);
 }
 
-content_settings::PatternPair HostContentSettingsMap::GetNarrowestPatterns (
+content_settings::PatternPair HostContentSettingsMap::GetNarrowestPatterns(
     const GURL& primary_url,
     const GURL& secondary_url,
     ContentSettingsType type) const {
@@ -535,8 +535,8 @@
     return content_settings::PatternPair();
   }
 
-  content_settings::PatternPair patterns = GetPatternsForContentSettingsType(
-      primary_url, secondary_url, type);
+  content_settings::PatternPair patterns =
+      GetPatternsForContentSettingsType(primary_url, secondary_url, type);
 
   ContentSettingsPattern::Relation r1 =
       info.primary_pattern.Compare(patterns.first);
diff --git a/components/content_settings/core/browser/host_content_settings_map.h b/components/content_settings/core/browser/host_content_settings_map.h
index b40e82f..78beb422 100644
--- a/components/content_settings/core/browser/host_content_settings_map.h
+++ b/components/content_settings/core/browser/host_content_settings_map.h
@@ -37,7 +37,7 @@
 namespace base {
 class Value;
 class Clock;
-}
+}  // namespace base
 
 namespace content_settings {
 class ObservableProvider;
@@ -46,7 +46,7 @@
 class TestUtils;
 class RuleIterator;
 class WebsiteSettingsInfo;
-}
+}  // namespace content_settings
 
 namespace user_prefs {
 class PrefRegistrySyncable;
@@ -239,10 +239,9 @@
 
   // Check if a call to SetNarrowestContentSetting would succeed or if it would
   // fail because of an invalid pattern.
-  bool CanSetNarrowestContentSetting(
-      const GURL& primary_url,
-      const GURL& secondary_url,
-      ContentSettingsType type) const;
+  bool CanSetNarrowestContentSetting(const GURL& primary_url,
+                                     const GURL& secondary_url,
+                                     ContentSettingsType type) const;
 
   // Checks whether the specified |type| controls a feature that is restricted
   // to secure origins.
diff --git a/components/content_settings/core/common/content_settings_constraints.h b/components/content_settings/core/common/content_settings_constraints.h
index f038603..81fa5e5 100644
--- a/components/content_settings/core/common/content_settings_constraints.h
+++ b/components/content_settings/core/common/content_settings_constraints.h
@@ -35,8 +35,8 @@
   SessionModel session_model = SessionModel::Durable;
   // Set to true to keep track of the last visit to the origin of this
   // permission.
-  // This is used for the Safety check permission module and unrelated to the "expiration" keyword
-  // above.
+  // This is used for the Safety check permission module and unrelated to the
+  // "expiration" keyword above.
   bool track_last_visit_for_autoexpiration = false;
 };
 
diff --git a/components/content_settings/core/common/content_settings_pattern.cc b/components/content_settings/core/common/content_settings_pattern.cc
index 4e9cce69..9fae532 100644
--- a/components/content_settings/core/common/content_settings_pattern.cc
+++ b/components/content_settings/core/common/content_settings_pattern.cc
@@ -122,8 +122,8 @@
 // ////////////////////////////////////////////////////////////////////////////
 // ContentSettingsPattern::Builder
 //
-class ContentSettingsPattern::Builder :
-    public ContentSettingsPattern::BuilderInterface {
+class ContentSettingsPattern::Builder
+    : public ContentSettingsPattern::BuilderInterface {
  public:
   Builder();
 
@@ -316,8 +316,7 @@
 
   // If the pattern is for a URL with a non-wildcard domain without a port,
   // test if it is valid.
-  if (IsNonWildcardDomainNonPortScheme(parts.scheme) &&
-      parts.port.empty() &&
+  if (IsNonWildcardDomainNonPortScheme(parts.scheme) && parts.port.empty() &&
       !parts.is_port_wildcard) {
     return true;
   }
@@ -345,10 +344,10 @@
 // ContentSettingsPattern::PatternParts
 //
 ContentSettingsPattern::PatternParts::PatternParts()
-        : is_scheme_wildcard(false),
-          has_domain_wildcard(false),
-          is_port_wildcard(false),
-          is_path_wildcard(false) {}
+    : is_scheme_wildcard(false),
+      has_domain_wildcard(false),
+      is_port_wildcard(false),
+      is_path_wildcard(false) {}
 
 ContentSettingsPattern::PatternParts::PatternParts(const PatternParts& other) =
     default;
@@ -357,10 +356,11 @@
 
 ContentSettingsPattern::PatternParts::~PatternParts() {}
 
-ContentSettingsPattern::PatternParts& ContentSettingsPattern::PatternParts::
-operator=(const PatternParts& other) = default;
-ContentSettingsPattern::PatternParts& ContentSettingsPattern::PatternParts::
-operator=(PatternParts&& other) = default;
+ContentSettingsPattern::PatternParts&
+ContentSettingsPattern::PatternParts::operator=(const PatternParts& other) =
+    default;
+ContentSettingsPattern::PatternParts&
+ContentSettingsPattern::PatternParts::operator=(PatternParts&& other) = default;
 
 // ////////////////////////////////////////////////////////////////////////////
 // ContentSettingsPattern
@@ -391,8 +391,7 @@
 }
 
 // static
-ContentSettingsPattern ContentSettingsPattern::FromURL(
-    const GURL& url) {
+ContentSettingsPattern ContentSettingsPattern::FromURL(const GURL& url) {
   ContentSettingsPattern::Builder builder;
   const GURL* local_url = &url;
   if (url.SchemeIsFileSystem() && url.inner_url()) {
@@ -522,15 +521,12 @@
   return builder->Build();
 }
 
-ContentSettingsPattern::ContentSettingsPattern()
-  : is_valid_(false) {
-}
+ContentSettingsPattern::ContentSettingsPattern() : is_valid_(false) {}
 
 ContentSettingsPattern::ContentSettingsPattern(PatternParts parts, bool valid)
     : parts_(std::move(parts)), is_valid_(valid) {}
 
-bool ContentSettingsPattern::Matches(
-    const GURL& url) const {
+bool ContentSettingsPattern::Matches(const GURL& url) const {
   // An invalid pattern matches nothing.
   if (!is_valid_)
     return false;
@@ -628,8 +624,7 @@
   // Two invalid patterns are identical in the way they behave. They don't match
   // anything and are represented as an empty string. So it's fair to treat them
   // as identical.
-  if ((this == &other) ||
-      (!is_valid_ && !other.is_valid_))
+  if ((this == &other) || (!is_valid_ && !other.is_valid_))
     return IDENTITY;
 
   if (!is_valid_ && other.is_valid_)
diff --git a/components/content_settings/core/common/content_settings_pattern.h b/components/content_settings/core/common/content_settings_pattern.h
index 9fb7d2bd..83da1001 100644
--- a/components/content_settings/core/common/content_settings_pattern.h
+++ b/components/content_settings/core/common/content_settings_pattern.h
@@ -22,7 +22,7 @@
 namespace mojom {
 class ContentSettingsPatternDataView;
 }
-}
+}  // namespace content_settings
 
 // A pattern used in content setting rules. See |IsValid| for a description of
 // possible patterns.
diff --git a/components/content_settings/core/common/content_settings_pattern_parser_unittest.cc b/components/content_settings/core/common/content_settings_pattern_parser_unittest.cc
index 82daa828..f2d46d3 100644
--- a/components/content_settings/core/common/content_settings_pattern_parser_unittest.cc
+++ b/components/content_settings/core/common/content_settings_pattern_parser_unittest.cc
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/content_settings/core/common/content_settings_pattern.h"
 #include "components/content_settings/core/common/content_settings_pattern_parser.h"
+#include "components/content_settings/core/common/content_settings_pattern.h"
 
 #include "base/strings/string_piece.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -33,121 +33,158 @@
 
   // WithPathWildcard() is not called for "*". (Need a strict Mock for this
   // case.)
-  EXPECT_CALL(builder, WithSchemeWildcard()).Times(1).WillOnce(
-      ::testing::Return(&builder));
-  EXPECT_CALL(builder, WithDomainWildcard()).Times(1).WillOnce(
-      ::testing::Return(&builder));
-  EXPECT_CALL(builder, WithPortWildcard()).Times(1).WillOnce(
-      ::testing::Return(&builder));
+  EXPECT_CALL(builder, WithSchemeWildcard())
+      .Times(1)
+      .WillOnce(::testing::Return(&builder));
+  EXPECT_CALL(builder, WithDomainWildcard())
+      .Times(1)
+      .WillOnce(::testing::Return(&builder));
+  EXPECT_CALL(builder, WithPortWildcard())
+      .Times(1)
+      .WillOnce(::testing::Return(&builder));
   content_settings::PatternParser::Parse("*", &builder);
   ::testing::Mock::VerifyAndClear(&builder);
 
-  EXPECT_CALL(builder, WithScheme("http")).Times(1).WillOnce(
-      ::testing::Return(&builder));
-  EXPECT_CALL(builder, WithHost("www.youtube.com")).Times(1).WillOnce(
-      ::testing::Return(&builder));
-  EXPECT_CALL(builder, WithPort("8080")).Times(1).WillOnce(
-      ::testing::Return(&builder));
-  content_settings::PatternParser::Parse(
-      "http://www.youtube.com:8080", &builder);
+  EXPECT_CALL(builder, WithScheme("http"))
+      .Times(1)
+      .WillOnce(::testing::Return(&builder));
+  EXPECT_CALL(builder, WithHost("www.youtube.com"))
+      .Times(1)
+      .WillOnce(::testing::Return(&builder));
+  EXPECT_CALL(builder, WithPort("8080"))
+      .Times(1)
+      .WillOnce(::testing::Return(&builder));
+  content_settings::PatternParser::Parse("http://www.youtube.com:8080",
+                                         &builder);
   ::testing::Mock::VerifyAndClear(&builder);
 
-  EXPECT_CALL(builder, WithSchemeWildcard()).Times(1).WillOnce(
-      ::testing::Return(&builder));
-  EXPECT_CALL(builder, WithHost("www.gmail.com")).Times(1).WillOnce(
-      ::testing::Return(&builder));
-  EXPECT_CALL(builder, WithPort("80")).Times(1).WillOnce(
-      ::testing::Return(&builder));
+  EXPECT_CALL(builder, WithSchemeWildcard())
+      .Times(1)
+      .WillOnce(::testing::Return(&builder));
+  EXPECT_CALL(builder, WithHost("www.gmail.com"))
+      .Times(1)
+      .WillOnce(::testing::Return(&builder));
+  EXPECT_CALL(builder, WithPort("80"))
+      .Times(1)
+      .WillOnce(::testing::Return(&builder));
   content_settings::PatternParser::Parse("*://www.gmail.com:80", &builder);
   ::testing::Mock::VerifyAndClear(&builder);
 
-  EXPECT_CALL(builder, WithScheme("http")).Times(1).WillOnce(
-      ::testing::Return(&builder));
-  EXPECT_CALL(builder, WithHost("www.gmail.com")).Times(1).WillOnce(
-      ::testing::Return(&builder));
-  EXPECT_CALL(builder, WithPortWildcard()).Times(1).WillOnce(
-      ::testing::Return(&builder));
+  EXPECT_CALL(builder, WithScheme("http"))
+      .Times(1)
+      .WillOnce(::testing::Return(&builder));
+  EXPECT_CALL(builder, WithHost("www.gmail.com"))
+      .Times(1)
+      .WillOnce(::testing::Return(&builder));
+  EXPECT_CALL(builder, WithPortWildcard())
+      .Times(1)
+      .WillOnce(::testing::Return(&builder));
   content_settings::PatternParser::Parse("http://www.gmail.com:*", &builder);
   ::testing::Mock::VerifyAndClear(&builder);
 
-  EXPECT_CALL(builder, WithScheme("http")).Times(1).WillOnce(
-      ::testing::Return(&builder));
-  EXPECT_CALL(builder, WithDomainWildcard()).WillOnce(
-      ::testing::Return(&builder));
-  EXPECT_CALL(builder, WithHost("google.com")).Times(1).WillOnce(
-      ::testing::Return(&builder));
-  EXPECT_CALL(builder, WithPort("80")).Times(1).WillOnce(
-      ::testing::Return(&builder));
+  EXPECT_CALL(builder, WithScheme("http"))
+      .Times(1)
+      .WillOnce(::testing::Return(&builder));
+  EXPECT_CALL(builder, WithDomainWildcard())
+      .WillOnce(::testing::Return(&builder));
+  EXPECT_CALL(builder, WithHost("google.com"))
+      .Times(1)
+      .WillOnce(::testing::Return(&builder));
+  EXPECT_CALL(builder, WithPort("80"))
+      .Times(1)
+      .WillOnce(::testing::Return(&builder));
   content_settings::PatternParser::Parse("http://[*.]google.com:80", &builder);
   ::testing::Mock::VerifyAndClear(&builder);
 
-  EXPECT_CALL(builder, WithScheme("https")).Times(1).WillOnce(
-      ::testing::Return(&builder));
-  EXPECT_CALL(builder, WithHost("[::1]")).Times(1).WillOnce(
-      ::testing::Return(&builder));
-  EXPECT_CALL(builder, WithPort("8080")).Times(1).WillOnce(
-      ::testing::Return(&builder));
+  EXPECT_CALL(builder, WithScheme("https"))
+      .Times(1)
+      .WillOnce(::testing::Return(&builder));
+  EXPECT_CALL(builder, WithHost("[::1]"))
+      .Times(1)
+      .WillOnce(::testing::Return(&builder));
+  EXPECT_CALL(builder, WithPort("8080"))
+      .Times(1)
+      .WillOnce(::testing::Return(&builder));
   content_settings::PatternParser::Parse("https://[::1]:8080", &builder);
   ::testing::Mock::VerifyAndClear(&builder);
 
-  EXPECT_CALL(builder, WithScheme("http")).Times(1).WillOnce(
-      ::testing::Return(&builder));
-  EXPECT_CALL(builder, WithHost("127.0.0.1")).Times(1).WillOnce(
-      ::testing::Return(&builder));
-  EXPECT_CALL(builder, WithPort("8080")).Times(1).WillOnce(
-      ::testing::Return(&builder));
+  EXPECT_CALL(builder, WithScheme("http"))
+      .Times(1)
+      .WillOnce(::testing::Return(&builder));
+  EXPECT_CALL(builder, WithHost("127.0.0.1"))
+      .Times(1)
+      .WillOnce(::testing::Return(&builder));
+  EXPECT_CALL(builder, WithPort("8080"))
+      .Times(1)
+      .WillOnce(::testing::Return(&builder));
   content_settings::PatternParser::Parse("http://127.0.0.1:8080", &builder);
   ::testing::Mock::VerifyAndClear(&builder);
 
   // Test valid pattern short forms
-  EXPECT_CALL(builder, WithSchemeWildcard()).Times(1).WillOnce(
-      ::testing::Return(&builder));
-  EXPECT_CALL(builder, WithHost("www.youtube.com")).Times(1).WillOnce(
-      ::testing::Return(&builder));
-  EXPECT_CALL(builder, WithPort("8080")).Times(1).WillOnce(
-      ::testing::Return(&builder));
+  EXPECT_CALL(builder, WithSchemeWildcard())
+      .Times(1)
+      .WillOnce(::testing::Return(&builder));
+  EXPECT_CALL(builder, WithHost("www.youtube.com"))
+      .Times(1)
+      .WillOnce(::testing::Return(&builder));
+  EXPECT_CALL(builder, WithPort("8080"))
+      .Times(1)
+      .WillOnce(::testing::Return(&builder));
   content_settings::PatternParser::Parse("www.youtube.com:8080", &builder);
   ::testing::Mock::VerifyAndClear(&builder);
 
-  EXPECT_CALL(builder, WithSchemeWildcard()).Times(1).WillOnce(
-      ::testing::Return(&builder));
-  EXPECT_CALL(builder, WithHost("www.youtube.com")).Times(1).WillOnce(
-      ::testing::Return(&builder));
-  EXPECT_CALL(builder, WithPortWildcard()).Times(1).WillOnce(
-      ::testing::Return(&builder));
+  EXPECT_CALL(builder, WithSchemeWildcard())
+      .Times(1)
+      .WillOnce(::testing::Return(&builder));
+  EXPECT_CALL(builder, WithHost("www.youtube.com"))
+      .Times(1)
+      .WillOnce(::testing::Return(&builder));
+  EXPECT_CALL(builder, WithPortWildcard())
+      .Times(1)
+      .WillOnce(::testing::Return(&builder));
   content_settings::PatternParser::Parse("www.youtube.com", &builder);
   ::testing::Mock::VerifyAndClear(&builder);
 
-  EXPECT_CALL(builder, WithSchemeWildcard()).Times(1).WillOnce(
-      ::testing::Return(&builder));
-  EXPECT_CALL(builder, WithDomainWildcard()).Times(1).WillOnce(
-      ::testing::Return(&builder));
-  EXPECT_CALL(builder, WithHost("youtube.com")).Times(1).WillOnce(
-      ::testing::Return(&builder));
-  EXPECT_CALL(builder, WithPortWildcard()).Times(1).WillOnce(
-      ::testing::Return(&builder));
+  EXPECT_CALL(builder, WithSchemeWildcard())
+      .Times(1)
+      .WillOnce(::testing::Return(&builder));
+  EXPECT_CALL(builder, WithDomainWildcard())
+      .Times(1)
+      .WillOnce(::testing::Return(&builder));
+  EXPECT_CALL(builder, WithHost("youtube.com"))
+      .Times(1)
+      .WillOnce(::testing::Return(&builder));
+  EXPECT_CALL(builder, WithPortWildcard())
+      .Times(1)
+      .WillOnce(::testing::Return(&builder));
   content_settings::PatternParser::Parse("[*.]youtube.com", &builder);
   ::testing::Mock::VerifyAndClear(&builder);
 
   // Test invalid patterns
-  EXPECT_CALL(builder, WithSchemeWildcard()).Times(1).WillOnce(
-      ::testing::Return(&builder));
-  EXPECT_CALL(builder, Invalid()).Times(1).WillOnce(
-      ::testing::Return(&builder));
+  EXPECT_CALL(builder, WithSchemeWildcard())
+      .Times(1)
+      .WillOnce(::testing::Return(&builder));
+  EXPECT_CALL(builder, Invalid())
+      .Times(1)
+      .WillOnce(::testing::Return(&builder));
   content_settings::PatternParser::Parse("*youtube.com", &builder);
   ::testing::Mock::VerifyAndClear(&builder);
 
-  EXPECT_CALL(builder, WithSchemeWildcard()).Times(1).WillOnce(
-      ::testing::Return(&builder));
-  EXPECT_CALL(builder, Invalid()).Times(1).WillOnce(
-      ::testing::Return(&builder));
+  EXPECT_CALL(builder, WithSchemeWildcard())
+      .Times(1)
+      .WillOnce(::testing::Return(&builder));
+  EXPECT_CALL(builder, Invalid())
+      .Times(1)
+      .WillOnce(::testing::Return(&builder));
   content_settings::PatternParser::Parse("*.youtube.com", &builder);
   ::testing::Mock::VerifyAndClear(&builder);
 
-  EXPECT_CALL(builder, WithSchemeWildcard()).Times(1).WillOnce(
-      ::testing::Return(&builder));
-  EXPECT_CALL(builder, Invalid()).Times(1).WillOnce(
-      ::testing::Return(&builder));
+  EXPECT_CALL(builder, WithSchemeWildcard())
+      .Times(1)
+      .WillOnce(::testing::Return(&builder));
+  EXPECT_CALL(builder, Invalid())
+      .Times(1)
+      .WillOnce(::testing::Return(&builder));
   content_settings::PatternParser::Parse("www.youtube.com*", &builder);
   ::testing::Mock::VerifyAndClear(&builder);
 
@@ -169,56 +206,62 @@
 TEST(ContentSettingsPatternParserTest, ParseFilePatterns) {
   ::testing::StrictMock<MockBuilder> builder;
 
-  EXPECT_CALL(builder, WithScheme("file")).Times(1).WillOnce(
-      ::testing::Return(&builder));
-  EXPECT_CALL(builder, WithPath("/foo/bar/test.html")).Times(1).WillOnce(
-      ::testing::Return(&builder));
-  content_settings::PatternParser::Parse(
-      "file:///foo/bar/test.html", &builder);
+  EXPECT_CALL(builder, WithScheme("file"))
+      .Times(1)
+      .WillOnce(::testing::Return(&builder));
+  EXPECT_CALL(builder, WithPath("/foo/bar/test.html"))
+      .Times(1)
+      .WillOnce(::testing::Return(&builder));
+  content_settings::PatternParser::Parse("file:///foo/bar/test.html", &builder);
   ::testing::Mock::VerifyAndClear(&builder);
 
-  EXPECT_CALL(builder, WithScheme("file")).Times(1).WillOnce(
-      ::testing::Return(&builder));
-  EXPECT_CALL(builder, WithDomainWildcard()).Times(1).WillOnce(
-      ::testing::Return(&builder));
-  content_settings::PatternParser::Parse(
-      "file://*", &builder);
+  EXPECT_CALL(builder, WithScheme("file"))
+      .Times(1)
+      .WillOnce(::testing::Return(&builder));
+  EXPECT_CALL(builder, WithDomainWildcard())
+      .Times(1)
+      .WillOnce(::testing::Return(&builder));
+  content_settings::PatternParser::Parse("file://*", &builder);
   ::testing::Mock::VerifyAndClear(&builder);
 
-  EXPECT_CALL(builder, WithScheme("file")).Times(1).WillOnce(
-      ::testing::Return(&builder));
-  EXPECT_CALL(builder, WithDomainWildcard()).Times(1).WillOnce(
-      ::testing::Return(&builder));
-  EXPECT_CALL(builder, WithPath("/")).Times(1).WillOnce(
-      ::testing::Return(&builder));
-  content_settings::PatternParser::Parse(
-      "file://*/", &builder);
+  EXPECT_CALL(builder, WithScheme("file"))
+      .Times(1)
+      .WillOnce(::testing::Return(&builder));
+  EXPECT_CALL(builder, WithDomainWildcard())
+      .Times(1)
+      .WillOnce(::testing::Return(&builder));
+  EXPECT_CALL(builder, WithPath("/"))
+      .Times(1)
+      .WillOnce(::testing::Return(&builder));
+  content_settings::PatternParser::Parse("file://*/", &builder);
   ::testing::Mock::VerifyAndClear(&builder);
 
-  EXPECT_CALL(builder, WithScheme("file")).Times(1).WillOnce(
-      ::testing::Return(&builder));
-  EXPECT_CALL(builder, WithDomainWildcard()).Times(1).WillOnce(
-      ::testing::Return(&builder));
-  EXPECT_CALL(builder, WithPathWildcard()).Times(1).WillOnce(
-      ::testing::Return(&builder));
-  content_settings::PatternParser::Parse(
-      "file://*/*", &builder);
+  EXPECT_CALL(builder, WithScheme("file"))
+      .Times(1)
+      .WillOnce(::testing::Return(&builder));
+  EXPECT_CALL(builder, WithDomainWildcard())
+      .Times(1)
+      .WillOnce(::testing::Return(&builder));
+  EXPECT_CALL(builder, WithPathWildcard())
+      .Times(1)
+      .WillOnce(::testing::Return(&builder));
+  content_settings::PatternParser::Parse("file://*/*", &builder);
   ::testing::Mock::VerifyAndClear(&builder);
 
-  EXPECT_CALL(builder, WithScheme("file")).Times(1).WillOnce(
-      ::testing::Return(&builder));
-  EXPECT_CALL(builder, WithPathWildcard()).Times(1).WillOnce(
-      ::testing::Return(&builder));
-  content_settings::PatternParser::Parse(
-      "file:///*", &builder);
+  EXPECT_CALL(builder, WithScheme("file"))
+      .Times(1)
+      .WillOnce(::testing::Return(&builder));
+  EXPECT_CALL(builder, WithPathWildcard())
+      .Times(1)
+      .WillOnce(::testing::Return(&builder));
+  content_settings::PatternParser::Parse("file:///*", &builder);
   ::testing::Mock::VerifyAndClear(&builder);
 
   // Invalid file patterns.
   EXPECT_CALL(builder, WithScheme("file"))
       .WillOnce(::testing::Return(&builder));
   EXPECT_CALL(builder, Invalid()).WillOnce(::testing::Return(&builder));
-  content_settings::PatternParser::Parse(
-      "file://**", &builder);
+  content_settings::PatternParser::Parse("file://**", &builder);
   ::testing::Mock::VerifyAndClear(&builder);
 
   EXPECT_CALL(builder, WithScheme("file"))
diff --git a/components/content_settings/core/common/content_settings_pattern_unittest.cc b/components/content_settings/core/common/content_settings_pattern_unittest.cc
index 5a022db8..6e6b80f 100644
--- a/components/content_settings/core/common/content_settings_pattern_unittest.cc
+++ b/components/content_settings/core/common/content_settings_pattern_unittest.cc
@@ -17,8 +17,7 @@
 
 TEST(ContentSettingsPatternTest, RealWorldPatterns) {
   // This is the place for real world patterns that unveiled bugs.
-  EXPECT_STREQ("[*.]ikea.com",
-               Pattern("[*.]ikea.com").ToString().c_str());
+  EXPECT_STREQ("[*.]ikea.com", Pattern("[*.]ikea.com").ToString().c_str());
 }
 
 TEST(ContentSettingsPatternTest, GURL) {
@@ -52,8 +51,8 @@
   // - A domain wildcard is added to the GURL host.
   // - A port wildcard is used instead of the schemes default port.
   //   In case of non-default ports the specific GURL port is used.
-  ContentSettingsPattern pattern = ContentSettingsPattern::FromURL(
-      GURL("http://www.youtube.com"));
+  ContentSettingsPattern pattern =
+      ContentSettingsPattern::FromURL(GURL("http://www.youtube.com"));
   EXPECT_TRUE(pattern.IsValid());
   EXPECT_STREQ("[*.]www.youtube.com", pattern.ToString().c_str());
 
@@ -102,24 +101,24 @@
 TEST(ContentSettingsPatternTest, FilesystemUrls) {
   ContentSettingsPattern pattern =
       ContentSettingsPattern::FromURL(GURL("http://www.google.com"));
-  EXPECT_TRUE(pattern.Matches(
-      GURL("filesystem:http://www.google.com/temporary/")));
-  EXPECT_TRUE(pattern.Matches(
-      GURL("filesystem:http://foo.www.google.com/temporary/")));
-  EXPECT_TRUE(pattern.Matches(
-      GURL("filesystem:http://www.google.com:80/temporary/")));
-  EXPECT_TRUE(pattern.Matches(
-      GURL("filesystem:http://www.google.com:81/temporary/")));
+  EXPECT_TRUE(
+      pattern.Matches(GURL("filesystem:http://www.google.com/temporary/")));
+  EXPECT_TRUE(
+      pattern.Matches(GURL("filesystem:http://foo.www.google.com/temporary/")));
+  EXPECT_TRUE(
+      pattern.Matches(GURL("filesystem:http://www.google.com:80/temporary/")));
+  EXPECT_TRUE(
+      pattern.Matches(GURL("filesystem:http://www.google.com:81/temporary/")));
 
   pattern = ContentSettingsPattern::FromURL(GURL("https://www.google.com"));
-  EXPECT_TRUE(pattern.Matches(
-      GURL("filesystem:https://www.google.com/temporary/")));
+  EXPECT_TRUE(
+      pattern.Matches(GURL("filesystem:https://www.google.com/temporary/")));
   EXPECT_TRUE(pattern.Matches(
       GURL("filesystem:https://www.google.com:443/temporary/")));
   EXPECT_TRUE(pattern.Matches(
       GURL("filesystem:https://foo.www.google.com/temporary/")));
-  EXPECT_FALSE(pattern.Matches(
-      GURL("filesystem:https://www.google.com:81/temporary/")));
+  EXPECT_FALSE(
+      pattern.Matches(GURL("filesystem:https://www.google.com:81/temporary/")));
 
   // A pattern from a filesystem URLs is equivalent to a pattern from the inner
   // URL of the filesystem URL.
@@ -132,20 +131,18 @@
   // TODO(msramek): Filesystem URLs do not return correct paths. For example,
   // GURL("filesystem:file:///temporary/test.txt").inner_url().path() returns
   // only '/temporary' instead of 'temporary/test.txt'. crbug.com/568110.
-  pattern =
-      ContentSettingsPattern::FromURL(
-          GURL("filesystem:file:///temporary/foo/bar"));
+  pattern = ContentSettingsPattern::FromURL(
+      GURL("filesystem:file:///temporary/foo/bar"));
   EXPECT_TRUE(pattern.Matches(GURL("filesystem:file:///temporary/")));
   EXPECT_TRUE(pattern.Matches(GURL("filesystem:file:///temporary/test.txt")));
   EXPECT_TRUE(pattern.Matches(GURL("file:///temporary")));
   EXPECT_FALSE(pattern.Matches(GURL("file://foo/bar")));
-  pattern2 =
-      ContentSettingsPattern::FromURL(
-          GURL("filesystem:file:///persistent/foo2/bar2"));
-  EXPECT_EQ(
-      ContentSettingsPattern::DISJOINT_ORDER_PRE, pattern.Compare(pattern2));
-  EXPECT_EQ(
-      ContentSettingsPattern::DISJOINT_ORDER_POST, pattern2.Compare(pattern));
+  pattern2 = ContentSettingsPattern::FromURL(
+      GURL("filesystem:file:///persistent/foo2/bar2"));
+  EXPECT_EQ(ContentSettingsPattern::DISJOINT_ORDER_PRE,
+            pattern.Compare(pattern2));
+  EXPECT_EQ(ContentSettingsPattern::DISJOINT_ORDER_POST,
+            pattern2.Compare(pattern));
 }
 
 TEST(ContentSettingsPatternTest, FromURLNoWildcard) {
@@ -157,8 +154,8 @@
   // Creating content settings patterns from strings behaves different. Pattern
   // parts that are omitted in pattern specifications (strings), are completed
   // with a wildcard.
-  ContentSettingsPattern pattern = ContentSettingsPattern::FromURLNoWildcard(
-      GURL("http://www.example.com"));
+  ContentSettingsPattern pattern =
+      ContentSettingsPattern::FromURLNoWildcard(GURL("http://www.example.com"));
   EXPECT_TRUE(pattern.IsValid());
   EXPECT_STREQ("http://www.example.com:80", pattern.ToString().c_str());
   EXPECT_TRUE(pattern.Matches(GURL("http://www.example.com")));
@@ -174,18 +171,17 @@
   EXPECT_FALSE(pattern.Matches(GURL("http://foo.www.example.com")));
 
   // Pattern for filesystem URLs
-  pattern =
-      ContentSettingsPattern::FromURLNoWildcard(
-          GURL("filesystem:http://www.google.com/temporary/"));
+  pattern = ContentSettingsPattern::FromURLNoWildcard(
+      GURL("filesystem:http://www.google.com/temporary/"));
   EXPECT_TRUE(pattern.IsValid());
   EXPECT_TRUE(pattern.Matches(GURL("http://www.google.com")));
   EXPECT_FALSE(pattern.Matches(GURL("http://foo.www.google.com")));
-  EXPECT_TRUE(pattern.Matches(
-      GURL("filesystem:http://www.google.com/persistent/")));
-  EXPECT_FALSE(pattern.Matches(
-      GURL("filesystem:https://www.google.com/persistent/")));
-  EXPECT_FALSE(pattern.Matches(
-      GURL("filesystem:https://www.google.com:81/temporary/")));
+  EXPECT_TRUE(
+      pattern.Matches(GURL("filesystem:http://www.google.com/persistent/")));
+  EXPECT_FALSE(
+      pattern.Matches(GURL("filesystem:https://www.google.com/persistent/")));
+  EXPECT_FALSE(
+      pattern.Matches(GURL("filesystem:https://www.google.com:81/temporary/")));
   EXPECT_FALSE(pattern.Matches(
       GURL("filesystem:https://foo.www.google.com/temporary/")));
 
@@ -209,8 +205,10 @@
 TEST(ContentSettingsPatternTest, ValidWildcardFastPath) {
   std::unique_ptr<ContentSettingsPattern::BuilderInterface> builder =
       ContentSettingsPattern::CreateBuilder();
-  builder->WithSchemeWildcard()->WithDomainWildcard()->WithPortWildcard()->
-           WithPathWildcard();
+  builder->WithSchemeWildcard()
+      ->WithDomainWildcard()
+      ->WithPortWildcard()
+      ->WithPathWildcard();
   ContentSettingsPattern built_wildcard = builder->Build();
   EXPECT_EQ(built_wildcard, ContentSettingsPattern::Wildcard());
 }
@@ -222,10 +220,10 @@
       GURL("http://www.google.com")));
   EXPECT_TRUE(ContentSettingsPattern::Wildcard().Matches(
       GURL("https://www.google.com")));
-  EXPECT_TRUE(ContentSettingsPattern::Wildcard().Matches(
-      GURL("https://myhost:8080")));
-  EXPECT_TRUE(ContentSettingsPattern::Wildcard().Matches(
-      GURL("file:///foo/bar.txt")));
+  EXPECT_TRUE(
+      ContentSettingsPattern::Wildcard().Matches(GURL("https://myhost:8080")));
+  EXPECT_TRUE(
+      ContentSettingsPattern::Wildcard().Matches(GURL("file:///foo/bar.txt")));
 
   EXPECT_STREQ("*", ContentSettingsPattern::Wildcard().ToString().c_str());
 
@@ -236,10 +234,10 @@
 
 TEST(ContentSettingsPatternTest, TrimEndingDotFromHost) {
   EXPECT_TRUE(Pattern("www.example.com").IsValid());
-  EXPECT_TRUE(Pattern("www.example.com").Matches(
-      GURL("http://www.example.com")));
-  EXPECT_TRUE(Pattern("www.example.com").Matches(
-      GURL("http://www.example.com.")));
+  EXPECT_TRUE(
+      Pattern("www.example.com").Matches(GURL("http://www.example.com")));
+  EXPECT_TRUE(
+      Pattern("www.example.com").Matches(GURL("http://www.example.com.")));
 
   EXPECT_TRUE(Pattern("www.example.com.").IsValid());
   EXPECT_STREQ("www.example.com",
@@ -316,14 +314,14 @@
   EXPECT_FALSE(Pattern("file:///foo/bar/file.*").IsValid());
 
   // File patterns match URLs with the same path on any host.
-  EXPECT_TRUE(Pattern("file:///foo/bar/file.html").Matches(
-      GURL("file://localhost/foo/bar/file.html")));
-  EXPECT_TRUE(Pattern("file:///foo/bar/file.html").Matches(
-      GURL("file://example.com/foo/bar/file.html")));
-  EXPECT_FALSE(Pattern("file:///foo/bar/file.html").Matches(
-        GURL("file://localhost/foo/bar/other.html")));
-  EXPECT_FALSE(Pattern("file:///foo/bar/file.html").Matches(
-      GURL("file://example.com/foo/bar/other.html")));
+  EXPECT_TRUE(Pattern("file:///foo/bar/file.html")
+                  .Matches(GURL("file://localhost/foo/bar/file.html")));
+  EXPECT_TRUE(Pattern("file:///foo/bar/file.html")
+                  .Matches(GURL("file://example.com/foo/bar/file.html")));
+  EXPECT_FALSE(Pattern("file:///foo/bar/file.html")
+                   .Matches(GURL("file://localhost/foo/bar/other.html")));
+  EXPECT_FALSE(Pattern("file:///foo/bar/file.html")
+                   .Matches(GURL("file://example.com/foo/bar/other.html")));
 
   ContentSettingsPattern pattern =
       ContentSettingsPattern::FromURL(GURL("file:///tmp/test.html"));
@@ -339,28 +337,26 @@
   ContentSettingsPattern pattern3 =
       ContentSettingsPattern::FromString("file:///tmp/other.html");
 
-  EXPECT_EQ(ContentSettingsPattern::IDENTITY,
-            pattern.Compare(pattern));
-  EXPECT_EQ(ContentSettingsPattern::IDENTITY,
-            pattern.Compare(pattern2));
+  EXPECT_EQ(ContentSettingsPattern::IDENTITY, pattern.Compare(pattern));
+  EXPECT_EQ(ContentSettingsPattern::IDENTITY, pattern.Compare(pattern2));
   EXPECT_EQ(ContentSettingsPattern::DISJOINT_ORDER_PRE,
             pattern.Compare(pattern3));
   EXPECT_EQ(ContentSettingsPattern::DISJOINT_ORDER_POST,
             pattern3.Compare(pattern));
-  EXPECT_EQ(ContentSettingsPattern::SUCCESSOR,
-            file_wildcard.Compare(pattern));
+  EXPECT_EQ(ContentSettingsPattern::SUCCESSOR, file_wildcard.Compare(pattern));
   EXPECT_EQ(ContentSettingsPattern::PREDECESSOR,
             pattern.Compare(file_wildcard));
 }
 
 TEST(ContentSettingsPatternTest, FromString_ExtensionPatterns) {
   EXPECT_TRUE(Pattern("chrome-extension://peoadpeiejnhkmpaakpnompolbglelel/")
-      .IsValid());
+                  .IsValid());
   EXPECT_EQ("chrome-extension://peoadpeiejnhkmpaakpnompolbglelel/",
-      Pattern("chrome-extension://peoadpeiejnhkmpaakpnompolbglelel/")
-          .ToString());
+            Pattern("chrome-extension://peoadpeiejnhkmpaakpnompolbglelel/")
+                .ToString());
   EXPECT_TRUE(Pattern("chrome-extension://peoadpeiejnhkmpaakpnompolbglelel/")
-      .Matches(GURL("chrome-extension://peoadpeiejnhkmpaakpnompolbglelel/")));
+                  .Matches(GURL(
+                      "chrome-extension://peoadpeiejnhkmpaakpnompolbglelel/")));
 }
 
 TEST(ContentSettingsPatternTest, FromString_SearchPatterns) {
@@ -412,10 +408,10 @@
   EXPECT_TRUE(Pattern("*://www.google.com.com:8080").IsValid());
   EXPECT_STREQ("www.google.com:8080",
                Pattern("*://www.google.com:8080").ToString().c_str());
-  EXPECT_TRUE(Pattern("*://www.google.com:8080").Matches(
-      GURL("http://www.google.com:8080")));
-  EXPECT_TRUE(Pattern("*://www.google.com:8080").Matches(
-      GURL("https://www.google.com:8080")));
+  EXPECT_TRUE(Pattern("*://www.google.com:8080")
+                  .Matches(GURL("http://www.google.com:8080")));
+  EXPECT_TRUE(Pattern("*://www.google.com:8080")
+                  .Matches(GURL("https://www.google.com:8080")));
   EXPECT_FALSE(
       Pattern("*://www.google.com").Matches(GURL("file:///foo/bar.html")));
 
@@ -437,43 +433,39 @@
   EXPECT_TRUE(Pattern("www.example.com").IsValid());
   EXPECT_STREQ("www.example.com",
                Pattern("www.example.com").ToString().c_str());
-  EXPECT_TRUE(Pattern("www.example.com").Matches(
-      GURL("http://www.example.com/")));
-  EXPECT_FALSE(Pattern("example.com").Matches(
-      GURL("http://example.org/")));
+  EXPECT_TRUE(
+      Pattern("www.example.com").Matches(GURL("http://www.example.com/")));
+  EXPECT_FALSE(Pattern("example.com").Matches(GURL("http://example.org/")));
 
   // Patterns with domain wildcard.
   EXPECT_TRUE(Pattern("[*.]example.com").IsValid());
   EXPECT_STREQ("[*.]example.com",
                Pattern("[*.]example.com").ToString().c_str());
-  EXPECT_TRUE(Pattern("[*.]example.com").Matches(
-      GURL("http://example.com/")));
-  EXPECT_TRUE(Pattern("[*.]example.com").Matches(
-      GURL("http://foo.example.com/")));
-  EXPECT_FALSE(Pattern("[*.]example.com").Matches(
-      GURL("http://example.org/")));
+  EXPECT_TRUE(Pattern("[*.]example.com").Matches(GURL("http://example.com/")));
+  EXPECT_TRUE(
+      Pattern("[*.]example.com").Matches(GURL("http://foo.example.com/")));
+  EXPECT_FALSE(Pattern("[*.]example.com").Matches(GURL("http://example.org/")));
 
-  EXPECT_TRUE(Pattern("[*.]google.com:80").Matches(
-      GURL("http://mail.google.com:80")));
-  EXPECT_FALSE(Pattern("[*.]google.com:80").Matches(
-      GURL("http://mail.google.com:81")));
-  EXPECT_TRUE(Pattern("[*.]google.com:80").Matches(
-      GURL("http://www.google.com")));
+  EXPECT_TRUE(
+      Pattern("[*.]google.com:80").Matches(GURL("http://mail.google.com:80")));
+  EXPECT_FALSE(
+      Pattern("[*.]google.com:80").Matches(GURL("http://mail.google.com:81")));
+  EXPECT_TRUE(
+      Pattern("[*.]google.com:80").Matches(GURL("http://www.google.com")));
 
-  EXPECT_TRUE(Pattern("[*.]google.com:8080").Matches(
-      GURL("http://mail.google.com:8080")));
+  EXPECT_TRUE(Pattern("[*.]google.com:8080")
+                  .Matches(GURL("http://mail.google.com:8080")));
 
-  EXPECT_TRUE(Pattern("[*.]google.com:443").Matches(
-      GURL("https://mail.google.com:443")));
-  EXPECT_TRUE(Pattern("[*.]google.com:443").Matches(
-      GURL("https://www.google.com")));
+  EXPECT_TRUE(Pattern("[*.]google.com:443")
+                  .Matches(GURL("https://mail.google.com:443")));
+  EXPECT_TRUE(
+      Pattern("[*.]google.com:443").Matches(GURL("https://www.google.com")));
 
-  EXPECT_TRUE(Pattern("[*.]google.com:4321").Matches(
-      GURL("https://mail.google.com:4321")));
-  EXPECT_TRUE(Pattern("[*.]example.com").Matches(
-      GURL("http://example.com/")));
-  EXPECT_TRUE(Pattern("[*.]example.com").Matches(
-      GURL("http://www.example.com/")));
+  EXPECT_TRUE(Pattern("[*.]google.com:4321")
+                  .Matches(GURL("https://mail.google.com:4321")));
+  EXPECT_TRUE(Pattern("[*.]example.com").Matches(GURL("http://example.com/")));
+  EXPECT_TRUE(
+      Pattern("[*.]example.com").Matches(GURL("http://www.example.com/")));
 
   // Patterns with host wildcard
   EXPECT_TRUE(Pattern("[*.]").IsValid());
@@ -641,15 +633,14 @@
 
 TEST(ContentSettingsPatternTest, Compare) {
   // Test identical patterns patterns.
-  ContentSettingsPattern pattern1 =
-      Pattern("http://www.google.com");
+  ContentSettingsPattern pattern1 = Pattern("http://www.google.com");
   EXPECT_EQ(ContentSettingsPattern::IDENTITY, pattern1.Compare(pattern1));
   EXPECT_EQ(ContentSettingsPattern::IDENTITY,
-            Pattern("http://www.google.com:80").Compare(
-                Pattern("http://www.google.com:80")));
-  EXPECT_EQ(ContentSettingsPattern::IDENTITY,
-            Pattern("*://[*.]google.com:*").Compare(
-                Pattern("*://[*.]google.com:*")));
+            Pattern("http://www.google.com:80")
+                .Compare(Pattern("http://www.google.com:80")));
+  EXPECT_EQ(
+      ContentSettingsPattern::IDENTITY,
+      Pattern("*://[*.]google.com:*").Compare(Pattern("*://[*.]google.com:*")));
 
   ContentSettingsPattern invalid_pattern1;
   ContentSettingsPattern invalid_pattern2 =
@@ -664,99 +655,89 @@
 
   // Compare a pattern with an IPv4 addresse to a pattern with a domain name.
   EXPECT_EQ(ContentSettingsPattern::DISJOINT_ORDER_POST,
-            Pattern("http://www.google.com").Compare(
-                Pattern("127.0.0.1")));
+            Pattern("http://www.google.com").Compare(Pattern("127.0.0.1")));
   EXPECT_EQ(ContentSettingsPattern::DISJOINT_ORDER_PRE,
-            Pattern("127.0.0.1").Compare(
-                Pattern("http://www.google.com")));
+            Pattern("127.0.0.1").Compare(Pattern("http://www.google.com")));
   EXPECT_TRUE(Pattern("127.0.0.1") > Pattern("http://www.google.com"));
   EXPECT_TRUE(Pattern("http://www.google.com") < Pattern("127.0.0.1"));
 
   // Compare a pattern with an IPv6 address to a patterns with a domain name.
   EXPECT_EQ(ContentSettingsPattern::DISJOINT_ORDER_POST,
-            Pattern("http://www.google.com").Compare(
-                Pattern("[::1]")));
+            Pattern("http://www.google.com").Compare(Pattern("[::1]")));
   EXPECT_EQ(ContentSettingsPattern::DISJOINT_ORDER_PRE,
-            Pattern("[::1]").Compare(
-                Pattern("http://www.google.com")));
+            Pattern("[::1]").Compare(Pattern("http://www.google.com")));
   EXPECT_TRUE(Pattern("[::1]") > Pattern("http://www.google.com"));
   EXPECT_TRUE(Pattern("http://www.google.com") < Pattern("[::1]"));
 
   // Compare a pattern with an IPv6 addresse to a pattern with an IPv4 addresse.
   EXPECT_EQ(ContentSettingsPattern::DISJOINT_ORDER_PRE,
-            Pattern("127.0.0.1").Compare(
-                Pattern("[::1]")));
+            Pattern("127.0.0.1").Compare(Pattern("[::1]")));
   EXPECT_EQ(ContentSettingsPattern::DISJOINT_ORDER_POST,
-            Pattern("[::1]").Compare(
-                Pattern("127.0.0.1")));
+            Pattern("[::1]").Compare(Pattern("127.0.0.1")));
   EXPECT_TRUE(Pattern("[::1]") < Pattern("127.0.0.1"));
   EXPECT_TRUE(Pattern("127.0.0.1") > Pattern("[::1]"));
 
   EXPECT_EQ(ContentSettingsPattern::DISJOINT_ORDER_PRE,
-            Pattern("http://www.google.com").Compare(
-                Pattern("http://www.youtube.com")));
+            Pattern("http://www.google.com")
+                .Compare(Pattern("http://www.youtube.com")));
   EXPECT_EQ(ContentSettingsPattern::DISJOINT_ORDER_PRE,
-            Pattern("http://[*.]google.com").Compare(
-                Pattern("http://[*.]youtube.com")));
+            Pattern("http://[*.]google.com")
+                .Compare(Pattern("http://[*.]youtube.com")));
 
   EXPECT_EQ(ContentSettingsPattern::DISJOINT_ORDER_POST,
-            Pattern("http://[*.]host.com").Compare(
-                Pattern("http://[*.]evilhost.com")));
+            Pattern("http://[*.]host.com")
+                .Compare(Pattern("http://[*.]evilhost.com")));
   EXPECT_EQ(ContentSettingsPattern::DISJOINT_ORDER_POST,
-            Pattern("*://www.google.com:80").Compare(
-                Pattern("*://www.google.com:8080")));
+            Pattern("*://www.google.com:80")
+                .Compare(Pattern("*://www.google.com:8080")));
   EXPECT_EQ(ContentSettingsPattern::DISJOINT_ORDER_PRE,
-            Pattern("https://www.google.com:80").Compare(
-                Pattern("http://www.google.com:80")));
+            Pattern("https://www.google.com:80")
+                .Compare(Pattern("http://www.google.com:80")));
 
   EXPECT_EQ(ContentSettingsPattern::DISJOINT_ORDER_PRE,
-            Pattern("http://[*.]google.com:90").Compare(
-                Pattern("http://mail.google.com:80")));
+            Pattern("http://[*.]google.com:90")
+                .Compare(Pattern("http://mail.google.com:80")));
   EXPECT_EQ(ContentSettingsPattern::DISJOINT_ORDER_PRE,
-            Pattern("https://[*.]google.com:80").Compare(
-                Pattern("http://mail.google.com:80")));
+            Pattern("https://[*.]google.com:80")
+                .Compare(Pattern("http://mail.google.com:80")));
   EXPECT_EQ(ContentSettingsPattern::DISJOINT_ORDER_PRE,
-            Pattern("https://mail.google.com:*").Compare(
-                Pattern("http://mail.google.com:80")));
+            Pattern("https://mail.google.com:*")
+                .Compare(Pattern("http://mail.google.com:80")));
 
   // Test patterns with different precedences.
   EXPECT_EQ(ContentSettingsPattern::PREDECESSOR,
-            Pattern("mail.google.com").Compare(
-                Pattern("[*.]google.com")));
+            Pattern("mail.google.com").Compare(Pattern("[*.]google.com")));
   EXPECT_EQ(ContentSettingsPattern::SUCCESSOR,
-            Pattern("[*.]google.com").Compare(
-                Pattern("mail.google.com")));
+            Pattern("[*.]google.com").Compare(Pattern("mail.google.com")));
   EXPECT_TRUE(Pattern("mail.google.com") > Pattern("[*.]google.com"));
 
   EXPECT_EQ(ContentSettingsPattern::PREDECESSOR,
-            Pattern("[*.]mail.google.com").Compare(
-                Pattern("[*.]google.com")));
+            Pattern("[*.]mail.google.com").Compare(Pattern("[*.]google.com")));
   EXPECT_EQ(ContentSettingsPattern::SUCCESSOR,
-            Pattern("[*.]google.com").Compare(
-                Pattern("[*.]mail.google.com")));
+            Pattern("[*.]google.com").Compare(Pattern("[*.]mail.google.com")));
   EXPECT_TRUE(Pattern("[*.]mail.google.com") > Pattern("[*.]google.com"));
 
-  EXPECT_EQ(ContentSettingsPattern::PREDECESSOR,
-            Pattern("mail.google.com:80").Compare(
-                Pattern("mail.google.com:*")));
-  EXPECT_EQ(ContentSettingsPattern::SUCCESSOR,
-            Pattern("mail.google.com:*").Compare(
-                Pattern("mail.google.com:80")));
+  EXPECT_EQ(
+      ContentSettingsPattern::PREDECESSOR,
+      Pattern("mail.google.com:80").Compare(Pattern("mail.google.com:*")));
+  EXPECT_EQ(
+      ContentSettingsPattern::SUCCESSOR,
+      Pattern("mail.google.com:*").Compare(Pattern("mail.google.com:80")));
   EXPECT_TRUE(Pattern("mail.google.com:80") > Pattern("mail.google.com:*"));
 
   EXPECT_EQ(ContentSettingsPattern::PREDECESSOR,
-            Pattern("https://mail.google.com:*").Compare(
-                Pattern("*://mail.google.com:*")));
+            Pattern("https://mail.google.com:*")
+                .Compare(Pattern("*://mail.google.com:*")));
   EXPECT_EQ(ContentSettingsPattern::SUCCESSOR,
-            Pattern("*://mail.google.com:*").Compare(
-                Pattern("https://mail.google.com:*")));
+            Pattern("*://mail.google.com:*")
+                .Compare(Pattern("https://mail.google.com:*")));
 
   EXPECT_EQ(ContentSettingsPattern::PREDECESSOR,
-            Pattern("*://mail.google.com:80").Compare(
-                Pattern("https://mail.google.com:*")));
+            Pattern("*://mail.google.com:80")
+                .Compare(Pattern("https://mail.google.com:*")));
   EXPECT_EQ(ContentSettingsPattern::SUCCESSOR,
-            Pattern("https://mail.google.com:*").Compare(
-                Pattern("*://mail.google.com:80")));
+            Pattern("https://mail.google.com:*")
+                .Compare(Pattern("*://mail.google.com:80")));
 }
 
 TEST(ContentSettingsPatternTest, CompareSubdomains) {
@@ -791,27 +772,27 @@
   EXPECT_EQ(ContentSettingsPattern::IDENTITY,
             ContentSettingsPattern::Wildcard().Compare(Pattern("*")));
 
-  EXPECT_EQ(ContentSettingsPattern::PREDECESSOR,
-            Pattern("[*.]google.com").Compare(
-                ContentSettingsPattern::Wildcard()));
+  EXPECT_EQ(
+      ContentSettingsPattern::PREDECESSOR,
+      Pattern("[*.]google.com").Compare(ContentSettingsPattern::Wildcard()));
   EXPECT_EQ(ContentSettingsPattern::PREDECESSOR,
             Pattern("[*.]google.com").Compare(Pattern("*")));
 
-  EXPECT_EQ(ContentSettingsPattern::SUCCESSOR,
-            ContentSettingsPattern::Wildcard().Compare(
-                 Pattern("[*.]google.com")));
+  EXPECT_EQ(
+      ContentSettingsPattern::SUCCESSOR,
+      ContentSettingsPattern::Wildcard().Compare(Pattern("[*.]google.com")));
   EXPECT_EQ(ContentSettingsPattern::SUCCESSOR,
             Pattern("*").Compare(Pattern("[*.]google.com")));
 
-  EXPECT_EQ(ContentSettingsPattern::PREDECESSOR,
-            Pattern("mail.google.com").Compare(
-                ContentSettingsPattern::Wildcard()));
+  EXPECT_EQ(
+      ContentSettingsPattern::PREDECESSOR,
+      Pattern("mail.google.com").Compare(ContentSettingsPattern::Wildcard()));
   EXPECT_EQ(ContentSettingsPattern::PREDECESSOR,
             Pattern("mail.google.com").Compare(Pattern("*")));
 
-  EXPECT_EQ(ContentSettingsPattern::SUCCESSOR,
-            ContentSettingsPattern::Wildcard().Compare(
-                 Pattern("mail.google.com")));
+  EXPECT_EQ(
+      ContentSettingsPattern::SUCCESSOR,
+      ContentSettingsPattern::Wildcard().Compare(Pattern("mail.google.com")));
   EXPECT_EQ(ContentSettingsPattern::SUCCESSOR,
             Pattern("*").Compare(Pattern("mail.google.com")));
 }
@@ -823,36 +804,27 @@
   EXPECT_TRUE(Pattern("example.com").IsValid());
   EXPECT_TRUE(Pattern("192.168.0.1").IsValid());
   EXPECT_TRUE(Pattern("[::1]").IsValid());
-  EXPECT_TRUE(
-      Pattern("file:///tmp/test.html").IsValid());
+  EXPECT_TRUE(Pattern("file:///tmp/test.html").IsValid());
   EXPECT_FALSE(Pattern("*example.com").IsValid());
   EXPECT_FALSE(Pattern("example.*").IsValid());
 
-  EXPECT_TRUE(
-      Pattern("http://example.com").IsValid());
-  EXPECT_TRUE(
-      Pattern("https://example.com").IsValid());
+  EXPECT_TRUE(Pattern("http://example.com").IsValid());
+  EXPECT_TRUE(Pattern("https://example.com").IsValid());
 
-  EXPECT_TRUE(Pattern("[*.]example.com").Matches(
-              GURL("http://example.com/")));
-  EXPECT_TRUE(Pattern("[*.]example.com").Matches(
-              GURL("http://www.example.com/")));
-  EXPECT_TRUE(Pattern("www.example.com").Matches(
-              GURL("http://www.example.com/")));
+  EXPECT_TRUE(Pattern("[*.]example.com").Matches(GURL("http://example.com/")));
   EXPECT_TRUE(
-      Pattern("file:///tmp/test.html").Matches(
-              GURL("file:///tmp/test.html")));
+      Pattern("[*.]example.com").Matches(GURL("http://www.example.com/")));
+  EXPECT_TRUE(
+      Pattern("www.example.com").Matches(GURL("http://www.example.com/")));
+  EXPECT_TRUE(
+      Pattern("file:///tmp/test.html").Matches(GURL("file:///tmp/test.html")));
   EXPECT_FALSE(Pattern(std::string()).Matches(GURL("http://www.example.com/")));
-  EXPECT_FALSE(Pattern("[*.]example.com").Matches(
-               GURL("http://example.org/")));
-  EXPECT_FALSE(Pattern("example.com").Matches(
-               GURL("http://example.org/")));
+  EXPECT_FALSE(Pattern("[*.]example.com").Matches(GURL("http://example.org/")));
+  EXPECT_FALSE(Pattern("example.com").Matches(GURL("http://example.org/")));
   EXPECT_FALSE(
-      Pattern("file:///tmp/test.html").Matches(
-               GURL("file:///tmp/other.html")));
+      Pattern("file:///tmp/test.html").Matches(GURL("file:///tmp/other.html")));
   EXPECT_FALSE(
-      Pattern("file:///tmp/test.html").Matches(
-               GURL("http://example.org/")));
+      Pattern("file:///tmp/test.html").Matches(GURL("http://example.org/")));
 }
 
 TEST(ContentSettingsPatternTest, CanonicalizePattern_Legacy) {
diff --git a/components/dbus/menu/menu_property_list_unittest.cc b/components/dbus/menu/menu_property_list_unittest.cc
index 4f969646..6970cb49 100644
--- a/components/dbus/menu/menu_property_list_unittest.cc
+++ b/components/dbus/menu/menu_property_list_unittest.cc
@@ -6,6 +6,7 @@
 
 #include <memory>
 
+#include "base/memory/ref_counted_memory.h"
 #include "base/strings/utf_string_conversions.h"
 #include "build/build_config.h"
 #include "build/chromecast_buildflags.h"
diff --git a/components/download/internal/common/download_worker.cc b/components/download/internal/common/download_worker.cc
index f62c1b6..2acdd6b 100644
--- a/components/download/internal/common/download_worker.cc
+++ b/components/download/internal/common/download_worker.cc
@@ -119,7 +119,7 @@
     std::unique_ptr<InputStream> input_stream,
     URLLoaderFactoryProvider::URLLoaderFactoryProviderPtr
         url_loader_factory_provider,
-    UrlDownloadHandler* downloader,
+    UrlDownloadHandlerID downloader,
     DownloadUrlParameters::OnStartedCallback callback) {
   // |callback| is not used in subsequent requests.
   DCHECK(callback.is_null());
diff --git a/components/download/internal/common/download_worker.h b/components/download/internal/common/download_worker.h
index fabd332b..1dbb98a 100644
--- a/components/download/internal/common/download_worker.h
+++ b/components/download/internal/common/download_worker.h
@@ -63,7 +63,7 @@
       std::unique_ptr<InputStream> input_stream,
       URLLoaderFactoryProvider::URLLoaderFactoryProviderPtr
           url_loader_factory_provider,
-      UrlDownloadHandler* downloader,
+      UrlDownloadHandlerID downloader,
       DownloadUrlParameters::OnStartedCallback callback) override;
   void OnUrlDownloadStopped(UrlDownloadHandlerID downloader) override;
   void OnUrlDownloadHandlerCreated(
diff --git a/components/download/internal/common/in_progress_download_manager.cc b/components/download/internal/common/in_progress_download_manager.cc
index f291aba..2de9e3e 100644
--- a/components/download/internal/common/in_progress_download_manager.cc
+++ b/components/download/internal/common/in_progress_download_manager.cc
@@ -224,7 +224,7 @@
     std::unique_ptr<InputStream> input_stream,
     URLLoaderFactoryProvider::URLLoaderFactoryProviderPtr
         url_loader_factory_provider,
-    UrlDownloadHandler* downloader,
+    UrlDownloadHandlerID downloader,
     DownloadUrlParameters::OnStartedCallback callback) {
   // If a new download's GUID already exists, skip it.
   if (!download_create_info->guid.empty() &&
@@ -237,8 +237,7 @@
   StartDownload(std::move(download_create_info), std::move(input_stream),
                 std::move(url_loader_factory_provider),
                 base::BindOnce(&InProgressDownloadManager::CancelUrlDownload,
-                               weak_factory_.GetWeakPtr(),
-                               base::UnsafeDanglingUntriaged(downloader)),
+                               weak_factory_.GetWeakPtr(), downloader),
                 std::move(callback));
 }
 
@@ -696,9 +695,9 @@
 }
 
 void InProgressDownloadManager::CancelUrlDownload(
-    UrlDownloadHandler* downloader,
+    UrlDownloadHandlerID downloader,
     bool user_cancel) {
-  OnUrlDownloadStopped(reinterpret_cast<UrlDownloadHandlerID>(downloader));
+  OnUrlDownloadStopped(downloader);
 }
 
 }  // namespace download
diff --git a/components/download/internal/common/resource_downloader.cc b/components/download/internal/common/resource_downloader.cc
index e09f061..02aef45 100644
--- a/components/download/internal/common/resource_downloader.cc
+++ b/components/download/internal/common/resource_downloader.cc
@@ -9,6 +9,7 @@
 #include "base/bind.h"
 #include "base/task/single_thread_task_runner.h"
 #include "components/download/public/common/stream_handle_input_stream.h"
+#include "components/download/public/common/url_download_handler.h"
 #include "components/download/public/common/url_loader_factory_provider.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/remote.h"
@@ -253,7 +254,7 @@
               new URLLoaderFactoryProvider(url_loader_factory_),
               base::OnTaskRunnerDeleter(
                   base::SingleThreadTaskRunner::GetCurrentDefault())),
-          this, std::move(callback_)));
+          reinterpret_cast<UrlDownloadHandlerID>(this), std::move(callback_)));
 }
 
 void ResourceDownloader::OnReceiveRedirect() {
diff --git a/components/download/public/common/in_progress_download_manager.h b/components/download/public/common/in_progress_download_manager.h
index f5711673..0f28abd 100644
--- a/components/download/public/common/in_progress_download_manager.h
+++ b/components/download/public/common/in_progress_download_manager.h
@@ -219,7 +219,7 @@
       std::unique_ptr<InputStream> input_stream,
       URLLoaderFactoryProvider::URLLoaderFactoryProviderPtr
           url_loader_factory_provider,
-      UrlDownloadHandler* downloader,
+      UrlDownloadHandlerID downloader,
       DownloadUrlParameters::OnStartedCallback callback) override;
   void OnUrlDownloadStopped(UrlDownloadHandlerID downloader) override;
   void OnUrlDownloadHandlerCreated(
@@ -251,7 +251,7 @@
   void NotifyDownloadsInitialized();
 
   // Cancels the given UrlDownloadHandler.
-  void CancelUrlDownload(UrlDownloadHandler* downloader, bool user_cancel);
+  void CancelUrlDownload(UrlDownloadHandlerID downloader, bool user_cancel);
 
   // Active download handlers.
   std::vector<UrlDownloadHandler::UniqueUrlDownloadHandlerPtr>
diff --git a/components/download/public/common/url_download_handler.h b/components/download/public/common/url_download_handler.h
index 47be2c7..892ec21 100644
--- a/components/download/public/common/url_download_handler.h
+++ b/components/download/public/common/url_download_handler.h
@@ -33,7 +33,7 @@
         std::unique_ptr<InputStream> input_stream,
         URLLoaderFactoryProvider::URLLoaderFactoryProviderPtr
             url_loader_factory_provider,
-        UrlDownloadHandler* downloader,
+        UrlDownloadHandlerID downloader,
         DownloadUrlParameters::OnStartedCallback callback) = 0;
 
     // Called after the connection is cancelled or finished.
diff --git a/components/embedder_support/android/metrics/android_metrics_service_client.h b/components/embedder_support/android/metrics/android_metrics_service_client.h
index e73d8715..241bc67 100644
--- a/components/embedder_support/android/metrics/android_metrics_service_client.h
+++ b/components/embedder_support/android/metrics/android_metrics_service_client.h
@@ -196,6 +196,9 @@
   // should use `GetAppPackageNameIfLoggable`.
   std::string GetAppPackageName();
 
+  // Returns the installer type of the app.
+  virtual InstallerPackageType GetInstallerPackageType();
+
  protected:
   // Called by MaybeStartMetrics() to allow embedder specific initialization.
   virtual void OnMetricsStart() = 0;
@@ -222,9 +225,6 @@
   // considerations.
   virtual bool IsInSample() const;
 
-  // Returns the installer type of the app.
-  virtual InstallerPackageType GetInstallerPackageType();
-
   // Determines if the embedder app is the type of app for which we may log the
   // package name. If this returns false, GetAppPackageNameIfLoggable() must
   // return empty string. Virtual for testing.
diff --git a/components/enterprise/browser/reporting/report_uploader.cc b/components/enterprise/browser/reporting/report_uploader.cc
index 0c5ba2b..9873de3 100644
--- a/components/enterprise/browser/reporting/report_uploader.cc
+++ b/components/enterprise/browser/reporting/report_uploader.cc
@@ -11,7 +11,7 @@
 #include "build/chromeos_buildflags.h"
 #include "components/enterprise/browser/reporting/report_type.h"
 #include "components/policy/core/common/cloud/cloud_policy_client.h"
-#include "device_management_backend.pb.h"
+#include "components/policy/proto/device_management_backend.pb.h"
 
 namespace em = enterprise_management;
 
@@ -60,6 +60,11 @@
     case ReportType::kBrowserVersion: {
       auto request = std::make_unique<ReportRequest::DeviceReportRequestProto>(
           requests_.front()->GetDeviceReportRequest());
+      // Because MessageLite does not support DebugMessage(), print
+      // serialize string for debugging purposes. It's a non-human-friendly
+      // binary string but still provide useful information.
+      VLOG(2) << "Uploading report: " << request->SerializeAsString();
+
 #if BUILDFLAG(IS_CHROMEOS_ASH)
       client_->UploadChromeOsUserReport(std::move(request),
                                         std::move(callback));
@@ -70,10 +75,11 @@
       break;
     }
     case ReportType::kProfileReport: {
-      client_->UploadChromeProfileReport(
-          std::make_unique<em::ChromeProfileReportRequest>(
-              requests_.front()->GetChromeProfileReportRequest()),
-          std::move(callback));
+      auto request = std::make_unique<em::ChromeProfileReportRequest>(
+          requests_.front()->GetChromeProfileReportRequest());
+      VLOG(2) << "Uploading report: " << request->SerializeAsString();
+      client_->UploadChromeProfileReport(std::move(request),
+                                         std::move(callback));
       break;
     }
   }
diff --git a/components/exo/wayland/protocol/aura-shell.xml b/components/exo/wayland/protocol/aura-shell.xml
index 55fed92e..14b2fde5 100644
--- a/components/exo/wayland/protocol/aura-shell.xml
+++ b/components/exo/wayland/protocol/aura-shell.xml
@@ -1046,7 +1046,8 @@
         representations for IEEE 754 floats, this protocol implicitly assumes
         that the caller and receiver are the same machine. To avoid redundant
         messages, this request needs to only be called once when the zaura
-        toplevel scale factor changes.
+        toplevel scale factor changes. This is double buffered state and will be
+        applied in the next commit.
       </description>
       <arg name="scale_factor_as_uint" type="uint"/>
     </request>
diff --git a/components/favicon/core/fallback_url_util.cc b/components/favicon/core/fallback_url_util.cc
index fa3c176..0c553e38 100644
--- a/components/favicon/core/fallback_url_util.cc
+++ b/components/favicon/core/fallback_url_util.cc
@@ -11,6 +11,9 @@
 
 namespace {
 const char* kFallbackIconTextForIP = "IP";
+#if BUILDFLAG(IS_IOS)
+const char* kFallbackIconTextForAndroidApp = "A";
+#endif
 }  // namespace
 
 namespace favicon {
@@ -24,6 +27,12 @@
     if (url.HostIsIPAddress())
       return base::ASCIIToUTF16(kFallbackIconTextForIP);
     domain = url.host();
+
+#if BUILDFLAG(IS_IOS)
+    // Return "A" if it's an Android app URL. iOS only.
+    if (url.is_valid() && url.spec().rfind("android://", 0) == 0)
+      return base::ASCIIToUTF16(kFallbackIconTextForAndroidApp);
+#endif
   }
   if (domain.empty())
     return std::u16string();
diff --git a/components/favicon/core/fallback_url_util_unittest.cc b/components/favicon/core/fallback_url_util_unittest.cc
index 1a12254..26dee76d 100644
--- a/components/favicon/core/fallback_url_util_unittest.cc
+++ b/components/favicon/core/fallback_url_util_unittest.cc
@@ -18,30 +18,34 @@
     const char* url_str;
     const char* expected;
   } test_cases[] = {
-      // Test vacuous or invalid cases.
-      {"", ""},
-      {"http:///", ""},
-      {"this is not an URL", ""},
-      {"!@#$%^&*()", ""},
-      // Test URLs with a domain in the registry.
-      {"http://www.google.com/", "G"},
-      {"ftp://GOogLE.com/", "G"},
-      {"https://www.google.com:8080/path?query#ref", "G"},
-      {"http://www.amazon.com", "A"},
-      {"http://zmzaon.co.uk/", "Z"},
-      {"http://w-3.137.org", "1"},
-      // Test URLs with a domian not in the registry.
-      {"http://localhost/", "L"},
-      {"chrome-search://most-visited/title.html", "M"},
-      // Test IP URLs.
-      {"http://192.168.0.1/", "IP"},
-      {"http://[2001:4860:4860::8888]/", "IP"},
-      // Miscellaneous edge cases.
-      {"http://www..com/", "."},
-      {"http://ip.ip/", "I"},
-      // xn-- related cases: we're not supporint xn-- yet
-      {"http://xn--oogle-60a/", "X"},
-      {"http://xn-oogle-60a/", "X"},
+    // Test vacuous or invalid cases.
+    {"", ""},
+    {"http:///", ""},
+    {"this is not an URL", ""},
+    {"!@#$%^&*()", ""},
+    // Test URLs with a domain in the registry.
+    {"http://www.google.com/", "G"},
+    {"ftp://GOogLE.com/", "G"},
+    {"https://www.google.com:8080/path?query#ref", "G"},
+    {"http://www.amazon.com", "A"},
+    {"http://zmzaon.co.uk/", "Z"},
+    {"http://w-3.137.org", "1"},
+    // Test URLs with a domain not in the registry.
+    {"http://localhost/", "L"},
+    {"chrome-search://most-visited/title.html", "M"},
+    // Test IP URLs.
+    {"http://192.168.0.1/", "IP"},
+    {"http://[2001:4860:4860::8888]/", "IP"},
+#if BUILDFLAG(IS_IOS)
+    // Test Android app URLs.
+    {"android://abc@org.coursera.android//", "A"},
+#endif
+    // Miscellaneous edge cases.
+    {"http://www..com/", "."},
+    {"http://ip.ip/", "I"},
+    // xn-- related cases: we're not supporint xn-- yet
+    {"http://xn--oogle-60a/", "X"},
+    {"http://xn-oogle-60a/", "X"},
   };
   for (size_t i = 0; i < std::size(test_cases); ++i) {
     std::u16string expected = base::ASCIIToUTF16(test_cases[i].expected);
diff --git a/components/feed/feed_feature_list.cc b/components/feed/feed_feature_list.cc
index 5c8d75a..03f22c0 100644
--- a/components/feed/feed_feature_list.cc
+++ b/components/feed/feed_feature_list.cc
@@ -133,7 +133,7 @@
 
 BASE_FEATURE(kPersonalizeFeedNonSyncUsers,
              "PersonalizeFeedNonSyncUsers",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 signin::ConsentLevel GetConsentLevelNeededForPersonalizedFeed() {
   if (!base::FeatureList::IsEnabled(kPersonalizeFeedNonSyncUsers))
diff --git a/components/global_media_controls/public/views/media_item_ui_view_unittest.cc b/components/global_media_controls/public/views/media_item_ui_view_unittest.cc
index c7facff2..b6de419 100644
--- a/components/global_media_controls/public/views/media_item_ui_view_unittest.cc
+++ b/components/global_media_controls/public/views/media_item_ui_view_unittest.cc
@@ -355,17 +355,9 @@
   SimulateHeaderClicked();
 }
 
-// TODO(crbug/1318789): Re-enable this test
-#if BUILDFLAG(IS_CHROMEOS) && defined(ADDRESS_SANITIZER)
-#define MAYBE_GestureScrollDisabledWhenSlidingOut \
-  DISABLED_GestureScrollDisabledWhenSlidingOut
-#else
-#define MAYBE_GestureScrollDisabledWhenSlidingOut \
-  GestureScrollDisabledWhenSlidingOut
-#endif
-TEST_F(MediaItemUIViewTest, MAYBE_GestureScrollDisabledWhenSlidingOut) {
-  auto* scroll_view = new views::ScrollView();
-  item_ui()->SetScrollView(scroll_view);
+TEST_F(MediaItemUIViewTest, GestureScrollDisabledWhenSlidingOut) {
+  auto scroll_view = std::make_unique<views::ScrollView>();
+  item_ui()->SetScrollView(scroll_view.get());
 
   // Vertical scroll bar should be enabled initially.
   EXPECT_EQ(scroll_view->GetVerticalScrollBarMode(),
diff --git a/components/history/core/browser/history_backend.cc b/components/history/core/browser/history_backend.cc
index 0e4147c..f77693c 100644
--- a/components/history/core/browser/history_backend.cc
+++ b/components/history/core/browser/history_backend.cc
@@ -60,6 +60,7 @@
 #include "components/history/core/browser/sync/typed_url_sync_bridge.h"
 #include "components/history/core/browser/url_utils.h"
 #include "components/sync/base/features.h"
+#include "components/sync/base/report_unrecoverable_error.h"
 #include "components/sync/model/client_tag_based_model_type_processor.h"
 #include "components/url_formatter/url_formatter.h"
 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
@@ -386,16 +387,18 @@
   typed_url_sync_bridge_ = std::make_unique<TypedURLSyncBridge>(
       this, db_ ? db_->GetTypedURLMetadataDB() : nullptr,
       std::make_unique<ClientTagBasedModelTypeProcessor>(
-          syncer::TYPED_URLS, /*dump_stack=*/base::RepeatingClosure()));
+          syncer::TYPED_URLS,
+          base::BindRepeating(&syncer::ReportUnrecoverableError,
+                              history_database_params.channel)));
   typed_url_sync_bridge_->Init();
 
   if (base::FeatureList::IsEnabled(syncer::kSyncEnableHistoryDataType)) {
-    // TODO(crbug.com/1318028): Plumb in syncer::ReportUnrecoverableError as the
-    // dump_stack callback.
     history_sync_bridge_ = std::make_unique<HistorySyncBridge>(
         this, db_ ? db_->GetHistoryMetadataDB() : nullptr,
         std::make_unique<ClientTagBasedModelTypeProcessor>(
-            syncer::HISTORY, /*dump_stack=*/base::RepeatingClosure()));
+            syncer::HISTORY,
+            base::BindRepeating(&syncer::ReportUnrecoverableError,
+                                history_database_params.channel)));
   }
 
   if (base::FeatureList::IsEnabled(kDeleteForeignVisitsOnStartup) && db_) {
@@ -2132,6 +2135,20 @@
   ScheduleCommit();
 }
 
+int64_t HistoryBackend::ReserveNextClusterId() {
+  TRACE_EVENT0("browser", "HistoryBackend::ReserveNextClusterId");
+  return db_ ? db_->ReserveNextClusterId() : 0;
+}
+
+void HistoryBackend::AddVisitsToCluster(int64_t cluster_id,
+                                        const std::vector<VisitID>& visits) {
+  TRACE_EVENT0("browser", "HistoryBackend::AddVisitsToCluster");
+  if (!db_)
+    return;
+
+  db_->AddVisitsToCluster(cluster_id, visits);
+}
+
 std::vector<Cluster> HistoryBackend::GetMostRecentClusters(
     base::Time inclusive_min_time,
     base::Time exclusive_max_time,
diff --git a/components/history/core/browser/history_backend.h b/components/history/core/browser/history_backend.h
index 726def5..7be53e0 100644
--- a/components/history/core/browser/history_backend.h
+++ b/components/history/core/browser/history_backend.h
@@ -521,6 +521,11 @@
   void ReplaceClusters(const std::vector<int64_t>& ids_to_delete,
                        const std::vector<Cluster>& clusters_to_add);
 
+  int64_t ReserveNextClusterId();
+
+  void AddVisitsToCluster(int64_t cluster_id,
+                          const std::vector<VisitID>& visits);
+
   std::vector<Cluster> GetMostRecentClusters(
       base::Time inclusive_min_time,
       base::Time exclusive_max_time,
diff --git a/components/history/core/browser/history_backend_unittest.cc b/components/history/core/browser/history_backend_unittest.cc
index 64e4188..85071bf 100644
--- a/components/history/core/browser/history_backend_unittest.cc
+++ b/components/history/core/browser/history_backend_unittest.cc
@@ -4079,6 +4079,26 @@
   VerifyCluster(backend_->GetCluster(2, true), {0});
 }
 
+TEST_F(HistoryBackendTest, ReserveNextClusterId_GetCluster) {
+  int64_t cluster_id = backend_->ReserveNextClusterId();
+
+  // We call from the DB instead of from the backend since the DB does
+  // additional checking around visit count.
+  auto cluster = backend_->db_->GetCluster(cluster_id);
+  EXPECT_EQ(cluster.cluster_id, cluster_id);
+  EXPECT_TRUE(cluster.should_show_on_prominent_ui_surfaces);
+}
+
+TEST_F(HistoryBackendTest, ReserveNextClusterId_AddVisitsToCluster_GetCluster) {
+  int64_t cluster_id = backend_->ReserveNextClusterId();
+
+  AddAnnotatedVisit(1);
+  AddAnnotatedVisit(2);
+  backend_->AddVisitsToCluster(cluster_id, {1, 2});
+
+  VerifyCluster(backend_->GetCluster(cluster_id, false), {cluster_id, {2, 1}});
+}
+
 TEST_F(HistoryBackendTest, GetRedirectChainStart) {
   auto last_visit_time = base::Time::Now();
   const auto add_visit = [&](std::string url, VisitID referring_visit,
diff --git a/components/history/core/browser/history_service.cc b/components/history/core/browser/history_service.cc
index bfbef38..f60f6db5 100644
--- a/components/history/core/browser/history_service.cc
+++ b/components/history/core/browser/history_service.cc
@@ -303,6 +303,29 @@
       std::move(callback));
 }
 
+base::CancelableTaskTracker::TaskId HistoryService::ReserveNextClusterId(
+    ClusterIdCallback callback,
+    base::CancelableTaskTracker* tracker) {
+  DCHECK(backend_task_runner_) << "History service being called after cleanup";
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return tracker->PostTaskAndReplyWithResult(
+      backend_task_runner_.get(), FROM_HERE,
+      base::BindOnce(&HistoryBackend::ReserveNextClusterId, history_backend_),
+      std::move(callback));
+}
+
+base::CancelableTaskTracker::TaskId HistoryService::AddVisitsToCluster(
+    int64_t cluster_id,
+    const std::vector<VisitID>& visits,
+    base::CancelableTaskTracker* tracker) {
+  DCHECK(backend_task_runner_) << "History service being called after cleanup";
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return tracker->PostTask(
+      backend_task_runner_.get(), FROM_HERE,
+      base::BindOnce(&HistoryBackend::AddVisitsToCluster, history_backend_,
+                     cluster_id, visits));
+}
+
 base::CancelableTaskTracker::TaskId HistoryService::GetMostRecentClusters(
     base::Time inclusive_min_time,
     base::Time exclusive_max_time,
diff --git a/components/history/core/browser/history_service.h b/components/history/core/browser/history_service.h
index 8f77306..b1dc74c 100644
--- a/components/history/core/browser/history_service.h
+++ b/components/history/core/browser/history_service.h
@@ -587,6 +587,24 @@
       base::OnceClosure callback,
       base::CancelableTaskTracker* tracker);
 
+  // Implemented and called by `ReserveNextClusterId()` below with the last
+  // cluster ID that was added to the database.
+  using ClusterIdCallback = base::OnceCallback<void(int64_t)>;
+
+  // Adds a cluster with no visits and invokes `callback` with the ID of the
+  // new cluster.
+  // Virtual for testing.
+  virtual base::CancelableTaskTracker::TaskId ReserveNextClusterId(
+      base::OnceCallback<void(int64_t)> callback,
+      base::CancelableTaskTracker* tracker);
+
+  // Adds all visit IDs in `visits` to the cluster `cluster_id`.
+  // Virtual for testing.
+  virtual base::CancelableTaskTracker::TaskId AddVisitsToCluster(
+      int64_t cluster_id,
+      const std::vector<VisitID>& visits,
+      base::CancelableTaskTracker* tracker);
+
   // Get the most recent `Cluster`s within the constraints. The most recent
   // visit of a cluster represents the cluster's time. `max_clusters` is a hard
   // cap. `max_visits_soft_cap` is a soft cap; `GetMostRecentClusters()` will
diff --git a/components/history/core/browser/visit_annotations_database.cc b/components/history/core/browser/visit_annotations_database.cc
index 7190f3c0..0432fc8b 100644
--- a/components/history/core/browser/visit_annotations_database.cc
+++ b/components/history/core/browser/visit_annotations_database.cc
@@ -672,6 +672,53 @@
   }
 }
 
+int64_t VisitAnnotationsDatabase::ReserveNextClusterId() {
+  sql::Statement clusters_statement(GetDB().GetCachedStatement(
+      SQL_FROM_HERE,
+      "INSERT INTO clusters"
+      "(should_show_on_prominent_ui_surfaces,label,raw_label)"
+      "VALUES(?,?,?)"));
+  // Tentatively set all clusters as visible.
+  clusters_statement.BindBool(0, true);
+  clusters_statement.BindString16(1, u"");
+  clusters_statement.BindString16(2, u"");
+  if (!clusters_statement.Run()) {
+    DVLOG(0) << "Failed to execute 'clusters' insert statement";
+  }
+  return GetDB().GetLastInsertRowId();
+}
+
+void VisitAnnotationsDatabase::AddVisitsToCluster(
+    int64_t cluster_id,
+    const std::vector<VisitID>& visits) {
+  DCHECK_GT(cluster_id, 0);
+  sql::Statement clusters_and_visits_statement(GetDB().GetCachedStatement(
+      SQL_FROM_HERE,
+      "INSERT INTO clusters_and_visits"
+      "(cluster_id,visit_id,score,engagement_score,url_for_deduping,"
+      "normalized_url,url_for_display)"
+      "VALUES(?,?,?,?,?,?,?)"));
+
+  // Insert each visit into 'clusters_and_visits'.
+  base::ranges::for_each(visits, [&](const auto visit_id) {
+    DCHECK_GT(visit_id, 0);
+    clusters_and_visits_statement.Reset(true);
+    clusters_and_visits_statement.BindInt64(0, cluster_id);
+    clusters_and_visits_statement.BindInt64(1, visit_id);
+    // Tentatively score everything as 1.0.
+    clusters_and_visits_statement.BindDouble(2, 1.0);
+    // Do not populate these initially.
+    clusters_and_visits_statement.BindDouble(3, 0);
+    clusters_and_visits_statement.BindString(4, "");
+    clusters_and_visits_statement.BindString(5, "");
+    clusters_and_visits_statement.BindString16(6, u"");
+    if (!clusters_and_visits_statement.Run()) {
+      DVLOG(0) << "Failed to execute 'clusters_and_visits' insert statement:  "
+               << "cluster_id = " << cluster_id << ", visit_id = " << visit_id;
+    }
+  });
+}
+
 Cluster VisitAnnotationsDatabase::GetCluster(int64_t cluster_id) {
   DCHECK_GT(cluster_id, 0);
   sql::Statement statement(GetDB().GetCachedStatement(
diff --git a/components/history/core/browser/visit_annotations_database.h b/components/history/core/browser/visit_annotations_database.h
index 426431d..aef8c367 100644
--- a/components/history/core/browser/visit_annotations_database.h
+++ b/components/history/core/browser/visit_annotations_database.h
@@ -83,6 +83,13 @@
   // entries for any `Cluster` that it failed to add.
   void AddClusters(const std::vector<Cluster>& clusters);
 
+  // Adds a cluster with no visits and returns the new cluster's ID.
+  int64_t ReserveNextClusterId();
+
+  // Adds visits to the cluster with id `cluster_id`.
+  void AddVisitsToCluster(int64_t cluster_id,
+                          const std::vector<VisitID>& visits);
+
   // Get a `Cluster`. Does not include the cluster's `visits` or
   // `keyword_to_data_map`.
   Cluster GetCluster(int64_t cluster_id);
diff --git a/components/history_clusters/core/config.cc b/components/history_clusters/core/config.cc
index 99105f6..0ea8134 100644
--- a/components/history_clusters/core/config.cc
+++ b/components/history_clusters/core/config.cc
@@ -378,6 +378,11 @@
             internal::kHistoryClustersNavigationContextClustering,
             "clean_up_duration_minutes",
             context_clustering_clean_up_duration.InMinutes()));
+
+    persist_context_clusters_at_navigation = GetFieldTrialParamByFeatureAsBool(
+        internal::kHistoryClustersNavigationContextClustering,
+        "persist_context_clusters_at_navigation",
+        persist_context_clusters_at_navigation);
   }
 
   // Lonely features without child params.
diff --git a/components/history_clusters/core/config.h b/components/history_clusters/core/config.h
index 54b45e8..4c13f2c4 100644
--- a/components/history_clusters/core/config.h
+++ b/components/history_clusters/core/config.h
@@ -341,8 +341,14 @@
   float search_results_page_ranking_weight = 2.0;
 
   // The `kHistoryClustersNavigationContextClustering` feature and child params.
+
+  // The duration between context clustering clean up passes.
   base::TimeDelta context_clustering_clean_up_duration = base::Minutes(10);
 
+  // Whether to persist the context clusters as the visits are coming in at
+  // navigation time.
+  bool persist_context_clusters_at_navigation = false;
+
   // Lonely features without child params.
 
   // Enables debug info in non-user-visible surfaces, like Chrome Inspector.
diff --git a/components/history_clusters/core/context_clusterer_history_service_observer.cc b/components/history_clusters/core/context_clusterer_history_service_observer.cc
index 0b8eba7a..ef1441f 100644
--- a/components/history_clusters/core/context_clusterer_history_service_observer.cc
+++ b/components/history_clusters/core/context_clusterer_history_service_observer.cc
@@ -41,13 +41,11 @@
     history::HistoryService* history_service,
     TemplateURLService* template_url_service,
     optimization_guide::NewOptimizationGuideDecider* optimization_guide_decider)
-    : template_url_service_(template_url_service),
+    : history_service_(history_service),
+      template_url_service_(template_url_service),
       optimization_guide_decider_(optimization_guide_decider),
       clock_(base::DefaultClock::GetInstance()) {
-  if (history_service) {
-    // History service is only null in tests.
-    history_service_observation_.Observe(history_service);
-  }
+  history_service_observation_.Observe(history_service);
 
   if (optimization_guide_decider_) {
     optimization_guide_decider_->RegisterOptimizationTypes(
@@ -95,7 +93,7 @@
   }
 
   // See what cluster we should add it to.
-  absl::optional<int64_t> cluster_idx;
+  absl::optional<int64_t> cluster_id;
 
   std::vector<history::VisitID> previous_visit_ids_to_check;
   if (new_visit.opener_visit != 0) {
@@ -110,7 +108,7 @@
     for (history::VisitID previous_visit_id : previous_visit_ids_to_check) {
       auto it = visit_id_to_cluster_map_.find(previous_visit_id);
       if (it != visit_id_to_cluster_map_.end()) {
-        cluster_idx = it->second;
+        cluster_id = it->second;
         break;
       }
     }
@@ -118,39 +116,61 @@
     // See if we have clustered the URL. (forward-back, reload, etc.)
     auto it = visit_url_to_cluster_map_.find(normalized_url);
     if (it != visit_url_to_cluster_map_.end()) {
-      cluster_idx = it->second;
+      cluster_id = it->second;
     }
   }
 
   // See if we should add to cluster.
-  if (cluster_idx) {
-    auto& in_progress_cluster = in_progress_clusters_.at(*cluster_idx);
+  if (cluster_id) {
+    auto& in_progress_cluster = in_progress_clusters_.at(*cluster_id);
     if (!ShouldAddVisitToCluster(new_visit, search_terms,
                                  in_progress_cluster)) {
-      FinalizeCluster(*cluster_idx);
+      FinalizeCluster(*cluster_id);
 
-      cluster_idx = absl::nullopt;
+      cluster_id = absl::nullopt;
     }
   }
+  bool is_new_cluster = !cluster_id;
 
   // Add a new cluster if we haven't assigned one already.
-  if (!cluster_idx) {
-    cluster_idx_counter_++;
-    cluster_idx = cluster_idx_counter_;
+  if (is_new_cluster) {
+    cluster_id_counter_++;
+    cluster_id = cluster_id_counter_;
 
-    in_progress_clusters_.emplace(*cluster_idx, InProgressCluster());
+    in_progress_clusters_.emplace(*cluster_id, InProgressCluster());
   }
 
   // Add to cluster maps.
-  auto& in_progress_cluster = in_progress_clusters_.at(*cluster_idx);
+  auto& in_progress_cluster = in_progress_clusters_.at(*cluster_id);
   in_progress_cluster.last_visit_time = new_visit.visit_time;
   in_progress_cluster.visit_urls.insert(normalized_url);
   in_progress_cluster.visit_ids.emplace_back(new_visit.visit_id);
   in_progress_cluster.search_terms = search_terms;
-  visit_id_to_cluster_map_[new_visit.visit_id] = *cluster_idx;
-  visit_url_to_cluster_map_[normalized_url] = *cluster_idx;
+  visit_id_to_cluster_map_[new_visit.visit_id] = *cluster_id;
+  visit_url_to_cluster_map_[normalized_url] = *cluster_id;
 
-  // TODO(b/259466296): Persist visit.
+  if (GetConfig().persist_context_clusters_at_navigation) {
+    // For new clusters, asyncly reserve an ID and have the
+    //   `OnPersistedClusterIdReceived()` callback add the visits.
+    // For clusters created recently for which history service hasn't yet
+    //   returned the IDs, there's already a callback pending that will add the
+    //   visits.
+    // For clusters whose IDs are already known, add the visits here.
+    if (in_progress_cluster.persisted_cluster_id > 0) {
+      // Persist visit to existing cluster.
+      history_service->AddVisitsToCluster(
+          in_progress_cluster.persisted_cluster_id, {new_visit.visit_id},
+          &task_tracker_);
+    } else if (is_new_cluster) {
+      // Cluster creation is async. Reserve next cluster ID and wait to persist
+      // items until it comes back in `OnPersistedClusterIdReceived()`.
+      history_service->ReserveNextClusterId(
+          base::BindOnce(&ContextClustererHistoryServiceObserver::
+                             OnPersistedClusterIdReceived,
+                         weak_ptr_factory_.GetWeakPtr(), *cluster_id),
+          &task_tracker_);
+    }
+  }
 }
 
 void ContextClustererHistoryServiceObserver::OnURLsDeleted(
@@ -185,8 +205,8 @@
   }
 
   // Finalize clusters.
-  for (int64_t cluster_idx : clusters_to_finalize) {
-    FinalizeCluster(cluster_idx);
+  for (int64_t cluster_id : clusters_to_finalize) {
+    FinalizeCluster(cluster_id);
   }
 }
 
@@ -210,8 +230,8 @@
   }
 
   // Finalize clusters.
-  for (int64_t cluster_idx : clusters_to_finalize) {
-    FinalizeCluster(cluster_idx);
+  for (int64_t cluster_id : clusters_to_finalize) {
+    FinalizeCluster(cluster_id);
   }
 
   base::UmaHistogramCounts1000(
@@ -224,12 +244,11 @@
 }
 
 void ContextClustererHistoryServiceObserver::FinalizeCluster(
-    int64_t cluster_idx) {
-  DCHECK(in_progress_clusters_.find(cluster_idx) !=
-         in_progress_clusters_.end());
+    int64_t cluster_id) {
+  DCHECK(in_progress_clusters_.find(cluster_id) != in_progress_clusters_.end());
 
   // Delete relevant visits from in-progress maps.
-  auto& cluster = in_progress_clusters_.at(cluster_idx);
+  auto& cluster = in_progress_clusters_.at(cluster_id);
   for (const auto& visit_url : cluster.visit_urls) {
     visit_url_to_cluster_map_.erase(visit_url);
   }
@@ -239,7 +258,24 @@
 
   // TODO(b/259466296): Kick off persisting keywords and prominence bits.
 
-  in_progress_clusters_.erase(cluster_idx);
+  in_progress_clusters_.erase(cluster_id);
+}
+
+void ContextClustererHistoryServiceObserver::OnPersistedClusterIdReceived(
+    int64_t cluster_id,
+    int64_t persisted_cluster_id) {
+  auto cluster_it = in_progress_clusters_.find(cluster_id);
+  base::UmaHistogramBoolean(
+      "History.Clusters.ContextClusterer.ClusterCleanedUpBeforePersistence",
+      cluster_it == in_progress_clusters_.end());
+  if (cluster_it == in_progress_clusters_.end()) {
+    return;
+  }
+
+  cluster_it->second.persisted_cluster_id = persisted_cluster_id;
+  // Persist all visits we've seen so far.
+  history_service_->AddVisitsToCluster(
+      persisted_cluster_id, cluster_it->second.visit_ids, &task_tracker_);
 }
 
 void ContextClustererHistoryServiceObserver::OverrideClockForTesting(
diff --git a/components/history_clusters/core/context_clusterer_history_service_observer.h b/components/history_clusters/core/context_clusterer_history_service_observer.h
index 1afce648..fe82c70e 100644
--- a/components/history_clusters/core/context_clusterer_history_service_observer.h
+++ b/components/history_clusters/core/context_clusterer_history_service_observer.h
@@ -10,7 +10,9 @@
 
 #include "base/containers/flat_set.h"
 #include "base/memory/raw_ptr.h"
+#include "base/memory/weak_ptr.h"
 #include "base/scoped_observation.h"
+#include "base/task/cancelable_task_tracker.h"
 #include "base/time/clock.h"
 #include "base/timer/timer.h"
 #include "components/history/core/browser/history_service_observer.h"
@@ -42,6 +44,8 @@
   // The search terms associated with this in-progress cluster. It will only be
   // set once if a search visit is part of this in-progress cluster.
   std::u16string search_terms;
+  // The corresponding cluster ID in the persisted database.
+  int64_t persisted_cluster_id = 0;
 };
 
 // A HistoryServiceObserver responsible for grouping visits into clusters.
@@ -78,14 +82,19 @@
   // Cleans up clusters that have not been interacted with for awhile.
   void CleanUpClusters();
 
-  // Finalizes the cluster with index, `cluster_idx`.
-  void FinalizeCluster(int64_t cluster_idx);
+  // Finalizes the cluster with index, `cluster_id`.
+  void FinalizeCluster(int64_t cluster_id);
+
+  // Callback invoked when the History Service returns the cluster ID
+  // (`persisted_cluster_id`) to use for `cluster_id`.
+  void OnPersistedClusterIdReceived(int64_t cluster_id,
+                                    int64_t persisted_cluster_id);
 
   // Overrides `clock_` for testing.
   void OverrideClockForTesting(const base::Clock* clock);
 
   // Returns the number of clusters created since the start of the session.
-  int64_t num_clusters_created() const { return cluster_idx_counter_; }
+  int64_t num_clusters_created() const { return cluster_id_counter_; }
 
   // Mapping from cluster ID to the contents of the in-progress cluster.
   std::map<int64_t, InProgressCluster> in_progress_clusters_;
@@ -98,11 +107,14 @@
   std::map<std::string, int64_t> visit_url_to_cluster_map_;
 
   // A running counter that is used to index the in-progress clusters.
-  int64_t cluster_idx_counter_ = 0;
+  int64_t cluster_id_counter_ = 0;
 
   // Used to invoke `CleanUpClusters()` periodically.
   base::RepeatingTimer clean_up_clusters_repeating_timer_;
 
+  // The History Service that `this` observers. Should never be null.
+  raw_ptr<history::HistoryService> history_service_;
+
   // The Template URL Service used to determine if a visit is a search visit.
   raw_ptr<TemplateURLService> template_url_service_;
 
@@ -118,6 +130,12 @@
   base::ScopedObservation<history::HistoryService,
                           history::HistoryServiceObserver>
       history_service_observation_{this};
+
+  // Task tracker for calls for the history service.
+  base::CancelableTaskTracker task_tracker_;
+
+  base::WeakPtrFactory<ContextClustererHistoryServiceObserver>
+      weak_ptr_factory_{this};
 };
 
 }  // namespace history_clusters
diff --git a/components/history_clusters/core/context_clusterer_history_service_observer_unittest.cc b/components/history_clusters/core/context_clusterer_history_service_observer_unittest.cc
index ef4999de..8d8d726f 100644
--- a/components/history_clusters/core/context_clusterer_history_service_observer_unittest.cc
+++ b/components/history_clusters/core/context_clusterer_history_service_observer_unittest.cc
@@ -4,17 +4,26 @@
 
 #include "components/history_clusters/core/context_clusterer_history_service_observer.h"
 
+#include "base/test/gmock_callback_support.h"
+#include "base/test/gmock_move_support.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/task_environment.h"
+#include "components/history/core/browser/history_service.h"
 #include "components/history_clusters/core/config.h"
 #include "components/optimization_guide/core/new_optimization_guide_decider.h"
 #include "components/search_engines/template_url_service.h"
+#include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace history_clusters {
 
 namespace {
 
+using ::testing::_;
+using ::testing::DoAll;
+using ::testing::Invoke;
+using ::testing::Return;
+
 history::URLRows CreateURLRows(const std::vector<GURL>& urls) {
   history::URLRows url_rows;
   for (const auto& url : urls) {
@@ -71,6 +80,24 @@
      "Not Default"},
 };
 
+class MockHistoryService : public history::HistoryService {
+ public:
+  MockHistoryService() = default;
+
+  MOCK_METHOD(base::CancelableTaskTracker::TaskId,
+              ReserveNextClusterId,
+              (ClusterIdCallback callback,
+               base::CancelableTaskTracker* tracker),
+              (override));
+
+  MOCK_METHOD(base::CancelableTaskTracker::TaskId,
+              AddVisitsToCluster,
+              (int64_t,
+               const std::vector<history::VisitID>&,
+               base::CancelableTaskTracker*),
+              (override));
+};
+
 }  // namespace
 
 class ContextClustererHistoryServiceObserverTest : public testing::Test {
@@ -79,6 +106,9 @@
   ~ContextClustererHistoryServiceObserverTest() override = default;
 
   void SetUp() override {
+    history_service_ =
+        std::make_unique<testing::StrictMock<MockHistoryService>>();
+
     // Set up a simple template URL service with a default search engine.
     template_url_service_ = std::make_unique<TemplateURLService>(
         kTemplateURLData, std::size(kTemplateURLData));
@@ -88,11 +118,38 @@
 
     // Instantiate observer.
     observer_ = std::make_unique<ContextClustererHistoryServiceObserver>(
-        /*history_service=*/nullptr, template_url_service_.get(),
+        history_service_.get(), template_url_service_.get(),
         optimization_guide_decider_.get());
     observer_->OverrideClockForTesting(task_environment_.GetMockClock());
   }
 
+  void TearDown() override {
+    // Just reset to original config at end of each test.
+    Config config;
+    SetConfigForTesting(config);
+  }
+
+  // Sets the config so that we expect to persist clusters and visits using this
+  // code path.
+  void SetPersistenceExpectedConfig() {
+    Config config;
+    config.persist_context_clusters_at_navigation = true;
+    SetConfigForTesting(config);
+  }
+
+  // Closure to capture the cluster ID callback.
+  base::CancelableTaskTracker::TaskId CaptureClusterIdCallbackAndReturn(
+      history::HistoryService::ClusterIdCallback callback,
+      base::CancelableTaskTracker* tracker) {
+    cluster_id_callback_ = std::move(callback);
+    return base::CancelableTaskTracker::TaskId();
+  }
+
+  // Runs the last cluster id callback received with `cluster_id`.
+  void RunLastClusterIdCallbackWithClusterId(int64_t cluster_id) {
+    std::move(cluster_id_callback_).Run(cluster_id);
+  }
+
   // Simulates a visit to URL.
   void VisitURL(const GURL& url,
                 history::VisitID visit_id,
@@ -107,7 +164,7 @@
     new_visit.opener_visit = opener_visit;
     new_visit.referring_visit = referring_visit;
     new_visit.is_known_to_sync = is_known_to_sync;
-    observer_->OnURLVisited(/*history_service=*/nullptr, url_row, new_visit);
+    observer_->OnURLVisited(history_service_.get(), url_row, new_visit);
   }
 
   // Simulates deleting `urls` from history. If `urls` is empty, we will
@@ -117,7 +174,7 @@
         urls.empty() ? history::DeletionInfo::ForAllHistory()
                      : history::DeletionInfo::ForUrls(CreateURLRows(urls),
                                                       /*favicon_urls=*/{});
-    observer_->OnURLsDeleted(/*history_service=*/nullptr, deletion_info);
+    observer_->OnURLsDeleted(history_service_.get(), deletion_info);
   }
 
   // Move clock forward by `time_delta`.
@@ -134,6 +191,9 @@
   // Returns the current time of this task environment's mock clock.
   base::Time Now() { return task_environment_.GetMockClock()->Now(); }
 
+ protected:
+  std::unique_ptr<MockHistoryService> history_service_;
+
  private:
   base::test::TaskEnvironment task_environment_{
       base::test::TaskEnvironment::TimeSource::MOCK_TIME};
@@ -141,26 +201,72 @@
   std::unique_ptr<TemplateURLService> template_url_service_;
   std::unique_ptr<TestOptimizationGuideDecider> optimization_guide_decider_;
   std::unique_ptr<ContextClustererHistoryServiceObserver> observer_;
+
+  history::HistoryService::ClusterIdCallback cluster_id_callback_;
 };
 
 TEST_F(ContextClustererHistoryServiceObserverTest, ClusterOneVisit) {
+  SetPersistenceExpectedConfig();
+  int64_t cluster_id = 123;
+
+  EXPECT_CALL(*history_service_,
+              ReserveNextClusterId(base::test::IsNotNullCallback(), _))
+      .WillOnce(Invoke(this, &ContextClustererHistoryServiceObserverTest::
+                                 CaptureClusterIdCallbackAndReturn));
   VisitURL(GURL("https://example.com"), 1, base::Time::FromTimeT(123));
 
+  std::vector<history::VisitID> visit_ids = {1};
+  EXPECT_CALL(*history_service_, AddVisitsToCluster(cluster_id, visit_ids, _))
+      .WillOnce(Return(base::CancelableTaskTracker::TaskId()));
+  RunLastClusterIdCallbackWithClusterId(cluster_id);
+
   EXPECT_EQ(1, GetNumClustersCreated());
 }
 
 TEST_F(ContextClustererHistoryServiceObserverTest,
        ClusterTwoVisitsTiedByReferringVisit) {
+  SetPersistenceExpectedConfig();
+  int64_t cluster_id = 123;
+
+  EXPECT_CALL(*history_service_,
+              ReserveNextClusterId(base::test::IsNotNullCallback(), _))
+      .WillOnce(Invoke(this, &ContextClustererHistoryServiceObserverTest::
+                                 CaptureClusterIdCallbackAndReturn));
   VisitURL(GURL("https://example.com"), 1, base::Time::FromTimeT(123));
   VisitURL(GURL("https://example.com/2"), 2, base::Time::FromTimeT(123),
            /*opener_visit=*/history::kInvalidVisitID, /*referring_visit=*/1);
 
+  // Should persist all visits for the cluster when callback is run.
+  std::vector<history::VisitID> visit_ids = {1, 2};
+  EXPECT_CALL(*history_service_, AddVisitsToCluster(cluster_id, visit_ids, _))
+      .WillOnce(Return(base::CancelableTaskTracker::TaskId()));
+  RunLastClusterIdCallbackWithClusterId(cluster_id);
+
   EXPECT_EQ(1, GetNumClustersCreated());
 }
 
 TEST_F(ContextClustererHistoryServiceObserverTest,
        ClusterTwoVisitsTiedByOpenerVisit) {
+  SetPersistenceExpectedConfig();
+  int64_t cluster_id = 123;
+
+  EXPECT_CALL(*history_service_,
+              ReserveNextClusterId(base::test::IsNotNullCallback(), _))
+      .WillOnce(Invoke(this, &ContextClustererHistoryServiceObserverTest::
+                                 CaptureClusterIdCallbackAndReturn));
   VisitURL(GURL("https://example.com"), 1, base::Time::FromTimeT(123));
+
+  // Should persist all visits for the cluster when callback is run.
+  std::vector<history::VisitID> visit_ids = {1};
+  EXPECT_CALL(*history_service_, AddVisitsToCluster(cluster_id, visit_ids, _))
+      .WillOnce(Return(base::CancelableTaskTracker::TaskId()));
+  RunLastClusterIdCallbackWithClusterId(cluster_id);
+
+  // Should persist as is since we already have the persisted cluster id at this
+  // visit.
+  visit_ids = {2};
+  EXPECT_CALL(*history_service_, AddVisitsToCluster(cluster_id, visit_ids, _))
+      .WillOnce(Return(base::CancelableTaskTracker::TaskId()));
   VisitURL(GURL("https://example.com/2"), 2, base::Time::FromTimeT(123),
            /*opener_visit=*/1, /*referring_visit=*/history::kInvalidVisitID);
 
@@ -202,12 +308,35 @@
 }
 
 TEST_F(ContextClustererHistoryServiceObserverTest, SplitClusterOnSearchTerm) {
+  SetPersistenceExpectedConfig();
+
+  EXPECT_CALL(*history_service_,
+              ReserveNextClusterId(base::test::IsNotNullCallback(), _))
+      .WillOnce(Invoke(this, &ContextClustererHistoryServiceObserverTest::
+                                 CaptureClusterIdCallbackAndReturn));
   VisitURL(GURL("http://default-engine.com/search?q=foo"), 1,
            base::Time::FromTimeT(123));
+
+  int64_t cluster_id = 123;
+  std::vector<history::VisitID> visit_ids = {1};
+  EXPECT_CALL(*history_service_, AddVisitsToCluster(cluster_id, visit_ids, _))
+      .WillOnce(Return(base::CancelableTaskTracker::TaskId()));
+  RunLastClusterIdCallbackWithClusterId(cluster_id);
+
+  EXPECT_CALL(*history_service_,
+              ReserveNextClusterId(base::test::IsNotNullCallback(), _))
+      .WillOnce(Invoke(this, &ContextClustererHistoryServiceObserverTest::
+                                 CaptureClusterIdCallbackAndReturn));
   VisitURL(GURL("http://default-engine.com/search?q=otherterm"), 2,
            base::Time::FromTimeT(123),
            /*opener_visit=*/1);
 
+  visit_ids = {2};
+  cluster_id = 124;
+  EXPECT_CALL(*history_service_, AddVisitsToCluster(cluster_id, visit_ids, _))
+      .WillOnce(Return(base::CancelableTaskTracker::TaskId()));
+  RunLastClusterIdCallbackWithClusterId(cluster_id);
+
   EXPECT_EQ(2, GetNumClustersCreated());
 }
 
@@ -223,6 +352,8 @@
 }
 
 TEST_F(ContextClustererHistoryServiceObserverTest, SkipsSyncedVisits) {
+  SetPersistenceExpectedConfig();
+
   VisitURL(GURL("https://example.com"), 1, base::Time::FromTimeT(123),
            history::kInvalidVisitID, history::kInvalidVisitID,
            /*is_known_to_sync=*/true);
@@ -231,6 +362,8 @@
 }
 
 TEST_F(ContextClustererHistoryServiceObserverTest, SkipsBlocklistedHost) {
+  SetPersistenceExpectedConfig();
+
   VisitURL(GURL("https://shouldskip.com"), 1, base::Time::FromTimeT(123));
 
   EXPECT_EQ(0, GetNumClustersCreated());
diff --git a/components/js_injection/browser/js_to_browser_messaging.cc b/components/js_injection/browser/js_to_browser_messaging.cc
index f8abe03..3973ebe9 100644
--- a/components/js_injection/browser/js_to_browser_messaging.cc
+++ b/components/js_injection/browser/js_to_browser_messaging.cc
@@ -16,6 +16,7 @@
 #include "content/public/browser/web_contents.h"
 #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
 #include "third_party/blink/public/common/messaging/message_port_descriptor.h"
+#include "third_party/blink/public/common/messaging/string_message_codec.h"
 #include "url/origin.h"
 #include "url/url_util.h"
 
@@ -50,7 +51,7 @@
   ~ReplyProxyImpl() override = default;
 
   // WebMessageReplyProxy:
-  void PostWebMessage(mojom::JsWebMessagePtr message) override {
+  void PostWebMessage(blink::WebMessagePayload message) override {
     java_to_js_messaging_->OnPostMessage(std::move(message));
   }
   bool IsInBackForwardCache() override {
@@ -83,7 +84,7 @@
 }
 
 void JsToBrowserMessaging::PostMessage(
-    mojom::JsWebMessagePtr message,
+    blink::WebMessagePayload message,
     std::vector<blink::MessagePortDescriptor> ports) {
   DCHECK(render_frame_host_);
 
diff --git a/components/js_injection/browser/js_to_browser_messaging.h b/components/js_injection/browser/js_to_browser_messaging.h
index 7e83a19..58b6e01a 100644
--- a/components/js_injection/browser/js_to_browser_messaging.h
+++ b/components/js_injection/browser/js_to_browser_messaging.h
@@ -17,6 +17,7 @@
 #include "mojo/public/cpp/bindings/pending_associated_receiver.h"
 #include "mojo/public/cpp/bindings/pending_associated_remote.h"
 #include "third_party/blink/public/common/messaging/message_port_descriptor.h"
+#include "third_party/blink/public/common/messaging/string_message_codec.h"
 
 namespace content {
 class RenderFrameHost;
@@ -47,7 +48,7 @@
   void OnBackForwardCacheStateChanged();
 
   // mojom::JsToBrowserMessaging implementation.
-  void PostMessage(mojom::JsWebMessagePtr message,
+  void PostMessage(blink::WebMessagePayload message,
                    std::vector<blink::MessagePortDescriptor> ports) override;
   void SetBrowserToJsMessaging(
       mojo::PendingAssociatedRemote<mojom::BrowserToJsMessaging>
diff --git a/components/js_injection/browser/web_message.h b/components/js_injection/browser/web_message.h
index 355b70c..342b108d4 100644
--- a/components/js_injection/browser/web_message.h
+++ b/components/js_injection/browser/web_message.h
@@ -7,8 +7,8 @@
 
 #include <vector>
 
-#include "components/js_injection/common/interfaces.mojom.h"
 #include "third_party/blink/public/common/messaging/message_port_descriptor.h"
+#include "third_party/blink/public/common/messaging/string_message_codec.h"
 
 namespace js_injection {
 
@@ -17,7 +17,7 @@
   WebMessage();
   ~WebMessage();
 
-  mojom::JsWebMessagePtr message;
+  blink::WebMessagePayload message;
   std::vector<blink::MessagePortDescriptor> ports;
 };
 
diff --git a/components/js_injection/browser/web_message_reply_proxy.h b/components/js_injection/browser/web_message_reply_proxy.h
index 8b1316a1..263ca5a4 100644
--- a/components/js_injection/browser/web_message_reply_proxy.h
+++ b/components/js_injection/browser/web_message_reply_proxy.h
@@ -5,7 +5,7 @@
 #ifndef COMPONENTS_JS_INJECTION_BROWSER_WEB_MESSAGE_REPLY_PROXY_H_
 #define COMPONENTS_JS_INJECTION_BROWSER_WEB_MESSAGE_REPLY_PROXY_H_
 
-#include "components/js_injection/common/interfaces.mojom-forward.h"
+#include "third_party/blink/public/common/messaging/string_message_codec.h"
 
 namespace content {
 class Page;
@@ -19,7 +19,7 @@
   // To match the JavaScript call, this function would ideally be named
   // PostMessage(), but that conflicts with a Windows macro, so PostWebMessage()
   // is used.
-  virtual void PostWebMessage(mojom::JsWebMessagePtr message) = 0;
+  virtual void PostWebMessage(blink::WebMessagePayload message) = 0;
 
   // Returns true if the page associated with the channel is in the back
   // forward cache.
diff --git a/components/js_injection/common/BUILD.gn b/components/js_injection/common/BUILD.gn
index e42923c..1229136 100644
--- a/components/js_injection/common/BUILD.gn
+++ b/components/js_injection/common/BUILD.gn
@@ -35,6 +35,18 @@
       traits_sources = [ "origin_matcher_mojom_traits.cc" ]
       traits_public_deps = [ ":common" ]
     },
+    {
+      types = [
+        {
+          mojom = "js_injection.mojom.JsWebMessage"
+          cpp = "::blink::WebMessagePayload"
+          move_only = true
+        },
+      ]
+      traits_headers = [ "web_message_mojom_traits.h" ]
+      traits_sources = [ "web_message_mojom_traits.cc" ]
+      traits_public_deps = [ "//third_party/blink/public/common" ]
+    },
   ]
   overridden_deps = [ "//third_party/blink/public/mojom:mojom_core" ]
   component_deps = [ "//third_party/blink/public/common" ]
@@ -59,7 +71,10 @@
 
 source_set("unit_tests") {
   testonly = true
-  sources = [ "origin_matcher_unittest.cc" ]
+  sources = [
+    "origin_matcher_unittest.cc",
+    "web_message_mojom_traits_unittest.cc",
+  ]
   deps = [
     ":common",
     ":common_mojom",
diff --git a/components/js_injection/common/web_message_mojom_traits.cc b/components/js_injection/common/web_message_mojom_traits.cc
new file mode 100644
index 0000000..b5d17a1
--- /dev/null
+++ b/components/js_injection/common/web_message_mojom_traits.cc
@@ -0,0 +1,58 @@
+// 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 "components/js_injection/common/web_message_mojom_traits.h"
+
+#include <string>
+
+#include "base/functional/overloaded.h"
+#include "components/js_injection/common/interfaces.mojom.h"
+#include "mojo/public/cpp/base/big_buffer.h"
+#include "mojo/public/cpp/bindings/union_traits.h"
+#include "third_party/abseil-cpp/absl/types/variant.h"
+#include "third_party/blink/public/common/messaging/string_message_codec.h"
+
+namespace mojo {
+
+// static
+js_injection::mojom::JsWebMessageDataView::Tag UnionTraits<
+    js_injection::mojom::JsWebMessageDataView,
+    blink::WebMessagePayload>::GetTag(const blink::WebMessagePayload& payload) {
+  return absl::visit(
+      base::Overloaded{
+          [](const std::u16string&) {
+            return js_injection::mojom::JsWebMessageDataView::Tag::kStringValue;
+          },
+          [](const std::unique_ptr<blink::WebMessageArrayBufferPayload>&) {
+            return js_injection::mojom::JsWebMessageDataView::Tag::
+                kArrayBufferValue;
+          }},
+      payload);
+}
+
+// static
+bool UnionTraits<
+    js_injection::mojom::JsWebMessageDataView,
+    blink::WebMessagePayload>::Read(js_injection::mojom::JsWebMessageDataView r,
+                                    blink::WebMessagePayload* out) {
+  if (r.is_string_value()) {
+    std::u16string string_value;
+    if (!r.ReadStringValue(&string_value))
+      return false;
+    out->emplace<std::u16string>(std::move(string_value));
+  } else if (r.is_array_buffer_value()) {
+    mojo_base::BigBufferView big_buffer_view;
+    if (!r.ReadArrayBufferValue(&big_buffer_view))
+      return false;
+    out->emplace<std::unique_ptr<blink::WebMessageArrayBufferPayload>>(
+        blink::WebMessageArrayBufferPayload::CreateFromBigBuffer(
+            mojo_base::BigBufferView::ToBigBuffer(std::move(big_buffer_view))));
+  } else {
+    return false;
+  }
+
+  return true;
+}
+
+}  // namespace mojo
diff --git a/components/js_injection/common/web_message_mojom_traits.h b/components/js_injection/common/web_message_mojom_traits.h
new file mode 100644
index 0000000..f5a5a7a
--- /dev/null
+++ b/components/js_injection/common/web_message_mojom_traits.h
@@ -0,0 +1,45 @@
+// 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 COMPONENTS_JS_INJECTION_COMMON_WEB_MESSAGE_MOJOM_TRAITS_H_
+#define COMPONENTS_JS_INJECTION_COMMON_WEB_MESSAGE_MOJOM_TRAITS_H_
+
+#include <string>
+
+#include "components/js_injection/common/interfaces.mojom-shared.h"
+#include "mojo/public/cpp/base/big_buffer.h"
+#include "mojo/public/cpp/base/big_buffer_mojom_traits.h"
+#include "mojo/public/cpp/bindings/union_traits.h"
+#include "third_party/blink/public/common/messaging/string_message_codec.h"
+
+namespace mojo {
+
+template <>
+struct UnionTraits<js_injection::mojom::JsWebMessageDataView,
+                   blink::WebMessagePayload> {
+  static const std::u16string& string_value(
+      const blink::WebMessagePayload& payload) {
+    return absl::get<std::u16string>(payload);
+  }
+
+  static mojo_base::BigBuffer array_buffer_value(
+      const blink::WebMessagePayload& payload) {
+    auto& array_buffer =
+        absl::get<std::unique_ptr<blink::WebMessageArrayBufferPayload>>(
+            payload);
+    auto big_buffer = mojo_base::BigBuffer(array_buffer->GetLength());
+    array_buffer->CopyInto(big_buffer);
+    return big_buffer;
+  }
+
+  static js_injection::mojom::JsWebMessageDataView::Tag GetTag(
+      const blink::WebMessagePayload& payload);
+
+  static bool Read(js_injection::mojom::JsWebMessageDataView r,
+                   blink::WebMessagePayload* out);
+};
+
+}  // namespace mojo
+
+#endif
diff --git a/components/js_injection/common/web_message_mojom_traits_unittest.cc b/components/js_injection/common/web_message_mojom_traits_unittest.cc
new file mode 100644
index 0000000..199efa1c
--- /dev/null
+++ b/components/js_injection/common/web_message_mojom_traits_unittest.cc
@@ -0,0 +1,41 @@
+// 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 "components/js_injection/common/web_message_mojom_traits.h"
+
+#include "components/js_injection/common/interfaces.mojom.h"
+#include "mojo/public/cpp/test_support/test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/messaging/string_message_codec.h"
+
+namespace js_injection {
+
+TEST(WebMessageMojomTraitsTest, StringRoundTrip) {
+  const std::u16string kString = u"hello";
+  blink::WebMessagePayload payload = kString;
+
+  blink::WebMessagePayload output;
+  EXPECT_TRUE(mojo::test::SerializeAndDeserialize<mojom::JsWebMessage>(payload,
+                                                                       output));
+
+  EXPECT_EQ(kString, absl::get<std::u16string>(output));
+}
+
+TEST(WebMessageMojomTraitsTest, ArrayBufferRoundTrip) {
+  std::vector<uint8_t> kArrayBuffer = {0x01, 0x02, 0x03, 0x04, 0x05};
+  blink::WebMessagePayload payload =
+      blink::WebMessageArrayBufferPayload::CreateForTesting(kArrayBuffer);
+
+  blink::WebMessagePayload output;
+  EXPECT_TRUE(mojo::test::SerializeAndDeserialize<mojom::JsWebMessage>(payload,
+                                                                       output));
+
+  auto& array_buffer_output =
+      absl::get<std::unique_ptr<blink::WebMessageArrayBufferPayload>>(output);
+  std::vector<uint8_t> output_vector(array_buffer_output->GetLength());
+  array_buffer_output->CopyInto(output_vector);
+  EXPECT_EQ(kArrayBuffer, output_vector);
+}
+
+}  // namespace js_injection
diff --git a/components/js_injection/renderer/js_binding.cc b/components/js_injection/renderer/js_binding.cc
index 883d8cd..f63a41b 100644
--- a/components/js_injection/renderer/js_binding.cc
+++ b/components/js_injection/renderer/js_binding.cc
@@ -8,6 +8,7 @@
 #include <vector>
 
 #include "base/containers/contains.h"
+#include "base/functional/overloaded.h"
 #include "base/ranges/algorithm.h"
 #include "base/strings/string_util.h"
 #include "components/js_injection/common/interfaces.mojom-forward.h"
@@ -16,8 +17,10 @@
 #include "gin/data_object_builder.h"
 #include "gin/handle.h"
 #include "gin/object_template_builder.h"
+#include "third_party/abseil-cpp/absl/types/variant.h"
 #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
 #include "third_party/blink/public/common/messaging/message_port_channel.h"
+#include "third_party/blink/public/common/messaging/string_message_codec.h"
 #include "third_party/blink/public/platform/web_security_origin.h"
 #include "third_party/blink/public/web/blink.h"
 #include "third_party/blink/public/web/web_frame.h"
@@ -90,7 +93,7 @@
 
 JsBinding::~JsBinding() = default;
 
-void JsBinding::OnPostMessage(mojom::JsWebMessagePtr message) {
+void JsBinding::OnPostMessage(blink::WebMessagePayload message) {
   // If `js_communication_` is null, this object will soon be destroyed.
   if (!js_communication_)
     return;
@@ -112,20 +115,23 @@
   v8::TryCatch try_catch(isolate);
   try_catch.SetVerbose(true);
 
-  v8::Local<v8::Value> v8_message;
-  if (message->is_string_value()) {
-    v8_message =
-        gin::ConvertToV8(isolate, std::move(message->get_string_value()));
-  } else if (message->is_array_buffer_value()) {
-    auto& big_buffer = message->get_array_buffer_value();
-    auto backing_store =
-        v8::ArrayBuffer::NewBackingStore(isolate, big_buffer.size());
-    CHECK(backing_store->ByteLength() == big_buffer.size());
-    memcpy(backing_store->Data(), big_buffer.data(), big_buffer.size());
-    v8_message = v8::ArrayBuffer::New(isolate, std::move(backing_store));
-  } else {
-    NOTREACHED() << "Unknown JsWebMessage type.";
-  }
+  v8::Local<v8::Value> v8_message = absl::visit(
+      base::Overloaded{
+          [isolate](std::u16string& string_value) -> v8::Local<v8::Value> {
+            return gin::ConvertToV8(isolate, std::move(string_value));
+          },
+          [isolate](std::unique_ptr<blink::WebMessageArrayBufferPayload>&
+                        array_buffer_value) -> v8::Local<v8::Value> {
+            auto backing_store = v8::ArrayBuffer::NewBackingStore(
+                isolate, array_buffer_value->GetLength());
+            CHECK(backing_store->ByteLength() ==
+                  array_buffer_value->GetLength());
+            array_buffer_value->CopyInto(
+                base::make_span(static_cast<uint8_t*>(backing_store->Data()),
+                                backing_store->ByteLength()));
+            return v8::ArrayBuffer::New(isolate, std::move(backing_store));
+          }},
+      message);
 
   // Simulate MessageEvent's data property. See
   // https://html.spec.whatwg.org/multipage/comms.html#messageevent
@@ -202,8 +208,7 @@
                         : nullptr;
   if (js_to_java_messaging) {
     js_to_java_messaging->PostMessage(
-        mojom::JsWebMessage::NewStringValue(std::move(message)),
-        blink::MessagePortChannel::ReleaseHandles(ports));
+        std::move(message), blink::MessagePortChannel::ReleaseHandles(ports));
   }
 }
 
diff --git a/components/js_injection/renderer/js_binding.h b/components/js_injection/renderer/js_binding.h
index 8ab2da75..ea147bd 100644
--- a/components/js_injection/renderer/js_binding.h
+++ b/components/js_injection/renderer/js_binding.h
@@ -14,6 +14,7 @@
 #include "gin/wrappable.h"
 #include "mojo/public/cpp/bindings/associated_receiver.h"
 #include "mojo/public/cpp/bindings/associated_remote.h"
+#include "third_party/blink/public/common/messaging/string_message_codec.h"
 
 namespace v8 {
 template <typename T>
@@ -45,7 +46,7 @@
       base::WeakPtr<JsCommunication> js_communication);
 
   // mojom::BrowserToJsMessaging implementation.
-  void OnPostMessage(mojom::JsWebMessagePtr message) override;
+  void OnPostMessage(blink::WebMessagePayload message) override;
 
   void ReleaseV8GlobalObjects();
 
diff --git a/components/keep_alive_registry/keep_alive_types.cc b/components/keep_alive_registry/keep_alive_types.cc
index 84c032e..10ea7d3 100644
--- a/components/keep_alive_registry/keep_alive_types.cc
+++ b/components/keep_alive_registry/keep_alive_types.cc
@@ -78,6 +78,8 @@
       return out << "APP_START_URL_MIGRATION";
     case KeepAliveOrigin::APP_GET_INFO:
       return out << "APP_GET_INFO";
+    case KeepAliveOrigin::WEB_APP_LAUNCH:
+      return out << "WEB_APP_LAUNCH";
     case KeepAliveOrigin::SESSION_DATA_DELETER:
       return out << "SESSION_DATA_DELETER";
   }
diff --git a/components/keep_alive_registry/keep_alive_types.h b/components/keep_alive_registry/keep_alive_types.h
index 0da9bb513..33c7406 100644
--- a/components/keep_alive_registry/keep_alive_types.h
+++ b/components/keep_alive_registry/keep_alive_types.h
@@ -74,6 +74,7 @@
   APP_MANIFEST_UPDATE,
   APP_START_URL_MIGRATION,
   APP_GET_INFO,
+  WEB_APP_LAUNCH,
 
   // c/b/sessions
   SESSION_DATA_DELETER,
diff --git a/components/lens/lens_features.cc b/components/lens/lens_features.cc
index ee7328f5..6669fb1 100644
--- a/components/lens/lens_features.cc
+++ b/components/lens/lens_features.cc
@@ -20,7 +20,7 @@
 
 BASE_FEATURE(kLensSearchOptimizations,
              "LensSearchOptimizations",
-             base::FEATURE_ENABLED_BY_DEFAULT);
+             base::FEATURE_DISABLED_BY_DEFAULT);
 
 BASE_FEATURE(kLensSearchImageInScreenshotSharing,
              "LensSearchImageInScreenshotSharing",
@@ -38,10 +38,6 @@
              "LensEnableRegionSearchOnPdfViewer",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
-BASE_FEATURE(kLensInstructionChipImprovements,
-             "LensInstructionChipImprovements",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 BASE_FEATURE(kEnableImageSearchSidePanelFor3PDse,
              "EnableImageSearchSidePanelFor3PDse",
              base::FEATURE_ENABLED_BY_DEFAULT);
@@ -78,18 +74,6 @@
 constexpr base::FeatureParam<int> kMaxPixelsForImageSearch{
     &kLensImageCompression, "dimensions-max-pixels", 1000};
 
-const base::FeatureParam<bool> kUseGoogleAsVisualSearchProvider{
-    &kLensSearchOptimizations, "use-google-as-visual-search-provider", true};
-
-const base::FeatureParam<bool> kRegionSearchUseMenuItemAltText1{
-    &kLensSearchOptimizations, "use-menu-item-alt-text-1", false};
-
-const base::FeatureParam<bool> kRegionSearchUseMenuItemAltText2{
-    &kLensSearchOptimizations, "use-menu-item-alt-text-2", false};
-
-const base::FeatureParam<bool> kRegionSearchUseMenuItemAltText3{
-    &kLensSearchOptimizations, "use-menu-item-alt-text-3", false};
-
 const base::FeatureParam<bool> kUseSidePanelForScreenshotSharing{
     &kLensSearchImageInScreenshotSharing,
     "use-side-panel-for-screenshot-sharing", false};
@@ -97,14 +81,8 @@
 const base::FeatureParam<bool> kEnablePersistentBubble{
     &kLensSearchImageInScreenshotSharing, "enable-persistent-bubble", false};
 
-const base::FeatureParam<bool> kUseSelectionIconWithImage{
-    &kLensInstructionChipImprovements, "use-selection-icon-with-image", false};
-
-const base::FeatureParam<bool> kUseAltChipString{
-    &kLensInstructionChipImprovements, "use-alt-chip-string", false};
-
 const base::FeatureParam<bool> kEnableLensFullscreenSearch{
-    &kLensSearchOptimizations, "enable-lens-fullscreen-search", false};
+    &kLensSearchOptimizations, "enable-lens-fullscreen-search", true};
 
 const base::FeatureParam<bool> kUseWebpInImageSearch{
     &kLensImageFormatOptimizations, "use-webp-image-search", true};
@@ -158,30 +136,6 @@
   return base::FeatureList::IsEnabled(kEnableImageSearchSidePanelFor3PDse);
 }
 
-bool UseRegionSearchMenuItemAltText1() {
-  return base::FeatureList::IsEnabled(kLensStandalone) &&
-         base::FeatureList::IsEnabled(kLensSearchOptimizations) &&
-         kRegionSearchUseMenuItemAltText1.Get();
-}
-
-bool UseRegionSearchMenuItemAltText2() {
-  return base::FeatureList::IsEnabled(kLensStandalone) &&
-         base::FeatureList::IsEnabled(kLensSearchOptimizations) &&
-         kRegionSearchUseMenuItemAltText2.Get();
-}
-
-bool UseRegionSearchMenuItemAltText3() {
-  return base::FeatureList::IsEnabled(kLensStandalone) &&
-         base::FeatureList::IsEnabled(kLensSearchOptimizations) &&
-         kRegionSearchUseMenuItemAltText3.Get();
-}
-
-bool UseGoogleAsVisualSearchProvider() {
-  return base::FeatureList::IsEnabled(kLensStandalone) &&
-         base::FeatureList::IsEnabled(kLensSearchOptimizations) &&
-         kUseGoogleAsVisualSearchProvider.Get();
-}
-
 bool IsLensFullscreenSearchEnabled() {
   return base::FeatureList::IsEnabled(kLensStandalone) &&
          base::FeatureList::IsEnabled(kLensSearchOptimizations) &&
@@ -202,20 +156,6 @@
          base::FeatureList::IsEnabled(kLensSearchImageInScreenshotSharing);
 }
 
-bool IsLensInstructionChipImprovementsEnabled() {
-  return base::FeatureList::IsEnabled(kLensStandalone) &&
-         base::FeatureList::IsEnabled(kLensInstructionChipImprovements);
-}
-
-bool UseSelectionIconWithImage() {
-  return IsLensInstructionChipImprovementsEnabled() &&
-         kUseSelectionIconWithImage.Get();
-}
-
-bool UseAltChipString() {
-  return IsLensInstructionChipImprovementsEnabled() && kUseAltChipString.Get();
-}
-
 // Does not check if kLensSearchImageInScreenshotSharing is enabled because this
 // method is not called if kLensSearchImageInScreenshotSharing is false
 bool UseSidePanelForScreenshotSharing() {
diff --git a/components/media_router/common/providers/cast/channel/cast_socket.cc b/components/media_router/common/providers/cast/channel/cast_socket.cc
index f6464d2..120d58cd 100644
--- a/components/media_router/common/providers/cast/channel/cast_socket.cc
+++ b/components/media_router/common/providers/cast/channel/cast_socket.cc
@@ -281,9 +281,13 @@
         policy {
           cookies_allowed: NO
           setting:
-            "This request cannot be disabled, but it would not be sent if user "
-            "does not connect a Cast device to the local network."
-          policy_exception_justification: "Not implemented."
+            "This request cannot be disabled in settings, but it would not be "
+            "sent if user does not connect a Cast device to the local network."
+          chrome_policy {
+            EnableMediaRouter {
+              EnableMediaRouter: false
+            }
+          }
         })");
 }
 
diff --git a/components/metrics/file_metrics_provider.cc b/components/metrics/file_metrics_provider.cc
index 9366d87..bb37689 100644
--- a/components/metrics/file_metrics_provider.cc
+++ b/components/metrics/file_metrics_provider.cc
@@ -4,7 +4,10 @@
 
 #include "components/metrics/file_metrics_provider.h"
 
+#include <stddef.h>
+
 #include <memory>
+#include <vector>
 
 #include "base/bind.h"
 #include "base/command_line.h"
@@ -193,10 +196,7 @@
 FileMetricsProvider::Params::~Params() {}
 
 FileMetricsProvider::FileMetricsProvider(PrefService* local_state)
-    : task_runner_(CreateBackgroundTaskRunner()),
-      pref_service_(local_state),
-      main_task_runner_(base::SingleThreadTaskRunner::GetCurrentDefault()) {
-  DCHECK(main_task_runner_);
+    : task_runner_(CreateBackgroundTaskRunner()), pref_service_(local_state) {
   base::StatisticsRecorder::RegisterHistogramProvider(
       weak_factory_.GetWeakPtr());
 }
@@ -385,10 +385,13 @@
   }
 }
 
-void FileMetricsProvider::CheckAndMergeMetricSourcesOnTaskRunner(
+// static
+std::vector<size_t> FileMetricsProvider::CheckAndMergeMetricSourcesOnTaskRunner(
     SourceInfoList* sources) {
   // This method has all state information passed in |sources| and is intended
   // to run on a worker thread rather than the UI thread.
+  std::vector<size_t> samples_counts;
+
   for (std::unique_ptr<SourceInfo>& source : *sources) {
     AccessResult result;
     do {
@@ -413,7 +416,7 @@
           break;
 
         if (source->association == ASSOCIATE_INTERNAL_PROFILE_SAMPLES_COUNTER) {
-          RecordFileMetadataOnTaskRunner(source.get());
+          samples_counts.push_back(CollectFileMetadataFromSource(source.get()));
         } else {
           MergeHistogramDeltasFromSource(source.get());
         }
@@ -433,6 +436,8 @@
     if (source->found_files && source->found_files->empty())
       source->found_files.reset();
   }
+
+  return samples_counts;
 }
 
 // This method has all state information passed in |source| and is intended
@@ -681,23 +686,25 @@
   return false;
 }
 
-void FileMetricsProvider::AppendToSamplesCountPref(size_t samples_count) {
+void FileMetricsProvider::AppendToSamplesCountPref(
+    std::vector<size_t> samples_counts) {
   ScopedListPrefUpdate update(pref_service_,
                               metrics::prefs::kMetricsFileMetricsMetadata);
-  update->Append(static_cast<int>(samples_count));
+  for (size_t samples_count : samples_counts) {
+    update->Append(static_cast<int>(samples_count));
+  }
 }
 
-void FileMetricsProvider::RecordFileMetadataOnTaskRunner(SourceInfo* source) {
+// static
+size_t FileMetricsProvider::CollectFileMetadataFromSource(SourceInfo* source) {
   base::HistogramBase::Count samples_count = 0;
   base::PersistentHistogramAllocator::Iterator it{source->allocator.get()};
   std::unique_ptr<base::HistogramBase> histogram;
   while ((histogram = it.GetNext()) != nullptr) {
     samples_count += histogram->SnapshotFinalDelta()->TotalCount();
   }
-  main_task_runner_->PostTask(
-      FROM_HERE, base::BindOnce(&FileMetricsProvider::AppendToSamplesCountPref,
-                                base::Unretained(this), samples_count));
   source->read_complete = true;
+  return samples_count;
 }
 
 void FileMetricsProvider::ScheduleSourcesCheck() {
@@ -712,18 +719,22 @@
   // because that must complete before the reply runs.
   SourceInfoList* check_list = new SourceInfoList();
   std::swap(sources_to_check_, *check_list);
-  task_runner_->PostTaskAndReply(
+  task_runner_->PostTaskAndReplyWithResult(
       FROM_HERE,
       base::BindOnce(
           &FileMetricsProvider::CheckAndMergeMetricSourcesOnTaskRunner,
-          base::UnsafeDanglingUntriaged(this), base::Unretained(check_list)),
+          base::Unretained(check_list)),
       base::BindOnce(&FileMetricsProvider::RecordSourcesChecked,
                      weak_factory_.GetWeakPtr(), base::Owned(check_list)));
 }
 
-void FileMetricsProvider::RecordSourcesChecked(SourceInfoList* checked) {
+void FileMetricsProvider::RecordSourcesChecked(
+    SourceInfoList* checked,
+    std::vector<size_t> samples_counts) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
+  AppendToSamplesCountPref(std::move(samples_counts));
+
   // Sources that still have an allocator at this point are read/write "active"
   // files that may need their contents merged on-demand. If there is no
   // allocator (not a read/write file) but a read was done on the task-runner,
diff --git a/components/metrics/file_metrics_provider.h b/components/metrics/file_metrics_provider.h
index 6d4c6460f..a887a9b 100644
--- a/components/metrics/file_metrics_provider.h
+++ b/components/metrics/file_metrics_provider.h
@@ -5,8 +5,11 @@
 #ifndef COMPONENTS_METRICS_FILE_METRICS_PROVIDER_H_
 #define COMPONENTS_METRICS_FILE_METRICS_PROVIDER_H_
 
+#include <stddef.h>
+
 #include <list>
 #include <memory>
+#include <vector>
 
 #include "base/callback_forward.h"
 #include "base/files/file_path.h"
@@ -268,7 +271,10 @@
 
   // Checks a list of sources (on a task-runner allowed to do I/O) and merge
   // any data found within them.
-  void CheckAndMergeMetricSourcesOnTaskRunner(SourceInfoList* sources);
+  // Returns a list of histogram sample counts for sources of type
+  // ASSOCIATE_INTERNAL_PROFILE_SAMPLES_COUNTER that were processed.
+  static std::vector<size_t> CheckAndMergeMetricSourcesOnTaskRunner(
+      SourceInfoList* sources);
 
   // Checks a single source and maps it into memory.
   static AccessResult CheckAndMapMetricSource(SourceInfo* source);
@@ -291,18 +297,20 @@
       SystemProfileProto* system_profile_proto,
       base::HistogramSnapshotManager* snapshot_manager);
 
-  // Records the metadata of the |source| to perf.
-  void RecordFileMetadataOnTaskRunner(SourceInfo* source);
+  // Collects the metadata of the |source|.
+  // Returns the number of histogram samples from that source.
+  static size_t CollectFileMetadataFromSource(SourceInfo* source);
 
   // Appends the samples count to pref on UI thread.
-  void AppendToSamplesCountPref(size_t samples_count);
+  void AppendToSamplesCountPref(std::vector<size_t> samples_count);
 
   // Creates a task to check all monitored sources for updates.
   void ScheduleSourcesCheck();
 
   // Takes a list of sources checked by an external task and determines what
   // to do with each.
-  void RecordSourcesChecked(SourceInfoList* checked);
+  void RecordSourcesChecked(SourceInfoList* checked,
+                            std::vector<size_t> samples_counts);
 
   // Schedules the deletion of a file in the background using the task-runner.
   void DeleteFileAsync(const base::FilePath& path);
@@ -355,8 +363,6 @@
   // The preferences-service used to store persistent state about sources.
   raw_ptr<PrefService> pref_service_;
 
-  const scoped_refptr<base::TaskRunner> main_task_runner_;
-
   SEQUENCE_CHECKER(sequence_checker_);
   base::WeakPtrFactory<FileMetricsProvider> weak_factory_{this};
 };
diff --git a/components/metrics/metrics_features.cc b/components/metrics/metrics_features.cc
index a246005b..4dd8435 100644
--- a/components/metrics/metrics_features.cc
+++ b/components/metrics/metrics_features.cc
@@ -11,4 +11,8 @@
 
 const base::FeatureParam<bool> kEmitHistogramsForIndependentLogs{
     &kEmitHistogramsEarlier, "emit_for_independent_logs", false};
+
+BASE_FEATURE(kMetricsServiceAsyncCollection,
+             "MetricsServiceAsyncCollection",
+             base::FEATURE_DISABLED_BY_DEFAULT);
 }  // namespace metrics::features
\ No newline at end of file
diff --git a/components/metrics/metrics_features.h b/components/metrics/metrics_features.h
index e1a12ff1..2f80fd3 100644
--- a/components/metrics/metrics_features.h
+++ b/components/metrics/metrics_features.h
@@ -17,6 +17,10 @@
 // If set, histograms that are expected to be set on every log will be emitted
 // in DisableRecording().
 extern const base::FeatureParam<bool> kEmitHistogramsForIndependentLogs;
+
+// Determines whether the metrics service should create periodic logs
+// asynchronously.
+BASE_DECLARE_FEATURE(kMetricsServiceAsyncCollection);
 }  // namespace metrics::features
 
 #endif  // COMPONENTS_METRICS_METRICS_FEATURES_H_
\ No newline at end of file
diff --git a/components/metrics/metrics_service.cc b/components/metrics/metrics_service.cc
index bc11b7e..5a81727 100644
--- a/components/metrics/metrics_service.cc
+++ b/components/metrics/metrics_service.cc
@@ -129,6 +129,7 @@
 #include "base/bind.h"
 #include "base/callback.h"
 #include "base/callback_list.h"
+#include "base/functional/bind.h"
 #include "base/location.h"
 #include "base/metrics/histogram_base.h"
 #include "base/metrics/histogram_flattener.h"
@@ -136,12 +137,12 @@
 #include "base/metrics/histogram_macros.h"
 #include "base/metrics/histogram_samples.h"
 #include "base/metrics/persistent_histogram_allocator.h"
-#include "base/metrics/statistics_recorder.h"
 #include "base/process/process_handle.h"
 #include "base/rand_util.h"
 #include "base/strings/string_piece.h"
 #include "base/task/sequenced_task_runner.h"
 #include "base/task/single_thread_task_runner.h"
+#include "base/task/thread_pool.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
@@ -404,6 +405,8 @@
 
   delegating_provider_.OnRecordingDisabled();
 
+  base::UmaHistogramBoolean("UMA.MetricsService.PendingOngoingLogOnDisable",
+                            pending_ongoing_log_);
   PushPendingLogsToPersistentStorage();
 
   // If kEmitHistogramsForIndependentLogs is set, call OnDidCreateMetricsLog()
@@ -469,6 +472,9 @@
   // persisting all logs. Unlinke a shutdown, the state is primed to be ready
   // to continue logging and uploading if the process does return.
   if (recording_active() && state_ >= SENDING_LOGS) {
+    base::UmaHistogramBoolean(
+        "UMA.MetricsService.PendingOngoingLogOnBackgrounded",
+        pending_ongoing_log_);
     PushPendingLogsToPersistentStorage();
     // Persisting logs closes the current log, so start recording a new log
     // immediately to capture any background work that might be done before the
@@ -483,6 +489,9 @@
   StartSchedulerIfNecessary();
 
   if (force_open_new_log && recording_active() && state_ >= SENDING_LOGS) {
+    base::UmaHistogramBoolean(
+        "UMA.MetricsService.PendingOngoingLogOnForegrounded",
+        pending_ongoing_log_);
     // Because state_ >= SENDING_LOGS, PushPendingLogsToPersistentStorage()
     // will close the log, allowing a new log to be opened.
     PushPendingLogsToPersistentStorage();
@@ -610,7 +619,7 @@
 }
 
 bool MetricsService::StageCurrentLogForTest() {
-  CloseCurrentLog();
+  CloseCurrentLog(/*async=*/false);
 
   MetricsLogStore* const log_store = reporting_service_.metrics_log_store();
   log_store->StageNextLog();
@@ -748,11 +757,13 @@
 //------------------------------------------------------------------------------
 // Recording control methods
 
-void MetricsService::OpenNewLog() {
+void MetricsService::OpenNewLog(bool call_providers) {
   DCHECK(!log_manager_.current_log());
 
   log_manager_.BeginLoggingWithLog(CreateLog(MetricsLog::ONGOING_LOG));
-  delegating_provider_.OnDidCreateMetricsLog();
+  if (call_providers) {
+    delegating_provider_.OnDidCreateMetricsLog();
+  }
 
   DCHECK_NE(CONSTRUCTED, state_);
   if (state_ == INITIALIZED) {
@@ -791,19 +802,26 @@
     : required_flags_(required_flags),
       flattener_(std::make_unique<IndependentFlattener>(log)),
       histogram_snapshot_manager_(
-          std::make_unique<base::HistogramSnapshotManager>(flattener_.get())) {}
+          std::make_unique<base::HistogramSnapshotManager>(flattener_.get())),
+      snapshot_transaction_id_(0) {}
 
 MetricsService::MetricsLogHistogramWriter::~MetricsLogHistogramWriter() =
     default;
 
 void MetricsService::MetricsLogHistogramWriter::
-    SnapshotStatisticsRecorderHistograms() {
-  base::StatisticsRecorder::PrepareDeltas(
+    SnapshotStatisticsRecorderDeltas() {
+  snapshot_transaction_id_ = base::StatisticsRecorder::PrepareDeltas(
       /*include_persistent=*/true,
       /*flags_to_set=*/base::Histogram::kNoFlags, required_flags_,
       histogram_snapshot_manager_.get());
 }
 
+void MetricsService::MetricsLogHistogramWriter::
+    SnapshotStatisticsRecorderUnloggedSamples() {
+  snapshot_transaction_id_ = base::StatisticsRecorder::SnapshotUnloggedSamples(
+      required_flags_, histogram_snapshot_manager_.get());
+}
+
 MetricsService::IndependentMetricsLoader::IndependentMetricsLoader(
     std::unique_ptr<MetricsLog> log)
     : log_(std::move(log)),
@@ -829,7 +847,8 @@
       &MetricsService::FinishedInitTask, self_ptr_factory_.GetWeakPtr()));
 }
 
-void MetricsService::CloseCurrentLog() {
+void MetricsService::CloseCurrentLog(bool async,
+                                     base::OnceClosure log_stored_callback) {
   if (!log_manager_.current_log())
     return;
 
@@ -863,11 +882,43 @@
 
   MetricsLog::LogType log_type = current_log->log_type();
   std::string signing_key = log_store()->GetSigningKeyForLogType(log_type);
-  FinalizedLog finalized_log = SnapshotHistogramsAndFinalizeLog(
-      std::move(log_histogram_writer), std::move(current_log),
-      /*truncate_events=*/true, client_->GetVersionString(),
-      std::move(signing_key));
-  StoreFinalizedLog(log_type, base::DoNothing(), std::move(finalized_log));
+  if (async) {
+    // To finalize the log asynchronously, we snapshot the unlogged samples of
+    // histograms and fill them into the log, without actually marking the
+    // samples as logged. We only mark them as logged after running the main
+    // thread reply task to store the log. This way, we will not lose the
+    // samples in case Chrome closes while the background task is running. Note
+    // that while this async log is being finalized, it is possible that another
+    // log is finalized and stored synchronously, which could potentially cause
+    // the same samples to be in two different logs, and hence sent twice. To
+    // prevent this, if a synchronous log is stored while the async one is being
+    // finalized, we discard the async log as it would be a subset of the
+    // synchronous one (in terms of histograms). For more details, see
+    // MaybeCleanUpAndStoreFinalizedLog().
+    //
+    // TODO(crbug/1052796): Find a way to save the other data such as user
+    // actions and omnibox events when we discard an async log.
+    MetricsLogHistogramWriter* log_histogram_writer_ptr =
+        log_histogram_writer.get();
+    base::ThreadPool::PostTaskAndReplyWithResult(
+        FROM_HERE,
+        base::BindOnce(&MetricsService::SnapshotUnloggedSamplesAndFinalizeLog,
+                       log_histogram_writer_ptr, std::move(current_log),
+                       /*truncate_events=*/true, client_->GetVersionString(),
+                       std::move(signing_key)),
+        base::BindOnce(&MetricsService::MaybeCleanUpAndStoreFinalizedLog,
+                       self_ptr_factory_.GetWeakPtr(),
+                       std::move(log_histogram_writer), log_type,
+                       std::move(log_stored_callback)));
+    async_ongoing_log_posted_time_ = base::TimeTicks::Now();
+  } else {
+    FinalizedLog finalized_log = SnapshotDeltasAndFinalizeLog(
+        std::move(log_histogram_writer), std::move(current_log),
+        /*truncate_events=*/true, client_->GetVersionString(),
+        std::move(signing_key));
+    StoreFinalizedLog(log_type, std::move(log_stored_callback),
+                      std::move(finalized_log));
+  }
 }
 
 void MetricsService::StoreFinalizedLog(MetricsLog::LogType log_type,
@@ -878,11 +929,65 @@
   std::move(done_callback).Run();
 }
 
+void MetricsService::MaybeCleanUpAndStoreFinalizedLog(
+    std::unique_ptr<MetricsLogHistogramWriter> log_histogram_writer,
+    MetricsLog::LogType log_type,
+    base::OnceClosure done_callback,
+    FinalizedLog finalized_log) {
+  UMA_HISTOGRAM_TIMES("UMA.MetricsService.PeriodicOngoingLog.ReplyTime",
+                      base::TimeTicks::Now() - async_ongoing_log_posted_time_);
+
+  // Store the finalized log only if the StatisticRecorder's last transaction ID
+  // is the same as the one from |log_histogram_writer|. If they are not the
+  // same, then it indicates that another log was created while creating
+  // |finalized_log| (that log would be a superset of |finalized_log| in terms
+  // of histograms, so we discard |finalized_log| by not storing it).
+  //
+  // TODO(crbug/1052796): Find a way to save the other data such as user actions
+  // and omnibox events when we discard |finalized_log|.
+  //
+  // Note that the call to StatisticsRecorder::GetLastSnapshotTransactionId()
+  // here should not have to wait for a lock since there should not be any async
+  // logs being created (|rotation_scheduler_| is only re-scheduled at the end
+  // of this method).
+  bool should_store_log =
+      (base::StatisticsRecorder::GetLastSnapshotTransactionId() ==
+       log_histogram_writer->snapshot_transaction_id());
+  base::UmaHistogramBoolean("UMA.MetricsService.ShouldStoreAsyncLog",
+                            should_store_log);
+
+  if (!should_store_log) {
+    // We still need to run |done_callback| even if we do not store the log.
+    std::move(done_callback).Run();
+    return;
+  }
+
+  SCOPED_UMA_HISTOGRAM_TIMER(
+      "UMA.MetricsService.MaybeCleanUpAndStoreFinalizedLog.Time");
+
+  log_histogram_writer->histogram_snapshot_manager()
+      ->MarkUnloggedSamplesAsLogged();
+  StoreFinalizedLog(log_type, std::move(done_callback),
+                    std::move(finalized_log));
+
+  // Call OnDidCreateMetricsLog() after storing a log instead of directly after
+  // opening a log. Otherwise, the async log that was created would potentially
+  // have mistakenly snapshotted the histograms intended for the newly opened
+  // log.
+  delegating_provider_.OnDidCreateMetricsLog();
+}
+
 void MetricsService::PushPendingLogsToPersistentStorage() {
   if (state_ < SENDING_LOGS)
     return;  // We didn't and still don't have time to get plugin list etc.
 
-  CloseCurrentLog();
+  base::UmaHistogramBoolean("UMA.MetricsService.PendingOngoingLog",
+                            pending_ongoing_log_);
+
+  // Close and store a log synchronously because this is usually called in
+  // critical code paths (e.g., shutdown) where we may not have time to run
+  // background tasks.
+  CloseCurrentLog(/*async=*/false);
   log_store()->TrimAndPersistUnsentLogs(/*overwrite_in_memory_store=*/true);
 }
 
@@ -956,8 +1061,36 @@
     return;
   }
 
-  CloseCurrentLog();
-  OpenNewLog();
+  SCOPED_UMA_HISTOGRAM_TIMER("UMA.MetricsService.PeriodicOngoingLog.CloseTime");
+
+  // There shouldn't be two periodic ongoing logs being finalized in the
+  // background simultaneously. This is currently enforced because:
+  // 1. Only periodic ongoing logs are finalized asynchronously (i.e., logs
+  //    created by the MetricsRotationScheduler).
+  // 2. We only re-schedule the MetricsRotationScheduler after storing a
+  //    periodic ongoing log.
+  //
+  // TODO(crbug/1052796): Consider making it possible to have multiple
+  // simultaneous async logs by having some queueing system (e.g., if we want
+  // the log created when foregrounding Chrome to be async).
+  DCHECK(!pending_ongoing_log_);
+  pending_ongoing_log_ = true;
+
+  base::OnceClosure log_stored_callback =
+      base::BindOnce(&MetricsService::OnPeriodicOngoingLogStored,
+                     self_ptr_factory_.GetWeakPtr());
+  if (base::FeatureList::IsEnabled(features::kMetricsServiceAsyncCollection)) {
+    CloseCurrentLog(/*async=*/true, std::move(log_stored_callback));
+    OpenNewLog(/*call_providers=*/false);
+  } else {
+    CloseCurrentLog(/*async=*/false, std::move(log_stored_callback));
+    OpenNewLog();
+  }
+}
+
+void MetricsService::OnPeriodicOngoingLogStored() {
+  pending_ongoing_log_ = false;
+
   // Trim and store unsent logs, including the log that was just closed, so that
   // they're not lost in case of a crash before upload time. However, the
   // in-memory log store is unchanged. I.e., logs that are trimmed will still be
@@ -967,9 +1100,17 @@
   // in-memory log store will be updated.
   log_store()->TrimAndPersistUnsentLogs(/*overwrite_in_memory_store=*/false);
 
-  reporting_service_.Start();
-  rotation_scheduler_->RotationFinished();
-  HandleIdleSinceLastTransmission(true);
+  // Do not re-schedule if metrics were turned off while finalizing the log.
+  if (!recording_active()) {
+    rotation_scheduler_->Stop();
+    rotation_scheduler_->RotationFinished();
+  } else {
+    // Only re-schedule |rotation_scheduler_| *after* the log was stored to
+    // ensure that only one log is created asynchronously at a time.
+    reporting_service_.Start();
+    rotation_scheduler_->RotationFinished();
+    HandleIdleSinceLastTransmission(true);
+  }
 }
 
 bool MetricsService::PrepareInitialStabilityLog(
@@ -981,6 +1122,7 @@
 
   // Do not call OnDidCreateMetricsLog here because the stability log describes
   // stats from the _previous_ session.
+
   if (!initial_stability_log->LoadSavedEnvironmentFromPrefs(local_state_))
     return false;
 
@@ -997,7 +1139,10 @@
 
   MetricsLog::LogType log_type = initial_stability_log->log_type();
   std::string signing_key = log_store()->GetSigningKeyForLogType(log_type);
-  FinalizedLog finalized_log = SnapshotHistogramsAndFinalizeLog(
+
+  // Synchronously create the initial stability log in order to ensure that the
+  // stability histograms are filled into this specific log.
+  FinalizedLog finalized_log = SnapshotDeltasAndFinalizeLog(
       std::move(log_histogram_writer), std::move(initial_stability_log),
       /*truncate_events=*/false, client_->GetVersionString(),
       std::move(signing_key));
@@ -1162,13 +1307,26 @@
 }
 
 // static
-MetricsService::FinalizedLog MetricsService::SnapshotHistogramsAndFinalizeLog(
+MetricsService::FinalizedLog MetricsService::SnapshotDeltasAndFinalizeLog(
     std::unique_ptr<MetricsLogHistogramWriter> log_histogram_writer,
     std::unique_ptr<MetricsLog> log,
     bool truncate_events,
     std::string current_app_version,
     std::string signing_key) {
-  log_histogram_writer->SnapshotStatisticsRecorderHistograms();
+  log_histogram_writer->SnapshotStatisticsRecorderDeltas();
+  return FinalizeLog(std::move(log), truncate_events,
+                     std::move(current_app_version), std::move(signing_key));
+}
+
+// static
+MetricsService::FinalizedLog
+MetricsService::SnapshotUnloggedSamplesAndFinalizeLog(
+    MetricsLogHistogramWriter* log_histogram_writer,
+    std::unique_ptr<MetricsLog> log,
+    bool truncate_events,
+    std::string current_app_version,
+    std::string signing_key) {
+  log_histogram_writer->SnapshotStatisticsRecorderUnloggedSamples();
   return FinalizeLog(std::move(log), truncate_events,
                      std::move(current_app_version), std::move(signing_key));
 }
diff --git a/components/metrics/metrics_service.h b/components/metrics/metrics_service.h
index 685c9106..6802a861f 100644
--- a/components/metrics/metrics_service.h
+++ b/components/metrics/metrics_service.h
@@ -23,6 +23,7 @@
 #include "base/metrics/field_trial.h"
 #include "base/metrics/histogram_flattener.h"
 #include "base/metrics/histogram_snapshot_manager.h"
+#include "base/metrics/statistics_recorder.h"
 #include "base/metrics/user_metrics.h"
 #include "base/observer_list.h"
 #include "base/scoped_observation.h"
@@ -333,24 +334,41 @@
 
     ~MetricsLogHistogramWriter();
 
-    // Snapshots histograms known by the StatisticsRecorder and writes them to
-    // |log_|'s proto.
-    void SnapshotStatisticsRecorderHistograms();
+    // Snapshots the deltas of histograms known by the StatisticsRecorder and
+    // writes them to the log passed in the constructor. This also marks the
+    // samples (the deltas) as logged.
+    void SnapshotStatisticsRecorderDeltas();
+
+    // Snapshots the unlogged samples of histograms known by the
+    // StatisticsRecorder and writes them to the log passed in the constructor.
+    // Note that unlike SnapshotStatisticsRecorderDeltas(), this does not mark
+    // the samples as logged. To do so, a call to MarkUnloggedSamplesAsLogged()
+    // (in |histogram_snapshot_manager_|) should be made.
+    void SnapshotStatisticsRecorderUnloggedSamples();
 
     base::HistogramSnapshotManager* histogram_snapshot_manager() {
       return histogram_snapshot_manager_.get();
     }
 
+    base::StatisticsRecorder::SnapshotTransactionId snapshot_transaction_id() {
+      return snapshot_transaction_id_;
+    }
+
    private:
     // Used to select which histograms to record when calling
     // SnapshotStatisticsRecorderHistograms().
     const base::HistogramBase::Flags required_flags_;
 
-    // Used to write histograms to |log_|.
+    // Used to write histograms to the log passed in the constructor.
     std::unique_ptr<base::HistogramFlattener> flattener_;
 
     // Used to snapshot histograms.
     std::unique_ptr<base::HistogramSnapshotManager> histogram_snapshot_manager_;
+
+    // The snapshot transaction ID of a call to either
+    // SnapshotStatisticsRecorderDeltas() or
+    // SnapshotStatisticsRecorderUnloggedSamples().
+    base::StatisticsRecorder::SnapshotTransactionId snapshot_transaction_id_;
   };
 
   // Loads "independent" metrics from a metrics provider and executes a
@@ -422,17 +440,39 @@
   // Set up client ID, session ID, etc.
   void InitializeMetricsState();
 
-  // Opens a new log for recording user experience metrics.
-  void OpenNewLog();
+  // Opens a new log for recording user experience metrics. If |call_providers|
+  // is true, OnDidCreateMetricsLog() of providers will be called right after
+  // opening the new log.
+  void OpenNewLog(bool call_providers = true);
 
-  // Closes out the current log after adding any last information.
-  void CloseCurrentLog();
+  // Closes out the current log after adding any last information. |async|
+  // determines whether finalizing the log will be done in a background thread.
+  // |log_stored_callback| will be run (on the main thread) after the finalized
+  // log is stored. Note that when |async| is true, the closed log could end up
+  // not being stored (see MaybeCleanUpAndStoreFinalizedLog()). Regardless,
+  // |log_stored_callback| is still run. Note that currently, there is only
+  // support to close one log asynchronously at a time (this should be enforced
+  // by the caller).
+  void CloseCurrentLog(
+      bool async,
+      base::OnceClosure log_stored_callback = base::DoNothing());
 
   // Stores the |finalized_log| in |log_store()|.
   void StoreFinalizedLog(MetricsLog::LogType log_type,
                          base::OnceClosure done_callback,
                          FinalizedLog finalized_log);
 
+  // Calls MarkUnloggedSamplesAsLogged() on |log_histogram_writer| and stores
+  // the |finalized_log| (see StoreFinalizedLog()), but only if the
+  // StatisticRecorder's last transaction ID is the same as the one from
+  // |log_histogram_writer| at the time of calling. See comments in the
+  // implementation for more details.
+  void MaybeCleanUpAndStoreFinalizedLog(
+      std::unique_ptr<MetricsLogHistogramWriter> log_histogram_writer,
+      MetricsLog::LogType log_type,
+      base::OnceClosure done_callback,
+      FinalizedLog finalized_log);
+
   // Pushes the text of the current and staged logs into persistent storage.
   void PushPendingLogsToPersistentStorage();
 
@@ -447,6 +487,10 @@
   // complete.
   void OnFinalLogInfoCollectionDone();
 
+  // Called via a callback after a periodic ongoing log (created through the
+  // MetricsRotationScheduler) was stored in |log_store()|.
+  void OnPeriodicOngoingLogStored();
+
   // Prepares the initial stability log, which is only logged when the previous
   // run of Chrome crashed.  This log contains any stability metrics left over
   // from that previous run, and only these stability metrics.  It uses the
@@ -480,16 +524,29 @@
   // Updates the "last live" browser timestamp and schedules the next update.
   void UpdateLastLiveTimestampTask();
 
-  // Snapshots histograms using the passed |log_histogram_writer| and then
+  // Snapshots histogram deltas using the passed |log_histogram_writer| and then
   // finalizes |log| by calling FinalizeLog(). |log|, |current_app_version| and
   // |signing_key| are used to finalize the log (see FinalizeLog()).
-  static FinalizedLog SnapshotHistogramsAndFinalizeLog(
+  static FinalizedLog SnapshotDeltasAndFinalizeLog(
       std::unique_ptr<MetricsLogHistogramWriter> log_histogram_writer,
       std::unique_ptr<MetricsLog> log,
       bool truncate_events,
       std::string current_app_version,
       std::string signing_key);
 
+  // Snapshots unlogged histogram samples using the passed
+  // |log_histogram_writer| and then finalizes |log| by calling FinalizeLog().
+  // |log|, |current_app_version| and |signing_key| are used to finalize the log
+  // (see FinalizeLog()). Note that unlike SnapshotDeltasAndFinalizeLog(), this
+  // does not own the passed |log_histogram_writer|, because it should be
+  // available to eventually mark the unlogged samples as logged.
+  static FinalizedLog SnapshotUnloggedSamplesAndFinalizeLog(
+      MetricsLogHistogramWriter* log_histogram_writer,
+      std::unique_ptr<MetricsLog> log,
+      bool truncate_events,
+      std::string current_app_version,
+      std::string signing_key);
+
   // Finalizes |log| (see MetricsLog::FinalizeLog()). The |signing_key| is used
   // to compute a signature for the log.
   static FinalizedLog FinalizeLog(std::unique_ptr<MetricsLog> log,
@@ -550,6 +607,14 @@
   // Indicates if loading of independent metrics is currently active.
   bool independent_loader_active_ = false;
 
+  // Indicates whether or not there is currently a periodic ongoing log being
+  // finalized (or is scheduled to be finalized).
+  bool pending_ongoing_log_ = false;
+
+  // Stores the time when we last posted a task to finalize a periodic ongoing
+  // log asynchronously.
+  base::TimeTicks async_ongoing_log_posted_time_;
+
   // Logs event manager to keep track of the various logs that the metrics
   // service interacts with. An unowned pointer of this instance is passed down
   // to various objects that are owned by this class.
diff --git a/components/named_mojo_ipc_server/BUILD.gn b/components/named_mojo_ipc_server/BUILD.gn
index ba2c792..12cffb14c 100644
--- a/components/named_mojo_ipc_server/BUILD.gn
+++ b/components/named_mojo_ipc_server/BUILD.gn
@@ -31,20 +31,11 @@
     "//mojo/public/cpp/system",
   ]
   if (is_linux) {
-    sources += [
-      "named_mojo_server_endpoint_connector_linux.cc",
-      "named_mojo_server_endpoint_connector_linux.h",
-    ]
+    sources += [ "named_mojo_server_endpoint_connector_linux.cc" ]
   } else if (is_win) {
-    sources += [
-      "named_mojo_server_endpoint_connector_win.cc",
-      "named_mojo_server_endpoint_connector_win.h",
-    ]
+    sources += [ "named_mojo_server_endpoint_connector_win.cc" ]
   } else if (is_mac) {
-    sources += [
-      "named_mojo_server_endpoint_connector_mac.cc",
-      "named_mojo_server_endpoint_connector_mac.h",
-    ]
+    sources += [ "named_mojo_server_endpoint_connector_mac.cc" ]
   } else {
     sources += [ "named_mojo_server_endpoint_connector_unsupported.cc" ]
   }
diff --git a/components/named_mojo_ipc_server/named_mojo_ipc_server.cc b/components/named_mojo_ipc_server/named_mojo_ipc_server.cc
index 41ef01e..27bcdc63 100644
--- a/components/named_mojo_ipc_server/named_mojo_ipc_server.cc
+++ b/components/named_mojo_ipc_server/named_mojo_ipc_server.cc
@@ -39,25 +39,32 @@
 
 namespace named_mojo_ipc_server {
 
-NamedMojoIpcServerBase::DelegateProxy::DelegateProxy(
-    base::WeakPtr<NamedMojoIpcServerBase> server)
-    : server_(server) {}
+// Forwards callbacks from a NamedMojoServerEndpointConnector to a
+// NamedMojoIpcServerBase. This allows the server to create a SequenceBound
+// interface to post callbacks from the IO sequence to the main sequence.
+class NamedMojoIpcServerBase::DelegateProxy final
+    : public NamedMojoServerEndpointConnector::Delegate {
+ public:
+  explicit DelegateProxy(base::WeakPtr<NamedMojoIpcServerBase> server)
+      : server_(server) {}
+  ~DelegateProxy() override = default;
 
-NamedMojoIpcServerBase::DelegateProxy::~DelegateProxy() = default;
-
-void NamedMojoIpcServerBase::DelegateProxy::OnClientConnected(
-    mojo::PlatformChannelEndpoint endpoint,
-    std::unique_ptr<ConnectionInfo> info) {
-  if (server_) {
-    server_->OnClientConnected(std::move(endpoint), std::move(info));
+  void OnClientConnected(mojo::PlatformChannelEndpoint endpoint,
+                         std::unique_ptr<ConnectionInfo> info) override {
+    if (server_) {
+      server_->OnClientConnected(std::move(endpoint), std::move(info));
+    }
   }
-}
 
-void NamedMojoIpcServerBase::DelegateProxy::OnServerEndpointCreated() {
-  if (server_) {
-    server_->OnServerEndpointCreated();
+  void OnServerEndpointCreated() override {
+    if (server_) {
+      server_->OnServerEndpointCreated();
+    }
   }
-}
+
+ private:
+  base::WeakPtr<NamedMojoIpcServerBase> server_;
+};
 
 NamedMojoIpcServerBase::NamedMojoIpcServerBase(
     const mojo::NamedPlatformChannel::ServerName& server_name,
diff --git a/components/named_mojo_ipc_server/named_mojo_ipc_server.h b/components/named_mojo_ipc_server/named_mojo_ipc_server.h
index 770bfa9..372c33a 100644
--- a/components/named_mojo_ipc_server/named_mojo_ipc_server.h
+++ b/components/named_mojo_ipc_server/named_mojo_ipc_server.h
@@ -84,22 +84,7 @@
   base::RepeatingClosure disconnect_handler_;
 
  private:
-  // Forwards callbacks from a NamedMojoServerEndpointConnector to a
-  // NamedMojoIpcServerBase. This allows the server to create a SequenceBound
-  // interface to post callbacks from the IO sequence to the main sequence.
-  class DelegateProxy : public NamedMojoServerEndpointConnector::Delegate {
-   public:
-    explicit DelegateProxy(base::WeakPtr<NamedMojoIpcServerBase> server);
-    ~DelegateProxy() override;
-
-    // Overrides for NamedMojoServerEndpointConnector::Delegate
-    void OnClientConnected(mojo::PlatformChannelEndpoint endpoint,
-                           std::unique_ptr<ConnectionInfo> info) override;
-    void OnServerEndpointCreated() override;
-
-   private:
-    base::WeakPtr<NamedMojoIpcServerBase> server_;
-  };
+  class DelegateProxy;
 
   void OnEndpointConnectorStarted(
       base::SequenceBound<NamedMojoServerEndpointConnector> endpoint_connector);
diff --git a/components/named_mojo_ipc_server/named_mojo_ipc_server_client_util.cc b/components/named_mojo_ipc_server/named_mojo_ipc_server_client_util.cc
index 3813b7c..5b1bdef 100644
--- a/components/named_mojo_ipc_server/named_mojo_ipc_server_client_util.cc
+++ b/components/named_mojo_ipc_server/named_mojo_ipc_server_client_util.cc
@@ -40,7 +40,7 @@
 
   kern_return_t kr = mach_msg_send(&message.header);
   if (kr != KERN_SUCCESS) {
-    MACH_LOG(ERROR, kr) << "mach_msg_send";
+    MACH_VLOG(1, kr) << "mach_msg_send";
     return mojo::PlatformChannelEndpoint();
   }
 
diff --git a/components/named_mojo_ipc_server/named_mojo_server_endpoint_connector_linux.cc b/components/named_mojo_ipc_server/named_mojo_server_endpoint_connector_linux.cc
index 1ce247e..3111aac 100644
--- a/components/named_mojo_ipc_server/named_mojo_server_endpoint_connector_linux.cc
+++ b/components/named_mojo_ipc_server/named_mojo_server_endpoint_connector_linux.cc
@@ -2,8 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/named_mojo_ipc_server/named_mojo_server_endpoint_connector_linux.h"
-
 #include <sys/socket.h>
 #include <sys/types.h>
 
@@ -14,12 +12,46 @@
 #include "base/files/file_descriptor_watcher_posix.h"
 #include "base/functional/callback_forward.h"
 #include "base/logging.h"
+#include "base/memory/raw_ptr.h"
+#include "base/sequence_checker.h"
+#include "base/thread_annotations.h"
 #include "base/threading/sequence_bound.h"
 #include "components/named_mojo_ipc_server/connection_info.h"
+#include "components/named_mojo_ipc_server/named_mojo_server_endpoint_connector.h"
 #include "mojo/public/cpp/platform/platform_channel_server_endpoint.h"
 #include "mojo/public/cpp/platform/socket_utils_posix.h"
 
 namespace named_mojo_ipc_server {
+namespace {
+
+class NamedMojoServerEndpointConnectorLinux final
+    : public NamedMojoServerEndpointConnector {
+ public:
+  explicit NamedMojoServerEndpointConnectorLinux(
+      const mojo::NamedPlatformChannel::ServerName& server_name,
+      base::SequenceBound<Delegate> delegate);
+  NamedMojoServerEndpointConnectorLinux(
+      const NamedMojoServerEndpointConnectorLinux&) = delete;
+  NamedMojoServerEndpointConnectorLinux& operator=(
+      const NamedMojoServerEndpointConnectorLinux&) = delete;
+  ~NamedMojoServerEndpointConnectorLinux() override;
+
+ private:
+  void OnSocketReady();
+
+  // Overrides for NamedMojoServerEndpointConnector.
+  bool TryStart() override;
+
+  // Note that |server_endpoint_| must outlive |read_watcher_controller_|;
+  // otherwise a bad file descriptor error will occur at destruction.
+  mojo::PlatformChannelServerEndpoint server_endpoint_
+      GUARDED_BY_CONTEXT(sequence_checker_);
+  std::unique_ptr<base::FileDescriptorWatcher::Controller>
+      read_watcher_controller_ GUARDED_BY_CONTEXT(sequence_checker_);
+
+  base::WeakPtrFactory<NamedMojoServerEndpointConnectorLinux> weak_factory_{
+      this};
+};
 
 NamedMojoServerEndpointConnectorLinux::NamedMojoServerEndpointConnectorLinux(
     const mojo::NamedPlatformChannel::ServerName& server_name,
@@ -85,6 +117,8 @@
   return true;
 }
 
+}  // namespace
+
 // static
 base::SequenceBound<NamedMojoServerEndpointConnector>
 NamedMojoServerEndpointConnector::Create(
diff --git a/components/named_mojo_ipc_server/named_mojo_server_endpoint_connector_linux.h b/components/named_mojo_ipc_server/named_mojo_server_endpoint_connector_linux.h
deleted file mode 100644
index a3cacf8..0000000
--- a/components/named_mojo_ipc_server/named_mojo_server_endpoint_connector_linux.h
+++ /dev/null
@@ -1,50 +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 COMPONENTS_NAMED_MOJO_IPC_SERVER_NAMED_MOJO_SERVER_ENDPOINT_CONNECTOR_LINUX_H_
-#define COMPONENTS_NAMED_MOJO_IPC_SERVER_NAMED_MOJO_SERVER_ENDPOINT_CONNECTOR_LINUX_H_
-
-#include "base/files/file_descriptor_watcher_posix.h"
-#include "base/memory/raw_ptr.h"
-#include "base/sequence_checker.h"
-#include "base/thread_annotations.h"
-#include "base/threading/sequence_bound.h"
-#include "components/named_mojo_ipc_server/named_mojo_server_endpoint_connector.h"
-#include "mojo/public/cpp/platform/platform_channel_server_endpoint.h"
-
-namespace named_mojo_ipc_server {
-
-// Linux implementation for MojoServerEndpointConnector.
-class NamedMojoServerEndpointConnectorLinux final
-    : public NamedMojoServerEndpointConnector {
- public:
-  explicit NamedMojoServerEndpointConnectorLinux(
-      const mojo::NamedPlatformChannel::ServerName& server_name,
-      base::SequenceBound<Delegate> delegate);
-  NamedMojoServerEndpointConnectorLinux(
-      const NamedMojoServerEndpointConnectorLinux&) = delete;
-  NamedMojoServerEndpointConnectorLinux& operator=(
-      const NamedMojoServerEndpointConnectorLinux&) = delete;
-  ~NamedMojoServerEndpointConnectorLinux() override;
-
- private:
-  void OnSocketReady();
-
-  // Overrides for NamedMojoServerEndpointConnector.
-  bool TryStart() override;
-
-  // Note that |server_endpoint_| must outlive |read_watcher_controller_|;
-  // otherwise a bad file descriptor error will occur at destruction.
-  mojo::PlatformChannelServerEndpoint server_endpoint_
-      GUARDED_BY_CONTEXT(sequence_checker_);
-  std::unique_ptr<base::FileDescriptorWatcher::Controller>
-      read_watcher_controller_ GUARDED_BY_CONTEXT(sequence_checker_);
-
-  base::WeakPtrFactory<NamedMojoServerEndpointConnectorLinux> weak_factory_{
-      this};
-};
-
-}  // namespace named_mojo_ipc_server
-
-#endif  // COMPONENTS_NAMED_MOJO_IPC_SERVER_NAMED_MOJO_SERVER_ENDPOINT_CONNECTOR_LINUX_H_
diff --git a/components/named_mojo_ipc_server/named_mojo_server_endpoint_connector_mac.cc b/components/named_mojo_ipc_server/named_mojo_server_endpoint_connector_mac.cc
index c267a24..6b44d08 100644
--- a/components/named_mojo_ipc_server/named_mojo_server_endpoint_connector_mac.cc
+++ b/components/named_mojo_ipc_server/named_mojo_server_endpoint_connector_mac.cc
@@ -2,26 +2,58 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/named_mojo_ipc_server/named_mojo_server_endpoint_connector_mac.h"
-
 #include <bsm/libbsm.h>
 #include <mach/kern_return.h>
 #include <mach/message.h>
 #include <mach/port.h>
 
+#include <memory>
+
 #include "base/functional/bind.h"
 #include "base/functional/callback_forward.h"
+#include "base/mac/dispatch_source_mach.h"
 #include "base/mac/mach_logging.h"
 #include "base/mac/scoped_mach_msg_destroy.h"
 #include "base/mac/scoped_mach_port.h"
+#include "base/sequence_checker.h"
 #include "base/task/sequenced_task_runner.h"
 #include "base/threading/sequence_bound.h"
 #include "base/threading/sequenced_task_runner_handle.h"
 #include "components/named_mojo_ipc_server/connection_info.h"
+#include "components/named_mojo_ipc_server/named_mojo_server_endpoint_connector.h"
 #include "mojo/public/cpp/platform/named_platform_channel.h"
 #include "mojo/public/cpp/platform/platform_channel_endpoint.h"
+#include "mojo/public/cpp/platform/platform_channel_server_endpoint.h"
 
 namespace named_mojo_ipc_server {
+namespace {
+
+// Mac implementation for MojoServerEndpointConnector.
+class NamedMojoServerEndpointConnectorMac final
+    : public NamedMojoServerEndpointConnector {
+ public:
+  explicit NamedMojoServerEndpointConnectorMac(
+      const mojo::NamedPlatformChannel::ServerName& server_name,
+      base::SequenceBound<Delegate> delegate);
+  NamedMojoServerEndpointConnectorMac(
+      const NamedMojoServerEndpointConnectorMac&) = delete;
+  NamedMojoServerEndpointConnectorMac& operator=(
+      const NamedMojoServerEndpointConnectorMac&) = delete;
+  ~NamedMojoServerEndpointConnectorMac() override;
+
+ private:
+  // Called by |dispatch_source_| on an arbitrary thread when a Mach message is
+  // ready to be received on |endpoint_|.
+  void HandleRequest();
+  mach_port_t port();
+
+  // Overrides for NamedMojoServerEndpointConnector.
+  bool TryStart() override;
+
+  // Note: |server_endpoint_| must outlive |dispatch_source_|.
+  mojo::PlatformChannelServerEndpoint server_endpoint_;
+  std::unique_ptr<base::DispatchSourceMach> dispatch_source_;
+};
 
 NamedMojoServerEndpointConnectorMac::NamedMojoServerEndpointConnectorMac(
     const mojo::NamedPlatformChannel::ServerName& server_name,
@@ -97,6 +129,8 @@
   return true;
 }
 
+}  // namespace
+
 // static
 base::SequenceBound<NamedMojoServerEndpointConnector>
 NamedMojoServerEndpointConnector::Create(
diff --git a/components/named_mojo_ipc_server/named_mojo_server_endpoint_connector_mac.h b/components/named_mojo_ipc_server/named_mojo_server_endpoint_connector_mac.h
deleted file mode 100644
index 770ca3c..0000000
--- a/components/named_mojo_ipc_server/named_mojo_server_endpoint_connector_mac.h
+++ /dev/null
@@ -1,49 +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.
-
-#ifndef COMPONENTS_NAMED_MOJO_IPC_SERVER_NAMED_MOJO_SERVER_ENDPOINT_CONNECTOR_MAC_H_
-#define COMPONENTS_NAMED_MOJO_IPC_SERVER_NAMED_MOJO_SERVER_ENDPOINT_CONNECTOR_MAC_H_
-
-#include <mach/port.h>
-
-#include <memory>
-
-#include "base/mac/dispatch_source_mach.h"
-#include "base/sequence_checker.h"
-#include "base/threading/sequence_bound.h"
-#include "components/named_mojo_ipc_server/named_mojo_server_endpoint_connector.h"
-#include "mojo/public/cpp/platform/named_platform_channel.h"
-#include "mojo/public/cpp/platform/platform_channel_server_endpoint.h"
-
-namespace named_mojo_ipc_server {
-
-// Mac implementation for MojoServerEndpointConnector.
-class NamedMojoServerEndpointConnectorMac final
-    : public NamedMojoServerEndpointConnector {
- public:
-  explicit NamedMojoServerEndpointConnectorMac(
-      const mojo::NamedPlatformChannel::ServerName& server_name,
-      base::SequenceBound<Delegate> delegate);
-  NamedMojoServerEndpointConnectorMac(
-      const NamedMojoServerEndpointConnectorMac&) = delete;
-  NamedMojoServerEndpointConnectorMac& operator=(
-      const NamedMojoServerEndpointConnectorMac&) = delete;
-  ~NamedMojoServerEndpointConnectorMac() override;
-
- private:
-  // Called by |dispatch_source_| on an arbitrary thread when a Mach message is
-  // ready to be received on |endpoint_|.
-  void HandleRequest();
-  mach_port_t port();
-
-  // Overrides for NamedMojoServerEndpointConnector.
-  bool TryStart() override;
-
-  // Note: |server_endpoint_| must outlive |dispatch_source_|.
-  mojo::PlatformChannelServerEndpoint server_endpoint_;
-  std::unique_ptr<base::DispatchSourceMach> dispatch_source_;
-};
-
-}  // namespace named_mojo_ipc_server
-#endif  // COMPONENTS_NAMED_MOJO_IPC_SERVER_NAMED_MOJO_SERVER_ENDPOINT_CONNECTOR_MAC_H_
diff --git a/components/named_mojo_ipc_server/named_mojo_server_endpoint_connector_win.cc b/components/named_mojo_ipc_server/named_mojo_server_endpoint_connector_win.cc
index fb3c5854..3880351 100644
--- a/components/named_mojo_ipc_server/named_mojo_server_endpoint_connector_win.cc
+++ b/components/named_mojo_ipc_server/named_mojo_server_endpoint_connector_win.cc
@@ -2,8 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/named_mojo_ipc_server/named_mojo_server_endpoint_connector_win.h"
-
 #include <string.h>
 #include <windows.h>
 
@@ -12,21 +10,24 @@
 
 #include "base/bind.h"
 #include "base/check.h"
-#include "base/functional/callback_forward.h"
 #include "base/logging.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/process/process_handle.h"
 #include "base/sequence_checker.h"
 #include "base/strings/stringprintf.h"
 #include "base/synchronization/waitable_event.h"
+#include "base/synchronization/waitable_event_watcher.h"
 #include "base/task/current_thread.h"
 #include "base/task/sequenced_task_runner.h"
+#include "base/thread_annotations.h"
 #include "base/threading/sequence_bound.h"
 #include "base/time/time.h"
+#include "base/timer/timer.h"
 #include "base/win/scoped_handle.h"
 #include "base/win/win_util.h"
 #include "base/win/windows_types.h"
 #include "components/named_mojo_ipc_server/connection_info.h"
+#include "components/named_mojo_ipc_server/named_mojo_server_endpoint_connector.h"
 #include "mojo/public/cpp/platform/named_platform_channel.h"
 #include "mojo/public/cpp/platform/platform_channel_endpoint.h"
 #include "mojo/public/cpp/platform/platform_handle.h"
@@ -57,7 +58,47 @@
   return channel.TakeServerEndpoint();
 }
 
-}  // namespace
+class NamedMojoServerEndpointConnectorWin final
+    : public NamedMojoServerEndpointConnector {
+ public:
+  explicit NamedMojoServerEndpointConnectorWin(
+      const mojo::NamedPlatformChannel::ServerName& server_name,
+      base::SequenceBound<Delegate> delegate);
+  NamedMojoServerEndpointConnectorWin(
+      const NamedMojoServerEndpointConnectorWin&) = delete;
+  NamedMojoServerEndpointConnectorWin& operator=(
+      const NamedMojoServerEndpointConnectorWin&) = delete;
+  ~NamedMojoServerEndpointConnectorWin() override;
+
+ private:
+  void OnConnectedEventSignaled(base::WaitableEvent* event);
+
+  void Connect();
+  void OnReady();
+  void OnError();
+
+  void ResetConnectionObjects();
+
+  // Overrides for NamedMojoServerEndpointConnector.
+  bool TryStart() override;
+
+  base::WaitableEventWatcher client_connection_watcher_
+      GUARDED_BY_CONTEXT(sequence_checker_);
+
+  // Non-null when there is a pending connection.
+  base::win::ScopedHandle pending_named_pipe_handle_
+      GUARDED_BY_CONTEXT(sequence_checker_);
+
+  // Signaled by ConnectNamedPipe() once |pending_named_pipe_handle_| is
+  // connected to a client.
+  base::WaitableEvent client_connected_event_
+      GUARDED_BY_CONTEXT(sequence_checker_);
+
+  // Object to allow ConnectNamedPipe() to run asynchronously.
+  OVERLAPPED connect_overlapped_ GUARDED_BY_CONTEXT(sequence_checker_);
+
+  base::OneShotTimer retry_connect_timer_;
+};
 
 NamedMojoServerEndpointConnectorWin::NamedMojoServerEndpointConnectorWin(
     const mojo::NamedPlatformChannel::ServerName& server_name,
@@ -168,16 +209,6 @@
                              &NamedMojoServerEndpointConnectorWin::Connect);
 }
 
-// static
-base::SequenceBound<NamedMojoServerEndpointConnector>
-NamedMojoServerEndpointConnector::Create(
-    scoped_refptr<base::SequencedTaskRunner> io_sequence,
-    const mojo::NamedPlatformChannel::ServerName& server_name,
-    base::SequenceBound<Delegate> delegate) {
-  return base::SequenceBound<NamedMojoServerEndpointConnectorWin>(
-      io_sequence, server_name, std::move(delegate));
-}
-
 void NamedMojoServerEndpointConnectorWin::ResetConnectionObjects() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
@@ -191,4 +222,16 @@
   return true;
 }
 
+}  // namespace
+
+// static
+base::SequenceBound<NamedMojoServerEndpointConnector>
+NamedMojoServerEndpointConnector::Create(
+    scoped_refptr<base::SequencedTaskRunner> io_sequence,
+    const mojo::NamedPlatformChannel::ServerName& server_name,
+    base::SequenceBound<Delegate> delegate) {
+  return base::SequenceBound<NamedMojoServerEndpointConnectorWin>(
+      io_sequence, server_name, std::move(delegate));
+}
+
 }  // namespace named_mojo_ipc_server
diff --git a/components/named_mojo_ipc_server/named_mojo_server_endpoint_connector_win.h b/components/named_mojo_ipc_server/named_mojo_server_endpoint_connector_win.h
deleted file mode 100644
index b4f770c..0000000
--- a/components/named_mojo_ipc_server/named_mojo_server_endpoint_connector_win.h
+++ /dev/null
@@ -1,69 +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 COMPONENTS_NAMED_MOJO_IPC_SERVER_NAMED_MOJO_SERVER_ENDPOINT_CONNECTOR_WIN_H_
-#define COMPONENTS_NAMED_MOJO_IPC_SERVER_NAMED_MOJO_SERVER_ENDPOINT_CONNECTOR_WIN_H_
-
-#include <windows.h>
-
-#include "base/functional/callback_forward.h"
-#include "base/memory/raw_ptr.h"
-#include "base/sequence_checker.h"
-#include "base/synchronization/waitable_event.h"
-#include "base/synchronization/waitable_event_watcher.h"
-#include "base/task/sequenced_task_runner.h"
-#include "base/thread_annotations.h"
-#include "base/timer/timer.h"
-#include "base/win/scoped_handle.h"
-#include "base/win/windows_types.h"
-#include "components/named_mojo_ipc_server/named_mojo_server_endpoint_connector.h"
-
-namespace named_mojo_ipc_server {
-
-// Windows implementation for NamedMojoServerEndpointConnector.
-class NamedMojoServerEndpointConnectorWin final
-    : public NamedMojoServerEndpointConnector {
- public:
-  explicit NamedMojoServerEndpointConnectorWin(
-      const mojo::NamedPlatformChannel::ServerName& server_name,
-      base::SequenceBound<Delegate> delegate);
-  NamedMojoServerEndpointConnectorWin(
-      const NamedMojoServerEndpointConnectorWin&) = delete;
-  NamedMojoServerEndpointConnectorWin& operator=(
-      const NamedMojoServerEndpointConnectorWin&) = delete;
-  ~NamedMojoServerEndpointConnectorWin() override;
-
- private:
-  void OnConnectedEventSignaled(base::WaitableEvent* event);
-
-  void Connect();
-  void OnReady();
-  void OnError();
-
-  void ResetConnectionObjects();
-
-  // Overrides for NamedMojoServerEndpointConnector.
-  bool TryStart() override;
-
-  base::WaitableEventWatcher client_connection_watcher_
-      GUARDED_BY_CONTEXT(sequence_checker_);
-
-  // Non-null when there is a pending connection.
-  base::win::ScopedHandle pending_named_pipe_handle_
-      GUARDED_BY_CONTEXT(sequence_checker_);
-
-  // Signaled by ConnectNamedPipe() once |pending_named_pipe_handle_| is
-  // connected to a client.
-  base::WaitableEvent client_connected_event_
-      GUARDED_BY_CONTEXT(sequence_checker_);
-
-  // Object to allow ConnectNamedPipe() to run asynchronously.
-  OVERLAPPED connect_overlapped_ GUARDED_BY_CONTEXT(sequence_checker_);
-
-  base::OneShotTimer retry_connect_timer_;
-};
-
-}  // namespace named_mojo_ipc_server
-
-#endif  // COMPONENTS_NAMED_MOJO_IPC_SERVER_NAMED_MOJO_SERVER_ENDPOINT_CONNECTOR_WIN_H_
diff --git a/components/omnibox/browser/omnibox_field_trial.cc b/components/omnibox/browser/omnibox_field_trial.cc
index aa36510d..3a01ceb 100644
--- a/components/omnibox/browser/omnibox_field_trial.cc
+++ b/components/omnibox/browser/omnibox_field_trial.cc
@@ -535,10 +535,6 @@
   return base::FeatureList::IsEnabled(omnibox::kOmniboxFuzzyUrlSuggestions);
 }
 
-bool OmniboxFieldTrial::IsDefaultBrowserPedalEnabled() {
-  return base::FeatureList::IsEnabled(omnibox::kOmniboxDefaultBrowserPedal);
-}
-
 const base::FeatureParam<bool>
     OmniboxFieldTrial::kFuzzyUrlSuggestionsCounterfactual(
         &omnibox::kOmniboxFuzzyUrlSuggestions,
@@ -589,6 +585,22 @@
         "FuzzyUrlSuggestionsPenaltyTaperLength",
         0);
 
+bool OmniboxFieldTrial::IsDefaultBrowserPedalEnabled() {
+  return base::FeatureList::IsEnabled(omnibox::kOmniboxDefaultBrowserPedal);
+}
+
+const base::FeatureParam<bool>
+    OmniboxFieldTrial::kDefaultBrowserPedalInteractive(
+        &omnibox::kOmniboxDefaultBrowserPedal,
+        "DefaultBrowserPedalInteractive",
+        true);
+
+const base::FeatureParam<bool>
+    OmniboxFieldTrial::kDefaultBrowserPedalUnattended(
+        &omnibox::kOmniboxDefaultBrowserPedal,
+        "DefaultBrowserPedalUnattended",
+        true);
+
 bool OmniboxFieldTrial::IsExperimentalKeywordModeEnabled() {
   return base::FeatureList::IsEnabled(omnibox::kExperimentalKeywordMode);
 }
@@ -641,10 +653,10 @@
   return base::FeatureList::IsEnabled(omnibox::kUniformRowHeight);
 }
 
-const base::FeatureParam<int> OmniboxFieldTrial::kSuggestionVerticalMargin(
+const base::FeatureParam<int> OmniboxFieldTrial::kRichSuggestionVerticalMargin(
     &omnibox::kUniformRowHeight,
-    "OmniboxSuggestionVerticalMargin",
-    8);
+    "OmniboxRichSuggestionVerticalMargin",
+    4);
 
 const char OmniboxFieldTrial::kBundledExperimentFieldTrialName[] =
     "OmniboxBundledExperimentV1";
diff --git a/components/omnibox/browser/omnibox_field_trial.h b/components/omnibox/browser/omnibox_field_trial.h
index e987c1a..064ce5d 100644
--- a/components/omnibox/browser/omnibox_field_trial.h
+++ b/components/omnibox/browser/omnibox_field_trial.h
@@ -339,6 +339,15 @@
 // Returns true if the default browser pedal feature is enabled.
 bool IsDefaultBrowserPedalEnabled();
 
+// Indicates whether the default browser pedal can be used when the
+// shell_integration API indicates the system sets default browser
+// interactively, e.g. by bringing up system settings.
+extern const base::FeatureParam<bool> kDefaultBrowserPedalInteractive;
+// Indicates whether the default browser pedal can be used when the
+// shell_integration API indicates the system sets default browser
+// without any further user interaction, i.e. "unattended".
+extern const base::FeatureParam<bool> kDefaultBrowserPedalUnattended;
+
 // Simply a convenient wrapper for testing a flag. Used downstream for an
 // assortment of keyword mode experiments.
 bool IsExperimentalKeywordModeEnabled();
@@ -365,7 +374,10 @@
 // Returns true if the feature to enable uniform row height is enabled.
 bool IsUniformRowHeightEnabled();
 // Specifies the row height in pixels for omnibox suggestions.
-extern const base::FeatureParam<int> kSuggestionVerticalMargin;
+extern const base::FeatureParam<int> kSuggestionRowHeight;
+// Specifies the vertical margin to use in one-line rich entity and answer
+// suggestions.
+extern const base::FeatureParam<int> kRichSuggestionVerticalMargin;
 
 // ---------------------------------------------------------
 // Clipboard URL suggestions:
diff --git a/components/optimization_guide/content/browser/BUILD.gn b/components/optimization_guide/content/browser/BUILD.gn
index 4b4a70c..3ea5ddf 100644
--- a/components/optimization_guide/content/browser/BUILD.gn
+++ b/components/optimization_guide/content/browser/BUILD.gn
@@ -95,6 +95,7 @@
     ":test_support",
     "//components/optimization_guide/core:test_support",
     "//components/search_engines",
+    "//components/ukm:test_support",
     "//content/test:test_support",
     "//testing/gmock",
     "//testing/gtest",
diff --git a/components/optimization_guide/content/browser/DEPS b/components/optimization_guide/content/browser/DEPS
index 7c420114..e4c9a2d 100644
--- a/components/optimization_guide/content/browser/DEPS
+++ b/components/optimization_guide/content/browser/DEPS
@@ -8,10 +8,11 @@
   "+components/optimization_guide/core",
   "+components/optimization_guide/proto",
   "+components/search_engines",
+  "+components/ukm",
   "+content/public/browser",
   "+content/public/test",
   "+mojo/public",
-  "+services/metrics/public/cpp",
+  "+services/metrics/public",
   "+services/service_manager/public",
   "+third_party/blink/public",
 ]
diff --git a/components/optimization_guide/content/browser/page_content_annotations_web_contents_observer_unittest.cc b/components/optimization_guide/content/browser/page_content_annotations_web_contents_observer_unittest.cc
index df02e189..2cc0ae5 100644
--- a/components/optimization_guide/content/browser/page_content_annotations_web_contents_observer_unittest.cc
+++ b/components/optimization_guide/content/browser/page_content_annotations_web_contents_observer_unittest.cc
@@ -16,10 +16,20 @@
 #include "components/optimization_guide/core/test_optimization_guide_model_provider.h"
 #include "components/optimization_guide/proto/page_entities_metadata.pb.h"
 #include "components/search_engines/template_url_service.h"
+#include "components/ukm/test_ukm_recorder.h"
 #include "content/public/browser/navigation_handle.h"
+#include "content/public/test/fake_local_frame.h"
 #include "content/public/test/navigation_simulator.h"
 #include "content/public/test/test_renderer_host.h"
+#include "mojo/public/cpp/bindings/associated_receiver_set.h"
+#include "services/metrics/public/cpp/ukm_builders.h"
+#include "services/metrics/public/cpp/ukm_source.h"
+#include "services/metrics/public/mojom/ukm_interface.mojom-forward.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
+#include "third_party/blink/public/mojom/frame/frame.mojom.h"
+#include "third_party/blink/public/mojom/opengraph/metadata.mojom.h"
+#include "url/gurl.h"
 
 namespace optimization_guide {
 
@@ -27,6 +37,37 @@
 
 using ::testing::UnorderedElementsAre;
 
+class FrameRemoteTester : public content::FakeLocalFrame {
+ public:
+  FrameRemoteTester() = default;
+  ~FrameRemoteTester() override = default;
+
+  bool did_get_request() const { return did_get_request_; }
+
+  void set_open_graph_md_response(blink::mojom::OpenGraphMetadataPtr response) {
+    response_ = std::move(response);
+  }
+
+  void BindPendingReceiver(mojo::ScopedInterfaceEndpointHandle handle) {
+    receivers_.Add(this,
+                   mojo::PendingAssociatedReceiver<blink::mojom::LocalFrame>(
+                       std::move(handle)));
+  }
+
+  // blink::mojom::LocalFrame:
+  void GetOpenGraphMetadata(
+      base::OnceCallback<void(blink::mojom::OpenGraphMetadataPtr)> callback)
+      override {
+    did_get_request_ = true;
+    std::move(callback).Run(std::move(response_));
+  }
+
+ private:
+  mojo::AssociatedReceiverSet<blink::mojom::LocalFrame> receivers_;
+  bool did_get_request_ = false;
+  blink::mojom::OpenGraphMetadataPtr response_;
+};
+
 }  // namespace
 
 const TemplateURLService::Initializer kTemplateURLData[] = {
@@ -331,6 +372,148 @@
   EXPECT_FALSE(last_request.has_value());
 }
 
+TEST_F(PageContentAnnotationsWebContentsObserverTest, OgImagePresent) {
+  base::HistogramTester histogram_tester;
+  ukm::TestAutoSetUkmRecorder ukm_recorder;
+
+  auto metadata = blink::mojom::OpenGraphMetadata::New();
+  metadata->image = GURL("http://www.google.com/image.png");
+  FrameRemoteTester frame_remote_tester;
+  frame_remote_tester.set_open_graph_md_response(std::move(metadata));
+
+  main_rfh()->GetRemoteAssociatedInterfaces()->OverrideBinderForTesting(
+      blink::mojom::LocalFrame::Name_,
+      base::BindRepeating(&FrameRemoteTester::BindPendingReceiver,
+                          base::Unretained(&frame_remote_tester)));
+
+  auto nav_simulator = content::NavigationSimulator::CreateBrowserInitiated(
+      GURL("http://foo.com/bar"), web_contents());
+  nav_simulator->Commit();
+  nav_simulator->StopLoading();
+  task_environment()->RunUntilIdle();
+
+  ASSERT_TRUE(frame_remote_tester.did_get_request());
+
+  histogram_tester.ExpectUniqueSample(
+      "OptimizationGuide.PageContentAnnotations.SalientImageAvailability",
+      /*kAvailableFromOgImage=*/3, 1);
+
+  std::vector<const ukm::mojom::UkmEntry*> entries =
+      ukm_recorder.GetEntriesByName(
+          ukm::builders::SalientImageAvailability::kEntryName);
+  ASSERT_EQ(1u, entries.size());
+
+  ASSERT_EQ(1u, entries[0]->metrics.size());
+  EXPECT_EQ(/*kAvailableFromOgImage=*/3, entries[0]->metrics.begin()->second);
+}
+
+TEST_F(PageContentAnnotationsWebContentsObserverTest, OgImageMalformed) {
+  base::HistogramTester histogram_tester;
+  ukm::TestAutoSetUkmRecorder ukm_recorder;
+
+  auto metadata = blink::mojom::OpenGraphMetadata::New();
+  metadata->image = GURL();
+  FrameRemoteTester frame_remote_tester;
+  frame_remote_tester.set_open_graph_md_response(std::move(metadata));
+
+  main_rfh()->GetRemoteAssociatedInterfaces()->OverrideBinderForTesting(
+      blink::mojom::LocalFrame::Name_,
+      base::BindRepeating(&FrameRemoteTester::BindPendingReceiver,
+                          base::Unretained(&frame_remote_tester)));
+
+  auto nav_simulator = content::NavigationSimulator::CreateBrowserInitiated(
+      GURL("http://foo.com/bar"), web_contents());
+  nav_simulator->Commit();
+  nav_simulator->StopLoading();
+  task_environment()->RunUntilIdle();
+
+  ASSERT_TRUE(frame_remote_tester.did_get_request());
+
+  histogram_tester.ExpectUniqueSample(
+      "OptimizationGuide.PageContentAnnotations.SalientImageAvailability",
+      /*kNotAvailable=*/1, 1);
+
+  std::vector<const ukm::mojom::UkmEntry*> entries =
+      ukm_recorder.GetEntriesByName(
+          ukm::builders::SalientImageAvailability::kEntryName);
+  ASSERT_EQ(1u, entries.size());
+
+  // Malformed URL is also reported as og image unavailable.
+  ASSERT_EQ(1u, entries[0]->metrics.size());
+  EXPECT_EQ(/*kNotAvailable=*/1, entries[0]->metrics.begin()->second);
+}
+
+TEST_F(PageContentAnnotationsWebContentsObserverTest, NoOgImage) {
+  base::HistogramTester histogram_tester;
+  ukm::TestAutoSetUkmRecorder ukm_recorder;
+
+  // Image not set on |metadata|.
+  auto metadata = blink::mojom::OpenGraphMetadata::New();
+  FrameRemoteTester frame_remote_tester;
+  frame_remote_tester.set_open_graph_md_response(std::move(metadata));
+
+  main_rfh()->GetRemoteAssociatedInterfaces()->OverrideBinderForTesting(
+      blink::mojom::LocalFrame::Name_,
+      base::BindRepeating(&FrameRemoteTester::BindPendingReceiver,
+                          base::Unretained(&frame_remote_tester)));
+
+  auto nav_simulator = content::NavigationSimulator::CreateBrowserInitiated(
+      GURL("http://foo.com/bar"), web_contents());
+  nav_simulator->Commit();
+  nav_simulator->StopLoading();
+  task_environment()->RunUntilIdle();
+
+  ASSERT_TRUE(frame_remote_tester.did_get_request());
+
+  histogram_tester.ExpectUniqueSample(
+      "OptimizationGuide.PageContentAnnotations.SalientImageAvailability",
+      /*kNotAvailable=*/1, 1);
+
+  std::vector<const ukm::mojom::UkmEntry*> entries =
+      ukm_recorder.GetEntriesByName(
+          ukm::builders::SalientImageAvailability::kEntryName);
+  ASSERT_EQ(1u, entries.size());
+
+  ASSERT_EQ(1u, entries[0]->metrics.size());
+  EXPECT_EQ(/*kNotAvailable=*/1, entries[0]->metrics.begin()->second);
+}
+
+TEST_F(PageContentAnnotationsWebContentsObserverTest, OgImageIsNotHTTP) {
+  base::HistogramTester histogram_tester;
+  ukm::TestAutoSetUkmRecorder ukm_recorder;
+
+  auto metadata = blink::mojom::OpenGraphMetadata::New();
+  metadata->image = GURL("ftp://foo.com");
+  FrameRemoteTester frame_remote_tester;
+  frame_remote_tester.set_open_graph_md_response(std::move(metadata));
+
+  main_rfh()->GetRemoteAssociatedInterfaces()->OverrideBinderForTesting(
+      blink::mojom::LocalFrame::Name_,
+      base::BindRepeating(&FrameRemoteTester::BindPendingReceiver,
+                          base::Unretained(&frame_remote_tester)));
+
+  auto nav_simulator = content::NavigationSimulator::CreateBrowserInitiated(
+      GURL("http://foo.com/bar"), web_contents());
+  nav_simulator->Commit();
+  nav_simulator->StopLoading();
+  task_environment()->RunUntilIdle();
+
+  ASSERT_TRUE(frame_remote_tester.did_get_request());
+
+  histogram_tester.ExpectUniqueSample(
+      "OptimizationGuide.PageContentAnnotations.SalientImageAvailability",
+      /*kNotAvailable=*/1, 1);
+
+  std::vector<const ukm::mojom::UkmEntry*> entries =
+      ukm_recorder.GetEntriesByName(
+          ukm::builders::SalientImageAvailability::kEntryName);
+  ASSERT_EQ(1u, entries.size());
+
+  // Non-HTTP URL is also reported as og image unavailable.
+  ASSERT_EQ(1u, entries[0]->metrics.size());
+  EXPECT_EQ(/*kNotAvailable=*/1, entries[0]->metrics.begin()->second);
+}
+
 class PageContentAnnotationsWebContentsObserverRelatedSearchesTest
     : public PageContentAnnotationsWebContentsObserverTest {
  public:
diff --git a/components/paint_preview/renderer/paint_preview_recorder_utils.cc b/components/paint_preview/renderer/paint_preview_recorder_utils.cc
index f9520e86..60e6e1b 100644
--- a/components/paint_preview/renderer/paint_preview_recorder_utils.cc
+++ b/components/paint_preview/renderer/paint_preview_recorder_utils.cc
@@ -11,6 +11,7 @@
 #include "base/trace_event/trace_event.h"
 #include "cc/paint/paint_image.h"
 #include "cc/paint/paint_image_builder.h"
+#include "cc/paint/paint_op_buffer_iterator.h"
 #include "components/paint_preview/common/file_stream.h"
 #include "components/paint_preview/common/paint_preview_tracker.h"
 #include "mojo/public/cpp/base/shared_memory_utils.h"
diff --git a/components/password_manager/core/browser/ui/OWNERS b/components/password_manager/core/browser/ui/OWNERS
new file mode 100644
index 0000000..8ee4bd2
--- /dev/null
+++ b/components/password_manager/core/browser/ui/OWNERS
@@ -0,0 +1 @@
+vsemeniuk@google.com
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 73c0fd30..70e9f491 100644
--- a/components/password_manager/core/browser/ui/credential_ui_entry.cc
+++ b/components/password_manager/core/browser/ui/credential_ui_entry.cc
@@ -9,9 +9,23 @@
 #include "components/password_manager/core/browser/import/csv_password.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"
+#include "components/url_formatter/elide_url.h"
 
 namespace password_manager {
 
+namespace {
+
+constexpr char kPlayStoreAppPrefix[] =
+    "https://play.google.com/store/apps/details?id=";
+
+std::string GetOrigin(const url::Origin& origin) {
+  return base::UTF16ToUTF8(url_formatter::FormatOriginForSecurityDisplay(
+      origin, url_formatter::SchemeDisplay::OMIT_CRYPTOGRAPHIC));
+}
+
+}  // namespace
+
 // CredentialFacet
 
 CredentialFacet::CredentialFacet() = default;
@@ -170,6 +184,32 @@
   return facets[0].url;
 }
 
+std::vector<CredentialUIEntry::DomainInfo>
+CredentialUIEntry::GetAffiliatedDomains() const {
+  std::vector<CredentialUIEntry::DomainInfo> domains;
+  for (const auto& facet : facets) {
+    CredentialUIEntry::DomainInfo domain;
+    password_manager::FacetURI facet_uri =
+        password_manager::FacetURI::FromPotentiallyInvalidSpec(
+            facet.signon_realm);
+    if (facet_uri.IsValidAndroidFacetURI()) {
+      domain.name = facet.display_name.empty()
+                        ? password_manager::SplitByDotAndReverse(
+                              facet_uri.android_package_name())
+                        : facet.display_name;
+      domain.url =
+          facet.affiliated_web_realm.empty()
+              ? GURL(kPlayStoreAppPrefix + facet_uri.android_package_name())
+              : GURL(facet.affiliated_web_realm);
+    } else {
+      domain.name = GetOrigin(url::Origin::Create(facet.url));
+      domain.url = facet.url;
+    }
+    domains.push_back(std::move(domain));
+  }
+  return domains;
+}
+
 bool operator==(const CredentialUIEntry& lhs, const CredentialUIEntry& rhs) {
   return CreateSortKey(lhs) == CreateSortKey(rhs);
 }
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 768e1ec..723e31f0 100644
--- a/components/password_manager/core/browser/ui/credential_ui_entry.h
+++ b/components/password_manager/core/browser/ui/credential_ui_entry.h
@@ -59,6 +59,17 @@
 // correspond to multiple PasswordForms.
 // TODO(crbug.com/1374029): Use class here instead of struct.
 struct CredentialUIEntry {
+  // Structure which represents affiliated domain and can be used by the UI to
+  // display affiliated domains as links.
+  struct DomainInfo {
+    // A human readable version of the URL of the credential's origin. For
+    // android credentials this is usually the app name.
+    std::string name;
+
+    // The URL that will be linked to when an entry is clicked.
+    GURL url;
+  };
+
   struct Less {
     bool operator()(const CredentialUIEntry& lhs,
                     const CredentialUIEntry& rhs) const;
@@ -131,6 +142,12 @@
   // Returns the first URL among all the URLs in the facets associated with this
   // entry.
   GURL GetURL() const;
+
+  // Returns a vector of pairs, where the first element is formatted string
+  // representing website or an Android application and a second parameter is a
+  // link which should be opened when item is clicked. Can be used by the UI to
+  // display all the affiliated domains.
+  std::vector<DomainInfo> GetAffiliatedDomains() const;
 };
 
 bool operator==(const CredentialUIEntry& lhs, const CredentialUIEntry& rhs);
diff --git a/components/password_manager/core/browser/ui/credential_ui_entry_unittest.cc b/components/password_manager/core/browser/ui/credential_ui_entry_unittest.cc
index 5ac7b7d..12905f9b 100644
--- a/components/password_manager/core/browser/ui/credential_ui_entry_unittest.cc
+++ b/components/password_manager/core/browser/ui/credential_ui_entry_unittest.cc
@@ -7,11 +7,25 @@
 #include <vector>
 
 #include "components/password_manager/core/browser/password_form.h"
+#include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
 
 namespace password_manager {
 
+namespace {
+
+using testing::ElementsAre;
+using testing::UnorderedElementsAre;
+
+// Creates matcher for a given domain info
+auto ExpectDomain(const std::string& name, const GURL& url) {
+  return AllOf(testing::Field(&CredentialUIEntry::DomainInfo::name, name),
+               testing::Field(&CredentialUIEntry::DomainInfo::url, url));
+}
+
+}  // namespace
+
 TEST(CredentialUIEntryTest, CredentialUIEntryWithForm) {
   std::u16string username = u"testUsername00";
   std::u16string password = u"testPassword01";
@@ -84,4 +98,45 @@
   EXPECT_EQ(entry.blocked_by_user, false);
 }
 
+TEST(CredentialUIEntryTest, TestGetAffiliatedDomains) {
+  std::vector<PasswordForm> forms;
+
+  PasswordForm android_form;
+  android_form.signon_realm = "android://certificate_hash@com.test.client/";
+  android_form.app_display_name = "g3.com";
+  android_form.affiliated_web_realm = "https://test.com";
+
+  PasswordForm web_form;
+  web_form.signon_realm = "https://g.com/";
+  web_form.url = GURL(web_form.signon_realm);
+
+  CredentialUIEntry entry = CredentialUIEntry({android_form, web_form});
+  EXPECT_THAT(entry.GetAffiliatedDomains(),
+              UnorderedElementsAre(
+                  ExpectDomain(android_form.app_display_name,
+                               GURL(android_form.affiliated_web_realm)),
+                  ExpectDomain("g.com", web_form.url)));
+}
+
+TEST(CredentialUIEntryTest, TestGetAffiliatedDomainsHttpForm) {
+  PasswordForm form;
+  form.signon_realm = "http://g.com/";
+  form.url = GURL(form.signon_realm);
+
+  CredentialUIEntry entry = CredentialUIEntry({form});
+  EXPECT_THAT(entry.GetAffiliatedDomains(),
+              ElementsAre(ExpectDomain("http://g.com", GURL(form.url))));
+}
+
+TEST(CredentialUIEntryTest, TestGetAffiliatedDomainsEmptyAndroidForm) {
+  PasswordForm android_form;
+  android_form.signon_realm = "android://certificate_hash@com.test.client/";
+
+  CredentialUIEntry entry = CredentialUIEntry({android_form});
+  EXPECT_THAT(entry.GetAffiliatedDomains(),
+              ElementsAre(ExpectDomain(
+                  "client.test.com", GURL("https://play.google.com/store/apps/"
+                                          "details?id=com.test.client"))));
+}
+
 }  // namespace password_manager
diff --git a/components/policy/proto/chrome_device_policy.proto b/components/policy/proto/chrome_device_policy.proto
index 39c828b..984b5d5c 100644
--- a/components/policy/proto/chrome_device_policy.proto
+++ b/components/policy/proto/chrome_device_policy.proto
@@ -1380,11 +1380,6 @@
   optional BacklightColor color = 1 [default = BACKLIGHT_UNSPECIFIED];
 }
 
-// Default keyboard brightness percentage.
-message KeyboardBrightnessProto {
-  optional int32 percentage = 1;
-}
-
 // Specifies how user policy from device GPOs interacts with user policy from
 // user GPOs. In 'MERGE' mode, device GPOs take preference in case of conflicts.
 // Applies to Active Directory management mode only.
@@ -1910,5 +1905,4 @@
   optional StringPolicyProto device_printing_client_name_template = 137;
   optional DeviceReportXDREventsProto device_report_xdr_events = 138;
   optional KeyboardBacklightColorProto keyboard_backlight_color = 139;
-  optional KeyboardBrightnessProto keyboard_brightness = 140;
 }
diff --git a/components/policy/resources/templates/device_policy_proto_map.yaml b/components/policy/resources/templates/device_policy_proto_map.yaml
index ab50cce..1cb22b4 100644
--- a/components/policy/resources/templates/device_policy_proto_map.yaml
+++ b/components/policy/resources/templates/device_policy_proto_map.yaml
@@ -47,7 +47,6 @@
 DeviceI18nShortcutsEnabled: device_i18n_shortcuts_enabled.enabled
 DeviceKerberosEncryptionTypes: device_kerberos_encryption_types.types
 DeviceKeyboardBacklightColor: keyboard_backlight_color.color
-DeviceKeyboardBrightness: keyboard_brightness.percentage
 DeviceKeylockerForStorageEncryptionEnabled: keylocker_for_storage_encryption_enabled.enabled
 DeviceLocalAccountAutoLoginBailoutEnabled: device_local_accounts.enable_auto_login_bailout
 DeviceLocalAccountAutoLoginDelay: device_local_accounts.auto_login_delay
diff --git a/components/policy/resources/templates/policies.yaml b/components/policy/resources/templates/policies.yaml
index dff4d26..5b171b23 100644
--- a/components/policy/resources/templates/policies.yaml
+++ b/components/policy/resources/templates/policies.yaml
@@ -1038,7 +1038,7 @@
   1037: ThrottleNonVisibleCrossOriginIframesAllowed
   1038: PdfLocalFileAccessAllowedForDomains
   1039: FloatingWorkspaceV2Enabled
-  1040: DeviceKeyboardBrightness
+  1040: ''
   1041: NewBaseUrlInheritanceBehaviorAllowed
   1042: ShowCastSessionsStartedByOtherDevices
 atomic_groups:
diff --git a/components/policy/resources/templates/policy_definitions/Miscellaneous/DeviceKeyboardBrightness.yaml b/components/policy/resources/templates/policy_definitions/Miscellaneous/DeviceKeyboardBrightness.yaml
deleted file mode 100644
index dd6c67a..0000000
--- a/components/policy/resources/templates/policy_definitions/Miscellaneous/DeviceKeyboardBrightness.yaml
+++ /dev/null
@@ -1,20 +0,0 @@
-caption: Default keyboard brightness
-default: 60
-desc: |-
-  Setting the policy to the value that will be used as the default
-
-  device keyboard brightness percentage when the user signs in.
-device_only: true
-example_value: 60
-features:
-  dynamic_refresh: true
-owners:
-- lbowen@google.com
-- zentaro@google.com
-- cros-demo-mode-eng@google.com
-schema:
-  type: integer
-supported_on:
-- chrome_os:110-
-tags: []
-type: int
diff --git a/components/policy/tools/syntax_check_policy_template_json.py b/components/policy/tools/syntax_check_policy_template_json.py
index af50da4..a6449fb 100755
--- a/components/policy/tools/syntax_check_policy_template_json.py
+++ b/components/policy/tools/syntax_check_policy_template_json.py
@@ -1424,6 +1424,9 @@
 
     released_platforms = {}
     rolling_out_platform = {}
+    if policy == None:
+      return released_platforms, rolling_out_platform
+
     for supported_on in policy.get('supported_on', []):
       (supported_platform, supported_from,
        _) = _GetSupportedVersionPlatformAndRange(supported_on)
@@ -1832,7 +1835,7 @@
                         original_rolling_out_platforms),
               MergeDict(new_released_platforms, new_rolling_out_platform),
               current_version, new_policy)
-      else:
+      elif new_policy:
         (new_released_platforms,
          new_rolling_out_platform) = self._GetReleasedPlatforms(
              new_policy, current_version)
diff --git a/components/remote_cocoa/app_shim/native_widget_ns_window_bridge.h b/components/remote_cocoa/app_shim/native_widget_ns_window_bridge.h
index 156d06e..dddd706 100644
--- a/components/remote_cocoa/app_shim/native_widget_ns_window_bridge.h
+++ b/components/remote_cocoa/app_shim/native_widget_ns_window_bridge.h
@@ -340,9 +340,6 @@
   // Returns true if window restoration data exists from session restore.
   bool HasWindowRestorationData();
 
-  // Returns true if the window is fullscreen.
-  bool IsFullscreen();
-
   // CocoaMouseCaptureDelegate:
   bool PostCapturedEvent(NSEvent* event) override;
   void OnMouseCaptureLost() override;
diff --git a/components/remote_cocoa/app_shim/native_widget_ns_window_bridge.mm b/components/remote_cocoa/app_shim/native_widget_ns_window_bridge.mm
index 7fcbaf1..0359472 100644
--- a/components/remote_cocoa/app_shim/native_widget_ns_window_bridge.mm
+++ b/components/remote_cocoa/app_shim/native_widget_ns_window_bridge.mm
@@ -450,7 +450,7 @@
   // Validate the window's initial state, otherwise the bridge's initial
   // tracking state will be incorrect.
   DCHECK(![window_ isVisible]);
-  DCHECK(!IsFullscreen());
+  DCHECK_EQ(0u, [window_ styleMask] & NSWindowStyleMaskFullScreen);
 
   // Include "regular" windows without the standard frame in the window cycle.
   // These use NSWindowStyleMaskBorderless so do not get it by default.
@@ -760,8 +760,9 @@
     [NSApp activateIgnoringOtherApps:YES];
   } else if (new_state == WindowVisibilityState::kShowInactive && !parent_ &&
              ![window_ isMiniaturized]) {
-    if ([[NSApp mainWindow] screen] == [window_ screen] ||
-        ![[NSApp mainWindow] isKeyWindow]) {
+    NSWindow* mainWindow = [NSApp mainWindow];
+    if (mainWindow && ([mainWindow screen] == [window_ screen] ||
+                       ![mainWindow isKeyWindow])) {
       // When the new window is on the same display as the main window or the
       // main window is inactive, order the window relative to the main window.
       // Avoid making it the front window (with e.g. orderFront:), which can
@@ -862,11 +863,6 @@
   return !pending_restoration_data_.empty();
 }
 
-bool NativeWidgetNSWindowBridge::IsFullscreen() {
-  return ([window_ styleMask] & NSWindowStyleMaskFullScreen) ==
-         NSWindowStyleMaskFullScreen;
-}
-
 bool NativeWidgetNSWindowBridge::RunMoveLoop(const gfx::Vector2d& drag_offset) {
   // https://crbug.com/876493
   CHECK(!HasCapture());
@@ -1286,10 +1282,15 @@
   }
 
   bool is_key_window = [window_ isKeyWindow];
-  bool was_fullscreen = IsFullscreen();
   [window_ toggleFullScreen:nil];
-  // Ensure the transitioning window maintains focus (crbug.com/1338659).
-  if (!was_fullscreen && is_key_window)
+  // Ensure the transitioning window maintains focus.
+  // When a key window moves to a different space, AppKit will focus a
+  // different window on the previouly focused space to become key, which can
+  // break cross-display fullscreen transitions by losing focus of the
+  // transitioning window (crbug.com/1338659) or changing the z-order of
+  // windows on the previous space. Making the window key here seems to
+  // alleviate those apparent defects (crbug.com/1392542). 
+  if (is_key_window)
     [window_ makeKeyAndOrderFront:nil];
 }
 
diff --git a/components/safe_browsing/core/browser/verdict_cache_manager_unittest.cc b/components/safe_browsing/core/browser/verdict_cache_manager_unittest.cc
index 9010b66..3e76d07 100644
--- a/components/safe_browsing/core/browser/verdict_cache_manager_unittest.cc
+++ b/components/safe_browsing/core/browser/verdict_cache_manager_unittest.cc
@@ -332,7 +332,13 @@
                     LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE));
 }
 
-TEST_F(VerdictCacheManagerTest, TestCleanUpExpiredVerdict) {
+// TODO(crbug.com/1264925): This test is flaky on device.
+#if TARGET_IPHONE_SIMULATOR
+#define MAYBE_TestCleanUpExpiredVerdict TestCleanUpExpiredVerdict
+#else
+#define MAYBE_TestCleanUpExpiredVerdict DISABLED_TestCleanUpExpiredVerdict
+#endif
+TEST_F(VerdictCacheManagerTest, MAYBE_TestCleanUpExpiredVerdict) {
   // Prepare 4 verdicts for PASSWORD_REUSE_EVENT with SIGN_IN_PASSWORD type:
   // (1) "foo.com/abc/" valid
   // (2) "foo.com/def/" expired
diff --git a/components/saved_tab_groups/saved_tab_group.cc b/components/saved_tab_groups/saved_tab_group.cc
index fe757c0..e23a35f 100644
--- a/components/saved_tab_groups/saved_tab_group.cc
+++ b/components/saved_tab_groups/saved_tab_group.cc
@@ -25,23 +25,20 @@
     const tab_groups::TabGroupColorId& color,
     const std::vector<SavedTabGroupTab>& urls,
     absl::optional<base::GUID> saved_guid,
+    absl::optional<int> position,
     absl::optional<tab_groups::TabGroupId> local_group_id,
     absl::optional<base::Time> creation_time_windows_epoch_micros,
     absl::optional<base::Time> update_time_windows_epoch_micros)
-    : saved_guid_(saved_guid.has_value() ? saved_guid.value()
-                                         : base::GUID::GenerateRandomV4()),
+    : saved_guid_(saved_guid.value_or(base::GUID::GenerateRandomV4())),
       local_group_id_(local_group_id),
       title_(title),
       color_(color),
       saved_tabs_(urls),
+      position_(position.value_or(kUnsetPosition)),
       creation_time_windows_epoch_micros_(
-          creation_time_windows_epoch_micros.has_value()
-              ? creation_time_windows_epoch_micros.value()
-              : base::Time::Now()),
+          creation_time_windows_epoch_micros.value_or(base::Time::Now())),
       update_time_windows_epoch_micros_(
-          update_time_windows_epoch_micros.has_value()
-              ? update_time_windows_epoch_micros.value()
-              : base::Time::Now()) {}
+          update_time_windows_epoch_micros.value_or(base::Time::Now())) {}
 
 SavedTabGroup::SavedTabGroup(const SavedTabGroup& other) = default;
 
@@ -119,6 +116,12 @@
   return *this;
 }
 
+SavedTabGroup& SavedTabGroup::SetPosition(int position) {
+  position_ = position;
+  SetUpdateTimeWindowsEpochMicros(base::Time::Now());
+  return *this;
+}
+
 SavedTabGroup& SavedTabGroup::AddTab(size_t index, SavedTabGroupTab tab) {
   CHECK_GE(index, 0u);
   CHECK_LE(index, saved_tabs_.size());
@@ -205,15 +208,18 @@
   const tab_groups::TabGroupColorId color =
       SyncColorToTabGroupColor(specific.group().color());
   const std::u16string& title = base::UTF8ToUTF16(specific.group().title());
+  int position = specific.group().position();
 
   base::GUID guid = base::GUID::ParseLowercase(specific.guid());
   base::Time creation_time = base::Time::FromDeltaSinceWindowsEpoch(
       base::Microseconds(specific.creation_time_windows_epoch_micros()));
   base::Time update_time = base::Time::FromDeltaSinceWindowsEpoch(
       base::Microseconds(specific.update_time_windows_epoch_micros()));
+  SavedTabGroup group = SavedTabGroup(title, color, {}, guid, position,
+                                      absl::nullopt, creation_time);
+  group.SetUpdateTimeWindowsEpochMicros(update_time);
 
-  return SavedTabGroup(title, color, {}, guid, absl::nullopt, creation_time,
-                       update_time);
+  return group;
 }
 
 std::unique_ptr<sync_pb::SavedTabGroupSpecifics> SavedTabGroup::ToSpecifics()
@@ -233,6 +239,7 @@
   sync_pb::SavedTabGroup* pb_group = pb_specific->mutable_group();
   pb_group->set_color(TabGroupColorToSyncColor(color()));
   pb_group->set_title(base::UTF16ToUTF8(title()));
+  pb_group->set_position(position());
 
   return pb_specific;
 }
diff --git a/components/saved_tab_groups/saved_tab_group.h b/components/saved_tab_groups/saved_tab_group.h
index adf2e52..d928c1b 100644
--- a/components/saved_tab_groups/saved_tab_group.h
+++ b/components/saved_tab_groups/saved_tab_group.h
@@ -24,11 +24,14 @@
 // tab_group_editor_bubble_view.
 class SavedTabGroup {
  public:
+  static constexpr int kUnsetPosition = -1;
+
   SavedTabGroup(
       const std::u16string& title,
       const tab_groups::TabGroupColorId& color,
       const std::vector<SavedTabGroupTab>& urls,
       absl::optional<base::GUID> saved_guid = absl::nullopt,
+      absl::optional<int> position = absl::nullopt,
       absl::optional<tab_groups::TabGroupId> local_group_id = absl::nullopt,
       absl::optional<base::Time> creation_time_windows_epoch_micros =
           absl::nullopt,
@@ -53,6 +56,8 @@
   const std::vector<SavedTabGroupTab>& saved_tabs() const {
     return saved_tabs_;
   }
+  int position() const { return position_; }
+
   std::vector<SavedTabGroupTab>& saved_tabs() { return saved_tabs_; }
 
   // Accessors for Tabs based on id.
@@ -75,6 +80,7 @@
       absl::optional<tab_groups::TabGroupId> tab_group_id);
   SavedTabGroup& SetUpdateTimeWindowsEpochMicros(
       base::Time update_time_windows_epoch_micros);
+  SavedTabGroup& SetPosition(int position);
 
   // Tab mutators.
   // Adds `tab` to `saved_tabs_` at the specified `index` unless the added tab
@@ -136,6 +142,11 @@
   // The URLS and later webcontents (such as favicons) of the saved tab group.
   std::vector<SavedTabGroupTab> saved_tabs_;
 
+  // The current position of the group in relation to all other saved groups.
+  // A value of -1 means that the group was not assigned a position and will be
+  // assigned one when it is added into the SavedTabGroupModel.
+  int position_;
+
   // Timestamp for when the tab was created using windows epoch microseconds.
   base::Time creation_time_windows_epoch_micros_;
 
diff --git a/components/saved_tab_groups/saved_tab_group_model.cc b/components/saved_tab_groups/saved_tab_group_model.cc
index 340edad7..77c9b91 100644
--- a/components/saved_tab_groups/saved_tab_group_model.cc
+++ b/components/saved_tab_groups/saved_tab_group_model.cc
@@ -8,6 +8,7 @@
 #include <memory>
 #include <vector>
 
+#include "base/guid.h"
 #include "base/observer_list.h"
 #include "base/strings/utf_string_conversions.h"
 #include "components/saved_tab_groups/saved_tab_group.h"
@@ -77,23 +78,28 @@
 }
 
 void SavedTabGroupModel::Add(SavedTabGroup saved_group) {
-  if (Contains(saved_group.saved_guid()))
+  base::GUID group_guid = saved_group.saved_guid();
+  if (Contains(group_guid))
     return;
 
-  saved_tab_groups_.emplace_back(std::move(saved_group));
-  const int index = Count() - 1;
-  for (auto& observer : observers_) {
-    observer.SavedTabGroupAddedLocally(saved_tab_groups_[index].saved_guid());
-  }
+  // Give a default position to groups if it is not already set.
+  if (saved_group.position() == SavedTabGroup::kUnsetPosition)
+    saved_group.SetPosition(Count());
+
+  InsertGroupImpl(std::move(saved_group));
+
+  for (auto& observer : observers_)
+    observer.SavedTabGroupAddedLocally(Get(group_guid)->saved_guid());
 }
 
 void SavedTabGroupModel::Remove(const tab_groups::TabGroupId tab_group_id) {
   if (!Contains(tab_group_id))
     return;
 
-  const absl::optional<int> index = GetIndexOf(tab_group_id);
+  const int index = GetIndexOf(tab_group_id).value();
   base::GUID removed_guid = Get(tab_group_id)->saved_guid();
-  std::unique_ptr<SavedTabGroup> removed_group = RemoveImpl(index.value());
+  std::unique_ptr<SavedTabGroup> removed_group = RemoveImpl(index);
+  UpdatePositionsImpl();
   for (auto& observer : observers_)
     observer.SavedTabGroupRemovedLocally(removed_group.get());
 }
@@ -102,9 +108,10 @@
   if (!Contains(id))
     return;
 
-  const absl::optional<int> index = GetIndexOf(id);
+  const int index = GetIndexOf(id).value();
   base::GUID removed_guid = Get(id)->saved_guid();
-  std::unique_ptr<SavedTabGroup> removed_group = RemoveImpl(index.value());
+  std::unique_ptr<SavedTabGroup> removed_group = RemoveImpl(index);
+  UpdatePositionsImpl();
   for (auto& observer : observers_)
     observer.SavedTabGroupRemovedLocally(removed_group.get());
 }
@@ -135,13 +142,14 @@
 }
 
 void SavedTabGroupModel::AddedFromSync(SavedTabGroup saved_group) {
-  if (Contains(saved_group.saved_guid()))
+  base::GUID group_guid = saved_group.saved_guid();
+  if (Contains(group_guid))
     return;
 
-  saved_tab_groups_.emplace_back(std::move(saved_group));
-  const int index = Count() - 1;
+  InsertGroupImpl(std::move(saved_group));
+
   for (auto& observer : observers_)
-    observer.SavedTabGroupAddedFromSync(saved_tab_groups_[index].saved_guid());
+    observer.SavedTabGroupAddedFromSync(Get(group_guid)->saved_guid());
 }
 
 void SavedTabGroupModel::RemovedFromSync(
@@ -259,13 +267,18 @@
 
   DCHECK(Contains(group_id));
 
-  absl::optional<int> index = GetIndexOf(group_id);
-  saved_tab_groups_[index.value()].MergeGroup(std::move(sync_specific));
+  int index = GetIndexOf(group_id).value();
+  saved_tab_groups_[index].MergeGroup(std::move(sync_specific));
+
+  int preferred_index = Get(group_id)->position();
+  if (index != preferred_index) {
+    Reorder(group_id, preferred_index);
+  }
 
   for (auto& observer : observers_)
     observer.SavedTabGroupUpdatedFromSync(group_id);
 
-  return saved_tab_groups_[index.value()].ToSpecifics();
+  return Get(group_id)->ToSpecifics();
 }
 
 std::unique_ptr<sync_pb::SavedTabGroupSpecifics> SavedTabGroupModel::MergeTab(
@@ -301,10 +314,59 @@
   saved_tab_groups_.emplace(saved_tab_groups_.begin() + new_index,
                             std::move(group));
 
+  UpdatePositionsImpl();
+
   for (auto& observer : observers_)
     observer.SavedTabGroupReorderedLocally();
 }
 
+void SavedTabGroupModel::UpdatePositionsImpl() {
+  for (size_t i = 0; i < saved_tab_groups_.size(); ++i)
+    saved_tab_groups_[i].SetPosition(i);
+}
+
+void SavedTabGroupModel::InsertGroupImpl(const SavedTabGroup& group) {
+  // We can always safely insert the first group.
+  if (saved_tab_groups_.empty()) {
+    saved_tab_groups_.emplace_back(std::move(group));
+    return;
+  }
+
+  // Because saved_tab_groups_ must be in sorted order, we can immediately place
+  // the group at the end of the vector if `group` is the largest
+  // element we have seen yet.
+  if (saved_tab_groups_[saved_tab_groups_.size() - 1].position() <
+      group.position()) {
+    saved_tab_groups_.emplace_back(std::move(group));
+    return;
+  }
+
+  // Insert `group` in front of an element if one of these criteria
+  // are met:
+  // 1. The current index is larger than `group`.
+  // 2. The current index has the same position as `group` and is not
+  // the most recently updated position.
+  for (size_t index = 0; index < saved_tab_groups_.size(); ++index) {
+    const SavedTabGroup& curr_group = saved_tab_groups_[index];
+    bool curr_position_larger = curr_group.position() > group.position();
+    bool curr_position_same = curr_group.position() == group.position();
+    bool curr_position_least_recently_updated =
+        curr_group.update_time_windows_epoch_micros() <=
+        group.update_time_windows_epoch_micros();
+
+    if (curr_position_larger ||
+        (curr_position_same && curr_position_least_recently_updated)) {
+      saved_tab_groups_.insert(saved_tab_groups_.begin() + index,
+                               std::move(group));
+      return;
+    }
+  }
+
+  // This can happen when the last element of the vector has the same position
+  // as `group` and was more recently updated.
+  saved_tab_groups_.emplace_back(std::move(group));
+}
+
 std::vector<sync_pb::SavedTabGroupSpecifics>
 SavedTabGroupModel::LoadStoredEntries(
     std::vector<sync_pb::SavedTabGroupSpecifics> entries) {
@@ -321,6 +383,8 @@
       tabs.emplace_back(SavedTabGroupTab::FromSpecifics(proto));
   }
 
+  UpdatePositionsImpl();
+
   for (const SavedTabGroupTab& tab : tabs) {
     absl::optional<int> index = GetIndexOf(tab.saved_group_guid());
     if (!index.has_value()) {
diff --git a/components/saved_tab_groups/saved_tab_group_model.h b/components/saved_tab_groups/saved_tab_group_model.h
index 38fe7d02..33f3b472 100644
--- a/components/saved_tab_groups/saved_tab_group_model.h
+++ b/components/saved_tab_groups/saved_tab_group_model.h
@@ -139,6 +139,16 @@
   void RemoveObserver(SavedTabGroupModelObserver* observer);
 
  private:
+  // Updates all group positions to match the index they are currently stored
+  // at.
+  void UpdatePositionsImpl();
+
+  // Insert `group` into sorted order based on its position compared to already
+  // stored groups in `saved_tab_groups_`. It should be noted that
+  // `saved_tab_groups` must already be in sorted order for this function to
+  // work as intended. To do this, UpdatePositionsImpl() can be called.
+  void InsertGroupImpl(const SavedTabGroup& group);
+
   // Implementations of CRUD operations.
   std::unique_ptr<SavedTabGroup> RemoveImpl(int index);
   void UpdateVisualDataImpl(int index,
@@ -147,7 +157,10 @@
   // Obsevers of the model.
   base::ObserverList<SavedTabGroupModelObserver>::Unchecked observers_;
 
-  // Storage of all saved tab groups in the order they are displayed.
+  // Storage of all saved tab groups in the order they are displayed. The
+  // position of the groups must maintain sorted order as sync may not propagate
+  // an entire update completely leaving us with missing groups / gaps between
+  // the positions.
   std::vector<SavedTabGroup> saved_tab_groups_;
 };
 
diff --git a/components/saved_tab_groups/saved_tab_group_model_unittest.cc b/components/saved_tab_groups/saved_tab_group_model_unittest.cc
index 7a1e4af..8ee808e 100644
--- a/components/saved_tab_groups/saved_tab_group_model_unittest.cc
+++ b/components/saved_tab_groups/saved_tab_group_model_unittest.cc
@@ -37,6 +37,70 @@
   return guid;
 }
 
+void CompareSavedTabGroupTabs(const std::vector<SavedTabGroupTab>& v1,
+                              const std::vector<SavedTabGroupTab>& v2) {
+  ASSERT_EQ(v1.size(), v2.size());
+  for (size_t i = 0; i < v1.size(); i++) {
+    SavedTabGroupTab tab1 = v1[i];
+    SavedTabGroupTab tab2 = v2[i];
+    EXPECT_EQ(tab1.url(), tab2.url());
+    EXPECT_EQ(tab1.title(), tab2.title());
+    EXPECT_EQ(tab1.favicon(), tab2.favicon());
+  }
+}
+
+bool CompareSavedTabGroups(const SavedTabGroup& g1, const SavedTabGroup& g2) {
+  if (g1.title() != g2.title())
+    return false;
+  if (g1.color() != g2.color())
+    return false;
+  if (g1.position() != g2.position())
+    return false;
+  if (g1.saved_guid() != g2.saved_guid())
+    return false;
+  if (g1.creation_time_windows_epoch_micros() !=
+      g2.creation_time_windows_epoch_micros()) {
+    return false;
+  }
+
+  return true;
+}
+
+SavedTabGroup CreateSavedTabGroup(
+    const std::u16string& group_title,
+    const tab_groups::TabGroupColorId& color,
+    const std::vector<SavedTabGroupTab>& group_tabs,
+    const base::GUID& id) {
+  return SavedTabGroup(group_title, color, group_tabs, id);
+}
+
+SavedTabGroupTab CreateSavedTabGroupTab(const std::string& url,
+                                        const std::u16string& title,
+                                        const base::GUID& group_guid) {
+  SavedTabGroupTab tab(GURL(url), title, group_guid);
+  tab.SetFavicon(gfx::Image());
+  return tab;
+}
+
+SavedTabGroup CreateTestSavedTabGroup() {
+  base::GUID id = GenerateNextGUID();
+  const std::u16string title = u"Test Test";
+  const tab_groups::TabGroupColorId& color = tab_groups::TabGroupColorId::kBlue;
+
+  SavedTabGroupTab tab1 =
+      CreateSavedTabGroupTab("www.google.com", u"Google", id);
+  SavedTabGroupTab tab2 =
+      CreateSavedTabGroupTab("chrome://newtab", u"new tab", id);
+
+  tab1.SetFavicon(gfx::Image());
+  tab2.SetFavicon(gfx::Image());
+
+  std::vector<SavedTabGroupTab> tabs = {tab1, tab2};
+
+  SavedTabGroup group(title, color, tabs, id);
+  return group;
+}
+
 }  // namespace
 
 // Serves to test the functions in SavedTabGroupModelObserver.
@@ -94,40 +158,6 @@
     retrieved_guid_ = base::GUID::GenerateRandomV4();
   }
 
-  SavedTabGroupTab CreateSavedTabGroupTab(const std::string& url,
-                                          const std::u16string& title) {
-    SavedTabGroupTab tab(GURL(base_path_ + url), title,
-                         base::GUID::GenerateRandomV4());
-    tab.SetFavicon(gfx::Image());
-    return tab;
-  }
-
-  SavedTabGroup CreateTestSavedTabGroup() {
-    base::GUID id_4 = GenerateNextGUID();
-    const std::u16string title_4 = u"Test Test";
-    const tab_groups::TabGroupColorId& color_4 =
-        tab_groups::TabGroupColorId::kBlue;
-
-    SavedTabGroupTab tab1 = CreateSavedTabGroupTab("4th group", u"first tab");
-    SavedTabGroupTab tab2 = CreateSavedTabGroupTab("2nd link", u"new tab");
-    std::vector<SavedTabGroupTab> group_4_tabs = {tab1, tab2};
-
-    SavedTabGroup group_4(title_4, color_4, group_4_tabs);
-    return group_4;
-  }
-
-  void CompareSavedTabGroupTabs(const std::vector<SavedTabGroupTab>& v1,
-                                const std::vector<SavedTabGroupTab>& v2) {
-    ASSERT_EQ(v1.size(), v2.size());
-    for (size_t i = 0; i < v1.size(); i++) {
-      SavedTabGroupTab tab1 = v1[i];
-      SavedTabGroupTab tab2 = v2[i];
-      EXPECT_EQ(tab1.url(), tab2.url());
-      EXPECT_EQ(tab1.title(), tab2.title());
-      EXPECT_EQ(tab1.favicon(), tab2.favicon());
-    }
-  }
-
   std::unique_ptr<SavedTabGroupModel> saved_tab_group_model_;
   std::vector<SavedTabGroup> retrieved_group_;
   int retrieved_index_ = -1;
@@ -203,34 +233,6 @@
     }
   }
 
-  SavedTabGroupTab CreateSavedTabGroupTab(const std::string& url,
-                                          const std::u16string& title,
-                                          const base::GUID& group_guid) {
-    SavedTabGroupTab tab(GURL(base_path_ + url), title, group_guid);
-    tab.SetFavicon(gfx::Image());
-    return tab;
-  }
-
-  SavedTabGroup CreateSavedTabGroup(
-      const std::u16string& group_title,
-      const tab_groups::TabGroupColorId& color,
-      const std::vector<SavedTabGroupTab>& group_tabs,
-      const base::GUID& id) {
-    return SavedTabGroup(group_title, color, group_tabs, id);
-  }
-
-  void CompareSavedTabGroupTabs(const std::vector<SavedTabGroupTab>& v1,
-                                const std::vector<SavedTabGroupTab>& v2) {
-    EXPECT_EQ(v1.size(), v2.size());
-    for (size_t i = 0; i < v1.size(); i++) {
-      const SavedTabGroupTab& tab1 = v1[i];
-      const SavedTabGroupTab& tab2 = v2[i];
-      EXPECT_EQ(tab1.url(), tab2.url());
-      EXPECT_EQ(tab1.title(), tab2.title());
-      EXPECT_EQ(tab1.favicon(), tab2.favicon());
-    }
-  }
-
   std::unique_ptr<SavedTabGroupModel> saved_tab_group_model_;
   std::string base_path_ = "file:///c:/tmp/";
   base::GUID id_1_;
@@ -241,8 +243,7 @@
 // Tests that SavedTabGroupModel::Count holds 3 elements initially.
 TEST_F(SavedTabGroupModelTest, InitialCountThree) {
   EXPECT_EQ(saved_tab_group_model_->Count(), 3);
-  EXPECT_EQ(saved_tab_group_model_->saved_tab_groups().size(),
-            static_cast<unsigned long>(3));
+  EXPECT_EQ(saved_tab_group_model_->saved_tab_groups().size(), 3u);
 }
 
 // Tests that SavedTabGroupModel::Contains returns the 3, the number of starting
@@ -559,6 +560,243 @@
             merged_tab.update_time_windows_epoch_micros());
 }
 
+// Tests that groups inserted in the model are in order stay inserted in sorted
+// order.
+TEST_F(SavedTabGroupModelTest, GroupsSortedWithInOrderPositions) {
+  RemoveTestData();
+
+  // Create an arbitrary number of groups.
+  SavedTabGroup group_1(u"Group 1", tab_groups::TabGroupColorId::kRed, {});
+  SavedTabGroup group_2(u"Group 2", tab_groups::TabGroupColorId::kOrange, {});
+  SavedTabGroup group_3(u"Group 3", tab_groups::TabGroupColorId::kYellow, {});
+  SavedTabGroup group_4(u"Group 4", tab_groups::TabGroupColorId::kGreen, {});
+  SavedTabGroup group_5(u"Group 5", tab_groups::TabGroupColorId::kBlue, {});
+  SavedTabGroup group_6(u"Group 6", tab_groups::TabGroupColorId::kPurple, {});
+
+  // Set the positions the groups should sit in the bookmarks bar.
+  group_1.SetPosition(0);
+  group_2.SetPosition(1);
+  group_3.SetPosition(2);
+  group_4.SetPosition(3);
+  group_5.SetPosition(4);
+  group_6.SetPosition(5);
+
+  // This is the order we expect the groups in the model to be.
+  std::vector<SavedTabGroup> groups = {group_1, group_2, group_3,
+                                       group_4, group_5, group_6};
+
+  // Add the groups into the model in order.
+  saved_tab_group_model_->Add(group_1);
+  saved_tab_group_model_->Add(group_2);
+  saved_tab_group_model_->Add(group_3);
+  saved_tab_group_model_->Add(group_4);
+  saved_tab_group_model_->Add(group_5);
+  saved_tab_group_model_->Add(group_6);
+
+  EXPECT_EQ(saved_tab_group_model_->saved_tab_groups().size(), groups.size());
+  for (size_t i = 0; i < groups.size(); ++i) {
+    EXPECT_TRUE(CompareSavedTabGroups(
+        groups[i], saved_tab_group_model_->saved_tab_groups()[i]));
+  }
+}
+
+// Tests that groups inserted in the model out of order are still inserted in
+// sorted order.
+TEST_F(SavedTabGroupModelTest, GroupsSortedWithOutOfOrderPositions) {
+  RemoveTestData();
+
+  // Create an arbitrary number of groups.
+  SavedTabGroup group_1(u"Group 1", tab_groups::TabGroupColorId::kRed, {});
+  SavedTabGroup group_2(u"Group 2", tab_groups::TabGroupColorId::kOrange, {});
+  SavedTabGroup group_3(u"Group 3", tab_groups::TabGroupColorId::kYellow, {});
+  SavedTabGroup group_4(u"Group 4", tab_groups::TabGroupColorId::kGreen, {});
+  SavedTabGroup group_5(u"Group 5", tab_groups::TabGroupColorId::kBlue, {});
+  SavedTabGroup group_6(u"Group 6", tab_groups::TabGroupColorId::kPurple, {});
+
+  // Set the positions the groups should sit in the bookmarks bar.
+  group_1.SetPosition(0);
+  group_2.SetPosition(1);
+  group_3.SetPosition(2);
+  group_4.SetPosition(3);
+  group_5.SetPosition(4);
+  group_6.SetPosition(5);
+
+  // This is the order we expect the groups in the model to be.
+  std::vector<SavedTabGroup> groups = {group_1, group_2, group_3,
+                                       group_4, group_5, group_6};
+
+  // Add the groups into the model in an arbitrary order.
+  saved_tab_group_model_->Add(group_6);
+  saved_tab_group_model_->Add(group_1);
+  saved_tab_group_model_->Add(group_4);
+  saved_tab_group_model_->Add(group_3);
+  saved_tab_group_model_->Add(group_5);
+  saved_tab_group_model_->Add(group_2);
+
+  EXPECT_EQ(saved_tab_group_model_->saved_tab_groups().size(), groups.size());
+  for (size_t i = 0; i < groups.size(); ++i) {
+    EXPECT_TRUE(CompareSavedTabGroups(
+        groups[i], saved_tab_group_model_->saved_tab_groups()[i]));
+  }
+}
+
+// Tests that groups inserted in the model with gaps between the positions are
+// still inserted in sorted order.
+TEST_F(SavedTabGroupModelTest, GroupsSortedWithGapsInPositions) {
+  RemoveTestData();
+
+  // Create an arbitrary number of groups.
+  SavedTabGroup group_1(u"Group 1", tab_groups::TabGroupColorId::kRed, {});
+  SavedTabGroup group_2(u"Group 2", tab_groups::TabGroupColorId::kOrange, {});
+  SavedTabGroup group_3(u"Group 3", tab_groups::TabGroupColorId::kYellow, {});
+  SavedTabGroup group_4(u"Group 4", tab_groups::TabGroupColorId::kGreen, {});
+  SavedTabGroup group_5(u"Group 5", tab_groups::TabGroupColorId::kBlue, {});
+  SavedTabGroup group_6(u"Group 6", tab_groups::TabGroupColorId::kPurple, {});
+
+  // Set the positions the groups should sit in the bookmarks bar.
+  group_1.SetPosition(0);
+  group_2.SetPosition(3);
+  group_3.SetPosition(8);
+  group_4.SetPosition(19);
+  group_5.SetPosition(21);
+  group_6.SetPosition(34);
+
+  // This is the order we expect the groups in the model to be.
+  std::vector<SavedTabGroup> groups = {group_1, group_2, group_3,
+                                       group_4, group_5, group_6};
+
+  // Add the groups into the model in an arbitrary order.
+  saved_tab_group_model_->Add(group_6);
+  saved_tab_group_model_->Add(group_1);
+  saved_tab_group_model_->Add(group_4);
+  saved_tab_group_model_->Add(group_3);
+  saved_tab_group_model_->Add(group_5);
+  saved_tab_group_model_->Add(group_2);
+
+  EXPECT_EQ(saved_tab_group_model_->saved_tab_groups().size(), groups.size());
+  for (size_t i = 0; i < groups.size(); ++i) {
+    EXPECT_TRUE(CompareSavedTabGroups(
+        groups[i], saved_tab_group_model_->saved_tab_groups()[i]));
+  }
+}
+
+// Tests that groups inserted in the model with gaps and in decreasing order
+// between the positions are still inserted in increasing sorted order.
+TEST_F(SavedTabGroupModelTest, GroupsSortedWithDecreasingPositions) {
+  RemoveTestData();
+
+  // Create an arbitrary number of groups.
+  SavedTabGroup group_1(u"Group 1", tab_groups::TabGroupColorId::kRed, {});
+  SavedTabGroup group_2(u"Group 2", tab_groups::TabGroupColorId::kOrange, {});
+  SavedTabGroup group_3(u"Group 3", tab_groups::TabGroupColorId::kYellow, {});
+  SavedTabGroup group_4(u"Group 4", tab_groups::TabGroupColorId::kGreen, {});
+  SavedTabGroup group_5(u"Group 5", tab_groups::TabGroupColorId::kBlue, {});
+  SavedTabGroup group_6(u"Group 6", tab_groups::TabGroupColorId::kPurple, {});
+
+  // Set the positions the groups should sit in the bookmarks bar.
+  group_1.SetPosition(0);
+  group_2.SetPosition(3);
+  group_3.SetPosition(8);
+  group_4.SetPosition(19);
+  group_5.SetPosition(21);
+  group_6.SetPosition(34);
+
+  // This is the order we expect the groups in the model to be.
+  std::vector<SavedTabGroup> groups = {group_1, group_2, group_3,
+                                       group_4, group_5, group_6};
+
+  // Add the groups into the model in an arbitrary order.
+  saved_tab_group_model_->Add(group_6);
+  saved_tab_group_model_->Add(group_5);
+  saved_tab_group_model_->Add(group_4);
+  saved_tab_group_model_->Add(group_3);
+  saved_tab_group_model_->Add(group_2);
+  saved_tab_group_model_->Add(group_1);
+
+  EXPECT_EQ(saved_tab_group_model_->saved_tab_groups().size(), groups.size());
+  for (size_t i = 0; i < groups.size(); ++i) {
+    EXPECT_TRUE(CompareSavedTabGroups(
+        groups[i], saved_tab_group_model_->saved_tab_groups()[i]));
+  }
+}
+
+// Tests that groups inserted in the model with a more recent update time take
+// precedence over groups with the same position.
+TEST_F(SavedTabGroupModelTest, GroupWithSamePositionSortedByUpdateTime) {
+  RemoveTestData();
+
+  // Create an arbitrary number of groups.
+  SavedTabGroup group_1(u"Group 1", tab_groups::TabGroupColorId::kRed, {});
+  SavedTabGroup group_2(u"Group 2", tab_groups::TabGroupColorId::kOrange, {});
+
+  // Set the positions the groups should sit in the bookmarks bar.
+  group_1.SetPosition(0);
+  group_2.SetPosition(0);
+
+  // This is the order we expect the groups in the model to be.
+  std::vector<SavedTabGroup> groups = {group_2, group_1};
+
+  // Add the groups into the model in an arbitrary order.
+  saved_tab_group_model_->Add(group_1);
+  saved_tab_group_model_->Add(group_2);
+
+  EXPECT_EQ(saved_tab_group_model_->saved_tab_groups().size(), groups.size());
+  for (size_t i = 0; i < groups.size(); ++i) {
+    EXPECT_TRUE(CompareSavedTabGroups(
+        groups[i], saved_tab_group_model_->saved_tab_groups()[i]));
+  }
+}
+
+// Tests that groups inserted in the model with no position are inserted at the
+// back of the model and have their position set to the last index at the time
+// they were inserted.
+TEST_F(SavedTabGroupModelTest, GroupsWithNoPositionInsertedAtEnd) {
+  RemoveTestData();
+
+  // Create an arbitrary number of groups.
+  SavedTabGroup group_1(u"Group 1", tab_groups::TabGroupColorId::kRed, {});
+  SavedTabGroup group_2(u"Group 2", tab_groups::TabGroupColorId::kOrange, {});
+  SavedTabGroup group_3(u"Group 3", tab_groups::TabGroupColorId::kYellow, {});
+  SavedTabGroup group_4(u"Group 4", tab_groups::TabGroupColorId::kGreen, {});
+  SavedTabGroup group_5(u"Group 5", tab_groups::TabGroupColorId::kBlue, {});
+  SavedTabGroup group_6(u"Group 6", tab_groups::TabGroupColorId::kPurple, {});
+
+  // Set the positions the groups should sit in the bookmarks bar.
+  group_1.SetPosition(0);
+  group_2.SetPosition(1);
+  group_3.SetPosition(2);
+  group_4.SetPosition(3);
+  group_5.SetPosition(4);
+
+  // This is the order we expect the groups in the model to be.
+  std::vector<SavedTabGroup> groups = {group_1, group_2, group_3,
+                                       group_4, group_5, group_6};
+
+  // Add the groups into the model in an arbitrary order.
+  saved_tab_group_model_->Add(group_1);
+  saved_tab_group_model_->Add(group_2);
+  saved_tab_group_model_->Add(group_3);
+  saved_tab_group_model_->Add(group_4);
+  saved_tab_group_model_->Add(group_5);
+  saved_tab_group_model_->Add(group_6);
+
+  groups[5].SetPosition(5);
+
+  EXPECT_EQ(saved_tab_group_model_->saved_tab_groups().size(), groups.size());
+
+  // Expect the 6th group to have a position of 5 (0-based indexing).
+  EXPECT_EQ(saved_tab_group_model_
+                ->saved_tab_groups()
+                    [saved_tab_group_model_->saved_tab_groups().size() - 1]
+                .position(),
+            groups[5].position());
+
+  for (size_t i = 0; i < groups.size(); ++i) {
+    EXPECT_TRUE(CompareSavedTabGroups(
+        groups[i], saved_tab_group_model_->saved_tab_groups()[i]));
+  }
+}
+
 // Tests that SavedTabGroupModelObserver::Added passes the correct element from
 // the model.
 TEST_F(SavedTabGroupModelObserverTest, AddElement) {
diff --git a/components/saved_tab_groups/saved_tab_group_proto_conversion_unittest.cc b/components/saved_tab_groups/saved_tab_group_proto_conversion_unittest.cc
index e0a9fea..25411b2 100644
--- a/components/saved_tab_groups/saved_tab_group_proto_conversion_unittest.cc
+++ b/components/saved_tab_groups/saved_tab_group_proto_conversion_unittest.cc
@@ -75,7 +75,7 @@
   absl::optional<base::Time> creation_time_windows_epoch_micros = time_;
   absl::optional<base::Time> update_time_windows_epoch_micros = time_;
   SavedTabGroup group(title, color, {}, saved_guid, absl::nullopt,
-                      creation_time_windows_epoch_micros,
+                      absl::nullopt, creation_time_windows_epoch_micros,
                       update_time_windows_epoch_micros);
 
   // Use the group to create a STGSpecific.
@@ -176,7 +176,7 @@
   absl::optional<base::Time> creation_time_windows_epoch_micros = time_;
   absl::optional<base::Time> update_time_windows_epoch_micros = time_;
   SavedTabGroup group1(title, color, {}, saved_guid, absl::nullopt,
-                       creation_time_windows_epoch_micros,
+                       absl::nullopt, creation_time_windows_epoch_micros,
                        update_time_windows_epoch_micros);
 
   // Create a new group with the same data and update it. Calling set functions
diff --git a/components/saved_tab_groups/saved_tab_group_sync_bridge.cc b/components/saved_tab_groups/saved_tab_group_sync_bridge.cc
index f7bd59d..f64eba4 100644
--- a/components/saved_tab_groups/saved_tab_group_sync_bridge.cc
+++ b/components/saved_tab_groups/saved_tab_group_sync_bridge.cc
@@ -195,9 +195,19 @@
 
   const SavedTabGroup* group = model_->Get(guid);
   DCHECK(group);
-  UpsertEntitySpecific(group->ToSpecifics(), write_batch.get());
-  for (const SavedTabGroupTab& tab : group->saved_tabs())
-    UpsertEntitySpecific(tab.ToSpecifics(), write_batch.get());
+
+  int index = model_->GetIndexOf(guid).value();
+  std::unique_ptr<sync_pb::SavedTabGroupSpecifics> group_specific =
+      group->ToSpecifics();
+  group_specific->mutable_group()->set_position(index);
+
+  UpsertEntitySpecific(std::move(group_specific), write_batch.get());
+  for (size_t i = 0; i < group->saved_tabs().size(); ++i) {
+    std::unique_ptr<sync_pb::SavedTabGroupSpecifics> tab_specific =
+        group->saved_tabs()[i].ToSpecifics();
+    tab_specific->mutable_tab()->set_position(i);
+    UpsertEntitySpecific(std::move(tab_specific), write_batch.get());
+  }
 
   store_->CommitWriteBatch(
       std::move(write_batch),
@@ -239,7 +249,8 @@
 
 void SavedTabGroupSyncBridge::SavedTabGroupReorderedLocally() {
   // TODO(dljames): Find a more efficient way to only upsert the data that has
-  // changed.
+  // changed. If a group has changed, update all groups. If a tab has changed,
+  // update all tabs in its group.
   std::unique_ptr<syncer::ModelTypeStore::WriteBatch> write_batch =
       store_->CreateWriteBatch();
 
diff --git a/components/saved_tab_groups/saved_tab_group_sync_bridge_unittest.cc b/components/saved_tab_groups/saved_tab_group_sync_bridge_unittest.cc
index 88b18f6..f60b8a8 100644
--- a/components/saved_tab_groups/saved_tab_group_sync_bridge_unittest.cc
+++ b/components/saved_tab_groups/saved_tab_group_sync_bridge_unittest.cc
@@ -54,6 +54,8 @@
     return false;
   if (sp1.group().color() != sp2.group().color())
     return false;
+  if (sp1.group().position() != sp2.group().position())
+    return false;
   if (sp1.creation_time_windows_epoch_micros() !=
       sp2.creation_time_windows_epoch_micros()) {
     return false;
@@ -157,6 +159,7 @@
   SavedTabGroupTab tab_2(GURL("https://google.com"), u"Google",
                          group.saved_guid());
   group.AddTab(0, tab_1).AddTab(1, tab_2);
+  group.SetPosition(0);
 
   // Note: Here the change type does not matter. The initial merge will add
   // all elements in the change list into the model resolving any conflicts if
@@ -209,12 +212,13 @@
   // Create an updated version of `group` using the same creation time and 1
   // less tab.
   SavedTabGroup updated_group(u"New Title", tab_groups::TabGroupColorId::kPink,
-                              {}, group_guid, absl::nullopt,
+                              {}, group_guid, absl::nullopt, absl::nullopt,
                               group_creation_time);
   SavedTabGroupTab updated_tab_1(GURL("https://support.google.com"), u"Support",
                                  group_guid, nullptr, tab_1_guid, absl::nullopt,
                                  tab_1_creation_time);
   updated_group.AddTab(0, updated_tab_1);
+  updated_group.SetPosition(0);
 
   syncer::EntityChangeList entity_change_list = CreateEntityChangeListFromGroup(
       updated_group, syncer::EntityChange::ChangeType::ACTION_UPDATE);
@@ -273,6 +277,7 @@
   SavedTabGroup missing_group(u"New Group Title",
                               tab_groups::TabGroupColorId::kOrange, {},
                               orphaned_guid);
+  missing_group.SetPosition(0);
   syncer::EntityChangeList missing_group_change_list;
   missing_group_change_list.push_back(
       CreateEntityChange(missing_group.ToSpecifics(),
@@ -400,6 +405,7 @@
   SavedTabGroupTab tab_2(GURL("https://google.com"), u"Google",
                          group.saved_guid());
   group.AddTab(0, tab_1).AddTab(1, tab_2);
+  group.SetPosition(0);
 
   bridge_->ApplySyncChanges(
       bridge_->CreateMetadataChangeList(),
@@ -465,6 +471,7 @@
   SavedTabGroupTab tab_2(GURL("https://google.com"), u"Google",
                          group.saved_guid());
   group.AddTab(0, tab_1).AddTab(1, tab_2);
+  group.SetPosition(0);
 
   bridge_->ApplySyncChanges(
       bridge_->CreateMetadataChangeList(),
diff --git a/components/services/app_service/BUILD.gn b/components/services/app_service/BUILD.gn
index 8e6d6bf..fcfc171 100644
--- a/components/services/app_service/BUILD.gn
+++ b/components/services/app_service/BUILD.gn
@@ -22,25 +22,3 @@
     "//components/services/app_service/public/protos",
   ]
 }
-
-source_set("unit_tests") {
-  testonly = true
-
-  sources = [ "app_service_mojom_impl_unittest.cc" ]
-
-  deps = [
-    ":lib",
-    "//components/prefs:test_support",
-    "//components/services/app_service/public/cpp:app_types",
-    "//components/services/app_service/public/cpp:app_update",
-    "//components/services/app_service/public/cpp:icon_types",
-    "//components/services/app_service/public/cpp:intents",
-    "//components/services/app_service/public/cpp:preferred_app",
-    "//components/services/app_service/public/cpp:preferred_apps",
-    "//components/services/app_service/public/cpp:publisher",
-    "//components/services/app_service/public/cpp:run_on_os_login",
-    "//components/services/app_service/public/cpp:test_support",
-    "//content/test:test_support",
-    "//testing/gtest",
-  ]
-}
diff --git a/components/services/app_service/app_service_mojom_impl_unittest.cc b/components/services/app_service/app_service_mojom_impl_unittest.cc
deleted file mode 100644
index 899ac42..0000000
--- a/components/services/app_service/app_service_mojom_impl_unittest.cc
+++ /dev/null
@@ -1,307 +0,0 @@
-// Copyright 2018 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include <set>
-#include <sstream>
-#include <utility>
-#include <vector>
-
-#include "base/bind.h"
-#include "base/callback.h"
-#include "base/files/scoped_temp_dir.h"
-#include "base/run_loop.h"
-#include "base/test/scoped_feature_list.h"
-#include "components/services/app_service/app_service_mojom_impl.h"
-#include "components/services/app_service/public/cpp/app_capability_access_cache.h"
-#include "components/services/app_service/public/cpp/features.h"
-#include "components/services/app_service/public/cpp/preferred_app.h"
-#include "components/services/app_service/public/cpp/preferred_apps_list.h"
-#include "components/services/app_service/public/cpp/publisher_base.h"
-#include "components/services/app_service/public/mojom/types.mojom.h"
-#include "content/public/test/browser_task_environment.h"
-#include "mojo/public/cpp/bindings/clone_traits.h"
-#include "mojo/public/cpp/bindings/pending_receiver.h"
-#include "mojo/public/cpp/bindings/pending_remote.h"
-#include "mojo/public/cpp/bindings/receiver_set.h"
-#include "mojo/public/cpp/bindings/remote.h"
-#include "mojo/public/cpp/bindings/remote_set.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/abseil-cpp/absl/types/optional.h"
-
-namespace apps {
-
-class FakePublisher : public apps::PublisherBase {
- public:
-  FakePublisher(AppServiceMojomImpl* impl,
-                apps::mojom::AppType app_type,
-                std::vector<std::string> initial_app_ids)
-      : app_type_(app_type), known_app_ids_(std::move(initial_app_ids)) {
-    mojo::PendingRemote<apps::mojom::Publisher> remote;
-    receivers_.Add(this, remote.InitWithNewPipeAndPassReceiver());
-    impl->RegisterPublisher(std::move(remote), app_type_);
-  }
-
-  void PublishMoreApps(std::vector<std::string> app_ids) {
-    for (auto& subscriber : subscribers_) {
-      CallOnApps(subscriber.get(), app_ids, /*uninstall=*/false);
-    }
-    for (const auto& app_id : app_ids) {
-      known_app_ids_.push_back(app_id);
-    }
-  }
-
-  void ModifyCapabilityAccess(const std::string& app_id,
-                              absl::optional<bool> accessing_camera,
-                              absl::optional<bool> accessing_microphone) {
-    if (accessing_camera.has_value()) {
-      if (accessing_camera.value()) {
-        apps_accessing_camera_.insert(app_id);
-      } else {
-        apps_accessing_camera_.erase(app_id);
-      }
-    }
-
-    if (accessing_microphone.has_value()) {
-      if (accessing_microphone.value()) {
-        apps_accessing_microphone_.insert(app_id);
-      } else {
-        apps_accessing_microphone_.erase(app_id);
-      }
-    }
-
-    PublisherBase::ModifyCapabilityAccess(
-        subscribers_, app_id, accessing_camera, accessing_microphone);
-  }
-
-  void UninstallApps(std::vector<std::string> app_ids,
-                     AppServiceMojomImpl* impl) {
-    for (auto& subscriber : subscribers_) {
-      CallOnApps(subscriber.get(), app_ids, /*uninstall=*/true);
-    }
-    for (const auto& app_id : app_ids) {
-      known_app_ids_.push_back(app_id);
-    }
-  }
-
-  std::string load_icon_app_id;
-
- private:
-  void Connect(mojo::PendingRemote<apps::mojom::Subscriber> subscriber_remote,
-               apps::mojom::ConnectOptionsPtr opts) override {
-    mojo::Remote<apps::mojom::Subscriber> subscriber(
-        std::move(subscriber_remote));
-    CallOnApps(subscriber.get(), known_app_ids_, /*uninstall=*/false);
-    CallOnCapabilityAccesses(subscriber.get(), known_app_ids_);
-    subscribers_.Add(std::move(subscriber));
-  }
-  void CallOnApps(apps::mojom::Subscriber* subscriber,
-                  std::vector<std::string>& app_ids,
-                  bool uninstall) {
-    std::vector<apps::mojom::AppPtr> apps;
-    for (const auto& app_id : app_ids) {
-      auto app = apps::mojom::App::New();
-      app->app_type = app_type_;
-      app->app_id = app_id;
-      if (uninstall) {
-        app->readiness = apps::mojom::Readiness::kUninstalledByUser;
-      }
-      apps.push_back(std::move(app));
-    }
-    subscriber->OnApps(std::move(apps), app_type_,
-                       false /* should_notify_initialized */);
-  }
-
-  void CallOnCapabilityAccesses(apps::mojom::Subscriber* subscriber,
-                                std::vector<std::string>& app_ids) {
-    std::vector<apps::mojom::CapabilityAccessPtr> capability_accesses;
-    for (const auto& app_id : app_ids) {
-      auto capability_access = apps::mojom::CapabilityAccess::New();
-      capability_access->app_id = app_id;
-      if (apps_accessing_camera_.find(app_id) != apps_accessing_camera_.end()) {
-        capability_access->camera = apps::mojom::OptionalBool::kTrue;
-      }
-      if (apps_accessing_microphone_.find(app_id) !=
-          apps_accessing_microphone_.end()) {
-        capability_access->microphone = apps::mojom::OptionalBool::kTrue;
-      }
-      capability_accesses.push_back(std::move(capability_access));
-    }
-    subscriber->OnCapabilityAccesses(std::move(capability_accesses));
-  }
-
-  apps::mojom::AppType app_type_;
-  std::vector<std::string> known_app_ids_;
-  std::set<std::string> apps_accessing_camera_;
-  std::set<std::string> apps_accessing_microphone_;
-  mojo::ReceiverSet<apps::mojom::Publisher> receivers_;
-  mojo::RemoteSet<apps::mojom::Subscriber> subscribers_;
-};
-
-class FakeSubscriber : public apps::mojom::Subscriber {
- public:
-  explicit FakeSubscriber(AppServiceMojomImpl* impl) {
-    mojo::PendingRemote<apps::mojom::Subscriber> remote;
-    receivers_.Add(this, remote.InitWithNewPipeAndPassReceiver());
-    impl->RegisterSubscriber(std::move(remote), nullptr);
-  }
-
-  std::string AppIdsSeen() {
-    std::stringstream ss;
-    for (const auto& app_id : app_ids_seen_) {
-      ss << app_id;
-    }
-    return ss.str();
-  }
-
-  std::string AppIdsAccessingCamera() {
-    std::stringstream ss;
-    for (const auto& app_id : cache_.GetAppsAccessingCamera()) {
-      ss << app_id;
-    }
-    return ss.str();
-  }
-
-  std::string AppIdsAccessingMicrophone() {
-    std::stringstream ss;
-    for (const auto& app_id : cache_.GetAppsAccessingMicrophone()) {
-      ss << app_id;
-    }
-    return ss.str();
-  }
-
- private:
-  void OnApps(std::vector<apps::mojom::AppPtr> deltas,
-              apps::mojom::AppType app_type,
-              bool should_notify_initialized) override {
-    for (const auto& delta : deltas) {
-      app_ids_seen_.insert(delta->app_id);
-    }
-  }
-
-  void OnCapabilityAccesses(
-      std::vector<apps::mojom::CapabilityAccessPtr> deltas) override {
-    cache_.OnCapabilityAccesses(std::move(deltas));
-  }
-
-  void Clone(mojo::PendingReceiver<apps::mojom::Subscriber> receiver) override {
-    receivers_.Add(this, std::move(receiver));
-  }
-
-  mojo::ReceiverSet<apps::mojom::Subscriber> receivers_;
-  std::set<std::string> app_ids_seen_;
-  AppCapabilityAccessCache cache_;
-};
-
-class AppServiceMojomImplTest : public testing::Test {
- protected:
-  AppServiceMojomImplTest() {
-    scoped_feature_list_.InitWithFeatures(
-        {}, {kAppServiceCapabilityAccessWithoutMojom});
-  }
-
-  content::BrowserTaskEnvironment task_environment_;
-  base::ScopedTempDir temp_dir_;
-  base::test::ScopedFeatureList scoped_feature_list_;
-};
-
-TEST_F(AppServiceMojomImplTest, PubSub) {
-  ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
-  AppServiceMojomImpl impl(temp_dir_.GetPath());
-
-  // Start with one subscriber.
-  FakeSubscriber sub0(&impl);
-  impl.FlushMojoCallsForTesting();
-  EXPECT_EQ("", sub0.AppIdsSeen());
-  EXPECT_EQ("", sub0.AppIdsAccessingCamera());
-  EXPECT_EQ("", sub0.AppIdsAccessingMicrophone());
-
-  // Add one publisher.
-  FakePublisher pub0(&impl, apps::mojom::AppType::kArc,
-                     std::vector<std::string>{"A", "B"});
-  impl.FlushMojoCallsForTesting();
-  EXPECT_EQ("AB", sub0.AppIdsSeen());
-  EXPECT_EQ("", sub0.AppIdsAccessingCamera());
-  EXPECT_EQ("", sub0.AppIdsAccessingMicrophone());
-
-  pub0.ModifyCapabilityAccess("B", absl::nullopt, absl::nullopt);
-  impl.FlushMojoCallsForTesting();
-  EXPECT_EQ("", sub0.AppIdsAccessingCamera());
-  EXPECT_EQ("", sub0.AppIdsAccessingMicrophone());
-
-  pub0.ModifyCapabilityAccess("B", true, absl::nullopt);
-  impl.FlushMojoCallsForTesting();
-  EXPECT_EQ("B", sub0.AppIdsAccessingCamera());
-  EXPECT_EQ("", sub0.AppIdsAccessingMicrophone());
-
-  // Have that publisher publish more apps.
-  pub0.PublishMoreApps(std::vector<std::string>{"C", "D", "E"});
-  impl.FlushMojoCallsForTesting();
-  EXPECT_EQ("ABCDE", sub0.AppIdsSeen());
-
-  pub0.ModifyCapabilityAccess("D", true, true);
-  impl.FlushMojoCallsForTesting();
-  EXPECT_EQ("BD", sub0.AppIdsAccessingCamera());
-  EXPECT_EQ("D", sub0.AppIdsAccessingMicrophone());
-
-  // Add a second publisher.
-  FakePublisher pub1(&impl, apps::mojom::AppType::kBuiltIn,
-                     std::vector<std::string>{"m"});
-  impl.FlushMojoCallsForTesting();
-  EXPECT_EQ("ABCDEm", sub0.AppIdsSeen());
-
-  // Have both publishers publish more apps.
-  pub0.PublishMoreApps(std::vector<std::string>{"F"});
-  pub1.PublishMoreApps(std::vector<std::string>{"n"});
-  pub0.ModifyCapabilityAccess("B", false, absl::nullopt);
-  pub1.ModifyCapabilityAccess("n", absl::nullopt, true);
-  impl.FlushMojoCallsForTesting();
-  EXPECT_EQ("ABCDEFmn", sub0.AppIdsSeen());
-  EXPECT_EQ("D", sub0.AppIdsAccessingCamera());
-  EXPECT_EQ("Dn", sub0.AppIdsAccessingMicrophone());
-
-  // Add a second subscriber.
-  FakeSubscriber sub1(&impl);
-  impl.FlushMojoCallsForTesting();
-  EXPECT_EQ("ABCDEFmn", sub0.AppIdsSeen());
-  EXPECT_EQ("ABCDEFmn", sub1.AppIdsSeen());
-  EXPECT_EQ("D", sub0.AppIdsAccessingCamera());
-  EXPECT_EQ("Dn", sub0.AppIdsAccessingMicrophone());
-  EXPECT_EQ("D", sub1.AppIdsAccessingCamera());
-  EXPECT_EQ("Dn", sub1.AppIdsAccessingMicrophone());
-
-  // Publish more apps.
-  pub0.ModifyCapabilityAccess("D", false, false);
-  pub1.PublishMoreApps(std::vector<std::string>{"o", "p", "q"});
-  pub1.ModifyCapabilityAccess("n", true, absl::nullopt);
-  impl.FlushMojoCallsForTesting();
-  EXPECT_EQ("ABCDEFmnopq", sub0.AppIdsSeen());
-  EXPECT_EQ("ABCDEFmnopq", sub1.AppIdsSeen());
-  EXPECT_EQ("n", sub0.AppIdsAccessingCamera());
-  EXPECT_EQ("n", sub0.AppIdsAccessingMicrophone());
-  EXPECT_EQ("n", sub1.AppIdsAccessingCamera());
-  EXPECT_EQ("n", sub1.AppIdsAccessingMicrophone());
-
-  // Add a third publisher.
-  FakePublisher pub2(&impl, apps::mojom::AppType::kCrostini,
-                     std::vector<std::string>{"$"});
-  impl.FlushMojoCallsForTesting();
-  EXPECT_EQ("$ABCDEFmnopq", sub0.AppIdsSeen());
-  EXPECT_EQ("$ABCDEFmnopq", sub1.AppIdsSeen());
-
-  // Publish more apps.
-  pub2.PublishMoreApps(std::vector<std::string>{"&"});
-  pub1.PublishMoreApps(std::vector<std::string>{"r"});
-  pub0.PublishMoreApps(std::vector<std::string>{"G"});
-  pub1.ModifyCapabilityAccess("n", false, false);
-  pub2.ModifyCapabilityAccess("&", true, false);
-  impl.FlushMojoCallsForTesting();
-  EXPECT_EQ("$&ABCDEFGmnopqr", sub0.AppIdsSeen());
-  EXPECT_EQ("$&ABCDEFGmnopqr", sub1.AppIdsSeen());
-  EXPECT_EQ("&", sub0.AppIdsAccessingCamera());
-  EXPECT_EQ("", sub0.AppIdsAccessingMicrophone());
-  EXPECT_EQ("&", sub1.AppIdsAccessingCamera());
-  EXPECT_EQ("", sub1.AppIdsAccessingMicrophone());
-}
-
-}  // namespace apps
diff --git a/components/services/app_service/public/cpp/BUILD.gn b/components/services/app_service/public/cpp/BUILD.gn
index c65d33e3..82431fe2 100644
--- a/components/services/app_service/public/cpp/BUILD.gn
+++ b/components/services/app_service/public/cpp/BUILD.gn
@@ -329,13 +329,11 @@
   testonly = true
 
   sources = [
-    "app_capability_access_cache_mojom_unittest.cc",
     "app_capability_access_cache_unittest.cc",
     "app_capability_access_cache_wrapper_unittest.cc",
     "app_registry_cache_unittest.cc",
     "app_registry_cache_wrapper_unittest.cc",
     "app_update_unittest.cc",
-    "capability_access_update_mojom_unittest.cc",
     "capability_access_update_unittest.cc",
     "icon_cache_unittest.cc",
     "icon_coalescer_unittest.cc",
diff --git a/components/services/app_service/public/cpp/app_capability_access_cache.cc b/components/services/app_service/public/cpp/app_capability_access_cache.cc
index 94a9a60d..7c39fa0 100644
--- a/components/services/app_service/public/cpp/app_capability_access_cache.cc
+++ b/components/services/app_service/public/cpp/app_capability_access_cache.cc
@@ -81,24 +81,6 @@
 }
 
 void AppCapabilityAccessCache::OnCapabilityAccesses(
-    std::vector<apps::mojom::CapabilityAccessPtr> deltas) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
-
-  if (!mojom_deltas_in_progress_.empty()) {
-    std::move(deltas.begin(), deltas.end(),
-              std::back_inserter(mojom_deltas_pending_));
-    return;
-  }
-
-  DoOnCapabilityAccesses(std::move(deltas));
-  while (!mojom_deltas_pending_.empty()) {
-    std::vector<apps::mojom::CapabilityAccessPtr> pending;
-    pending.swap(mojom_deltas_pending_);
-    DoOnCapabilityAccesses(std::move(pending));
-  }
-}
-
-void AppCapabilityAccessCache::OnCapabilityAccesses(
     std::vector<CapabilityAccessPtr> deltas) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
 
@@ -117,53 +99,6 @@
 }
 
 void AppCapabilityAccessCache::DoOnCapabilityAccesses(
-    std::vector<apps::mojom::CapabilityAccessPtr> deltas) {
-  // Merge any deltas elements that have the same app_id. If an observer's
-  // OnCapabilityAccessUpdate calls back into this AppCapabilityAccessCache then
-  // we can therefore present a single delta for any given app_id.
-  for (auto& delta : deltas) {
-    auto d_iter = mojom_deltas_in_progress_.find(delta->app_id);
-    if (d_iter != mojom_deltas_in_progress_.end()) {
-      CapabilityAccessUpdate::Merge(d_iter->second, delta.get());
-    } else {
-      mojom_deltas_in_progress_[delta->app_id] = delta.get();
-    }
-  }
-
-  // The remaining for loops range over the deltas_in_progress_ map, not the
-  // deltas vector, so that OnCapabilityAccessUpdate is called only once per
-  // unique app_id.
-
-  // Notify the observers for every de-duplicated delta.
-  for (const auto& d_iter : mojom_deltas_in_progress_) {
-    auto s_iter = mojom_states_.find(d_iter.first);
-    apps::mojom::CapabilityAccess* state =
-        (s_iter != mojom_states_.end()) ? s_iter->second.get() : nullptr;
-    apps::mojom::CapabilityAccess* delta = d_iter.second;
-
-    for (auto& obs : observers_) {
-      obs.OnCapabilityAccessUpdate(
-          CapabilityAccessUpdate(state, delta, account_id_));
-    }
-  }
-
-  // Update the states for every de-duplicated delta.
-  for (const auto& d_iter : mojom_deltas_in_progress_) {
-    auto s_iter = mojom_states_.find(d_iter.first);
-    apps::mojom::CapabilityAccess* state =
-        (s_iter != mojom_states_.end()) ? s_iter->second.get() : nullptr;
-    apps::mojom::CapabilityAccess* delta = d_iter.second;
-
-    if (state) {
-      CapabilityAccessUpdate::Merge(state, delta);
-    } else {
-      mojom_states_.insert(std::make_pair(delta->app_id, delta->Clone()));
-    }
-  }
-  mojom_deltas_in_progress_.clear();
-}
-
-void AppCapabilityAccessCache::DoOnCapabilityAccesses(
     std::vector<CapabilityAccessPtr> deltas) {
   // Merge any deltas elements that have the same app_id. If an observer's
   // OnCapabilityAccessUpdate calls back into this AppCapabilityAccessCache then
diff --git a/components/services/app_service/public/cpp/app_capability_access_cache.h b/components/services/app_service/public/cpp/app_capability_access_cache.h
index 5d1ff2f3..7e01b8c 100644
--- a/components/services/app_service/public/cpp/app_capability_access_cache.h
+++ b/components/services/app_service/public/cpp/app_capability_access_cache.h
@@ -17,7 +17,6 @@
 #include "components/account_id/account_id.h"
 #include "components/services/app_service/public/cpp/capability_access.h"
 #include "components/services/app_service/public/cpp/capability_access_update.h"
-#include "components/services/app_service/public/cpp/features.h"
 
 namespace apps {
 
@@ -110,8 +109,6 @@
   // The callee will consume the deltas. An apps::mojom::CapabilityAccessPtr has
   // the ownership semantics of a unique_ptr, and will be deleted when out of
   // scope. The caller presumably calls OnCapabilityAccesses(std::move(deltas)).
-  void OnCapabilityAccesses(
-      std::vector<apps::mojom::CapabilityAccessPtr> deltas);
   void OnCapabilityAccesses(std::vector<CapabilityAccessPtr> deltas);
 
   // Calls f, a void-returning function whose arguments are (const
@@ -132,50 +129,25 @@
   void ForEachApp(FunctionType f) {
     DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
 
-    if (base::FeatureList::IsEnabled(kAppServiceCapabilityAccessWithoutMojom)) {
-      for (const auto& s_iter : states_) {
-        const CapabilityAccess* state = s_iter.second.get();
+    for (const auto& s_iter : states_) {
+      const CapabilityAccess* state = s_iter.second.get();
 
-        auto d_iter = deltas_in_progress_.find(s_iter.first);
-        const CapabilityAccess* delta =
-            (d_iter != deltas_in_progress_.end()) ? d_iter->second : nullptr;
+      auto d_iter = deltas_in_progress_.find(s_iter.first);
+      const CapabilityAccess* delta =
+          (d_iter != deltas_in_progress_.end()) ? d_iter->second : nullptr;
 
-        f(CapabilityAccessUpdate(state, delta, account_id_));
-      }
-
-      for (const auto& d_iter : deltas_in_progress_) {
-        const CapabilityAccess* delta = d_iter.second;
-
-        auto s_iter = states_.find(d_iter.first);
-        if (s_iter != states_.end()) {
-          continue;
-        }
-
-        f(CapabilityAccessUpdate(nullptr, delta, account_id_));
-      }
-      return;
+      f(CapabilityAccessUpdate(state, delta, account_id_));
     }
 
-    for (const auto& s_iter : mojom_states_) {
-      const apps::mojom::CapabilityAccess* state = s_iter.second.get();
+    for (const auto& d_iter : deltas_in_progress_) {
+      const CapabilityAccess* delta = d_iter.second;
 
-      auto d_iter = mojom_deltas_in_progress_.find(s_iter.first);
-      const apps::mojom::CapabilityAccess* delta =
-          (d_iter != mojom_deltas_in_progress_.end()) ? d_iter->second
-                                                      : nullptr;
-
-      f(apps::CapabilityAccessUpdate(state, delta, account_id_));
-    }
-
-    for (const auto& d_iter : mojom_deltas_in_progress_) {
-      const apps::mojom::CapabilityAccess* delta = d_iter.second;
-
-      auto s_iter = mojom_states_.find(d_iter.first);
-      if (s_iter != mojom_states_.end()) {
+      auto s_iter = states_.find(d_iter.first);
+      if (s_iter != states_.end()) {
         continue;
       }
 
-      f(apps::CapabilityAccessUpdate(nullptr, delta, account_id_));
+      f(CapabilityAccessUpdate(nullptr, delta, account_id_));
     }
   }
 
@@ -191,46 +163,27 @@
   bool ForOneApp(const std::string& app_id, FunctionType f) {
     DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
 
-    if (base::FeatureList::IsEnabled(kAppServiceCapabilityAccessWithoutMojom)) {
-      auto s_iter = states_.find(app_id);
-      const CapabilityAccess* state =
-          (s_iter != states_.end()) ? s_iter->second.get() : nullptr;
+    auto s_iter = states_.find(app_id);
+    const CapabilityAccess* state =
+        (s_iter != states_.end()) ? s_iter->second.get() : nullptr;
 
-      auto d_iter = deltas_in_progress_.find(app_id);
-      const CapabilityAccess* delta =
-          (d_iter != deltas_in_progress_.end()) ? d_iter->second : nullptr;
-
-      if (state || delta) {
-        f(CapabilityAccessUpdate(state, delta, account_id_));
-        return true;
-      }
-      return false;
-    }
-
-    auto s_iter = mojom_states_.find(app_id);
-    const apps::mojom::CapabilityAccess* state =
-        (s_iter != mojom_states_.end()) ? s_iter->second.get() : nullptr;
-
-    auto d_iter = mojom_deltas_in_progress_.find(app_id);
-    const apps::mojom::CapabilityAccess* delta =
-        (d_iter != mojom_deltas_in_progress_.end()) ? d_iter->second : nullptr;
+    auto d_iter = deltas_in_progress_.find(app_id);
+    const CapabilityAccess* delta =
+        (d_iter != deltas_in_progress_.end()) ? d_iter->second : nullptr;
 
     if (state || delta) {
-      f(apps::CapabilityAccessUpdate(state, delta, account_id_));
+      f(CapabilityAccessUpdate(state, delta, account_id_));
       return true;
     }
     return false;
   }
 
  private:
-  void DoOnCapabilityAccesses(
-      std::vector<apps::mojom::CapabilityAccessPtr> deltas);
   void DoOnCapabilityAccesses(std::vector<CapabilityAccessPtr> deltas);
 
   base::ObserverList<Observer> observers_;
 
   // Maps from app_id to the latest state: the "sum" of all previous deltas.
-  std::map<std::string, apps::mojom::CapabilityAccessPtr> mojom_states_;
   std::map<std::string, CapabilityAccessPtr> states_;
 
   // Track the deltas being processed or are about to be processed by
@@ -251,9 +204,6 @@
   // Nested OnCapabilityAccesses calls are expected to be rare (but still dealt
   // with sensibly). In the typical case, OnCapabilityAccesses should call
   // DoOnCapabilityAccesses exactly once, and deltas_pending_ will stay empty.
-  std::map<std::string, apps::mojom::CapabilityAccess*>
-      mojom_deltas_in_progress_;
-  std::vector<apps::mojom::CapabilityAccessPtr> mojom_deltas_pending_;
   std::map<std::string, CapabilityAccess*> deltas_in_progress_;
   std::vector<CapabilityAccessPtr> deltas_pending_;
 
diff --git a/components/services/app_service/public/cpp/app_capability_access_cache_mojom_unittest.cc b/components/services/app_service/public/cpp/app_capability_access_cache_mojom_unittest.cc
deleted file mode 100644
index 67360ac7..0000000
--- a/components/services/app_service/public/cpp/app_capability_access_cache_mojom_unittest.cc
+++ /dev/null
@@ -1,442 +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.
-
-#include <set>
-
-#include "base/memory/raw_ptr.h"
-#include "base/test/scoped_feature_list.h"
-#include "components/services/app_service/public/cpp/app_capability_access_cache.h"
-#include "components/services/app_service/public/cpp/features.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-class AppCapabilityAccessCacheMojomTest
-    : public testing::Test,
-      public apps::AppCapabilityAccessCache::Observer {
- protected:
-  AppCapabilityAccessCacheMojomTest() {
-    scoped_feature_list_.Reset();
-    scoped_feature_list_.InitAndDisableFeature(
-        apps::kAppServiceCapabilityAccessWithoutMojom);
-  }
-
-  static apps::mojom::CapabilityAccessPtr MakeCapabilityAccess(
-      const char* app_id,
-      apps::mojom::OptionalBool camera,
-      apps::mojom::OptionalBool microphone) {
-    apps::mojom::CapabilityAccessPtr access =
-        apps::mojom::CapabilityAccess::New();
-    access->app_id = app_id;
-    access->camera = camera;
-    access->microphone = microphone;
-    return access;
-  }
-
-  void CallForEachApp(apps::AppCapabilityAccessCache& cache) {
-    cache.ForEachApp([this](const apps::CapabilityAccessUpdate& update) {
-      OnCapabilityAccessUpdate(update);
-    });
-  }
-
-  // apps::AppCapabilityAccessCache::Observer overrides.
-  void OnCapabilityAccessUpdate(
-      const apps::CapabilityAccessUpdate& update) override {
-    EXPECT_EQ(account_id_, update.AccountId());
-    updated_ids_.insert(update.AppId());
-    auto camera = update.Camera();
-    if (camera.value_or(false)) {
-      accessing_camera_apps_.insert(update.AppId());
-    } else {
-      accessing_camera_apps_.erase(update.AppId());
-    }
-    auto microphone = update.Microphone();
-    if (microphone.value_or(false)) {
-      accessing_microphone_apps_.insert(update.AppId());
-    } else {
-      accessing_microphone_apps_.erase(update.AppId());
-    }
-  }
-
-  void OnAppCapabilityAccessCacheWillBeDestroyed(
-      apps::AppCapabilityAccessCache* cache) override {
-    // The test code explicitly calls both AddObserver and RemoveObserver.
-    NOTREACHED();
-  }
-
-  const AccountId& account_id() const { return account_id_; }
-
-  base::test::ScopedFeatureList scoped_feature_list_;
-  std::set<std::string> updated_ids_;
-  std::set<std::string> accessing_camera_apps_;
-  std::set<std::string> accessing_microphone_apps_;
-  AccountId account_id_ = AccountId::FromUserEmail("test@gmail.com");
-};
-
-// Responds to a app_capability_access's OnCapabilityAccessUpdate to call back
-// into AppCapabilityAccessCache, checking that AppCapabilityAccessCache
-// presents a self-consistent snapshot. For example, the camera should match for
-// the outer and inner CapabilityAccessUpdate.
-//
-// In the tests below, just "recursive" means that
-// app_capability_access.OnCapabilityAccesses calls
-// observer.OnCapabilityAccessUpdate which calls
-// app_capability_access.ForEachApp and app_capability_access.ForOneApp.
-// "Super-recursive" means that app_capability_access.OnCapabilityAccesses calls
-// observer.OnCapabilityAccessUpdate calls
-// app_capability_access.OnCapabilityAccesses which calls
-// observer.OnCapabilityAccessUpdate.
-class CapabilityAccessRecursiveObserverMojom
-    : public apps::AppCapabilityAccessCache::Observer {
- public:
-  explicit CapabilityAccessRecursiveObserverMojom(
-      apps::AppCapabilityAccessCache* cache)
-      : cache_(cache) {
-    Observe(cache);
-  }
-
-  ~CapabilityAccessRecursiveObserverMojom() override = default;
-
-  void PrepareForOnCapabilityAccesses(
-      int expected_num_apps,
-      std::vector<apps::mojom::CapabilityAccessPtr>* super_recursive_accesses =
-          nullptr) {
-    expected_num_apps_ = expected_num_apps;
-    num_apps_seen_on_capability_access_update_ = 0;
-
-    if (super_recursive_accesses) {
-      super_recursive_accesses_.swap(*super_recursive_accesses);
-    }
-  }
-
-  int NumAppsSeenOnCapabilityAccessUpdate() {
-    return num_apps_seen_on_capability_access_update_;
-  }
-
-  const std::set<std::string>& accessing_camera_apps() {
-    return accessing_camera_apps_;
-  }
-
-  const std::set<std::string>& accessing_microphone_apps() {
-    return accessing_microphone_apps_;
-  }
-
- protected:
-  // apps::AppCapabilityAccessCache::Observer overrides.
-  void OnCapabilityAccessUpdate(
-      const apps::CapabilityAccessUpdate& outer) override {
-    EXPECT_EQ(account_id_, outer.AccountId());
-    int num_apps = 0;
-    cache_->ForEachApp(
-        [this, &outer, &num_apps](const apps::CapabilityAccessUpdate& inner) {
-          auto camera = inner.Camera();
-          if (camera.value_or(false)) {
-            accessing_camera_apps_.insert(inner.AppId());
-          } else {
-            accessing_camera_apps_.erase(inner.AppId());
-          }
-          auto microphone = inner.Microphone();
-          if (microphone.value_or(false)) {
-            accessing_microphone_apps_.insert(inner.AppId());
-          } else {
-            accessing_microphone_apps_.erase(inner.AppId());
-          }
-
-          if (outer.AppId() == inner.AppId()) {
-            ExpectEq(outer, inner);
-          }
-
-          num_apps++;
-        });
-    EXPECT_EQ(expected_num_apps_, num_apps);
-
-    EXPECT_FALSE(cache_->ForOneApp(
-        "no_such_app_id", [&outer](const apps::CapabilityAccessUpdate& inner) {
-          ExpectEq(outer, inner);
-        }));
-
-    EXPECT_TRUE(cache_->ForOneApp(
-        outer.AppId(), [&outer](const apps::CapabilityAccessUpdate& inner) {
-          ExpectEq(outer, inner);
-        }));
-
-    std::vector<apps::mojom::CapabilityAccessPtr> super_recursive;
-    while (!super_recursive_accesses_.empty()) {
-      apps::mojom::CapabilityAccessPtr access =
-          std::move(super_recursive_accesses_.back());
-      super_recursive_accesses_.pop_back();
-      if (access.get() == nullptr) {
-        // This is the placeholder 'punctuation'.
-        break;
-      }
-      super_recursive.push_back(std::move(access));
-    }
-    if (!super_recursive.empty()) {
-      cache_->OnCapabilityAccesses(std::move(super_recursive));
-    }
-
-    num_apps_seen_on_capability_access_update_++;
-  }
-
-  void OnAppCapabilityAccessCacheWillBeDestroyed(
-      apps::AppCapabilityAccessCache* cache) override {
-    Observe(nullptr);
-  }
-
-  static void ExpectEq(const apps::CapabilityAccessUpdate& outer,
-                       const apps::CapabilityAccessUpdate& inner) {
-    EXPECT_EQ(outer.AppId(), inner.AppId());
-    EXPECT_EQ(outer.StateIsNull(), inner.StateIsNull());
-    EXPECT_EQ(outer.Camera(), inner.Camera());
-    EXPECT_EQ(outer.Microphone(), inner.Microphone());
-  }
-
- private:
-  raw_ptr<apps::AppCapabilityAccessCache> cache_;
-  AccountId account_id_ = AccountId::FromUserEmail("test@gmail.com");
-  std::set<std::string> accessing_camera_apps_;
-  std::set<std::string> accessing_microphone_apps_;
-
-  int expected_num_apps_;
-  int num_apps_seen_on_capability_access_update_;
-
-  // Non-empty when this.OnCapabilityAccessUpdate should trigger more
-  // app_capability_access_.OnCapabilityAccesses calls.
-  //
-  // During OnCapabilityAccessUpdate, this vector (a stack) is popped from the
-  // back until a nullptr 'punctuation' element (a group terminator) is seen. If
-  // that group of popped elements (in LIFO order) is non-empty, that group
-  // forms the vector of CapabilityAccess's passed to
-  // app_capability_access_.OnCapabilityAccesses.
-  std::vector<apps::mojom::CapabilityAccessPtr> super_recursive_accesses_;
-};
-
-TEST_F(AppCapabilityAccessCacheMojomTest, ForEachApp) {
-  std::vector<apps::mojom::CapabilityAccessPtr> deltas;
-  apps::AppCapabilityAccessCache cache;
-  cache.SetAccountId(account_id());
-
-  CallForEachApp(cache);
-
-  EXPECT_EQ(0u, updated_ids_.size());
-
-  deltas.clear();
-  deltas.push_back(MakeCapabilityAccess("a", apps::mojom::OptionalBool::kTrue,
-                                        apps::mojom::OptionalBool::kTrue));
-  deltas.push_back(MakeCapabilityAccess("b", apps::mojom::OptionalBool::kFalse,
-                                        apps::mojom::OptionalBool::kFalse));
-  deltas.push_back(MakeCapabilityAccess("c", apps::mojom::OptionalBool::kTrue,
-                                        apps::mojom::OptionalBool::kFalse));
-  cache.OnCapabilityAccesses(std::move(deltas));
-
-  updated_ids_.clear();
-  CallForEachApp(cache);
-
-  EXPECT_EQ(3u, updated_ids_.size());
-  EXPECT_EQ(2u, accessing_camera_apps_.size());
-  EXPECT_EQ(cache.GetAppsAccessingCamera(), accessing_camera_apps_);
-  EXPECT_EQ(1u, accessing_microphone_apps_.size());
-  EXPECT_EQ(cache.GetAppsAccessingMicrophone(), accessing_microphone_apps_);
-
-  deltas.clear();
-  deltas.push_back(MakeCapabilityAccess("a", apps::mojom::OptionalBool::kFalse,
-                                        apps::mojom::OptionalBool::kFalse));
-  deltas.push_back(MakeCapabilityAccess("d", apps::mojom::OptionalBool::kFalse,
-                                        apps::mojom::OptionalBool::kTrue));
-  cache.OnCapabilityAccesses(std::move(deltas));
-
-  updated_ids_.clear();
-  CallForEachApp(cache);
-
-  EXPECT_EQ(4u, updated_ids_.size());
-  EXPECT_EQ(1u, accessing_camera_apps_.size());
-  EXPECT_EQ(cache.GetAppsAccessingCamera(), accessing_camera_apps_);
-  EXPECT_EQ(1u, accessing_microphone_apps_.size());
-  EXPECT_EQ(cache.GetAppsAccessingMicrophone(), accessing_microphone_apps_);
-
-  // Test that ForOneApp succeeds for "c" and fails for "e".
-
-  bool found_c = false;
-  EXPECT_TRUE(cache.ForOneApp(
-      "c", [&found_c](const apps::CapabilityAccessUpdate& update) {
-        found_c = true;
-        EXPECT_EQ("c", update.AppId());
-        EXPECT_TRUE(update.Camera().value_or(false));
-        EXPECT_FALSE(update.Microphone().value_or(true));
-      }));
-  EXPECT_TRUE(found_c);
-
-  bool found_e = false;
-  EXPECT_FALSE(cache.ForOneApp(
-      "e", [&found_e](const apps::CapabilityAccessUpdate& update) {
-        found_e = true;
-        EXPECT_EQ("e", update.AppId());
-      }));
-  EXPECT_FALSE(found_e);
-}
-
-TEST_F(AppCapabilityAccessCacheMojomTest, Observer) {
-  std::vector<apps::mojom::CapabilityAccessPtr> deltas;
-  apps::AppCapabilityAccessCache cache;
-  cache.SetAccountId(account_id());
-
-  cache.AddObserver(this);
-
-  updated_ids_.clear();
-  deltas.clear();
-  deltas.push_back(MakeCapabilityAccess("a", apps::mojom::OptionalBool::kTrue,
-                                        apps::mojom::OptionalBool::kTrue));
-  deltas.push_back(MakeCapabilityAccess("b", apps::mojom::OptionalBool::kFalse,
-                                        apps::mojom::OptionalBool::kFalse));
-  deltas.push_back(MakeCapabilityAccess("c", apps::mojom::OptionalBool::kTrue,
-                                        apps::mojom::OptionalBool::kFalse));
-  cache.OnCapabilityAccesses(std::move(deltas));
-
-  EXPECT_EQ(3u, updated_ids_.size());
-  EXPECT_EQ(2u, accessing_camera_apps_.size());
-  EXPECT_EQ(cache.GetAppsAccessingCamera(), accessing_camera_apps_);
-  EXPECT_EQ(1u, accessing_microphone_apps_.size());
-  EXPECT_EQ(cache.GetAppsAccessingMicrophone(), accessing_microphone_apps_);
-
-  updated_ids_.clear();
-  deltas.clear();
-  deltas.push_back(MakeCapabilityAccess("b", apps::mojom::OptionalBool::kTrue,
-                                        apps::mojom::OptionalBool::kTrue));
-  deltas.push_back(MakeCapabilityAccess("c", apps::mojom::OptionalBool::kFalse,
-                                        apps::mojom::OptionalBool::kTrue));
-  cache.OnCapabilityAccesses(std::move(deltas));
-
-  EXPECT_EQ(2u, updated_ids_.size());
-  EXPECT_EQ(2u, accessing_camera_apps_.size());
-  EXPECT_EQ(cache.GetAppsAccessingCamera(), accessing_camera_apps_);
-  EXPECT_EQ(3u, accessing_microphone_apps_.size());
-  EXPECT_EQ(cache.GetAppsAccessingMicrophone(), accessing_microphone_apps_);
-
-  cache.RemoveObserver(this);
-
-  updated_ids_.clear();
-  accessing_camera_apps_.clear();
-  accessing_microphone_apps_.clear();
-  deltas.clear();
-  deltas.push_back(MakeCapabilityAccess("d", apps::mojom::OptionalBool::kFalse,
-                                        apps::mojom::OptionalBool::kTrue));
-  cache.OnCapabilityAccesses(std::move(deltas));
-
-  EXPECT_EQ(0u, accessing_camera_apps_.size());
-  EXPECT_EQ(0u, accessing_microphone_apps_.size());
-}
-
-TEST_F(AppCapabilityAccessCacheMojomTest, Recursive) {
-  std::vector<apps::mojom::CapabilityAccessPtr> deltas;
-  apps::AppCapabilityAccessCache cache;
-  cache.SetAccountId(account_id());
-  CapabilityAccessRecursiveObserverMojom observer(&cache);
-
-  observer.PrepareForOnCapabilityAccesses(2);
-  deltas.clear();
-  deltas.push_back(MakeCapabilityAccess("a", apps::mojom::OptionalBool::kTrue,
-                                        apps::mojom::OptionalBool::kFalse));
-  deltas.push_back(MakeCapabilityAccess("b", apps::mojom::OptionalBool::kFalse,
-                                        apps::mojom::OptionalBool::kTrue));
-  cache.OnCapabilityAccesses(std::move(deltas));
-  EXPECT_EQ(2, observer.NumAppsSeenOnCapabilityAccessUpdate());
-
-  observer.PrepareForOnCapabilityAccesses(3);
-  deltas.clear();
-  deltas.push_back(MakeCapabilityAccess("b", apps::mojom::OptionalBool::kTrue,
-                                        apps::mojom::OptionalBool::kFalse));
-  deltas.push_back(MakeCapabilityAccess("c", apps::mojom::OptionalBool::kTrue,
-                                        apps::mojom::OptionalBool::kTrue));
-  cache.OnCapabilityAccesses(std::move(deltas));
-  EXPECT_EQ(2, observer.NumAppsSeenOnCapabilityAccessUpdate());
-
-  observer.PrepareForOnCapabilityAccesses(3);
-  deltas.clear();
-  deltas.push_back(MakeCapabilityAccess("b", apps::mojom::OptionalBool::kTrue,
-                                        apps::mojom::OptionalBool::kFalse));
-  deltas.push_back(MakeCapabilityAccess("b", apps::mojom::OptionalBool::kTrue,
-                                        apps::mojom::OptionalBool::kFalse));
-  deltas.push_back(MakeCapabilityAccess("b", apps::mojom::OptionalBool::kTrue,
-                                        apps::mojom::OptionalBool::kTrue));
-  cache.OnCapabilityAccesses(std::move(deltas));
-  EXPECT_EQ(1, observer.NumAppsSeenOnCapabilityAccessUpdate());
-
-  EXPECT_EQ(cache.GetAppsAccessingCamera(), observer.accessing_camera_apps());
-  EXPECT_EQ(cache.GetAppsAccessingMicrophone(),
-            observer.accessing_microphone_apps());
-}
-
-TEST_F(AppCapabilityAccessCacheMojomTest, SuperRecursive) {
-  std::vector<apps::mojom::CapabilityAccessPtr> deltas;
-  apps::AppCapabilityAccessCache cache;
-  cache.SetAccountId(account_id());
-  CapabilityAccessRecursiveObserverMojom observer(&cache);
-
-  // Set up a series of OnCapabilityAccesses to be called during
-  // observer.OnCapabilityAccessUpdate:
-  //  - the 1st update is {"b, "c"}.
-  //  - the 2nd update is {}.
-  //  - the 3rd update is {"b", "c", "b"}.
-  //  - the 4th update is {"a"}.
-  //  - the 5th update is {}.
-  //  - the 6th update is {"b"}.
-  //
-  // The vector is processed in LIFO order with nullptr punctuation to
-  // terminate each group. See the comment on the
-  // RecursiveObserver::super_recursive_accesses_ field.
-  std::vector<apps::mojom::CapabilityAccessPtr> super_recursive_accesses;
-  super_recursive_accesses.push_back(nullptr);
-  super_recursive_accesses.push_back(MakeCapabilityAccess(
-      "b", apps::mojom::OptionalBool::kTrue, apps::mojom::OptionalBool::kTrue));
-  super_recursive_accesses.push_back(nullptr);
-  super_recursive_accesses.push_back(nullptr);
-  super_recursive_accesses.push_back(MakeCapabilityAccess(
-      "a", apps::mojom::OptionalBool::kTrue, apps::mojom::OptionalBool::kTrue));
-  super_recursive_accesses.push_back(nullptr);
-  super_recursive_accesses.push_back(
-      MakeCapabilityAccess("b", apps::mojom::OptionalBool::kTrue,
-                           apps::mojom::OptionalBool::kFalse));
-  super_recursive_accesses.push_back(
-      MakeCapabilityAccess("a", apps::mojom::OptionalBool::kFalse,
-                           apps::mojom::OptionalBool::kFalse));
-  super_recursive_accesses.push_back(
-      MakeCapabilityAccess("b", apps::mojom::OptionalBool::kFalse,
-                           apps::mojom::OptionalBool::kFalse));
-  super_recursive_accesses.push_back(nullptr);
-  super_recursive_accesses.push_back(nullptr);
-  super_recursive_accesses.push_back(MakeCapabilityAccess(
-      "c", apps::mojom::OptionalBool::kTrue, apps::mojom::OptionalBool::kTrue));
-  super_recursive_accesses.push_back(MakeCapabilityAccess(
-      "b", apps::mojom::OptionalBool::kTrue, apps::mojom::OptionalBool::kTrue));
-
-  observer.PrepareForOnCapabilityAccesses(3, &super_recursive_accesses);
-  deltas.clear();
-  deltas.push_back(MakeCapabilityAccess("a", apps::mojom::OptionalBool::kTrue,
-                                        apps::mojom::OptionalBool::kFalse));
-  deltas.push_back(MakeCapabilityAccess("b", apps::mojom::OptionalBool::kFalse,
-                                        apps::mojom::OptionalBool::kTrue));
-  deltas.push_back(MakeCapabilityAccess("c", apps::mojom::OptionalBool::kFalse,
-                                        apps::mojom::OptionalBool::kFalse));
-  cache.OnCapabilityAccesses(std::move(deltas));
-
-  // After all of that, check that for each app_id, the last delta won.
-  EXPECT_EQ(3u, observer.accessing_camera_apps().size());
-  EXPECT_NE(observer.accessing_camera_apps().end(),
-            observer.accessing_camera_apps().find("a"));
-  EXPECT_NE(observer.accessing_camera_apps().end(),
-            observer.accessing_camera_apps().find("b"));
-  EXPECT_NE(observer.accessing_camera_apps().end(),
-            observer.accessing_camera_apps().find("c"));
-
-  EXPECT_EQ(3u, observer.accessing_microphone_apps().size());
-  EXPECT_NE(observer.accessing_microphone_apps().end(),
-            observer.accessing_microphone_apps().find("a"));
-  EXPECT_NE(observer.accessing_microphone_apps().end(),
-            observer.accessing_microphone_apps().find("b"));
-  EXPECT_NE(observer.accessing_microphone_apps().end(),
-            observer.accessing_microphone_apps().find("c"));
-
-  EXPECT_EQ(cache.GetAppsAccessingCamera(), observer.accessing_camera_apps());
-  EXPECT_EQ(cache.GetAppsAccessingMicrophone(),
-            observer.accessing_microphone_apps());
-}
diff --git a/components/services/app_service/public/cpp/app_capability_access_cache_unittest.cc b/components/services/app_service/public/cpp/app_capability_access_cache_unittest.cc
index e5f40e8e..fc52158 100644
--- a/components/services/app_service/public/cpp/app_capability_access_cache_unittest.cc
+++ b/components/services/app_service/public/cpp/app_capability_access_cache_unittest.cc
@@ -6,10 +6,8 @@
 #include <utility>
 
 #include "base/memory/raw_ptr.h"
-#include "base/test/scoped_feature_list.h"
 #include "components/services/app_service/public/cpp/app_capability_access_cache.h"
 #include "components/services/app_service/public/cpp/capability_access.h"
-#include "components/services/app_service/public/cpp/features.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -17,11 +15,7 @@
     : public testing::Test,
       public apps::AppCapabilityAccessCache::Observer {
  protected:
-  AppCapabilityAccessCacheTest() {
-    scoped_feature_list_.Reset();
-    scoped_feature_list_.InitAndEnableFeature(
-        apps::kAppServiceCapabilityAccessWithoutMojom);
-  }
+  AppCapabilityAccessCacheTest() = default;
 
   static apps::CapabilityAccessPtr MakeCapabilityAccess(
       const char* app_id,
@@ -68,7 +62,6 @@
   const AccountId& account_id() const { return account_id_; }
 
  protected:
-  base::test::ScopedFeatureList scoped_feature_list_;
   std::set<std::string> updated_ids_;
   std::set<std::string> accessing_camera_apps_;
   std::set<std::string> accessing_microphone_apps_;
diff --git a/components/services/app_service/public/cpp/app_capability_access_cache_wrapper_unittest.cc b/components/services/app_service/public/cpp/app_capability_access_cache_wrapper_unittest.cc
index bbec53b..18efa45 100644
--- a/components/services/app_service/public/cpp/app_capability_access_cache_wrapper_unittest.cc
+++ b/components/services/app_service/public/cpp/app_capability_access_cache_wrapper_unittest.cc
@@ -5,12 +5,10 @@
 #include <utility>
 #include <vector>
 
-#include "base/test/scoped_feature_list.h"
 #include "components/account_id/account_id.h"
 #include "components/services/app_service/public/cpp/app_capability_access_cache.h"
 #include "components/services/app_service/public/cpp/app_capability_access_cache_wrapper.h"
 #include "components/services/app_service/public/cpp/capability_access.h"
-#include "components/services/app_service/public/cpp/features.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -20,12 +18,6 @@
     : public testing::Test,
       public AppCapabilityAccessCache::Observer {
  protected:
-  AppCapabilityAccessCacheWrapperTest() {
-    scoped_feature_list_.Reset();
-    scoped_feature_list_.InitAndEnableFeature(
-        apps::kAppServiceCapabilityAccessWithoutMojom);
-  }
-
   // AppCapabilityAccessCache::Observer:
   void OnCapabilityAccessUpdate(const CapabilityAccessUpdate& update) override {
     last_account_id_ = update.AccountId();
@@ -54,7 +46,6 @@
   AccountId& account_id_2() { return account_id_2_; }
 
  private:
-  base::test::ScopedFeatureList scoped_feature_list_;
   AccountId last_account_id_;
   AccountId account_id_1_ = AccountId::FromUserEmail("fake_email@gmail.com");
   AccountId account_id_2_ = AccountId::FromUserEmail("fake_email2@gmail.com");
diff --git a/components/services/app_service/public/cpp/capability_access.cc b/components/services/app_service/public/cpp/capability_access.cc
index 7faad12..32a936c 100644
--- a/components/services/app_service/public/cpp/capability_access.cc
+++ b/components/services/app_service/public/cpp/capability_access.cc
@@ -21,57 +21,4 @@
   return capability_access;
 }
 
-CapabilityAccessPtr ConvertMojomCapabilityAccessToCapabilityAccess(
-    const apps::mojom::CapabilityAccessPtr& mojom_capability_access) {
-  if (!mojom_capability_access)
-    return nullptr;
-
-  auto capability_access =
-      std::make_unique<CapabilityAccess>(mojom_capability_access->app_id);
-  capability_access->camera = GetOptionalBool(mojom_capability_access->camera);
-  capability_access->microphone =
-      GetOptionalBool(mojom_capability_access->microphone);
-  return capability_access;
-}
-
-apps::mojom::CapabilityAccessPtr ConvertCapabilityAccessToMojomCapabilityAccess(
-    const CapabilityAccessPtr& capability_access) {
-  auto mojom_capability_access = apps::mojom::CapabilityAccess::New();
-  if (!capability_access) {
-    return mojom_capability_access;
-  }
-
-  mojom_capability_access->app_id = capability_access->app_id;
-  mojom_capability_access->camera =
-      GetMojomOptionalBool(capability_access->camera);
-  mojom_capability_access->microphone =
-      GetMojomOptionalBool(capability_access->microphone);
-  return mojom_capability_access;
-}
-
-std::vector<CapabilityAccessPtr>
-ConvertMojomCapabilityAccessesToCapabilityAccesses(
-    const std::vector<apps::mojom::CapabilityAccessPtr>&
-        mojom_capability_accesses) {
-  std::vector<CapabilityAccessPtr> ret;
-  ret.reserve(mojom_capability_accesses.size());
-  for (const auto& mojom_capability_access : mojom_capability_accesses) {
-    ret.push_back(ConvertMojomCapabilityAccessToCapabilityAccess(
-        mojom_capability_access));
-  }
-  return ret;
-}
-
-std::vector<apps::mojom::CapabilityAccessPtr>
-ConvertCapabilityAccessesToMojomCapabilityAccesses(
-    const std::vector<CapabilityAccessPtr>& capability_accesses) {
-  std::vector<apps::mojom::CapabilityAccessPtr> ret;
-  ret.reserve(capability_accesses.size());
-  for (const auto& capability_access : capability_accesses) {
-    ret.push_back(
-        ConvertCapabilityAccessToMojomCapabilityAccess(capability_access));
-  }
-  return ret;
-}
-
 }  // namespace apps
diff --git a/components/services/app_service/public/cpp/capability_access.h b/components/services/app_service/public/cpp/capability_access.h
index 85fd423..991a993 100644
--- a/components/services/app_service/public/cpp/capability_access.h
+++ b/components/services/app_service/public/cpp/capability_access.h
@@ -9,7 +9,6 @@
 #include <utility>
 
 #include "base/component_export.h"
-#include "components/services/app_service/public/mojom/types.mojom.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace apps {
@@ -40,27 +39,6 @@
 
 using CapabilityAccessPtr = std::unique_ptr<CapabilityAccess>;
 
-// TODO(crbug.com/1253250): Remove these functions after migrating to non-mojo
-// AppService.
-COMPONENT_EXPORT(APP_TYPES)
-CapabilityAccessPtr ConvertMojomCapabilityAccessToCapabilityAccess(
-    const apps::mojom::CapabilityAccessPtr& mojom_capability_access);
-
-COMPONENT_EXPORT(APP_TYPES)
-apps::mojom::CapabilityAccessPtr ConvertCapabilityAccessToMojomCapabilityAccess(
-    const CapabilityAccessPtr& capability_access);
-
-COMPONENT_EXPORT(APP_TYPES)
-std::vector<CapabilityAccessPtr>
-ConvertMojomCapabilityAccessesToCapabilityAccesses(
-    const std::vector<apps::mojom::CapabilityAccessPtr>&
-        mojom_capability_accesses);
-
-COMPONENT_EXPORT(APP_TYPES)
-std::vector<apps::mojom::CapabilityAccessPtr>
-ConvertCapabilityAccessesToMojomCapabilityAccesses(
-    const std::vector<CapabilityAccessPtr>& capability_accesses);
-
 }  // namespace apps
 
 #endif  // COMPONENTS_SERVICES_APP_SERVICE_PUBLIC_CPP_CAPABILITY_ACCESS_H_
diff --git a/components/services/app_service/public/cpp/capability_access_update.cc b/components/services/app_service/public/cpp/capability_access_update.cc
index dc1cbc82..a027d5d1 100644
--- a/components/services/app_service/public/cpp/capability_access_update.cc
+++ b/components/services/app_service/public/cpp/capability_access_update.cc
@@ -4,37 +4,13 @@
 
 #include "components/services/app_service/public/cpp/capability_access_update.h"
 
+#include "base/check.h"
 #include "base/logging.h"
 #include "components/services/app_service/public/cpp/macros.h"
 
 namespace apps {
 
 // static
-void CapabilityAccessUpdate::Merge(apps::mojom::CapabilityAccess* state,
-                                   const apps::mojom::CapabilityAccess* delta) {
-  DCHECK(state);
-  if (!delta) {
-    return;
-  }
-
-  if (delta->app_id != state->app_id) {
-    LOG(ERROR) << "inconsistent (app_id): (" << delta->app_id << ") vs ("
-               << state->app_id << ") ";
-    DCHECK(false);
-    return;
-  }
-
-  if (delta->camera != apps::mojom::OptionalBool::kUnknown) {
-    state->camera = delta->camera;
-  }
-  if (delta->microphone != apps::mojom::OptionalBool::kUnknown) {
-    state->microphone = delta->microphone;
-  }
-  // When adding new fields to the CapabilityAccess Mojo type, this function
-  // should also be updated.
-}
-
-// static
 void CapabilityAccessUpdate::Merge(CapabilityAccess* state,
                                    const CapabilityAccess* delta) {
   DCHECK(state);
@@ -56,17 +32,6 @@
   // should also be updated.
 }
 
-CapabilityAccessUpdate::CapabilityAccessUpdate(
-    const apps::mojom::CapabilityAccess* state,
-    const apps::mojom::CapabilityAccess* delta,
-    const ::AccountId& account_id)
-    : mojom_state_(state), mojom_delta_(delta), account_id_(account_id) {
-  DCHECK(mojom_state_ || mojom_delta_);
-  if (mojom_state_ && mojom_delta_) {
-    DCHECK(mojom_state_->app_id == delta->app_id);
-  }
-}
-
 CapabilityAccessUpdate::CapabilityAccessUpdate(const CapabilityAccess* state,
                                                const CapabilityAccess* delta,
                                                const ::AccountId& account_id)
@@ -78,64 +43,30 @@
 }
 
 bool CapabilityAccessUpdate::StateIsNull() const {
-  if (ShouldUseNonMojomStruct()) {
-    return state_ == nullptr;
-  }
-
-  return mojom_state_ == nullptr;
+  return state_ == nullptr;
 }
 
 const std::string& CapabilityAccessUpdate::AppId() const {
-  if (ShouldUseNonMojomStruct()) {
-    return delta_ ? delta_->app_id : state_->app_id;
-  }
-
-  return mojom_delta_ ? mojom_delta_->app_id : mojom_state_->app_id;
+  return delta_ ? delta_->app_id : state_->app_id;
 }
 
 absl::optional<bool> CapabilityAccessUpdate::Camera() const {
-  if (ShouldUseNonMojomStruct()) {
-    GET_VALUE_WITH_FALLBACK(camera, absl::nullopt)
-  }
-
-  CONVERT_MOJOM_OPTIONALBOOL_TO_OPTIONAL_VALUE(camera);
+  GET_VALUE_WITH_FALLBACK(camera, absl::nullopt)
 }
 
 bool CapabilityAccessUpdate::CameraChanged() const {
-  if (ShouldUseNonMojomStruct()) {
-    RETURN_OPTIONAL_VALUE_CHANGED(camera)
-  }
-
-  return mojom_delta_ &&
-         (mojom_delta_->camera != apps::mojom::OptionalBool::kUnknown) &&
-         (!mojom_state_ || (mojom_delta_->camera != mojom_state_->camera));
-}
+    RETURN_OPTIONAL_VALUE_CHANGED(camera)}
 
 absl::optional<bool> CapabilityAccessUpdate::Microphone() const {
-  if (ShouldUseNonMojomStruct()) {
-    GET_VALUE_WITH_FALLBACK(microphone, absl::nullopt)
-  }
-
-  CONVERT_MOJOM_OPTIONALBOOL_TO_OPTIONAL_VALUE(microphone);
+  GET_VALUE_WITH_FALLBACK(microphone, absl::nullopt)
 }
 
 bool CapabilityAccessUpdate::MicrophoneChanged() const {
-  if (ShouldUseNonMojomStruct()) {
-    RETURN_OPTIONAL_VALUE_CHANGED(microphone)
-  }
-
-  return mojom_delta_ &&
-         (mojom_delta_->microphone != apps::mojom::OptionalBool::kUnknown) &&
-         (!mojom_state_ ||
-          (mojom_delta_->microphone != mojom_state_->microphone));
+  RETURN_OPTIONAL_VALUE_CHANGED(microphone)
 }
 
 const ::AccountId& CapabilityAccessUpdate::AccountId() const {
   return *account_id_;
 }
 
-bool CapabilityAccessUpdate::ShouldUseNonMojomStruct() const {
-  return state_ || delta_;
-}
-
 }  // namespace apps
diff --git a/components/services/app_service/public/cpp/capability_access_update.h b/components/services/app_service/public/cpp/capability_access_update.h
index 6de0c6e1..b3830412 100644
--- a/components/services/app_service/public/cpp/capability_access_update.h
+++ b/components/services/app_service/public/cpp/capability_access_update.h
@@ -12,12 +12,11 @@
 #include "base/memory/raw_ref.h"
 #include "components/account_id/account_id.h"
 #include "components/services/app_service/public/cpp/capability_access.h"
-#include "components/services/app_service/public/mojom/types.mojom.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace apps {
 
-// Wraps two apps::mojom::CapabilityAccessPtr's, a prior state and a delta on
+// Wraps two apps::CapabilityAccessPtr's, a prior state and a delta on
 // top of that state. The state is conceptually the "sum" of all of the previous
 // deltas, with "addition" or "merging" simply being that the most recent
 // version of each field "wins".
@@ -38,22 +37,13 @@
 // CapabilityAccessUpdate.
 //
 // See components/services/app_service/README.md for more details.
-//
-// TODO(crbug.com/1253250): Remove all mojom related code.
-// 1. Modify comments.
-// 2. Replace mojom related functions with non-mojom functions.
 class COMPONENT_EXPORT(APP_UPDATE) CapabilityAccessUpdate {
  public:
   // Modifies |state| by copying over all of |delta|'s known fields: those
   // fields whose values aren't "unknown". The |state| may not be nullptr.
-  static void Merge(apps::mojom::CapabilityAccess* state,
-                    const apps::mojom::CapabilityAccess* delta);
   static void Merge(CapabilityAccess* state, const CapabilityAccess* delta);
 
   // At most one of |state| or |delta| may be nullptr.
-  CapabilityAccessUpdate(const apps::mojom::CapabilityAccess* state,
-                         const apps::mojom::CapabilityAccess* delta,
-                         const AccountId& account_id);
   CapabilityAccessUpdate(const CapabilityAccess* state,
                          const CapabilityAccess* delta,
                          const AccountId& account_id);
@@ -76,12 +66,6 @@
   const ::AccountId& AccountId() const;
 
  private:
-  // TODO(crbug.com/1253250): Remove when the non mojom struct is used.
-  bool ShouldUseNonMojomStruct() const;
-
-  raw_ptr<const apps::mojom::CapabilityAccess> mojom_state_ = nullptr;
-  raw_ptr<const apps::mojom::CapabilityAccess> mojom_delta_ = nullptr;
-
   raw_ptr<const CapabilityAccess> state_ = nullptr;
   raw_ptr<const CapabilityAccess, DanglingUntriaged> delta_ = nullptr;
 
diff --git a/components/services/app_service/public/cpp/capability_access_update_mojom_unittest.cc b/components/services/app_service/public/cpp/capability_access_update_mojom_unittest.cc
deleted file mode 100644
index fe1baaf..0000000
--- a/components/services/app_service/public/cpp/capability_access_update_mojom_unittest.cc
+++ /dev/null
@@ -1,114 +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.
-
-#include "components/services/app_service/public/cpp/capability_access_update.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/abseil-cpp/absl/types/optional.h"
-
-namespace {
-const char app_id[] = "abcdefgh";
-}  // namespace
-
-class CapabilityAccessUpdateMojomTest : public testing::Test {
- protected:
-  absl::optional<bool> expect_camera_;
-  bool expect_camera_changed_;
-
-  absl::optional<bool> expect_microphone_;
-  bool expect_microphone_changed_;
-
-  AccountId account_id_ = AccountId::FromUserEmail("test@gmail.com");
-
-  void ExpectNoChange() {
-    expect_camera_changed_ = false;
-    expect_microphone_changed_ = false;
-  }
-
-  void CheckExpects(const apps::CapabilityAccessUpdate& u) {
-    EXPECT_EQ(expect_camera_, u.Camera());
-    EXPECT_EQ(expect_camera_changed_, u.CameraChanged());
-
-    EXPECT_EQ(expect_microphone_, u.Microphone());
-    EXPECT_EQ(expect_microphone_changed_, u.MicrophoneChanged());
-
-    EXPECT_EQ(account_id_, u.AccountId());
-  }
-
-  void TestCapabilityAccessUpdate(apps::mojom::CapabilityAccess* state,
-                                  apps::mojom::CapabilityAccess* delta) {
-    apps::CapabilityAccessUpdate u(state, delta, account_id_);
-
-    EXPECT_EQ(app_id, u.AppId());
-    EXPECT_EQ(state == nullptr, u.StateIsNull());
-
-    ExpectNoChange();
-    CheckExpects(u);
-
-    // IsAccessingCamera tests.
-    if (state) {
-      state->camera = apps::mojom::OptionalBool::kFalse;
-      expect_camera_ = false;
-      expect_camera_changed_ = false;
-      CheckExpects(u);
-    }
-
-    if (delta) {
-      delta->camera = apps::mojom::OptionalBool::kTrue;
-      expect_camera_ = true;
-      expect_camera_changed_ = true;
-      CheckExpects(u);
-    }
-
-    if (state) {
-      apps::CapabilityAccessUpdate::Merge(state, delta);
-      ExpectNoChange();
-      CheckExpects(u);
-    }
-
-    // IsAccessingMicrophone tests.
-    if (state) {
-      state->microphone = apps::mojom::OptionalBool::kFalse;
-      expect_microphone_ = false;
-      expect_microphone_changed_ = false;
-      CheckExpects(u);
-    }
-
-    if (delta) {
-      delta->microphone = apps::mojom::OptionalBool::kTrue;
-      expect_microphone_ = true;
-      expect_microphone_changed_ = true;
-      CheckExpects(u);
-    }
-
-    if (state) {
-      apps::CapabilityAccessUpdate::Merge(state, delta);
-      ExpectNoChange();
-      CheckExpects(u);
-    }
-  }
-};
-
-TEST_F(CapabilityAccessUpdateMojomTest, StateIsNonNull) {
-  apps::mojom::CapabilityAccessPtr state = apps::mojom::CapabilityAccess::New();
-  state->app_id = app_id;
-
-  TestCapabilityAccessUpdate(state.get(), nullptr);
-}
-
-TEST_F(CapabilityAccessUpdateMojomTest, DeltaIsNonNull) {
-  apps::mojom::CapabilityAccessPtr delta = apps::mojom::CapabilityAccess::New();
-  delta->app_id = app_id;
-
-  TestCapabilityAccessUpdate(nullptr, delta.get());
-}
-
-TEST_F(CapabilityAccessUpdateMojomTest, BothAreNonNull) {
-  apps::mojom::CapabilityAccessPtr state = apps::mojom::CapabilityAccess::New();
-  state->app_id = app_id;
-
-  apps::mojom::CapabilityAccessPtr delta = apps::mojom::CapabilityAccess::New();
-  delta->app_id = app_id;
-
-  TestCapabilityAccessUpdate(state.get(), delta.get());
-}
diff --git a/components/services/app_service/public/cpp/capability_access_update_unittest.cc b/components/services/app_service/public/cpp/capability_access_update_unittest.cc
index 4aae5b72..b6f0799 100644
--- a/components/services/app_service/public/cpp/capability_access_update_unittest.cc
+++ b/components/services/app_service/public/cpp/capability_access_update_unittest.cc
@@ -4,7 +4,6 @@
 
 #include "components/services/app_service/public/cpp/capability_access_update.h"
 
-#include "components/services/app_service/public/mojom/types.mojom.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
@@ -110,35 +109,3 @@
       std::make_unique<apps::CapabilityAccess>(app_id);
   TestCapabilityAccessUpdate(state.get(), delta.get());
 }
-
-TEST_F(CapabilityAccessUpdateTest, ConvertEmptyCapabilityAccesses) {
-  std::vector<apps::mojom::CapabilityAccessPtr> src;
-  EXPECT_EQ(src,
-            apps::ConvertCapabilityAccessesToMojomCapabilityAccesses(
-                apps::ConvertMojomCapabilityAccessesToCapabilityAccesses(src)));
-}
-
-TEST_F(CapabilityAccessUpdateTest, Convert) {
-  apps::mojom::CapabilityAccessPtr access1 =
-      apps::mojom::CapabilityAccess::New();
-  access1->app_id = "a";
-
-  apps::mojom::CapabilityAccessPtr access2 =
-      apps::mojom::CapabilityAccess::New();
-  access2->app_id = "b";
-  access2->camera = apps::mojom::OptionalBool::kTrue;
-
-  apps::mojom::CapabilityAccessPtr access3 =
-      apps::mojom::CapabilityAccess::New();
-  access3->app_id = "c";
-  access3->camera = apps::mojom::OptionalBool::kFalse;
-  access3->microphone = apps::mojom::OptionalBool::kTrue;
-
-  std::vector<apps::mojom::CapabilityAccessPtr> src;
-  src.push_back(std::move(access1));
-  src.push_back(std::move(access2));
-  src.push_back(std::move(access3));
-  EXPECT_EQ(src,
-            apps::ConvertCapabilityAccessesToMojomCapabilityAccesses(
-                apps::ConvertMojomCapabilityAccessesToCapabilityAccesses(src)));
-}
diff --git a/components/services/app_service/public/cpp/features.cc b/components/services/app_service/public/cpp/features.cc
index e382666..23168e1 100644
--- a/components/services/app_service/public/cpp/features.cc
+++ b/components/services/app_service/public/cpp/features.cc
@@ -10,10 +10,6 @@
              "AppServiceWithoutMojom",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
-BASE_FEATURE(kAppServiceCapabilityAccessWithoutMojom,
-             "AppServiceCapabilityAccessWithoutMojom",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 BASE_FEATURE(kStopMojomAppService,
              "StopMojomAppService",
              base::FEATURE_ENABLED_BY_DEFAULT);
diff --git a/components/services/app_service/public/cpp/features.h b/components/services/app_service/public/cpp/features.h
index 876bc85..e86ddeb 100644
--- a/components/services/app_service/public/cpp/features.h
+++ b/components/services/app_service/public/cpp/features.h
@@ -11,8 +11,6 @@
 namespace apps {
 
 COMPONENT_EXPORT(APP_TYPES) BASE_DECLARE_FEATURE(kAppServiceWithoutMojom);
-COMPONENT_EXPORT(APP_TYPES)
-BASE_DECLARE_FEATURE(kAppServiceCapabilityAccessWithoutMojom);
 COMPONENT_EXPORT(APP_TYPES) BASE_DECLARE_FEATURE(kStopMojomAppService);
 COMPONENT_EXPORT(APP_TYPES) BASE_DECLARE_FEATURE(kUnifiedAppServiceIconLoading);
 COMPONENT_EXPORT(APP_TYPES) BASE_DECLARE_FEATURE(kAppServiceStorage);
diff --git a/components/services/app_service/public/cpp/publisher_base.cc b/components/services/app_service/public/cpp/publisher_base.cc
index 85741d5..b6a587e 100644
--- a/components/services/app_service/public/cpp/publisher_base.cc
+++ b/components/services/app_service/public/cpp/publisher_base.cc
@@ -71,37 +71,6 @@
   }
 }
 
-void PublisherBase::ModifyCapabilityAccess(
-    const mojo::RemoteSet<apps::mojom::Subscriber>& subscribers,
-    const std::string& app_id,
-    absl::optional<bool> accessing_camera,
-    absl::optional<bool> accessing_microphone) {
-  if (!accessing_camera.has_value() && !accessing_microphone.has_value()) {
-    return;
-  }
-
-  for (auto& subscriber : subscribers) {
-    std::vector<apps::mojom::CapabilityAccessPtr> capability_accesses;
-    auto capability_access = apps::mojom::CapabilityAccess::New();
-    capability_access->app_id = app_id;
-
-    if (accessing_camera.has_value()) {
-      capability_access->camera = accessing_camera.value()
-                                      ? apps::mojom::OptionalBool::kTrue
-                                      : apps::mojom::OptionalBool::kFalse;
-    }
-
-    if (accessing_microphone.has_value()) {
-      capability_access->microphone = accessing_microphone.value()
-                                          ? apps::mojom::OptionalBool::kTrue
-                                          : apps::mojom::OptionalBool::kFalse;
-    }
-
-    capability_accesses.push_back(std::move(capability_access));
-    subscriber->OnCapabilityAccesses(std::move(capability_accesses));
-  }
-}
-
 void PublisherBase::PauseApp(const std::string& app_id) {
   NOTIMPLEMENTED();
 }
diff --git a/components/services/app_service/public/cpp/publisher_base.h b/components/services/app_service/public/cpp/publisher_base.h
index 5f4407fb..f45d269 100644
--- a/components/services/app_service/public/cpp/publisher_base.h
+++ b/components/services/app_service/public/cpp/publisher_base.h
@@ -48,13 +48,6 @@
   void Publish(apps::mojom::AppPtr app,
                const mojo::RemoteSet<apps::mojom::Subscriber>& subscribers);
 
-  // Modifies CapabilityAccess to all subscribers in |subscribers|.
-  void ModifyCapabilityAccess(
-      const mojo::RemoteSet<apps::mojom::Subscriber>& subscribers,
-      const std::string& app_id,
-      absl::optional<bool> accessing_camera,
-      absl::optional<bool> accessing_microphone);
-
   mojo::Receiver<apps::mojom::Publisher>& receiver() { return receiver_; }
 
  private:
diff --git a/components/services/app_service/public/mojom/app_service.mojom b/components/services/app_service/public/mojom/app_service.mojom
index acb2e9e..ede0701 100644
--- a/components/services/app_service/public/mojom/app_service.mojom
+++ b/components/services/app_service/public/mojom/app_service.mojom
@@ -120,10 +120,6 @@
   // has finished initiating apps.
   OnApps(array<App> deltas, AppType app_type, bool should_notify_initialized);
 
-  // Receives a stream of accesses from publishers, and save to
-  // AppCapabilityAccess.
-  OnCapabilityAccesses(array<CapabilityAccess> deltas);
-
   // Binds this to the given receiver (message pipe endpoint), being to Mojo
   // interfaces what POSIX's dup is to file descriptors.
   //
diff --git a/components/services/screen_ai/public/cpp/screen_ai_install_state.cc b/components/services/screen_ai/public/cpp/screen_ai_install_state.cc
index c20fab8..56c2583 100644
--- a/components/services/screen_ai/public/cpp/screen_ai_install_state.cc
+++ b/components/services/screen_ai/public/cpp/screen_ai_install_state.cc
@@ -77,10 +77,6 @@
 
 void ScreenAIInstallState::SetComponentFolder(
     const base::FilePath& component_folder) {
-  // We do not expect the component to be changed after the first loading.
-  DCHECK_NE(state_, State::kReady);
-  DCHECK(component_binary_path_.empty());
-
   component_binary_path_ =
       component_folder.Append(GetComponentBinaryFileName());
 
diff --git a/components/sync/base/features.cc b/components/sync/base/features.cc
index 30a761a..791f457 100644
--- a/components/sync/base/features.cc
+++ b/components/sync/base/features.cc
@@ -34,7 +34,7 @@
              base::FEATURE_DISABLED_BY_DEFAULT);
 BASE_FEATURE(kSyncAndroidPromosWithAlternativeTitle,
              "SyncAndroidPromosWithAlternativeTitle",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 BASE_FEATURE(kSyncAndroidPromosWithIllustration,
              "SyncAndroidPromosWithIllustration",
              base::FEATURE_DISABLED_BY_DEFAULT);
@@ -43,7 +43,7 @@
              base::FEATURE_DISABLED_BY_DEFAULT);
 BASE_FEATURE(kSyncAndroidPromosWithTitle,
              "SyncAndroidPromosWithTitle",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 #endif  // BUILDFLAG(IS_ANDROID)
 
 BASE_FEATURE(kSyncAutofillWalletUsageData,
diff --git a/components/test/data/ad_tagging/ad_script.js b/components/test/data/ad_tagging/ad_script.js
index 5a3f78bb..a57527a 100644
--- a/components/test/data/ad_tagging/ad_script.js
+++ b/components/test/data/ad_tagging/ad_script.js
@@ -35,8 +35,12 @@
   });
 }
 
-function windowOpenFromAdScript() {
-  window.open();
+function windowOpenFromAdScript(url) {
+  window.open(url);
+}
+
+function navigateIframeFromAdScript(name, url) {
+  document.getElementsByName(name)[0].src = url;
 }
 
 async function createDocWrittenAdFrame(name, base_url) {
diff --git a/components/viz/common/frame_sinks/delay_based_time_source.cc b/components/viz/common/frame_sinks/delay_based_time_source.cc
index 2a743c0..515ce868 100644
--- a/components/viz/common/frame_sinks/delay_based_time_source.cc
+++ b/components/viz/common/frame_sinks/delay_based_time_source.cc
@@ -158,7 +158,7 @@
     DCHECK_GT(next_tick_time_, now);
   }
   timer_.Start(FROM_HERE, next_tick_time_, tick_closure_,
-               base::ExactDeadline(true));
+               base::subtle::DelayPolicy::kPrecise);
 }
 
 std::string DelayBasedTimeSource::TypeString() const {
diff --git a/components/viz/service/display/direct_renderer.h b/components/viz/service/display/direct_renderer.h
index 4ef756b..a2c38f0 100644
--- a/components/viz/service/display/direct_renderer.h
+++ b/components/viz/service/display/direct_renderer.h
@@ -105,6 +105,7 @@
     SwapFrameData& operator=(const SwapFrameData&) = delete;
 
     std::vector<ui::LatencyInfo> latency_info;
+    int64_t seq = -1;
     bool top_controls_visible_height_changed = false;
 #if BUILDFLAG(IS_MAC)
     gfx::CALayerResult ca_layer_error_code = gfx::kCALayerSuccess;
diff --git a/components/viz/service/display/display.cc b/components/viz/service/display/display.cc
index 1e105eb..fb507be 100644
--- a/components/viz/service/display/display.cc
+++ b/components/viz/service/display/display.cc
@@ -888,6 +888,8 @@
     IssueDisplayRenderingStatsEvent();
     DirectRenderer::SwapFrameData swap_frame_data;
     swap_frame_data.latency_info = std::move(frame.latency_info);
+    swap_frame_data.seq =
+        current_surface_id_.local_surface_id().child_sequence_number();
     swap_frame_data.choreographer_vsync_id = params.choreographer_vsync_id;
     if (frame.top_controls_visible_height.has_value()) {
       swap_frame_data.top_controls_visible_height_changed =
diff --git a/components/viz/service/display/display_scheduler.cc b/components/viz/service/display/display_scheduler.cc
index 11495ed..342b369 100644
--- a/components/viz/service/display/display_scheduler.cc
+++ b/components/viz/service/display/display_scheduler.cc
@@ -11,6 +11,7 @@
 #include "base/cxx17_backports.h"
 #include "base/memory/raw_ptr.h"
 #include "base/metrics/histogram_macros.h"
+#include "base/task/delay_policy.h"
 #include "base/trace_event/trace_event.h"
 #include "components/viz/common/features.h"
 #include "components/viz/service/performance_hint/hint_session.h"
@@ -469,9 +470,11 @@
     return;
   }
 
-  begin_frame_deadline_timer_.Start(FROM_HERE, desired_deadline,
-                                    begin_frame_deadline_closure_,
-                                    base::ExactDeadline(true));
+  begin_frame_deadline_timer_.Start(
+      FROM_HERE, desired_deadline, begin_frame_deadline_closure_,
+      deadline_mode == BeginFrameDeadlineMode::kLate
+          ? base::subtle::DelayPolicy::kFlexibleNoSooner
+          : base::subtle::DelayPolicy::kPrecise);
   TRACE_EVENT2("viz", "Using new deadline", "deadline_mode", deadline_mode,
                "desired_deadline", desired_deadline);
 }
diff --git a/components/viz/service/display/external_use_client.cc b/components/viz/service/display/external_use_client.cc
index 151c9dde..17f7831 100644
--- a/components/viz/service/display/external_use_client.cc
+++ b/components/viz/service/display/external_use_client.cc
@@ -31,11 +31,12 @@
   NOTREACHED();
 }
 
-void ExternalUseClient::ImageContext::SetImage(sk_sp<SkImage> image,
-                                               GrBackendFormat backend_format) {
+void ExternalUseClient::ImageContext::SetImage(
+    sk_sp<SkImage> image,
+    std::vector<GrBackendFormat> backend_formats) {
   DCHECK(!image_);
   image_ = std::move(image);
-  backend_format_ = backend_format;
+  backend_formats_ = backend_formats;
 }
 
 }  // namespace viz
diff --git a/components/viz/service/display/external_use_client.h b/components/viz/service/display/external_use_client.h
index ceaa102e..e1d4136a 100644
--- a/components/viz/service/display/external_use_client.h
+++ b/components/viz/service/display/external_use_client.h
@@ -78,9 +78,12 @@
 
     bool has_image() { return !!image_; }
     sk_sp<SkImage> image() { return image_; }
-    void SetImage(sk_sp<SkImage> image, GrBackendFormat backend_format);
+    void SetImage(sk_sp<SkImage> image,
+                  std::vector<GrBackendFormat> backend_formats);
     void clear_image() { image_.reset(); }
-    const GrBackendFormat& backend_format() { return backend_format_; }
+    const std::vector<GrBackendFormat>& backend_formats() {
+      return backend_formats_;
+    }
     const cc::PaintOpBuffer* paint_op_buffer() const {
       return paint_op_buffer_;
     }
@@ -110,7 +113,7 @@
 
     // The promise image which is used on display thread.
     sk_sp<SkImage> image_;
-    GrBackendFormat backend_format_;
+    std::vector<GrBackendFormat> backend_formats_;
     raw_ptr<const cc::PaintOpBuffer> paint_op_buffer_ = nullptr;
     absl::optional<SkColor4f> clear_color_;
   };
diff --git a/components/viz/service/display/skia_output_surface.h b/components/viz/service/display/skia_output_surface.h
index ddd568c..7501f8d41 100644
--- a/components/viz/service/display/skia_output_surface.h
+++ b/components/viz/service/display/skia_output_surface.h
@@ -86,9 +86,11 @@
   // Skia will not read the content of the resource until the |sync_token| in
   // the |image_context| is satisfied. The SwapBuffers should take care of this
   // by scheduling a GPU task with all resource sync tokens recorded by
-  // MakePromiseSkImage for the current frame.
+  // MakePromiseSkImage for the current frame. The |yuv_color_space| is the
+  // original color space needed for yuv to rgb conversion.
   virtual void MakePromiseSkImage(
-      ExternalUseClient::ImageContext* image_context) = 0;
+      ExternalUseClient::ImageContext* image_context,
+      const gfx::ColorSpace& yuv_color_space) = 0;
 
   // Make a promise SkImage from the given |contexts| and |image_color_space|.
   // The number of contexts provided should match the number of planes indicated
diff --git a/components/viz/service/display/skia_renderer.cc b/components/viz/service/display/skia_renderer.cc
index 85d45b5b..7fb2f06 100644
--- a/components/viz/service/display/skia_renderer.cc
+++ b/components/viz/service/display/skia_renderer.cc
@@ -82,6 +82,7 @@
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/geometry/rect_conversions.h"
 #include "ui/gfx/geometry/rect_f.h"
+#include "ui/gfx/geometry/size.h"
 #include "ui/gfx/geometry/size_conversions.h"
 #include "ui/gfx/geometry/size_f.h"
 #include "ui/gfx/geometry/skia_conversions.h"
@@ -634,7 +635,10 @@
     image_context->set_origin(origin);
   }
 
-  skia_renderer->skia_output_surface_->MakePromiseSkImage(image_context);
+  // We need the original TransferableResource.color_space for YUV => RGB
+  // conversion.
+  skia_renderer->skia_output_surface_->MakePromiseSkImage(
+      image_context, resource_provider->GetOverlayColorSpace(resource_id));
   paint_op_buffer_ = image_context->paint_op_buffer();
   clear_color_ = image_context->clear_color();
   sk_image_ = image_context->image().get();
@@ -907,12 +911,14 @@
   DCHECK(output_surface_->capabilities().supports_viewporter ||
          viewport_size_for_swap_buffers() == surface_size_for_swap_buffers());
   TRACE_EVENT0("viz,benchmark", "SkiaRenderer::SwapBuffers");
+
   OutputSurfaceFrame output_frame;
   output_frame.latency_info = std::move(swap_frame_data.latency_info);
   output_frame.top_controls_visible_height_changed =
       swap_frame_data.top_controls_visible_height_changed;
   output_frame.choreographer_vsync_id = swap_frame_data.choreographer_vsync_id;
   output_frame.size = viewport_size_for_swap_buffers();
+  output_frame.data.seq = swap_frame_data.seq;
   if (use_partial_swap_) {
     swap_buffer_rect_.Intersect(gfx::Rect(surface_size_for_swap_buffers()));
     output_frame.sub_buffer_rect = swap_buffer_rect_;
@@ -3239,7 +3245,7 @@
   // loop through the list in a reverse order since there might be multiple
   // render pass overlays with the same render pass id.
   RenderPassOverlayParams* overlay_found = nullptr;
-  bool Found_in_available_backings = false;
+  bool found_in_available_backings = false;
   for (auto rit = in_flight_render_pass_overlay_backings_.rbegin();
        rit != in_flight_render_pass_overlay_backings_.rend(); ++rit) {
     if (rit->render_pass_id == render_pass_id) {
@@ -3257,7 +3263,7 @@
          rit != available_render_pass_overlay_backings_.rend();
          ++rit, ++index) {
       if (rit->render_pass_id == render_pass_id) {
-        Found_in_available_backings = true;
+        found_in_available_backings = true;
         // Cannot use reverse_iterator. Convert it to const_iterator.
         it_to_delete =
             available_render_pass_overlay_backings_.begin() +
@@ -3289,7 +3295,7 @@
 
   if (no_change_in_rpdq && no_change_in_filters &&
       no_change_in_backdrop_filters) {
-    if (Found_in_available_backings) {
+    if (found_in_available_backings) {
       in_flight_render_pass_overlay_backings_.push_back(*overlay_found);
       available_render_pass_overlay_backings_.erase(it_to_delete);
       *output_render_pass_overlay =
@@ -3499,21 +3505,18 @@
       overlay_params->render_pass_backing;
   overlay->mailbox = dst_overlay_backing.mailbox;
 
-  // Still call BeginPaintRenderPass/EndPaint when skipping the render pass, so
-  // we get the notification (DidReceiveReleasedOverlays) for releaseing the
-  // mailbox of the skipped rendering.
-  current_canvas_ = skia_output_surface_->BeginPaintRenderPass(
-      quad->render_pass_id, dst_overlay_backing.size,
-      dst_overlay_backing.format, /*mipmap=*/false,
-      RenderPassBackingSkColorSpace(dst_overlay_backing),
-      /*is_overlay=*/true, overlay->mailbox);
-  if (!current_canvas_) {
-    DLOG(ERROR)
-        << "BeginPaintRenderPass() in PrepareRenderPassOverlay() failed.";
-    return;
-  }
-
   if (!can_skip_render_pass) {
+    current_canvas_ = skia_output_surface_->BeginPaintRenderPass(
+        quad->render_pass_id, dst_overlay_backing.size,
+        dst_overlay_backing.format, /*mipmap=*/false,
+        RenderPassBackingSkColorSpace(dst_overlay_backing),
+        /*is_overlay=*/true, overlay->mailbox);
+    if (!current_canvas_) {
+      DLOG(ERROR)
+          << "BeginPaintRenderPass() in PrepareRenderPassOverlay() failed.";
+      return;
+    }
+
     // Clear the backing to ARGB(0,0,0,0).
     current_canvas_->clear(SkColorSetARGB(0, 0, 0, 0));
 
@@ -3567,10 +3570,10 @@
       DrawSingleImage(content_image.get(), valid_texel_bounds, &rpdq_params,
                       &paint, &params);
     }
-  }
-  current_canvas_ = nullptr;
 
-  EndPaint(/*failed=*/false);
+    current_canvas_ = nullptr;
+    EndPaint(/*failed=*/false);
+  }
 
 #if BUILDFLAG(IS_APPLE)
   // Adjust |bounds_rect| to contain the whole buffer and at the right location.
diff --git a/components/viz/service/display_embedder/image_context_impl.cc b/components/viz/service/display_embedder/image_context_impl.cc
index 8cbcffd..291663e 100644
--- a/components/viz/service/display_embedder/image_context_impl.cc
+++ b/components/viz/service/display_embedder/image_context_impl.cc
@@ -111,19 +111,20 @@
   // We can't allocate a fallback texture as the original texture was externally
   // allocated. Skia will skip drawing a null SkPromiseImageTexture, do nothing
   // and leave it null.
-  if (backend_format().textureType() == GrTextureType::kExternal)
+  const auto& formats = backend_formats();
+  if (formats.empty() || formats[0].textureType() == GrTextureType::kExternal)
     return;
 
-  DCHECK(!format().PrefersExternalSampler());
   DCHECK(!fallback_context_state_);
   fallback_context_state_ = context_state;
 
   std::vector<sk_sp<SkPromiseImageTexture>> promise_textures;
   for (int plane_index = 0; plane_index < format().NumberOfPlanes();
        plane_index++) {
+    DCHECK_NE(formats[plane_index].textureType(), GrTextureType::kExternal);
     auto fallback_texture =
         fallback_context_state_->gr_context()->createBackendTexture(
-            size().width(), size().height(), backend_format(),
+            size().width(), size().height(), formats[plane_index],
             GetFallbackColorForPlane(format(), plane_index), GrMipMapped::kNo,
             GrRenderable::kYes);
 
diff --git a/components/viz/service/display_embedder/skia_output_surface_impl.cc b/components/viz/service/display_embedder/skia_output_surface_impl.cc
index 093fa3f1..cd4de2e9 100644
--- a/components/viz/service/display_embedder/skia_output_surface_impl.cc
+++ b/components/viz/service/display_embedder/skia_output_surface_impl.cc
@@ -37,6 +37,7 @@
 #include "gpu/command_buffer/common/sync_token.h"
 #include "gpu/command_buffer/service/scheduler.h"
 #include "gpu/command_buffer/service/shared_image/shared_image_factory.h"
+#include "gpu/command_buffer/service/shared_image/shared_image_format_utils.h"
 #include "gpu/command_buffer/service/shared_image/shared_image_representation.h"
 #include "gpu/command_buffer/service/single_task_sequence.h"
 #include "gpu/command_buffer/service/skia_utils.h"
@@ -48,6 +49,7 @@
 #include "third_party/skia/include/core/SkPromiseImageTexture.h"
 #include "third_party/skia/include/gpu/GrYUVABackendTextures.h"
 #include "ui/base/ui_base_features.h"
+#include "ui/gfx/color_space.h"
 #include "ui/gfx/geometry/skia_conversions.h"
 #include "ui/gl/gl_context.h"
 #include "ui/gl/gl_gl_api_implementation.h"
@@ -402,7 +404,9 @@
   return current_paint_->recorder()->getCanvas();
 }
 
-void SkiaOutputSurfaceImpl::MakePromiseSkImage(ImageContext* image_context) {
+void SkiaOutputSurfaceImpl::MakePromiseSkImage(
+    ImageContext* image_context,
+    const gfx::ColorSpace& yuv_color_space) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   DCHECK(current_paint_);
   DCHECK(!image_context->mailbox_holder().mailbox.IsZero());
@@ -431,20 +435,50 @@
   if (image_context->has_image())
     return;
 
-  SkColorType color_type =
-      ToClosestSkColorType(true /* gpu_compositing */, image_context->format());
-  GrBackendFormat backend_format = GetGrBackendFormatForTexture(
-      image_context->format().resource_format(),
-      image_context->mailbox_holder().texture_target,
-      image_context->ycbcr_info());
-  FulfillForPlane* fulfill = new FulfillForPlane(impl);
-  auto image = SkImage::MakePromiseTexture(
-      gr_context_thread_safe_, backend_format,
-      {image_context->size().width(), image_context->size().height()},
-      GrMipMapped::kNo, image_context->origin(), color_type,
-      image_context->alpha_type(), image_context->color_space(), Fulfill,
-      CleanUp, fulfill);
-  image_context->SetImage(std::move(image), backend_format);
+  auto format = image_context->format();
+  if (format.is_single_plane() || format.PrefersExternalSampler()) {
+    SkColorType color_type =
+        ToClosestSkColorType(/*gpu_compositing=*/true, format);
+    GrBackendFormat backend_format = GetGrBackendFormatForTexture(
+        format, /*plane_index=*/0,
+        image_context->mailbox_holder().texture_target,
+        image_context->ycbcr_info());
+    FulfillForPlane* fulfill = new FulfillForPlane(impl);
+    auto image = SkImage::MakePromiseTexture(
+        gr_context_thread_safe_, backend_format,
+        gfx::SizeToSkISize(image_context->size()), GrMipMapped::kNo,
+        image_context->origin(), color_type, image_context->alpha_type(),
+        image_context->color_space(), Fulfill, CleanUp, fulfill);
+    image_context->SetImage(std::move(image), {backend_format});
+  } else {
+    SkYUVAInfo::PlaneConfig plane_config = gpu::ToSkYUVAPlaneConfig(format);
+    SkYUVAInfo::Subsampling subsampling = gpu::ToSkYUVASubsampling(format);
+    // TODO(crbug.com/828599): This should really default to rec709.
+    SkYUVColorSpace sk_yuv_color_space = kRec601_SkYUVColorSpace;
+    yuv_color_space.ToSkYUVColorSpace(format.MultiplanarBitDepth(),
+                                      &sk_yuv_color_space);
+    SkYUVAInfo yuva_info(gfx::SizeToSkISize(image_context->size()),
+                         plane_config, subsampling, sk_yuv_color_space);
+
+    std::vector<GrBackendFormat> formats;
+    void* fulfills[4] = {};
+    for (int plane_index = 0; plane_index < format.NumberOfPlanes();
+         ++plane_index) {
+      DCHECK_EQ(image_context->origin(), kTopLeft_GrSurfaceOrigin);
+      formats.push_back(GetGrBackendFormatForTexture(
+          format, plane_index, image_context->mailbox_holder().texture_target,
+          image_context->ycbcr_info()));
+      fulfills[plane_index] = new FulfillForPlane(impl, plane_index);
+    }
+
+    GrYUVABackendTextureInfo yuva_backend_info(
+        yuva_info, formats.data(), GrMipmapped::kNo, kTopLeft_GrSurfaceOrigin);
+    auto image = SkImage::MakePromiseYUVATexture(
+        gr_context_thread_safe_, yuva_backend_info,
+        image_context->color_space(), Fulfill, CleanUp, fulfills);
+    DCHECK(image);
+    image_context->SetImage(std::move(image), formats);
+  }
 
   if (mailbox_holder.sync_token.HasData()) {
     resource_sync_tokens_.push_back(mailbox_holder.sync_token);
@@ -464,8 +498,8 @@
 
   auto* y_context = static_cast<ImageContextImpl*>(contexts[0]);
   // Note: YUV to RGB conversion is handled by a color filter in SkiaRenderer.
-  SkYUVAInfo yuva_info({y_context->size().width(), y_context->size().height()},
-                       plane_config, subsampling, kIdentity_SkYUVColorSpace);
+  SkYUVAInfo yuva_info(gfx::SizeToSkISize(y_context->size()), plane_config,
+                       subsampling, kIdentity_SkYUVColorSpace);
 
   GrBackendFormat formats[4] = {};
   SkDeferredDisplayListRecorder::PromiseImageTextureContext
@@ -473,15 +507,15 @@
   void* fulfills[4] = {};
   for (size_t i = 0; i < contexts.size(); ++i) {
     auto* context = static_cast<ImageContextImpl*>(contexts[i]);
-    DCHECK(context->origin() == kTopLeft_GrSurfaceOrigin);
+    DCHECK_EQ(context->origin(), kTopLeft_GrSurfaceOrigin);
     formats[i] =
-        GetGrBackendFormatForTexture(context->format().resource_format(),
+        GetGrBackendFormatForTexture(context->format(), /*plane_index=*/0,
                                      context->mailbox_holder().texture_target,
                                      /*ycbcr_info=*/absl::nullopt);
 
     // NOTE: We don't have promises for individual planes, but still need format
     // for fallback
-    context->SetImage(nullptr, formats[i]);
+    context->SetImage(nullptr, {formats[i]});
 
     if (context->mailbox_holder().sync_token.HasData()) {
       resource_sync_tokens_.push_back(context->mailbox_holder().sync_token);
@@ -723,9 +757,9 @@
   DCHECK(current_paint_);
 
   auto& image_context = render_pass_image_cache_[id];
+  auto si_format = SharedImageFormat::SinglePlane(format);
   if (!image_context) {
     gpu::MailboxHolder mailbox_holder(mailbox, gpu::SyncToken(), 0);
-    auto si_format = SharedImageFormat::SinglePlane(format);
     image_context = std::make_unique<ImageContextImpl>(
         mailbox_holder, size, si_format, /*maybe_concurrent_reads=*/false,
         /*ycbcr_info=*/absl::nullopt, std::move(color_space),
@@ -735,15 +769,16 @@
     SkColorType color_type =
         ResourceFormatToClosestSkColorType(true /* gpu_compositing */, format);
     GrBackendFormat backend_format = GetGrBackendFormatForTexture(
-        format, GL_TEXTURE_2D, /*ycbcr_info=*/absl::nullopt);
+        si_format, /*plane_index=*/0, GL_TEXTURE_2D,
+        /*ycbcr_info=*/absl::nullopt);
     FulfillForPlane* fulfill = new FulfillForPlane(image_context.get());
     auto image = SkImage::MakePromiseTexture(
         gr_context_thread_safe_, backend_format,
-        {image_context->size().width(), image_context->size().height()},
+        gfx::SizeToSkISize(image_context->size()),
         mipmap ? GrMipMapped::kYes : GrMipMapped::kNo, image_context->origin(),
         color_type, image_context->alpha_type(), image_context->color_space(),
         Fulfill, CleanUp, fulfill);
-    image_context->SetImage(std::move(image), backend_format);
+    image_context->SetImage(std::move(image), {backend_format});
     if (!image_context->has_image()) {
       return nullptr;
     }
@@ -1178,17 +1213,17 @@
 }
 
 GrBackendFormat SkiaOutputSurfaceImpl::GetGrBackendFormatForTexture(
-    ResourceFormat resource_format,
+    SharedImageFormat si_format,
+    int plane_index,
     uint32_t gl_texture_target,
     const absl::optional<gpu::VulkanYCbCrInfo>& ycbcr_info) {
   if (dependency_->IsUsingVulkan()) {
 #if BUILDFLAG(ENABLE_VULKAN)
     if (!ycbcr_info) {
       // YCbCr info is required for YUV images.
-      DCHECK(resource_format != YVU_420 &&
-             resource_format != YUV_420_BIPLANAR &&
-             resource_format != YUVA_420_TRIPLANAR && resource_format != P010);
-      return GrBackendFormat::MakeVk(ToVkFormat(resource_format));
+      DCHECK(si_format.is_single_plane());
+      // TODO(hitawala): Add multiplanar support for Skia-Vulkan.
+      return GrBackendFormat::MakeVk(gpu::ToVkFormat(si_format));
     }
 
     // Assume optimal tiling.
@@ -1208,12 +1243,14 @@
 #endif  // BUILDFLAG(ENABLE_VULKAN)
   } else if (dependency_->IsUsingDawn()) {
 #if BUILDFLAG(SKIA_USE_DAWN)
-    wgpu::TextureFormat format = ToDawnFormat(resource_format);
+    // TODO(hitawala): Add multiplanar support for Skia-Dawn.
+    wgpu::TextureFormat format = gpu::ToDawnFormat(si_format);
     return GrBackendFormat::MakeDawn(format);
 #endif
   } else if (dependency_->IsUsingMetal()) {
 #if BUILDFLAG(IS_APPLE)
-    return GrBackendFormat::MakeMtl(ToMTLPixelFormat(resource_format));
+    return GrBackendFormat::MakeMtl(
+        gpu::ToMTLPixelFormat(si_format, plane_index));
 #endif
   } else {
 #if !BUILDFLAG(IS_LINUX)
@@ -1222,8 +1259,16 @@
     DCHECK(!ycbcr_info);
 #endif
     // Convert internal format from GLES2 to platform GL.
+    bool use_angle_rgbx_format = impl_on_gpu_->GetFeatureInfo()
+                                     ->feature_flags()
+                                     .angle_rgbx_internal_format;
+    auto gl_format_desc = si_format.PrefersExternalSampler()
+                              ? gpu::ToGLFormatDescExternalSampler(si_format)
+                              : gpu::ToGLFormatDesc(si_format, plane_index,
+                                                    use_angle_rgbx_format);
+    auto gl_storage_internal_format = gl_format_desc.storage_internal_format;
     unsigned int texture_storage_format = gpu::GetGrGLBackendTextureFormat(
-        impl_on_gpu_->GetFeatureInfo(), resource_format,
+        impl_on_gpu_->GetFeatureInfo(), gl_storage_internal_format,
         gr_context_thread_safe_);
 
     return GrBackendFormat::MakeGL(texture_storage_format, gl_texture_target);
diff --git a/components/viz/service/display_embedder/skia_output_surface_impl.h b/components/viz/service/display_embedder/skia_output_surface_impl.h
index f49b281..9cd4536 100644
--- a/components/viz/service/display_embedder/skia_output_surface_impl.h
+++ b/components/viz/service/display_embedder/skia_output_surface_impl.h
@@ -123,7 +123,8 @@
       base::OnceClosure on_finished,
       base::OnceCallback<void(gfx::GpuFenceHandle)> return_release_fence_cb,
       bool is_overlay) override;
-  void MakePromiseSkImage(ImageContext* image_context) override;
+  void MakePromiseSkImage(ImageContext* image_context,
+                          const gfx::ColorSpace& yuv_color_space) override;
   sk_sp<SkImage> MakePromiseSkImageFromRenderPass(
       const AggregatedRenderPassId& id,
       const gfx::Size& size,
@@ -217,7 +218,8 @@
   };
   void FlushGpuTasks(SyncMode sync_mode);
   GrBackendFormat GetGrBackendFormatForTexture(
-      ResourceFormat resource_format,
+      SharedImageFormat si_format,
+      int plane_index,
       uint32_t gl_texture_target,
       const absl::optional<gpu::VulkanYCbCrInfo>& ycbcr_info);
   void ContextLost();
diff --git a/components/viz/test/fake_skia_output_surface.cc b/components/viz/test/fake_skia_output_surface.cc
index ce67735..0b5432c 100644
--- a/components/viz/test/fake_skia_output_surface.cc
+++ b/components/viz/test/fake_skia_output_surface.cc
@@ -112,7 +112,9 @@
   return sk_surface->getCanvas();
 }
 
-void FakeSkiaOutputSurface::MakePromiseSkImage(ImageContext* image_context) {
+void FakeSkiaOutputSurface::MakePromiseSkImage(
+    ImageContext* image_context,
+    const gfx::ColorSpace& yuv_color_space) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
 
   if (image_context->has_image())
@@ -131,7 +133,7 @@
                                kTopLeft_GrSurfaceOrigin, sk_color_type,
                                image_context->alpha_type(),
                                image_context->color_space()),
-      backend_texture.getBackendFormat());
+      {backend_texture.getBackendFormat()});
 }
 
 sk_sp<SkImage> FakeSkiaOutputSurface::MakePromiseSkImageFromYUV(
diff --git a/components/viz/test/fake_skia_output_surface.h b/components/viz/test/fake_skia_output_surface.h
index c51e944..33c39f2 100644
--- a/components/viz/test/fake_skia_output_surface.h
+++ b/components/viz/test/fake_skia_output_surface.h
@@ -77,7 +77,8 @@
       base::OnceClosure on_finished,
       base::OnceCallback<void(gfx::GpuFenceHandle)> return_release_fence_cb,
       bool is_overlay) override;
-  void MakePromiseSkImage(ImageContext* image_context) override;
+  void MakePromiseSkImage(ImageContext* image_context,
+                          const gfx::ColorSpace& yuv_color_space) override;
   sk_sp<SkImage> MakePromiseSkImageFromRenderPass(
       const AggregatedRenderPassId& id,
       const gfx::Size& size,
diff --git a/content/app/content_main_runner_impl.cc b/content/app/content_main_runner_impl.cc
index 7304b32..6b51120 100644
--- a/content/app/content_main_runner_impl.cc
+++ b/content/app/content_main_runner_impl.cc
@@ -40,6 +40,7 @@
 #include "base/process/process_handle.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
+#include "base/task/thread_pool/environment_config.h"
 #include "base/task/thread_pool/thread_pool_instance.h"
 #include "base/threading/hang_watcher.h"
 #include "base/threading/platform_thread.h"
@@ -460,6 +461,11 @@
     blink::WebFontRenderStyle::SetSkiaFontManager(
         SkFontMgr_New_Android(&custom));
   }
+
+  // Preload and cache the results since the methods may use the prlimit64
+  // system call that is not allowed by all sandbox types.
+  base::internal::CanUseBackgroundThreadTypeForWorkerThread();
+  base::internal::CanUseUtilityThreadTypeForWorkerThread();
 }
 #endif  // BUILDFLAG(USE_ZYGOTE_HANDLE)
 
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index 9ce8745..089fb4f 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -1412,6 +1412,8 @@
     "preloading/anchor_element_interaction_host_impl.h",
     "preloading/preconnector.cc",
     "preloading/preconnector.h",
+    "preloading/prefetch/no_vary_search_helper.cc",
+    "preloading/prefetch/no_vary_search_helper.h",
     "preloading/prefetch/prefetch_canary_checker.cc",
     "preloading/prefetch/prefetch_canary_checker.h",
     "preloading/prefetch/prefetch_container.cc",
diff --git a/content/browser/accessibility/browser_accessibility.cc b/content/browser/accessibility/browser_accessibility.cc
index 673f075..8d6ac7f4 100644
--- a/content/browser/accessibility/browser_accessibility.cc
+++ b/content/browser/accessibility/browser_accessibility.cc
@@ -876,7 +876,8 @@
   return nodes;
 }
 
-std::set<ui::AXPlatformNode*> BrowserAccessibility::GetReverseRelations(
+std::set<ui::AXPlatformNode*>
+BrowserAccessibility::GetSourceNodesForReverseRelations(
     ax::mojom::IntAttribute attr) {
   DCHECK(manager_);
   DCHECK(ui::IsNodeIdIntAttribute(attr));
@@ -884,7 +885,8 @@
       manager_->ax_tree()->GetReverseRelations(attr, GetData().id));
 }
 
-std::set<ui::AXPlatformNode*> BrowserAccessibility::GetReverseRelations(
+std::set<ui::AXPlatformNode*>
+BrowserAccessibility::GetSourceNodesForReverseRelations(
     ax::mojom::IntListAttribute attr) {
   DCHECK(manager_);
   DCHECK(ui::IsNodeIdIntListAttribute(attr));
diff --git a/content/browser/accessibility/browser_accessibility.h b/content/browser/accessibility/browser_accessibility.h
index cea96ee..1725a88 100644
--- a/content/browser/accessibility/browser_accessibility.h
+++ b/content/browser/accessibility/browser_accessibility.h
@@ -446,9 +446,9 @@
       ax::mojom::IntAttribute attr) override;
   std::vector<ui::AXPlatformNode*> GetTargetNodesForRelation(
       ax::mojom::IntListAttribute attr) override;
-  std::set<ui::AXPlatformNode*> GetReverseRelations(
+  std::set<ui::AXPlatformNode*> GetSourceNodesForReverseRelations(
       ax::mojom::IntAttribute attr) override;
-  std::set<ui::AXPlatformNode*> GetReverseRelations(
+  std::set<ui::AXPlatformNode*> GetSourceNodesForReverseRelations(
       ax::mojom::IntListAttribute attr) override;
   absl::optional<int> GetPosInSet() const override;
   absl::optional<int> GetSetSize() const override;
diff --git a/content/browser/attribution_reporting/attribution_data_host_manager_impl.cc b/content/browser/attribution_reporting/attribution_data_host_manager_impl.cc
index fb8c9188..bac2db2c 100644
--- a/content/browser/attribution_reporting/attribution_data_host_manager_impl.cc
+++ b/content/browser/attribution_reporting/attribution_data_host_manager_impl.cc
@@ -376,9 +376,10 @@
 }
 
 void AttributionDataHostManagerImpl::SourceDataAvailable(
+    attribution_reporting::SuitableOrigin reporting_origin,
     attribution_reporting::SourceRegistration data) {
   // This is validated by the Mojo typemapping.
-  DCHECK(data.reporting_origin.IsValid());
+  DCHECK(reporting_origin.IsValid());
   DCHECK(data.destination.IsValid());
 
   ReceiverContext& context = receivers_.current_context();
@@ -404,7 +405,7 @@
   }
 
   attribution_manager_->HandleSource(
-      StorableSource(std::move(data),
+      StorableSource(std::move(reporting_origin), std::move(data),
                      /*source_time=*/base::Time::Now(),
                      /*source_origin=*/context.context_origin(), source_type,
                      context.is_within_fenced_frame()));
diff --git a/content/browser/attribution_reporting/attribution_data_host_manager_impl.h b/content/browser/attribution_reporting/attribution_data_host_manager_impl.h
index 371b52de..8162d8e 100644
--- a/content/browser/attribution_reporting/attribution_data_host_manager_impl.h
+++ b/content/browser/attribution_reporting/attribution_data_host_manager_impl.h
@@ -90,7 +90,9 @@
   struct NavigationRedirectSourceRegistrations;
 
   // blink::mojom::AttributionDataHost:
-  void SourceDataAvailable(attribution_reporting::SourceRegistration) override;
+  void SourceDataAvailable(
+      attribution_reporting::SuitableOrigin reporting_origin,
+      attribution_reporting::SourceRegistration) override;
   void TriggerDataAvailable(
       attribution_reporting::TriggerRegistration) override;
 
diff --git a/content/browser/attribution_reporting/attribution_data_host_manager_impl_unittest.cc b/content/browser/attribution_reporting/attribution_data_host_manager_impl_unittest.cc
index 497686e9..520f720 100644
--- a/content/browser/attribution_reporting/attribution_data_host_manager_impl_unittest.cc
+++ b/content/browser/attribution_reporting/attribution_data_host_manager_impl_unittest.cc
@@ -157,8 +157,9 @@
       HandleSource(AllOf(
           SourceTypeIs(AttributionSourceType::kEvent), SourceEventIdIs(10),
           DestinationOriginIs(destination_origin),
-          ImpressionOriginIs(page_origin), SourcePriorityIs(20),
-          SourceDebugKeyIs(789), AggregationKeysAre(aggregation_keys),
+          ImpressionOriginIs(page_origin), ReportingOriginIs(reporting_origin),
+          SourcePriorityIs(20), SourceDebugKeyIs(789),
+          AggregationKeysAre(aggregation_keys),
           SourceIsWithinFencedFrameIs(false), SourceDebugReportingIs(true))));
   {
     RemoteDataHost data_host_remote{.task_environment =
@@ -170,13 +171,14 @@
 
     task_environment_.FastForwardBy(base::Milliseconds(1));
 
-    SourceRegistration source_data(destination_origin, reporting_origin);
+    SourceRegistration source_data(destination_origin);
     source_data.source_event_id = 10;
     source_data.priority = 20;
     source_data.debug_key = 789;
     source_data.aggregation_keys = aggregation_keys;
     source_data.debug_reporting = true;
-    data_host_remote.data_host->SourceDataAvailable(std::move(source_data));
+    data_host_remote.data_host->SourceDataAvailable(reporting_origin,
+                                                    std::move(source_data));
     data_host_remote.data_host.FlushForTesting();
   }
 
@@ -219,24 +221,28 @@
         /*is_within_fenced_frame=*/false,
         AttributionRegistrationType::kSourceOrTrigger);
 
-    SourceRegistration source_data(destination_origin, reporting_origin);
-    data_host_remote.data_host->SourceDataAvailable(source_data);
+    SourceRegistration source_data(destination_origin);
+    data_host_remote.data_host->SourceDataAvailable(reporting_origin,
+                                                    source_data);
     data_host_remote.data_host.FlushForTesting();
 
     checkpoint.Call(1);
 
-    data_host_remote.data_host->SourceDataAvailable(source_data);
+    data_host_remote.data_host->SourceDataAvailable(reporting_origin,
+                                                    source_data);
     data_host_remote.data_host.FlushForTesting();
 
     checkpoint.Call(2);
 
     source_data.destination =
         *SuitableOrigin::Deserialize("https://other-trigger.example");
-    data_host_remote.data_host->SourceDataAvailable(source_data);
+    data_host_remote.data_host->SourceDataAvailable(reporting_origin,
+                                                    source_data);
     data_host_remote.data_host.FlushForTesting();
 
     checkpoint.Call(3);
-    data_host_remote.data_host->SourceDataAvailable(std::move(source_data));
+    data_host_remote.data_host->SourceDataAvailable(std::move(reporting_origin),
+                                                    std::move(source_data));
     data_host_remote.data_host.FlushForTesting();
   }
 
@@ -359,9 +365,10 @@
     {
       mojo::test::BadMessageObserver bad_message_observer;
 
-      SourceRegistration source_data(destination_origin, reporting_origin);
+      SourceRegistration source_data(destination_origin);
 
-      data_host_remote.data_host->SourceDataAvailable(std::move(source_data));
+      data_host_remote.data_host->SourceDataAvailable(
+          std::move(reporting_origin), std::move(source_data));
       data_host_remote.data_host.FlushForTesting();
 
       EXPECT_EQ(bad_message_observer.WaitForBadMessage(),
@@ -414,14 +421,16 @@
         /*is_within_fenced_frame=*/false,
         AttributionRegistrationType::kSourceOrTrigger);
 
-    SourceRegistration source_data(destination_origin, reporting_origin);
+    SourceRegistration source_data(destination_origin);
 
-    data_host_remote.data_host->SourceDataAvailable(source_data);
+    data_host_remote.data_host->SourceDataAvailable(reporting_origin,
+                                                    source_data);
     data_host_remote.data_host.FlushForTesting();
 
     checkpoint.Call(1);
 
-    data_host_remote.data_host->SourceDataAvailable(source_data);
+    data_host_remote.data_host->SourceDataAvailable(reporting_origin,
+                                                    source_data);
     data_host_remote.data_host.FlushForTesting();
 
     checkpoint.Call(2);
@@ -439,7 +448,8 @@
 
     checkpoint.Call(3);
 
-    data_host_remote.data_host->SourceDataAvailable(std::move(source_data));
+    data_host_remote.data_host->SourceDataAvailable(std::move(reporting_origin),
+                                                    std::move(source_data));
     data_host_remote.data_host.FlushForTesting();
   }
 
@@ -475,7 +485,8 @@
         HandleSource(AllOf(
             SourceTypeIs(AttributionSourceType::kNavigation),
             SourceEventIdIs(10), DestinationOriginIs(destination_origin),
-            ImpressionOriginIs(page_origin), SourcePriorityIs(20),
+            ImpressionOriginIs(page_origin),
+            ReportingOriginIs(reporting_origin), SourcePriorityIs(20),
             SourceDebugKeyIs(789), AggregationKeysAre(aggregation_keys),
             SourceIsWithinFencedFrameIs(false), SourceDebugReportingIs(true))));
     EXPECT_CALL(checkpoint, Call(1));
@@ -498,13 +509,14 @@
         attribution_src_token, page_origin,
         AttributionNavigationType::kContextMenu);
 
-    SourceRegistration source_data(destination_origin, reporting_origin);
+    SourceRegistration source_data(destination_origin);
     source_data.source_event_id = 10;
     source_data.priority = 20;
     source_data.debug_key = 789;
     source_data.aggregation_keys = aggregation_keys;
     source_data.debug_reporting = true;
-    data_host_remote.data_host->SourceDataAvailable(source_data);
+    data_host_remote.data_host->SourceDataAvailable(reporting_origin,
+                                                    source_data);
     data_host_remote.data_host.FlushForTesting();
 
     checkpoint.Call(1);
@@ -513,7 +525,8 @@
     // final navigation site.
     source_data.destination =
         *SuitableOrigin::Deserialize("https://trigger2.example");
-    data_host_remote.data_host->SourceDataAvailable(std::move(source_data));
+    data_host_remote.data_host->SourceDataAvailable(reporting_origin,
+                                                    std::move(source_data));
     data_host_remote.data_host.FlushForTesting();
   }
 
@@ -1233,9 +1246,10 @@
       AttributionRegistrationType::kSourceOrTrigger);
 
   SourceRegistration source_data(
-      *SuitableOrigin::Deserialize("https://dest.test"),
-      *SuitableOrigin::Deserialize("https://report1.test"));
-  source_data_host_remote->SourceDataAvailable(std::move(source_data));
+      *SuitableOrigin::Deserialize("https://dest.test"));
+  source_data_host_remote->SourceDataAvailable(
+      /*reporting_origin=*/*SuitableOrigin::Deserialize("https://report1.test"),
+      std::move(source_data));
   source_data_host_remote.FlushForTesting();
 
   // Because there is still a connected data host in source mode, this trigger
@@ -1335,15 +1349,17 @@
       *SuitableOrigin::Deserialize("https://page.example"),
       AttributionNavigationType::kAnchor);
 
-  SourceRegistration source_data(
-      destination_origin,
-      *SuitableOrigin::Deserialize("https://reporter.example"));
+  auto reporting_origin =
+      *SuitableOrigin::Deserialize("https://reporter.example");
+
+  SourceRegistration source_data(destination_origin);
   source_data.source_event_id = 1;
-  data_host_remote1->SourceDataAvailable(source_data);
+  data_host_remote1->SourceDataAvailable(reporting_origin, source_data);
   data_host_remote1.FlushForTesting();
 
   source_data.source_event_id = 2;
-  data_host_remote2->SourceDataAvailable(std::move(source_data));
+  data_host_remote2->SourceDataAvailable(std::move(reporting_origin),
+                                         std::move(source_data));
   data_host_remote2.FlushForTesting();
 }
 
@@ -1360,7 +1376,8 @@
       HandleSource(AllOf(
           SourceTypeIs(AttributionSourceType::kEvent), SourceEventIdIs(10),
           DestinationOriginIs(destination_origin),
-          ImpressionOriginIs(page_origin), SourceIsWithinFencedFrameIs(true))));
+          ImpressionOriginIs(page_origin), ReportingOriginIs(reporting_origin),
+          SourceIsWithinFencedFrameIs(true))));
 
   mojo::Remote<blink::mojom::AttributionDataHost> data_host_remote;
   data_host_manager_.RegisterDataHost(
@@ -1370,9 +1387,10 @@
 
   task_environment_.FastForwardBy(base::Milliseconds(1));
 
-  SourceRegistration source_data(destination_origin, reporting_origin);
+  SourceRegistration source_data(destination_origin);
   source_data.source_event_id = 10;
-  data_host_remote->SourceDataAvailable(std::move(source_data));
+  data_host_remote->SourceDataAvailable(reporting_origin,
+                                        std::move(source_data));
   data_host_remote.FlushForTesting();
 }
 
diff --git a/content/browser/attribution_reporting/attribution_header_utils.cc b/content/browser/attribution_reporting/attribution_header_utils.cc
index 15f15ad..6298e26 100644
--- a/content/browser/attribution_reporting/attribution_header_utils.cc
+++ b/content/browser/attribution_reporting/attribution_header_utils.cc
@@ -28,12 +28,13 @@
   base::expected<attribution_reporting::SourceRegistration,
                  attribution_reporting::mojom::SourceRegistrationError>
       reg = attribution_reporting::SourceRegistration::Parse(
-          std::move(registration), std::move(reporting_origin));
+          std::move(registration));
   if (!reg.has_value())
     return base::unexpected(reg.error());
 
-  return StorableSource(std::move(*reg), source_time, std::move(source_origin),
-                        source_type, is_within_fenced_frame);
+  return StorableSource(std::move(reporting_origin), std::move(*reg),
+                        source_time, std::move(source_origin), source_type,
+                        is_within_fenced_frame);
 }
 
 }  // namespace content
diff --git a/content/browser/attribution_reporting/attribution_test_utils.cc b/content/browser/attribution_reporting/attribution_test_utils.cc
index b00f0a67..b8d8ead 100644
--- a/content/browser/attribution_reporting/attribution_test_utils.cc
+++ b/content/browser/attribution_reporting/attribution_test_utils.cc
@@ -114,6 +114,7 @@
 }
 
 void MockDataHost::SourceDataAvailable(
+    attribution_reporting::SuitableOrigin reporting_origin,
     attribution_reporting::SourceRegistration data) {
   source_data_.push_back(std::move(data));
   if (source_data_.size() < min_source_data_count_) {
diff --git a/content/browser/attribution_reporting/attribution_test_utils.h b/content/browser/attribution_reporting/attribution_test_utils.h
index 25e5ee1..db1e980b 100644
--- a/content/browser/attribution_reporting/attribution_test_utils.h
+++ b/content/browser/attribution_reporting/attribution_test_utils.h
@@ -149,7 +149,9 @@
 
  private:
   // blink::mojom::AttributionDataHost:
-  void SourceDataAvailable(attribution_reporting::SourceRegistration) override;
+  void SourceDataAvailable(
+      attribution_reporting::SuitableOrigin reporting_origin,
+      attribution_reporting::SourceRegistration) override;
   void TriggerDataAvailable(
       attribution_reporting::TriggerRegistration) override;
 
@@ -787,6 +789,11 @@
                             result_listener);
 }
 
+MATCHER_P(ReportingOriginIs, matcher, "") {
+  return ExplainMatchResult(matcher, arg.common_info().reporting_origin(),
+                            result_listener);
+}
+
 MATCHER_P(SourceTypeIs, matcher, "") {
   return ExplainMatchResult(matcher, arg.common_info().source_type(),
                             result_listener);
diff --git a/content/browser/attribution_reporting/attributions_browsertest.cc b/content/browser/attribution_reporting/attributions_browsertest.cc
index 9030802..42e311e 100644
--- a/content/browser/attribution_reporting/attributions_browsertest.cc
+++ b/content/browser/attribution_reporting/attributions_browsertest.cc
@@ -983,112 +983,6 @@
   expected_report.WaitForReport();
 }
 
-// TODO(crbug.com/1382603): Re-enable this test on Fuchsia when deflaked.
-#if BUILDFLAG(IS_FUCHSIA)
-#define MAYBE_MultipleImpressionsPerConversion_ReportSentWithAttribution \
-  DISABLED_MultipleImpressionsPerConversion_ReportSentWithAttribution
-#else
-#define MAYBE_MultipleImpressionsPerConversion_ReportSentWithAttribution \
-  MultipleImpressionsPerConversion_ReportSentWithAttribution
-#endif
-
-IN_PROC_BROWSER_TEST_F(
-    AttributionsBrowserTest,
-    MAYBE_MultipleImpressionsPerConversion_ReportSentWithAttribution) {
-  ExpectedReportWaiter expected_report(
-      GURL("https://b.test/.well-known/attribution-reporting/"
-           "report-event-attribution"),
-      /*attribution_destination=*/"https://d.test",
-      /*source_event_id=*/"2", /*source_type=*/"navigation",
-      /*trigger_data=*/"7", https_server());
-  ASSERT_TRUE(https_server()->Start());
-
-  GURL first_impression_url = https_server()->GetURL(
-      "a.test", "/attribution_reporting/page_with_impression_creator.html");
-  EXPECT_TRUE(NavigateToURL(web_contents(), first_impression_url));
-
-  GURL second_impression_url = https_server()->GetURL(
-      "c.test", "/attribution_reporting/page_with_impression_creator.html");
-  Shell* shell2 =
-      Shell::CreateNewWindow(shell()->web_contents()->GetBrowserContext(),
-                             GURL(), nullptr, gfx::Size(100, 100));
-  EXPECT_TRUE(NavigateToURL(shell2->web_contents(), second_impression_url));
-
-  GURL conversion_url = https_server()->GetURL(
-      "d.test", "/attribution_reporting/page_with_conversion_redirect.html");
-  GURL register_source_url = https_server()->GetURL(
-      "b.test", "/attribution_reporting/register_source_headers.html");
-
-  CreateAndClickSource(shell()->web_contents(), conversion_url,
-                       register_source_url.spec());
-
-  GURL register_source_url_2 = https_server()->GetURL(
-      "b.test", "/attribution_reporting/register_source_headers_2.html");
-  CreateAndClickSource(shell2->web_contents(), conversion_url,
-                       register_source_url_2.spec());
-
-  GURL register_trigger_url = https_server()->GetURL(
-      "b.test", "/attribution_reporting/register_trigger_headers.html");
-  EXPECT_TRUE(ExecJs(
-      shell2, JsReplace("createAttributionSrcImg($1);", register_trigger_url)));
-
-  expected_report.WaitForReport();
-}
-
-// TODO(https://crbug.com/1393162): Flaky timeouts on Fuchsia.
-#if BUILDFLAG(IS_FUCHSIA)
-#define MAYBE_MultipleImpressionsPerConversion_ReportSentWithHighestPriority \
-  DISABLED_MultipleImpressionsPerConversion_ReportSentWithHighestPriority
-#else
-#define MAYBE_MultipleImpressionsPerConversion_ReportSentWithHighestPriority \
-  MultipleImpressionsPerConversion_ReportSentWithHighestPriority
-#endif
-IN_PROC_BROWSER_TEST_F(
-    AttributionsBrowserTest,
-    MAYBE_MultipleImpressionsPerConversion_ReportSentWithHighestPriority) {
-  // Report will be sent for the impression with highest priority.
-  ExpectedReportWaiter expected_report(
-      GURL("https://b.test/.well-known/attribution-reporting/"
-           "report-event-attribution"),
-      /*attribution_destination=*/"https://d.test",
-      /*source_event_id=*/"3", /*source_type=*/"navigation",
-      /*trigger_data=*/"7", https_server());
-  ASSERT_TRUE(https_server()->Start());
-
-  GURL first_impression_url = https_server()->GetURL(
-      "a.test", "/attribution_reporting/page_with_impression_creator.html");
-  EXPECT_TRUE(NavigateToURL(web_contents(), first_impression_url));
-
-  GURL second_impression_url = https_server()->GetURL(
-      "c.test", "/attribution_reporting/page_with_impression_creator.html");
-  Shell* shell2 =
-      Shell::CreateNewWindow(shell()->web_contents()->GetBrowserContext(),
-                             GURL(), nullptr, gfx::Size(100, 100));
-  EXPECT_TRUE(NavigateToURL(shell2->web_contents(), second_impression_url));
-
-  // Register impressions from both windows.
-  GURL conversion_url = https_server()->GetURL(
-      "d.test", "/attribution_reporting/page_with_conversion_redirect.html");
-  GURL register_source_url = https_server()->GetURL(
-      "b.test",
-      "/attribution_reporting/register_source_headers_high_priority.html");
-
-  CreateAndClickSource(shell()->web_contents(), conversion_url,
-                       register_source_url.spec());
-
-  GURL register_source_url_2 = https_server()->GetURL(
-      "b.test", "/attribution_reporting/register_source_headers.html");
-  CreateAndClickSource(shell2->web_contents(), conversion_url,
-                       register_source_url_2.spec());
-
-  GURL register_trigger_url = https_server()->GetURL(
-      "b.test", "/attribution_reporting/register_trigger_headers.html");
-  EXPECT_TRUE(ExecJs(
-      shell2, JsReplace("createAttributionSrcImg($1);", register_trigger_url)));
-
-  expected_report.WaitForReport();
-}
-
 IN_PROC_BROWSER_TEST_F(
     AttributionsBrowserTest,
     ServiceWorkerPerformsAttributionSrcRedirect_ReporterSet) {
@@ -1404,58 +1298,6 @@
 }
 
 IN_PROC_BROWSER_TEST_F(AttributionsBrowserTest,
-                       ImpressionConversionWithDedupKey_Deduped) {
-  // Expected reports must be registered before the server starts.
-  ExpectedReportWaiter expected_report1(
-      GURL("https://a.test/.well-known/attribution-reporting/"
-           "report-event-attribution"),
-      /*attribution_destination=*/"https://d.test",
-      /*source_event_id=*/"5", /*source_type=*/"navigation",
-      /*trigger_data=*/"1", https_server());
-  // 12 below is sanitized to 4 here by `SanitizeTriggerData()`.
-  ExpectedReportWaiter expected_report2(
-      GURL("https://a.test/.well-known/attribution-reporting/"
-           "report-event-attribution"),
-      /*attribution_destination=*/"https://d.test",
-      /*source_event_id=*/"5", /*source_type=*/"navigation",
-      /*trigger_data=*/"7", https_server());
-  ASSERT_TRUE(https_server()->Start());
-
-  GURL impression_url = https_server()->GetURL(
-      "a.test", "/attribution_reporting/page_with_impression_creator.html");
-  EXPECT_TRUE(NavigateToURL(web_contents(), impression_url));
-
-  GURL conversion_url = https_server()->GetURL(
-      "d.test", "/attribution_reporting/page_with_conversion_redirect.html");
-  GURL register_source_url = https_server()->GetURL(
-      "a.test", "/attribution_reporting/register_source_headers.html");
-
-  CreateAndClickSource(web_contents(), conversion_url,
-                       register_source_url.spec());
-
-  GURL register_trigger_with_dedup_url = https_server()->GetURL(
-      "a.test", "/attribution_reporting/register_trigger_headers_dedup.html");
-  EXPECT_TRUE(
-      ExecJs(web_contents(), JsReplace("createAttributionSrcImg($1);",
-                                       register_trigger_with_dedup_url)));
-
-  expected_report1.WaitForReport();
-
-  // This report should be deduped against the previous one.
-  EXPECT_TRUE(
-      ExecJs(web_contents(), JsReplace("createAttributionSrcImg($1);",
-                                       register_trigger_with_dedup_url)));
-
-  // This report should be received, as it has a different dedupKey.
-  GURL register_trigger_url = https_server()->GetURL(
-      "a.test", "/attribution_reporting/register_trigger_headers.html");
-  EXPECT_TRUE(ExecJs(web_contents(), JsReplace("createAttributionSrcImg($1);",
-                                               register_trigger_url)));
-
-  expected_report2.WaitForReport();
-}
-
-IN_PROC_BROWSER_TEST_F(AttributionsBrowserTest,
                        TriggerAndSourceSameRedirectChain_Handled) {
   ASSERT_TRUE(https_server()->Start());
 
diff --git a/content/browser/attribution_reporting/storable_source.cc b/content/browser/attribution_reporting/storable_source.cc
index cbe4999..93e6098 100644
--- a/content/browser/attribution_reporting/storable_source.cc
+++ b/content/browser/attribution_reporting/storable_source.cc
@@ -27,6 +27,7 @@
       debug_reporting_(debug_reporting) {}
 
 StorableSource::StorableSource(
+    attribution_reporting::SuitableOrigin reporting_origin,
     attribution_reporting::SourceRegistration reg,
     base::Time source_time,
     attribution_reporting::SuitableOrigin source_origin,
@@ -37,7 +38,7 @@
               reg.source_event_id,
               std::move(source_origin),
               std::move(reg.destination),
-              std::move(reg.reporting_origin),
+              std::move(reporting_origin),
               source_time,
               CommonSourceInfo::GetExpiryTime(reg.expiry,
                                               source_time,
diff --git a/content/browser/attribution_reporting/storable_source.h b/content/browser/attribution_reporting/storable_source.h
index 7336659..6bdb4e6 100644
--- a/content/browser/attribution_reporting/storable_source.h
+++ b/content/browser/attribution_reporting/storable_source.h
@@ -44,7 +44,8 @@
                  bool is_within_fenced_frame,
                  bool debug_reporting);
 
-  StorableSource(attribution_reporting::SourceRegistration,
+  StorableSource(attribution_reporting::SuitableOrigin reporting_origin,
+                 attribution_reporting::SourceRegistration,
                  base::Time source_time,
                  attribution_reporting::SuitableOrigin source_origin,
                  AttributionSourceType,
diff --git a/content/browser/attribution_reporting/storable_source_unittest.cc b/content/browser/attribution_reporting/storable_source_unittest.cc
index e7e3e16..3b44c840 100644
--- a/content/browser/attribution_reporting/storable_source_unittest.cc
+++ b/content/browser/attribution_reporting/storable_source_unittest.cc
@@ -97,13 +97,12 @@
   };
 
   for (const auto& test_case : kTestCases) {
-    attribution_reporting::SourceRegistration reg(destination,
-                                                  reporting_origin);
+    attribution_reporting::SourceRegistration reg(destination);
     reg.expiry = test_case.expiry;
     reg.event_report_window = test_case.event_report_window;
     reg.aggregatable_report_window = test_case.aggregatable_report_window;
 
-    StorableSource actual(std::move(reg), kSourceTime,
+    StorableSource actual(reporting_origin, std::move(reg), kSourceTime,
                           *SuitableOrigin::Deserialize("https://source.test"),
                           AttributionSourceType::kNavigation,
                           /*is_within_fenced_frame=*/false);
diff --git a/content/browser/bluetooth/OWNERS b/content/browser/bluetooth/OWNERS
index ff41043..3610b9e 100644
--- a/content/browser/bluetooth/OWNERS
+++ b/content/browser/bluetooth/OWNERS
@@ -1 +1,2 @@
 reillyg@chromium.org
+chengweih@chromium.org
diff --git a/content/browser/devtools/protocol/devtools_protocol_browsertest.cc b/content/browser/devtools/protocol/devtools_protocol_browsertest.cc
index f17c456..4fc9c3c8 100644
--- a/content/browser/devtools/protocol/devtools_protocol_browsertest.cc
+++ b/content/browser/devtools/protocol/devtools_protocol_browsertest.cc
@@ -30,6 +30,7 @@
 #include "content/browser/devtools/protocol/browser_handler.h"
 #include "content/browser/devtools/protocol/devtools_download_manager_delegate.h"
 #include "content/browser/devtools/protocol/devtools_protocol_test_support.h"
+#include "content/browser/devtools/protocol/system_info.h"
 #include "content/browser/devtools/render_frame_devtools_agent_host.h"
 #include "content/browser/download/download_manager_impl.h"
 #include "content/browser/host_zoom_map_impl.h"
@@ -3784,6 +3785,26 @@
 }
 
 IN_PROC_BROWSER_TEST_F(PrerenderDevToolsProtocolTest,
+                       CheckReportedPrerenderFeatures) {
+  AttachToBrowserTarget();
+  base::Value::Dict params;
+  params.Set("featureState", "PrerenderHoldback");
+  const base::Value::Dict* result =
+      SendCommand("SystemInfo.getFeatureState", std::move(params));
+  EXPECT_THAT(result->FindBool("featureEnabled"), false);
+}
+
+IN_PROC_BROWSER_TEST_F(PrerenderHoldbackDevToolsProtocolTest,
+                       CheckReportedPrerenderFeatures) {
+  AttachToBrowserTarget();
+  base::Value::Dict params;
+  params.Set("featureState", "PrerenderHoldback");
+  const base::Value::Dict* result =
+      SendCommand("SystemInfo.getFeatureState", std::move(params));
+  EXPECT_THAT(result->FindBool("featureEnabled"), true);
+}
+
+IN_PROC_BROWSER_TEST_F(PrerenderDevToolsProtocolTest,
                        RemoveStoredPrerenderActivationIfNavigateAway) {
   base::HistogramTester histogram_tester;
   ASSERT_TRUE(embedded_test_server()->Start());
diff --git a/content/browser/devtools/protocol/system_info_handler.cc b/content/browser/devtools/protocol/system_info_handler.cc
index 0ca93b4d3..da3f4e8 100644
--- a/content/browser/devtools/protocol/system_info_handler.cc
+++ b/content/browser/devtools/protocol/system_info_handler.cc
@@ -21,6 +21,7 @@
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/child_process_data.h"
 #include "content/public/browser/render_process_host.h"
+#include "content/public/common/content_features.h"
 #include "gpu/config/gpu_feature_type.h"
 #include "gpu/config/gpu_info.h"
 #include "gpu/config/gpu_switches.h"
@@ -427,5 +428,15 @@
   callback->sendSuccess(std::move(process_info));
 }
 
+Response SystemInfoHandler::GetFeatureState(const String& in_featureState,
+                                            bool* featureEnabled) {
+  if (in_featureState == "PrerenderHoldback") {
+    *featureEnabled =
+        std::move(base::FeatureList::IsEnabled(features::kPrerender2Holdback));
+    return Response::Success();
+  }
+  return Response::InvalidParams("Unknown feature");
+}
+
 }  // namespace protocol
 }  // namespace content
diff --git a/content/browser/devtools/protocol/system_info_handler.h b/content/browser/devtools/protocol/system_info_handler.h
index daaa93d3..8d40c1d 100644
--- a/content/browser/devtools/protocol/system_info_handler.h
+++ b/content/browser/devtools/protocol/system_info_handler.h
@@ -31,6 +31,8 @@
   void GetInfo(std::unique_ptr<GetInfoCallback> callback) override;
   void GetProcessInfo(
       std::unique_ptr<GetProcessInfoCallback> callback) override;
+  Response GetFeatureState(const String& in_featureState,
+                           bool* featureEnabled) override;
 
  private:
   friend class SystemInfoHandlerGpuObserver;
diff --git a/content/browser/devtools/protocol_config.json b/content/browser/devtools/protocol_config.json
index 58f8a169a..5dc68598 100644
--- a/content/browser/devtools/protocol_config.json
+++ b/content/browser/devtools/protocol_config.json
@@ -95,6 +95,7 @@
             },
             {
                 "domain": "SystemInfo",
+                "include": ["getInfo", "getProcessInfo", "getFeatureState"],
                 "async": ["getInfo", "getProcessInfo"]
             },
             {
diff --git a/content/browser/fenced_frame/fenced_frame.cc b/content/browser/fenced_frame/fenced_frame.cc
index 0441b5fb..335e99b 100644
--- a/content/browser/fenced_frame/fenced_frame.cc
+++ b/content/browser/fenced_frame/fenced_frame.cc
@@ -12,6 +12,7 @@
 #include "third_party/blink/public/common/fenced_frame/fenced_frame_utils.h"
 #include "third_party/blink/public/common/frame/fenced_frame_sandbox_flags.h"
 #include "third_party/blink/public/common/frame/frame_owner_element_type.h"
+#include "third_party/blink/public/mojom/navigation/navigation_initiator_activation_and_ad_status.mojom.h"
 #include "url/gurl.h"
 
 namespace content {
@@ -108,6 +109,14 @@
   // need to provide a `source_site_instance`.
   url::Origin initiator_origin;
 
+  // TODO(yaoxia): implement this. This information will be propagated to the
+  // `NavigationHandle`. Skip propagating here is fine for now, because we are
+  // currently only interested navigation that occurs in the outermost RFH.
+  blink::mojom::NavigationInitiatorActivationAndAdStatus
+      initiator_activation_and_ad_status =
+          blink::mojom::NavigationInitiatorActivationAndAdStatus::
+              kDidNotStartWithTransientActivation;
+
   inner_root->navigator().NavigateFromFrameProxy(
       inner_root->current_frame_host(), validated_url,
       /*initiator_frame_token=*/nullptr,
@@ -119,7 +128,8 @@
       /*blob_url_loader_factory=*/nullptr,
       network::mojom::SourceLocation::New(), /*has_user_gesture=*/false,
       /*is_form_submission=*/false,
-      /*impression=*/absl::nullopt, navigation_start_time,
+      /*impression=*/absl::nullopt, initiator_activation_and_ad_status,
+      navigation_start_time,
       /*is_embedder_initiated_fenced_frame_navigation=*/true);
 }
 
diff --git a/content/browser/interest_group/interest_group_browsertest.cc b/content/browser/interest_group/interest_group_browsertest.cc
index 2558aca7..72c0a7d 100644
--- a/content/browser/interest_group/interest_group_browsertest.cc
+++ b/content/browser/interest_group/interest_group_browsertest.cc
@@ -4738,6 +4738,11 @@
           https_server_->GetURL("a.test", "/interest_group/decision_logic.js")),
       rfh1));
 
+  // Wait for leave to be committed to the database.
+  while (GetJoinCount(test_origin, "cars") > 0) {
+    base::RunLoop().RunUntilIdle();
+  }
+
   ASSERT_NO_FATAL_FAILURE(RunAuctionAndNavigateFencedFrame(
       ad_url2,
       JsReplace(
diff --git a/content/browser/loader/navigation_url_loader_impl_unittest.cc b/content/browser/loader/navigation_url_loader_impl_unittest.cc
index a67b57a..1f2540ff 100644
--- a/content/browser/loader/navigation_url_loader_impl_unittest.cc
+++ b/content/browser/loader/navigation_url_loader_impl_unittest.cc
@@ -228,7 +228,9 @@
             nullptr /* trust_token_params */, absl::nullopt /* impression */,
             base::TimeTicks() /* renderer_before_unload_start */,
             base::TimeTicks() /* renderer_before_unload_end */,
-            absl::nullopt /* web_bundle_token */);
+            absl::nullopt /* web_bundle_token */,
+            blink::mojom::NavigationInitiatorActivationAndAdStatus::
+                kDidNotStartWithTransientActivation);
 
     auto common_params = blink::CreateCommonNavigationParams();
     common_params->url = url;
diff --git a/content/browser/loader/navigation_url_loader_unittest.cc b/content/browser/loader/navigation_url_loader_unittest.cc
index 72bbcb9..7c161db 100644
--- a/content/browser/loader/navigation_url_loader_unittest.cc
+++ b/content/browser/loader/navigation_url_loader_unittest.cc
@@ -95,7 +95,9 @@
             nullptr /* trust_token_params */, absl::nullopt /* impression */,
             base::TimeTicks() /* renderer_before_unload_start */,
             base::TimeTicks() /* renderer_before_unload_end */,
-            absl::nullopt /* web_bundle_token */);
+            absl::nullopt /* web_bundle_token */,
+            blink::mojom::NavigationInitiatorActivationAndAdStatus::
+                kDidNotStartWithTransientActivation);
     auto common_params = blink::CreateCommonNavigationParams();
     common_params->url = url;
     common_params->initiator_origin = url::Origin::Create(url);
diff --git a/content/browser/media/media_capabilities_browsertest.cc b/content/browser/media/media_capabilities_browsertest.cc
index 0d7957e..9f6b804 100644
--- a/content/browser/media/media_capabilities_browsertest.cc
+++ b/content/browser/media/media_capabilities_browsertest.cc
@@ -171,18 +171,11 @@
   }
 };
 
-// Fails on Linux and Chrome OS: http://crbug.com/1220321.
-#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
-#define MAYBE_CommonVideoDecodeTypes DISABLED_CommonVideoDecodeTypes
-#else
-#define MAYBE_CommonVideoDecodeTypes CommonVideoDecodeTypes
-#endif
-
 // Cover basic codec support of content types where the answer of support
 // (or not) should be common to both "media-source" and "file" query types.
 // for more exhaustive codec string testing.
 IN_PROC_BROWSER_TEST_P(MediaCapabilitiesTestWithConfigType,
-                       MAYBE_CommonVideoDecodeTypes) {
+                       CommonVideoDecodeTypes) {
   base::FilePath file_path = media::GetTestDataFilePath(kDecodeTestFile);
 
   const std::string& config_type = GetTypeString();
diff --git a/content/browser/portal/portal.cc b/content/browser/portal/portal.cc
index 9f5b143f..48c8163 100644
--- a/content/browser/portal/portal.cc
+++ b/content/browser/portal/portal.cc
@@ -33,6 +33,7 @@
 #include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/common/frame/frame_owner_element_type.h"
 #include "third_party/blink/public/mojom/frame/frame_owner_properties.mojom.h"
+#include "third_party/blink/public/mojom/navigation/navigation_initiator_activation_and_ad_status.mojom.h"
 
 namespace content {
 
@@ -273,6 +274,12 @@
   // TODO(crbug.com/1290239): Measure the start time in the renderer process.
   const auto navigation_start_time = base::TimeTicks::Now();
 
+  // TODO(yaoxia): figure out if we need to propagate this.
+  blink::mojom::NavigationInitiatorActivationAndAdStatus
+      initiator_activation_and_ad_status =
+          blink::mojom::NavigationInitiatorActivationAndAdStatus::
+              kDidNotStartWithTransientActivation;
+
   // TODO(https://crbug.com/1074422): It is possible for a portal to be
   // navigated by a frame other than the owning frame. Find a way to route the
   // correct initiator of the portal navigation to this call.
@@ -287,7 +294,8 @@
       should_replace_entry, download_policy, "GET", nullptr, "", nullptr,
       network::mojom::SourceLocation::New(), false,
       /*is_form_submission=*/false,
-      /*impression=*/absl::nullopt, navigation_start_time);
+      /*impression=*/absl::nullopt, initiator_activation_and_ad_status,
+      navigation_start_time);
 
   std::move(callback).Run();
 }
diff --git a/content/browser/preloading/prefetch/no_vary_search_helper.cc b/content/browser/preloading/prefetch/no_vary_search_helper.cc
new file mode 100644
index 0000000..3a4ed466
--- /dev/null
+++ b/content/browser/preloading/prefetch/no_vary_search_helper.cc
@@ -0,0 +1,98 @@
+// 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 "content/browser/preloading/prefetch/no_vary_search_helper.h"
+
+#include <map>
+#include <utility>
+#include <vector>
+
+#include "base/feature_list.h"
+#include "services/network/public/cpp/features.h"
+#include "services/network/public/mojom/url_response_head.mojom.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+
+namespace content {
+
+namespace {
+// Parse No-Vary-Search from mojom structure received from network service.
+net::HttpNoVarySearchData ParseHttpNoVarySearchDataFromMojom(
+    const network::mojom::NoVarySearchPtr& no_vary_search_ptr) {
+  if (no_vary_search_ptr->search_variance->is_vary_params()) {
+    return net::HttpNoVarySearchData::CreateFromVaryParams(
+        no_vary_search_ptr->search_variance->get_vary_params(),
+        no_vary_search_ptr->vary_on_key_order);
+  }
+  return net::HttpNoVarySearchData::CreateFromNoVaryParams(
+      no_vary_search_ptr->search_variance->get_no_vary_params(),
+      no_vary_search_ptr->vary_on_key_order);
+}
+}  // namespace
+
+NoVarySearchHelper::NoVarySearchHelper() = default;
+NoVarySearchHelper::~NoVarySearchHelper() = default;
+
+void NoVarySearchHelper::AddUrl(const GURL& url,
+                                const network::mojom::URLResponseHead& head) {
+  DCHECK(
+      base::FeatureList::IsEnabled(network::features::kPrefetchNoVarySearch));
+  // Check if the prefetched response has a No-Vary-Search header and
+  // add the prefetched response to |prefetches_with_no_vary_search_| if it
+  // does.
+  if (head.parsed_headers && head.parsed_headers->no_vary_search) {
+    auto no_vary_search_data =
+        ParseHttpNoVarySearchDataFromMojom(head.parsed_headers->no_vary_search);
+    // Key the map using the url without the reference and query.
+    // In almost all cases we expect one key to only one (url,no-vary-search)
+    // mapping.
+    GURL::Replacements replacements;
+    replacements.ClearRef();
+    replacements.ClearQuery();
+    prefetches_with_no_vary_search_[url.ReplaceComponents(replacements)]
+        .emplace_back(url, std::move(no_vary_search_data));
+  }
+}
+
+absl::optional<GURL> NoVarySearchHelper::MatchUrl(const GURL& url) const {
+  DCHECK(
+      base::FeatureList::IsEnabled(network::features::kPrefetchNoVarySearch));
+  // Check if we have a URL that matches No-Vary-Search
+  GURL::Replacements replacements;
+  replacements.ClearRef();
+  replacements.ClearQuery();
+  const auto nvs_prefetch_iter =
+      prefetches_with_no_vary_search_.find(url.ReplaceComponents(replacements));
+  if (nvs_prefetch_iter == prefetches_with_no_vary_search_.end()) {
+    return absl::nullopt;
+  }
+  const auto nvs_iter = std::find_if(
+      nvs_prefetch_iter->second.begin(), nvs_prefetch_iter->second.end(),
+      [&](const std::pair<GURL, net::HttpNoVarySearchData>& nvs) {
+        return nvs.second.AreEquivalent(url, nvs.first);
+      });
+  if (nvs_iter == nvs_prefetch_iter->second.end()) {
+    return absl::nullopt;
+  }
+  return nvs_iter->first;
+}
+
+const std::vector<std::pair<GURL, net::HttpNoVarySearchData>>*
+NoVarySearchHelper::GetAllForUrlWithoutRefAndQueryForTesting(
+    const GURL& url) const {
+  DCHECK(
+      base::FeatureList::IsEnabled(network::features::kPrefetchNoVarySearch));
+  GURL::Replacements replacements;
+  replacements.ClearRef();
+  replacements.ClearQuery();
+  const auto it =
+      prefetches_with_no_vary_search_.find(url.ReplaceComponents(replacements));
+  if (it == prefetches_with_no_vary_search_.end()) {
+    return nullptr;
+  }
+  return &it->second;
+}
+
+}  // namespace content
diff --git a/content/browser/preloading/prefetch/no_vary_search_helper.h b/content/browser/preloading/prefetch/no_vary_search_helper.h
new file mode 100644
index 0000000..3160e260
--- /dev/null
+++ b/content/browser/preloading/prefetch/no_vary_search_helper.h
@@ -0,0 +1,55 @@
+// 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 CONTENT_BROWSER_PRELOADING_PREFETCH_NO_VARY_SEARCH_HELPER_H_
+#define CONTENT_BROWSER_PRELOADING_PREFETCH_NO_VARY_SEARCH_HELPER_H_
+
+#include <map>
+#include <memory>
+#include <vector>
+
+#include "content/common/content_export.h"
+#include "net/http/http_no_vary_search_data.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+class GURL;
+namespace network::mojom {
+class URLResponseHead;
+}  // namespace network::mojom
+
+namespace content {
+
+// Helper class to keep track of prefetched URLs that have No-Vary-Search
+// header present in their responses.
+class CONTENT_EXPORT NoVarySearchHelper {
+ public:
+  NoVarySearchHelper();
+  ~NoVarySearchHelper();
+
+  // Track `url` with No-Vary-Search header information if applicable.
+  // If `url` doesn't have a No-Vary-Search header this method will not
+  // track `url` at all. It is left to the caller to handle the case of
+  // true URL equality.
+  void AddUrl(const GURL& url, const network::mojom::URLResponseHead& head);
+
+  // Try to match `url` within tracked urls with No-Vary-Search information.
+  // Return the matched url or absl::nullopt otherwise.
+  absl::optional<GURL> MatchUrl(const GURL& url) const;
+
+  // Return the (URL,NoVarySearchInfo) pairs for a specific Url without
+  // query and reference. Allow as input urls with query and/or reference
+  // for ease of use (remove query/reference during lookup).
+  const std::vector<std::pair<GURL, net::HttpNoVarySearchData>>*
+  GetAllForUrlWithoutRefAndQueryForTesting(const GURL& url) const;
+
+ private:
+  // The set of urls that have No-Vary-Search header in their prefetched
+  // response keyed by their path without the ref and query parts.
+  std::map<GURL, std::vector<std::pair<GURL, net::HttpNoVarySearchData>>>
+      prefetches_with_no_vary_search_;
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_PRELOADING_PREFETCH_NO_VARY_SEARCH_HELPER_H_
diff --git a/content/browser/preloading/prefetch/no_vary_search_helper_unittest.cc b/content/browser/preloading/prefetch/no_vary_search_helper_unittest.cc
new file mode 100644
index 0000000..960c94a
--- /dev/null
+++ b/content/browser/preloading/prefetch/no_vary_search_helper_unittest.cc
@@ -0,0 +1,137 @@
+// 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 "content/browser/preloading/prefetch/no_vary_search_helper.h"
+
+#include "base/test/scoped_feature_list.h"
+#include "services/network/public/cpp/features.h"
+#include "services/network/public/mojom/url_response_head.mojom.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+namespace {
+
+network::mojom::URLResponseHeadPtr CreateHead() {
+  network::mojom::URLResponseHeadPtr head =
+      network::mojom::URLResponseHead::New();
+  head->parsed_headers = network::mojom::ParsedHeaders::New();
+  head->parsed_headers->no_vary_search = network::mojom::NoVarySearch::New();
+  head->parsed_headers->no_vary_search->vary_on_key_order = true;
+  return head;
+}
+
+TEST(NoVarySearchHelperTest, AddAndMatchUrlNonEmptyVaryParams) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(
+      network::features::kPrefetchNoVarySearch);
+
+  network::mojom::URLResponseHeadPtr head = CreateHead();
+  head->parsed_headers->no_vary_search->search_variance =
+      network::mojom::SearchParamsVariance::NewVaryParams({"a"});
+
+  NoVarySearchHelper helper;
+  const GURL test_url("https://a.com/index.html?a=2&b=3");
+  helper.AddUrl(test_url, *head);
+
+  const auto* urls_with_no_vary_search =
+      helper.GetAllForUrlWithoutRefAndQueryForTesting(test_url);
+  ASSERT_TRUE(urls_with_no_vary_search);
+  ASSERT_EQ(urls_with_no_vary_search->size(), 1u);
+  EXPECT_EQ(urls_with_no_vary_search->at(0).first, test_url);
+  EXPECT_THAT(urls_with_no_vary_search->at(0).second.vary_params(),
+              testing::UnorderedElementsAreArray({"a"}));
+  EXPECT_TRUE(urls_with_no_vary_search->at(0).second.no_vary_params().empty());
+  EXPECT_FALSE(urls_with_no_vary_search->at(0).second.vary_by_default());
+  EXPECT_TRUE(urls_with_no_vary_search->at(0).second.vary_on_key_order());
+  EXPECT_TRUE(helper.MatchUrl(GURL("https://a.com/index.html?b=4&a=2&c=5")));
+  EXPECT_TRUE(helper.MatchUrl(GURL("https://a.com/index.html?a=2")));
+  EXPECT_FALSE(helper.MatchUrl(GURL("https://a.com/index.html")));
+  EXPECT_FALSE(helper.MatchUrl(GURL("https://a.com/index.html?b=4")));
+}
+
+TEST(NoVarySearchHelperTest, AddAndMatchUrlNonEmptyNoVaryParams) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(
+      network::features::kPrefetchNoVarySearch);
+
+  network::mojom::URLResponseHeadPtr head = CreateHead();
+  head->parsed_headers->no_vary_search->search_variance =
+      network::mojom::SearchParamsVariance::NewNoVaryParams({"a"});
+  const GURL test_url = GURL("https://a.com/home.html?a=2&b=3");
+
+  NoVarySearchHelper helper;
+  helper.AddUrl(test_url, *head);
+  const auto* urls_with_no_vary_search =
+      helper.GetAllForUrlWithoutRefAndQueryForTesting(test_url);
+  ASSERT_TRUE(urls_with_no_vary_search);
+  ASSERT_EQ(urls_with_no_vary_search->size(), 1u);
+  EXPECT_EQ(urls_with_no_vary_search->at(0).first, test_url);
+  EXPECT_THAT(urls_with_no_vary_search->at(0).second.no_vary_params(),
+              testing::UnorderedElementsAreArray({"a"}));
+  EXPECT_TRUE(urls_with_no_vary_search->at(0).second.vary_params().empty());
+  EXPECT_TRUE(urls_with_no_vary_search->at(0).second.vary_by_default());
+  EXPECT_TRUE(urls_with_no_vary_search->at(0).second.vary_on_key_order());
+  EXPECT_TRUE(helper.MatchUrl(test_url));
+  EXPECT_TRUE(helper.MatchUrl(GURL("https://a.com/home.html?b=3")));
+  EXPECT_TRUE(helper.MatchUrl(GURL("https://a.com/home.html?b=3&a=4")));
+  EXPECT_FALSE(helper.MatchUrl(GURL("https://a.com/home.html")));
+  EXPECT_FALSE(helper.MatchUrl(GURL("https://a.com/home.html?b=4")));
+}
+
+TEST(NoVarySearchHelperTest, AddAndMatchUrlEmptyNoVaryParams) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(
+      network::features::kPrefetchNoVarySearch);
+
+  network::mojom::URLResponseHeadPtr head = CreateHead();
+  head->parsed_headers->no_vary_search->search_variance =
+      network::mojom::SearchParamsVariance::NewNoVaryParams({});
+  head->parsed_headers->no_vary_search->vary_on_key_order = false;
+  const GURL test_url = GURL("https://a.com/away.html?a=2&b=3&c=6");
+
+  NoVarySearchHelper helper;
+  helper.AddUrl(test_url, *head);
+  const auto* urls_with_no_vary_search =
+      helper.GetAllForUrlWithoutRefAndQueryForTesting(test_url);
+  ASSERT_TRUE(urls_with_no_vary_search);
+  ASSERT_EQ(urls_with_no_vary_search->size(), 1u);
+  EXPECT_EQ(urls_with_no_vary_search->at(0).first, test_url);
+  EXPECT_TRUE(urls_with_no_vary_search->at(0).second.no_vary_params().empty());
+  EXPECT_TRUE(urls_with_no_vary_search->at(0).second.vary_params().empty());
+  EXPECT_TRUE(urls_with_no_vary_search->at(0).second.vary_by_default());
+  EXPECT_FALSE(urls_with_no_vary_search->at(0).second.vary_on_key_order());
+  EXPECT_FALSE(helper.MatchUrl(GURL("https://a.com/away.html?b=3")));
+  EXPECT_FALSE(helper.MatchUrl(GURL("https://a.com/away.html?b=3&a=4")));
+  EXPECT_FALSE(helper.MatchUrl(GURL("https://a.com/away.html")));
+  EXPECT_FALSE(helper.MatchUrl(GURL("https://a.com/away.html?b=4")));
+  EXPECT_FALSE(helper.MatchUrl(GURL("https://a.com/away.html?b=3&c=6&a=4")));
+  EXPECT_FALSE(
+      helper.MatchUrl(GURL("https://a.com/away.html?a=2&b=3&c=6&d=5")));
+  EXPECT_FALSE(
+      helper.MatchUrl(GURL("https://a.com/away.html?a=2&b=3&c=6&a=5")));
+  EXPECT_TRUE(helper.MatchUrl(GURL("https://a.com/away.html?b=3&c=6&a=2")));
+  EXPECT_TRUE(helper.MatchUrl(test_url));
+}
+
+TEST(NoVarySearchHelperTests, AddUrlWithoutNoVarySearchTest) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(
+      network::features::kPrefetchNoVarySearch);
+
+  network::mojom::URLResponseHead head;
+  head.parsed_headers = network::mojom::ParsedHeaders::New();
+
+  NoVarySearchHelper helper;
+  GURL test_url("https://a.com/index.html?a=2&b=3");
+  helper.AddUrl(test_url, head);
+
+  auto* urls_with_no_vary_search =
+      helper.GetAllForUrlWithoutRefAndQueryForTesting(test_url);
+  ASSERT_FALSE(urls_with_no_vary_search);
+  EXPECT_FALSE(helper.MatchUrl(test_url));
+}
+
+}  // namespace
+}  // namespace content
diff --git a/content/browser/preloading/prefetch/prefetch_container.cc b/content/browser/preloading/prefetch/prefetch_container.cc
index 6ee7ba0..7a5bbb2 100644
--- a/content/browser/preloading/prefetch/prefetch_container.cc
+++ b/content/browser/preloading/prefetch/prefetch_container.cc
@@ -236,6 +236,12 @@
   }
 }
 
+void PrefetchContainer::OnPrefetchedResponseHeadReceived() {
+  if (prefetch_document_manager_) {
+    prefetch_document_manager_->OnPrefetchedHeadReceived(GetURL());
+  }
+}
+
 void PrefetchContainer::OnPrefetchComplete() {
   if (!loader_)
     return;
@@ -276,11 +282,13 @@
   DCHECK(!prefetched_response_);
   DCHECK(!is_decoy_);
 
-  if (prefetch_document_manager_)
-    prefetch_document_manager_->OnPrefetchSuccessful();
-
   prefetch_received_time_ = base::TimeTicks::Now();
   prefetched_response_ = std::move(prefetched_response);
+
+  OnPrefetchedResponseHeadReceived();
+  if (prefetch_document_manager_) {
+    prefetch_document_manager_->OnPrefetchSuccessful();
+  }
 }
 
 std::unique_ptr<PrefetchedMainframeResponseContainer>
@@ -289,4 +297,8 @@
   return std::move(prefetched_response_);
 }
 
+const network::mojom::URLResponseHead* PrefetchContainer::GetHead() {
+  return prefetched_response_ ? prefetched_response_->GetHead() : nullptr;
+}
+
 }  // namespace content
diff --git a/content/browser/preloading/prefetch/prefetch_container.h b/content/browser/preloading/prefetch/prefetch_container.h
index d1f8dc4..7f10f8a 100644
--- a/content/browser/preloading/prefetch/prefetch_container.h
+++ b/content/browser/preloading/prefetch/prefetch_container.h
@@ -160,6 +160,11 @@
   // Whether or not |this| has a prefetched response.
   bool HasValidPrefetchedResponse(base::TimeDelta cacheable_duration) const;
 
+  // Called when |this| has received prefetched response's head.
+  // Once this is called, we should be able to call GetHead() and receive a
+  // non-null result.
+  void OnPrefetchedResponseHeadReceived();
+
   // |this| takes ownership of the given |prefetched_response|.
   void TakePrefetchedResponse(
       std::unique_ptr<PrefetchedMainframeResponseContainer>
@@ -170,6 +175,10 @@
   std::unique_ptr<PrefetchedMainframeResponseContainer>
   ReleasePrefetchedResponse();
 
+  // Returns the head of the prefetched response. If there is no valid response,
+  // then returns null.
+  const network::mojom::URLResponseHead* GetHead();
+
   // Returns the time between the prefetch request was sent and the time the
   // response headers were received. Not set if the prefetch request hasn't been
   // sent or the response headers haven't arrived.
diff --git a/content/browser/preloading/prefetch/prefetch_container_unittest.cc b/content/browser/preloading/prefetch/prefetch_container_unittest.cc
index b7b7305f..c81321f 100644
--- a/content/browser/preloading/prefetch/prefetch_container_unittest.cc
+++ b/content/browser/preloading/prefetch/prefetch_container_unittest.cc
@@ -107,6 +107,7 @@
   EXPECT_EQ(prefetch_container.GetPrefetchContainerKey(),
             std::make_pair(GlobalRenderFrameHostId(1234, 5678),
                            GURL("https://test.com")));
+  EXPECT_FALSE(prefetch_container.GetHead());
 }
 
 TEST_F(PrefetchContainerTest, PrefetchStatus) {
@@ -154,6 +155,7 @@
 
   EXPECT_FALSE(prefetch_container.HasValidPrefetchedResponse(base::Minutes(1)));
   EXPECT_TRUE(prefetch_container.HasValidPrefetchedResponse(base::Minutes(3)));
+  EXPECT_TRUE(prefetch_container.GetHead());
 }
 
 TEST_F(PrefetchContainerTest, CookieListener) {
diff --git a/content/browser/preloading/prefetch/prefetch_document_manager.cc b/content/browser/preloading/prefetch/prefetch_document_manager.cc
index 2e0a5629..aea0044 100644
--- a/content/browser/preloading/prefetch/prefetch_document_manager.cc
+++ b/content/browser/preloading/prefetch/prefetch_document_manager.cc
@@ -5,6 +5,7 @@
 #include "content/browser/preloading/prefetch/prefetch_document_manager.h"
 
 #include <algorithm>
+#include <memory>
 #include <tuple>
 #include <vector>
 
@@ -20,6 +21,10 @@
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_contents_observer.h"
+#include "net/http/http_no_vary_search_data.h"
+#include "services/network/public/cpp/features.h"
+#include "services/network/public/mojom/no_vary_search.mojom.h"
+#include "services/network/public/mojom/url_response_head.mojom.h"
 #include "url/origin.h"
 
 namespace content {
@@ -41,6 +46,7 @@
     return;
 
   for (const auto& prefetch_iter : owned_prefetches_) {
+    DCHECK(prefetch_iter.second);
     prefetch_service->RemovePrefetch(
         prefetch_iter.second->GetPrefetchContainerKey());
   }
@@ -71,10 +77,26 @@
   serving_page_metrics_container->SetSameTabAsPrefetchingTab(true);
 
   // Get the prefetch for the URL being navigated to. If there is no prefetch
-  // for that URL, then stop.
+  // for that URL, then check if there is an equivalent prefetch using
+  // No-Vary-Search equivalence. If there is not then stop.
   auto prefetch_iter = all_prefetches_.find(navigation_handle->GetURL());
-  if (prefetch_iter == all_prefetches_.end() || !prefetch_iter->second)
-    return;
+  if (prefetch_iter == all_prefetches_.end() || !prefetch_iter->second) {
+    if (!base::FeatureList::IsEnabled(
+            network::features::kPrefetchNoVarySearch)) {
+      return;
+    }
+    const auto no_vary_search_match_url =
+        GetNoVarySearchHelper().MatchUrl(navigation_handle->GetURL());
+    if (!no_vary_search_match_url.has_value()) {
+      return;
+    }
+    // Find the prefetched url matching |navigation_handle->GetURL()| based on
+    // No-Vary-Search in |all_prefetches_|.
+    prefetch_iter = all_prefetches_.find(no_vary_search_match_url.value());
+    if (prefetch_iter == all_prefetches_.end() || !prefetch_iter->second) {
+      return;
+    }
+  }
 
   // If this prefetch has already been used with another navigation then stop.
   if (prefetch_iter->second->HasPrefetchBeenConsideredToServe())
@@ -90,7 +112,13 @@
   }
 
   // Inform |PrefetchService| of the navigation to the prefetch.
-  GetPrefetchService()->PrepareToServe(prefetch_iter->second);
+  // |navigation_handle->GetURL()| and |prefetched_iter->second->GetURL()|
+  // might be different but be equivalent under No-Vary-Search.
+  PrefetchService* prefetch_service = GetPrefetchService();
+  if (prefetch_service) {
+    prefetch_service->PrepareToServe(navigation_handle->GetURL(),
+                                     prefetch_iter->second);
+  }
 }
 
 void PrefetchDocumentManager::ProcessCandidates(
@@ -176,7 +204,10 @@
 
   // Send a reference of the new |PrefetchContainer| to |PrefetchService| to
   // start the prefetch process.
-  GetPrefetchService()->PrefetchUrl(weak_container);
+  PrefetchService* prefetch_service = GetPrefetchService();
+  if (prefetch_service) {
+    prefetch_service->PrefetchUrl(weak_container);
+  }
 }
 
 std::unique_ptr<PrefetchContainer>
@@ -259,11 +290,31 @@
       ->GetPrefetchService();
 }
 
+const NoVarySearchHelper& PrefetchDocumentManager::GetNoVarySearchHelper()
+    const {
+  return no_vary_search_helper_;
+}
+
 void PrefetchDocumentManager::OnEligibilityCheckComplete(bool is_eligible) {
   if (is_eligible)
     referring_page_metrics_.prefetch_eligible_count++;
 }
 
+void PrefetchDocumentManager::OnPrefetchedHeadReceived(const GURL& url) {
+  if (!base::FeatureList::IsEnabled(network::features::kPrefetchNoVarySearch)) {
+    return;
+  }
+  // Find the PrefetchContainer associated with |url|.
+  const auto it = all_prefetches_.find(url);
+  if (it == all_prefetches_.end() || !it->second) {
+    return;
+  }
+
+  const auto* head = it->second->GetHead();
+  DCHECK(head);
+  no_vary_search_helper_.AddUrl(url, *head);
+}
+
 void PrefetchDocumentManager::OnPrefetchSuccessful() {
   referring_page_metrics_.prefetch_successful_count++;
 }
diff --git a/content/browser/preloading/prefetch/prefetch_document_manager.h b/content/browser/preloading/prefetch/prefetch_document_manager.h
index d149616..aa0a13f 100644
--- a/content/browser/preloading/prefetch/prefetch_document_manager.h
+++ b/content/browser/preloading/prefetch/prefetch_document_manager.h
@@ -10,12 +10,14 @@
 #include <vector>
 
 #include "base/memory/weak_ptr.h"
+#include "content/browser/preloading/prefetch/no_vary_search_helper.h"
 #include "content/browser/preloading/prefetch/prefetch_type.h"
 #include "content/common/content_export.h"
 #include "content/public/browser/document_user_data.h"
 #include "content/public/browser/prefetch_metrics.h"
 #include "content/public/browser/speculation_host_delegate.h"
 #include "content/public/browser/web_contents_observer.h"
+#include "net/http/http_no_vary_search_data.h"
 #include "third_party/blink/public/mojom/speculation_rules/speculation_rules.mojom.h"
 #include "url/gurl.h"
 
@@ -81,6 +83,9 @@
   // page load is completed.
   void OnEligibilityCheckComplete(bool is_eligible);
 
+  // Called when the head is available in the prefetched response.
+  void OnPrefetchedHeadReceived(const GURL& url);
+
   // Updates metrics when the response for a prefetch requested by this page
   // load is received.
   void OnPrefetchSuccessful();
@@ -88,6 +93,9 @@
   // Whether the prefetch attempt for target |url| failed or discarded
   bool IsPrefetchAttemptFailedOrDiscarded(const GURL& url);
 
+  // Helper function to get the |NoVarySearchHelper| associated with |this|.
+  const NoVarySearchHelper& GetNoVarySearchHelper() const;
+
   static void SetPrefetchServiceForTesting(PrefetchService* prefetch_service);
 
  private:
@@ -117,6 +125,10 @@
   // Metrics related to the prefetches requested by this page load.
   PrefetchReferringPageMetrics referring_page_metrics_;
 
+  // NoVarySearchHelper that manages NoVarySearch data and url matching.
+  // Used through the getter GetNoVarySearchHelper
+  NoVarySearchHelper no_vary_search_helper_;
+
   base::WeakPtrFactory<PrefetchDocumentManager> weak_method_factory_{this};
 
   DOCUMENT_USER_DATA_KEY_DECL();
diff --git a/content/browser/preloading/prefetch/prefetch_document_manager_unittest.cc b/content/browser/preloading/prefetch/prefetch_document_manager_unittest.cc
index 0bb98b8..002337f6 100644
--- a/content/browser/preloading/prefetch/prefetch_document_manager_unittest.cc
+++ b/content/browser/preloading/prefetch/prefetch_document_manager_unittest.cc
@@ -4,16 +4,21 @@
 
 #include "content/browser/preloading/prefetch/prefetch_document_manager.h"
 
+#include <memory>
 #include <string>
 #include <vector>
 
 #include "base/test/scoped_feature_list.h"
 #include "content/browser/preloading/prefetch/prefetch_features.h"
 #include "content/browser/preloading/prefetch/prefetch_service.h"
+#include "content/browser/preloading/prefetch/prefetched_mainframe_response_container.h"
+#include "content/public/test/navigation_simulator.h"
 #include "content/public/test/test_browser_context.h"
 #include "content/test/test_web_contents.h"
+#include "services/network/public/cpp/features.h"
 #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
 #include "services/network/test/test_url_loader_factory.h"
+#include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/mojom/loader/referrer.mojom.h"
 #include "third_party/blink/public/mojom/speculation_rules/speculation_rules.mojom.h"
@@ -21,6 +26,9 @@
 namespace content {
 namespace {
 
+using testing::IsEmpty;
+using testing::UnorderedElementsAreArray;
+
 class TestPrefetchService : public PrefetchService {
  public:
   explicit TestPrefetchService(BrowserContext* browser_context)
@@ -31,7 +39,15 @@
     prefetches_.push_back(prefetch_container);
   }
 
+  void PrepareToServe(
+      const GURL& url,
+      base::WeakPtr<PrefetchContainer> prefetch_container) override {
+    prefetches_prepared_to_serve_.emplace_back(url, prefetch_container);
+  }
+
   std::vector<base::WeakPtr<PrefetchContainer>> prefetches_;
+  std::vector<std::pair<GURL, base::WeakPtr<PrefetchContainer>>>
+      prefetches_prepared_to_serve_;
 };
 
 class PrefetchDocumentManagerTest : public RenderViewHostTestHarness {
@@ -76,10 +92,23 @@
     return GURL("https://other.example.com" + path);
   }
 
+  void NavigateMainframeRendererTo(const GURL& url) {
+    std::unique_ptr<NavigationSimulator> simulator =
+        content::NavigationSimulator::CreateRendererInitiated(
+            url, &GetPrimaryMainFrame());
+    simulator->SetTransition(ui::PAGE_TRANSITION_LINK);
+    simulator->Start();
+  }
+
   const std::vector<base::WeakPtr<PrefetchContainer>>& GetPrefetches() {
     return prefetch_service_->prefetches_;
   }
 
+  const std::vector<std::pair<GURL, base::WeakPtr<PrefetchContainer>>>&
+  GetPrefetchesPreparedToServe() {
+    return prefetch_service_->prefetches_prepared_to_serve_;
+  }
+
  private:
   base::test::ScopedFeatureList scoped_feature_list_;
 
@@ -88,6 +117,130 @@
   std::unique_ptr<TestPrefetchService> prefetch_service_;
 };
 
+TEST_F(PrefetchDocumentManagerTest, ProcessNoVarySearchResponse) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(
+      network::features::kPrefetchNoVarySearch);
+  // Used to create responses.
+  const net::IsolationInfo info;
+  // Process the candidates with the |PrefetchDocumentManager| for the current
+  // document.
+  auto* prefetch_document_manager =
+      PrefetchDocumentManager::GetOrCreateForCurrentDocument(
+          &GetPrimaryMainFrame());
+  {
+    // Create list of SpeculationCandidatePtrs.
+    std::vector<blink::mojom::SpeculationCandidatePtr> candidates;
+    // Create candidate for private cross-origin prefetch. This candidate should
+    // be prefetched by |PrefetchDocumentManager|.
+    auto candidate1 = blink::mojom::SpeculationCandidate::New();
+    const auto test_url = GetCrossOriginUrl("/candidate1.html?a=2&b=3");
+    candidate1->action = blink::mojom::SpeculationAction::kPrefetch;
+    candidate1->requires_anonymous_client_ip_when_cross_origin = false;
+    candidate1->url = test_url;
+    candidate1->referrer = blink::mojom::Referrer::New();
+
+    candidates.push_back(std::move(candidate1));
+
+    prefetch_document_manager->ProcessCandidates(candidates,
+                                                 /*devtools_observer=*/nullptr);
+    const auto& helper = prefetch_document_manager->GetNoVarySearchHelper();
+
+    // Now call TakePrefetchedResponse
+    auto body = std::make_unique<std::string>("empty");
+    network::mojom::URLResponseHeadPtr head =
+        network::mojom::URLResponseHead::New();
+    head->parsed_headers = network::mojom::ParsedHeaders::New();
+    head->parsed_headers->no_vary_search = network::mojom::NoVarySearch::New();
+    head->parsed_headers->no_vary_search->vary_on_key_order = true;
+    head->parsed_headers->no_vary_search->search_variance =
+        network::mojom::SearchParamsVariance::NewVaryParams({"a"});
+
+    auto response = std::make_unique<PrefetchedMainframeResponseContainer>(
+        info, std::move(head), std::move(body));
+    GetPrefetches()[0]->TakePrefetchedResponse(std::move(response));
+
+    const auto* urls_with_no_vary_search =
+        helper.GetAllForUrlWithoutRefAndQueryForTesting(test_url);
+    ASSERT_TRUE(urls_with_no_vary_search);
+    ASSERT_EQ(urls_with_no_vary_search->size(), 1u);
+    EXPECT_EQ(urls_with_no_vary_search->at(0).first, test_url);
+    const net::HttpNoVarySearchData& nvs =
+        urls_with_no_vary_search->at(0).second;
+    EXPECT_THAT(nvs.vary_params(), UnorderedElementsAreArray({"a"}));
+    EXPECT_THAT(nvs.no_vary_params(), IsEmpty());
+    EXPECT_FALSE(nvs.vary_by_default());
+    EXPECT_TRUE(nvs.vary_on_key_order());
+    EXPECT_TRUE(
+        helper.MatchUrl(GetCrossOriginUrl("/candidate1.html?b=4&a=2&c=5")));
+    EXPECT_TRUE(helper.MatchUrl(GetCrossOriginUrl("/candidate1.html?a=2")));
+    EXPECT_FALSE(helper.MatchUrl(GetCrossOriginUrl("/candidate1.html")));
+    EXPECT_FALSE(helper.MatchUrl(GetCrossOriginUrl("/candidate1.html?b=4")));
+  }
+  {
+    const auto test_url = GetCrossOriginUrl("/candidate2.html?a=2&b=3");
+    auto candidate1 = blink::mojom::SpeculationCandidate::New();
+    candidate1->action = blink::mojom::SpeculationAction::kPrefetch;
+    candidate1->requires_anonymous_client_ip_when_cross_origin = false;
+    candidate1->url = test_url;
+    candidate1->referrer = blink::mojom::Referrer::New();
+
+    // Create list of SpeculationCandidatePtrs.
+    std::vector<blink::mojom::SpeculationCandidatePtr> candidates;
+    candidates.emplace_back(std::move(candidate1));
+    prefetch_document_manager->ProcessCandidates(candidates,
+                                                 /*devtools_observer=*/nullptr);
+
+    auto body = std::make_unique<std::string>("empty");
+    network::mojom::URLResponseHeadPtr head =
+        network::mojom::URLResponseHead::New();
+    head->parsed_headers = network::mojom::ParsedHeaders::New();
+
+    auto response = std::make_unique<PrefetchedMainframeResponseContainer>(
+        info, std::move(head), std::move(body));
+    GetPrefetches().back()->TakePrefetchedResponse(std::move(response));
+
+    const auto& helper = prefetch_document_manager->GetNoVarySearchHelper();
+    const auto* urls_with_no_vary_search =
+        helper.GetAllForUrlWithoutRefAndQueryForTesting(test_url);
+    ASSERT_FALSE(urls_with_no_vary_search);
+  }
+
+  NavigateMainframeRendererTo(GetCrossOriginUrl("/candidate2.html?a=2&b=3"));
+  EXPECT_EQ(GetPrefetchesPreparedToServe()[0].first,
+            GetCrossOriginUrl("/candidate2.html?a=2&b=3"));
+  EXPECT_EQ(GetCrossOriginUrl("/candidate2.html?a=2&b=3"),
+            GetPrefetchesPreparedToServe()[0].second->GetURL());
+
+  NavigateMainframeRendererTo(
+      GetCrossOriginUrl("/candidate1.html?b=4&a=2&c=5"));
+  EXPECT_EQ(GetPrefetchesPreparedToServe()[1].first,
+            GetCrossOriginUrl("/candidate1.html?b=4&a=2&c=5"));
+  EXPECT_EQ(GetPrefetchesPreparedToServe()[1].second->GetURL(),
+            GetCrossOriginUrl("/candidate1.html?a=2&b=3"));
+
+  NavigateMainframeRendererTo(
+      GetCrossOriginUrl("/not_prefetched.html?b=4&a=2&c=5"));
+  EXPECT_EQ(GetPrefetchesPreparedToServe().size(), 2u);
+
+  // Cover the case where we want to navigate again to the same prefetched
+  // Url.
+  // Simulate that we've already navigated to prefetched URL.
+  GetPrefetchesPreparedToServe()[0].second->OnNavigationToPrefetch();
+  // Try to navigate again to the same URL.
+  NavigateMainframeRendererTo(GetCrossOriginUrl("/candidate2.html?a=2&b=3"));
+  EXPECT_EQ(GetPrefetchesPreparedToServe().size(), 2u);
+
+  // Cover the case where we want to navigate to a URL with No-Vary-Search for
+  // which the PrefetchContainer WeakPtr is not valid anymore.
+  prefetch_document_manager->ReleasePrefetchContainer(
+      GetPrefetchesPreparedToServe()[1].second->GetURL());
+  DCHECK(!GetPrefetchesPreparedToServe()[1].second);
+  NavigateMainframeRendererTo(
+      GetCrossOriginUrl("/candidate1.html?b=4&a=2&c=5"));
+  EXPECT_EQ(GetPrefetchesPreparedToServe().size(), 2u);
+}
+
 TEST_F(PrefetchDocumentManagerTest, ProcessSpeculationCandidates) {
   // Create list of SpeculationCandidatePtrs.
   std::vector<blink::mojom::SpeculationCandidatePtr> candidates;
diff --git a/content/browser/preloading/prefetch/prefetch_service.cc b/content/browser/preloading/prefetch/prefetch_service.cc
index d83ff93b..ba63eaa 100644
--- a/content/browser/preloading/prefetch/prefetch_service.cc
+++ b/content/browser/preloading/prefetch/prefetch_service.cc
@@ -977,6 +977,7 @@
 }
 
 void PrefetchService::PrepareToServe(
+    const GURL& url,
     base::WeakPtr<PrefetchContainer> prefetch_container) {
   // Ensure |this| has this prefetch.
   if (all_prefetches_.find(prefetch_container->GetPrefetchContainerKey()) ==
@@ -997,12 +998,13 @@
 
   // If there is already a prefetch with the same URL as |prefetch_container| in
   // |prefetches_ready_to_serve_|, then don't do anything.
-  if (prefetches_ready_to_serve_.find(prefetch_container->GetURL()) !=
-      prefetches_ready_to_serve_.end())
+  if (prefetches_ready_to_serve_.find(url) !=
+      prefetches_ready_to_serve_.end()) {
     return;
+  }
 
   // Move prefetch into |prefetches_ready_to_serve_|.
-  prefetches_ready_to_serve_[prefetch_container->GetURL()] = prefetch_container;
+  prefetches_ready_to_serve_[url] = prefetch_container;
 
   // Start the process of copying cookies from the isolated network context used
   // to make the prefetch to the default network context.
diff --git a/content/browser/preloading/prefetch/prefetch_service.h b/content/browser/preloading/prefetch/prefetch_service.h
index e424dbd8..71d0af7d 100644
--- a/content/browser/preloading/prefetch/prefetch_service.h
+++ b/content/browser/preloading/prefetch/prefetch_service.h
@@ -74,9 +74,13 @@
 
   virtual void PrefetchUrl(base::WeakPtr<PrefetchContainer> prefetch_container);
 
-  // Called when a navigation to the URL associated with |prefetch_container| is
-  // likely to occur in the immediate future.
-  void PrepareToServe(base::WeakPtr<PrefetchContainer> prefetch_container);
+  // Called when a navigation to `url` that will be served by
+  // `prefetch_container` is likely to occur in the immediate future.
+  // |url| and |prefetch_container->GetURL()| might not be the same
+  // because of No-Vary-Search non-exact url match.
+  virtual void PrepareToServe(
+      const GURL& url,
+      base::WeakPtr<PrefetchContainer> prefetch_container);
 
   // Returns the prefetch with |url| that is ready to serve. In order for a
   // prefetch to be ready to serve, |PrepareToServe| must have been previously
diff --git a/content/browser/preloading/prefetch/prefetch_service_unittest.cc b/content/browser/preloading/prefetch/prefetch_service_unittest.cc
index ca518ee4..58bda80 100644
--- a/content/browser/preloading/prefetch/prefetch_service_unittest.cc
+++ b/content/browser/preloading/prefetch/prefetch_service_unittest.cc
@@ -25,6 +25,8 @@
 #include "content/test/test_content_browser_client.h"
 #include "net/base/load_flags.h"
 #include "net/base/proxy_server.h"
+#include "services/network/public/cpp/features.h"
+#include "services/network/public/cpp/parsed_headers.h"
 #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
 #include "services/network/public/mojom/cookie_manager.mojom.h"
 #include "services/network/public/mojom/network_context.mojom.h"
@@ -322,6 +324,11 @@
     for (const auto& header : headers) {
       head->headers->AddHeader(header.first, header.second);
     }
+    if (!head->parsed_headers) {
+      head->parsed_headers = network::PopulateParsedHeaders(
+          head->headers.get(), request->request.url);
+    }
+
     network::URLLoaderCompletionStatus status(net_error);
     test_url_loader_factory_.AddResponse(request->request.url, std::move(head),
                                          body, status);
@@ -484,9 +491,10 @@
             PrefetchStatus::kPrefetchSuccessful);
   EXPECT_TRUE(serveable_prefetch_container->HasValidPrefetchedResponse(
       base::TimeDelta::Max()));
-  EXPECT_TRUE(serveable_prefetch_container->ReleasePrefetchedResponse()
-                  ->ReleaseHead()
-                  ->was_in_prefetch_cache);
+  auto prefetched_response =
+      serveable_prefetch_container->ReleasePrefetchedResponse();
+  EXPECT_TRUE(prefetched_response->GetHead());
+  EXPECT_TRUE(prefetched_response->ReleaseHead()->was_in_prefetch_cache);
 }
 
 TEST_F(PrefetchServiceTest, NoPrefetchingPreloadingDisabled) {
@@ -2231,5 +2239,52 @@
 
 // TODO(https://crbug.com/1299059): Add test for incognito mode.
 
+class PrefetchServiceNoVarySearchTest : public PrefetchServiceTest {
+ public:
+  void InitScopedFeatureList() override {
+    scoped_feature_list_.InitWithFeatures(
+        {content::features::kPrefetchUseContentRefactor,
+         network::features::kPrefetchNoVarySearch},
+        {});
+  }
+};
+
+// TODO(crbug.com/1396460): Test flaky on lacros trybots.
+#if BUILDFLAG(IS_CHROMEOS)
+#define MAYBE_NoVarySearchSuccessCase DISABLED_NoVarySearchSuccessCase
+#else
+#define MAYBE_NoVarySearchSuccessCase NoVarySearchSuccessCase
+#endif
+TEST_F(PrefetchServiceNoVarySearchTest, MAYBE_NoVarySearchSuccessCase) {
+  MakePrefetchService(
+      std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
+
+  MakePrefetchOnMainFrame(GURL("https://example.com/?a=1"),
+                          PrefetchType(/*use_isolated_network_context=*/true,
+                                       /*use_prefetch_proxy=*/true));
+  base::RunLoop().RunUntilIdle();
+
+  VerifyCommonRequestState(GURL("https://example.com/?a=1"),
+                           /*use_prefetch_proxy=*/true);
+  MakeResponseAndWait(
+      net::HTTP_OK, net::OK, kHTMLMimeType,
+      /*use_prefetch_proxy=*/true,
+      {{"X-Testing", "Hello World"}, {"No-Vary-Search", R"(params=("a"))"}},
+      kHTMLBody);
+
+  Navigate(GURL("https://example.com"), main_rfh()->GetGlobalId());
+
+  base::WeakPtr<PrefetchContainer> serveable_prefetch_container =
+      prefetch_service_->GetPrefetchToServe(GURL("https://example.com"));
+  ASSERT_TRUE(serveable_prefetch_container);
+  EXPECT_EQ(serveable_prefetch_container->GetURL(),
+            GURL("https://example.com/?a=1"));
+  EXPECT_TRUE(serveable_prefetch_container->HasPrefetchStatus());
+  EXPECT_EQ(serveable_prefetch_container->GetPrefetchStatus(),
+            PrefetchStatus::kPrefetchSuccessful);
+  EXPECT_TRUE(serveable_prefetch_container->HasValidPrefetchedResponse(
+      base::TimeDelta::Max()));
+}
+
 }  // namespace
 }  // namespace content
diff --git a/content/browser/preloading/prefetch/prefetch_url_loader_interceptor.cc b/content/browser/preloading/prefetch/prefetch_url_loader_interceptor.cc
index 3fb1db2..9c098c2c 100644
--- a/content/browser/preloading/prefetch/prefetch_url_loader_interceptor.cc
+++ b/content/browser/preloading/prefetch/prefetch_url_loader_interceptor.cc
@@ -93,9 +93,18 @@
   DCHECK(!loader_callback_);
   loader_callback_ = std::move(callback);
   url_ = tenative_resource_request.url;
-
   base::WeakPtr<PrefetchContainer> prefetch_container = GetPrefetch(url_);
-  DCHECK(!prefetch_container || prefetch_container->GetURL() == url_);
+// The |url_| might be different from |prefetch_container->GetURL()| because
+// of No-Vary-Search non-exact url match.
+#if DCHECK_IS_ON()
+  if (prefetch_container) {
+    GURL::Replacements replacements;
+    replacements.ClearRef();
+    replacements.ClearQuery();
+    DCHECK_EQ(url_.ReplaceComponents(replacements),
+              prefetch_container->GetURL().ReplaceComponents(replacements));
+  }
+#endif
   if (!prefetch_container ||
       !prefetch_container->HasValidPrefetchedResponse(
           PrefetchCacheableDuration()) ||
diff --git a/content/browser/preloading/prefetch/prefetched_mainframe_response_container.h b/content/browser/preloading/prefetch/prefetched_mainframe_response_container.h
index 6cfc8ea..e710581 100644
--- a/content/browser/preloading/prefetch/prefetched_mainframe_response_container.h
+++ b/content/browser/preloading/prefetch/prefetched_mainframe_response_container.h
@@ -28,6 +28,9 @@
 
   const net::IsolationInfo& isolation_info() { return isolation_info_; }
 
+  // Returns reference to the response head.
+  const network::mojom::URLResponseHead* GetHead() { return head_.get(); }
+
   // Releases the ownership of the response head from |this| and gives it to the
   // caller.
   network::mojom::URLResponseHeadPtr ReleaseHead();
diff --git a/content/browser/renderer_host/frame_tree_node.cc b/content/browser/renderer_host/frame_tree_node.cc
index 44d026d..f1ca7b5 100644
--- a/content/browser/renderer_host/frame_tree_node.cc
+++ b/content/browser/renderer_host/frame_tree_node.cc
@@ -542,7 +542,7 @@
   render_frame_host->SetNavigationRequest(std::move(navigation_request_));
 }
 
-void FrameTreeNode::CreatedNavigationRequest(
+void FrameTreeNode::TakeNavigationRequest(
     std::unique_ptr<NavigationRequest> navigation_request) {
   // This is never called when navigating to a Javascript URL. For the loading
   // state, this matches what Blink is doing: Blink doesn't send throbber
diff --git a/content/browser/renderer_host/frame_tree_node.h b/content/browser/renderer_host/frame_tree_node.h
index 351111e..125db57 100644
--- a/content/browser/renderer_host/frame_tree_node.h
+++ b/content/browser/renderer_host/frame_tree_node.h
@@ -316,7 +316,7 @@
   // NavigationRequest of this frame. This corresponds to the start of a new
   // navigation. If there was an ongoing navigation request before calling this
   // function, it is canceled. |navigation_request| should not be null.
-  void CreatedNavigationRequest(
+  void TakeNavigationRequest(
       std::unique_ptr<NavigationRequest> navigation_request);
 
   // Resets the navigation request owned by `this` (which shouldn't have reached
diff --git a/content/browser/renderer_host/media/audio_output_authorization_handler.cc b/content/browser/renderer_host/media/audio_output_authorization_handler.cc
index eab92ca..c0a9d37 100644
--- a/content/browser/renderer_host/media/audio_output_authorization_handler.cc
+++ b/content/browser/renderer_host/media/audio_output_authorization_handler.cc
@@ -280,23 +280,17 @@
     const std::string& device_id,
     const std::string& salt,
     const url::Origin& security_origin,
-    media::mojom::DeviceEnumerationResult result,
     const MediaDeviceEnumeration& enumeration) const {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
   DCHECK(!media::AudioDeviceDescription::IsDefaultDevice(device_id));
 
-  // TODO(crbug.com/1313822): Propagate errors rather than acting as if there
-  // are no devices.
-  if (result == media::mojom::DeviceEnumerationResult::kSuccess) {
-    for (const blink::WebMediaDeviceInfo& device_info :
-         enumeration[static_cast<size_t>(
-             MediaDeviceType::MEDIA_AUDIO_OUTPUT)]) {
-      if (DoesMediaDeviceIDMatchHMAC(salt, security_origin, device_id,
-                                     device_info.device_id)) {
-        GetDeviceParameters(std::move(trace_scope), std::move(cb),
-                            device_info.device_id);
-        return;
-      }
+  for (const blink::WebMediaDeviceInfo& device_info :
+       enumeration[static_cast<size_t>(MediaDeviceType::MEDIA_AUDIO_OUTPUT)]) {
+    if (DoesMediaDeviceIDMatchHMAC(salt, security_origin, device_id,
+                                   device_info.device_id)) {
+      GetDeviceParameters(std::move(trace_scope), std::move(cb),
+                          device_info.device_id);
+      return;
     }
   }
 
diff --git a/content/browser/renderer_host/media/audio_output_authorization_handler.h b/content/browser/renderer_host/media/audio_output_authorization_handler.h
index 9dbd4d7f..e17ee7f 100644
--- a/content/browser/renderer_host/media/audio_output_authorization_handler.h
+++ b/content/browser/renderer_host/media/audio_output_authorization_handler.h
@@ -100,7 +100,6 @@
                          const std::string& device_id,
                          const std::string& salt,
                          const url::Origin& security_origin,
-                         media::mojom::DeviceEnumerationResult result,
                          const MediaDeviceEnumeration& enumeration) const;
 
   void GetDeviceParameters(std::unique_ptr<TraceScope> trace_scope,
diff --git a/content/browser/renderer_host/media/audio_output_authorization_handler_unittest.cc b/content/browser/renderer_host/media/audio_output_authorization_handler_unittest.cc
index 5495d142..01e4296 100644
--- a/content/browser/renderer_host/media/audio_output_authorization_handler_unittest.cc
+++ b/content/browser/renderer_host/media/audio_output_authorization_handler_unittest.cc
@@ -161,14 +161,13 @@
     media_stream_manager_->media_devices_manager()->EnumerateDevices(
         devices_to_enumerate,
         base::BindOnce(
-            [](std::string* out, media::mojom::DeviceEnumerationResult,
-               const MediaDeviceEnumeration& enumeration) {
+            [](std::string* out, const MediaDeviceEnumeration& result) {
               // Index 0 is default, so use 1.
-              CHECK(enumeration[static_cast<size_t>(
-                                    MediaDeviceType::MEDIA_AUDIO_OUTPUT)]
+              CHECK(result[static_cast<size_t>(
+                               MediaDeviceType::MEDIA_AUDIO_OUTPUT)]
                         .size() > 1)
                   << "Expected to have a nondefault device.";
-              *out = enumeration[static_cast<size_t>(
+              *out = result[static_cast<size_t>(
                   MediaDeviceType::MEDIA_AUDIO_OUTPUT)][1]
                          .device_id;
             },
diff --git a/content/browser/renderer_host/media/media_devices_dispatcher_host.cc b/content/browser/renderer_host/media/media_devices_dispatcher_host.cc
index d989295..cf0d5446 100644
--- a/content/browser/renderer_host/media/media_devices_dispatcher_host.cc
+++ b/content/browser/renderer_host/media/media_devices_dispatcher_host.cc
@@ -377,16 +377,8 @@
     GetVideoInputCapabilitiesCallback client_callback,
     const MediaDeviceSaltAndOrigin& salt_and_origin,
     const std::string& default_device_id,
-    media::mojom::DeviceEnumerationResult result,
     const MediaDeviceEnumeration& enumeration) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  // TODO(crbug.com/1313822): Propagate errors rather than acting as if there
-  // are no devices.
-  if (result != media::mojom::DeviceEnumerationResult::kSuccess) {
-    std::move(client_callback).Run({});
-    return;
-  }
-
   std::vector<blink::mojom::VideoInputDeviceCapabilitiesPtr>
       video_input_capabilities;
   for (const auto& device_info :
@@ -496,31 +488,24 @@
 
 void MediaDevicesDispatcherHost::GotAudioInputEnumeration(
     const std::string& default_device_id,
-    media::mojom::DeviceEnumerationResult result,
     const MediaDeviceEnumeration& enumeration) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
   DCHECK_GT(pending_audio_input_capabilities_requests_.size(), 0U);
   DCHECK(current_audio_input_capabilities_.empty());
   DCHECK_EQ(num_pending_audio_input_parameters_, 0U);
-
-  // TODO(crbug.com/1313822): Propagate errors rather than acting as if there
-  // are no devices.
-  if (result == media::mojom::DeviceEnumerationResult::kSuccess) {
-    for (const auto& device_info :
-         enumeration[static_cast<size_t>(MediaDeviceType::MEDIA_AUDIO_INPUT)]) {
-      auto parameters = media::AudioParameters::UnavailableDeviceParams();
-      blink::mojom::AudioInputDeviceCapabilities capabilities(
-          device_info.device_id, device_info.group_id, parameters,
-          parameters.IsValid(), parameters.channels(), parameters.sample_rate(),
-          parameters.GetBufferDuration());
-      if (device_info.device_id == default_device_id)
-        current_audio_input_capabilities_.insert(
-            current_audio_input_capabilities_.begin(), std::move(capabilities));
-      else
-        current_audio_input_capabilities_.push_back(std::move(capabilities));
-    }
+  for (const auto& device_info :
+       enumeration[static_cast<size_t>(MediaDeviceType::MEDIA_AUDIO_INPUT)]) {
+    auto parameters = media::AudioParameters::UnavailableDeviceParams();
+    blink::mojom::AudioInputDeviceCapabilities capabilities(
+        device_info.device_id, device_info.group_id, parameters,
+        parameters.IsValid(), parameters.channels(), parameters.sample_rate(),
+        parameters.GetBufferDuration());
+    if (device_info.device_id == default_device_id)
+      current_audio_input_capabilities_.insert(
+          current_audio_input_capabilities_.begin(), std::move(capabilities));
+    else
+      current_audio_input_capabilities_.push_back(std::move(capabilities));
   }
-
   // No devices or fake devices, no need to read audio parameters.
   if (current_audio_input_capabilities_.empty() ||
       base::CommandLine::ForCurrentProcess()->HasSwitch(
diff --git a/content/browser/renderer_host/media/media_devices_dispatcher_host.h b/content/browser/renderer_host/media/media_devices_dispatcher_host.h
index f000195f..6ded7c8 100644
--- a/content/browser/renderer_host/media/media_devices_dispatcher_host.h
+++ b/content/browser/renderer_host/media/media_devices_dispatcher_host.h
@@ -95,7 +95,6 @@
       GetVideoInputCapabilitiesCallback client_callback,
       const MediaDeviceSaltAndOrigin& salt_and_origin,
       const std::string& default_device_id,
-      media::mojom::DeviceEnumerationResult result,
       const MediaDeviceEnumeration& enumeration);
 
   void GetDefaultAudioInputDeviceID(
@@ -105,7 +104,6 @@
   void GotDefaultAudioInputDeviceID(const std::string& default_device_id);
 
   void GotAudioInputEnumeration(const std::string& default_device_id,
-                                media::mojom::DeviceEnumerationResult result,
                                 const MediaDeviceEnumeration& enumeration);
 
   void GotAudioInputParameters(
diff --git a/content/browser/renderer_host/media/media_devices_dispatcher_host_unittest.cc b/content/browser/renderer_host/media/media_devices_dispatcher_host_unittest.cc
index c9ab4e0..fae96668 100644
--- a/content/browser/renderer_host/media/media_devices_dispatcher_host_unittest.cc
+++ b/content/browser/renderer_host/media/media_devices_dispatcher_host_unittest.cc
@@ -61,12 +61,9 @@
 
 const auto kIgnoreLogMessageCB = base::DoNothing();
 
-void PhysicalDevicesEnumerated(
-    base::OnceClosure quit_closure,
-    MediaDeviceEnumeration* out,
-    media::mojom::DeviceEnumerationResult result_code,
-    const MediaDeviceEnumeration& enumeration) {
-  CHECK_EQ(result_code, media::mojom::DeviceEnumerationResult::kSuccess);
+void PhysicalDevicesEnumerated(base::OnceClosure quit_closure,
+                               MediaDeviceEnumeration* out,
+                               const MediaDeviceEnumeration& enumeration) {
   *out = enumeration;
   std::move(quit_closure).Run();
 }
@@ -311,11 +308,14 @@
   }
 
  protected:
-  void DevicesEnumerated(base::OnceClosure closure_after,
-                         EnumerationResponsePtr response) {
-    CHECK_EQ(response->result_code,
-             media::mojom::DeviceEnumerationResult::kSuccess);
-    enumerated_devices_ = response->enumeration;
+  void DevicesEnumerated(
+      base::OnceClosure closure_after,
+      const std::vector<std::vector<blink::WebMediaDeviceInfo>>& devices,
+      std::vector<blink::mojom::VideoInputDeviceCapabilitiesPtr>
+          video_input_capabilities,
+      std::vector<blink::mojom::AudioInputDeviceCapabilitiesPtr>
+          audio_input_capabilities) {
+    enumerated_devices_ = devices;
     std::move(closure_after).Run();
   }
 
diff --git a/content/browser/renderer_host/media/media_devices_manager.cc b/content/browser/renderer_host/media/media_devices_manager.cc
index 9ed9d23..24a9354 100644
--- a/content/browser/renderer_host/media/media_devices_manager.cc
+++ b/content/browser/renderer_host/media/media_devices_manager.cc
@@ -50,8 +50,6 @@
 
 namespace {
 
-using media::mojom::DeviceEnumerationResult;
-
 // Resolutions used if the source doesn't support capability enumeration.
 struct {
   uint16_t width;
@@ -419,6 +417,7 @@
       DoEnumerateDevices(static_cast<MediaDeviceType>(i));
     }
   }
+
   if (all_results_cached)
     ProcessRequests();
 }
@@ -714,20 +713,9 @@
     EnumerateDevicesCallback callback,
     const MediaDeviceSaltAndOrigin& salt_and_origin,
     const MediaDevicesManager::BoolDeviceTypes& has_permissions,
-    DeviceEnumerationResult enumeration_result,
     const MediaDeviceEnumeration& enumeration) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
 
-  if (enumeration_result != DeviceEnumerationResult::kSuccess) {
-    SendLogMessage(base::StringPrintf(
-        "OnDevicesEnumerated failing request with error code %d",
-        enumeration_result));
-    EnumerationResponsePtr response = blink::mojom::EnumerationResponse::New();
-    response->result_code = enumeration_result;
-    std::move(callback).Run(std::move(response));
-    return;
-  }
-
   const bool video_input_capabilities_requested =
       has_permissions[static_cast<size_t>(
           MediaDeviceType::MEDIA_VIDEO_INPUT)] &&
@@ -864,22 +852,18 @@
 
 void MediaDevicesManager::FinalizeDevicesEnumerated(
     EnumerationState enumeration_state) {
-  EnumerationResponsePtr response = blink::mojom::EnumerationResponse::New();
-  response->result_code = DeviceEnumerationResult::kSuccess;
-  response->video_input_device_capabilities =
-      enumeration_state.video_input_capabilities_requested
-          ? ComputeVideoInputCapabilities(
-                enumeration_state.raw_enumeration_results[static_cast<size_t>(
-                    MediaDeviceType::MEDIA_VIDEO_INPUT)],
-                enumeration_state
-                    .hashed_enumeration_results[static_cast<size_t>(
-                        MediaDeviceType::MEDIA_VIDEO_INPUT)])
-          : std::vector<VideoInputDeviceCapabilitiesPtr>();
-  response->audio_input_device_capabilities =
-      std::move(enumeration_state.audio_capabilities);
-  response->enumeration =
-      std::move(enumeration_state.hashed_enumeration_results);
-  std::move(enumeration_state.completion_cb).Run(std::move(response));
+  std::move(enumeration_state.completion_cb)
+      .Run(std::move(enumeration_state.hashed_enumeration_results),
+           enumeration_state.video_input_capabilities_requested
+               ? ComputeVideoInputCapabilities(
+                     enumeration_state
+                         .raw_enumeration_results[static_cast<size_t>(
+                             MediaDeviceType::MEDIA_VIDEO_INPUT)],
+                     enumeration_state
+                         .hashed_enumeration_results[static_cast<size_t>(
+                             MediaDeviceType::MEDIA_VIDEO_INPUT)])
+               : std::vector<VideoInputDeviceCapabilitiesPtr>(),
+           std::move(enumeration_state.audio_capabilities));
 }
 
 std::vector<VideoInputDeviceCapabilitiesPtr>
@@ -945,30 +929,18 @@
 }
 
 void MediaDevicesManager::VideoInputDevicesEnumerated(
-    DeviceEnumerationResult result_code,
+    media::mojom::DeviceEnumerationResult result_code,
     const media::VideoCaptureDeviceDescriptors& descriptors) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  if (result_code != DeviceEnumerationResult::kSuccess) {
+  if (result_code != media::mojom::DeviceEnumerationResult::kSuccess) {
     std::string log_message = base::StringPrintf(
         "VideoInputDevicesEnumerated got error %d", result_code);
     // Log to both WebRTC logs (for feedback reports) and text logs for
     // manually-collected chrome logs at customers.
     SendLogMessage(log_message);
     VLOG(1) << log_message;
-
-    // Fail all pending requests for video devices.
-    base::EraseIf(requests_, [result_code](EnumerationRequest& request) {
-      if (request.requested[static_cast<size_t>(
-              MediaDeviceType::MEDIA_VIDEO_INPUT)]) {
-        std::move(request.callback).Run(result_code, {});
-        return true;
-      }
-      return false;
-    });
-    cache_infos_[static_cast<size_t>(MediaDeviceType::MEDIA_VIDEO_INPUT)]
-        .UpdateCompleted();
-
-    return;
+    // TODO(crbug.com/1313822): Propagate this as an error response to the
+    // page and expose in the JS API.
   }
   blink::WebMediaDeviceInfoArray snapshot;
   for (const auto& descriptor : descriptors) {
@@ -1101,8 +1073,7 @@
 
   base::EraseIf(requests_, [this](EnumerationRequest& request) {
     if (IsEnumerationRequestReady(request)) {
-      std::move(request.callback)
-          .Run(DeviceEnumerationResult::kSuccess, current_snapshot_);
+      std::move(request.callback).Run(current_snapshot_);
       return true;
     }
     return false;
diff --git a/content/browser/renderer_host/media/media_devices_manager.h b/content/browser/renderer_host/media/media_devices_manager.h
index 7716622..68afbf2 100644
--- a/content/browser/renderer_host/media/media_devices_manager.h
+++ b/content/browser/renderer_host/media/media_devices_manager.h
@@ -29,7 +29,6 @@
 #include "third_party/blink/public/mojom/mediastream/media_devices.mojom.h"
 
 using blink::mojom::AudioInputDeviceCapabilitiesPtr;
-using blink::mojom::EnumerationResponsePtr;
 using blink::mojom::MediaDeviceType;
 using blink::mojom::VideoInputDeviceCapabilitiesPtr;
 
@@ -65,10 +64,11 @@
   };
 
   using EnumerationCallback =
-      base::OnceCallback<void(media::mojom::DeviceEnumerationResult,
-                              const MediaDeviceEnumeration&)>;
-  using EnumerateDevicesCallback =
-      base::OnceCallback<void(EnumerationResponsePtr)>;
+      base::OnceCallback<void(const MediaDeviceEnumeration&)>;
+  using EnumerateDevicesCallback = base::OnceCallback<void(
+      const std::vector<blink::WebMediaDeviceInfoArray>&,
+      std::vector<VideoInputDeviceCapabilitiesPtr>,
+      std::vector<AudioInputDeviceCapabilitiesPtr>)>;
   using StopRemovedInputDeviceCallback = base::RepeatingCallback<void(
       MediaDeviceType type,
       const blink::WebMediaDeviceInfo& media_device_info)>;
@@ -241,7 +241,6 @@
       EnumerateDevicesCallback callback,
       const MediaDeviceSaltAndOrigin& salt_and_origin,
       const MediaDevicesManager::BoolDeviceTypes& has_permissions,
-      media::mojom::DeviceEnumerationResult enumeration_result,
       const MediaDeviceEnumeration& enumeration);
   void GetAudioInputCapabilities(
       bool request_video_input_capabilities,
diff --git a/content/browser/renderer_host/media/media_devices_manager_unittest.cc b/content/browser/renderer_host/media/media_devices_manager_unittest.cc
index ddbf3e83..ae677110 100644
--- a/content/browser/renderer_host/media/media_devices_manager_unittest.cc
+++ b/content/browser/renderer_host/media/media_devices_manager_unittest.cc
@@ -18,7 +18,6 @@
 #include "build/build_config.h"
 #include "content/browser/media/media_devices_permission_checker.h"
 #include "content/browser/renderer_host/media/in_process_video_capture_provider.h"
-#include "content/browser/renderer_host/media/mock_video_capture_provider.h"
 #include "content/browser/renderer_host/media/video_capture_manager.h"
 #include "content/public/test/browser_task_environment.h"
 #include "media/audio/audio_device_name.h"
@@ -44,8 +43,6 @@
 
 namespace {
 
-using media::mojom::DeviceEnumerationResult;
-
 const int kRenderProcessId = 1;
 const int kRenderFrameId = 3;
 
@@ -244,8 +241,7 @@
 
   ~MediaDevicesManagerTest() override { audio_manager_->Shutdown(); }
 
-  MOCK_METHOD2(MockCallback,
-               void(DeviceEnumerationResult, const MediaDeviceEnumeration&));
+  MOCK_METHOD1(MockCallback, void(const MediaDeviceEnumeration&));
 
   void TrackRemovedDevice(MediaDeviceType type,
                           const blink::WebMediaDeviceInfo& media_device_info) {
@@ -266,18 +262,15 @@
   }
 
   void EnumerateCallback(base::RunLoop* run_loop,
-                         media::mojom::DeviceEnumerationResult result_code,
                          const MediaDeviceEnumeration& result) {
-    if (result_code == media::mojom::DeviceEnumerationResult::kSuccess) {
-      for (int i = 0;
-           i < static_cast<int>(MediaDeviceType::NUM_MEDIA_DEVICE_TYPES); ++i) {
-        for (const auto& device_info : result[i]) {
-          EXPECT_FALSE(device_info.device_id.empty());
-          EXPECT_FALSE(device_info.group_id.empty());
-        }
+    for (int i = 0;
+         i < static_cast<int>(MediaDeviceType::NUM_MEDIA_DEVICE_TYPES); ++i) {
+      for (const auto& device_info : result[i]) {
+        EXPECT_FALSE(device_info.device_id.empty());
+        EXPECT_FALSE(device_info.group_id.empty());
       }
     }
-    MockCallback(result_code, result);
+    MockCallback(result);
     run_loop->Quit();
   }
 
@@ -285,12 +278,9 @@
       const std::vector<media::FakeVideoCaptureDeviceSettings>&
           expected_video_capture_device_settings,
       base::RunLoop* run_loop,
-      EnumerationResponsePtr response) {
-    EXPECT_EQ(response->result_code,
-              media::mojom::DeviceEnumerationResult::kSuccess);
-
-    const std::vector<VideoInputDeviceCapabilitiesPtr>& video_capabilities =
-        response->video_input_device_capabilities;
+      const std::vector<blink::WebMediaDeviceInfoArray>& devices,
+      std::vector<VideoInputDeviceCapabilitiesPtr> video_capabilities,
+      std::vector<AudioInputDeviceCapabilitiesPtr> audio_capabilities) {
     EXPECT_EQ(video_capabilities.size(),
               expected_video_capture_device_settings.size());
     for (size_t i = 0; i < video_capabilities.size(); ++i) {
@@ -303,8 +293,6 @@
             expected_video_capture_device_settings[i].supported_formats[j]);
       }
     }
-    const std::vector<AudioInputDeviceCapabilitiesPtr>& audio_capabilities =
-        response->audio_input_device_capabilities;
     EXPECT_EQ(audio_capabilities.size(), kNumAudioInputDevices);
     for (size_t i = 0; i < audio_capabilities.size(); ++i) {
       EXPECT_TRUE(audio_capabilities[i]->parameters.IsValid());
@@ -328,25 +316,13 @@
     video_capture_device_factory_ = video_capture_device_factory.get();
     auto video_capture_system = std::make_unique<media::VideoCaptureSystemImpl>(
         std::move(video_capture_device_factory));
-    in_process_video_capture_provider_ =
+    auto video_capture_provider =
         std::make_unique<InProcessVideoCaptureProvider>(
             std::move(video_capture_system),
             base::SingleThreadTaskRunner::GetCurrentDefault(),
             kIgnoreLogMessageCB);
-
-    auto mock_video_capture_provider =
-        std::make_unique<MockVideoCaptureProvider>();
-    mock_video_capture_provider_ = mock_video_capture_provider.get();
-    // By default, forward calls to the real InProcessVideoCaptureProvider.
-    ON_CALL(*mock_video_capture_provider_, GetDeviceInfosAsync(_))
-        .WillByDefault(Invoke(
-            [&](VideoCaptureProvider::GetDeviceInfosCallback result_callback) {
-              in_process_video_capture_provider_->GetDeviceInfosAsync(
-                  std::move(result_callback));
-            }));
-
     video_capture_manager_ = new VideoCaptureManager(
-        std::move(mock_video_capture_provider), kIgnoreLogMessageCB);
+        std::move(video_capture_provider), kIgnoreLogMessageCB);
     media_devices_manager_ = std::make_unique<MediaDevicesManager>(
         audio_system_.get(), video_capture_manager_,
         base::BindRepeating(
@@ -378,9 +354,6 @@
   testing::StrictMock<MockMediaDevicesManagerClient>
       media_devices_manager_client_;
   std::set<std::string> removed_device_ids_;
-  raw_ptr<MockVideoCaptureProvider> mock_video_capture_provider_;
-  std::unique_ptr<InProcessVideoCaptureProvider>
-      in_process_video_capture_provider_;
 };
 
 TEST_F(MediaDevicesManagerTest, EnumerateNoCacheAudioInput) {
@@ -388,8 +361,7 @@
       .Times(kNumCalls);
   EXPECT_CALL(*video_capture_device_factory_, MockGetDevicesInfo()).Times(0);
   EXPECT_CALL(*audio_manager_, MockGetAudioOutputDeviceNames(_)).Times(0);
-  EXPECT_CALL(*this, MockCallback(DeviceEnumerationResult::kSuccess, _))
-      .Times(kNumCalls);
+  EXPECT_CALL(*this, MockCallback(_)).Times(kNumCalls);
   EXPECT_CALL(media_devices_manager_client_, InputDevicesChangedUI(_, _));
   MediaDevicesManager::BoolDeviceTypes devices_to_enumerate;
   devices_to_enumerate[static_cast<size_t>(
@@ -408,11 +380,8 @@
   EXPECT_CALL(*audio_manager_, MockGetAudioInputDeviceNames(_)).Times(0);
   EXPECT_CALL(*video_capture_device_factory_, MockGetDevicesInfo())
       .Times(kNumCalls);
-  EXPECT_CALL(*mock_video_capture_provider_, GetDeviceInfosAsync(_))
-      .Times(kNumCalls);
   EXPECT_CALL(*audio_manager_, MockGetAudioOutputDeviceNames(_)).Times(0);
-  EXPECT_CALL(*this, MockCallback(DeviceEnumerationResult::kSuccess, _))
-      .Times(kNumCalls);
+  EXPECT_CALL(*this, MockCallback(_)).Times(kNumCalls);
   EXPECT_CALL(media_devices_manager_client_, InputDevicesChangedUI(_, _));
   MediaDevicesManager::BoolDeviceTypes devices_to_enumerate;
   devices_to_enumerate[static_cast<size_t>(
@@ -432,8 +401,7 @@
   EXPECT_CALL(*video_capture_device_factory_, MockGetDevicesInfo()).Times(0);
   EXPECT_CALL(*audio_manager_, MockGetAudioOutputDeviceNames(_))
       .Times(kNumCalls);
-  EXPECT_CALL(*this, MockCallback(DeviceEnumerationResult::kSuccess, _))
-      .Times(kNumCalls);
+  EXPECT_CALL(*this, MockCallback(_)).Times(kNumCalls);
   MediaDevicesManager::BoolDeviceTypes devices_to_enumerate;
   devices_to_enumerate[static_cast<size_t>(
       MediaDeviceType::MEDIA_AUDIO_OUTPUT)] = true;
@@ -452,8 +420,7 @@
       .Times(kNumCalls);
   EXPECT_CALL(*audio_manager_, MockGetAudioInputDeviceNames(_))
       .Times(kNumCalls);
-  EXPECT_CALL(*this, MockCallback(DeviceEnumerationResult::kSuccess, _))
-      .Times(kNumCalls);
+  EXPECT_CALL(*this, MockCallback(_)).Times(kNumCalls);
   EXPECT_CALL(media_devices_manager_client_, InputDevicesChangedUI(_, _));
   MediaDevicesManager::BoolDeviceTypes devices_to_enumerate;
   devices_to_enumerate[static_cast<size_t>(
@@ -474,8 +441,7 @@
   EXPECT_CALL(*audio_manager_, MockGetAudioInputDeviceNames(_)).Times(1);
   EXPECT_CALL(*video_capture_device_factory_, MockGetDevicesInfo()).Times(0);
   EXPECT_CALL(*audio_manager_, MockGetAudioOutputDeviceNames(_)).Times(1);
-  EXPECT_CALL(*this, MockCallback(DeviceEnumerationResult::kSuccess, _))
-      .Times(kNumCalls);
+  EXPECT_CALL(*this, MockCallback(_)).Times(kNumCalls);
   EXPECT_CALL(media_devices_manager_client_, InputDevicesChangedUI(_, _));
   EnableCache(MediaDeviceType::MEDIA_AUDIO_INPUT);
   EnableCache(MediaDeviceType::MEDIA_AUDIO_OUTPUT);
@@ -497,10 +463,8 @@
 TEST_F(MediaDevicesManagerTest, EnumerateCacheVideo) {
   EXPECT_CALL(*audio_manager_, MockGetAudioInputDeviceNames(_)).Times(0);
   EXPECT_CALL(*video_capture_device_factory_, MockGetDevicesInfo()).Times(1);
-  EXPECT_CALL(*mock_video_capture_provider_, GetDeviceInfosAsync(_)).Times(1);
   EXPECT_CALL(*audio_manager_, MockGetAudioOutputDeviceNames(_)).Times(0);
-  EXPECT_CALL(*this, MockCallback(DeviceEnumerationResult::kSuccess, _))
-      .Times(kNumCalls);
+  EXPECT_CALL(*this, MockCallback(_)).Times(kNumCalls);
   EXPECT_CALL(media_devices_manager_client_, InputDevicesChangedUI(_, _));
   EnableCache(MediaDeviceType::MEDIA_VIDEO_INPUT);
   MediaDevicesManager::BoolDeviceTypes devices_to_enumerate;
@@ -521,9 +485,9 @@
   EXPECT_CALL(*audio_manager_, MockGetAudioOutputDeviceNames(_)).Times(3);
   EXPECT_CALL(*video_capture_device_factory_, MockGetDevicesInfo()).Times(0);
   EXPECT_CALL(*audio_manager_, MockGetAudioInputDeviceNames(_)).Times(3);
-  EXPECT_CALL(*this, MockCallback(DeviceEnumerationResult::kSuccess, _))
+  EXPECT_CALL(*this, MockCallback(_))
       .Times(3 * kNumCalls)
-      .WillRepeatedly(SaveArg<1>(&enumeration));
+      .WillRepeatedly(SaveArg<0>(&enumeration));
   EXPECT_CALL(media_devices_manager_client_, InputDevicesChangedUI(_, _));
   size_t num_audio_input_devices = 5;
   size_t num_audio_output_devices = 3;
@@ -616,11 +580,10 @@
   MediaDeviceEnumeration enumeration;
   EXPECT_CALL(*audio_manager_, MockGetAudioOutputDeviceNames(_)).Times(0);
   EXPECT_CALL(*video_capture_device_factory_, MockGetDevicesInfo()).Times(3);
-  EXPECT_CALL(*mock_video_capture_provider_, GetDeviceInfosAsync(_)).Times(3);
   EXPECT_CALL(*audio_manager_, MockGetAudioInputDeviceNames(_)).Times(0);
-  EXPECT_CALL(*this, MockCallback(DeviceEnumerationResult::kSuccess, _))
+  EXPECT_CALL(*this, MockCallback(_))
       .Times(3 * kNumCalls)
-      .WillRepeatedly(SaveArg<1>(&enumeration));
+      .WillRepeatedly(SaveArg<0>(&enumeration));
   EXPECT_CALL(media_devices_manager_client_, InputDevicesChangedUI(_, _));
 
   // First enumeration.
@@ -699,11 +662,10 @@
   MediaDeviceEnumeration enumeration;
   EXPECT_CALL(*audio_manager_, MockGetAudioOutputDeviceNames(_)).Times(2);
   EXPECT_CALL(*video_capture_device_factory_, MockGetDevicesInfo()).Times(2);
-  EXPECT_CALL(*mock_video_capture_provider_, GetDeviceInfosAsync(_)).Times(2);
   EXPECT_CALL(*audio_manager_, MockGetAudioInputDeviceNames(_)).Times(2);
-  EXPECT_CALL(*this, MockCallback(DeviceEnumerationResult::kSuccess, _))
+  EXPECT_CALL(*this, MockCallback(_))
       .Times(2 * kNumCalls)
-      .WillRepeatedly(SaveArg<1>(&enumeration));
+      .WillRepeatedly(SaveArg<0>(&enumeration));
   EXPECT_CALL(media_devices_manager_client_, InputDevicesChangedUI(_, _))
       .Times(2);
 
@@ -793,7 +755,6 @@
 TEST_F(MediaDevicesManagerTest, SubscribeDeviceChanges) {
   EXPECT_CALL(*audio_manager_, MockGetAudioOutputDeviceNames(_)).Times(3);
   EXPECT_CALL(*video_capture_device_factory_, MockGetDevicesInfo()).Times(3);
-  EXPECT_CALL(*mock_video_capture_provider_, GetDeviceInfosAsync(_)).Times(3);
   EXPECT_CALL(*audio_manager_, MockGetAudioInputDeviceNames(_)).Times(3);
   EXPECT_CALL(media_devices_manager_client_, InputDevicesChangedUI(_, _))
       .Times(2);
@@ -808,7 +769,7 @@
 
   // Run an enumeration to make sure |media_devices_manager_| has the new
   // configuration.
-  EXPECT_CALL(*this, MockCallback(DeviceEnumerationResult::kSuccess, _));
+  EXPECT_CALL(*this, MockCallback(_));
   MediaDevicesManager::BoolDeviceTypes devices_to_enumerate;
   devices_to_enumerate[static_cast<size_t>(
       MediaDeviceType::MEDIA_AUDIO_INPUT)] = true;
@@ -973,7 +934,6 @@
   EXPECT_CALL(media_devices_manager_client_,
               InputDevicesChangedUI(MediaDeviceType::MEDIA_AUDIO_INPUT, _));
   EXPECT_CALL(*video_capture_device_factory_, MockGetDevicesInfo());
-  EXPECT_CALL(*mock_video_capture_provider_, GetDeviceInfosAsync(_));
   EXPECT_CALL(media_devices_manager_client_,
               InputDevicesChangedUI(MediaDeviceType::MEDIA_VIDEO_INPUT, _));
   // Configure fake devices with video formats different from the fallback
@@ -1025,8 +985,7 @@
   EXPECT_CALL(*audio_manager_, MockGetAudioInputDeviceNames(_)).Times(2);
   EXPECT_CALL(*video_capture_device_factory_, MockGetDevicesInfo()).Times(0);
   EXPECT_CALL(*audio_manager_, MockGetAudioOutputDeviceNames(_)).Times(0);
-  EXPECT_CALL(*this, MockCallback(DeviceEnumerationResult::kSuccess, _))
-      .Times(2);
+  EXPECT_CALL(*this, MockCallback(_)).Times(2);
   EXPECT_CALL(media_devices_manager_client_, InputDevicesChangedUI(_, _))
       .Times(2);
   // Since we will be removing the default device, we expect that we will remove
@@ -1066,8 +1025,7 @@
   EXPECT_CALL(*audio_manager_, MockGetAudioInputDeviceNames(_)).Times(2);
   EXPECT_CALL(*video_capture_device_factory_, MockGetDevicesInfo()).Times(0);
   EXPECT_CALL(*audio_manager_, MockGetAudioOutputDeviceNames(_)).Times(0);
-  EXPECT_CALL(*this, MockCallback(DeviceEnumerationResult::kSuccess, _))
-      .Times(2);
+  EXPECT_CALL(*this, MockCallback(_)).Times(2);
   EXPECT_CALL(media_devices_manager_client_, InputDevicesChangedUI(_, _))
       .Times(2);
   // Since we will be removing the communications device, we expect that we will
@@ -1111,8 +1069,7 @@
   EXPECT_CALL(*audio_manager_, MockGetAudioInputDeviceNames(_)).Times(2);
   EXPECT_CALL(*video_capture_device_factory_, MockGetDevicesInfo()).Times(0);
   EXPECT_CALL(*audio_manager_, MockGetAudioOutputDeviceNames(_)).Times(0);
-  EXPECT_CALL(*this, MockCallback(DeviceEnumerationResult::kSuccess, _))
-      .Times(2);
+  EXPECT_CALL(*this, MockCallback(_)).Times(2);
   EXPECT_CALL(media_devices_manager_client_, InputDevicesChangedUI(_, _))
       .Times(2);
   // Since we will be removing the device identified as 'default' and
@@ -1230,7 +1187,6 @@
 
 TEST_F(MediaDevicesManagerTest, DeviceIdSaltReset) {
   EXPECT_CALL(*video_capture_device_factory_, MockGetDevicesInfo()).Times(2);
-  EXPECT_CALL(*mock_video_capture_provider_, GetDeviceInfosAsync(_)).Times(2);
   EXPECT_CALL(media_devices_manager_client_, InputDevicesChangedUI(_, _))
       .Times(1);
 
@@ -1240,7 +1196,7 @@
 
   // Run an enumeration to make sure |media_devices_manager_| has the new
   // configuration.
-  EXPECT_CALL(*this, MockCallback(DeviceEnumerationResult::kSuccess, _));
+  EXPECT_CALL(*this, MockCallback(_));
   MediaDevicesManager::BoolDeviceTypes devices_to_enumerate;
   devices_to_enumerate[static_cast<size_t>(
       MediaDeviceType::MEDIA_VIDEO_INPUT)] = true;
@@ -1278,150 +1234,4 @@
   EXPECT_EQ(num_video_input_devices, notification_video_input.size());
 }
 
-TEST_F(MediaDevicesManagerTest, EnumerateVideoInputFailsOnce) {
-  // Inject an UnknownError on the first call to GetDeviceInfosAsync, otherwise
-  // fall through to the in_process_video_capture_provider_.
-  EXPECT_CALL(*mock_video_capture_provider_, GetDeviceInfosAsync(_))
-      .Times(kNumCalls)
-      .WillOnce(Invoke(
-          [&](VideoCaptureProvider::GetDeviceInfosCallback result_callback) {
-            std::move(result_callback)
-                .Run(DeviceEnumerationResult::kUnknownError, {});
-          }))
-      .WillRepeatedly(Invoke(
-          [&](VideoCaptureProvider::GetDeviceInfosCallback result_callback) {
-            in_process_video_capture_provider_->GetDeviceInfosAsync(
-                std::move(result_callback));
-          }));
-  EXPECT_CALL(*video_capture_device_factory_, MockGetDevicesInfo())
-      .Times(kNumCalls - 1);
-
-  EXPECT_CALL(*audio_manager_, MockGetAudioInputDeviceNames(_)).Times(0);
-  EXPECT_CALL(*audio_manager_, MockGetAudioOutputDeviceNames(_)).Times(0);
-  EXPECT_CALL(*this, MockCallback(DeviceEnumerationResult::kUnknownError, _))
-      .Times(1);
-  EXPECT_CALL(*this, MockCallback(DeviceEnumerationResult::kSuccess, _))
-      .Times(kNumCalls - 1);
-  EXPECT_CALL(media_devices_manager_client_, InputDevicesChangedUI(_, _));
-  MediaDevicesManager::BoolDeviceTypes devices_to_enumerate;
-  devices_to_enumerate[static_cast<size_t>(
-      MediaDeviceType::MEDIA_VIDEO_INPUT)] = true;
-  for (int i = 0; i < kNumCalls; i++) {
-    base::RunLoop run_loop;
-    media_devices_manager_->EnumerateDevices(
-        devices_to_enumerate,
-        base::BindOnce(&MediaDevicesManagerTest::EnumerateCallback,
-                       base::Unretained(this), &run_loop));
-    run_loop.Run();
-  }
-}
-
-TEST_F(MediaDevicesManagerTest, EnumerateVideoInputFailsMultipleTimes) {
-  EXPECT_CALL(*mock_video_capture_provider_, GetDeviceInfosAsync(_))
-      .Times(kNumCalls)
-      .WillRepeatedly(Invoke(
-          [&](VideoCaptureProvider::GetDeviceInfosCallback result_callback) {
-            std::move(result_callback)
-                .Run(DeviceEnumerationResult::kUnknownError, {});
-          }));
-
-  EXPECT_CALL(*audio_manager_, MockGetAudioInputDeviceNames(_)).Times(0);
-  EXPECT_CALL(*audio_manager_, MockGetAudioOutputDeviceNames(_)).Times(0);
-  EXPECT_CALL(*this, MockCallback(DeviceEnumerationResult::kUnknownError, _))
-      .Times(kNumCalls);
-  MediaDevicesManager::BoolDeviceTypes devices_to_enumerate;
-  devices_to_enumerate[static_cast<size_t>(
-      MediaDeviceType::MEDIA_VIDEO_INPUT)] = true;
-  for (int i = 0; i < kNumCalls; i++) {
-    base::RunLoop run_loop;
-    media_devices_manager_->EnumerateDevices(
-        devices_to_enumerate,
-        base::BindOnce(&MediaDevicesManagerTest::EnumerateCallback,
-                       base::Unretained(this), &run_loop));
-    run_loop.Run();
-  }
-}
-
-TEST_F(MediaDevicesManagerTest, EnumerateDevicesWithCapabilitiesFailsOnce) {
-  // Inject an UnknownError on the first call to GetDeviceInfosAsync, otherwise
-  // fall through to the in_process_video_capture_provider_.
-  EXPECT_CALL(*mock_video_capture_provider_, GetDeviceInfosAsync(_))
-      .Times(kNumCalls)
-      .WillOnce(Invoke(
-          [&](VideoCaptureProvider::GetDeviceInfosCallback result_callback) {
-            std::move(result_callback)
-                .Run(DeviceEnumerationResult::kUnknownError, {});
-          }))
-      .WillRepeatedly(Invoke(
-          [&](VideoCaptureProvider::GetDeviceInfosCallback result_callback) {
-            in_process_video_capture_provider_->GetDeviceInfosAsync(
-                std::move(result_callback));
-          }));
-  EXPECT_CALL(*audio_manager_, MockGetAudioInputDeviceNames(_))
-      .Times(kNumCalls);
-  EXPECT_CALL(*video_capture_device_factory_, MockGetDevicesInfo())
-      .Times(kNumCalls - 1);
-  EXPECT_CALL(media_devices_manager_client_,
-              InputDevicesChangedUI(MediaDeviceType::MEDIA_AUDIO_INPUT, _));
-  EXPECT_CALL(media_devices_manager_client_,
-              InputDevicesChangedUI(MediaDeviceType::MEDIA_VIDEO_INPUT, _));
-
-  MediaDevicesManager::BoolDeviceTypes devices_to_enumerate;
-  devices_to_enumerate[static_cast<size_t>(
-      MediaDeviceType::MEDIA_VIDEO_INPUT)] = true;
-  devices_to_enumerate[static_cast<size_t>(
-      MediaDeviceType::MEDIA_AUDIO_INPUT)] = true;
-  for (int i = 0; i < kNumCalls; i++) {
-    base::RunLoop run_loop;
-    media_devices_manager_->EnumerateDevices(
-        -1, -1, devices_to_enumerate, true, true,
-        base::BindOnce(
-            [](bool expect_success, base::RunLoop* run_loop,
-               EnumerationResponsePtr response) {
-              EXPECT_EQ(response->result_code,
-                        expect_success
-                            ? DeviceEnumerationResult::kSuccess
-                            : DeviceEnumerationResult::kUnknownError);
-              run_loop->Quit();
-            },
-            /*expect_success=*/i > 0, &run_loop));
-    run_loop.Run();
-  }
-}
-
-TEST_F(MediaDevicesManagerTest,
-       EnumerateDevicesWithCapabilitiesFailsRepeatedly) {
-  // Inject an UnknownError on all calls to GetDeviceInfosAsync.
-  EXPECT_CALL(*mock_video_capture_provider_, GetDeviceInfosAsync(_))
-      .Times(kNumCalls)
-      .WillRepeatedly(Invoke(
-          [&](VideoCaptureProvider::GetDeviceInfosCallback result_callback) {
-            std::move(result_callback)
-                .Run(DeviceEnumerationResult::kUnknownError, {});
-          }));
-  EXPECT_CALL(*audio_manager_, MockGetAudioInputDeviceNames(_))
-      .Times(kNumCalls);
-  EXPECT_CALL(media_devices_manager_client_,
-              InputDevicesChangedUI(MediaDeviceType::MEDIA_AUDIO_INPUT, _));
-
-  MediaDevicesManager::BoolDeviceTypes devices_to_enumerate;
-  devices_to_enumerate[static_cast<size_t>(
-      MediaDeviceType::MEDIA_VIDEO_INPUT)] = true;
-  devices_to_enumerate[static_cast<size_t>(
-      MediaDeviceType::MEDIA_AUDIO_INPUT)] = true;
-  for (int i = 0; i < kNumCalls; i++) {
-    base::RunLoop run_loop;
-    media_devices_manager_->EnumerateDevices(
-        -1, -1, devices_to_enumerate, true, true,
-        base::BindOnce(
-            [](base::RunLoop* run_loop, EnumerationResponsePtr response) {
-              EXPECT_EQ(response->result_code,
-                        DeviceEnumerationResult::kUnknownError);
-              run_loop->Quit();
-            },
-            &run_loop));
-    run_loop.Run();
-  }
-}
-
 }  // namespace content
diff --git a/content/browser/renderer_host/media/media_stream_dispatcher_host_unittest.cc b/content/browser/renderer_host/media/media_stream_dispatcher_host_unittest.cc
index 4e921dc..f49a574 100644
--- a/content/browser/renderer_host/media/media_stream_dispatcher_host_unittest.cc
+++ b/content/browser/renderer_host/media/media_stream_dispatcher_host_unittest.cc
@@ -107,13 +107,9 @@
   return true;
 }
 
-void AudioInputDevicesEnumerated(
-    base::OnceClosure quit_closure,
-    media::AudioDeviceDescriptions* out,
-    media::mojom::DeviceEnumerationResult result_code,
-    const MediaDeviceEnumeration& enumeration) {
-  EXPECT_EQ(result_code, media::mojom::DeviceEnumerationResult::kSuccess);
-
+void AudioInputDevicesEnumerated(base::OnceClosure quit_closure,
+                                 media::AudioDeviceDescriptions* out,
+                                 const MediaDeviceEnumeration& enumeration) {
   for (const auto& info : enumeration[static_cast<size_t>(
            blink::mojom::MediaDeviceType::MEDIA_AUDIO_INPUT)]) {
     out->emplace_back(info.label, info.device_id, info.group_id);
diff --git a/content/browser/renderer_host/media/media_stream_manager.cc b/content/browser/renderer_host/media/media_stream_manager.cc
index 7b3b88d..7afa280 100644
--- a/content/browser/renderer_host/media/media_stream_manager.cc
+++ b/content/browser/renderer_host/media/media_stream_manager.cc
@@ -484,18 +484,13 @@
     const std::string& source_id,
     scoped_refptr<base::SequencedTaskRunner> task_runner,
     base::OnceCallback<void(const absl::optional<std::string>&)> callback,
-    media::mojom::DeviceEnumerationResult result,
     const MediaDeviceEnumeration& enumeration) {
-  // TODO(crbug.com/1313822): Propagate an error rather than acting as if there
-  // are no devices.
-  if (result == media::mojom::DeviceEnumerationResult::kSuccess) {
-    for (const auto& device : enumeration[static_cast<size_t>(type)]) {
-      if (MediaStreamManager::DoesMediaDeviceIDMatchHMAC(
-              salt, security_origin, source_id, device.device_id)) {
-        task_runner->PostTask(
-            FROM_HERE, base::BindOnce(std::move(callback), device.device_id));
-        return;
-      }
+  for (const auto& device : enumeration[static_cast<size_t>(type)]) {
+    if (MediaStreamManager::DoesMediaDeviceIDMatchHMAC(
+            salt, security_origin, source_id, device.device_id)) {
+      task_runner->PostTask(
+          FROM_HERE, base::BindOnce(std::move(callback), device.device_id));
+      return;
     }
   }
   task_runner->PostTask(FROM_HERE,
@@ -3029,21 +3024,13 @@
     bool requested_audio_input,
     bool requested_video_input,
     const std::string& label,
-    media::mojom::DeviceEnumerationResult result,
     const MediaDeviceEnumeration& enumeration) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
+
   DeviceRequest* request = FindRequest(label);
   if (!request)
     return;
 
-  if (result != media::mojom::DeviceEnumerationResult::kSuccess) {
-    // TODO(crbug.com/1313822): Propagate a more specific error depending on
-    // result.
-    FinalizeRequestFailed(label, request,
-                          MediaStreamRequestResult::NO_HARDWARE);
-    return;
-  }
-
   SendLogMessage(base::StringPrintf(
       "DevicesEnumerated({label=%s}, {requester_id=%d}, {request_type=%s})",
       label.c_str(), request->requester_id,
diff --git a/content/browser/renderer_host/media/media_stream_manager.h b/content/browser/renderer_host/media/media_stream_manager.h
index 73d76f3..2f84a843a 100644
--- a/content/browser/renderer_host/media/media_stream_manager.h
+++ b/content/browser/renderer_host/media/media_stream_manager.h
@@ -637,7 +637,6 @@
   void DevicesEnumerated(bool requested_audio_input,
                          bool requested_video_input,
                          const std::string& label,
-                         media::mojom::DeviceEnumerationResult result,
                          const MediaDeviceEnumeration& enumeration);
 
   // Creates blink::MediaStreamDevices for |devices_infos| of |stream_type|. For
diff --git a/content/browser/renderer_host/media/render_frame_audio_input_stream_factory.cc b/content/browser/renderer_host/media/render_frame_audio_input_stream_factory.cc
index 39f41c9..58a102d 100644
--- a/content/browser/renderer_host/media/render_frame_audio_input_stream_factory.cc
+++ b/content/browser/renderer_host/media/render_frame_audio_input_stream_factory.cc
@@ -72,20 +72,15 @@
 void TranslateDeviceId(const std::string& device_id,
                        const MediaDeviceSaltAndOrigin& salt_and_origin,
                        base::RepeatingCallback<void(const std::string&)> cb,
-                       media::mojom::DeviceEnumerationResult result,
                        const MediaDeviceEnumeration& device_array) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  // TODO(crbug.com/1313822): Propagate errors rather than acting as if there
-  // are no devices.
-  if (result == media::mojom::DeviceEnumerationResult::kSuccess) {
-    for (const auto& device_info : device_array[static_cast<size_t>(
-             MediaDeviceType::MEDIA_AUDIO_OUTPUT)]) {
-      if (MediaStreamManager::DoesMediaDeviceIDMatchHMAC(
-              salt_and_origin.device_id_salt, salt_and_origin.origin, device_id,
-              device_info.device_id)) {
-        cb.Run(device_info.device_id);
-        break;
-      }
+  for (const auto& device_info :
+       device_array[static_cast<size_t>(MediaDeviceType::MEDIA_AUDIO_OUTPUT)]) {
+    if (MediaStreamManager::DoesMediaDeviceIDMatchHMAC(
+            salt_and_origin.device_id_salt, salt_and_origin.origin, device_id,
+            device_info.device_id)) {
+      cb.Run(device_info.device_id);
+      break;
     }
   }
   // If we're unable to translate the device id, |cb| will not be run.
diff --git a/content/browser/renderer_host/media/video_capture_unittest.cc b/content/browser/renderer_host/media/video_capture_unittest.cc
index 0315915..1beaa22 100644
--- a/content/browser/renderer_host/media/video_capture_unittest.cc
+++ b/content/browser/renderer_host/media/video_capture_unittest.cc
@@ -53,7 +53,6 @@
                                  const std::string& salt,
                                  const url::Origin& security_origin,
                                  blink::WebMediaDeviceInfoArray* out,
-                                 media::mojom::DeviceEnumerationResult,
                                  const MediaDeviceEnumeration& enumeration) {
   for (const auto& info : enumeration[static_cast<size_t>(
            blink::mojom::MediaDeviceType::MEDIA_VIDEO_INPUT)]) {
diff --git a/content/browser/renderer_host/navigation_controller_impl.cc b/content/browser/renderer_host/navigation_controller_impl.cc
index 5288566..ca48d101 100644
--- a/content/browser/renderer_host/navigation_controller_impl.cc
+++ b/content/browser/renderer_host/navigation_controller_impl.cc
@@ -2661,6 +2661,8 @@
     scoped_refptr<network::SharedURLLoaderFactory> blob_url_loader_factory,
     bool is_form_submission,
     const absl::optional<blink::Impression>& impression,
+    blink::mojom::NavigationInitiatorActivationAndAdStatus
+        initiator_activation_and_ad_status,
     base::TimeTicks navigation_start_time,
     bool is_embedder_initiated_fenced_frame_navigation,
     bool is_unfenced_top_navigation,
@@ -2778,6 +2780,8 @@
   params.impression = impression;
   params.download_policy = std::move(download_policy);
   params.is_form_submission = is_form_submission;
+  params.initiator_activation_and_ad_status =
+      initiator_activation_and_ad_status;
 
   std::unique_ptr<NavigationRequest> request =
       CreateNavigationRequestFromLoadParams(
@@ -3760,6 +3764,7 @@
   DCHECK_EQ(is_view_source_mode, virtual_url.SchemeIs(kViewSourceScheme));
 
   blink::NavigationDownloadPolicy download_policy = params.download_policy;
+
   // Update |download_policy| if the virtual URL is view-source.
   if (is_view_source_mode)
     download_policy.SetDisallowed(blink::NavigationDownloadType::kViewSource);
@@ -3855,8 +3860,8 @@
       params.initiator_process_id, extra_headers_crlf, frame_entry, entry,
       params.is_form_submission,
       params.navigation_ui_data ? params.navigation_ui_data->Clone() : nullptr,
-      params.impression, params.is_pdf,
-      is_embedder_initiated_fenced_frame_navigation);
+      params.impression, params.initiator_activation_and_ad_status,
+      params.is_pdf, is_embedder_initiated_fenced_frame_navigation);
   navigation_request->set_from_download_cross_origin_redirect(
       params.from_download_cross_origin_redirect);
   navigation_request->set_force_new_browsing_instance(
@@ -3983,6 +3988,8 @@
       ChildProcessHost::kInvalidUniqueID /* initiator_process_id */,
       entry->extra_headers(), frame_entry, entry, is_form_submission,
       nullptr /* navigation_ui_data */, absl::nullopt /* impression */,
+      blink::mojom::NavigationInitiatorActivationAndAdStatus::
+          kDidNotStartWithTransientActivation,
       false /* is_pdf */);
 }
 
@@ -4111,7 +4118,7 @@
           false /* is_pdf */);
   navigation_request->set_post_commit_error_page_html(error_page_html);
   navigation_request->set_net_error(error);
-  node->CreatedNavigationRequest(std::move(navigation_request));
+  node->TakeNavigationRequest(std::move(navigation_request));
   DCHECK(node->navigation_request());
 
   // Calling BeginNavigation may destroy the NavigationRequest.
diff --git a/content/browser/renderer_host/navigation_controller_impl.h b/content/browser/renderer_host/navigation_controller_impl.h
index 7a208a1..a647e20 100644
--- a/content/browser/renderer_host/navigation_controller_impl.h
+++ b/content/browser/renderer_host/navigation_controller_impl.h
@@ -35,6 +35,7 @@
 #include "third_party/blink/public/common/scheduler/task_attribution_id.h"
 #include "third_party/blink/public/common/tokens/tokens.h"
 #include "third_party/blink/public/mojom/navigation/navigation_api_history_entry_arrays.mojom-forward.h"
+#include "third_party/blink/public/mojom/navigation/navigation_initiator_activation_and_ad_status.mojom.h"
 #include "third_party/blink/public/mojom/navigation/navigation_params.mojom-forward.h"
 
 namespace blink {
@@ -220,6 +221,8 @@
       scoped_refptr<network::SharedURLLoaderFactory> blob_url_loader_factory,
       bool is_form_submission,
       const absl::optional<blink::Impression>& impression,
+      blink::mojom::NavigationInitiatorActivationAndAdStatus
+          initiator_activation_and_ad_status,
       base::TimeTicks navigation_start_time,
       bool is_embedder_initiated_fenced_frame_navigation = false,
       bool is_unfenced_top_navigation = false,
diff --git a/content/browser/renderer_host/navigation_controller_impl_unittest.cc b/content/browser/renderer_host/navigation_controller_impl_unittest.cc
index 1de9404d..3447a65 100644
--- a/content/browser/renderer_host/navigation_controller_impl_unittest.cc
+++ b/content/browser/renderer_host/navigation_controller_impl_unittest.cc
@@ -4330,6 +4330,8 @@
       blink::NavigationDownloadPolicy(), "GET", nullptr, "",
       network::mojom::SourceLocation::New(), nullptr,
       false /*is_form_submission*/, absl::nullopt,
+      blink::mojom::NavigationInitiatorActivationAndAdStatus::
+          kDidNotStartWithTransientActivation,
       base::TimeTicks::Now() /* navigation_start_time */);
 
   // Clean up the handler.
@@ -4371,6 +4373,8 @@
       should_replace_current_entry, blink::NavigationDownloadPolicy(), "GET",
       nullptr, "", network::mojom::SourceLocation::New(), nullptr,
       false /*is_form_submission*/, absl::nullopt,
+      blink::mojom::NavigationInitiatorActivationAndAdStatus::
+          kDidNotStartWithTransientActivation,
       base::TimeTicks::Now() /* navigation_start_time */);
   NavigationRequest* request = node->navigation_request();
   ASSERT_TRUE(request);
@@ -4704,6 +4708,8 @@
       blink::NavigationDownloadPolicy(), "GET", nullptr, "",
       network::mojom::SourceLocation::New(), nullptr,
       false /*is_form_submission*/, absl::nullopt,
+      blink::mojom::NavigationInitiatorActivationAndAdStatus::
+          kDidNotStartWithTransientActivation,
       base::TimeTicks::Now() /* navigation_start_time */);
 
   NavigationRequest* request =
diff --git a/content/browser/renderer_host/navigation_request.cc b/content/browser/renderer_host/navigation_request.cc
index 63bfed2..04f7350 100644
--- a/content/browser/renderer_host/navigation_request.cc
+++ b/content/browser/renderer_host/navigation_request.cc
@@ -1092,8 +1092,10 @@
       frame_tree_node, std::move(common_params), std::move(commit_params),
       /*browser_initiated=*/true, was_opener_suppressed, initiator_frame_token,
       initiator_process_id, extra_headers, frame_entry, entry,
-      is_form_submission, std::move(navigation_ui_data), impression, is_pdf,
-      is_embedder_initiated_fenced_frame_navigation);
+      is_form_submission, std::move(navigation_ui_data), impression,
+      blink::mojom::NavigationInitiatorActivationAndAdStatus::
+          kDidNotStartWithTransientActivation,
+      is_pdf, is_embedder_initiated_fenced_frame_navigation);
 }
 
 // static
@@ -1111,6 +1113,8 @@
     bool is_form_submission,
     std::unique_ptr<NavigationUIData> navigation_ui_data,
     const absl::optional<blink::Impression>& impression,
+    blink::mojom::NavigationInitiatorActivationAndAdStatus
+        initiator_activation_and_ad_status,
     bool is_pdf,
     bool is_embedder_initiated_fenced_frame_navigation) {
   TRACE_EVENT1("navigation", "NavigationRequest::Create", "browser_initiated",
@@ -1141,7 +1145,7 @@
       nullptr /* trust_token_params */, impression,
       base::TimeTicks() /* renderer_before_unload_start */,
       base::TimeTicks() /* renderer_before_unload_end */,
-      std::move(web_bundle_token_params));
+      std::move(web_bundle_token_params), initiator_activation_and_ad_status);
 
   // Shift-Reload forces bypassing caches and service workers.
   if (common_params->navigation_type ==
@@ -7186,6 +7190,11 @@
   return !commit_params_->is_browser_initiated;
 }
 
+blink::mojom::NavigationInitiatorActivationAndAdStatus
+NavigationRequest::GetNavigationInitiatorActivationAndAdStatus() {
+  return begin_params_->initiator_activation_and_ad_status;
+}
+
 bool NavigationRequest::IsSameOrigin() {
   DCHECK(HasCommitted());
   return same_origin_;
diff --git a/content/browser/renderer_host/navigation_request.h b/content/browser/renderer_host/navigation_request.h
index 1192bd76..7cb007d8 100644
--- a/content/browser/renderer_host/navigation_request.h
+++ b/content/browser/renderer_host/navigation_request.h
@@ -64,6 +64,7 @@
 #include "third_party/blink/public/common/runtime_feature_state/runtime_feature_state_context.h"
 #include "third_party/blink/public/common/tokens/tokens.h"
 #include "third_party/blink/public/mojom/loader/mixed_content.mojom-forward.h"
+#include "third_party/blink/public/mojom/navigation/navigation_initiator_activation_and_ad_status.mojom.h"
 #include "third_party/blink/public/mojom/navigation/navigation_params.mojom-forward.h"
 #include "third_party/perfetto/include/perfetto/tracing/traced_value_forward.h"
 #include "url/gurl.h"
@@ -251,6 +252,8 @@
       bool is_form_submission,
       std::unique_ptr<NavigationUIData> navigation_ui_data,
       const absl::optional<blink::Impression>& impression,
+      blink::mojom::NavigationInitiatorActivationAndAdStatus
+          initiator_activation_and_ad_status,
       bool is_pdf,
       bool is_embedder_initiated_fenced_frame_navigation = false);
 
@@ -336,6 +339,8 @@
   bool IsInFencedFrameTree() const override;
   FrameType GetNavigatingFrameType() const override;
   bool IsRendererInitiated() override;
+  blink::mojom::NavigationInitiatorActivationAndAdStatus
+  GetNavigationInitiatorActivationAndAdStatus() override;
   bool IsSameOrigin() override;
   bool WasServerRedirect() override;
   const std::vector<GURL>& GetRedirectChain() override;
diff --git a/content/browser/renderer_host/navigation_request_unittest.cc b/content/browser/renderer_host/navigation_request_unittest.cc
index 6c8bf94..a3ded44e 100644
--- a/content/browser/renderer_host/navigation_request_unittest.cc
+++ b/content/browser/renderer_host/navigation_request_unittest.cc
@@ -215,7 +215,7 @@
         nullptr /* entry */, false /* is_form_submission */,
         nullptr /* navigation_ui_data */, absl::nullopt /* impression */,
         false /* is_pdf */);
-    main_test_rfh()->frame_tree_node()->CreatedNavigationRequest(
+    main_test_rfh()->frame_tree_node()->TakeNavigationRequest(
         std::move(request));
     GetNavigationRequest()->StartNavigation();
   }
diff --git a/content/browser/renderer_host/navigator.cc b/content/browser/renderer_host/navigator.cc
index d77259c..27ecc1fd 100644
--- a/content/browser/renderer_host/navigator.cc
+++ b/content/browser/renderer_host/navigator.cc
@@ -741,7 +741,7 @@
   bool is_pending_entry =
       controller_.GetPendingEntry() &&
       (nav_entry_id == controller_.GetPendingEntry()->GetUniqueID());
-  frame_tree_node->CreatedNavigationRequest(std::move(request));
+  frame_tree_node->TakeNavigationRequest(std::move(request));
   DCHECK(frame_tree_node->navigation_request());
 
   // Have the current renderer execute its beforeunload event if needed. If it
@@ -881,6 +881,8 @@
     bool has_user_gesture,
     bool is_form_submission,
     const absl::optional<blink::Impression>& impression,
+    blink::mojom::NavigationInitiatorActivationAndAdStatus
+        initiator_activation_and_ad_status,
     base::TimeTicks navigation_start_time,
     bool is_embedder_initiated_fenced_frame_navigation,
     bool is_unfenced_top_navigation,
@@ -926,9 +928,9 @@
       referrer_to_use, page_transition, should_replace_current_entry,
       download_policy, method, post_body, extra_headers,
       std::move(source_location), std::move(blob_url_loader_factory),
-      is_form_submission, impression, navigation_start_time,
-      is_embedder_initiated_fenced_frame_navigation, is_unfenced_top_navigation,
-      force_new_browsing_instance);
+      is_form_submission, impression, initiator_activation_and_ad_status,
+      navigation_start_time, is_embedder_initiated_fenced_frame_navigation,
+      is_unfenced_top_navigation, force_new_browsing_instance);
 }
 
 void Navigator::BeforeUnloadCompleted(FrameTreeNode* frame_tree_node,
@@ -989,10 +991,6 @@
     mojo::PendingReceiver<mojom::NavigationRendererCancellationListener>
         renderer_cancellation_listener) {
   TRACE_EVENT0("navigation", "Navigator::OnBeginNavigation");
-  // TODO(clamy): the url sent by the renderer should be validated with
-  // FilterURL.
-  // This is a renderer-initiated navigation.
-  DCHECK(frame_tree_node);
 
   if (common_params->is_history_navigation_in_new_child_frame) {
     // Try to find a FrameNavigationEntry that matches this frame instead, based
@@ -1033,7 +1031,7 @@
   if (navigation_entry)
     navigation_entry->SetIsOverridingUserAgent(override_user_agent);
 
-  frame_tree_node->CreatedNavigationRequest(
+  frame_tree_node->TakeNavigationRequest(
       NavigationRequest::CreateRendererInitiated(
           frame_tree_node, navigation_entry, std::move(common_params),
           std::move(begin_params), controller_.GetLastCommittedEntryIndex(),
@@ -1098,7 +1096,7 @@
     return;
 
   navigation_request->ResetForCrossDocumentRestart();
-  frame_tree_node->CreatedNavigationRequest(std::move(navigation_request));
+  frame_tree_node->TakeNavigationRequest(std::move(navigation_request));
   frame_tree_node->navigation_request()->BeginNavigation();
   // DO NOT USE THE NAVIGATION REQUEST BEYOND THIS POINT. It might have been
   // destroyed in BeginNavigation().
diff --git a/content/browser/renderer_host/navigator.h b/content/browser/renderer_host/navigator.h
index 577f3dd..5f7d6bd 100644
--- a/content/browser/renderer_host/navigator.h
+++ b/content/browser/renderer_host/navigator.h
@@ -19,6 +19,7 @@
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/common/navigation/impression.h"
 #include "third_party/blink/public/mojom/frame/triggering_event_info.mojom-shared.h"
+#include "third_party/blink/public/mojom/navigation/navigation_initiator_activation_and_ad_status.mojom.h"
 #include "third_party/blink/public/mojom/navigation/navigation_params.mojom-forward.h"
 #include "ui/base/window_open_disposition.h"
 
@@ -157,6 +158,8 @@
       bool has_user_gesture,
       bool is_form_submission,
       const absl::optional<blink::Impression>& impression,
+      blink::mojom::NavigationInitiatorActivationAndAdStatus
+          initiator_activation_and_ad_status,
       base::TimeTicks navigation_start_time,
       bool is_embedder_initiated_fenced_frame_navigation = false,
       bool is_unfenced_top_navigation = false,
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
index ebe7626..daae954e1 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -7110,7 +7110,8 @@
         /*post_body=*/nullptr, params->extra_headers,
         /*blob_url_loader_factory=*/nullptr,
         network::mojom::SourceLocation::New(), /*has_user_gesture=*/false,
-        params->is_form_submission, params->impression, base::TimeTicks::Now(),
+        params->is_form_submission, params->impression,
+        params->initiator_activation_and_ad_status, base::TimeTicks::Now(),
         /*is_embedder_initiated_fenced_frame_navigation=*/false,
         /*is_unfenced_top_navigation=*/true,
         /*force_new_browsing_instance=*/true);
diff --git a/content/browser/renderer_host/render_frame_host_manager_unittest.cc b/content/browser/renderer_host/render_frame_host_manager_unittest.cc
index eb130c7..be3e2e4 100644
--- a/content/browser/renderer_host/render_frame_host_manager_unittest.cc
+++ b/content/browser/renderer_host/render_frame_host_manager_unittest.cc
@@ -435,6 +435,8 @@
             ChildProcessHost::kInvalidUniqueID /* initiator_process_id */,
             entry->extra_headers(), frame_entry, entry, is_form_submission,
             nullptr /* navigation_ui_data */, absl::nullopt /* impression */,
+            blink::mojom::NavigationInitiatorActivationAndAdStatus::
+                kDidNotStartWithTransientActivation,
             false /* is_pdf */);
 
     // Simulates request creation that triggers the 1st internal call to
diff --git a/content/browser/renderer_host/render_frame_proxy_host.cc b/content/browser/renderer_host/render_frame_proxy_host.cc
index 5ab5672..336b070 100644
--- a/content/browser/renderer_host/render_frame_proxy_host.cc
+++ b/content/browser/renderer_host/render_frame_proxy_host.cc
@@ -46,6 +46,7 @@
 #include "third_party/blink/public/common/storage_key/storage_key.h"
 #include "third_party/blink/public/mojom/frame/frame_owner_properties.mojom.h"
 #include "third_party/blink/public/mojom/messaging/transferable_message.mojom.h"
+#include "third_party/blink/public/mojom/navigation/navigation_initiator_activation_and_ad_status.mojom.h"
 #include "third_party/blink/public/mojom/scroll/scroll_into_view_params.mojom.h"
 #include "ui/gfx/geometry/rect_f.h"
 
@@ -706,7 +707,8 @@
       params->post_body ? "POST" : "GET", params->post_body,
       params->extra_headers, std::move(blob_url_loader_factory),
       std::move(params->source_location), params->user_gesture,
-      params->is_form_submission, params->impression, navigation_start_time);
+      params->is_form_submission, params->impression,
+      params->initiator_activation_and_ad_status, navigation_start_time);
 }
 
 void RenderFrameProxyHost::UpdateViewportIntersection(
diff --git a/content/browser/security_exploit_browsertest.cc b/content/browser/security_exploit_browsertest.cc
index 8d9de03..345d81e6c 100644
--- a/content/browser/security_exploit_browsertest.cc
+++ b/content/browser/security_exploit_browsertest.cc
@@ -1534,7 +1534,9 @@
           nullptr /* trust_token_params */, absl::nullopt /* impression */,
           base::TimeTicks() /* renderer_before_unload_start */,
           base::TimeTicks() /* renderer_before_unload_end */,
-          absl::nullopt /* web_bundle_token */);
+          absl::nullopt /* web_bundle_token */,
+          blink::mojom::NavigationInitiatorActivationAndAdStatus::
+              kDidNotStartWithTransientActivation);
 
   // Receiving the invalid IPC message should lead to renderer process
   // termination.
diff --git a/content/browser/site_info.cc b/content/browser/site_info.cc
index ff5cf87b..3627fd1 100644
--- a/content/browser/site_info.cc
+++ b/content/browser/site_info.cc
@@ -364,7 +364,7 @@
                    const GURL& process_lock_url,
                    bool requires_origin_keyed_process,
                    bool is_sandboxed,
-                   int unique_sandbox_id_,
+                   int unique_sandbox_id,
                    const StoragePartitionConfig storage_partition_config,
                    const WebExposedIsolationInfo& web_exposed_isolation_info,
                    bool is_guest,
@@ -376,7 +376,7 @@
       process_lock_url_(process_lock_url),
       requires_origin_keyed_process_(requires_origin_keyed_process),
       is_sandboxed_(is_sandboxed),
-      unique_sandbox_id_(unique_sandbox_id_),
+      unique_sandbox_id_(unique_sandbox_id),
       storage_partition_config_(storage_partition_config),
       web_exposed_isolation_info_(web_exposed_isolation_info),
       is_guest_(is_guest),
diff --git a/content/browser/usb/OWNERS b/content/browser/usb/OWNERS
index c21268b..d74f809 100644
--- a/content/browser/usb/OWNERS
+++ b/content/browser/usb/OWNERS
@@ -1,2 +1,3 @@
 reillyg@chromium.org
 rockot@google.com
+chengweih@chromium.org
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index 9278914d..44f90bc 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -4233,6 +4233,8 @@
               ? NavigationController::UA_OVERRIDE_TRUE
               : NavigationController::UA_OVERRIDE_FALSE;
       load_params->download_policy = params.download_policy;
+      load_params->initiator_activation_and_ad_status =
+          params.initiator_activation_and_ad_status;
 
       if (delegate_ && !is_guest &&
           !delegate_->ShouldResumeRequestsForCreatedWindow()) {
diff --git a/content/browser/webid/federated_auth_request_impl.cc b/content/browser/webid/federated_auth_request_impl.cc
index 6a310d2..544e5be9 100644
--- a/content/browser/webid/federated_auth_request_impl.cc
+++ b/content/browser/webid/federated_auth_request_impl.cc
@@ -529,9 +529,6 @@
 
   base::flat_map<GURL, IdentityProviderGetInfo> get_infos;
 
-  // TODO(crbug.com/1361642): Handle cases where not all IDPs' requests are
-  // successful. Currently when multiple IDPs are specified, an accounts
-  // dialog is shown only when the last IDP's request is successful.
   for (auto& idp_get_params_ptr : idp_get_params_ptrs) {
     for (auto& idp_ptr : idp_get_params_ptr->providers) {
       idp_order_.push_back(idp_ptr->config_url);
@@ -657,19 +654,6 @@
     std::vector<FederatedManifestRequester::FetchResult> fetch_results) {
   for (const FederatedManifestRequester::FetchResult& fetch_result :
        fetch_results) {
-    if (fetch_result.error) {
-      const FederatedManifestRequester::FetchError& fetch_error =
-          *fetch_result.error;
-      if (fetch_error.additional_console_error_message) {
-        render_frame_host().AddMessageToConsole(
-            blink::mojom::ConsoleMessageLevel::kError,
-            *fetch_error.additional_console_error_message);
-      }
-      CompleteRequestWithError(fetch_error.result, fetch_error.token_status,
-                               /*should_delay_callback=*/true);
-      return;
-    }
-
     const GURL& identity_provider_config_url =
         fetch_result.identity_provider_config_url;
     auto get_info_it = get_infos.find(identity_provider_config_url);
@@ -682,9 +666,24 @@
         std::make_unique<IdentityProviderInfo>(
             std::move(get_info_it->second.provider),
             std::move(fetch_result.endpoints),
-            std::move(*fetch_result.metadata),
+            fetch_result.metadata ? std::move(*fetch_result.metadata)
+                                  : IdentityProviderMetadata(),
             get_info_it->second.prefer_auto_signin);
 
+    if (fetch_result.error) {
+      const FederatedManifestRequester::FetchError& fetch_error =
+          *fetch_result.error;
+      if (fetch_error.additional_console_error_message) {
+        render_frame_host().AddMessageToConsole(
+            blink::mojom::ConsoleMessageLevel::kError,
+            *fetch_error.additional_console_error_message);
+      }
+      OnFetchDataForIdpFailed(std::move(idp_info), fetch_error.result,
+                              fetch_error.token_status,
+                              /*should_delay_callback=*/true);
+      continue;
+    }
+
     // Make sure that we don't fetch accounts if the IDP sign-in bit is reset to
     // false during the API call. e.g. by the login/logout HEADER.
     idp_info->has_failing_idp_signin_status =
@@ -692,10 +691,16 @@
                                             permission_delegate_);
     if (idp_info->has_failing_idp_signin_status &&
         GetFedCmIdpSigninStatusMode() == FedCmIdpSigninStatusMode::ENABLED) {
-      CompleteRequestWithError(FederatedAuthRequestResult::kError,
-                               TokenStatus::kNotSignedInWithIdp,
-                               /*should_delay_callback=*/true);
-      return;
+      // Do not send metrics for IDP where the user is not signed-in in order
+      // to prevent IDP from using the user IP to make a probabilistic model
+      // of which websites a user visits.
+      idp_info->endpoints.metrics = GURL();
+
+      OnFetchDataForIdpFailed(std::move(idp_info),
+                              FederatedAuthRequestResult::kError,
+                              TokenStatus::kNotSignedInWithIdp,
+                              /*should_delay_callback=*/true);
+      continue;
     }
 
     GURL accounts_endpoint = idp_info->endpoints.accounts;
@@ -712,15 +717,60 @@
     const IdpNetworkRequestManager::AccountList& accounts,
     IdpNetworkRequestManager::FetchStatus status,
     IdpNetworkRequestManager::ClientMetadata client_metadata) {
-  MaybeShowAccountsDialog(std::move(idp_info), accounts, client_metadata);
+  // TODO(yigu): Clean up the client metadata related errors for metrics and
+  // console logs.
+  OnFetchDataForIdpSucceeded(std::move(idp_info), accounts, client_metadata);
 }
 
-void FederatedAuthRequestImpl::MaybeShowAccountsDialog(
+void FederatedAuthRequestImpl::OnFetchDataForIdpSucceeded(
     std::unique_ptr<IdentityProviderInfo> idp_info,
     const IdpNetworkRequestManager::AccountList& accounts,
     const IdpNetworkRequestManager::ClientMetadata& client_metadata) {
-  // TODO(yigu): Clean up the client metadata related errors for metrics and
-  // console logs.
+  const GURL& idp_config_url = idp_info->provider.config_url;
+  const std::string idp_for_display = FormatUrlForDisplay(idp_config_url);
+  idp_info->data = IdentityProviderData(
+      idp_for_display, accounts, idp_info->metadata,
+      ClientMetadata{GURL(client_metadata.terms_of_service_url),
+                     GURL(client_metadata.privacy_policy_url)});
+  idp_infos_[idp_config_url] = std::move(idp_info);
+
+  pending_idps_.erase(idp_config_url);
+  MaybeShowAccountsDialog();
+}
+
+void FederatedAuthRequestImpl::OnFetchDataForIdpFailed(
+    const std::unique_ptr<IdentityProviderInfo> idp_info,
+    blink::mojom::FederatedAuthRequestResult result,
+    absl::optional<TokenStatus> token_status,
+    bool should_delay_callback) {
+  const GURL& idp_config_url = idp_info->provider.config_url;
+  if (idp_order_.size() == 1u) {
+    CompleteRequestWithError(result, token_status, should_delay_callback);
+    return;
+  }
+
+  AddInspectorIssue(result);
+  AddConsoleErrorMessage(result);
+
+  if (IsFedCmMetricsEndpointEnabled())
+    SendFailedTokenRequestMetrics(idp_info->endpoints.metrics, result);
+
+  pending_idps_.erase(idp_config_url);
+  metrics_endpoints_.erase(idp_config_url);
+  std::vector<GURL>::iterator idp_order_new_end_it =
+      std::remove(idp_order_.begin(), idp_order_.end(), idp_config_url);
+  idp_order_.erase(idp_order_new_end_it, idp_order_.end());
+
+  idp_infos_.erase(idp_config_url);
+  // Do not use `idp_config_url` after this line because the reference is no
+  // longer valid.
+
+  MaybeShowAccountsDialog();
+}
+
+void FederatedAuthRequestImpl::MaybeShowAccountsDialog() {
+  if (!pending_idps_.empty())
+    return;
 
   bool is_visible = (render_frame_host().IsActive() &&
                      render_frame_host().GetVisibilityState() ==
@@ -735,28 +785,9 @@
     return;
   }
 
-  WebContents* rp_web_contents =
-      WebContents::FromRenderFrameHost(&render_frame_host());
-  DCHECK(render_frame_host().GetMainFrame()->IsInPrimaryMainFrame());
-
-  ClientMetadata metadata{GURL(client_metadata.terms_of_service_url),
-                          GURL(client_metadata.privacy_policy_url)};
-
   show_accounts_dialog_time_ = base::TimeTicks::Now();
   fedcm_metrics_->RecordShowAccountsDialogTime(show_accounts_dialog_time_ -
                                                start_time_);
-
-  GURL idp_provider_config_url = idp_info->provider.config_url;
-  std::string idp_for_display = FormatUrlForDisplay(idp_provider_config_url);
-  idp_info->data = IdentityProviderData(idp_for_display, accounts,
-                                        idp_info->metadata, metadata);
-  idp_infos_[idp_provider_config_url] = std::move(idp_info);
-  // Do not use `idp_info` after this line.
-
-  pending_idps_.erase(idp_provider_config_url);
-  if (!pending_idps_.empty())
-    return;
-
   std::string rp_url_for_display = FormatOriginForDisplay(GetEmbeddingOrigin());
 
   bool prefer_auto_signin = true;
@@ -769,6 +800,10 @@
     }
   }
 
+  WebContents* rp_web_contents =
+      WebContents::FromRenderFrameHost(&render_frame_host());
+  DCHECK(render_frame_host().GetMainFrame()->IsInPrimaryMainFrame());
+
   bool screen_reader_is_on = rp_web_contents->GetAccessibilityMode().has_mode(
       ui::AXMode::kScreenReader);
   // Auto signs in returning users if they have a single account and are
@@ -793,15 +828,16 @@
 }
 
 void FederatedAuthRequestImpl::HandleAccountsFetchFailure(
-    const url::Origin& idp_origin,
+    std::unique_ptr<IdentityProviderInfo> idp_info,
     blink::mojom::FederatedAuthRequestResult result,
     absl::optional<TokenStatus> token_status) {
   if (GetFedCmIdpSigninStatusMode() == FedCmIdpSigninStatusMode::DISABLED) {
-    CompleteRequestWithError(result, token_status,
-                             /*should_delay_callback=*/true);
+    OnFetchDataForIdpFailed(std::move(idp_info), result, token_status,
+                            /*should_delay_callback=*/true);
     return;
   }
 
+  url::Origin idp_origin = url::Origin::Create(idp_info->provider.config_url);
   const absl::optional<bool> idp_signin_status =
       permission_delegate_->GetIdpSigninStatus(idp_origin);
 
@@ -810,8 +846,8 @@
 
   if (!idp_signin_status.has_value() ||
       GetFedCmIdpSigninStatusMode() == FedCmIdpSigninStatusMode::METRICS_ONLY) {
-    CompleteRequestWithError(result, token_status,
-                             /*should_delay_callback=*/true);
+    OnFetchDataForIdpFailed(std::move(idp_info), result, token_status,
+                            /*should_delay_callback=*/true);
     return;
   }
 
@@ -851,7 +887,7 @@
     case IdpNetworkRequestManager::ParseStatus::kHttpNotFoundError: {
       MaybeAddResponseCodeToConsole(kAccountsUrl, status.response_code);
       HandleAccountsFetchFailure(
-          idp_origin,
+          std::move(idp_info),
           FederatedAuthRequestResult::kErrorFetchingAccountsHttpNotFound,
           TokenStatus::kAccountsHttpNotFound);
       return;
@@ -859,7 +895,7 @@
     case IdpNetworkRequestManager::ParseStatus::kNoResponseError: {
       MaybeAddResponseCodeToConsole(kAccountsUrl, status.response_code);
       HandleAccountsFetchFailure(
-          idp_origin,
+          std::move(idp_info),
           FederatedAuthRequestResult::kErrorFetchingAccountsNoResponse,
           TokenStatus::kAccountsNoResponse);
       return;
@@ -867,7 +903,7 @@
     case IdpNetworkRequestManager::ParseStatus::kInvalidResponseError: {
       MaybeAddResponseCodeToConsole(kAccountsUrl, status.response_code);
       HandleAccountsFetchFailure(
-          idp_origin,
+          std::move(idp_info),
           FederatedAuthRequestResult::kErrorFetchingAccountsInvalidResponse,
           TokenStatus::kAccountsInvalidResponse);
       return;
@@ -907,8 +943,8 @@
                 weak_ptr_factory_.GetWeakPtr(), std::move(idp_info),
                 std::move(accounts)));
       } else {
-        MaybeShowAccountsDialog(std::move(idp_info), accounts,
-                                IdpNetworkRequestManager::ClientMetadata());
+        OnFetchDataForIdpSucceeded(std::move(idp_info), accounts,
+                                   IdpNetworkRequestManager::ClientMetadata());
       }
     }
   }
@@ -1216,26 +1252,12 @@
       result != FederatedAuthRequestResult::kSuccess) {
     errors_logged_to_console_ = true;
 
-    // It would be possible to add this inspector issue on the renderer, which
-    // will receive the callback. However, it is preferable to do so on the
-    // browser because this is closer to the source, which means adding
-    // additional metadata is easier. In addition, in the future we may only
-    // need to pass a small amount of information to the renderer in the case of
-    // an error, so it would be cleaner to do this by reporting the inspector
-    // issue from the browser.
     AddInspectorIssue(result);
     AddConsoleErrorMessage(result);
 
     if (IsFedCmMetricsEndpointEnabled()) {
-      for (const auto& metrics_endpoint_kv : metrics_endpoints_) {
-        const GURL& metrics_endpoint = metrics_endpoint_kv.second;
-        if (!metrics_endpoint.is_valid())
-          continue;
-
-        network_manager_->SendFailedTokenRequestMetrics(
-            metrics_endpoint,
-            FederatedAuthRequestResultToMetricsEndpointErrorCode(result));
-      }
+      for (const auto& metrics_endpoint_kv : metrics_endpoints_)
+        SendFailedTokenRequestMetrics(metrics_endpoint_kv.second, result);
     }
   }
 
@@ -1259,6 +1281,18 @@
   }
 }
 
+void FederatedAuthRequestImpl::SendFailedTokenRequestMetrics(
+    const GURL& metrics_endpoint,
+    blink::mojom::FederatedAuthRequestResult result) {
+  DCHECK(IsFedCmMetricsEndpointEnabled());
+  if (!metrics_endpoint.is_valid())
+    return;
+
+  network_manager_->SendFailedTokenRequestMetrics(
+      metrics_endpoint,
+      FederatedAuthRequestResultToMetricsEndpointErrorCode(result));
+}
+
 void FederatedAuthRequestImpl::CleanUp() {
   weak_ptr_factory_.InvalidateWeakPtrs();
   request_dialog_controller_.reset();
@@ -1279,6 +1313,14 @@
 void FederatedAuthRequestImpl::AddInspectorIssue(
     FederatedAuthRequestResult result) {
   DCHECK_NE(result, FederatedAuthRequestResult::kSuccess);
+
+  // It would be possible to add this inspector issue on the renderer, which
+  // will receive the callback. However, it is preferable to do so on the
+  // browser because this is closer to the source, which means adding
+  // additional metadata is easier. In addition, in the future we may only
+  // need to pass a small amount of information to the renderer in the case of
+  // an error, so it would be cleaner to do this by reporting the inspector
+  // issue from the browser.
   auto details = blink::mojom::InspectorIssueDetails::New();
   auto federated_auth_request_details =
       blink::mojom::FederatedAuthRequestIssueDetails::New(result);
diff --git a/content/browser/webid/federated_auth_request_impl.h b/content/browser/webid/federated_auth_request_impl.h
index 83ab1bb..7c9c6d88 100644
--- a/content/browser/webid/federated_auth_request_impl.h
+++ b/content/browser/webid/federated_auth_request_impl.h
@@ -119,15 +119,28 @@
       const IdpNetworkRequestManager::AccountList& accounts,
       IdpNetworkRequestManager::FetchStatus status,
       IdpNetworkRequestManager::ClientMetadata client_metadata);
-  void MaybeShowAccountsDialog(
+
+  // Called when all of the data needed to display the FedCM prompt has been
+  // fetched for `idp_info`.
+  void OnFetchDataForIdpSucceeded(
       std::unique_ptr<IdentityProviderInfo> idp_info,
       const IdpNetworkRequestManager::AccountList& accounts,
       const IdpNetworkRequestManager::ClientMetadata& client_metadata);
 
+  // Called when there is an error in fetching information to show the prompt
+  // for a given IDP - `idp_info`.
+  void OnFetchDataForIdpFailed(
+      std::unique_ptr<IdentityProviderInfo> idp_info,
+      blink::mojom::FederatedAuthRequestResult result,
+      absl::optional<content::FedCmRequestIdTokenStatus> token_status,
+      bool should_delay_callback);
+
+  void MaybeShowAccountsDialog();
+
   // Updates the IdpSigninStatus in case of accounts fetch failure and shows a
   // failure UI if applicable.
   void HandleAccountsFetchFailure(
-      const url::Origin& idp_origin,
+      std::unique_ptr<IdentityProviderInfo> idp_info,
       blink::mojom::FederatedAuthRequestResult result,
       absl::optional<content::FedCmRequestIdTokenStatus> token_status);
 
@@ -153,6 +166,7 @@
                                const std::string& token);
   void DispatchOneLogout();
   void OnLogoutCompleted();
+
   void CompleteRequestWithError(
       blink::mojom::FederatedAuthRequestResult result,
       absl::optional<content::FedCmRequestIdTokenStatus> token_status,
@@ -165,6 +179,12 @@
       bool should_delay_callback);
   void CompleteLogoutRequest(blink::mojom::LogoutRpsStatus);
 
+  // Notifies metrics endpoint that either the user did not select the IDP in
+  // the prompt or that there was an error in fetching data for the IDP.
+  void SendFailedTokenRequestMetrics(
+      const GURL& metrics_endpoint,
+      blink::mojom::FederatedAuthRequestResult result);
+
   void CleanUp();
 
   std::unique_ptr<IdpNetworkRequestManager> CreateNetworkManager();
diff --git a/content/browser/webid/federated_auth_request_impl_unittest.cc b/content/browser/webid/federated_auth_request_impl_unittest.cc
index de373bec..bdcfc49 100644
--- a/content/browser/webid/federated_auth_request_impl_unittest.cc
+++ b/content/browser/webid/federated_auth_request_impl_unittest.cc
@@ -136,13 +136,23 @@
   bool prefer_auto_sign_in;
 };
 
+// Bitshift to get from MANIFEST->MANIFEST_MULTI,
+// CLIENT_METADATA->CLIENT_METADATA_MULTI etc.
+const int kFetchedEndpointMultiBitshift = 5;
+
 enum FetchedEndpoint {
   MANIFEST = 1,
   CLIENT_METADATA = 1 << 1,
   ACCOUNTS = 1 << 2,
   TOKEN = 1 << 3,
-  REVOCATION = 1 << 4,
-  MANIFEST_LIST = 1 << 5,
+  MANIFEST_LIST = 1 << 4,
+
+  MANIFEST_MULTI = MANIFEST | (MANIFEST << kFetchedEndpointMultiBitshift),
+  CLIENT_METADATA_MULTI =
+      CLIENT_METADATA | (CLIENT_METADATA << kFetchedEndpointMultiBitshift),
+  ACCOUNTS_MULTI = ACCOUNTS | (ACCOUNTS << kFetchedEndpointMultiBitshift),
+  MANIFEST_LIST_MULTI =
+      MANIFEST_LIST | (MANIFEST_LIST << kFetchedEndpointMultiBitshift),
 };
 
 // All endpoints which are fetched in a successful
@@ -152,10 +162,15 @@
     FetchedEndpoint::ACCOUNTS | FetchedEndpoint::TOKEN |
     FetchedEndpoint::MANIFEST_LIST;
 
+int FETCH_ENDPOINT_ALL_REQUEST_TOKEN_MULTI =
+    FetchedEndpoint::MANIFEST_MULTI | FetchedEndpoint::CLIENT_METADATA_MULTI |
+    FetchedEndpoint::ACCOUNTS_MULTI | FetchedEndpoint::TOKEN |
+    FetchedEndpoint::MANIFEST_LIST_MULTI;
+
 // Expected return values from a call to RequestToken.
 struct RequestExpectations {
   absl::optional<RequestTokenStatus> return_status;
-  absl::optional<FederatedAuthRequestResult> devtools_issue_status;
+  std::vector<FederatedAuthRequestResult> devtools_issue_statuses;
   absl::optional<std::string> selected_idp_config_url;
   // Any combination of FetchedEndpoint flags.
   int fetched_endpoints;
@@ -249,8 +264,16 @@
     /*wait_for_callback=*/true};
 
 static const RequestExpectations kExpectationSuccess{
-    RequestTokenStatus::kSuccess, FederatedAuthRequestResult::kSuccess,
-    kProviderUrlFull, FETCH_ENDPOINT_ALL_REQUEST_TOKEN};
+    RequestTokenStatus::kSuccess,
+    {FederatedAuthRequestResult::kSuccess},
+    kProviderUrlFull,
+    FETCH_ENDPOINT_ALL_REQUEST_TOKEN};
+
+static const RequestExpectations kExpectationSuccessMultiIdp{
+    RequestTokenStatus::kSuccess,
+    {FederatedAuthRequestResult::kSuccess},
+    kProviderUrlFull,
+    FETCH_ENDPOINT_ALL_REQUEST_TOKEN_MULTI};
 
 static const RequestParameters kDefaultMultiIdpRequestParameters{
     std::vector<IdentityProviderParameters>{
@@ -465,7 +488,7 @@
 
   void FetchManifestList(const GURL& provider,
                          FetchManifestListCallback callback) override {
-    fetched_endpoints_ |= FetchedEndpoint::MANIFEST_LIST;
+    add_fetched_endpoint(FetchedEndpoint::MANIFEST_LIST);
 
     const char* provider_key = ConvertProviderToChar(provider);
     std::set<GURL> url_set(
@@ -480,7 +503,7 @@
                      int idp_brand_icon_ideal_size,
                      int idp_brand_icon_minimum_size,
                      FetchManifestCallback callback) override {
-    fetched_endpoints_ |= FetchedEndpoint::MANIFEST;
+    add_fetched_endpoint(FetchedEndpoint::MANIFEST);
 
     const char* provider_key = ConvertProviderToChar(provider);
     IdpNetworkRequestManager::Endpoints endpoints;
@@ -505,7 +528,7 @@
   void FetchClientMetadata(const GURL& endpoint,
                            const std::string& client_id,
                            FetchClientMetadataCallback callback) override {
-    fetched_endpoints_ |= FetchedEndpoint::CLIENT_METADATA;
+    add_fetched_endpoint(FetchedEndpoint::CLIENT_METADATA);
 
     // Find the info of the provider with the same client metadata endpoint.
     MockIdpInfo info;
@@ -526,7 +549,7 @@
   void SendAccountsRequest(const GURL& accounts_url,
                            const std::string& client_id,
                            AccountsRequestCallback callback) override {
-    fetched_endpoints_ |= FetchedEndpoint::ACCOUNTS;
+    add_fetched_endpoint(FetchedEndpoint::ACCOUNTS);
 
     // Find the info of the provider with the same accounts endpoint.
     MockIdpInfo info;
@@ -545,7 +568,7 @@
                         const std::string& account,
                         const std::string& url_encoded_post_data,
                         TokenRequestCallback callback) override {
-    fetched_endpoints_ |= FetchedEndpoint::TOKEN;
+    add_fetched_endpoint(FetchedEndpoint::TOKEN);
 
     std::string delivered_token =
         config_.token_response.parse_status == ParseStatus::kSuccess
@@ -569,6 +592,15 @@
   std::vector<base::OnceClosure> delayed_callbacks_;
 
  private:
+  void add_fetched_endpoint(int fetched_endpoint) {
+    if ((fetched_endpoints_ & fetched_endpoint) != 0) {
+      // Endpoint has already been fetched. Mark endpoint as fetched multiple
+      // times (Example: MANIFEST_MULTI).
+      fetched_endpoint <<= kFetchedEndpointMultiBitshift;
+    }
+    fetched_endpoints_ |= fetched_endpoint;
+  }
+
   const char* ConvertProviderToChar(const GURL& provider) {
     // TODO(crbug.com/1362079): We iterate through config_idp_info to find the
     // correct provider. This is because we cannot have a static GURL
@@ -762,19 +794,33 @@
     EXPECT_EQ(expectation.fetched_endpoints,
               test_network_request_manager_->get_fetched_endpoints());
 
-    if (expectation.devtools_issue_status) {
-      int issue_count = main_test_rfh()->GetFederatedAuthRequestIssueCount(
-          *expectation.devtools_issue_status);
-      if (std::get<0>(auth_response) == RequestTokenStatus::kSuccess) {
-        EXPECT_EQ(0, issue_count);
-      } else {
-        EXPECT_LT(0, issue_count);
+    if (!expectation.devtools_issue_statuses.empty()) {
+      std::map<FederatedAuthRequestResult, int> devtools_issue_counts;
+      for (FederatedAuthRequestResult devtools_issue_status :
+           expectation.devtools_issue_statuses) {
+        if (devtools_issue_status == FederatedAuthRequestResult::kSuccess)
+          continue;
+
+        ++devtools_issue_counts[devtools_issue_status];
       }
-      CheckConsoleMessages(*expectation.devtools_issue_status);
+
+      for (auto& [devtools_issue_status, expected_count] :
+           devtools_issue_counts) {
+        int issue_count = main_test_rfh()->GetFederatedAuthRequestIssueCount(
+            devtools_issue_status);
+        EXPECT_LE(expected_count, issue_count);
+      }
+      if (devtools_issue_counts.empty()) {
+        int issue_count =
+            main_test_rfh()->GetFederatedAuthRequestIssueCount(absl::nullopt);
+        EXPECT_EQ(0, issue_count);
+      }
+      CheckConsoleMessages(expectation.devtools_issue_statuses);
     }
   }
 
-  void CheckConsoleMessages(FederatedAuthRequestResult devtools_issue_status) {
+  void CheckConsoleMessages(
+      const std::vector<FederatedAuthRequestResult>& devtools_issue_statuses) {
     static std::unordered_map<FederatedAuthRequestResult,
                               absl::optional<std::string>>
         status_to_message = {
@@ -828,14 +874,25 @@
         };
     std::vector<std::string> messages =
         RenderFrameHostTester::For(main_rfh())->GetConsoleMessages();
-    absl::optional<std::string> expected_message =
-        status_to_message[devtools_issue_status];
-    if (!expected_message) {
-      EXPECT_EQ(0u, messages.size());
-    } else {
-      ASSERT_LE(1u, messages.size());
-      EXPECT_EQ(expected_message.value(), messages[messages.size() - 1]);
+
+    bool did_expect_any_messages = false;
+    size_t expected_message_index = messages.size() - 1;
+    for (auto statuses_reverse_it = devtools_issue_statuses.rbegin();
+         statuses_reverse_it != devtools_issue_statuses.rend();
+         ++statuses_reverse_it) {
+      absl::optional<std::string> expected_message =
+          status_to_message[*statuses_reverse_it];
+      if (!expected_message)
+        continue;
+
+      did_expect_any_messages = true;
+      ASSERT_GE(expected_message_index, 0u);
+      EXPECT_EQ(expected_message.value(), messages[expected_message_index]);
+      --expected_message_index;
     }
+
+    if (!did_expect_any_messages)
+      EXPECT_EQ(0u, messages.size());
   }
 
   std::tuple<absl::optional<RequestTokenStatus>,
@@ -1098,7 +1155,7 @@
 TEST_F(FederatedAuthRequestImplTest, ManifestListNotInList) {
   RequestExpectations request_not_in_list = {
       RequestTokenStatus::kError,
-      FederatedAuthRequestResult::kErrorManifestNotInManifestList,
+      {FederatedAuthRequestResult::kErrorManifestNotInManifestList},
       /*selected_idp_config_url=*/absl::nullopt,
       FetchedEndpoint::MANIFEST_LIST | FetchedEndpoint::MANIFEST};
 
@@ -1126,7 +1183,7 @@
 
   RequestExpectations expectations = {
       RequestTokenStatus::kError,
-      FederatedAuthRequestResult::kErrorManifestNotInManifestList,
+      {FederatedAuthRequestResult::kErrorManifestNotInManifestList},
       /*selected_idp_config_url=*/absl::nullopt,
       FetchedEndpoint::MANIFEST_LIST | FetchedEndpoint::MANIFEST};
   RunAuthTest(parameters, expectations, config);
@@ -1138,7 +1195,7 @@
   configuration.idp_info[kProviderUrlFull].manifest.token_endpoint = "";
   RequestExpectations expectations = {
       RequestTokenStatus::kError,
-      FederatedAuthRequestResult::kErrorFetchingManifestInvalidResponse,
+      {FederatedAuthRequestResult::kErrorFetchingManifestInvalidResponse},
       /*selected_idp_config_url=*/absl::nullopt,
       FetchedEndpoint::MANIFEST | FetchedEndpoint::MANIFEST_LIST};
   RunAuthTest(kDefaultRequestParameters, expectations, configuration);
@@ -1160,7 +1217,7 @@
   configuration.idp_info[kProviderUrlFull].manifest.accounts_endpoint = "";
   RequestExpectations expectations = {
       RequestTokenStatus::kError,
-      FederatedAuthRequestResult::kErrorFetchingManifestInvalidResponse,
+      {FederatedAuthRequestResult::kErrorFetchingManifestInvalidResponse},
       /*selected_idp_config_url=*/absl::nullopt,
       FetchedEndpoint::MANIFEST | FetchedEndpoint::MANIFEST_LIST};
   RunAuthTest(kDefaultRequestParameters, expectations, configuration);
@@ -1182,7 +1239,8 @@
   configuration.idp_info[kProviderUrlFull].manifest.client_metadata_endpoint =
       "";
   RequestExpectations expectations = {
-      RequestTokenStatus::kSuccess, FederatedAuthRequestResult::kSuccess,
+      RequestTokenStatus::kSuccess,
+      {FederatedAuthRequestResult::kSuccess},
       kProviderUrlFull,
       FETCH_ENDPOINT_ALL_REQUEST_TOKEN & ~FetchedEndpoint::CLIENT_METADATA};
   RunAuthTest(kDefaultRequestParameters, expectations, configuration);
@@ -1196,7 +1254,7 @@
       kCrossOriginAccountsEndpoint;
   RequestExpectations expectations = {
       RequestTokenStatus::kError,
-      FederatedAuthRequestResult::kErrorFetchingManifestInvalidResponse,
+      {FederatedAuthRequestResult::kErrorFetchingManifestInvalidResponse},
       /*selected_idp_config_url=*/absl::nullopt,
       FetchedEndpoint::MANIFEST | FetchedEndpoint::MANIFEST_LIST};
   RunAuthTest(kDefaultRequestParameters, expectations, configuration);
@@ -1211,7 +1269,7 @@
       /*prefer_auto_sign_in=*/false};
   MockConfiguration configuration = kConfigurationValid;
   RequestExpectations expectations = {RequestTokenStatus::kError,
-                                      FederatedAuthRequestResult::kError,
+                                      {FederatedAuthRequestResult::kError},
                                       /*selected_idp_config_url=*/absl::nullopt,
                                       /*fetched_endpoints=*/0};
   RunAuthTest(request, expectations, configuration);
@@ -1228,7 +1286,7 @@
       ParseStatus::kNoResponseError;
   RequestExpectations expectations = {
       RequestTokenStatus::kError,
-      FederatedAuthRequestResult::kErrorFetchingAccountsNoResponse,
+      {FederatedAuthRequestResult::kErrorFetchingAccountsNoResponse},
       /*selected_idp_config_url=*/absl::nullopt,
       FetchedEndpoint::MANIFEST | FetchedEndpoint::ACCOUNTS |
           FetchedEndpoint::MANIFEST_LIST};
@@ -1242,7 +1300,7 @@
       ParseStatus::kInvalidResponseError;
   RequestExpectations expectations = {
       RequestTokenStatus::kError,
-      FederatedAuthRequestResult::kErrorFetchingAccountsInvalidResponse,
+      {FederatedAuthRequestResult::kErrorFetchingAccountsInvalidResponse},
       /*selected_idp_config_url=*/absl::nullopt,
       FetchedEndpoint::MANIFEST | FetchedEndpoint::ACCOUNTS |
           FetchedEndpoint::MANIFEST_LIST};
@@ -1292,7 +1350,7 @@
   configuration.idp_info[kProviderUrlFull].manifest.token_endpoint = "";
   RequestExpectations expectations = {
       RequestTokenStatus::kError,
-      FederatedAuthRequestResult::kErrorFetchingManifestInvalidResponse,
+      {FederatedAuthRequestResult::kErrorFetchingManifestInvalidResponse},
       /*selected_idp_config_url=*/absl::nullopt,
       FetchedEndpoint::MANIFEST | FetchedEndpoint::MANIFEST_LIST};
   RunAuthTest(kDefaultRequestParameters, expectations, configuration);
@@ -1417,7 +1475,7 @@
       ParseStatus::kInvalidResponseError;
   RequestExpectations expectations = {
       RequestTokenStatus::kError,
-      FederatedAuthRequestResult::kErrorFetchingIdTokenInvalidResponse,
+      {FederatedAuthRequestResult::kErrorFetchingIdTokenInvalidResponse},
       /*selected_idp_config_url=*/absl::nullopt,
       FETCH_ENDPOINT_ALL_REQUEST_TOKEN};
   RunAuthTest(kDefaultRequestParameters, expectations, configuration);
@@ -1628,7 +1686,8 @@
   configuration.wait_for_callback = false;
   configuration.customized_dialog = true;
   RequestExpectations expectations = {
-      RequestTokenStatus::kError, FederatedAuthRequestResult::kShouldEmbargo,
+      RequestTokenStatus::kError,
+      {FederatedAuthRequestResult::kShouldEmbargo},
       /*selected_idp_config_url=*/absl::nullopt,
       FETCH_ENDPOINT_ALL_REQUEST_TOKEN & ~FetchedEndpoint::TOKEN};
   RunAuthTest(kDefaultRequestParameters, expectations, configuration);
@@ -1688,7 +1747,7 @@
   configuration.customized_dialog = true;
   RequestExpectations expectations = {
       /*return_status=*/absl::nullopt,
-      /*devtools_issue_status=*/absl::nullopt,
+      /*devtools_issue_statuses=*/{},
       /*selected_idp_config_url=*/absl::nullopt,
       FETCH_ENDPOINT_ALL_REQUEST_TOKEN & ~FetchedEndpoint::TOKEN};
   RunAuthTest(kDefaultRequestParameters, expectations, configuration);
@@ -1747,7 +1806,7 @@
   configuration.customized_dialog = true;
   RequestExpectations expectations = {
       RequestTokenStatus::kError,
-      FederatedAuthRequestResult::kErrorRpPageNotVisible,
+      {FederatedAuthRequestResult::kErrorRpPageNotVisible},
       /*selected_idp_config_url=*/absl::nullopt,
       FETCH_ENDPOINT_ALL_REQUEST_TOKEN & ~FetchedEndpoint::TOKEN};
   RunAuthTest(kDefaultRequestParameters, expectations, configuration);
@@ -1761,7 +1820,7 @@
                      ApiPermissionStatus::BLOCKED_THIRD_PARTY_COOKIES_BLOCKED);
 
   RequestExpectations expectations = {RequestTokenStatus::kError,
-                                      FederatedAuthRequestResult::kError,
+                                      {FederatedAuthRequestResult::kError},
                                       /*selected_idp_config_url=*/absl::nullopt,
                                       /*fetched_endpoints=*/0};
   RunAuthTest(kDefaultRequestParameters, expectations, kConfigurationValid);
@@ -1779,7 +1838,7 @@
                      ApiPermissionStatus::BLOCKED_VARIATIONS);
 
   RequestExpectations expectations = {RequestTokenStatus::kError,
-                                      FederatedAuthRequestResult::kError,
+                                      {FederatedAuthRequestResult::kError},
                                       /*selected_idp_config_url=*/absl::nullopt,
                                       /*fetched_endpoints=*/0};
   RunAuthTest(kDefaultRequestParameters, expectations, kConfigurationValid);
@@ -1799,7 +1858,7 @@
   MockConfiguration configuration = kConfigurationValid;
   configuration.wait_for_callback = false;
   RequestExpectations expectations = {/*return_status=*/absl::nullopt,
-                                      /*devtools_issue_status=*/absl::nullopt,
+                                      /*devtools_issue_statuses=*/{},
                                       /*selected_idp_config_url=*/absl::nullopt,
                                       /*fetched_endpoints=*/0};
   RunAuthTest(kDefaultRequestParameters, expectations, configuration);
@@ -1824,7 +1883,7 @@
   MockConfiguration configuration = kConfigurationValid;
   configuration.wait_for_callback = false;
   RequestExpectations expectations = {/*return_status=*/absl::nullopt,
-                                      /*devtools_issue_status=*/absl::nullopt,
+                                      /*devtools_issue_statuses=*/{},
                                       /*selected_idp_config_url=*/absl::nullopt,
                                       /*fetched_endpoints=*/0};
   RunAuthTest(kDefaultRequestParameters, expectations, configuration);
@@ -1964,7 +2023,8 @@
 // IdentityRequestDialogController::ShowAccountsDialog() callback requests it.
 TEST_F(FederatedAuthRequestImplTest, RequestEmbargo) {
   RequestExpectations expectations = {
-      RequestTokenStatus::kError, FederatedAuthRequestResult::kShouldEmbargo,
+      RequestTokenStatus::kError,
+      {FederatedAuthRequestResult::kShouldEmbargo},
       /*selected_idp_config_url=*/absl::nullopt,
       FETCH_ENDPOINT_ALL_REQUEST_TOKEN & ~FetchedEndpoint::TOKEN};
 
@@ -2008,7 +2068,7 @@
                      ApiPermissionStatus::BLOCKED_SETTINGS);
   RequestExpectations expectations = {
       RequestTokenStatus::kError,
-      FederatedAuthRequestResult::kErrorDisabledInSettings,
+      {FederatedAuthRequestResult::kErrorDisabledInSettings},
       /*selected_idp_config_url=*/absl::nullopt,
       /*fetched_endpoints=*/0};
   RunAuthTest(kDefaultRequestParameters, expectations, kConfigurationValid);
@@ -2051,7 +2111,7 @@
   configuration.wait_for_callback = false;
   RequestExpectations expectation = {
       /*return_status=*/absl::nullopt,
-      /*devtools_issue_status=*/absl::nullopt,
+      /*devtools_issue_statuses=*/{},
       /*selected_idp_config_url=*/absl::nullopt,
       /*fetched_endpoints=*/
       fedcm_disabled
@@ -2101,7 +2161,7 @@
   configuration.customized_dialog = true;
   RequestExpectations expectations = {
       RequestTokenStatus::kError,
-      FederatedAuthRequestResult::kErrorDisabledInSettings,
+      {FederatedAuthRequestResult::kErrorDisabledInSettings},
       /*selected_idp_config_url=*/absl::nullopt,
       FETCH_ENDPOINT_ALL_REQUEST_TOKEN & ~FetchedEndpoint::TOKEN};
 
@@ -2242,7 +2302,7 @@
 
   RequestExpectations expectations = {
       RequestTokenStatus::kError,
-      /*devtools_issue_status=*/absl::nullopt,
+      /*devtools_issue_statuses=*/{},
       /*selected_idp_config_url=*/absl::nullopt,
       FetchedEndpoint::MANIFEST | FetchedEndpoint::CLIENT_METADATA |
           FetchedEndpoint::MANIFEST_LIST | FetchedEndpoint::ACCOUNTS};
@@ -2252,7 +2312,7 @@
 // Test that the account chooser is not shown if the page navigates prior to the
 // accounts endpoint request completing and BFCache is disabled.
 TEST_F(FederatedAuthRequestImplTest,
-       NavigateDuringAccountFetchBFCacheDisabled) {
+       NavigateDuringClientMetadataFetchBFCacheDisabled) {
   base::test::ScopedFeatureList list;
   list.InitAndDisableFeature(features::kBackForwardCache);
   ASSERT_FALSE(content::IsBackForwardCacheEnabled());
@@ -2268,7 +2328,7 @@
 
   RequestExpectations expectations = {
       /*return_status=*/absl::nullopt,
-      /*devtools_issue_status=*/absl::nullopt,
+      /*devtools_issue_statuses=*/{},
       /*selected_idp_config_url=*/absl::nullopt,
       FetchedEndpoint::MANIFEST | FetchedEndpoint::CLIENT_METADATA |
           FetchedEndpoint::MANIFEST_LIST | FetchedEndpoint::ACCOUNTS};
@@ -2334,7 +2394,7 @@
       ParseStatus::kInvalidResponseError;
   RequestExpectations expectations = {
       RequestTokenStatus::kError,
-      FederatedAuthRequestResult::kErrorFetchingAccountsInvalidResponse,
+      {FederatedAuthRequestResult::kErrorFetchingAccountsInvalidResponse},
       /*selected_idp_config_url=*/absl::nullopt,
       FetchedEndpoint::MANIFEST | FetchedEndpoint::ACCOUNTS |
           FetchedEndpoint::MANIFEST_LIST};
@@ -2365,11 +2425,12 @@
   MockConfiguration configuration = kConfigurationValid;
   configuration.idp_info[kProviderUrlFull].accounts_response.parse_status =
       ParseStatus::kInvalidResponseError;
-  RequestExpectations expectations = {
-      RequestTokenStatus::kError, FederatedAuthRequestResult::kError,
-      /*selected_idp_config_url=*/absl::nullopt,
-      FetchedEndpoint::MANIFEST | FetchedEndpoint::ACCOUNTS |
-          FetchedEndpoint::MANIFEST_LIST};
+  RequestExpectations expectations = {RequestTokenStatus::kError,
+                                      {FederatedAuthRequestResult::kError},
+                                      /*selected_idp_config_url=*/absl::nullopt,
+                                      FetchedEndpoint::MANIFEST |
+                                          FetchedEndpoint::ACCOUNTS |
+                                          FetchedEndpoint::MANIFEST_LIST};
   RunAuthTest(kDefaultRequestParameters, expectations, configuration);
 }
 
@@ -2390,7 +2451,7 @@
   EXPECT_CALL(*mock_dialog_controller_, ShowFailureDialog(_, _, _, _)).Times(0);
   MockConfiguration configuration = kConfigurationValid;
   RequestExpectations expectations = {RequestTokenStatus::kError,
-                                      FederatedAuthRequestResult::kError,
+                                      {FederatedAuthRequestResult::kError},
                                       /*selected_idp_config_url=*/absl::nullopt,
                                       /*fetched_endpoints=*/0};
   RunAuthTest(kDefaultRequestParameters, expectations, configuration);
@@ -2454,10 +2515,12 @@
   MockConfiguration configuration = kConfigurationValid;
   configuration.idp_info[kProviderUrlFull].accounts_response.parse_status =
       ParseStatus::kInvalidResponseError;
-  RequestExpectations expectations = {
-      RequestTokenStatus::kError, absl::nullopt, absl::nullopt,
-      FetchedEndpoint::ACCOUNTS | FetchedEndpoint::MANIFEST |
-          FetchedEndpoint::MANIFEST_LIST};
+  RequestExpectations expectations = {RequestTokenStatus::kError,
+                                      {},
+                                      absl::nullopt,
+                                      FetchedEndpoint::ACCOUNTS |
+                                          FetchedEndpoint::MANIFEST |
+                                          FetchedEndpoint::MANIFEST_LIST};
   RunAuthTest(kDefaultRequestParameters, expectations, configuration);
 }
 
@@ -2467,8 +2530,8 @@
   base::test::ScopedFeatureList list;
   list.InitAndDisableFeature(features::kFedCmMultipleIdentityProviders);
 
-  RequestExpectations expectations = {RequestTokenStatus::kError, absl::nullopt,
-                                      absl::nullopt, 0};
+  RequestExpectations expectations = {
+      RequestTokenStatus::kError, {}, absl::nullopt, 0};
 
   RunAuthTest(kDefaultMultiIdpRequestParameters, expectations,
               kConfigurationMultiIdpValid);
@@ -2479,28 +2542,74 @@
   base::test::ScopedFeatureList list;
   list.InitAndEnableFeature(features::kFedCmMultipleIdentityProviders);
 
-  RunAuthTest(kDefaultMultiIdpRequestParameters, kExpectationSuccess,
+  RunAuthTest(kDefaultMultiIdpRequestParameters, kExpectationSuccessMultiIdp,
               kConfigurationMultiIdpValid);
 }
 
-// Test some successful IDP and some failed IDP multi IDP FedCM request.
-TEST_F(FederatedAuthRequestImplTest, PartiallySuccessfulMultiIdpRequest) {
+// Test fetching information for the 1st IdP failing, and succeeding for the
+// second.
+TEST_F(FederatedAuthRequestImplTest, FirstIdpManifestListInvalid) {
   base::test::ScopedFeatureList list;
   list.InitAndEnableFeature(features::kFedCmMultipleIdentityProviders);
 
-  EXPECT_CALL(*mock_dialog_controller_, ShowAccountsDialog(_, _, _, _, _, _))
-      .Times(0);
-
-  // Intentionally fail the first provider's request by having an invalid
+  // Intentionally fail the 1st provider's request by having an invalid
   // manifest list.
   MockConfiguration configuration = kConfigurationMultiIdpValid;
   configuration.idp_info[kProviderUrlFull].manifest_list.provider_urls =
       std::set<std::string>{"https://not-in-list.example"};
 
   RequestExpectations expectations = {
-      RequestTokenStatus::kError, absl::nullopt,
+      RequestTokenStatus::kSuccess,
+      {FederatedAuthRequestResult::kErrorManifestNotInManifestList},
+      /*selected_idp_config_url=*/kProviderTwoUrlFull,
+      FetchedEndpoint::MANIFEST_MULTI | FetchedEndpoint::MANIFEST_LIST_MULTI |
+          FetchedEndpoint::CLIENT_METADATA | FetchedEndpoint::ACCOUNTS |
+          FetchedEndpoint::TOKEN};
+
+  RunAuthTest(kDefaultMultiIdpRequestParameters, expectations, configuration);
+}
+
+// Test fetching information for the 1st IdP succeeding, and failing for the
+// second.
+TEST_F(FederatedAuthRequestImplTest, SecondIdpManifestListInvalid) {
+  base::test::ScopedFeatureList list;
+  list.InitAndEnableFeature(features::kFedCmMultipleIdentityProviders);
+
+  // Intentionally fail the 2nd provider's request by having an invalid
+  // manifest list.
+  MockConfiguration configuration = kConfigurationMultiIdpValid;
+  configuration.idp_info[kProviderTwoUrlFull].manifest_list.provider_urls =
+      std::set<std::string>{"https://not-in-list.example"};
+
+  RequestExpectations expectations = {
+      RequestTokenStatus::kSuccess,
+      {FederatedAuthRequestResult::kErrorManifestNotInManifestList},
+      /*selected_idp_config_url=*/kProviderUrlFull,
+      FetchedEndpoint::MANIFEST_MULTI | FetchedEndpoint::MANIFEST_LIST_MULTI |
+          FetchedEndpoint::CLIENT_METADATA | FetchedEndpoint::ACCOUNTS |
+          FetchedEndpoint::TOKEN};
+
+  RunAuthTest(kDefaultMultiIdpRequestParameters, expectations, configuration);
+}
+
+// Test fetching information for all of the IdPs failing.
+TEST_F(FederatedAuthRequestImplTest, AllManifestListsInvalid) {
+  base::test::ScopedFeatureList list;
+  list.InitAndEnableFeature(features::kFedCmMultipleIdentityProviders);
+
+  // Intentionally fail the requests for both IdPs by returning an invalid
+  // manifest list.
+  MockConfiguration configuration = kConfigurationMultiIdpValid;
+  configuration.idp_info[kProviderUrlFull].manifest_list.provider_urls =
+      std::set<std::string>{"https://not-in-list.example"};
+  configuration.idp_info[kProviderTwoUrlFull].manifest_list.provider_urls =
+      std::set<std::string>{"https://not-in-list.example"};
+
+  RequestExpectations expectations = {
+      RequestTokenStatus::kError,
+      {FederatedAuthRequestResult::kErrorManifestNotInManifestList},
       /*selected_idp_config_url=*/absl::nullopt,
-      FetchedEndpoint::MANIFEST_LIST | FetchedEndpoint::MANIFEST};
+      FetchedEndpoint::MANIFEST_MULTI | FetchedEndpoint::MANIFEST_LIST_MULTI};
 
   RunAuthTest(kDefaultMultiIdpRequestParameters, expectations, configuration);
 }
@@ -2520,7 +2629,7 @@
       .Times(0);
 
   RequestExpectations expectations = {RequestTokenStatus::kError,
-                                      /*devtools_issue_status=*/absl::nullopt,
+                                      /*devtools_issue_statuses=*/{},
                                       /*selected_idp_config_url=*/absl::nullopt,
                                       /*fetched_endpoints=*/0};
 
@@ -2545,7 +2654,7 @@
   configuration.customized_dialog = true;
   RequestExpectations expectations = {
       /*return_status=*/absl::nullopt,
-      /*devtools_issue_status=*/absl::nullopt,
+      /*devtools_issue_statuses=*/{},
       /*selected_idp_config_url=*/absl::nullopt,
       /*fetched_endpoints=*/FETCH_ENDPOINT_ALL_REQUEST_TOKEN &
           ~FetchedEndpoint::TOKEN};
@@ -2559,7 +2668,7 @@
   // The next FedCM request should fail since the initial request has not yet
   // been finalized.
   expectations = {RequestTokenStatus::kErrorTooManyRequests,
-                  /*devtools_issue_status=*/absl::nullopt,
+                  /*devtools_issue_statuses=*/{},
                   /*selected_idp_config_url=*/absl::nullopt,
                   /*fetched_endpoints=*/0};
   RunAuthTest(kDefaultRequestParameters, expectations, configuration);
@@ -2587,7 +2696,7 @@
   configuration.customized_dialog = true;
   RequestExpectations expectations = {
       /*return_status=*/absl::nullopt,
-      /*devtools_issue_status=*/absl::nullopt,
+      /*devtools_issue_statuses=*/{},
       /*selected_idp_config_url=*/absl::nullopt,
       /*fetched_endpoints=*/FETCH_ENDPOINT_ALL_REQUEST_TOKEN &
           ~FetchedEndpoint::TOKEN};
@@ -2634,7 +2743,7 @@
   // RequestTokenStatus::kErrorTooManyRequests since the main frame's FedCM
   // request has not yet been finalized.
   expectations = {RequestTokenStatus::kErrorTooManyRequests,
-                  /*devtools_issue_status=*/absl::nullopt,
+                  /*devtools_issue_statuses=*/{},
                   /*selected_idp_config_url=*/absl::nullopt,
                   /*fetched_endpoints=*/0};
   RunAuthTest(kDefaultRequestParameters, expectations, configuration);
@@ -2693,7 +2802,7 @@
       unique_metrics_recorder.get();
   SetNetworkRequestManager(std::move(unique_metrics_recorder));
 
-  RunAuthTest(kDefaultMultiIdpRequestParameters, kExpectationSuccess,
+  RunAuthTest(kDefaultMultiIdpRequestParameters, kExpectationSuccessMultiIdp,
               kConfigurationMultiIdpValid);
   EXPECT_THAT(metrics_recorder->get_metrics_endpoints_notified_success(),
               ElementsAre(kMetricsEndpoint));
@@ -2717,9 +2826,10 @@
   SetNetworkRequestManager(std::move(unique_metrics_recorder));
 
   RequestExpectations expectations = {
-      RequestTokenStatus::kError, FederatedAuthRequestResult::kShouldEmbargo,
+      RequestTokenStatus::kError,
+      {FederatedAuthRequestResult::kShouldEmbargo},
       /* selected_idp_config_url=*/absl::nullopt,
-      FETCH_ENDPOINT_ALL_REQUEST_TOKEN & ~FetchedEndpoint::TOKEN};
+      FETCH_ENDPOINT_ALL_REQUEST_TOKEN_MULTI & ~FetchedEndpoint::TOKEN};
 
   MockConfiguration configuration = kConfigurationMultiIdpValid;
   configuration.customized_dialog = true;
diff --git a/content/common/content_navigation_policy.cc b/content/common/content_navigation_policy.cc
index 32ebc55..3008285 100644
--- a/content/common/content_navigation_policy.cc
+++ b/content/common/content_navigation_policy.cc
@@ -33,7 +33,7 @@
     // 2GB devices which report lower RAM due to carveouts.
     int default_memory_threshold_mb =
 #if BUILDFLAG(IS_ANDROID)
-        1700;
+        1200;
 #else
         // Desktop has lower memory limitations compared to Android allowing us
         // to enable BackForwardCache for all devices.
diff --git a/content/common/frame.mojom b/content/common/frame.mojom
index 8d9b71b..768c56e 100644
--- a/content/common/frame.mojom
+++ b/content/common/frame.mojom
@@ -42,6 +42,7 @@
 import "third_party/blink/public/mojom/loader/url_loader_factory_bundle.mojom";
 import "third_party/blink/public/mojom/messaging/transferable_message.mojom";
 import "third_party/blink/public/mojom/navigation/navigation_params.mojom";
+import "third_party/blink/public/mojom/navigation/navigation_initiator_activation_and_ad_status.mojom";
 import "third_party/blink/public/mojom/page/page.mojom";
 import "third_party/blink/public/mojom/picture_in_picture_window_options/picture_in_picture_window_options.mojom";
 import "third_party/blink/public/mojom/widget/platform_widget.mojom";
@@ -591,6 +592,10 @@
 
   // Additional parameters for creating picture-in-picture windows.
   blink.mojom.PictureInPictureWindowOptions? pip_options;
+
+  // The navigation initiator's user activation and ad status.
+  blink.mojom.NavigationInitiatorActivationAndAdStatus
+      initiator_activation_and_ad_status;
 };
 
 // Operation result when the renderer asks the browser to create a new window.
diff --git a/content/public/browser/navigation_controller.h b/content/public/browser/navigation_controller.h
index 3f56a51..b9013d69 100644
--- a/content/public/browser/navigation_controller.h
+++ b/content/public/browser/navigation_controller.h
@@ -29,6 +29,7 @@
 #include "third_party/blink/public/common/navigation/impression.h"
 #include "third_party/blink/public/common/navigation/navigation_policy.h"
 #include "third_party/blink/public/common/tokens/tokens.h"
+#include "third_party/blink/public/mojom/navigation/navigation_initiator_activation_and_ad_status.mojom.h"
 #include "third_party/blink/public/mojom/navigation/was_activated_option.mojom.h"
 #include "ui/base/page_transition_types.h"
 #include "url/gurl.h"
@@ -301,6 +302,12 @@
     // Download policy to be applied if this navigation turns into a download.
     blink::NavigationDownloadPolicy download_policy;
 
+    // Common begin navigation status.
+    blink::mojom::NavigationInitiatorActivationAndAdStatus
+        initiator_activation_and_ad_status =
+            blink::mojom::NavigationInitiatorActivationAndAdStatus::
+                kDidNotStartWithTransientActivation;
+
     // Indicates that this navigation is for PDF content in a renderer.
     bool is_pdf = false;
 
diff --git a/content/public/browser/navigation_handle.h b/content/public/browser/navigation_handle.h
index 596c7eb..f92e195 100644
--- a/content/public/browser/navigation_handle.h
+++ b/content/public/browser/navigation_handle.h
@@ -31,6 +31,7 @@
 #include "third_party/blink/public/common/tokens/tokens.h"
 #include "third_party/blink/public/mojom/loader/referrer.mojom.h"
 #include "third_party/blink/public/mojom/loader/transferrable_url_loader.mojom-forward.h"
+#include "third_party/blink/public/mojom/navigation/navigation_initiator_activation_and_ad_status.mojom.h"
 #include "third_party/perfetto/include/perfetto/tracing/traced_value_forward.h"
 #include "ui/base/page_transition_types.h"
 
@@ -163,6 +164,13 @@
   //  * any other "explicit" URL navigations, e.g. bookmarks
   virtual bool IsRendererInitiated() = 0;
 
+  // The navigation initiator's user activation and ad status.
+  //
+  // TODO(yaoxia): this will be used for recording a page load UKM
+  // (https://crrev.com/c/4080612).
+  virtual blink::mojom::NavigationInitiatorActivationAndAdStatus
+  GetNavigationInitiatorActivationAndAdStatus() = 0;
+
   // Whether the previous document in this frame was same-origin with the new
   // one created by this navigation.
   //
diff --git a/content/public/test/mock_navigation_handle.h b/content/public/test/mock_navigation_handle.h
index ffee362..02746c5 100644
--- a/content/public/test/mock_navigation_handle.h
+++ b/content/public/test/mock_navigation_handle.h
@@ -66,6 +66,11 @@
   }
   // By default, MockNavigationHandles are renderer-initiated navigations.
   bool IsRendererInitiated() override { return is_renderer_initiated_; }
+  blink::mojom::NavigationInitiatorActivationAndAdStatus
+  GetNavigationInitiatorActivationAndAdStatus() override {
+    return blink::mojom::NavigationInitiatorActivationAndAdStatus::
+        kDidNotStartWithTransientActivation;
+  }
   bool IsSameOrigin() override {
     NOTIMPLEMENTED();
     return false;
diff --git a/content/public/test/test_navigation_observer.cc b/content/public/test/test_navigation_observer.cc
index f7b11747..4dd859c 100644
--- a/content/public/test/test_navigation_observer.cc
+++ b/content/public/test/test_navigation_observer.cc
@@ -274,6 +274,8 @@
   last_initiator_process_id_ = navigation_handle->GetInitiatorProcessID();
   last_navigation_succeeded_ =
       navigation_handle->HasCommitted() && !navigation_handle->IsErrorPage();
+  last_navigation_initiator_activation_and_ad_status_ =
+      navigation_handle->GetNavigationInitiatorActivationAndAdStatus();
   last_net_error_code_ = navigation_handle->GetNetErrorCode();
   last_nav_entry_id_ =
       NavigationRequest::From(navigation_handle)->nav_entry_id();
diff --git a/content/public/test/test_navigation_observer.h b/content/public/test/test_navigation_observer.h
index b54226a..6416bb5b1 100644
--- a/content/public/test/test_navigation_observer.h
+++ b/content/public/test/test_navigation_observer.h
@@ -16,6 +16,7 @@
 #include "net/base/net_errors.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/common/chrome_debug_urls.h"
+#include "third_party/blink/public/mojom/navigation/navigation_initiator_activation_and_ad_status.mojom.h"
 #include "url/gurl.h"
 
 namespace content {
@@ -99,6 +100,12 @@
   // filters, if set) succeeded.
   bool last_navigation_succeeded() const { return last_navigation_succeeded_; }
 
+  // The last navigation initiator's user activation and ad status.
+  blink::mojom::NavigationInitiatorActivationAndAdStatus
+  last_navigation_initiator_activation_and_ad_status() const {
+    return last_navigation_initiator_activation_and_ad_status_;
+  }
+
   // Returns the initiator origin of the last finished navigation (that matched
   // URL / net error filters, if set).
   const absl::optional<url::Origin>& last_initiator_origin() const {
@@ -240,6 +247,12 @@
   // True if the last navigation succeeded.
   bool last_navigation_succeeded_;
 
+  // The last navigation initiator's user activation and ad status.
+  blink::mojom::NavigationInitiatorActivationAndAdStatus
+      last_navigation_initiator_activation_and_ad_status_ =
+          blink::mojom::NavigationInitiatorActivationAndAdStatus::
+              kDidNotStartWithTransientActivation;
+
   // True if we have called EventTriggered following wait. This is used for
   // internal checks-- we expect certain conditions to be valid until we call
   // EventTriggered at which point we reset state.
diff --git a/content/renderer/content_security_policy_util.cc b/content/renderer/content_security_policy_util.cc
index a02e54f..5cb6412 100644
--- a/content/renderer/content_security_policy_util.cc
+++ b/content/renderer/content_security_policy_util.cc
@@ -48,9 +48,10 @@
       std::move(sources), BuildVectorOfStrings(source_list.nonces),
       std::move(hashes), source_list.allow_self, source_list.allow_star,
       source_list.allow_response_redirects, source_list.allow_inline,
-      source_list.allow_eval, source_list.allow_wasm_eval,
-      source_list.allow_wasm_unsafe_eval, source_list.allow_dynamic,
-      source_list.allow_unsafe_hashes, source_list.report_sample);
+      source_list.allow_inline_speculation_rules, source_list.allow_eval,
+      source_list.allow_wasm_eval, source_list.allow_wasm_unsafe_eval,
+      source_list.allow_dynamic, source_list.allow_unsafe_hashes,
+      source_list.report_sample);
 }
 
 blink::WebVector<blink::WebString> ToWebVectorOfWebStrings(
@@ -91,6 +92,7 @@
           source_list->allow_star,
           source_list->allow_response_redirects,
           source_list->allow_inline,
+          source_list->allow_inline_speculation_rules,
           source_list->allow_eval,
           source_list->allow_wasm_eval,
           source_list->allow_wasm_unsafe_eval,
diff --git a/content/renderer/media/renderer_webaudiodevice_impl.cc b/content/renderer/media/renderer_webaudiodevice_impl.cc
index 1163ac7..bf8eee3a2 100644
--- a/content/renderer/media/renderer_webaudiodevice_impl.cc
+++ b/content/renderer/media/renderer_webaudiodevice_impl.cc
@@ -18,6 +18,7 @@
 #include "base/task/thread_pool.h"
 #include "base/time/time.h"
 #include "media/audio/null_audio_sink.h"
+#include "media/base/audio_glitch_info.h"
 #include "media/base/audio_timestamp_helper.h"
 #include "media/base/limits.h"
 #include "media/base/silent_sink_suspender.h"
@@ -263,10 +264,11 @@
     silent_sink_suspender_->SetDetectSilence(enable_silence_detection);
 }
 
-int RendererWebAudioDeviceImpl::Render(base::TimeDelta delay,
-                                       base::TimeTicks delay_timestamp,
-                                       int prior_frames_skipped,
-                                       media::AudioBus* dest) {
+int RendererWebAudioDeviceImpl::Render(
+    base::TimeDelta delay,
+    base::TimeTicks delay_timestamp,
+    const media::AudioGlitchInfo& glitch_info,
+    media::AudioBus* dest) {
   // Wrap the output pointers using WebVector.
   CHECK_EQ(dest->channels(), sink_params_.channels());
   for (int i = 0; i < dest->channels(); ++i)
diff --git a/content/renderer/media/renderer_webaudiodevice_impl.h b/content/renderer/media/renderer_webaudiodevice_impl.h
index 62fc7d37..302fa0e 100644
--- a/content/renderer/media/renderer_webaudiodevice_impl.h
+++ b/content/renderer/media/renderer_webaudiodevice_impl.h
@@ -66,7 +66,7 @@
   // AudioRendererSink::RenderCallback implementation.
   int Render(base::TimeDelta delay,
              base::TimeTicks delay_timestamp,
-             int prior_frames_skipped,
+             const media::AudioGlitchInfo& glitch_info,
              media::AudioBus* dest) override;
 
   void OnRenderError() override;
diff --git a/content/renderer/media/renderer_webaudiodevice_impl_unittest.cc b/content/renderer/media/renderer_webaudiodevice_impl_unittest.cc
index ee3284d..71d91f6d0 100644
--- a/content/renderer/media/renderer_webaudiodevice_impl_unittest.cc
+++ b/content/renderer/media/renderer_webaudiodevice_impl_unittest.cc
@@ -12,6 +12,7 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "build/build_config.h"
 #include "media/base/audio_capturer_source.h"
+#include "media/base/audio_glitch_info.h"
 #include "media/base/limits.h"
 #include "media/base/mock_audio_renderer_sink.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -296,7 +297,7 @@
   SetupDevice(blink::WebAudioSinkDescriptor(kFrameToken));
   webaudio_device_->Start();
   mock_audio_renderer_sink_->callback_->Render(
-      base::TimeDelta::Min(), base::TimeTicks::Now(), 0,
+      base::TimeDelta::Min(), base::TimeTicks::Now(), {},
       media::AudioBus::Create(1, kHardwareBufferSize).get());
   webaudio_device_->Stop();
 }
@@ -341,12 +342,12 @@
   SetupDevice(blink::WebAudioSinkDescriptor(kFrameToken));
   webaudio_device_->Start();
   mock_audio_renderer_sink_->callback_->Render(
-      base::TimeDelta::Min(), base::TimeTicks::Now(), 0,
+      base::TimeDelta::Min(), base::TimeTicks::Now(), {},
       media::AudioBus::Create(1, kHardwareBufferSize).get());
   webaudio_device_->Stop();
   webaudio_device_->Start();
   mock_audio_renderer_sink_->callback_->Render(
-      base::TimeDelta::Min(), base::TimeTicks::Now(), 0,
+      base::TimeDelta::Min(), base::TimeTicks::Now(), {},
       media::AudioBus::Create(1, kHardwareBufferSize).get());
   webaudio_device_->Stop();
 }
diff --git a/content/renderer/pepper/pepper_media_device_manager.cc b/content/renderer/pepper/pepper_media_device_manager.cc
index 3c1d813..d9f59286 100644
--- a/content/renderer/pepper/pepper_media_device_manager.cc
+++ b/content/renderer/pepper/pepper_media_device_manager.cc
@@ -263,10 +263,14 @@
 void PepperMediaDeviceManager::DevicesEnumerated(
     DevicesOnceCallback client_callback,
     MediaDeviceType type,
-    blink::mojom::EnumerationResponsePtr response) {
+    const std::vector<blink::WebMediaDeviceInfoArray>& enumeration,
+    std::vector<blink::mojom::VideoInputDeviceCapabilitiesPtr>
+        video_input_capabilities,
+    std::vector<blink::mojom::AudioInputDeviceCapabilitiesPtr>
+        audio_input_capabilities) {
   std::move(client_callback)
-      .Run(FromMediaDeviceInfoArray(
-          type, response->enumeration[static_cast<size_t>(type)]));
+      .Run(FromMediaDeviceInfoArray(type,
+                                    enumeration[static_cast<size_t>(type)]));
 }
 
 blink::mojom::MediaStreamDispatcherHost*
diff --git a/content/renderer/pepper/pepper_media_device_manager.h b/content/renderer/pepper/pepper_media_device_manager.h
index 0d08629c..b951581 100644
--- a/content/renderer/pepper/pepper_media_device_manager.h
+++ b/content/renderer/pepper/pepper_media_device_manager.h
@@ -98,9 +98,14 @@
                       const std::string& label,
                       const blink::MediaStreamDevice& device);
 
-  void DevicesEnumerated(DevicesOnceCallback callback,
-                         MediaDeviceType type,
-                         blink::mojom::EnumerationResponsePtr response);
+  void DevicesEnumerated(
+      DevicesOnceCallback callback,
+      MediaDeviceType type,
+      const std::vector<blink::WebMediaDeviceInfoArray>& enumeration,
+      std::vector<blink::mojom::VideoInputDeviceCapabilitiesPtr>
+          video_input_capabilities,
+      std::vector<blink::mojom::AudioInputDeviceCapabilitiesPtr>
+          audio_input_capabilities);
 
   blink::mojom::MediaStreamDispatcherHost* GetMediaStreamDispatcherHost();
   blink::WebMediaStreamDeviceObserver* GetMediaStreamDeviceObserver() const;
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index ff5b733..37fe8c4d 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -5497,6 +5497,11 @@
       info->url_request.RequestorOrigin().CanAccess(
           frame_->GetSecurityOrigin()),
       has_download_sandbox_flag, from_ad);
+
+  params->initiator_activation_and_ad_status =
+      blink::GetNavigationInitiatorActivationAndAdStatus(
+          info->url_request.HasUserGesture(), info->is_ad_script_in_stack);
+
   GetFrameHost()->OpenURL(std::move(params));
 }
 
@@ -5765,6 +5770,11 @@
             -1 /* render_process_id, to be filled in the browser process */));
   }
 
+  blink::mojom::NavigationInitiatorActivationAndAdStatus
+      initiator_activation_and_ad_status =
+          blink::GetNavigationInitiatorActivationAndAdStatus(
+              info->url_request.HasUserGesture(), info->is_ad_script_in_stack);
+
   blink::mojom::BeginNavigationParamsPtr begin_navigation_params =
       blink::mojom::BeginNavigationParams::New(
           info->initiator_frame_token,
@@ -5779,7 +5789,8 @@
               ? info->url_request.TrustTokenParams()->Clone()
               : nullptr,
           info->impression, renderer_before_unload_start,
-          renderer_before_unload_end, web_bundle_token_params);
+          renderer_before_unload_end, web_bundle_token_params,
+          initiator_activation_and_ad_status);
 
   mojo::PendingAssociatedRemote<mojom::NavigationClient>
       navigation_client_remote;
@@ -6264,6 +6275,10 @@
       /*openee_can_access_opener_origin=*/true,
       !GetWebFrame()->IsAllowedToDownload(), GetWebFrame()->IsAdFrame());
 
+  params->initiator_activation_and_ad_status =
+      blink::GetNavigationInitiatorActivationAndAdStatus(
+          request.HasUserGesture(), GetWebFrame()->IsAdScriptInStack());
+
   // We preserve this information before sending the message since |params| is
   // moved on send.
   bool is_background_tab =
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index 096c8365..d938c3d6 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -2343,6 +2343,7 @@
     "../browser/picture_in_picture/picture_in_picture_service_impl_unittest.cc",
     "../browser/preloading/anchor_element_interaction_host_impl_unittest.cc",
     "../browser/preloading/preconnector_unittest.cc",
+    "../browser/preloading/prefetch/no_vary_search_helper_unittest.cc",
     "../browser/preloading/prefetch/prefetch_canary_checker_unittest.cc",
     "../browser/preloading/prefetch/prefetch_container_unittest.cc",
     "../browser/preloading/prefetch/prefetch_cookie_listener_unittest.cc",
diff --git a/content/test/data/attribution_reporting/register_source_headers_2.html b/content/test/data/attribution_reporting/register_source_headers_2.html
deleted file mode 100644
index 6c9de327..0000000
--- a/content/test/data/attribution_reporting/register_source_headers_2.html
+++ /dev/null
@@ -1 +0,0 @@
-Registers a source by providing headers.
diff --git a/content/test/data/attribution_reporting/register_source_headers_2.html.mock-http-headers b/content/test/data/attribution_reporting/register_source_headers_2.html.mock-http-headers
deleted file mode 100644
index d0f8ace0..0000000
--- a/content/test/data/attribution_reporting/register_source_headers_2.html.mock-http-headers
+++ /dev/null
@@ -1,2 +0,0 @@
-HTTP/1.1 200 OK
-Attribution-Reporting-Register-Source:{"source_event_id":"2","destination":"https://d.test"}
diff --git a/content/test/data/attribution_reporting/register_source_headers_high_priority.html b/content/test/data/attribution_reporting/register_source_headers_high_priority.html
deleted file mode 100644
index 85d016d..0000000
--- a/content/test/data/attribution_reporting/register_source_headers_high_priority.html
+++ /dev/null
@@ -1 +0,0 @@
-Registers a source with high priority.
diff --git a/content/test/data/attribution_reporting/register_source_headers_high_priority.html.mock-http-headers b/content/test/data/attribution_reporting/register_source_headers_high_priority.html.mock-http-headers
deleted file mode 100644
index 72b96412..0000000
--- a/content/test/data/attribution_reporting/register_source_headers_high_priority.html.mock-http-headers
+++ /dev/null
@@ -1,2 +0,0 @@
-HTTP/1.1 200 OK
-Attribution-Reporting-Register-Source:{"source_event_id":"3","destination":"https://d.test","priority":"10"}
diff --git a/content/test/data/attribution_reporting/register_trigger_headers_dedup.html b/content/test/data/attribution_reporting/register_trigger_headers_dedup.html
deleted file mode 100644
index b56e1988..0000000
--- a/content/test/data/attribution_reporting/register_trigger_headers_dedup.html
+++ /dev/null
@@ -1 +0,0 @@
-Registers a trigger with a dedup key.
diff --git a/content/test/data/attribution_reporting/register_trigger_headers_dedup.html.mock-http-headers b/content/test/data/attribution_reporting/register_trigger_headers_dedup.html.mock-http-headers
deleted file mode 100644
index 38358f8..0000000
--- a/content/test/data/attribution_reporting/register_trigger_headers_dedup.html.mock-http-headers
+++ /dev/null
@@ -1,2 +0,0 @@
-HTTP/1.1 200 OK
-Attribution-Reporting-Register-Trigger:{"event_trigger_data":[{"trigger_data": "1", "deduplication_key":"123"}]}
diff --git a/content/test/fuzzer/video_capture_host_mojolpm_fuzzer.cc b/content/test/fuzzer/video_capture_host_mojolpm_fuzzer.cc
index 0681d54..67dfbb5 100644
--- a/content/test/fuzzer/video_capture_host_mojolpm_fuzzer.cc
+++ b/content/test/fuzzer/video_capture_host_mojolpm_fuzzer.cc
@@ -139,7 +139,6 @@
       const std::string& salt,
       const url::Origin& security_origin,
       blink::WebMediaDeviceInfoArray* out,
-      media::mojom::DeviceEnumerationResult,
       const content::MediaDeviceEnumeration& enumeration);
 
   // A callback which confirms opening device success. This provides the
@@ -489,7 +488,6 @@
     const std::string& salt,
     const url::Origin& security_origin,
     blink::WebMediaDeviceInfoArray* out,
-    media::mojom::DeviceEnumerationResult,
     const content::MediaDeviceEnumeration& enumeration) {
   for (const auto& info :
        enumeration[static_cast<size_t>(MediaDeviceType::MEDIA_VIDEO_INPUT)]) {
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 3ac987c4..bf5a5fa 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
@@ -519,6 +519,9 @@
 # crbug.com/1288595 [ android android-pixel-6 passthrough ] conformance/canvas/render-after-resize-test.html [ Failure ]
 crbug.com/1288603 [ android android-pixel-6 passthrough angle-opengles ] conformance/extensions/angle-instanced-arrays-out-of-bounds.html [ Failure ]
 crbug.com/1357064 [ android android-pixel-6 no-passthrough ] conformance/rendering/blending.html [ Failure ]
+crbug.com/1399541 [ android android-pixel-6 renderer-skia-vulkan passthrough ] conformance/extensions/webgl-compressed-texture-s3tc-srgb.html [ RetryOnFailure ]
+crbug.com/1399541 [ android android-pixel-6 renderer-skia-vulkan no-passthrough ] conformance/glsl/functions/glsl-function-cos.html [ RetryOnFailure ]
+crbug.com/1399541 [ android android-pixel-6 renderer-skia-vulkan passthrough ] conformance/glsl/misc/shader-with-function-scoped-struct.html [ RetryOnFailure ]
 
 # Misc failures
 
diff --git a/content/test/gpu/gpu_tests/webgpu_cts_integration_test.py b/content/test/gpu/gpu_tests/webgpu_cts_integration_test.py
index 3978a17..8829ece 100644
--- a/content/test/gpu/gpu_tests/webgpu_cts_integration_test.py
+++ b/content/test/gpu/gpu_tests/webgpu_cts_integration_test.py
@@ -105,6 +105,7 @@
   _is_asan = False
   _enable_dawn_backend_validation = False
   _use_webgpu_adapter = None  # use the default
+  _original_environ = None
 
   _build_dir = None
 
@@ -253,6 +254,23 @@
     cls._use_webgpu_adapter = options.use_webgpu_adapter
 
   @classmethod
+  def _ModifyBrowserEnvironment(cls) -> None:
+    super(WebGpuCtsIntegrationTest, cls)._ModifyBrowserEnvironment()
+    if sys.platform == 'darwin' and cls._enable_dawn_backend_validation:
+      if cls._original_environ is None:
+        cls._original_environ = os.environ.copy()
+      os.environ['MTL_DEBUG_LAYER'] = '1'
+      os.environ['MTL_DEBUG_LAYER_VALIDATE_LOAD_ACTIONS'] = '1'
+      os.environ['MTL_DEBUG_LAYER_VALIDATE_STORE_ACTIONS'] = '1'
+      os.environ['MTL_DEBUG_LAYER_VALIDATE_UNRETAINED_RESOURCES'] = '4'
+
+  @classmethod
+  def _RestoreBrowserEnvironment(cls) -> None:
+    if cls._original_environ is not None:
+      os.environ = cls._original_environ.copy()
+    super(WebGpuCtsIntegrationTest, cls)._RestoreBrowserEnvironment()
+
+  @classmethod
   def GenerateGpuTests(cls, options: ct.ParsedCmdArgs) -> ct.TestGenerator:
     cls._SetClassVariablesFromOptions(options)
     if cls._test_list is None:
diff --git a/content/test/navigation_simulator_impl.cc b/content/test/navigation_simulator_impl.cc
index 7b895bc..34de5569 100644
--- a/content/test/navigation_simulator_impl.cc
+++ b/content/test/navigation_simulator_impl.cc
@@ -1320,7 +1320,9 @@
           nullptr /* trust_token_params */, impression_,
           base::TimeTicks() /* renderer_before_unload_start */,
           base::TimeTicks() /* renderer_before_unload_end */,
-          absl::nullopt /* web_bundle_token */);
+          absl::nullopt /* web_bundle_token */,
+          blink::mojom::NavigationInitiatorActivationAndAdStatus::
+              kDidNotStartWithTransientActivation);
   auto common_params = blink::CreateCommonNavigationParams();
   common_params->navigation_start = base::TimeTicks::Now();
   common_params->input_start = navigation_input_start_;
diff --git a/content/test/test_render_frame_host.cc b/content/test/test_render_frame_host.cc
index a0e03d14..75ac1b5 100644
--- a/content/test/test_render_frame_host.cc
+++ b/content/test/test_render_frame_host.cc
@@ -278,8 +278,15 @@
 }
 
 int TestRenderFrameHost::GetFederatedAuthRequestIssueCount(
-    blink::mojom::FederatedAuthRequestResult result) {
-  auto it = federated_auth_counts_.find(result);
+    absl::optional<blink::mojom::FederatedAuthRequestResult> filter) {
+  if (!filter) {
+    int total = 0;
+    for (const auto& [result, count] : federated_auth_counts_)
+      total += count;
+    return total;
+  }
+
+  auto it = federated_auth_counts_.find(*filter);
   if (it == federated_auth_counts_.end())
     return 0;
   return it->second;
@@ -410,7 +417,9 @@
           nullptr /* trust_token_params */, absl::nullopt /* impression */,
           base::TimeTicks() /* renderer_before_unload_start */,
           base::TimeTicks() /* renderer_before_unload_end */,
-          absl::nullopt /* web_bundle_token */);
+          absl::nullopt /* web_bundle_token */,
+          blink::mojom::NavigationInitiatorActivationAndAdStatus::
+              kDidNotStartWithTransientActivation);
   auto common_params = blink::CreateCommonNavigationParams();
   common_params->url = url;
   common_params->initiator_origin = GetLastCommittedOrigin();
diff --git a/content/test/test_render_frame_host.h b/content/test/test_render_frame_host.h
index 35e6b55..3756f48 100644
--- a/content/test/test_render_frame_host.h
+++ b/content/test/test_render_frame_host.h
@@ -161,10 +161,11 @@
   void DidEnforceInsecureRequestPolicy(
       blink::mojom::InsecureRequestPolicy policy);
 
-  // Returns the number of FedCM issues sent to DevTools with the given
-  // FederatedAuthRequestResult.
+  // Returns the number of FedCM issues of FederatedAuthRequestResult type
+  // `filter` sent to DevTools. If `filter` is absl::nullopt, returns the total
+  // number of FedCM issues of any type sent to DevTools.
   int GetFederatedAuthRequestIssueCount(
-      blink::mojom::FederatedAuthRequestResult result);
+      absl::optional<blink::mojom::FederatedAuthRequestResult> filter);
 
   // If set, navigations will appear to have cleared the history list in the
   // RenderFrame (DidCommitProvisionalLoadParams::history_list_was_cleared).
diff --git a/device/bluetooth/floss/bluetooth_adapter_floss.cc b/device/bluetooth/floss/bluetooth_adapter_floss.cc
index 812e18a..b38ffc4 100644
--- a/device/bluetooth/floss/bluetooth_adapter_floss.cc
+++ b/device/bluetooth/floss/bluetooth_adapter_floss.cc
@@ -1065,7 +1065,7 @@
     std::unique_ptr<device::BluetoothLowEnergyScanFilter> filter,
     base::WeakPtr<device::BluetoothLowEnergyScanSession::Delegate> delegate) {
   auto scan_session = std::make_unique<BluetoothLowEnergyScanSessionFloss>(
-      delegate,
+      std::move(filter), delegate,
       base::BindOnce(&BluetoothAdapterFloss::OnLowEnergyScanSessionDestroyed,
                      weak_ptr_factory_.GetWeakPtr()));
   FlossDBusManager::Get()->GetLEScanClient()->RegisterScanner(base::BindOnce(
@@ -1112,7 +1112,7 @@
   FlossDBusManager::Get()->GetLEScanClient()->StartScan(
       base::BindOnce(&BluetoothAdapterFloss::OnStartScan,
                      weak_ptr_factory_.GetWeakPtr(), uuid, scanner_id),
-      scanner_id, ScanSettings{}, absl::nullopt);
+      scanner_id, ScanSettings{}, scanners_[uuid]->GetFlossScanFilter());
 }
 
 void BluetoothAdapterFloss::ScanResultReceived(ScanResult scan_result) {
diff --git a/device/bluetooth/floss/bluetooth_low_energy_scan_session_floss.cc b/device/bluetooth/floss/bluetooth_low_energy_scan_session_floss.cc
index e761bca..fcd8a65 100644
--- a/device/bluetooth/floss/bluetooth_low_energy_scan_session_floss.cc
+++ b/device/bluetooth/floss/bluetooth_low_energy_scan_session_floss.cc
@@ -11,9 +11,11 @@
 namespace floss {
 
 BluetoothLowEnergyScanSessionFloss::BluetoothLowEnergyScanSessionFloss(
+    std::unique_ptr<device::BluetoothLowEnergyScanFilter> filter,
     base::WeakPtr<device::BluetoothLowEnergyScanSession::Delegate> delegate,
     base::OnceCallback<void(const std::string&)> destructor_callback)
-    : delegate_(std::move(delegate)),
+    : filter_(std::move(filter)),
+      delegate_(std::move(delegate)),
       destructor_callback_(std::move(destructor_callback)) {}
 
 BluetoothLowEnergyScanSessionFloss::~BluetoothLowEnergyScanSessionFloss() {
@@ -74,4 +76,28 @@
   uuid_ = uuid;
 }
 
+absl::optional<ScanFilter>
+BluetoothLowEnergyScanSessionFloss::GetFlossScanFilter() {
+  if (!filter_)
+    return absl::nullopt;
+
+  ScanFilter filter;
+  filter.rssi_high_threshold = filter_->device_found_rssi_threshold();
+  filter.rssi_low_threshold = filter_->device_lost_rssi_threshold();
+  filter.rssi_low_timeout = filter_->device_lost_timeout().InSeconds();
+  filter.rssi_sampling_period =
+      filter_->rssi_sampling_period().value().InMilliseconds() / 100;
+
+  for (auto& pattern : filter_->patterns()) {
+    ScanFilterPattern p;
+    p.start_position = pattern.start_position();
+    p.ad_type = static_cast<uint8_t>(pattern.data_type());
+    p.content = pattern.value();
+
+    filter.condition.patterns.push_back(p);
+  }
+
+  return filter;
+}
+
 }  // namespace floss
diff --git a/device/bluetooth/floss/bluetooth_low_energy_scan_session_floss.h b/device/bluetooth/floss/bluetooth_low_energy_scan_session_floss.h
index 00cb1f0..3fd2f45b 100644
--- a/device/bluetooth/floss/bluetooth_low_energy_scan_session_floss.h
+++ b/device/bluetooth/floss/bluetooth_low_energy_scan_session_floss.h
@@ -9,7 +9,9 @@
 #include "base/memory/weak_ptr.h"
 #include "device/bluetooth/bluetooth_device.h"
 #include "device/bluetooth/bluetooth_export.h"
+#include "device/bluetooth/bluetooth_low_energy_scan_filter.h"
 #include "device/bluetooth/bluetooth_low_energy_scan_session.h"
+#include "device/bluetooth/floss/floss_lescan_client.h"
 
 namespace floss {
 
@@ -17,6 +19,7 @@
     : public device::BluetoothLowEnergyScanSession {
  public:
   BluetoothLowEnergyScanSessionFloss(
+      std::unique_ptr<device::BluetoothLowEnergyScanFilter> filter,
       base::WeakPtr<device::BluetoothLowEnergyScanSession::Delegate> delegate,
       base::OnceCallback<void(const std::string&)> destructor_callback);
   ~BluetoothLowEnergyScanSessionFloss() override;
@@ -27,6 +30,7 @@
   void OnDeviceLost(device::BluetoothDevice* device);
   void OnRegistered(device::BluetoothUUID uuid);
   uint8_t GetScannerId() { return scanner_id_; }
+  absl::optional<ScanFilter> GetFlossScanFilter();
 
   base::WeakPtr<BluetoothLowEnergyScanSessionFloss> GetWeakPtr() {
     return weak_ptr_factory_.GetWeakPtr();
@@ -34,6 +38,7 @@
 
  protected:
  private:
+  std::unique_ptr<device::BluetoothLowEnergyScanFilter> filter_;
   base::WeakPtr<device::BluetoothLowEnergyScanSession::Delegate> delegate_;
   base::OnceCallback<void(const std::string&)> destructor_callback_;
   device::BluetoothUUID uuid_;
diff --git a/device/bluetooth/floss/floss_lescan_client.cc b/device/bluetooth/floss/floss_lescan_client.cc
index 599e8d24..f732905 100644
--- a/device/bluetooth/floss/floss_lescan_client.cc
+++ b/device/bluetooth/floss/floss_lescan_client.cc
@@ -24,10 +24,20 @@
 const char kNoCallbackRegistered[] =
     "org.chromium.bluetooth.Error.NoCallbackRegistered";
 
+ScanFilterPattern::ScanFilterPattern() = default;
+ScanFilterPattern::ScanFilterPattern(const ScanFilterPattern&) = default;
+ScanFilterPattern::~ScanFilterPattern() = default;
+
+ScanFilterCondition::ScanFilterCondition() = default;
+ScanFilterCondition::ScanFilterCondition(const ScanFilterCondition&) = default;
+ScanFilterCondition::~ScanFilterCondition() = default;
+
+ScanFilter::ScanFilter() = default;
+ScanFilter::ScanFilter(const ScanFilter&) = default;
+ScanFilter::~ScanFilter() = default;
+
 ScanResult::ScanResult() = default;
-
 ScanResult::ScanResult(const ScanResult&) = default;
-
 ScanResult::~ScanResult() = default;
 
 std::unique_ptr<FlossLEScanClient> FlossLEScanClient::Create() {
@@ -173,12 +183,24 @@
 
 template <>
 void FlossDBusClient::WriteDBusParam(dbus::MessageWriter* writer,
+                                     const ScanFilterPattern& data) {
+  dbus::MessageWriter array_writer(nullptr);
+  writer->OpenArray("{sv}", &array_writer);
+
+  WriteDictEntry(&array_writer, "start_position", data.start_position);
+  WriteDictEntry(&array_writer, "ad_type", data.ad_type);
+  WriteDictEntry(&array_writer, "content", data.content);
+
+  writer->CloseContainer(&array_writer);
+}
+
+template <>
+void FlossDBusClient::WriteDBusParam(dbus::MessageWriter* writer,
                                      const ScanFilterCondition& data) {
   dbus::MessageWriter array_writer(nullptr);
   writer->OpenArray("{sv}", &array_writer);
 
-  // TODO(b/217274013): Update fields here.
-  WriteDictEntry(&array_writer, "patterns", std::vector<uint8_t>());
+  WriteDictEntry(&array_writer, "patterns", data.patterns);
 
   writer->CloseContainer(&array_writer);
 }
@@ -189,13 +211,15 @@
   dbus::MessageWriter array_writer(nullptr);
   writer->OpenArray("{sv}", &array_writer);
 
-  // TODO(b/217274013): Update fields here.
-  WriteDictEntry(&array_writer, "rssi_high_threshold", static_cast<uint8_t>(3));
-  WriteDictEntry(&array_writer, "rssi_low_threshold", static_cast<uint8_t>(3));
-  WriteDictEntry(&array_writer, "rssi_low_timeout", static_cast<uint8_t>(3));
+  WriteDictEntry(&array_writer, "rssi_high_threshold",
+                 static_cast<uint8_t>(data.rssi_high_threshold));
+  WriteDictEntry(&array_writer, "rssi_low_threshold",
+                 static_cast<uint8_t>(data.rssi_low_threshold));
+  WriteDictEntry(&array_writer, "rssi_low_timeout",
+                 static_cast<uint8_t>(data.rssi_low_timeout));
   WriteDictEntry(&array_writer, "rssi_sampling_period",
-                 static_cast<uint8_t>(3));
-  WriteDictEntry(&array_writer, "condition", ScanFilterCondition{});
+                 static_cast<uint8_t>(data.rssi_sampling_period));
+  WriteDictEntry(&array_writer, "condition", data.condition);
 
   writer->CloseContainer(&array_writer);
 }
diff --git a/device/bluetooth/floss/floss_lescan_client.h b/device/bluetooth/floss/floss_lescan_client.h
index c540ea0..c4433db 100644
--- a/device/bluetooth/floss/floss_lescan_client.h
+++ b/device/bluetooth/floss/floss_lescan_client.h
@@ -33,11 +33,58 @@
 // TODO(b/217274013): Update structs to support filtering
 class ScanSettings {};
 
-class ScanFilterPattern {};
+struct DEVICE_BLUETOOTH_EXPORT ScanFilterPattern {
+  // Specifies the starting byte position of the pattern immediately following
+  // AD Type.
+  uint8_t start_position = 0;
 
-enum ScanFilterCondition {};
+  // Advertising Data type (https://www.bluetooth.com/specifications/assigned-numbers/).
+  uint8_t ad_type = 0;
 
-class ScanFilter {};
+  // The pattern to be matched for the specified AD Type within the
+  // advertisement packet from the specified starting byte.
+  std::vector<uint8_t> content;
+
+  ScanFilterPattern();
+  ScanFilterPattern(const ScanFilterPattern&);
+  ~ScanFilterPattern();
+};
+
+struct DEVICE_BLUETOOTH_EXPORT ScanFilterCondition {
+  // Match by pattern anywhere in the advertisement data. Multiple patterns are
+  // "OR"-ed.
+  std::vector<ScanFilterPattern> patterns;
+
+  ScanFilterCondition();
+  ScanFilterCondition(const ScanFilterCondition&);
+  ~ScanFilterCondition();
+};
+
+// Modeled based on MSFT HCI extension spec:
+// https://learn.microsoft.com/en-us/windows-hardware/drivers/bluetooth/microsoft-defined-bluetooth-hci-commands-and-events#command_parameters-1
+struct DEVICE_BLUETOOTH_EXPORT ScanFilter {
+  // Advertisements with RSSI above or equal this value is considered "found".
+  uint8_t rssi_high_threshold = 0;
+
+  // Advertisements with RSSI below or equal this value (for a period of
+  // rssi_low_timeout) is considered "lost".
+  uint8_t rssi_low_threshold = 0;
+
+  // Time in seconds over which the RSSI value should be below
+  // rssi_low_threshold before being considered "lost".
+  uint8_t rssi_low_timeout = 0;
+
+  // Sampling interval in 100 milliseconds.
+  // i.e. The real sampling period in ms = rssi_sampling_period * 100.
+  uint8_t rssi_sampling_period = 0;
+
+  //The condition to match advertisements with.
+  ScanFilterCondition condition;
+
+  ScanFilter();
+  ScanFilter(const ScanFilter&);
+  ~ScanFilter();
+};
 
 struct DEVICE_BLUETOOTH_EXPORT ScanResult {
   std::string name;
diff --git a/docs/fuchsia/web_tests.md b/docs/fuchsia/web_tests.md
index a5d5d79..924ec0b 100644
--- a/docs/fuchsia/web_tests.md
+++ b/docs/fuchsia/web_tests.md
@@ -5,9 +5,14 @@
 General instruction on running and debugging web_tests can be found
 [here](../testing/web_tests.md).
 
+Unlike on other platforms, where tests are directly invoked via the
+[blink test script](third_party/blink/tools/blinkpy/web_tests/run_web_tests.py),
+Fuchsia layers on top [its own test script] (../../build/fuchsia/test/run_test.py),
+which handles preparation such as installing the content_shell binary.
+
 Currently, only
 [a small subset of web tests](../../third_party/blink/web_tests/SmokeTests/Default.txt)
-can be run on Fuchsia. Build the target `blink_tests` first before running any
+can be run on Fuchsia. Build the target `blink_web_tests` first before running any
 of the commands below:
 
 ## Hermetic emulation
@@ -15,16 +20,11 @@
 The test script brings up an emulator, runs the tests on it, and shuts the
 emulator down when finished.
 ```bash
-$ third_party/blink/tools/run_web_tests.py -t <output-dir>  --platform=fuchsia
+$ <output-dir>/bin/run_blink_web_tests
 ```
 
 ## Run on an physical device.
 
-Note the `--fuchsia-host-ip` flag, which is the ip address of the test host that
-the Fuchsia device uses to establish a connection.
-
 ```bash
-$ third_party/blink/tools/run_web_tests.py -t <output-dir> --platform=fuchsia
---device=device --fuchsia-target-cpu=<device-cpu-arch>
---fuchsia-out-dir=/path/to/fuchsia/outdir --fuchsia-host-ip=test.host.ip.address
+$ <output-dir>/bin/run_blink_web_tests --target-id=<device-target-id>
 ```
diff --git a/docs/ui/android/dynamic_colors.md b/docs/ui/android/dynamic_colors.md
index 66a0e9b..fdf7699 100644
--- a/docs/ui/android/dynamic_colors.md
+++ b/docs/ui/android/dynamic_colors.md
@@ -61,7 +61,7 @@
 
 #### SurfaceColorDrawable
 
-[`SurfaceColorDrawable`](https://source.chromium.org/chromium/chromium/src/+/main:components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/SurfaceColorDrawable.java;drc=9636025627ac8986e82cfaeb5a633c2f7d53238f;l=41) is a custom drawable that automatically calculates its surface color based on the provided `app:surfaceElevation` attribute. This can be used in xml to define a drawable similarly to `<shape>`, e.g. [`oval_surface_1`](https://source.chromium.org/chromium/chromium/src/+/main:components/browser_ui/widget/android/java/res/drawable-v31/oval_surface_1.xml;drc=1eeb153bf06ca256b6a132d9e17fd6a83e702bc4). Similar to the `ChromeColors#getSurfaceColor()` method, the provided `app:surfaceElevation` should be one of the predefined elevation levels (mentioned above). The only limitation with `SurfaceColorDrawable` is that custom drawables are only supported API 24 and above. So, until the minimum API level for Chrome is increased from 23 to 24, this method will require keeping 2 versions of the same drawable: one for `drawable-v23` and one for `drawable-v24+`.
+[`SurfaceColorDrawable`](https://source.chromium.org/chromium/chromium/src/+/main:components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/SurfaceColorDrawable.java;drc=9636025627ac8986e82cfaeb5a633c2f7d53238f;l=41) is a custom drawable that automatically calculates its surface color based on the provided `app:surfaceElevation` attribute. This can be used in xml to define a drawable similarly to `<shape>`, e.g. [`oval_surface_1`](https://source.chromium.org/chromium/chromium/src/+/main:components/browser_ui/widget/android/java/res/drawable-v31/oval_surface_1.xml;drc=1eeb153bf06ca256b6a132d9e17fd6a83e702bc4). Similar to the `ChromeColors#getSurfaceColor()` method, the provided `app:surfaceElevation` should be one of the predefined elevation levels (mentioned above).
 
 
 ### Illustrations
diff --git a/docs/updater/design_doc.md b/docs/updater/design_doc.md
index 0d852e3..86cb507 100644
--- a/docs/updater/design_doc.md
+++ b/docs/updater/design_doc.md
@@ -693,6 +693,38 @@
 [application commands](functional_spec.md#application-commands-applicable-to-the-windows-version-of-the-updater)
 in the functional spec.
 
+#### COM interface versioning
+
+The COM interfaces are declared in `updater_idl.template`,
+`updater_internal_idl.template`, and `updater_legacy_idl.template`.
+
+* `updater_idl.template`:
+  * contains the interfaces for the currently active updater version.
+  * the interface IDs are `uuid5-generated` and are distinct based on branding.
+
+* `updater_internal_idl.template`:
+  * contains the side-by-side interfaces which allow each updater COM client to
+    talk to the same version COM server.
+  * the interface IDs are `uuid5-generated` and are distinct based on branding
+    and version.
+
+* `updater_legacy_idl.template`:
+  * contains legacy interfaces for the currently active updater version.
+  * the interface IDs are hardcoded and based on branding.
+
+The interface ID generation and substitution can be seen in the
+[build file](https://source.chromium.org/chromium/chromium/src/+/main:chrome/updater/app/server/win/BUILD.gn).
+
+The `uuid5-generation` and substitutions are done via `midl.gni` using the
+values provided via the `dynamic_guids` property.
+
+`dynamic_guids` can contain hardcoded values, for instance, in the case of
+`updater_internal_idl.template`. Or it can contain `uuid5:`-prefixed values, in
+which case the IDs are `uuid5-generated`.
+
+More information on how `uuid5-generation` works can be found
+[here](http://crrev.com/825994).
+
 #### COM Marshaling
 
 Typelib marshaling is used to marshal the updater interfaces. Each interface is
@@ -742,7 +774,7 @@
 * IDL file changes use the following rules:
   * If there are no interface parameters in any of the methods of the interface,
     simply derive the `User` and `System` suffixed interfaces from the
-    non-suffixed interface. 
+    non-suffixed interface.
 
     Example: `IUpdaterInternalCallbackUser` and `IUpdaterInternalCallbackSystem`
     derive from `IUpdaterInternalCallback` in `updater_internal_idl.template`.
diff --git a/extensions/browser/api/hid/OWNERS b/extensions/browser/api/hid/OWNERS
index b259d5a..909f91d58 100644
--- a/extensions/browser/api/hid/OWNERS
+++ b/extensions/browser/api/hid/OWNERS
@@ -1,3 +1,4 @@
 mattreynolds@chromium.org
 reillyg@chromium.org
 rockot@google.com
+chengweih@chromium.org
diff --git a/extensions/browser/api/hid/hid_device_manager.cc b/extensions/browser/api/hid/hid_device_manager.cc
index d3c2ddba..ce0d83d 100644
--- a/extensions/browser/api/hid/hid_device_manager.cc
+++ b/extensions/browser/api/hid/hid_device_manager.cc
@@ -22,6 +22,7 @@
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/device_service.h"
 #include "extensions/browser/api/device_permissions_manager.h"
+#include "extensions/common/mojom/event_dispatcher.mojom-forward.h"
 #include "extensions/common/permissions/permissions_data.h"
 #include "extensions/common/permissions/usb_device_permission.h"
 #include "mojo/public/cpp/bindings/callback_helpers.h"
@@ -29,6 +30,7 @@
 #include "services/device/public/cpp/hid/hid_device_filter.h"
 #include "services/device/public/cpp/hid/hid_usage_and_page.h"
 #include "services/device/public/mojom/hid.mojom.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace hid = extensions::api::hid;
 
@@ -79,8 +81,8 @@
     Feature::Context target_context,
     const Extension* extension,
     const base::Value::Dict* listener_filter,
-    std::unique_ptr<base::Value::List>* event_args_out,
-    mojom::EventFilteringInfoPtr* event_filtering_info_out) {
+    absl::optional<base::Value::List>& event_args_out,
+    mojom::EventFilteringInfoPtr& event_filtering_info_out) {
   if (device_manager && extension) {
     return device_manager->HasPermission(extension, device_info, false);
   }
diff --git a/extensions/browser/api/printer_provider/printer_provider_api.cc b/extensions/browser/api/printer_provider/printer_provider_api.cc
index 1477310f..5c3d8518 100644
--- a/extensions/browser/api/printer_provider/printer_provider_api.cc
+++ b/extensions/browser/api/printer_provider/printer_provider_api.cc
@@ -35,6 +35,8 @@
 #include "extensions/common/api/printer_provider_internal.h"
 #include "extensions/common/api/usb.h"
 #include "extensions/common/extension.h"
+#include "extensions/common/mojom/event_dispatcher.mojom-forward.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace extensions {
 
@@ -303,8 +305,8 @@
       Feature::Context target_context,
       const Extension* extension,
       const base::Value::Dict* listener_filter,
-      std::unique_ptr<base::Value::List>* event_args_out,
-      mojom::EventFilteringInfoPtr* event_filtering_info_out);
+      absl::optional<base::Value::List>& event_args_out,
+      mojom::EventFilteringInfoPtr& event_filtering_info_out);
 
   raw_ptr<content::BrowserContext> browser_context_;
 
@@ -759,8 +761,8 @@
     Feature::Context target_context,
     const Extension* extension,
     const base::Value::Dict* listener_filter,
-    std::unique_ptr<base::Value::List>* event_args_out,
-    mojom::EventFilteringInfoPtr* event_filtering_info_out) {
+    absl::optional<base::Value::List>& event_args_out,
+    mojom::EventFilteringInfoPtr& event_filtering_info_out) {
   if (!extension)
     return false;
   EventRouter* event_router = EventRouter::Get(browser_context_);
diff --git a/extensions/browser/api/usb/OWNERS b/extensions/browser/api/usb/OWNERS
index 3bd510d..3c8ced4 100644
--- a/extensions/browser/api/usb/OWNERS
+++ b/extensions/browser/api/usb/OWNERS
@@ -2,3 +2,4 @@
 # chrome/browser/extensions/OWNERS
 reillyg@chromium.org
 rockot@google.com
+chengweih@chromium.org
diff --git a/extensions/browser/api/usb/usb_device_manager.cc b/extensions/browser/api/usb/usb_device_manager.cc
index bc2c521..46bdeb6 100644
--- a/extensions/browser/api/usb/usb_device_manager.cc
+++ b/extensions/browser/api/usb/usb_device_manager.cc
@@ -20,10 +20,12 @@
 #include "extensions/browser/api/extensions_api_client.h"
 #include "extensions/browser/event_router_factory.h"
 #include "extensions/common/api/usb.h"
+#include "extensions/common/mojom/event_dispatcher.mojom-forward.h"
 #include "extensions/common/permissions/permissions_data.h"
 #include "extensions/common/permissions/usb_device_permission.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "services/device/public/mojom/usb_enumeration_options.mojom.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace usb = extensions::api::usb;
 
@@ -72,8 +74,8 @@
     Feature::Context target_context,
     const Extension* extension,
     const base::Value::Dict* listener_filter,
-    std::unique_ptr<base::Value::List>* event_args_out,
-    mojom::EventFilteringInfoPtr* event_filtering_info_out) {
+    absl::optional<base::Value::List>& event_args_out,
+    mojom::EventFilteringInfoPtr& event_filtering_info_out) {
   // Check install-time and optional permissions.
   std::unique_ptr<UsbDevicePermission::CheckParam> param =
       UsbDevicePermission::CheckParam::ForUsbDevice(extension, device_info);
diff --git a/extensions/browser/content_verifier_unittest.cc b/extensions/browser/content_verifier_unittest.cc
index e86be3c..a127559 100644
--- a/extensions/browser/content_verifier_unittest.cc
+++ b/extensions/browser/content_verifier_unittest.cc
@@ -205,34 +205,33 @@
   // Create a test extension with a content script and possibly a background
   // page or background script.
   scoped_refptr<Extension> CreateTestExtension() {
-    base::DictionaryValue manifest;
-    manifest.SetStringKey("name", "Dummy Extension");
-    manifest.SetStringKey("version", "1");
-    manifest.SetIntKey("manifest_version", 2);
+    base::Value::Dict manifest;
+    manifest.Set("name", "Dummy Extension");
+    manifest.Set("version", "1");
+    manifest.Set("manifest_version", 2);
 
     if (background_manifest_type_ ==
         BackgroundManifestType::kBackgroundScript) {
-      base::Value background_scripts(base::Value::Type::LIST);
+      base::Value::List background_scripts;
       background_scripts.Append("foo/bg.txt");
-      manifest.Set(
-          manifest_keys::kBackgroundScripts,
-          base::Value::ToUniquePtrValue(std::move(background_scripts)));
+      manifest.SetByDottedPath(manifest_keys::kBackgroundScripts,
+                               std::move(background_scripts));
     } else if (background_manifest_type_ ==
                BackgroundManifestType::kBackgroundPage) {
-      manifest.SetStringPath(manifest_keys::kBackgroundPage, "foo/page.txt");
+      manifest.SetByDottedPath(manifest_keys::kBackgroundPage, "foo/page.txt");
     }
 
-    base::Value content_scripts(base::Value::Type::LIST);
-    base::Value content_script(base::Value::Type::DICTIONARY);
-    base::Value js_files(base::Value::Type::LIST);
-    base::Value matches(base::Value::Type::LIST);
+    base::Value::List content_scripts;
+    base::Value::Dict content_script;
+    base::Value::List js_files;
+    base::Value::List matches;
     js_files.Append("foo/content.txt");
-    content_script.SetPath("js", std::move(js_files));
+    content_script.Set("js", std::move(js_files));
     matches.Append("http://*/*");
-    content_script.SetPath("matches", std::move(matches));
+    content_script.Set("matches", std::move(matches));
     content_scripts.Append(std::move(content_script));
     manifest.Set(api::content_scripts::ManifestKeys::kContentScripts,
-                 base::Value::ToUniquePtrValue(std::move(content_scripts)));
+                 std::move(content_scripts));
 
     base::FilePath path;
     EXPECT_TRUE(base::PathService::Get(DIR_TEST_DATA, &path));
diff --git a/extensions/browser/event_listener_map.cc b/extensions/browser/event_listener_map.cc
index c35dcf2..723ec37c 100644
--- a/extensions/browser/event_listener_map.cc
+++ b/extensions/browser/event_listener_map.cc
@@ -26,7 +26,7 @@
     const std::string& event_name,
     const std::string& extension_id,
     content::RenderProcessHost* process,
-    std::unique_ptr<base::Value::Dict> filter) {
+    absl::optional<base::Value::Dict> filter) {
   // The process parameter is nullptr when creating lazy listener.
   // TODO(richardzh): Update lazy listener creation to either calling
   // ForExtensionServiceWorker instead, or update this method signature to add a
@@ -45,7 +45,7 @@
     const std::string& event_name,
     const GURL& listener_url,
     content::RenderProcessHost* process,
-    std::unique_ptr<base::Value::Dict> filter) {
+    absl::optional<base::Value::Dict> filter) {
   // Use only the origin to identify the event listener, e.g. chrome://settings
   // for chrome://settings/accounts, to avoid multiple events being triggered
   // for the same process. See crbug.com/536858 for details. // TODO(devlin): If
@@ -65,7 +65,7 @@
     const GURL& service_worker_scope,
     int64_t service_worker_version_id,
     int worker_thread_id,
-    std::unique_ptr<base::Value::Dict> filter) {
+    absl::optional<base::Value::Dict> filter) {
   return base::WrapUnique(new EventListener(
       event_name, extension_id, service_worker_scope, process, browser_context,
       true, service_worker_version_id, worker_thread_id, std::move(filter)));
@@ -88,15 +88,13 @@
          is_for_service_worker_ == other->is_for_service_worker_ &&
          service_worker_version_id_ == other->service_worker_version_id_ &&
          worker_thread_id_ == other->worker_thread_id_ &&
-         ((!!filter_.get()) == (!!other->filter_.get())) &&
-         (!filter_.get() || *filter_ == *other->filter_);
+         filter_ == other->filter_;
 }
 
 std::unique_ptr<EventListener> EventListener::Copy() const {
-  std::unique_ptr<base::Value::Dict> filter_copy;
-  if (filter_) {
-    filter_copy = std::make_unique<base::Value::Dict>(filter_->Clone());
-  }
+  absl::optional<base::Value::Dict> filter_copy;
+  if (filter_)
+    filter_copy = filter_->Clone();
   return base::WrapUnique(new EventListener(
       event_name_, extension_id_, listener_url_, process_, browser_context_,
       is_for_service_worker_, service_worker_version_id_, worker_thread_id_,
@@ -126,7 +124,7 @@
                              bool is_for_service_worker,
                              int64_t service_worker_version_id,
                              int worker_thread_id,
-                             std::unique_ptr<base::Value::Dict> filter)
+                             absl::optional<base::Value::Dict> filter)
     : event_name_(event_name),
       extension_id_(extension_id),
       listener_url_(listener_url),
@@ -285,7 +283,8 @@
     const std::string& extension_id,
     const std::set<std::string>& event_names) {
   for (const auto& name : event_names) {
-    AddListener(EventListener::ForExtension(name, extension_id, nullptr, {}));
+    AddListener(EventListener::ForExtension(name, extension_id, nullptr,
+                                            absl::nullopt));
   }
 }
 
@@ -296,11 +295,9 @@
   for (const auto& name : event_names) {
     AddListener(EventListener::ForExtensionServiceWorker(
         name, extension_id, nullptr, browser_context,
-        // TODO(lazyboy): We need to store correct scopes of each worker into
-        // ExtensionPrefs for events. This currently assumes all workers are
-        // registered in the '/' scope. https://crbug.com/773103.
         Extension::GetBaseURLFromExtensionId(extension_id),
-        blink::mojom::kInvalidServiceWorkerVersionId, kMainThreadId, nullptr));
+        blink::mojom::kInvalidServiceWorkerVersionId, kMainThreadId,
+        absl::nullopt));
   }
 }
 
@@ -316,22 +313,16 @@
     for (const base::Value& filter_value : item.second.GetList()) {
       if (!filter_value.is_dict())
         continue;
+      const base::Value::Dict& filter = filter_value.GetDict();
       if (is_for_service_worker) {
         AddListener(EventListener::ForExtensionServiceWorker(
             item.first, extension_id, nullptr, browser_context,
-            // TODO(lazyboy): We need to store correct scopes of each worker
-            // into ExtensionPrefs for events. This currently assumes all
-            // workers are registered in the '/' scope.
-            // https://crbug.com/773103.
             Extension::GetBaseURLFromExtensionId(extension_id),
             blink::mojom::kInvalidServiceWorkerVersionId, kMainThreadId,
-            std::make_unique<base::Value::Dict>(
-                filter_value.GetDict().Clone())));
+            filter.Clone()));
       } else {
-        AddListener(
-            EventListener::ForExtension(item.first, extension_id, nullptr,
-                                        std::make_unique<base::Value::Dict>(
-                                            filter_value.GetDict().Clone())));
+        AddListener(EventListener::ForExtension(item.first, extension_id,
+                                                nullptr, filter.Clone()));
       }
     }
   }
diff --git a/extensions/browser/event_listener_map.h b/extensions/browser/event_listener_map.h
index 95e37d2..f62a480 100644
--- a/extensions/browser/event_listener_map.h
+++ b/extensions/browser/event_listener_map.h
@@ -16,6 +16,7 @@
 #include "base/values.h"
 #include "extensions/common/event_filter.h"
 #include "extensions/common/extension_id.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/mojom/service_worker/service_worker_database.mojom-forward.h"
 #include "url/gurl.h"
 
@@ -52,12 +53,12 @@
       const std::string& event_name,
       const std::string& extension_id,
       content::RenderProcessHost* process,
-      std::unique_ptr<base::Value::Dict> filter);
+      absl::optional<base::Value::Dict> filter);
   static std::unique_ptr<EventListener> ForURL(
       const std::string& event_name,
       const GURL& listener_url,
       content::RenderProcessHost* process,
-      std::unique_ptr<base::Value::Dict> filter);
+      absl::optional<base::Value::Dict> filter);
   // Constructs EventListener for an Extension service worker.
   // Similar to ForExtension above with the only difference that
   // |worker_thread_id_| contains a valid worker thread, as opposed to
@@ -71,7 +72,7 @@
       const GURL& service_worker_scope,
       int64_t service_worker_version_id,
       int worker_thread_id,
-      std::unique_ptr<base::Value::Dict> filter);
+      absl::optional<base::Value::Dict> filter);
 
   EventListener(const EventListener&) = delete;
   EventListener& operator=(const EventListener&) = delete;
@@ -98,7 +99,9 @@
   const GURL& listener_url() const { return listener_url_; }
   content::RenderProcessHost* process() const { return process_; }
   content::BrowserContext* browser_context() const { return browser_context_; }
-  base::Value::Dict* filter() const { return filter_.get(); }
+  const base::Value::Dict* filter() const {
+    return filter_.has_value() ? &*filter_ : nullptr;
+  }
   EventFilter::MatcherID matcher_id() const { return matcher_id_; }
   void set_matcher_id(EventFilter::MatcherID id) { matcher_id_ = id; }
   int64_t service_worker_version_id() const {
@@ -115,7 +118,7 @@
                 bool is_for_service_worker,
                 int64_t service_worker_version_id,
                 int worker_thread_id,
-                std::unique_ptr<base::Value::Dict> filter);
+                absl::optional<base::Value::Dict> filter);
 
   const std::string event_name_;
   const std::string extension_id_;
@@ -134,7 +137,7 @@
   // worker events, this will be kMainThreadId.
   int worker_thread_id_;
 
-  std::unique_ptr<base::Value::Dict> filter_;
+  absl::optional<base::Value::Dict> filter_;
   EventFilter::MatcherID matcher_id_ = -1;
 };
 
diff --git a/extensions/browser/event_listener_map_unittest.cc b/extensions/browser/event_listener_map_unittest.cc
index 6b7bda2f..acbefe2 100644
--- a/extensions/browser/event_listener_map_unittest.cc
+++ b/extensions/browser/event_listener_map_unittest.cc
@@ -44,7 +44,7 @@
     base::RepeatingCallback<std::unique_ptr<EventListener>(
         const std::string& /* event_name */,
         content::RenderProcessHost* /* process */,
-        std::unique_ptr<base::Value::Dict> /* filter */)>;
+        absl::optional<base::Value::Dict> /* filter */)>;
 
 class EmptyDelegate : public EventListenerMap::Delegate {
   void OnListenerAdded(const EventListener* listener) override {}
@@ -72,16 +72,15 @@
     ExtensionsTest::TearDown();
   }
 
-  std::unique_ptr<base::Value::Dict> CreateHostSuffixFilter(
-      const std::string& suffix) {
+  base::Value::Dict CreateHostSuffixFilter(const std::string& suffix) {
     base::Value::Dict filter_dict;
     filter_dict.Set("hostSuffix", suffix);
 
     base::Value::List filter_list;
     filter_list.Append(std::move(filter_dict));
 
-    auto filter = std::make_unique<base::Value::Dict>();
-    filter->Set("url", std::move(filter_list));
+    base::Value::Dict filter;
+    filter.Set("url", base::Value(std::move(filter_list)));
     return filter;
   }
 
@@ -101,7 +100,7 @@
   std::unique_ptr<EventListener> CreateLazyListener(
       const std::string& event_name,
       const ExtensionId& extension_id,
-      std::unique_ptr<base::Value::Dict> filter,
+      absl::optional<base::Value::Dict> filter,
       bool is_for_service_worker) {
     if (is_for_service_worker) {
       return EventListener::ForExtensionServiceWorker(
@@ -136,7 +135,7 @@
     const std::string& extension_id,
     const std::string& event_name,
     content::RenderProcessHost* process,
-    std::unique_ptr<base::Value::Dict> filter) {
+    absl::optional<base::Value::Dict> filter) {
   return EventListener::ForExtension(event_name, extension_id, process,
                                      std::move(filter));
 }
@@ -145,7 +144,7 @@
     const GURL& listener_url,
     const std::string& event_name,
     content::RenderProcessHost* process,
-    std::unique_ptr<base::Value::Dict> filter) {
+    absl::optional<base::Value::Dict> filter) {
   return EventListener::ForURL(event_name, listener_url, process,
                                std::move(filter));
 }
@@ -154,7 +153,7 @@
     const std::string& extension_id,
     const std::string& event_name,
     content::RenderProcessHost* process,
-    std::unique_ptr<base::Value::Dict> filter) {
+    absl::optional<base::Value::Dict> filter) {
   content::BrowserContext* browser_context =
       process ? process->GetBrowserContext() : nullptr;
   return EventListener::ForExtensionServiceWorker(
@@ -165,8 +164,8 @@
 
 void EventListenerMapTest::TestUnfilteredEventsGoToAllListeners(
     const EventListenerConstructor& constructor) {
-  listeners_->AddListener(constructor.Run(
-      kEvent1Name, process_.get(), std::make_unique<base::Value::Dict>()));
+  listeners_->AddListener(
+      constructor.Run(kEvent1Name, process_.get(), base::Value::Dict()));
   std::unique_ptr<Event> event(CreateNamedEvent(kEvent1Name));
   ASSERT_EQ(1u, listeners_->GetEventListeners(*event).size());
 }
@@ -191,9 +190,7 @@
   auto create_filter = [&](const std::string& filter_str) {
     return CreateHostSuffixFilter(filter_str);
   };
-  auto create_empty_filter = []() {
-    return std::make_unique<base::Value::Dict>();
-  };
+  auto create_empty_filter = []() { return base::Value::Dict(); };
 
   for (bool is_for_service_worker : {false, true}) {
     listeners_->AddListener(CreateLazyListener(kEvent1Name, kExt1Id,
@@ -354,15 +351,13 @@
   std::unique_ptr<Event> event(CreateNamedEvent(kEvent1Name));
   event->filter_info->url = GURL("http://www.google.com");
   std::set<const EventListener*> targets(listeners_->GetEventListeners(*event));
-  ASSERT_EQ(0u, targets.size());
+  EXPECT_EQ(0u, targets.size());
 }
 
 TEST_F(EventListenerMapTest, HostSuffixFilterEquality) {
-  std::unique_ptr<base::Value::Dict> filter1(
-      CreateHostSuffixFilter("google.com"));
-  std::unique_ptr<base::Value::Dict> filter2(
-      CreateHostSuffixFilter("google.com"));
-  ASSERT_EQ(*filter1, *filter2);
+  base::Value::Dict filter1 = CreateHostSuffixFilter("google.com");
+  base::Value::Dict filter2 = CreateHostSuffixFilter("google.com");
+  EXPECT_EQ(filter1, filter2);
 }
 
 TEST_F(EventListenerMapTest, RemoveListenersForExtension) {
@@ -405,13 +400,13 @@
 
 void EventListenerMapTest::TestAddExistingUnfilteredListener(
     const EventListenerConstructor& constructor) {
-  bool first_add = listeners_->AddListener(constructor.Run(
-      kEvent1Name, process_.get(), std::make_unique<base::Value::Dict>()));
-  bool second_add = listeners_->AddListener(constructor.Run(
-      kEvent1Name, process_.get(), std::make_unique<base::Value::Dict>()));
+  bool first_add = listeners_->AddListener(
+      constructor.Run(kEvent1Name, process_.get(), base::Value::Dict()));
+  bool second_add = listeners_->AddListener(
+      constructor.Run(kEvent1Name, process_.get(), base::Value::Dict()));
 
-  std::unique_ptr<EventListener> listener(constructor.Run(
-      kEvent1Name, process_.get(), std::make_unique<base::Value::Dict>()));
+  std::unique_ptr<EventListener> listener(
+      constructor.Run(kEvent1Name, process_.get(), base::Value::Dict()));
   bool first_remove = listeners_->RemoveListener(listener.get());
   bool second_remove = listeners_->RemoveListener(listener.get());
 
@@ -438,15 +433,12 @@
 }
 
 TEST_F(EventListenerMapTest, RemovingRouters) {
-  listeners_->AddListener(
-      EventListener::ForExtension(kEvent1Name, kExt1Id, process_.get(),
-                                  std::unique_ptr<base::Value::Dict>()));
-  listeners_->AddListener(
-      EventListener::ForURL(kEvent1Name, GURL(kURL), process_.get(),
-                            std::unique_ptr<base::Value::Dict>()));
+  listeners_->AddListener(EventListener::ForExtension(
+      kEvent1Name, kExt1Id, process_.get(), base::Value::Dict()));
+  listeners_->AddListener(EventListener::ForURL(
+      kEvent1Name, GURL(kURL), process_.get(), base::Value::Dict()));
   listeners_->AddListener(CreateEventListenerForExtensionServiceWorker(
-      kExt1Id, kEvent1Name, process_.get(),
-      std::unique_ptr<base::Value::Dict>()));
+      kExt1Id, kEvent1Name, process_.get(), absl::nullopt));
   listeners_->RemoveListenersForProcess(process_.get());
   ASSERT_FALSE(listeners_->HasListenerForEvent(kEvent1Name));
 }
@@ -455,8 +447,8 @@
     const EventListenerConstructor& constructor) {
   ASSERT_FALSE(listeners_->HasListenerForEvent(kEvent1Name));
 
-  listeners_->AddListener(constructor.Run(
-      kEvent1Name, process_.get(), std::make_unique<base::Value::Dict>()));
+  listeners_->AddListener(
+      constructor.Run(kEvent1Name, process_.get(), base::Value::Dict()));
 
   ASSERT_FALSE(listeners_->HasListenerForEvent(kEvent2Name));
   ASSERT_TRUE(listeners_->HasListenerForEvent(kEvent1Name));
@@ -483,17 +475,15 @@
   ASSERT_FALSE(listeners_->HasListenerForExtension(kExt1Id, kEvent1Name));
 
   auto create_event_listener = [&](bool is_for_service_worker, bool lazy) {
-    auto filter = std::unique_ptr<base::Value::Dict>();
     if (is_for_service_worker) {
       return EventListener::ForExtensionServiceWorker(
           kEvent1Name, kExt1Id, lazy ? nullptr : process_.get(),
           process_->GetBrowserContext(),
           Extension::GetBaseURLFromExtensionId(kExt1Id),
-          GetWorkerVersionId(lazy), GetWorkerThreadId(lazy), std::move(filter));
+          GetWorkerVersionId(lazy), GetWorkerThreadId(lazy), absl::nullopt);
     }
-    return EventListener::ForExtension(kEvent1Name, kExt1Id,
-                                       lazy ? nullptr : process_.get(),
-                                       std::move(filter));
+    return EventListener::ForExtension(
+        kEvent1Name, kExt1Id, lazy ? nullptr : process_.get(), absl::nullopt);
   };
 
   for (bool is_for_service_worker : {false, true}) {
@@ -523,10 +513,8 @@
       {"yahoo.com", "http://www.yahoo.com"},
   };
   base::Value::List filter_list;
-  for (const TestCase& test_case : kTestCases) {
-    filter_list.Append(
-        std::move(*CreateHostSuffixFilter(test_case.filter_host_suffix)));
-  }
+  for (const TestCase& test_case : kTestCases)
+    filter_list.Append(CreateHostSuffixFilter(test_case.filter_host_suffix));
 
   base::Value::Dict filtered_listeners;
   filtered_listeners.Set(kEvent1Name, std::move(filter_list));
@@ -563,8 +551,7 @@
 TEST_F(EventListenerMapTest, CorruptedExtensionPrefsShouldntCrash) {
   base::Value::Dict filtered_listeners;
   // kEvent1Name should be associated with a list, not a dictionary.
-  filtered_listeners.Set(kEvent1Name,
-                         std::move(*CreateHostSuffixFilter("google.com")));
+  filtered_listeners.Set(kEvent1Name, CreateHostSuffixFilter("google.com"));
 
   listeners_->LoadFilteredLazyListeners(browser_context(), kExt1Id, false,
                                         filtered_listeners);
diff --git a/extensions/browser/event_router.cc b/extensions/browser/event_router.cc
index 30b720b..680ef79 100644
--- a/extensions/browser/event_router.cc
+++ b/extensions/browser/event_router.cc
@@ -43,8 +43,8 @@
 #include "extensions/common/mojom/event_dispatcher.mojom.h"
 #include "extensions/common/permissions/permissions_data.h"
 #include "ipc/ipc_channel_proxy.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
-using base::ListValue;
 using content::BrowserContext;
 using content::BrowserThread;
 using content::RenderProcessHost;
@@ -55,10 +55,11 @@
 
 // A dictionary of event names to lists of filters that this extension has
 // registered from its lazy background page.
-const char kFilteredEvents[] = "filtered_events";
+constexpr char kFilteredEvents[] = "filtered_events";
 
 // Similar to |kFilteredEvents|, but applies to extension service worker events.
-const char kFilteredServiceWorkerEvents[] = "filtered_service_worker_events";
+constexpr char kFilteredServiceWorkerEvents[] =
+    "filtered_service_worker_events";
 
 // A message when mojom::EventRouter::AddListenerForMainThread() is called with
 // an invalid param.
@@ -351,7 +352,8 @@
       EventListener::ForExtensionServiceWorker(
           event_name, extension_id, nullptr, browser_context_, worker_scope_url,
           // Lazy listener, without worker version id and thread id.
-          blink::mojom::kInvalidServiceWorkerVersionId, kMainThreadId, nullptr);
+          blink::mojom::kInvalidServiceWorkerVersionId, kMainThreadId,
+          absl::nullopt);
   AddLazyEventListenerImpl(std::move(listener),
                            RegisteredEventType::kServiceWorker);
 }
@@ -457,7 +459,8 @@
       EventListener::ForExtensionServiceWorker(
           event_name, extension_id, nullptr, browser_context_, worker_scope_url,
           // Lazy listener, without worker version id and thread id.
-          blink::mojom::kInvalidServiceWorkerVersionId, kMainThreadId, nullptr);
+          blink::mojom::kInvalidServiceWorkerVersionId, kMainThreadId,
+          absl::nullopt);
   RemoveLazyEventListenerImpl(std::move(listener),
                               RegisteredEventType::kServiceWorker);
 }
@@ -504,8 +507,8 @@
 void EventRouter::AddEventListener(const std::string& event_name,
                                    RenderProcessHost* process,
                                    const std::string& extension_id) {
-  listeners_.AddListener(
-      EventListener::ForExtension(event_name, extension_id, process, nullptr));
+  listeners_.AddListener(EventListener::ForExtension(event_name, extension_id,
+                                                     process, absl::nullopt));
 }
 
 void EventRouter::AddServiceWorkerEventListener(
@@ -518,14 +521,14 @@
   listeners_.AddListener(EventListener::ForExtensionServiceWorker(
       event_name, extension_id, process, process->GetBrowserContext(),
       service_worker_scope, service_worker_version_id, worker_thread_id,
-      nullptr));
+      absl::nullopt));
 }
 
 void EventRouter::RemoveEventListener(const std::string& event_name,
                                       RenderProcessHost* process,
                                       const std::string& extension_id) {
-  std::unique_ptr<EventListener> listener =
-      EventListener::ForExtension(event_name, extension_id, process, nullptr);
+  std::unique_ptr<EventListener> listener = EventListener::ForExtension(
+      event_name, extension_id, process, absl::nullopt);
   listeners_.RemoveListener(listener.get());
 }
 
@@ -540,7 +543,7 @@
       EventListener::ForExtensionServiceWorker(
           event_name, extension_id, process, process->GetBrowserContext(),
           service_worker_scope, service_worker_version_id, worker_thread_id,
-          nullptr);
+          absl::nullopt);
   listeners_.RemoveListener(listener.get());
 }
 
@@ -548,14 +551,14 @@
                                          RenderProcessHost* process,
                                          const GURL& listener_url) {
   listeners_.AddListener(
-      EventListener::ForURL(event_name, listener_url, process, nullptr));
+      EventListener::ForURL(event_name, listener_url, process, absl::nullopt));
 }
 
 void EventRouter::RemoveEventListenerForURL(const std::string& event_name,
                                             RenderProcessHost* process,
                                             const GURL& listener_url) {
   std::unique_ptr<EventListener> listener =
-      EventListener::ForURL(event_name, listener_url, process, nullptr);
+      EventListener::ForURL(event_name, listener_url, process, absl::nullopt);
   listeners_.RemoveListener(listener.get());
 }
 
@@ -639,15 +642,16 @@
 
 void EventRouter::AddLazyEventListener(const std::string& event_name,
                                        const ExtensionId& extension_id) {
-  AddLazyEventListenerImpl(
-      EventListener::ForExtension(event_name, extension_id, nullptr, nullptr),
-      RegisteredEventType::kLazy);
+  AddLazyEventListenerImpl(EventListener::ForExtension(event_name, extension_id,
+                                                       nullptr, absl::nullopt),
+                           RegisteredEventType::kLazy);
 }
 
 void EventRouter::RemoveLazyEventListener(const std::string& event_name,
                                           const ExtensionId& extension_id) {
   RemoveLazyEventListenerImpl(
-      EventListener::ForExtension(event_name, extension_id, nullptr, nullptr),
+      EventListener::ForExtension(event_name, extension_id, nullptr,
+                                  absl::nullopt),
       RegisteredEventType::kLazy);
 }
 
@@ -665,8 +669,7 @@
     regular_listener = EventListener::ForExtensionServiceWorker(
         event_name, param->get_extension_id(), process,
         process->GetBrowserContext(), sw_identifier->scope,
-        sw_identifier->version_id, sw_identifier->thread_id,
-        std::make_unique<base::Value::Dict>(filter.Clone()));
+        sw_identifier->version_id, sw_identifier->thread_id, filter.Clone());
     if (add_lazy_listener) {
       // TODO(richardzh): take browser context from the process instead of the
       // regular browser context attached to the event router. Browser context
@@ -680,22 +683,20 @@
           sw_identifier->scope,
           // Lazy listener, without worker version id and thread id.
           blink::mojom::kInvalidServiceWorkerVersionId, kMainThreadId,
-          std::make_unique<base::Value::Dict>(filter.Clone()));
+          filter.Clone());
     }
   } else if (param->is_extension_id()) {
     regular_listener = EventListener::ForExtension(
-        event_name, param->get_extension_id(), process,
-        std::make_unique<base::Value::Dict>(filter.Clone()));
+        event_name, param->get_extension_id(), process, filter.Clone());
     if (add_lazy_listener) {
-      lazy_listener = EventListener::ForExtension(
-          event_name, param->get_extension_id(),
-          nullptr,  // Lazy, without process.
-          std::make_unique<base::Value::Dict>(filter.Clone()));
+      lazy_listener =
+          EventListener::ForExtension(event_name, param->get_extension_id(),
+                                      nullptr,  // Lazy, without process.
+                                      filter.Clone());
     }
   } else if (param->is_listener_url() && !add_lazy_listener) {
     regular_listener = EventListener::ForURL(
-        event_name, param->get_listener_url(), process,
-        std::make_unique<base::Value::Dict>(filter.Clone()));
+        event_name, param->get_listener_url(), process, filter.Clone());
   } else {
     mojo::ReportBadMessage(kAddEventListenerWithInvalidParam);
     return;
@@ -707,7 +708,7 @@
     bool added = listeners_.AddListener(std::move(lazy_listener));
     if (added) {
       AddFilterToEvent(event_name, param->get_extension_id(),
-                       is_for_service_worker, &filter);
+                       is_for_service_worker, filter);
     }
   }
 }
@@ -725,17 +726,14 @@
     listener = EventListener::ForExtensionServiceWorker(
         event_name, param->get_extension_id(), process,
         process->GetBrowserContext(), sw_identifier->scope,
-        sw_identifier->version_id, sw_identifier->thread_id,
-        std::make_unique<base::Value::Dict>(filter.Clone()));
+        sw_identifier->version_id, sw_identifier->thread_id, filter.Clone());
   } else if (param->is_extension_id()) {
     listener = EventListener::ForExtension(
-        event_name, param->get_extension_id(), process,
-        std::make_unique<base::Value::Dict>(filter.Clone()));
+        event_name, param->get_extension_id(), process, filter.Clone());
 
   } else if (param->is_listener_url() && !remove_lazy_listener) {
-    listener = EventListener::ForURL(
-        event_name, param->get_listener_url(), process,
-        std::make_unique<base::Value::Dict>(filter.Clone()));
+    listener = EventListener::ForURL(event_name, param->get_listener_url(),
+                                     process, filter.Clone());
   } else {
     mojo::ReportBadMessage(kRemoveEventListenerWithInvalidParam);
     return;
@@ -749,7 +747,7 @@
 
     if (removed) {
       RemoveFilterFromEvent(event_name, param->get_extension_id(),
-                            is_for_service_worker, &filter);
+                            is_for_service_worker, filter);
     }
   }
 }
@@ -823,7 +821,7 @@
 void EventRouter::RemoveFilterFromEvent(const std::string& event_name,
                                         const std::string& extension_id,
                                         bool is_for_service_worker,
-                                        const base::Value::Dict* filter) {
+                                        const base::Value::Dict& filter) {
   ExtensionPrefs::ScopedDictionaryUpdate update(
       extension_prefs_, extension_id,
       is_for_service_worker ? kFilteredServiceWorkerEvents : kFilteredEvents);
@@ -833,7 +831,7 @@
       !filtered_events->GetListWithoutPathExpansion(event_name, &filter_list)) {
     return;
   }
-  filter_list->erase(base::ranges::find(*filter_list, *filter));
+  filter_list->erase(base::ranges::find(*filter_list, filter));
 }
 
 const base::Value::Dict* EventRouter::GetFilteredEvents(
@@ -1059,12 +1057,12 @@
     return;
   }
 
-  std::unique_ptr<base::Value::List> modified_event_args;
+  absl::optional<base::Value::List> modified_event_args;
   mojom::EventFilteringInfoPtr modified_event_filter_info;
   if (!event.will_dispatch_callback.is_null() &&
       !event.will_dispatch_callback.Run(
           listener_context, target_context, extension, listener_filter,
-          &modified_event_args, &modified_event_filter_info)) {
+          modified_event_args, modified_event_filter_info)) {
     return;
   }
 
@@ -1271,7 +1269,7 @@
 void EventRouter::AddFilterToEvent(const std::string& event_name,
                                    const std::string& extension_id,
                                    bool is_for_service_worker,
-                                   const base::Value::Dict* filter) {
+                                   const base::Value::Dict& filter) {
   ExtensionPrefs::ScopedDictionaryUpdate update(
       extension_prefs_, extension_id,
       is_for_service_worker ? kFilteredServiceWorkerEvents : kFilteredEvents);
@@ -1283,7 +1281,7 @@
     filtered_events->GetListWithoutPathExpansion(event_name, &filter_list);
   }
 
-  filter_list->Append(filter->Clone());
+  filter_list->Append(filter.Clone());
 }
 
 void EventRouter::OnExtensionLoaded(content::BrowserContext* browser_context,
diff --git a/extensions/browser/event_router.h b/extensions/browser/event_router.h
index 0e06a12..c6d000f 100644
--- a/extensions/browser/event_router.h
+++ b/extensions/browser/event_router.h
@@ -29,12 +29,13 @@
 #include "extensions/browser/lazy_context_task_queue.h"
 #include "extensions/common/constants.h"
 #include "extensions/common/features/feature.h"
-#include "extensions/common/mojom/event_dispatcher.mojom.h"
+#include "extensions/common/mojom/event_dispatcher.mojom-forward.h"
 #include "extensions/common/mojom/event_router.mojom.h"
 #include "ipc/ipc_sender.h"
 #include "mojo/public/cpp/bindings/associated_receiver_set.h"
 #include "mojo/public/cpp/bindings/associated_remote.h"
 #include "mojo/public/cpp/bindings/pending_associated_receiver.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "url/gurl.h"
 
 class GURL;
@@ -447,13 +448,13 @@
   void AddFilterToEvent(const std::string& event_name,
                         const std::string& extension_id,
                         bool is_for_service_worker,
-                        const base::Value::Dict* filter);
+                        const base::Value::Dict& filter);
 
   // Removes a filter from an event.
   void RemoveFilterFromEvent(const std::string& event_name,
                              const std::string& extension_id,
                              bool is_for_service_worker,
-                             const base::Value::Dict* filter);
+                             const base::Value::Dict& filter);
 
   // Returns the dictionary of event filters that the given extension has
   // registered.
@@ -550,8 +551,8 @@
       Feature::Context,
       const Extension*,
       const base::Value::Dict*,
-      std::unique_ptr<base::Value::List>* event_args_out,
-      mojom::EventFilteringInfoPtr* event_filtering_info_out)>;
+      absl::optional<base::Value::List>& event_args_out,
+      mojom::EventFilteringInfoPtr& event_filtering_info_out)>;
 
   using DidDispatchCallback = base::RepeatingCallback<void(const EventTarget&)>;
 
diff --git a/extensions/browser/event_router_unittest.cc b/extensions/browser/event_router_unittest.cc
index 5a79b3a..7fed31b 100644
--- a/extensions/browser/event_router_unittest.cc
+++ b/extensions/browser/event_router_unittest.cc
@@ -28,7 +28,6 @@
 #include "third_party/blink/public/mojom/service_worker/service_worker_database.mojom-blink-forward.h"
 
 using base::DictionaryValue;
-using base::ListValue;
 using base::Value;
 
 namespace extensions {
@@ -78,13 +77,13 @@
     base::RepeatingCallback<std::unique_ptr<EventListener>(
         const std::string& /* event_name */,
         content::RenderProcessHost* /* process */,
-        std::unique_ptr<base::Value::Dict> /* filter */)>;
+        base::Value::Dict /* filter */)>;
 
 std::unique_ptr<EventListener> CreateEventListenerForExtension(
     const std::string& extension_id,
     const std::string& event_name,
     content::RenderProcessHost* process,
-    std::unique_ptr<base::Value::Dict> filter) {
+    base::Value::Dict filter) {
   return EventListener::ForExtension(event_name, extension_id, process,
                                      std::move(filter));
 }
@@ -93,7 +92,7 @@
     const GURL& listener_url,
     const std::string& event_name,
     content::RenderProcessHost* process,
-    std::unique_ptr<base::Value::Dict> filter) {
+    base::Value::Dict filter) {
   return EventListener::ForURL(event_name, listener_url, process,
                                std::move(filter));
 }
@@ -104,7 +103,7 @@
     int worker_thread_id,
     const std::string& event_name,
     content::RenderProcessHost* process,
-    std::unique_ptr<base::Value::Dict> filter) {
+    base::Value::Dict filter) {
   content::BrowserContext* browser_context =
       process ? process->GetBrowserContext() : nullptr;
   return EventListener::ForExtensionServiceWorker(
@@ -144,16 +143,15 @@
   return builder.Build();
 }
 
-std::unique_ptr<base::Value::Dict> CreateHostSuffixFilter(
-    const std::string& suffix) {
-  auto filter_dict = std::make_unique<base::Value::Dict>();
-  filter_dict->Set("hostSuffix", suffix);
+base::Value::Dict CreateHostSuffixFilter(const std::string& suffix) {
+  base::Value::Dict filter_dict;
+  filter_dict.Set("hostSuffix", Value(suffix));
 
   base::Value::List filter_list;
-  filter_list.Append(std::move(*filter_dict));
+  filter_list.Append(std::move(filter_dict));
 
-  auto filter = std::make_unique<base::Value::Dict>();
-  filter->Set("url", std::move(filter_list));
+  base::Value::Dict filter;
+  filter.Set("url", std::move(filter_list));
   return filter;
 }
 
@@ -322,8 +320,7 @@
     const EventListenerConstructor& constructor) {
   EventRouter router(nullptr, nullptr);
   std::unique_ptr<EventListener> listener =
-      constructor.Run("event_name", render_process_host(),
-                      std::make_unique<base::Value::Dict>());
+      constructor.Run("event_name", render_process_host(), base::Value::Dict());
 
   // Add/remove works without any observers.
   router.OnListenerAdded(listener.get());
@@ -358,9 +355,8 @@
   // Adding a listener with a sub-event notifies the main observer with
   // proper details.
   matching_observer.Reset();
-  std::unique_ptr<EventListener> sub_event_listener =
-      constructor.Run("event_name/1", render_process_host(),
-                      std::make_unique<base::Value::Dict>());
+  std::unique_ptr<EventListener> sub_event_listener = constructor.Run(
+      "event_name/1", render_process_host(), base::Value::Dict());
   router.OnListenerAdded(sub_event_listener.get());
   EXPECT_EQ(1, matching_observer.listener_added_count());
   EXPECT_EQ(0, matching_observer.listener_removed_count());
@@ -393,9 +389,9 @@
 
 TEST_F(EventRouterTest, MultipleEventRouterObserver) {
   EventRouter router(nullptr, nullptr);
-  std::unique_ptr<EventListener> listener = EventListener::ForURL(
-      "event_name", GURL("http://google.com/path"), render_process_host(),
-      std::make_unique<base::Value::Dict>());
+  std::unique_ptr<EventListener> listener =
+      EventListener::ForURL("event_name", GURL("http://google.com/path"),
+                            render_process_host(), base::Value::Dict());
 
   // Add/remove works without any observers.
   router.OnListenerAdded(listener.get());
@@ -491,13 +487,12 @@
     worker_identifier =
         absl::make_optional<ServiceWorkerIdentifier>(std::move(identifier));
   }
-  std::vector<std::unique_ptr<base::Value::Dict>> filters;
-  for (const std::string& host_suffix : kHostSuffixes) {
-    std::unique_ptr<base::Value::Dict> filter =
-        CreateHostSuffixFilter(host_suffix);
+  std::vector<base::Value::Dict> filters;
+  for (const auto& host_suffix : kHostSuffixes) {
+    base::Value::Dict filter = CreateHostSuffixFilter(host_suffix);
     event_router()->AddFilteredEventListener(kEventName, render_process_host(),
                                              param.Clone(), worker_identifier,
-                                             *filter, true);
+                                             filter, true);
     filters.push_back(std::move(filter));
   }
 
@@ -510,51 +505,51 @@
   ASSERT_TRUE(iter->second.is_list());
   ASSERT_EQ(3u, iter->second.GetList().size());
 
-  ASSERT_TRUE(ContainsFilter(kExtensionId, kEventName, *filters[0]));
-  ASSERT_TRUE(ContainsFilter(kExtensionId, kEventName, *filters[1]));
-  ASSERT_TRUE(ContainsFilter(kExtensionId, kEventName, *filters[2]));
+  ASSERT_TRUE(ContainsFilter(kExtensionId, kEventName, filters[0]));
+  ASSERT_TRUE(ContainsFilter(kExtensionId, kEventName, filters[1]));
+  ASSERT_TRUE(ContainsFilter(kExtensionId, kEventName, filters[2]));
 
   // Remove the second filter.
   event_router()->RemoveFilteredEventListener(kEventName, render_process_host(),
                                               param.Clone(), worker_identifier,
-                                              *filters[1], true);
-  ASSERT_TRUE(ContainsFilter(kExtensionId, kEventName, *filters[0]));
-  ASSERT_FALSE(ContainsFilter(kExtensionId, kEventName, *filters[1]));
-  ASSERT_TRUE(ContainsFilter(kExtensionId, kEventName, *filters[2]));
+                                              filters[1], true);
+  ASSERT_TRUE(ContainsFilter(kExtensionId, kEventName, filters[0]));
+  ASSERT_FALSE(ContainsFilter(kExtensionId, kEventName, filters[1]));
+  ASSERT_TRUE(ContainsFilter(kExtensionId, kEventName, filters[2]));
 
   // Remove the first filter.
   event_router()->RemoveFilteredEventListener(kEventName, render_process_host(),
                                               param.Clone(), worker_identifier,
-                                              *filters[0], true);
-  ASSERT_FALSE(ContainsFilter(kExtensionId, kEventName, *filters[0]));
-  ASSERT_FALSE(ContainsFilter(kExtensionId, kEventName, *filters[1]));
-  ASSERT_TRUE(ContainsFilter(kExtensionId, kEventName, *filters[2]));
+                                              filters[0], true);
+  ASSERT_FALSE(ContainsFilter(kExtensionId, kEventName, filters[0]));
+  ASSERT_FALSE(ContainsFilter(kExtensionId, kEventName, filters[1]));
+  ASSERT_TRUE(ContainsFilter(kExtensionId, kEventName, filters[2]));
 
   // Remove the third filter.
   event_router()->RemoveFilteredEventListener(kEventName, render_process_host(),
                                               param.Clone(), worker_identifier,
-                                              *filters[2], true);
-  ASSERT_FALSE(ContainsFilter(kExtensionId, kEventName, *filters[0]));
-  ASSERT_FALSE(ContainsFilter(kExtensionId, kEventName, *filters[1]));
-  ASSERT_FALSE(ContainsFilter(kExtensionId, kEventName, *filters[2]));
+                                              filters[2], true);
+  ASSERT_FALSE(ContainsFilter(kExtensionId, kEventName, filters[0]));
+  ASSERT_FALSE(ContainsFilter(kExtensionId, kEventName, filters[1]));
+  ASSERT_FALSE(ContainsFilter(kExtensionId, kEventName, filters[2]));
 }
 
 TEST_P(EventRouterFilterTest, URLBasedFilteredEventListener) {
   const std::string kEventName = "windows.onRemoved";
   const GURL kUrl("chrome-untrusted://terminal");
   absl::optional<ServiceWorkerIdentifier> worker_identifier = absl::nullopt;
-  auto filter = std::make_unique<base::Value::Dict>();
+  base::Value::Dict filter;
   bool lazy = false;
   EXPECT_FALSE(event_router()->HasEventListener(kEventName));
   event_router()->AddFilteredEventListener(
       kEventName, render_process_host(),
       mojom::EventListenerParam::NewListenerUrl(kUrl), worker_identifier,
-      *filter, lazy);
+      filter, lazy);
   EXPECT_TRUE(event_router()->HasEventListener(kEventName));
   event_router()->RemoveFilteredEventListener(
       kEventName, render_process_host(),
       mojom::EventListenerParam::NewListenerUrl(kUrl), worker_identifier,
-      *filter, lazy);
+      filter, lazy);
   EXPECT_FALSE(event_router()->HasEventListener(kEventName));
 }
 
diff --git a/extensions/browser/events/lazy_event_dispatcher.cc b/extensions/browser/events/lazy_event_dispatcher.cc
index 5417f08..329d022 100644
--- a/extensions/browser/events/lazy_event_dispatcher.cc
+++ b/extensions/browser/events/lazy_event_dispatcher.cc
@@ -11,6 +11,8 @@
 #include "extensions/browser/extensions_browser_client.h"
 #include "extensions/browser/lazy_context_id.h"
 #include "extensions/common/features/feature.h"
+#include "extensions/common/mojom/event_dispatcher.mojom.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 using content::BrowserContext;
 
@@ -71,7 +73,7 @@
   // to avoid lifetime issues. Use a separate copy of the event args, so they
   // last until the event is dispatched.
   if (!dispatched_event->will_dispatch_callback.is_null()) {
-    std::unique_ptr<base::Value::List> modified_event_args;
+    absl::optional<base::Value::List> modified_event_args;
     mojom::EventFilteringInfoPtr modified_event_filter_info;
     if (!dispatched_event->will_dispatch_callback.Run(
             dispatch_context.browser_context(),
@@ -79,8 +81,7 @@
             // context (either an event page or a service worker), which are
             // always BLESSED_EXTENSION_CONTEXTs
             extensions::Feature::BLESSED_EXTENSION_CONTEXT, extension,
-            listener_filter, &modified_event_args,
-            &modified_event_filter_info)) {
+            listener_filter, modified_event_args, modified_event_filter_info)) {
       // The event has been canceled.
       return true;
     }
diff --git a/extensions/browser/extension_function_histogram_value.h b/extensions/browser/extension_function_histogram_value.h
index daa5f4b..6959f42 100644
--- a/extensions/browser/extension_function_histogram_value.h
+++ b/extensions/browser/extension_function_histogram_value.h
@@ -1801,6 +1801,7 @@
   OS_DIAGNOSTICS_RUNFINGERPRINTALIVEROUTINE = 1738,
   DEVELOPERPRIVATE_UPDATESITEACCESS = 1739,
   AUTOTESTPRIVATE_REFRESHREMOTECOMMANDS = 1740,
+  FILESYSTEMPROVIDERINTERNAL_RESPONDTOMOUNTREQUEST = 1741,
   // Last entry: Add new entries above, then run:
   // tools/metrics/histograms/update_extension_histograms.py
   ENUM_BOUNDARY
diff --git a/extensions/browser/extension_icon_image_unittest.cc b/extensions/browser/extension_icon_image_unittest.cc
index f393e8d..9544bca 100644
--- a/extensions/browser/extension_icon_image_unittest.cc
+++ b/extensions/browser/extension_icon_image_unittest.cc
@@ -102,18 +102,22 @@
     std::string error;
     JSONFileValueDeserializer deserializer(
         test_file.AppendASCII("manifest.json"));
-    std::unique_ptr<base::DictionaryValue> valid_value =
-        base::DictionaryValue::From(
-            deserializer.Deserialize(&error_code, &error));
+    std::unique_ptr<base::Value> valid_value =
+        deserializer.Deserialize(&error_code, &error);
     EXPECT_EQ(0, error_code) << error;
     if (error_code != 0)
       return nullptr;
 
-    EXPECT_TRUE(valid_value.get());
+    EXPECT_TRUE(valid_value);
     if (!valid_value)
       return nullptr;
 
-    return Extension::Create(test_file, location, *valid_value,
+    const base::Value::Dict* valid_dict = valid_value->GetIfDict();
+    EXPECT_TRUE(valid_dict);
+    if (!valid_dict)
+      return nullptr;
+
+    return Extension::Create(test_file, location, *valid_dict,
                              Extension::NO_FLAGS, &error);
   }
 
diff --git a/extensions/common/api/declarative/declarative_manifest_data.cc b/extensions/common/api/declarative/declarative_manifest_data.cc
index b65923e..28cba49f 100644
--- a/extensions/common/api/declarative/declarative_manifest_data.cc
+++ b/extensions/common/api/declarative/declarative_manifest_data.cc
@@ -10,6 +10,7 @@
 #include "base/strings/string_piece.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
 #include "extensions/common/api/declarative/declarative_constants.h"
 #include "extensions/common/manifest_constants.h"
 
@@ -134,20 +135,20 @@
   }
 
   for (const auto& element : value.GetList()) {
-    const base::DictionaryValue* dict = nullptr;
-    if (!element.GetAsDictionary(&dict)) {
+    if (!element.is_dict()) {
       error_builder.Append("expected dictionary, got %s",
                            base::Value::GetTypeName(element.type()));
       return nullptr;
     }
-    std::string event;
-    if (!dict->GetString("event", &event)) {
+    const base::Value::Dict& dict = element.GetDict();
+    const std::string* event = dict.FindString("event");
+    if (!event) {
       error_builder.Append("'event' is required");
       return nullptr;
     }
 
     Rule rule;
-    if (!Rule::Populate(*dict, &rule)) {
+    if (!Rule::Populate(element, &rule)) {
       error_builder.Append("rule failed to populate");
       return nullptr;
     }
@@ -155,7 +156,7 @@
     if (!ConvertManifestRule(rule, &error_builder))
       return nullptr;
 
-    result->event_rules_map_[event].push_back(std::move(rule));
+    result->event_rules_map_[*event].push_back(std::move(rule));
   }
   return result;
 }
diff --git a/extensions/common/extension_set_unittest.cc b/extensions/common/extension_set_unittest.cc
index 4a009cb..6e92abf8 100644
--- a/extensions/common/extension_set_unittest.cc
+++ b/extensions/common/extension_set_unittest.cc
@@ -29,18 +29,18 @@
 #endif
   path = path.AppendASCII(name);
 
-  base::DictionaryValue manifest;
-  manifest.SetStringKey("name", name);
-  manifest.SetStringKey("version", "1");
-  manifest.SetIntKey("manifest_version", 2);
+  base::Value::Dict manifest;
+  manifest.Set("name", name);
+  manifest.Set("version", "1");
+  manifest.Set("manifest_version", 2);
 
   if (!launch_url.empty())
-    manifest.SetStringPath("app.launch.web_url", launch_url);
+    manifest.SetByDottedPath("app.launch.web_url", launch_url);
 
   if (!extent.empty()) {
     base::Value urls(base::Value::Type::LIST);
     urls.Append(extent);
-    manifest.SetPath("app.urls", std::move(urls));
+    manifest.SetByDottedPath("app.urls", std::move(urls));
   }
 
   std::string error;
diff --git a/extensions/common/extension_unittest.cc b/extensions/common/extension_unittest.cc
index 7fc3097..e5de677 100644
--- a/extensions/common/extension_unittest.cc
+++ b/extensions/common/extension_unittest.cc
@@ -28,7 +28,7 @@
 }
 
 testing::AssertionResult RunManifestVersionSuccess(
-    std::unique_ptr<base::DictionaryValue> manifest,
+    base::Value::Dict manifest,
     Manifest::Type expected_type,
     int expected_manifest_version,
     base::StringPiece expected_warning = "",
@@ -36,7 +36,7 @@
     ManifestLocation manifest_location = ManifestLocation::kInternal) {
   std::string error;
   scoped_refptr<const Extension> extension = Extension::Create(
-      base::FilePath(), manifest_location, *manifest, custom_flag, &error);
+      base::FilePath(), manifest_location, manifest, custom_flag, &error);
   if (!extension) {
     return testing::AssertionFailure()
            << "Extension creation failed: " << error;
@@ -70,12 +70,12 @@
 }
 
 testing::AssertionResult RunManifestVersionFailure(
-    std::unique_ptr<base::DictionaryValue> manifest,
+    base::Value::Dict manifest,
     Extension::InitFromValueFlags custom_flag = Extension::NO_FLAGS) {
   std::string error;
   scoped_refptr<const Extension> extension =
-      Extension::Create(base::FilePath(), ManifestLocation::kInternal,
-                        *manifest, custom_flag, &error);
+      Extension::Create(base::FilePath(), ManifestLocation::kInternal, manifest,
+                        custom_flag, &error);
   if (extension)
     return testing::AssertionFailure() << "Extension creation succeeded.";
 
@@ -83,13 +83,13 @@
 }
 
 testing::AssertionResult RunCreationWithFlags(
-    const base::DictionaryValue* manifest,
+    const base::Value::Dict& manifest,
     mojom::ManifestLocation location,
     Manifest::Type expected_type,
     Extension::InitFromValueFlags custom_flag = Extension::NO_FLAGS) {
   std::string error;
   scoped_refptr<const Extension> extension = Extension::Create(
-      base::FilePath(), location, *manifest, custom_flag, &error);
+      base::FilePath(), location, manifest, custom_flag, &error);
   if (!extension) {
     return testing::AssertionFailure()
            << "Extension creation failed: " << error;
@@ -115,7 +115,7 @@
         .Set("description", "An awesome extension");
     if (manifest_version)
       builder.Set("manifest_version", *manifest_version);
-    return builder.Build();
+    return builder.BuildDict();
   };
 
   const Manifest::Type kType = Manifest::TYPE_EXTENSION;
@@ -162,7 +162,7 @@
              DictionaryBuilder().Set("background", background.Build()).Build());
     if (manifest_version)
       builder.Set("manifest_version", *manifest_version);
-    return builder.Build();
+    return builder.BuildDict();
   };
 
   const Manifest::Type kType = Manifest::TYPE_PLATFORM_APP;
@@ -202,7 +202,7 @@
         .Set("app", app.Build());
     if (manifest_version)
       builder.Set("manifest_version", *manifest_version);
-    return builder.Build();
+    return builder.BuildDict();
   };
 
   const Manifest::Type kType = Manifest::TYPE_HOSTED_APP;
@@ -230,7 +230,7 @@
         .Set("converted_from_user_script", true);
     if (manifest_version)
       builder.Set("manifest_version", *manifest_version);
-    return builder.Build();
+    return builder.BuildDict();
   };
 
   const Manifest::Type kType = Manifest::TYPE_USER_SCRIPT;
@@ -255,14 +255,14 @@
       .Set("version", "0.1")
       .Set("description", "An awesome extension")
       .Set("manifest_version", 2);
-  std::unique_ptr<base::DictionaryValue> manifest = builder.Build();
+  base::Value::Dict manifest = builder.BuildDict();
 
-  EXPECT_TRUE(
-      RunCreationWithFlags(manifest.get(), ManifestLocation::kExternalPolicy,
-                           Manifest::TYPE_EXTENSION, Extension::NO_FLAGS));
-  EXPECT_TRUE(RunCreationWithFlags(
-      manifest.get(), ManifestLocation::kExternalPolicy,
-      Manifest::TYPE_LOGIN_SCREEN_EXTENSION, Extension::FOR_LOGIN_SCREEN));
+  EXPECT_TRUE(RunCreationWithFlags(manifest, ManifestLocation::kExternalPolicy,
+                                   Manifest::TYPE_EXTENSION,
+                                   Extension::NO_FLAGS));
+  EXPECT_TRUE(RunCreationWithFlags(manifest, ManifestLocation::kExternalPolicy,
+                                   Manifest::TYPE_LOGIN_SCREEN_EXTENSION,
+                                   Extension::FOR_LOGIN_SCREEN));
 }
 
 }  // namespace extensions
diff --git a/extensions/renderer/module_system.cc b/extensions/renderer/module_system.cc
index fc69f0bd..b7b1eb21 100644
--- a/extensions/renderer/module_system.cc
+++ b/extensions/renderer/module_system.cc
@@ -225,17 +225,23 @@
   // TODO(1276144): remove checks once investigation finished.
   CHECK(!has_been_invalidated_);
   has_been_invalidated_ = true;
+
+  v8::Isolate* isolate = GetIsolate();
   // Clear the module system properties from the global context. It's polite,
   // and we use this as a signal in lazy handlers that we no longer exist.
   {
-    v8::HandleScope scope(GetIsolate());
-    v8::Local<v8::Object> global = context()->v8_context()->Global();
-    // TODO(1276144): remove checks once investigation finished.
-    v8::Local<v8::Value> dummy_value;
-    CHECK(GetPrivate(global, kModulesField, &dummy_value));
-    DeletePrivate(global, kModulesField);
-    CHECK(GetPrivate(global, kModuleSystem, &dummy_value));
-    DeletePrivate(global, kModuleSystem);
+    // Note: It isn't safe to access v8::Private if IsExecutionTerminating
+    // returns true. It crashes if we do so: http://crbug.com/1276144.
+    if (!isolate->IsExecutionTerminating()) {
+      v8::HandleScope scope(GetIsolate());
+      v8::Local<v8::Object> global = context()->v8_context()->Global();
+      // TODO(1276144): remove checks once investigation finished.
+      v8::Local<v8::Value> dummy_value;
+      CHECK(GetPrivate(global, kModulesField, &dummy_value));
+      DeletePrivate(global, kModulesField);
+      CHECK(GetPrivate(global, kModuleSystem, &dummy_value));
+      DeletePrivate(global, kModuleSystem);
+    }
   }
 
   // Invalidate all active and clobbered NativeHandlers we own.
diff --git a/extensions/renderer/object_backed_native_handler.cc b/extensions/renderer/object_backed_native_handler.cc
index 7ffb141..8559dc4 100644
--- a/extensions/renderer/object_backed_native_handler.cc
+++ b/extensions/renderer/object_backed_native_handler.cc
@@ -71,6 +71,17 @@
 
   v8::Local<v8::Value> handler_function_value;
   v8::Local<v8::Value> feature_name_value;
+
+  // If the execution is terminating, it's unsafe to access any privates on the
+  // object. Bail.
+  if (isolate->IsExecutionTerminating())
+    return;
+
+  // Check with cbruni@ if we can turn on the following CHECK:
+  // // We should never enter a v8::Function callback if execution is
+  // // terminating.
+  // CHECK(!isolate->IsExecutionTerminating());
+
   // See comment in header file for why we do this.
   if (!GetPrivate(context, data, kHandlerFunction, &handler_function_value) ||
       handler_function_value->IsUndefined() ||
@@ -141,9 +152,13 @@
   v8::Context::Scope context_scope(context_->v8_context());
 
   v8::Local<v8::Object> data = v8::Object::New(isolate);
+  // Create and store a new HandlerFunction, and add a weak reference to it
+  // in a v8 object so that we can retrieve it from the constructed v8
+  // function.
+  handler_functions_.push_back(
+      std::make_unique<HandlerFunction>(std::move(handler_function)));
   SetPrivate(data, kHandlerFunction,
-             v8::External::New(
-                 isolate, new HandlerFunction(std::move(handler_function))));
+             v8::External::New(isolate, handler_functions_.back().get()));
   DCHECK(feature_name.empty() ||
          ExtensionAPI::GetSharedInstance()->GetFeatureDependency(feature_name))
       << feature_name;
@@ -166,16 +181,19 @@
   v8::HandleScope handle_scope(isolate);
   v8::Context::Scope context_scope(context_->v8_context());
 
-  for (auto& data : router_data_) {
-    v8::Local<v8::Object> local_data = data.Get(isolate);
-    v8::Local<v8::Value> handler_function_value;
-    CHECK(GetPrivate(local_data, kHandlerFunction, &handler_function_value));
-    delete static_cast<HandlerFunction*>(
-        handler_function_value.As<v8::External>()->Value());
-    DeletePrivate(local_data, kHandlerFunction);
+  // Note: It isn't safe to access v8::Private if IsExecutionTerminating returns
+  // true. It crashes if we do so: http://crbug.com/1276144.
+  if (!isolate->IsExecutionTerminating()) {
+    for (auto& data : router_data_) {
+      v8::Local<v8::Object> local_data = data.Get(isolate);
+      v8::Local<v8::Value> handler_function_value;
+      CHECK(GetPrivate(local_data, kHandlerFunction, &handler_function_value));
+      DeletePrivate(local_data, kHandlerFunction);
+    }
   }
 
   router_data_.clear();
+  handler_functions_.clear();
   object_template_.Reset();
 
   NativeHandler::Invalidate();
diff --git a/extensions/renderer/object_backed_native_handler.h b/extensions/renderer/object_backed_native_handler.h
index fb44a03..de3a6bd 100644
--- a/extensions/renderer/object_backed_native_handler.h
+++ b/extensions/renderer/object_backed_native_handler.h
@@ -123,12 +123,14 @@
   // A scenario when v8 will outlive us is if a frame holds onto the
   // contentWindow of an iframe after it's removed.
   //
-  // So, we use v8::Objects here to hold that data, effectively refcounting
-  // the data. When |this| is destroyed we remove the base::Bound function from
-  // the object to indicate that it shouldn't be called.
+  // So, we use v8::Objects here to hold that data as a weak reference. The
+  // strong reference is stored in `handler_functions_`.
   using RouterData = std::vector<v8::Global<v8::Object>>;
   RouterData router_data_;
 
+  // Owned list of HandlerFunctions.
+  std::vector<std::unique_ptr<HandlerFunction>> handler_functions_;
+
   ScriptContext* context_;
 
   v8::Global<v8::ObjectTemplate> object_template_;
diff --git a/extensions/strings/extensions_strings.grd b/extensions/strings/extensions_strings.grd
index cade0c1..992acd9 100644
--- a/extensions/strings/extensions_strings.grd
+++ b/extensions/strings/extensions_strings.grd
@@ -303,7 +303,9 @@
       <message name="IDS_EXTENSION_DISABLED_UPDATE_REQUIRED_BY_POLICY" desc="Error message when an extension doesn't meet minimum version required by administrator policy.">
         The administrator of this machine requires <ph name="EXTENSION_NAME">$1<ex>Google Talk</ex></ph> to have a minimum version of <ph name="EXTENSION_VERSION">$2<ex>1.1.0</ex></ph>. It cannot be enabled until it has updated to that version (or higher).
       </message>
-
+      <message name="IDS_EXTENSION_MANIFEST_VERSION_NOT_SUPPORTED" desc="Error message when an extension doesn't meet minimum manifest version required by administrator policy.">
+        The administrator of this machine requires <ph name="EXTENSION_NAME">$1<ex>Google Talk</ex></ph> to have a minimum manifest version of 3.
+      </message>
       <!--
         Device API strings. Please keep alphabetized.
 
diff --git a/extensions/strings/extensions_strings_grd/IDS_EXTENSION_MANIFEST_VERSION_NOT_SUPPORTED.png.sha1 b/extensions/strings/extensions_strings_grd/IDS_EXTENSION_MANIFEST_VERSION_NOT_SUPPORTED.png.sha1
new file mode 100644
index 0000000..f0f54c3
--- /dev/null
+++ b/extensions/strings/extensions_strings_grd/IDS_EXTENSION_MANIFEST_VERSION_NOT_SUPPORTED.png.sha1
@@ -0,0 +1 @@
+d6eeb7567460649cd6e3c6763febae8fc3954d07
\ No newline at end of file
diff --git a/fuchsia_web/shell/BUILD.gn b/fuchsia_web/shell/BUILD.gn
index 17eb3aa..67a81668 100644
--- a/fuchsia_web/shell/BUILD.gn
+++ b/fuchsia_web/shell/BUILD.gn
@@ -100,19 +100,10 @@
     data_deps = [ ":cast_streaming_shell_exe" ]
   }
 
-  fuchsia_component("cast_streaming_shell_component_v1") {
-    testonly = true
-    manifest = "cast_streaming_shell.cmx"
-    data_deps = [ ":cast_streaming_shell_exe" ]
-  }
-
   fuchsia_package("cast_streaming_shell_pkg") {
     testonly = true
     package_name = "cast_streaming_shell"
-    deps = [
-      ":cast_streaming_shell_component",
-      ":cast_streaming_shell_component_v1",
-    ]
+    deps = [ ":cast_streaming_shell_component" ]
   }
 
   fuchsia_package_installer("cast_streaming_shell") {
diff --git a/fuchsia_web/shell/cast_streaming_shell.cmx b/fuchsia_web/shell/cast_streaming_shell.cmx
deleted file mode 100644
index a2ebfd7..0000000
--- a/fuchsia_web/shell/cast_streaming_shell.cmx
+++ /dev/null
@@ -1,42 +0,0 @@
-{
-  "program": {
-    "binary": "cast_streaming_shell_exe"
-  },
-  "sandbox": {
-    "features": [
-      "deprecated-ambient-replace-as-executable",
-      "isolated-persistent-storage",
-      "isolated-temp"
-    ],
-    "services": [
-      "fuchsia.accessibility.semantics.SemanticsManager",
-      "fuchsia.buildinfo.Provider",
-      "fuchsia.device.NameProvider",
-      "fuchsia.feedback.ComponentDataRegister",
-      "fuchsia.feedback.CrashReportingProductRegister",
-      "fuchsia.fonts.Provider",
-      "fuchsia.intl.PropertyProvider",
-      "fuchsia.kernel.VmexResource",
-      "fuchsia.logger.LogSink",
-      "fuchsia.media.Audio",
-      "fuchsia.media.AudioDeviceEnumerator",
-      "fuchsia.media.ProfileProvider",
-      "fuchsia.media.SessionAudioConsumerFactory",
-      "fuchsia.mediacodec.CodecFactory",
-      "fuchsia.memorypressure.Provider",
-      "fuchsia.net.interfaces.State",
-      "fuchsia.net.name.Lookup",
-      "fuchsia.posix.socket.Provider",
-      "fuchsia.process.Launcher",
-      "fuchsia.sys.Environment",
-      "fuchsia.sys.Launcher",
-      "fuchsia.sys.Loader",
-      "fuchsia.sysmem.Allocator",
-      "fuchsia.ui.composition.Allocator",
-      "fuchsia.ui.composition.Flatland",
-      "fuchsia.ui.policy.Presenter",
-      "fuchsia.ui.scenic.Scenic",
-      "fuchsia.vulkan.loader.Loader"
-    ]
-  }
-}
diff --git a/fuchsia_web/webengine/browser/frame_impl_browsertest.cc b/fuchsia_web/webengine/browser/frame_impl_browsertest.cc
index 1f4df5fd..478265d 100644
--- a/fuchsia_web/webengine/browser/frame_impl_browsertest.cc
+++ b/fuchsia_web/webengine/browser/frame_impl_browsertest.cc
@@ -380,29 +380,32 @@
   frame.navigation_listener().RunUntilUrlTitleBackForwardEquals(
       title2, kPage2Title, true, false);
 
-  VerifyCanGoBackAndForward(frame, true, false);
   frame.GetNavigationController()->GoBack();
   frame.navigation_listener().RunUntilUrlTitleBackForwardEquals(
       title1, kPage1Title, false, true);
 
   // At the top of the navigation entry list; this should be a no-op.
-  VerifyCanGoBackAndForward(frame, false, true);
   frame.GetNavigationController()->GoBack();
 
   // Process the navigation request message.
   base::RunLoop().RunUntilIdle();
 
+  // Verify that the can-go-back/forward state has not changed.
   VerifyCanGoBackAndForward(frame, false, true);
+
   frame.GetNavigationController()->GoForward();
   frame.navigation_listener().RunUntilUrlTitleBackForwardEquals(
       title2, kPage2Title, true, false);
 
   // At the end of the navigation entry list; this should be a no-op.
-  VerifyCanGoBackAndForward(frame, true, false);
   frame.GetNavigationController()->GoForward();
 
   // Process the navigation request message.
   base::RunLoop().RunUntilIdle();
+
+  // Back/forward state should not have changed, since the request was a
+  // no-op.
+  VerifyCanGoBackAndForward(frame, true, false);
 }
 
 // An HTTP response stream whose response payload can be sent as "chunks"
diff --git a/fuchsia_web/webengine/browser/navigation_controller_impl.cc b/fuchsia_web/webengine/browser/navigation_controller_impl.cc
index c2ae225..b495690 100644
--- a/fuchsia_web/webengine/browser/navigation_controller_impl.cc
+++ b/fuchsia_web/webengine/browser/navigation_controller_impl.cc
@@ -76,18 +76,19 @@
 namespace {
 
 // For each field that differs between |old_entry| and |new_entry|, the field
-// is set to its new value in |difference|. |new_entry| is assumed to have been
-// fully-populated with fields.
+// is set to its new value in |difference|. All other fields in |difference| are
+// left unchanged, such that a series of DiffNavigationEntries() calls may be
+// used to accumulate differences across a progression of NavigationStates.
 void DiffNavigationEntries(const fuchsia::web::NavigationState& old_entry,
                            const fuchsia::web::NavigationState& new_entry,
                            fuchsia::web::NavigationState* difference) {
   DCHECK(difference);
 
-  // |new_entry| should not be empty when the difference is between states
-  // pre- and post-navigation. It is possible for non-navigation events (e.g.
-  // Renderer-process teardown) to trigger notifications, in which case both
-  // states may be empty (i.e. both come from the "initial" NavigationEntry).
-  if (new_entry.IsEmpty() && old_entry.IsEmpty()) {
+  // NavigationStates will only be empty for "initial" navigation entries, so
+  // if |new_entry| is empty then |old_entry| must necessarily also be empty,
+  // and there is no difference to report.
+  if (new_entry.IsEmpty()) {
+    CHECK(old_entry.IsEmpty());
     return;
   }
 
diff --git a/fuchsia_web/webengine/renderer/web_engine_audio_output_device.cc b/fuchsia_web/webengine/renderer/web_engine_audio_output_device.cc
index b68df4a..18b36f2 100644
--- a/fuchsia_web/webengine/renderer/web_engine_audio_output_device.cc
+++ b/fuchsia_web/webengine/renderer/web_engine_audio_output_device.cc
@@ -11,6 +11,7 @@
 #include "base/no_destructor.h"
 #include "base/threading/thread.h"
 #include "base/threading/thread_task_runner_handle.h"
+#include "media/base/audio_glitch_info.h"
 #include "media/base/audio_timestamp_helper.h"
 
 namespace {
@@ -419,7 +420,7 @@
       return;
 
     frames_filled =
-        callback_->Render(playback_time - now, now, 0, audio_bus_.get());
+        callback_->Render(playback_time - now, now, {}, audio_bus_.get());
   }
 
   if (frames_filled) {
diff --git a/fuchsia_web/webengine/renderer/web_engine_audio_output_device_test.cc b/fuchsia_web/webengine/renderer/web_engine_audio_output_device_test.cc
index 1d21a91..98152e3 100644
--- a/fuchsia_web/webengine/renderer/web_engine_audio_output_device_test.cc
+++ b/fuchsia_web/webengine/renderer/web_engine_audio_output_device_test.cc
@@ -34,10 +34,9 @@
   // AudioRendererSink::Renderer interface.
   int Render(base::TimeDelta delay,
              base::TimeTicks delay_timestamp,
-             int prior_frames_skipped,
+             const media::AudioGlitchInfo& audio_glitch_info,
              media::AudioBus* dest) override {
     EXPECT_EQ(dest->channels(), kNumChannels);
-    frames_skipped_ += prior_frames_skipped;
     frames_rendered_ += dest->frames();
 
     EXPECT_GT(delay, base::TimeDelta());
@@ -52,7 +51,6 @@
   int frames_rendered() const { return frames_rendered_; }
   void reset_frames_rendered() { frames_rendered_ = 0; }
 
-  int frames_skipped() const { return frames_skipped_; }
   int num_render_errors() const { return num_render_errors_; }
 
   base::TimeTicks last_presentation_time() const {
@@ -61,7 +59,6 @@
 
  private:
   int frames_rendered_ = 0;
-  int frames_skipped_ = 0;
   int num_render_errors_ = 0;
   base::TimeTicks last_presentation_time_;
 };
@@ -161,7 +158,6 @@
   for (int i = 0; i < 3; ++i) {
     task_environment_.FastForwardBy(kPeriod);
     EXPECT_EQ(renderer_.frames_rendered(), kFramesPerPeriod);
-    EXPECT_EQ(renderer_.frames_skipped(), 0);
     renderer_.reset_frames_rendered();
   }
 }
@@ -175,7 +171,6 @@
   // Render().
   task_environment_.FastForwardBy(kPeriod);
   EXPECT_EQ(renderer_.frames_rendered(), kFramesPerPeriod);
-  EXPECT_EQ(renderer_.frames_skipped(), 0);
   renderer_.reset_frames_rendered();
 
   // Render() should not be called while paused.
@@ -187,7 +182,6 @@
   output_device_->Play();
   task_environment_.FastForwardBy(kPeriod);
   EXPECT_GT(renderer_.frames_rendered(), 0);
-  EXPECT_EQ(renderer_.frames_skipped(), 0);
 }
 
 TEST_F(WebEngineAudioOutputDeviceTest, Underflow) {
@@ -201,14 +195,12 @@
   task_environment_.AdvanceClock(kPeriod * 2);
   task_environment_.RunUntilIdle();
   EXPECT_EQ(renderer_.frames_rendered(), kFramesPerPeriod * 2);
-  EXPECT_EQ(renderer_.frames_skipped(), 0);
   renderer_.reset_frames_rendered();
 
   // Advance time by 100ms, causing some frames to be skipped.
   task_environment_.AdvanceClock(kPeriod * 10);
   task_environment_.RunUntilIdle();
   EXPECT_EQ(renderer_.frames_rendered(), kFramesPerPeriod * 3);
-  EXPECT_EQ(renderer_.frames_skipped(), 0);
   renderer_.reset_frames_rendered();
 
   ValidatePresentationTime();
diff --git a/gpu/command_buffer/client/cmd_buffer_helper.h b/gpu/command_buffer/client/cmd_buffer_helper.h
index 8a7e834..bdb61437 100644
--- a/gpu/command_buffer/client/cmd_buffer_helper.h
+++ b/gpu/command_buffer/client/cmd_buffer_helper.h
@@ -272,6 +272,7 @@
   CommandBuffer* command_buffer() const { return command_buffer_; }
 
   scoped_refptr<Buffer> get_ring_buffer() const { return ring_buffer_; }
+  int32_t get_ring_buffer_id() const { return ring_buffer_id_; }
 
   uint32_t flush_generation() const { return flush_generation_; }
 
diff --git a/gpu/command_buffer/service/ahardwarebuffer_utils.cc b/gpu/command_buffer/service/ahardwarebuffer_utils.cc
index 59f40b3..fd54b0a 100644
--- a/gpu/command_buffer/service/ahardwarebuffer_utils.cc
+++ b/gpu/command_buffer/service/ahardwarebuffer_utils.cc
@@ -22,6 +22,13 @@
 #include "ui/gl/scoped_binders.h"
 
 namespace gpu {
+
+// Helper to allow for easy friending of the below restricted function.
+void SetColorSpaceOnGLImage(gl::GLImage* gl_image,
+                            const gfx::ColorSpace& color_space) {
+  gl_image->SetColorSpace(color_space);
+}
+
 namespace {
 
 gles2::Texture* MakeGLTexture(
@@ -84,7 +91,7 @@
     api->glDeleteTexturesFn(1, &service_id);
     return;
   }
-  egl_image->SetColorSpace(color_space);
+  SetColorSpaceOnGLImage(egl_image.get(), color_space);
 
   if (passthrough_texture) {
     *passthrough_texture = MakeGLTexturePassthrough(
diff --git a/gpu/command_buffer/service/native_image_buffer.cc b/gpu/command_buffer/service/native_image_buffer.cc
index 6a792832..009cc42 100644
--- a/gpu/command_buffer/service/native_image_buffer.cc
+++ b/gpu/command_buffer/service/native_image_buffer.cc
@@ -4,13 +4,7 @@
 
 #include "gpu/command_buffer/service/native_image_buffer.h"
 
-#include <list>
-
-#include "base/memory/raw_ptr.h"
-#include "base/notreached.h"
-#include "base/synchronization/lock.h"
 #include "build/build_config.h"
-#include "ui/gl/gl_image.h"
 #include "ui/gl/gl_implementation.h"
 
 #if !BUILDFLAG(IS_MAC)
@@ -33,25 +27,10 @@
  private:
   NativeImageBufferEGL(EGLDisplay display, EGLImageKHR image);
   ~NativeImageBufferEGL() override;
-  void AddClient(gl::GLImage* client) override;
-  void RemoveClient(gl::GLImage* client) override;
-  bool IsClient(gl::GLImage* client) override;
   void BindToTexture(GLenum target) const override;
 
   const EGLDisplay egl_display_;
   const EGLImageKHR egl_image_;
-
-  base::Lock lock_;
-
-  struct ClientInfo {
-    explicit ClientInfo(gl::GLImage* client);
-    ~ClientInfo();
-
-    raw_ptr<gl::GLImage> client;
-    bool needs_wait_before_read;
-  };
-  std::list<ClientInfo> client_infos_;
-  raw_ptr<gl::GLImage> write_client_;
 };
 
 scoped_refptr<NativeImageBufferEGL> NativeImageBufferEGL::Create(
@@ -85,54 +64,18 @@
   return new NativeImageBufferEGL(egl_display, egl_image);
 }
 
-NativeImageBufferEGL::ClientInfo::ClientInfo(gl::GLImage* client)
-    : client(client), needs_wait_before_read(true) {}
-
-NativeImageBufferEGL::ClientInfo::~ClientInfo() = default;
-
 NativeImageBufferEGL::NativeImageBufferEGL(EGLDisplay display,
                                            EGLImageKHR image)
-    : egl_display_(display),
-      egl_image_(image),
-      write_client_(nullptr) {
+    : egl_display_(display), egl_image_(image) {
   DCHECK(egl_display_ != EGL_NO_DISPLAY);
   DCHECK(egl_image_ != EGL_NO_IMAGE_KHR);
 }
 
 NativeImageBufferEGL::~NativeImageBufferEGL() {
-  DCHECK(client_infos_.empty());
   if (egl_image_ != EGL_NO_IMAGE_KHR)
     eglDestroyImageKHR(egl_display_, egl_image_);
 }
 
-void NativeImageBufferEGL::AddClient(gl::GLImage* client) {
-  base::AutoLock lock(lock_);
-  client_infos_.emplace_back(client);
-}
-
-void NativeImageBufferEGL::RemoveClient(gl::GLImage* client) {
-  base::AutoLock lock(lock_);
-  if (write_client_ == client)
-    write_client_ = nullptr;
-  for (std::list<ClientInfo>::iterator it = client_infos_.begin();
-       it != client_infos_.end(); it++) {
-    if (it->client == client) {
-      client_infos_.erase(it);
-      return;
-    }
-  }
-  NOTREACHED();
-}
-
-bool NativeImageBufferEGL::IsClient(gl::GLImage* client) {
-  base::AutoLock lock(lock_);
-  for (auto & client_info : client_infos_) {
-    if (client_info.client == client)
-      return true;
-  }
-  return false;
-}
-
 void NativeImageBufferEGL::BindToTexture(GLenum target) const {
   DCHECK(egl_image_ != EGL_NO_IMAGE_KHR);
   glEGLImageTargetTexture2DOES(target, egl_image_);
@@ -142,22 +85,7 @@
 
 #endif
 
-class NativeImageBufferStub : public NativeImageBuffer {
- public:
-  NativeImageBufferStub() = default;
-
-  NativeImageBufferStub(const NativeImageBufferStub&) = delete;
-  NativeImageBufferStub& operator=(const NativeImageBufferStub&) = delete;
-
- private:
-  ~NativeImageBufferStub() override = default;
-  void AddClient(gl::GLImage* client) override {}
-  void RemoveClient(gl::GLImage* client) override {}
-  bool IsClient(gl::GLImage* client) override { return true; }
-  void BindToTexture(GLenum target) const override {}
-};
-
-}  // anonymous namespace
+}  // namespace
 
 // static
 scoped_refptr<NativeImageBuffer> NativeImageBuffer::Create(GLuint texture_id) {
@@ -167,9 +95,6 @@
     case gl::kGLImplementationEGLANGLE:
       return NativeImageBufferEGL::Create(texture_id);
 #endif
-    case gl::kGLImplementationMockGL:
-    case gl::kGLImplementationStubGL:
-      return new NativeImageBufferStub;
     default:
       NOTREACHED();
       return nullptr;
diff --git a/gpu/command_buffer/service/native_image_buffer.h b/gpu/command_buffer/service/native_image_buffer.h
index df43fe8..47ebe834 100644
--- a/gpu/command_buffer/service/native_image_buffer.h
+++ b/gpu/command_buffer/service/native_image_buffer.h
@@ -8,10 +8,6 @@
 #include "base/memory/ref_counted.h"
 #include "gpu/command_buffer/service/gl_utils.h"
 
-namespace gl {
-class GLImage;
-}
-
 namespace gpu {
 namespace gles2 {
 
@@ -22,9 +18,6 @@
   NativeImageBuffer(const NativeImageBuffer&) = delete;
   NativeImageBuffer& operator=(const NativeImageBuffer&) = delete;
 
-  virtual void AddClient(gl::GLImage* client) = 0;
-  virtual void RemoveClient(gl::GLImage* client) = 0;
-  virtual bool IsClient(gl::GLImage* client) = 0;
   virtual void BindToTexture(GLenum target) const = 0;
 
  protected:
diff --git a/gpu/command_buffer/service/raster_decoder.cc b/gpu/command_buffer/service/raster_decoder.cc
index 0606017..e87f78b 100644
--- a/gpu/command_buffer/service/raster_decoder.cc
+++ b/gpu/command_buffer/service/raster_decoder.cc
@@ -25,6 +25,7 @@
 #include "base/trace_event/trace_event.h"
 #include "build/build_config.h"
 #include "cc/paint/paint_cache.h"
+#include "cc/paint/paint_op.h"
 #include "cc/paint/paint_op_buffer.h"
 #include "cc/paint/transfer_cache_deserialize_helper.h"
 #include "cc/paint/transfer_cache_entry.h"
@@ -3336,7 +3337,7 @@
   }
 
   alignas(
-      cc::PaintOpBuffer::PaintOpAlign) char data[sizeof(cc::LargestPaintOp)];
+      cc::PaintOpBuffer::kPaintOpAlign) char data[sizeof(cc::LargestPaintOp)];
 
   size_t paint_buffer_size = raster_shm_size;
   gl::ScopedProgressReporter report_progress(
diff --git a/gpu/command_buffer/service/shared_image/d3d_image_backing_factory_unittest.cc b/gpu/command_buffer/service/shared_image/d3d_image_backing_factory_unittest.cc
index 1ca6af5e..1e19ad8 100644
--- a/gpu/command_buffer/service/shared_image/d3d_image_backing_factory_unittest.cc
+++ b/gpu/command_buffer/service/shared_image/d3d_image_backing_factory_unittest.cc
@@ -137,6 +137,8 @@
   return true;
 }
 
+}  // anonymous namespace
+
 class D3DImageBackingFactoryTestBase : public testing::Test {
  public:
   void SetUp() override {
@@ -266,8 +268,8 @@
   GLuint back_texture_id = back_texture->service_id();
   EXPECT_NE(back_texture_id, 0u);
 
-  auto* back_image = gl::GLImageD3D::FromGLImage(
-      back_texture->GetLevelImage(GL_TEXTURE_2D, 0));
+  auto* back_image =
+      gl::GLImage::ToGLImageD3D(back_texture->GetLevelImage(GL_TEXTURE_2D, 0));
 
   auto front_texture = shared_image_representation_factory_
                            ->ProduceGLTexturePassthrough(front_buffer_mailbox)
@@ -278,8 +280,8 @@
   GLuint front_texture_id = front_texture->service_id();
   EXPECT_NE(front_texture_id, 0u);
 
-  auto* front_image = gl::GLImageD3D::FromGLImage(
-      front_texture->GetLevelImage(GL_TEXTURE_2D, 0));
+  auto* front_image =
+      gl::GLImage::ToGLImageD3D(front_texture->GetLevelImage(GL_TEXTURE_2D, 0));
 
   ASSERT_TRUE(back_image);
   EXPECT_EQ(back_image->ShouldBindOrCopy(), gl::GLImage::BIND);
@@ -1882,7 +1884,7 @@
   ASSERT_TRUE(scoped_read_access);
 
   auto* gl_image_d3d =
-      gl::GLImageD3D::FromGLImage(scoped_read_access->gl_image());
+      gl::GLImage::ToGLImageD3D(scoped_read_access->gl_image());
   ASSERT_TRUE(gl_image_d3d);
 
   Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device =
@@ -2097,7 +2099,7 @@
     ASSERT_TRUE(scoped_read_access);
 
     auto* gl_image_memory =
-        gl::GLImageMemory::FromGLImage(scoped_read_access->gl_image());
+        gl::GLImage::ToGLImageMemory(scoped_read_access->gl_image());
     ASSERT_TRUE(gl_image_memory);
 
     CheckYUV(gl_image_memory->memory(), gl_image_memory->stride(), size,
@@ -2105,5 +2107,4 @@
   }
 }
 
-}  // anonymous namespace
 }  // namespace gpu
diff --git a/gpu/command_buffer/service/shared_image/external_vk_image_backing_factory.cc b/gpu/command_buffer/service/shared_image/external_vk_image_backing_factory.cc
index 7006caf..36ccf1f 100644
--- a/gpu/command_buffer/service/shared_image/external_vk_image_backing_factory.cc
+++ b/gpu/command_buffer/service/shared_image/external_vk_image_backing_factory.cc
@@ -170,6 +170,18 @@
     return false;
   }
 
+  // TODO: remove it when below formats are converted to multi plane shared
+  // image formats.
+#if BUILDFLAG(IS_LINUX)
+  switch (format.resource_format()) {
+    case viz::YUV_420_BIPLANAR:
+    case viz::YUVA_420_TRIPLANAR:
+      return false;
+    default:
+      break;
+  }
+#endif
+
   if (gmb_type != gfx::EMPTY_BUFFER && !CanImportGpuMemoryBuffer(gmb_type)) {
     return false;
   }
diff --git a/gpu/command_buffer/service/shared_image/iosurface_image_backing_factory.mm b/gpu/command_buffer/service/shared_image/iosurface_image_backing_factory.mm
index 0692944..5922d2d 100644
--- a/gpu/command_buffer/service/shared_image/iosurface_image_backing_factory.mm
+++ b/gpu/command_buffer/service/shared_image/iosurface_image_backing_factory.mm
@@ -457,7 +457,7 @@
     return nullptr;
   }
   gl::GLImageIOSurface* image_io_surface =
-      gl::GLImageIOSurface::FromGLImage(image.get());
+      gl::GLImage::ToGLImageIOSurface(image.get());
   if (!image_io_surface) {
     LOG(ERROR) << "Created image was not IOSurface-backed.";
     return nullptr;
diff --git a/gpu/command_buffer/service/shared_image/iosurface_image_backing_factory_unittest.cc b/gpu/command_buffer/service/shared_image/iosurface_image_backing_factory_unittest.cc
index 4f32ee6..fc93e1e 100644
--- a/gpu/command_buffer/service/shared_image/iosurface_image_backing_factory_unittest.cc
+++ b/gpu/command_buffer/service/shared_image/iosurface_image_backing_factory_unittest.cc
@@ -49,7 +49,6 @@
 using testing::AtLeast;
 
 namespace gpu {
-namespace {
 
 class IOSurfaceImageBackingFactoryTest : public testing::Test {
  public:
@@ -707,6 +706,12 @@
   viz::SharedImageFormat get_format() { return GetParam(); }
 
  protected:
+  // Hide the access that parameterized tests make to restricted interfaces of
+  // GLImage here to ease friending.
+  void AssertGLImageHasTypeNone(gl::GLImage* image) {
+    ASSERT_EQ(image->GetType(), gl::GLImage::Type::NONE);
+  }
+
   ::testing::NiceMock<MockProgressReporter> progress_reporter_;
   scoped_refptr<gl::GLSurface> surface_;
   scoped_refptr<gl::GLContext> context_;
@@ -1187,7 +1192,7 @@
       shared_image_manager_->Register(std::move(backing),
                                       memory_type_tracker_.get());
   scoped_refptr<gl::GLImage> image = GetImageFromMailbox(mailbox);
-  ASSERT_EQ(image->GetType(), gl::GLImage::Type::NONE);
+  AssertGLImageHasTypeNone(image.get());
   auto* stub_image = static_cast<StubImage*>(image.get());
   EXPECT_FALSE(stub_image->bound());
   int update_counter = stub_image->update_counter();
@@ -1230,5 +1235,4 @@
                          kSharedImageFormats,
                          TestParamToString);
 
-}  // anonymous namespace
 }  // namespace gpu
diff --git a/gpu/command_buffer/service/shared_image/ozone_image_backing_factory.cc b/gpu/command_buffer/service/shared_image/ozone_image_backing_factory.cc
index e807629..b335e57 100644
--- a/gpu/command_buffer/service/shared_image/ozone_image_backing_factory.cc
+++ b/gpu/command_buffer/service/shared_image/ozone_image_backing_factory.cc
@@ -35,6 +35,25 @@
 namespace gpu {
 namespace {
 
+// Returns BufferFormat for given `format`.
+gfx::BufferFormat GetBufferFormat(viz::SharedImageFormat format) {
+  if (format.is_single_plane())
+    viz::BufferFormat(format.resource_format());
+
+  switch (format.plane_config()) {
+    case viz::SharedImageFormat::PlaneConfig::kY_V_U:
+      return gfx::BufferFormat::YVU_420;
+    case viz::SharedImageFormat::PlaneConfig::kY_UV:
+      return format.channel_format() ==
+                     viz::SharedImageFormat::ChannelFormat::k10
+                 ? gfx::BufferFormat::P010
+                 : gfx::BufferFormat::YUV_420_BIPLANAR;
+    case viz::SharedImageFormat::PlaneConfig::kY_UV_A:
+      return gfx::BufferFormat::YUVA_420_TRIPLANAR;
+  }
+  NOTREACHED();
+}
+
 gfx::BufferUsage GetBufferUsage(uint32_t usage) {
   if (usage & SHARED_IMAGE_USAGE_WEBGPU) {
     // Just use SCANOUT for WebGPU since the memory doesn't need to be linear.
@@ -184,6 +203,36 @@
   return backing;
 }
 
+std::unique_ptr<SharedImageBacking> OzoneImageBackingFactory::CreateSharedImage(
+    const Mailbox& mailbox,
+    viz::SharedImageFormat format,
+    const gfx::Size& size,
+    const gfx::ColorSpace& color_space,
+    GrSurfaceOrigin surface_origin,
+    SkAlphaType alpha_type,
+    uint32_t usage,
+    gfx::GpuMemoryBufferHandle handle) {
+  DCHECK_EQ(handle.type, gfx::NATIVE_PIXMAP);
+
+  ui::SurfaceFactoryOzone* surface_factory =
+      ui::OzonePlatform::GetInstance()->GetSurfaceFactoryOzone();
+  scoped_refptr<gfx::NativePixmap> pixmap =
+      surface_factory->CreateNativePixmapFromHandle(
+          kNullSurfaceHandle, size, GetBufferFormat(format),
+          std::move(handle.native_pixmap_handle));
+  if (!pixmap) {
+    return nullptr;
+  }
+
+  auto backing = std::make_unique<OzoneImageBacking>(
+      mailbox, format, gfx::BufferPlane::DEFAULT, size, color_space,
+      surface_origin, alpha_type, usage, shared_context_state_.get(),
+      std::move(pixmap), dawn_procs_, workarounds_, use_passthrough_);
+  backing->SetCleared();
+
+  return backing;
+}
+
 bool OzoneImageBackingFactory::IsSupported(
     uint32_t usage,
     viz::SharedImageFormat format,
@@ -192,7 +241,7 @@
     gfx::GpuMemoryBufferType gmb_type,
     GrContextType gr_context_type,
     base::span<const uint8_t> pixel_data) {
-  if (format.is_multi_plane()) {
+  if (format.is_multi_plane() && gmb_type != gfx::NATIVE_PIXMAP) {
     return false;
   }
 
diff --git a/gpu/command_buffer/service/shared_image/ozone_image_backing_factory.h b/gpu/command_buffer/service/shared_image/ozone_image_backing_factory.h
index 0caa9b4..abf54b0 100644
--- a/gpu/command_buffer/service/shared_image/ozone_image_backing_factory.h
+++ b/gpu/command_buffer/service/shared_image/ozone_image_backing_factory.h
@@ -54,6 +54,16 @@
 
   std::unique_ptr<SharedImageBacking> CreateSharedImage(
       const Mailbox& mailbox,
+      viz::SharedImageFormat format,
+      const gfx::Size& size,
+      const gfx::ColorSpace& color_space,
+      GrSurfaceOrigin surface_origin,
+      SkAlphaType alpha_type,
+      uint32_t usage,
+      gfx::GpuMemoryBufferHandle handle) override;
+
+  std::unique_ptr<SharedImageBacking> CreateSharedImage(
+      const Mailbox& mailbox,
       int client_id,
       gfx::GpuMemoryBufferHandle handle,
       gfx::BufferFormat format,
diff --git a/gpu/command_buffer/service/shared_image/raw_draw_image_backing.cc b/gpu/command_buffer/service/shared_image/raw_draw_image_backing.cc
index c73da28..48290314 100644
--- a/gpu/command_buffer/service/shared_image/raw_draw_image_backing.cc
+++ b/gpu/command_buffer/service/shared_image/raw_draw_image_backing.cc
@@ -13,6 +13,7 @@
 #include "gpu/command_buffer/service/shared_context_state.h"
 #include "gpu/command_buffer/service/shared_image/shared_image_representation.h"
 #include "gpu/command_buffer/service/skia_utils.h"
+#include "third_party/skia/include/core/SkCanvas.h"
 #include "third_party/skia/include/core/SkColorSpace.h"
 #include "third_party/skia/include/core/SkPromiseImageTexture.h"
 #include "ui/gl/trace_util.h"
diff --git a/gpu/command_buffer/service/shared_image/shared_image_format_utils.cc b/gpu/command_buffer/service/shared_image/shared_image_format_utils.cc
index 8f7f650..b99ddf7 100644
--- a/gpu/command_buffer/service/shared_image/shared_image_format_utils.cc
+++ b/gpu/command_buffer/service/shared_image/shared_image_format_utils.cc
@@ -113,16 +113,15 @@
 
   // For multiplanar formats without external sampler, GL formats are per plane.
   // For single channel 8-bit planes Y, U, V, A return GL_RED_EXT.
-  // For single channel 10-bit planes Y return GL_R16_EXT.
+  // For single channel 10/16-bit planes Y,  U, V, A return GL_R16_EXT.
   // For 2 channel plane 8-bit UV return GL_RG_EXT.
-  // For 2 channel plane 16-bit UV return GL_RG16_EXT.
+  // For 2 channel plane 10/16-bit UV return GL_RG16_EXT.
   int num_channels = format.NumChannelsInPlane(plane_index);
   DCHECK_LE(num_channels, 2);
   switch (format.channel_format()) {
     case viz::SharedImageFormat::ChannelFormat::k8:
       return num_channels == 2 ? GL_RG_EXT : GL_RED_EXT;
     case viz::SharedImageFormat::ChannelFormat::k10:
-      return num_channels == 2 ? GL_RG16_EXT : GL_R16_EXT;
     case viz::SharedImageFormat::ChannelFormat::k16:
       return num_channels == 2 ? GL_RG16_EXT : GL_R16_EXT;
     case viz::SharedImageFormat::ChannelFormat::k16F:
@@ -140,16 +139,15 @@
 
   // For multiplanar formats without external sampler, GL formats are per plane.
   // For single channel 8-bit planes Y, U, V, A return GL_R8_EXT.
-  // For single channel 10-bit planes Y return GL_R16_EXT.
+  // For single channel 10/16-bit planes Y,  U, V, A return GL_R16_EXT.
   // For 2 channel plane 8-bit UV return GL_RG8_EXT.
-  // For 2 channel plane 16-bit UV return GL_RG16_EXT.
+  // For 2 channel plane 10/16-bit UV return GL_RG16_EXT.
   int num_channels = format.NumChannelsInPlane(plane_index);
   DCHECK_LE(num_channels, 2);
   switch (format.channel_format()) {
     case viz::SharedImageFormat::ChannelFormat::k8:
       return num_channels == 2 ? GL_RG8_EXT : GL_R8_EXT;
     case viz::SharedImageFormat::ChannelFormat::k10:
-      return num_channels == 2 ? GL_RG16_EXT : GL_R16_EXT;
     case viz::SharedImageFormat::ChannelFormat::k16:
       return num_channels == 2 ? GL_RG16_EXT : GL_R16_EXT;
     case viz::SharedImageFormat::ChannelFormat::k16F:
diff --git a/gpu/command_buffer/service/shared_image/shared_image_format_utils.h b/gpu/command_buffer/service/shared_image/shared_image_format_utils.h
index 428e247..0a01f7b 100644
--- a/gpu/command_buffer/service/shared_image/shared_image_format_utils.h
+++ b/gpu/command_buffer/service/shared_image/shared_image_format_utils.h
@@ -100,7 +100,8 @@
 // SharedImageFormat.
 #if BUILDFLAG(IS_APPLE)
 // Returns MtlPixelFormat format for given `format`.
-GPU_GLES2_EXPORT unsigned int ToMTLPixelFormat(viz::SharedImageFormat format);
+GPU_GLES2_EXPORT unsigned int ToMTLPixelFormat(viz::SharedImageFormat format,
+                                               int plane_index = 0);
 #endif
 
 }  // namespace gpu
diff --git a/gpu/command_buffer/service/shared_image/shared_image_format_utils_mac.mm b/gpu/command_buffer/service/shared_image/shared_image_format_utils_mac.mm
index 7c4bcf0..47bbe82 100644
--- a/gpu/command_buffer/service/shared_image/shared_image_format_utils_mac.mm
+++ b/gpu/command_buffer/service/shared_image/shared_image_format_utils_mac.mm
@@ -4,13 +4,47 @@
 
 #include "gpu/command_buffer/service/shared_image/shared_image_format_utils.h"
 
+#include <Metal/MTLPixelFormat.h>
+
+#include "base/check_op.h"
 #include "components/viz/common/resources/resource_format_utils.h"
 
 namespace gpu {
 
-// TODO (hitawala): Add support for multiplanar formats.
-unsigned int ToMTLPixelFormat(viz::SharedImageFormat format) {
-  return viz::ToMTLPixelFormat(format.resource_format());
+unsigned int ToMTLPixelFormat(viz::SharedImageFormat format, int plane_index) {
+  if (format.is_single_plane())
+    return viz::ToMTLPixelFormat(format.resource_format());
+
+  // Does not support external sampler.
+  if (format.PrefersExternalSampler())
+    return static_cast<unsigned int>(MTLPixelFormatInvalid);
+
+  // For multiplanar formats without external sampler, Metal formats are per
+  // plane.
+  // For single channel 8-bit planes Y, U, V, A return MTLPixelFormatR8Unorm.
+  // For 2 channel plane 8-bit UV return MTLPixelFormatRG8Unorm.
+  // For single channel 10/16-bit planes Y, U, V, A return
+  // MTLPixelFormatR16Unorm. For 2 channel plane 10/16-bit UV return
+  // MTLPixelFormatRG16Unorm.
+  int num_channels = format.NumChannelsInPlane(plane_index);
+  DCHECK_LE(num_channels, 2);
+  MTLPixelFormat mtl_pixel_format = MTLPixelFormatInvalid;
+  switch (format.channel_format()) {
+    case viz::SharedImageFormat::ChannelFormat::k8:
+      mtl_pixel_format =
+          num_channels == 2 ? MTLPixelFormatRG8Unorm : MTLPixelFormatR8Unorm;
+      break;
+    case viz::SharedImageFormat::ChannelFormat::k10:
+    case viz::SharedImageFormat::ChannelFormat::k16:
+      mtl_pixel_format =
+          num_channels == 2 ? MTLPixelFormatRG16Unorm : MTLPixelFormatR16Unorm;
+      break;
+    case viz::SharedImageFormat::ChannelFormat::k16F:
+      mtl_pixel_format =
+          num_channels == 2 ? MTLPixelFormatRG16Float : MTLPixelFormatR16Float;
+      break;
+  }
+  return static_cast<unsigned int>(mtl_pixel_format);
 }
 
 }  // namespace gpu
\ No newline at end of file
diff --git a/gpu/ipc/service/command_buffer_stub.cc b/gpu/ipc/service/command_buffer_stub.cc
index cbb7b0e..c51a4a9 100644
--- a/gpu/ipc/service/command_buffer_stub.cc
+++ b/gpu/ipc/service/command_buffer_stub.cc
@@ -254,7 +254,7 @@
     process_delayed_work_timer_.Start(
         FROM_HERE, current_time + delay,
         base::BindOnce(&CommandBufferStub::PollWork, AsWeakPtr()),
-        base::ExactDeadline(true));
+        base::subtle::DelayPolicy::kPrecise);
     return;
   }
 
@@ -278,7 +278,7 @@
   process_delayed_work_timer_.Start(
       FROM_HERE, current_time + delay,
       base::BindOnce(&CommandBufferStub::PollWork, AsWeakPtr()),
-      base::ExactDeadline(true));
+      base::subtle::DelayPolicy::kPrecise);
 }
 
 bool CommandBufferStub::MakeCurrent() {
diff --git a/infra/config/generated/builders/ci/GPU Mac Builder/properties.json b/infra/config/generated/builders/ci/GPU Mac Builder/properties.json
index b926564..d160c0554 100644
--- a/infra/config/generated/builders/ci/GPU Mac Builder/properties.json
+++ b/infra/config/generated/builders/ci/GPU Mac Builder/properties.json
@@ -122,10 +122,6 @@
           "group": "tryserver.chromium.mac"
         },
         {
-          "builder": "mac-rel-cft",
-          "group": "tryserver.chromium.mac"
-        },
-        {
           "builder": "mac-rel-inverse-fyi",
           "group": "tryserver.chromium.mac"
         }
diff --git a/infra/config/generated/builders/ci/Mac Builder/properties.json b/infra/config/generated/builders/ci/Mac Builder/properties.json
index 3cced7f..ed962967 100644
--- a/infra/config/generated/builders/ci/Mac Builder/properties.json
+++ b/infra/config/generated/builders/ci/Mac Builder/properties.json
@@ -228,10 +228,6 @@
           "group": "tryserver.chromium.mac"
         },
         {
-          "builder": "mac-rel-cft",
-          "group": "tryserver.chromium.mac"
-        },
-        {
           "builder": "mac-rel-inverse-fyi",
           "group": "tryserver.chromium.mac"
         },
diff --git "a/infra/config/generated/builders/ci/Mac Release \050Intel\051/properties.json" "b/infra/config/generated/builders/ci/Mac Release \050Intel\051/properties.json"
index 12ccca0a..337ba511 100644
--- "a/infra/config/generated/builders/ci/Mac Release \050Intel\051/properties.json"
+++ "b/infra/config/generated/builders/ci/Mac Release \050Intel\051/properties.json"
@@ -80,10 +80,6 @@
           "group": "tryserver.chromium.mac"
         },
         {
-          "builder": "mac-rel-cft",
-          "group": "tryserver.chromium.mac"
-        },
-        {
           "builder": "mac-rel-inverse-fyi",
           "group": "tryserver.chromium.mac"
         }
diff --git a/infra/config/generated/builders/ci/Mac12 Tests/properties.json b/infra/config/generated/builders/ci/Mac12 Tests/properties.json
index 1afa07f0..59e2553 100644
--- a/infra/config/generated/builders/ci/Mac12 Tests/properties.json
+++ b/infra/config/generated/builders/ci/Mac12 Tests/properties.json
@@ -79,10 +79,6 @@
           "group": "tryserver.chromium.mac"
         },
         {
-          "builder": "mac-rel-cft",
-          "group": "tryserver.chromium.mac"
-        },
-        {
           "builder": "mac-rel-inverse-fyi",
           "group": "tryserver.chromium.mac"
         },
diff --git a/infra/config/generated/builders/ci/mac-rel-cft/properties.json b/infra/config/generated/builders/ci/mac-rel-cft/properties.json
new file mode 100644
index 0000000..a3e82ff
--- /dev/null
+++ b/infra/config/generated/builders/ci/mac-rel-cft/properties.json
@@ -0,0 +1,63 @@
+{
+  "$build/chromium_tests_builder_config": {
+    "builder_config": {
+      "builder_db": {
+        "entries": [
+          {
+            "builder_id": {
+              "bucket": "ci",
+              "builder": "mac-rel-cft",
+              "project": "chromium"
+            },
+            "builder_spec": {
+              "builder_group": "chromium.cft",
+              "execution_mode": "COMPILE_AND_TEST",
+              "legacy_chromium_config": {
+                "apply_configs": [
+                  "mb"
+                ],
+                "build_config": "Release",
+                "config": "chromium",
+                "target_bits": 64,
+                "target_platform": "mac"
+              },
+              "legacy_gclient_config": {
+                "config": "chromium"
+              }
+            }
+          }
+        ]
+      },
+      "builder_ids": [
+        {
+          "bucket": "ci",
+          "builder": "mac-rel-cft",
+          "project": "chromium"
+        }
+      ],
+      "mirroring_builder_group_and_names": [
+        {
+          "builder": "mac-rel-cft",
+          "group": "tryserver.chromium.cft"
+        }
+      ]
+    }
+  },
+  "$build/reclient": {
+    "instance": "rbe-chromium-trusted",
+    "jobs": 500,
+    "metrics_project": "chromium-reclient-metrics"
+  },
+  "$recipe_engine/resultdb/test_presentation": {
+    "column_keys": [],
+    "grouping_keys": [
+      "status",
+      "v.test_suite"
+    ]
+  },
+  "builder_group": "chromium.cft",
+  "recipe": "chromium",
+  "sheriff_rotations": [
+    "cft"
+  ]
+}
\ No newline at end of file
diff --git a/infra/config/generated/builders/try/mac-rel-cft/properties.json b/infra/config/generated/builders/try/mac-rel-cft/properties.json
index d462704c..514ec8a 100644
--- a/infra/config/generated/builders/try/mac-rel-cft/properties.json
+++ b/infra/config/generated/builders/try/mac-rel-cft/properties.json
@@ -6,73 +6,15 @@
           {
             "builder_id": {
               "bucket": "ci",
-              "builder": "GPU Mac Builder",
+              "builder": "mac-rel-cft",
               "project": "chromium"
             },
             "builder_spec": {
-              "build_gs_bucket": "chromium-gpu-archive",
-              "builder_group": "chromium.gpu",
+              "builder_group": "chromium.cft",
               "execution_mode": "COMPILE_AND_TEST",
               "legacy_chromium_config": {
                 "apply_configs": [
-                  "mb",
-                  "goma_use_local"
-                ],
-                "build_config": "Release",
-                "config": "chromium",
-                "target_bits": 64,
-                "target_platform": "mac"
-              },
-              "legacy_gclient_config": {
-                "apply_configs": [
-                  "use_clang_coverage"
-                ],
-                "config": "chromium"
-              }
-            }
-          },
-          {
-            "builder_id": {
-              "bucket": "ci",
-              "builder": "Mac Builder",
-              "project": "chromium"
-            },
-            "builder_spec": {
-              "build_gs_bucket": "chromium-mac-archive",
-              "builder_group": "chromium.mac",
-              "execution_mode": "COMPILE_AND_TEST",
-              "legacy_chromium_config": {
-                "apply_configs": [
-                  "mb",
-                  "goma_use_local"
-                ],
-                "build_config": "Release",
-                "config": "chromium",
-                "target_bits": 64,
-                "target_platform": "mac"
-              },
-              "legacy_gclient_config": {
-                "apply_configs": [
-                  "use_clang_coverage"
-                ],
-                "config": "chromium"
-              }
-            }
-          },
-          {
-            "builder_id": {
-              "bucket": "ci",
-              "builder": "Mac Release (Intel)",
-              "project": "chromium"
-            },
-            "builder_spec": {
-              "build_gs_bucket": "chromium-gpu-archive",
-              "builder_group": "chromium.gpu",
-              "execution_mode": "TEST",
-              "legacy_chromium_config": {
-                "apply_configs": [
-                  "mb",
-                  "goma_use_local"
+                  "mb"
                 ],
                 "build_config": "Release",
                 "config": "chromium",
@@ -81,40 +23,6 @@
               },
               "legacy_gclient_config": {
                 "config": "chromium"
-              },
-              "parent": {
-                "bucket": "ci",
-                "builder": "GPU Mac Builder",
-                "project": "chromium"
-              }
-            }
-          },
-          {
-            "builder_id": {
-              "bucket": "ci",
-              "builder": "Mac12 Tests",
-              "project": "chromium"
-            },
-            "builder_spec": {
-              "builder_group": "chromium.mac",
-              "execution_mode": "TEST",
-              "legacy_chromium_config": {
-                "apply_configs": [
-                  "mb",
-                  "goma_use_local"
-                ],
-                "build_config": "Release",
-                "config": "chromium",
-                "target_bits": 64,
-                "target_platform": "mac"
-              },
-              "legacy_gclient_config": {
-                "config": "chromium"
-              },
-              "parent": {
-                "bucket": "ci",
-                "builder": "Mac Builder",
-                "project": "chromium"
               }
             }
           }
@@ -123,30 +31,14 @@
       "builder_ids": [
         {
           "bucket": "ci",
-          "builder": "GPU Mac Builder",
-          "project": "chromium"
-        },
-        {
-          "bucket": "ci",
-          "builder": "Mac Builder",
-          "project": "chromium"
-        }
-      ],
-      "builder_ids_in_scope_for_testing": [
-        {
-          "bucket": "ci",
-          "builder": "Mac Release (Intel)",
-          "project": "chromium"
-        },
-        {
-          "bucket": "ci",
-          "builder": "Mac12 Tests",
+          "builder": "mac-rel-cft",
           "project": "chromium"
         }
       ]
     }
   },
   "$build/goma": {
+    "jobs": 150,
     "rpc_extra_params": "?prod",
     "server_host": "goma.chromium.org"
   },
@@ -157,6 +49,6 @@
       "v.test_suite"
     ]
   },
-  "builder_group": "tryserver.chromium.mac",
+  "builder_group": "tryserver.chromium.cft",
   "recipe": "chromium_trybot"
 }
\ 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 470e5eb..c9bbed3b 100644
--- a/infra/config/generated/luci/cr-buildbucket.cfg
+++ b/infra/config/generated/luci/cr-buildbucket.cfg
@@ -43087,6 +43087,96 @@
       }
     }
     builders {
+      name: "mac-rel-cft"
+      swarming_host: "chromium-swarm.appspot.com"
+      dimensions: "builderless:1"
+      dimensions: "cpu:x86-64"
+      dimensions: "free_space:standard"
+      dimensions: "os:Mac-12"
+      dimensions: "pool:luci.chromium.ci"
+      dimensions: "ssd:1"
+      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/ci/mac-rel-cft/properties.json",'
+        '    "top_level_project": {'
+        '      "ref": "refs/heads/main",'
+        '      "repo": {'
+        '        "host": "chromium.googlesource.com",'
+        '        "project": "chromium/src"'
+        '      }'
+        '    }'
+        '  },'
+        '  "builder_group": "chromium.cft",'
+        '  "led_builder_is_bootstrapped": true,'
+        '  "recipe": "chromium",'
+        '  "sheriff_rotations": ['
+        '    "cft"'
+        '  ]'
+        '}'
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "chromium_swarming.expose_merge_script_failures"
+        value: 100
+      }
+      experiments {
+        key: "luci.buildbucket.omit_python2"
+        value: 100
+      }
+      experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      resultdb {
+        enable: true
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "ci_test_results"
+          test_results {}
+        }
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "gpu_ci_test_results"
+          test_results {
+            predicate {
+              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+            }
+          }
+        }
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "blink_web_tests_ci_test_results"
+          test_results {
+            predicate {
+              test_id_regexp: "(ninja://[^/]*blink_web_tests/.+)|(ninja://[^/]*blink_wpt_tests/.+)"
+            }
+          }
+        }
+        history_options {
+          use_invocation_timestamp: true
+        }
+      }
+    }
+    builders {
       name: "mac-swangle-chromium-x64"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builderless:1"
@@ -85719,7 +85809,7 @@
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builderless:1"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac"
+      dimensions: "os:Mac-12"
       dimensions: "pool:luci.chromium.try"
       dimensions: "ssd:1"
       exe {
@@ -85748,7 +85838,7 @@
         '      }'
         '    }'
         '  },'
-        '  "builder_group": "tryserver.chromium.mac",'
+        '  "builder_group": "tryserver.chromium.cft",'
         '  "led_builder_is_bootstrapped": true,'
         '  "recipe": "chromium_trybot"'
         '}'
diff --git a/infra/config/generated/luci/luci-milo.cfg b/infra/config/generated/luci/luci-milo.cfg
index 11ab7b6..7120c33 100644
--- a/infra/config/generated/luci/luci-milo.cfg
+++ b/infra/config/generated/luci/luci-milo.cfg
@@ -5570,6 +5570,338 @@
   }
 }
 consoles {
+  id: "chromium.cft"
+  name: "chromium.cft"
+  repo_url: "https://chromium.googlesource.com/chromium/src"
+  refs: "regexp:refs/heads/main"
+  manifest_name: "REVISION"
+  builders {
+    name: "buildbucket/luci.chromium.ci/mac-rel-cft"
+  }
+  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: "packager"
+        url: "/p/chromium/g/chromium.packager"
+        alt: "Chromium Packager 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: "m102"
+        url: "/p/chromium-m102/g/main/console"
+      }
+      links {
+        text: "m104"
+        url: "/p/chromium-m104/g/main/console"
+      }
+      links {
+        text: "m105"
+        url: "/p/chromium-m105/g/main/console"
+      }
+      links {
+        text: "m106"
+        url: "/p/chromium-m106/g/main/console"
+      }
+      links {
+        text: "m107"
+        url: "/p/chromium-m107/g/main/console"
+      }
+      links {
+        text: "m108"
+        url: "/p/chromium-m108/g/main/console"
+      }
+      links {
+        text: "m109"
+        url: "/p/chromium-m109/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: "chromium.chromiumos"
   name: "chromium.chromiumos"
   repo_url: "https://chromium.googlesource.com/chromium/src"
@@ -17556,6 +17888,14 @@
   builder_view_only: true
 }
 consoles {
+  id: "tryserver.chromium.cft"
+  name: "tryserver.chromium.cft"
+  builders {
+    name: "buildbucket/luci.chromium.try/mac-rel-cft"
+  }
+  builder_view_only: true
+}
+consoles {
   id: "tryserver.chromium.chromiumos"
   name: "tryserver.chromium.chromiumos"
   builders {
@@ -18136,9 +18476,6 @@
     name: "buildbucket/luci.chromium.try/mac-rel"
   }
   builders {
-    name: "buildbucket/luci.chromium.try/mac-rel-cft"
-  }
-  builders {
     name: "buildbucket/luci.chromium.try/mac-rel-compilator"
   }
   builders {
diff --git a/infra/config/generated/luci/luci-scheduler.cfg b/infra/config/generated/luci/luci-scheduler.cfg
index 62a4cee..06092ea 100644
--- a/infra/config/generated/luci/luci-scheduler.cfg
+++ b/infra/config/generated/luci/luci-scheduler.cfg
@@ -5335,6 +5335,15 @@
   }
 }
 job {
+  id: "mac-rel-cft"
+  realm: "ci"
+  buildbucket {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "ci"
+    builder: "mac-rel-cft"
+  }
+}
+job {
   id: "mac-swangle-chromium-x64"
   realm: "ci"
   buildbucket {
@@ -6243,6 +6252,7 @@
   triggers: "mac-code-coverage"
   triggers: "mac-hermetic-upgrade-rel"
   triggers: "mac-official"
+  triggers: "mac-rel-cft"
   triggers: "mac-swangle-chromium-x64"
   triggers: "mac-updater-builder-arm64-dbg"
   triggers: "mac-updater-builder-arm64-rel"
diff --git a/infra/config/generated/sheriff-rotations/cft.txt b/infra/config/generated/sheriff-rotations/cft.txt
new file mode 100644
index 0000000..5f4494d5
--- /dev/null
+++ b/infra/config/generated/sheriff-rotations/cft.txt
@@ -0,0 +1 @@
+ci/mac-rel-cft
diff --git a/infra/config/lib/builders.star b/infra/config/lib/builders.star
index 25ff090e..cfdc647 100644
--- a/infra/config/lib/builders.star
+++ b/infra/config/lib/builders.star
@@ -148,6 +148,7 @@
     ANDROID = _rotation("android"),
     ANGLE = _rotation("angle"),
     CHROMIUM = _rotation("chromium"),
+    CFT = _rotation("cft"),
     FUCHSIA = _rotation("fuchsia"),
     CHROMIUM_CLANG = _rotation("chromium.clang"),
     CHROMIUM_FUZZ = _rotation("chromium.fuzz"),
diff --git a/infra/config/lib/polymorphic.star b/infra/config/lib/polymorphic.star
index 7cc136fa0..b5edcf5 100644
--- a/infra/config/lib/polymorphic.star
+++ b/infra/config/lib/polymorphic.star
@@ -4,6 +4,8 @@
 
 """Library for defining polymorphic builders."""
 
+load("@stdlib//internal/graph.star", "graph")
+load("@stdlib//internal/luci/common.star", "builder_ref", "keys")
 load("./builders.star", "builder", "defaults")
 load("./nodes.star", "nodes")
 load("//project.star", "settings")
@@ -11,6 +13,7 @@
 _LAUNCHER = nodes.create_bucket_scoped_node_type("polymorphic-launcher")
 _RUNNER = nodes.create_link_node_type("polymorphic-runner", _LAUNCHER, nodes.BUILDER)
 _TARGET_BUILDER = nodes.create_link_node_type("polymorphic-target", _LAUNCHER, nodes.BUILDER)
+_TARGET_TESTER = nodes.create_link_node_type("polymorphic-target-tester", nodes.BUILDER, nodes.BUILDER)
 
 def _builder_ref_to_builder_id(ref):
     bucket, builder = ref.split("/", 1)
@@ -20,7 +23,7 @@
         builder = builder,
     )
 
-def _target_builder(*, builder, dimensions = {}):
+def _target_builder(*, builder, dimensions = {}, testers = None):
     """Details for a target builder for a polymorphic launcher.
 
     Args:
@@ -31,10 +34,14 @@
             the runner builder. An empty dimension value will remove the
             dimension when the runner builder is triggered for the target
             builder.
+        testers: (list[str]) An optional list of testers to restrict the
+            operation to. If not specified, then the operation will include all
+            testers that are triggered by the target builder.
     """
     return struct(
         builder = builder,
         dimensions = dimensions,
+        testers = testers,
     )
 
 def _launcher(
@@ -56,7 +63,9 @@
         target_builders: (list[str|target_builder]) The target builders that the
             runner builder should be triggered for. Can either be an object
             returned by polymorphic.target_builder or a string with the
-            bucket-qualified reference to the target builder.
+            bucket-qualified reference to the target builder. It should be noted
+            that an empty list has different behavior from the default: none of
+            the triggered testers will be included in the operation.
         **kwargs: Additional keyword arguments to be passed onto
             `builders.builder`.
 
@@ -68,7 +77,11 @@
     target_builders = [_target_builder(builder = t) if type(t) == type("") else t for t in target_builders]
     bucket = defaults.get_value_from_kwargs("bucket", kwargs)
 
-    launcher_key = _LAUNCHER.add(bucket, name)
+    launcher_key = _LAUNCHER.add(bucket, name, props = dict(
+        runner = runner,
+        target_builders = target_builders,
+    ))
+    graph.add_edge(keys.project(), launcher_key)
 
     # Create links to the runner and target builders. We don't actually do
     # anything with the links, but lucicfg will check that the nodes that are
@@ -77,25 +90,15 @@
     _RUNNER.link(launcher_key, runner)
     for t in target_builders:
         _TARGET_BUILDER.link(launcher_key, t.builder)
-
-    def _target_builder_prop(t):
-        p = {"builder_id": _builder_ref_to_builder_id(t.builder)}
-        if t.dimensions:
-            p["dimensions"] = t.dimensions
-        return p
-
-    properties = dict(kwargs.pop("properties", {}))
-    properties.update({
-        "runner_builder": _builder_ref_to_builder_id(runner),
-        "target_builders": [_target_builder_prop(t) for t in target_builders],
-    })
+        if t.testers != None:
+            for tester in t.testers:
+                _TARGET_TESTER.link(launcher_key, tester)
 
     kwargs.setdefault("executable", "recipe:chromium_polymorphic/launcher")
     kwargs.setdefault("resultdb_enable", False)
 
     return builder(
         name = name,
-        properties = properties,
         **kwargs
     )
 
@@ -103,3 +106,63 @@
     launcher = _launcher,
     target_builder = _target_builder,
 )
+
+def _get_tester_group_and_name(context_node, builder_proto_by_key, tester_ref):
+    builder_ref_node = graph.node(keys.builder_ref(tester_ref))
+    builder_node = builder_ref.follow(builder_ref_node, context_node)
+    builder_proto = builder_proto_by_key[builder_node.key]
+    builder_group = json.decode(builder_proto.properties)["builder_group"]
+    return {
+        "group": builder_group,
+        "builder": builder_node.key.id,
+    }
+
+def _target_builder_prop(context_node, builder_proto_by_key, target_builder):
+    p = {"builder_id": _builder_ref_to_builder_id(target_builder.builder)}
+    if target_builder.dimensions:
+        p["dimensions"] = t.dimensions
+    if target_builder.testers != None:
+        testers = []
+        p["tester_filter"] = testers
+        for t in target_builder.testers:
+            testers.append(_get_tester_group_and_name(context_node, builder_proto_by_key, t))
+    return p
+
+def _generate_launcher_properties(ctx):
+    cfg = None
+    for f in ctx.output:
+        if f.startswith("luci/cr-buildbucket"):
+            cfg = ctx.output[f]
+            break
+    if cfg == None:
+        fail("There is no buildbucket configuration file to update properties")
+
+    builder_proto_by_key = {}
+    for bucket in cfg.buckets:
+        if not proto.has(bucket, "swarming"):
+            continue
+        bucket_name = bucket.name
+        for builder in bucket.swarming.builders:
+            builder_name = builder.name
+            builder_proto_by_key[keys.builder(bucket_name, builder_name)] = builder
+
+    for bucket in cfg.buckets:
+        if not proto.has(bucket, "swarming"):
+            continue
+        bucket_name = bucket.name
+        for builder in bucket.swarming.builders:
+            builder_name = builder.name
+            node = _LAUNCHER.get(bucket_name, builder_name)
+            if not node:
+                continue
+
+            properties = json.decode(builder.properties)
+
+            properties.update({
+                "runner_builder": _builder_ref_to_builder_id(node.props.runner),
+                "target_builders": [_target_builder_prop(node, builder_proto_by_key, t) for t in node.props.target_builders],
+            })
+
+            builder.properties = json.encode(properties)
+
+lucicfg.generator(_generate_launcher_properties)
diff --git a/infra/config/subprojects/chromium/ci.star b/infra/config/subprojects/chromium/ci.star
index 3099c6e7..43e185a 100644
--- a/infra/config/subprojects/chromium/ci.star
+++ b/infra/config/subprojects/chromium/ci.star
@@ -77,6 +77,7 @@
         "chromium.gpu",
         "chromium.fyi",
         "chromium.android.fyi",
+        "chromium.cft",
         "chromium.clang",
         "chromium.fuzz",
         "chromium.gpu.fyi",
@@ -145,6 +146,7 @@
 exec("./ci/chromium.android.star")
 exec("./ci/chromium.android.fyi.star")
 exec("./ci/chromium.angle.star")
+exec("./ci/chromium.cft.star")
 exec("./ci/chromium.chromiumos.star")
 exec("./ci/chromium.clang.star")
 exec("./ci/chromium.dawn.star")
diff --git a/infra/config/subprojects/chromium/ci/chromium.cft.star b/infra/config/subprojects/chromium/ci/chromium.cft.star
new file mode 100644
index 0000000..461d9fe
--- /dev/null
+++ b/infra/config/subprojects/chromium/ci/chromium.cft.star
@@ -0,0 +1,49 @@
+# 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.
+"""Definitions for the chromium.cft (chrome for testing) builder group."""
+
+load("//lib/builder_config.star", "builder_config")
+load("//lib/builders.star", "os", "reclient", "sheriff_rotations")
+load("//lib/ci.star", "ci")
+load("//lib/consoles.star", "consoles")
+
+ci.defaults.set(
+    builder_group = "chromium.cft",
+    builderless = True,
+    cores = 8,
+    executable = ci.DEFAULT_EXECUTABLE,
+    execution_timeout = ci.DEFAULT_EXECUTION_TIMEOUT,
+    pool = ci.DEFAULT_POOL,
+    reclient_jobs = reclient.jobs.HIGH_JOBS_FOR_CI,
+    reclient_instance = reclient.instance.DEFAULT_TRUSTED,
+    service_account = ci.DEFAULT_SERVICE_ACCOUNT,
+    sheriff_rotations = sheriff_rotations.CFT,
+    ssd = True,
+    tree_closing = False,
+)
+
+consoles.console_view(
+    name = "chromium.cft",
+)
+
+ci.builder(
+    name = "mac-rel-cft",
+    console_view_entry = consoles.console_view_entry(),
+    builder_spec = builder_config.builder_spec(
+        chromium_config = builder_config.chromium_config(
+            config = "chromium",
+            apply_configs = [
+                "mb",
+            ],
+            build_config = builder_config.build_config.RELEASE,
+            target_bits = 64,
+            target_platform = builder_config.target_platform.MAC,
+        ),
+        gclient_config = builder_config.gclient_config(
+            config = "chromium",
+        ),
+    ),
+    cores = None,
+    os = os.MAC_DEFAULT,
+)
diff --git a/infra/config/subprojects/chromium/try.star b/infra/config/subprojects/chromium/try.star
index 27c8ced4..252021f 100644
--- a/infra/config/subprojects/chromium/try.star
+++ b/infra/config/subprojects/chromium/try.star
@@ -135,6 +135,7 @@
 exec("./try/tryserver.chromium.android.star")
 exec("./try/tryserver.chromium.angle.star")
 exec("./try/tryserver.chromium.chromiumos.star")
+exec("./try/tryserver.chromium.cft.star")
 exec("./try/tryserver.chromium.dawn.star")
 exec("./try/tryserver.chromium.fuchsia.star")
 exec("./try/tryserver.chromium.linux.star")
diff --git a/infra/config/subprojects/chromium/try/tryserver.chromium.cft.star b/infra/config/subprojects/chromium/try/tryserver.chromium.cft.star
new file mode 100644
index 0000000..4eeb19f
--- /dev/null
+++ b/infra/config/subprojects/chromium/try/tryserver.chromium.cft.star
@@ -0,0 +1,33 @@
+# 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.
+"""Definitions of builders in the tryserver.chromium.mac builder group."""
+
+load("//lib/builders.star", "goma", "os")
+load("//lib/try.star", "try_")
+load("//lib/consoles.star", "consoles")
+
+try_.defaults.set(
+    builder_group = "tryserver.chromium.cft",
+    builderless = True,
+    executable = try_.DEFAULT_EXECUTABLE,
+    execution_timeout = try_.DEFAULT_EXECUTION_TIMEOUT,
+    goma_backend = goma.backend.RBE_PROD,
+    goma_jobs = goma.jobs.J150,
+    pool = try_.DEFAULT_POOL,
+    service_account = try_.DEFAULT_SERVICE_ACCOUNT,
+    ssd = True,
+)
+
+consoles.list_view(
+    name = "tryserver.chromium.cft",
+)
+
+try_.builder(
+    name = "mac-rel-cft",
+    mirrors = [
+        "ci/mac-rel-cft",
+    ],
+    cores = None,
+    os = os.MAC_DEFAULT,
+)
diff --git a/infra/config/subprojects/chromium/try/tryserver.chromium.mac.star b/infra/config/subprojects/chromium/try/tryserver.chromium.mac.star
index 5fa648e..6bd4f9d 100644
--- a/infra/config/subprojects/chromium/try/tryserver.chromium.mac.star
+++ b/infra/config/subprojects/chromium/try/tryserver.chromium.mac.star
@@ -291,11 +291,6 @@
     goma_backend = None,  # Does not use Goma.
 )
 
-try_.builder(
-    name = "mac-rel-cft",
-    mirrors = builder_config.copy_from("mac-rel"),
-)
-
 ios_builder(
     name = "ios-asan",
     mirrors = [
diff --git a/ios/chrome/app/spotlight/actions_spotlight_manager.mm b/ios/chrome/app/spotlight/actions_spotlight_manager.mm
index e2ec3e5..5bc979c 100644
--- a/ios/chrome/app/spotlight/actions_spotlight_manager.mm
+++ b/ios/chrome/app/spotlight/actions_spotlight_manager.mm
@@ -41,6 +41,7 @@
 const char kSpotlightActionNewIncognitoTab[] = "OpenIncognitoTab";
 const char kSpotlightActionVoiceSearch[] = "OpenVoiceSearch";
 const char kSpotlightActionQRScanner[] = "OpenQRScanner";
+const char kSpotlightActionSetDefaultBrowser[] = "SetDefaultBrowser";
 
 // Enum is used to record the actions performed by the user.
 enum {
@@ -52,6 +53,8 @@
   SPOTLIGHT_ACTION_VOICE_SEARCH_PRESSED,
   // Recorded when a user pressed the QR scanner spotlight action.
   SPOTLIGHT_ACTION_QR_CODE_SCANNER_PRESSED,
+  // Recorded when a user pressed the Set Default Browser spotlight action.
+  SPOTLIGHT_ACTION_SET_DEFAULT_BROWSER_PRESSED,
   // NOTE: Add new spotlight actions in sources only immediately above this
   // line. Also, make sure the enum list for histogram `SpotlightActions` in
   // histograms.xml is updated with any change in here.
@@ -92,6 +95,16 @@
                               SPOTLIGHT_ACTION_NEW_TAB_PRESSED,
                               SPOTLIGHT_ACTION_COUNT);
     [startupParams setApplicationMode:ApplicationModeForTabOpening::NORMAL];
+  } else if ([action isEqualToString:base::SysUTF8ToNSString(
+                                         kSpotlightActionSetDefaultBrowser)]) {
+    UMA_HISTOGRAM_ENUMERATION(kSpotlightActionsHistogram,
+                              SPOTLIGHT_ACTION_SET_DEFAULT_BROWSER_PRESSED,
+                              SPOTLIGHT_ACTION_COUNT);
+    [[UIApplication sharedApplication]
+                  openURL:[NSURL
+                              URLWithString:UIApplicationOpenSettingsURLString]
+                  options:{}
+        completionHandler:nil];
   } else {
     return NO;
   }
@@ -165,11 +178,18 @@
           NSString* qrScannerAction =
               base::SysUTF8ToNSString(spotlight::kSpotlightActionQRScanner);
 
+          NSString* defaultBrowserTitle = l10n_util::GetNSString(
+              IDS_IOS_APPLICATION_SHORTCUT_SET_DEFAULT_BROWSER);
+          NSString* defaultBrowserAction = base::SysUTF8ToNSString(
+              spotlight::kSpotlightActionSetDefaultBrowser);
+
           NSArray* spotlightItems = @[
             [strongSelf itemForAction:voiceSearchAction title:voiceSearchTitle],
             [strongSelf itemForAction:newTabAction title:newTabTitle],
             [strongSelf itemForAction:incognitoAction title:incognitoTitle],
             [strongSelf itemForAction:qrScannerAction title:qrScannerTitle],
+            [strongSelf itemForAction:defaultBrowserAction
+                                title:defaultBrowserTitle],
           ];
 
           [[CSSearchableIndex defaultSearchableIndex]
diff --git a/ios/chrome/app/strings/ios_strings.grd b/ios/chrome/app/strings/ios_strings.grd
index 0cc7048..f1af7fd 100644
--- a/ios/chrome/app/strings/ios_strings.grd
+++ b/ios/chrome/app/strings/ios_strings.grd
@@ -258,6 +258,9 @@
       <message name="IDS_IOS_APPLICATION_SHORTCUT_VOICE_SEARCH_TITLE" desc="Message when opening a Voice Search from springboard force touch static shortcuts. [Length: unlimited] [iOS only]." meaning="3D Touch entry to trigger a voice search.">
         Voice Search
       </message>
+      <message name="IDS_IOS_APPLICATION_SHORTCUT_SET_DEFAULT_BROWSER" desc="The title of the spotlight item to set Chrome as a default browser. [Length: unlimited] [iOS only]." meaning="Spotlight item title for setting default browser.">
+        Set Chrome as Default Browser
+      </message>
       <message name="IDS_IOS_APP_LAUNCHER_OPEN_APP_BUTTON_LABEL" desc="Label of a button to open an application. [Length: 10em] [iOS only]">
         Open
       </message>
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_APPLICATION_SHORTCUT_SET_DEFAULT_BROWSER.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_APPLICATION_SHORTCUT_SET_DEFAULT_BROWSER.png.sha1
new file mode 100644
index 0000000..258fabe
--- /dev/null
+++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_APPLICATION_SHORTCUT_SET_DEFAULT_BROWSER.png.sha1
@@ -0,0 +1 @@
+fc98653aedc7d5391a606c94e14dd4955bd0e15b
\ No newline at end of file
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_header_synchronizer.mm b/ios/chrome/browser/ui/content_suggestions/content_suggestions_header_synchronizer.mm
index 7771ff3..ee93d65a 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_header_synchronizer.mm
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_header_synchronizer.mm
@@ -134,8 +134,20 @@
       MAX(-self.additionalOffset, pinnedOffsetY - [self adjustedOffset].y);
   self.shouldAnimateHeader = YES;
 
+  CGFloat pinnedOffsetBeforeAnimation = [self pinnedOffsetY];
   __weak __typeof(self) weakSelf = self;
 
+  ProceduralBlock shiftOmniboxToTop = ^{
+    __typeof(weakSelf) strongSelf = weakSelf;
+    // Changing the contentOffset of the collection results in a
+    // scroll and a change in the constraints of the header.
+    strongSelf.collectionView.contentOffset =
+        CGPointMake(0, [strongSelf pinnedOffsetY]);
+    // Layout the header for the constraints to be animated.
+    [strongSelf.headerController layoutHeader];
+    [strongSelf.collectionView.collectionViewLayout invalidateLayout];
+  };
+
   self.animator = [[UIViewPropertyAnimator alloc]
       initWithDuration:kShiftTilesUpAnimationDuration
                  curve:UIViewAnimationCurveEaseInOut
@@ -149,14 +161,7 @@
                   [self pinnedOffsetY]) {
                 if (animations)
                   animations();
-                // Changing the contentOffset of the collection results in a
-                // scroll and a change in the constraints of the header.
-                strongSelf.collectionView.contentOffset =
-                    CGPointMake(0, [self pinnedOffsetY]);
-                // Layout the header for the constraints to be animated.
-                [strongSelf.headerController layoutHeader];
-                [strongSelf.collectionView
-                        .collectionViewLayout invalidateLayout];
+                shiftOmniboxToTop();
               }
             }];
 
@@ -167,6 +172,19 @@
     }
 
     if (finalPosition == UIViewAnimatingPositionEnd) {
+      // Content suggestion headers can be updated during the scroll, causing
+      // `pinnedOffsetY` to be invalid. When this happens during the animation,
+      // the tiles are not scrolled to the top causing the omnibox to be hidden
+      // by the `PrimaryToolbarView`. In that state, the omnibox's popup and the
+      // keyboard are still visible.
+      // If the animation is not interrupted and `pinnedOffsetY` changed
+      // during the animation, shift the omnibox to the top at the end of the
+      // animation.
+      if ([strongSelf pinnedOffsetY] != pinnedOffsetBeforeAnimation &&
+          strongSelf.collectionView.contentOffset.y <
+              [strongSelf pinnedOffsetY]) {
+        shiftOmniboxToTop();
+      }
       strongSelf.shouldAnimateHeader = NO;
     }
 
diff --git a/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_row_cell.mm b/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_row_cell.mm
index 2e35297..da77b3ee 100644
--- a/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_row_cell.mm
+++ b/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_row_cell.mm
@@ -461,6 +461,8 @@
 - (void)prepareForReuse {
   [super prepareForReuse];
 
+  self.highlighted = NO;
+  self.selected = NO;
   self.suggestion = nil;
   self.incognito = NO;
 
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_bottom_toolbar.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_bottom_toolbar.mm
index f1c7b5f..3c7b2401 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_bottom_toolbar.mm
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_bottom_toolbar.mm
@@ -404,7 +404,7 @@
     [NSLayoutConstraint deactivateConstraints:_floatingConstraints];
     [_toolbar removeFromSuperview];
     [_largeNewTabButton removeFromSuperview];
-    self.hidden = !self.subviews.count;
+    self.hidden = YES;
     return;
   }
   _largeNewTabButtonBottomAnchor.constant =
@@ -418,7 +418,7 @@
     ]];
     [self addSubview:_toolbar];
     [NSLayoutConstraint activateConstraints:_compactConstraints];
-    self.hidden = !self.subviews.count;
+    self.hidden = NO;
     return;
   }
   UIBarButtonItem* leadingButton = _closeAllOrUndoButton;
@@ -504,12 +504,6 @@
 
 // Updates the visibility of the backgrounds based on the state of the TabGrid.
 - (void)updateBackgroundVisibility {
-  if (self.mode == TabGridModeSearch) {
-    _scrolledToBottomBackgroundView.hidden = YES;
-    _scrolledBackgroundView.hidden = YES;
-    return;
-  }
-
   _scrolledToBottomBackgroundView.hidden = !_scrolledToEdge;
   _scrolledBackgroundView.hidden = _scrolledToEdge;
 }
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 d21fcee..bc409651 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
@@ -2018,6 +2018,7 @@
   if (!self.scrimView.superview) {
     [self.scrollContentView addSubview:self.scrimView];
     AddSameConstraints(self.scrimView, self.view.superview);
+    [self.view layoutIfNeeded];
   }
   self.currentPageViewController.accessibilityElementsHidden = YES;
   __weak __typeof(self) weakSelf = self;
@@ -2028,7 +2029,6 @@
           return;
         strongSelf.scrimView.hidden = NO;
         strongSelf.scrimView.alpha = 1.0f;
-        [strongSelf.view layoutIfNeeded];
       }
       completion:^(BOOL finished) {
         TabGridViewController* strongSelf = weakSelf;
diff --git a/media/audio/audio_output_device_thread_callback.cc b/media/audio/audio_output_device_thread_callback.cc
index 542f587f..d84ea4f 100644
--- a/media/audio/audio_output_device_thread_callback.cc
+++ b/media/audio/audio_output_device_thread_callback.cc
@@ -88,7 +88,8 @@
   // frames, and ask client to render audio.  Since |output_bus_| is wrapping
   // the shared memory the Render() call is writing directly into the shared
   // memory.
-  render_callback_->Render(delay, delay_timestamp, 0, output_bus_.get());
+  render_callback_->Render(delay, delay_timestamp, glitch_info,
+                           output_bus_.get());
 
   if (audio_parameters_.IsBitstreamFormat()) {
     buffer->params.bitstream_data_size = output_bus_->GetBitstreamDataSize();
diff --git a/media/audio/audio_output_device_unittest.cc b/media/audio/audio_output_device_unittest.cc
index 0e1136b1e..220c3ef 100644
--- a/media/audio/audio_output_device_unittest.cc
+++ b/media/audio/audio_output_device_unittest.cc
@@ -56,7 +56,7 @@
   MOCK_METHOD4(Render,
                int(base::TimeDelta delay,
                    base::TimeTicks timestamp,
-                   int prior_frames_skipped,
+                   const AudioGlitchInfo& glitch_info,
                    AudioBus* dest));
   MOCK_METHOD0(OnRenderError, void());
 };
diff --git a/media/audio/clockless_audio_sink.cc b/media/audio/clockless_audio_sink.cc
index ab42142..ad2931c 100644
--- a/media/audio/clockless_audio_sink.cc
+++ b/media/audio/clockless_audio_sink.cc
@@ -13,6 +13,7 @@
 #include "base/task/sequenced_task_runner.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/threading/simple_thread.h"
+#include "media/base/audio_glitch_info.h"
 #include "media/base/audio_hash.h"
 
 namespace media {
diff --git a/media/audio/null_audio_sink.cc b/media/audio/null_audio_sink.cc
index 12c4fe7..a82d045 100644
--- a/media/audio/null_audio_sink.cc
+++ b/media/audio/null_audio_sink.cc
@@ -10,6 +10,7 @@
 #include "base/location.h"
 #include "base/task/sequenced_task_runner.h"
 #include "base/task/single_thread_task_runner.h"
+#include "media/base/audio_glitch_info.h"
 #include "media/base/audio_hash.h"
 #include "media/base/fake_audio_worker.h"
 
diff --git a/media/base/audio_bus_perftest.cc b/media/base/audio_bus_perftest.cc
index e0b6f43..d31a1c55 100644
--- a/media/base/audio_bus_perftest.cc
+++ b/media/base/audio_bus_perftest.cc
@@ -60,7 +60,7 @@
 TEST(AudioBusPerfTest, Interleave) {
   std::unique_ptr<AudioBus> bus = AudioBus::Create(2, kSampleRate * 120);
   FakeAudioRenderCallback callback(0.2, kSampleRate);
-  callback.Render(base::TimeDelta(), base::TimeTicks::Now(), 0, bus.get());
+  callback.Render(base::TimeDelta(), base::TimeTicks::Now(), {}, bus.get());
 
   // Only benchmark these two types since they're the only commonly used ones.
   RunInterleaveBench<int16_t, SignedInt16SampleTypeTraits>(bus.get(),
@@ -71,7 +71,7 @@
 TEST(AudioBusPerfTest, DISABLED_ToInterleavedFloat) {
   std::unique_ptr<AudioBus> bus = AudioBus::Create(2, kSampleRate * 120);
   FakeAudioRenderCallback callback(0.2, kSampleRate);
-  callback.Render(base::TimeDelta(), base::TimeTicks::Now(), 0, bus.get());
+  callback.Render(base::TimeDelta(), base::TimeTicks::Now(), {}, bus.get());
 
   RunInterleaveBench<float, Float32SampleTypeTraits>(
       bus.get(), "to_interleave_float", true);
@@ -85,7 +85,7 @@
   std::unique_ptr<AudioBus> bus = AudioBus::Create(2, kSampleRate * 120);
   std::unique_ptr<AudioBus> dest = AudioBus::Create(2, kSampleRate * 120);
   FakeAudioRenderCallback callback(0.2, kSampleRate);
-  callback.Render(base::TimeDelta(), base::TimeTicks::Now(), 0, bus.get());
+  callback.Render(base::TimeDelta(), base::TimeTicks::Now(), {}, bus.get());
 
   // Warmup.
   for (int i = 0; i < kBenchmarkIterations; ++i)
diff --git a/media/base/audio_converter_unittest.cc b/media/base/audio_converter_unittest.cc
index c1bdd7f..8cc61c8 100644
--- a/media/base/audio_converter_unittest.cc
+++ b/media/base/audio_converter_unittest.cc
@@ -116,7 +116,7 @@
     converter_->Convert(audio_bus_.get());
 
     // Render expected audio data.
-    expected_callback_->Render(base::TimeDelta(), base::TimeTicks::Now(), 0,
+    expected_callback_->Render(base::TimeDelta(), base::TimeTicks::Now(), {},
                                expected_audio_bus_.get());
 
     // Zero out unused channels in the expected AudioBus just as AudioConverter
diff --git a/media/base/audio_glitch_info.cc b/media/base/audio_glitch_info.cc
index e33740a8..8b2ef5c 100644
--- a/media/base/audio_glitch_info.cc
+++ b/media/base/audio_glitch_info.cc
@@ -6,8 +6,17 @@
 
 #include <utility>
 
+#include "base/strings/strcat.h"
+#include "base/strings/string_number_conversions.h"
+
 namespace media {
 
+std::string AudioGlitchInfo::ToString() const {
+  return base::StrCat(
+      {"duration (ms): ", base::NumberToString(duration.InMilliseconds()),
+       ", count: ", base::NumberToString(count)});
+}
+
 AudioGlitchInfo& AudioGlitchInfo::operator+=(const AudioGlitchInfo& other) {
   duration += other.duration;
   count += other.count;
diff --git a/media/base/audio_glitch_info.h b/media/base/audio_glitch_info.h
index 2182fabc..2557c4c 100644
--- a/media/base/audio_glitch_info.h
+++ b/media/base/audio_glitch_info.h
@@ -19,6 +19,9 @@
   // Number of glitches.
   unsigned int count = 0;
 
+  // Stringifies the info for human-readable logging.
+  std::string ToString() const;
+
   AudioGlitchInfo& operator+=(const AudioGlitchInfo& other);
 
   class MEDIA_EXPORT Accumulator;
diff --git a/media/base/audio_glitch_info_unittest.cc b/media/base/audio_glitch_info_unittest.cc
index 17fb4994..7ddff8f 100644
--- a/media/base/audio_glitch_info_unittest.cc
+++ b/media/base/audio_glitch_info_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "media/base/audio_glitch_info.h"
 
+#include "base/time/time.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace media {
@@ -36,4 +37,10 @@
   EXPECT_EQ(accumulator.GetAndReset(), AudioGlitchInfo());
 }
 
+TEST(AudioGlitchInfo, ToString) {
+  AudioGlitchInfo info{.duration = base::Milliseconds(123), .count = 456};
+
+  EXPECT_EQ(info.ToString(), "duration (ms): 123, count: 456");
+}
+
 }  // namespace media
diff --git a/media/base/audio_hash_unittest.cc b/media/base/audio_hash_unittest.cc
index 60a6480..a12d52a 100644
--- a/media/base/audio_hash_unittest.cc
+++ b/media/base/audio_hash_unittest.cc
@@ -35,7 +35,7 @@
     // audio data, we need to fill each channel manually.
     for (int ch = 0; ch < audio_bus->channels(); ++ch) {
       wrapped_bus->SetChannelData(0, audio_bus->channel(ch));
-      fake_callback_.Render(base::TimeDelta(), base::TimeTicks::Now(), 0,
+      fake_callback_.Render(base::TimeDelta(), base::TimeTicks::Now(), {},
                             wrapped_bus.get());
     }
   }
diff --git a/media/base/audio_renderer_mixer.cc b/media/base/audio_renderer_mixer.cc
index 5ab2e76..56c7cb3 100644
--- a/media/base/audio_renderer_mixer.cc
+++ b/media/base/audio_renderer_mixer.cc
@@ -117,7 +117,7 @@
 
 int AudioRendererMixer::Render(base::TimeDelta delay,
                                base::TimeTicks delay_timestamp,
-                               int prior_frames_skipped,
+                               const AudioGlitchInfo& glitch_info,
                                AudioBus* audio_bus) {
   TRACE_EVENT0("audio", "AudioRendererMixer::Render");
   base::AutoLock auto_lock(lock_);
@@ -140,7 +140,7 @@
 
   uint32_t frames_delayed =
       AudioTimestampHelper::TimeToFrames(delay, output_params_.sample_rate());
-  aggregate_converter_.ConvertWithInfo(frames_delayed, {}, audio_bus);
+  aggregate_converter_.ConvertWithInfo(frames_delayed, glitch_info, audio_bus);
   return audio_bus->frames();
 }
 
diff --git a/media/base/audio_renderer_mixer.h b/media/base/audio_renderer_mixer.h
index 622a11db..1ff0356f 100644
--- a/media/base/audio_renderer_mixer.h
+++ b/media/base/audio_renderer_mixer.h
@@ -59,7 +59,7 @@
   // AudioRendererSink::RenderCallback implementation.
   int Render(base::TimeDelta delay,
              base::TimeTicks delay_timestamp,
-             int prior_frames_skipped,
+             const AudioGlitchInfo& glitch_info,
              AudioBus* audio_bus) override;
   void OnRenderError() override;
 
diff --git a/media/base/audio_renderer_mixer_input.cc b/media/base/audio_renderer_mixer_input.cc
index 94109bf8..14519b0 100644
--- a/media/base/audio_renderer_mixer_input.cc
+++ b/media/base/audio_renderer_mixer_input.cc
@@ -227,7 +227,7 @@
       AudioTimestampHelper::FramesToTime(frames_delayed, params_.sample_rate());
 
   int frames_filled =
-      callback_->Render(delay, base::TimeTicks::Now(), 0, audio_bus);
+      callback_->Render(delay, base::TimeTicks::Now(), glitch_info, audio_bus);
 
   // AudioConverter expects unfilled frames to be zeroed.
   if (frames_filled < audio_bus->frames()) {
diff --git a/media/base/audio_renderer_mixer_unittest.cc b/media/base/audio_renderer_mixer_unittest.cc
index 96e9eb6..cda235c 100644
--- a/media/base/audio_renderer_mixer_unittest.cc
+++ b/media/base/audio_renderer_mixer_unittest.cc
@@ -171,12 +171,12 @@
 
     // Render actual audio data.
     int frames = mixer_callback_->Render(
-        base::TimeDelta(), base::TimeTicks::Now(), 0, audio_bus_.get());
+        base::TimeDelta(), base::TimeTicks::Now(), {}, audio_bus_.get());
     if (frames != audio_bus_->frames())
       return false;
 
     // Render expected audio data (without scaling).
-    expected_callback_->Render(base::TimeDelta(), base::TimeTicks::Now(), 0,
+    expected_callback_->Render(base::TimeDelta(), base::TimeTicks::Now(), {},
                                expected_audio_bus_.get());
 
     if (half_fill_) {
@@ -340,6 +340,44 @@
       mixer_inputs_[i]->Stop();
   }
 
+  // Verify that glitch info is being propagated properly.
+  void GlitchInfoTest(int inputs) {
+    InitializeInputs(inputs);
+
+    // Play() all mixer inputs and ensure we get the right values.
+    for (auto& mixer_input : mixer_inputs_) {
+      mixer_input->Start();
+      mixer_input->Play();
+    }
+
+    AudioGlitchInfo glitch_info{.duration = base::Milliseconds(100),
+                                .count = 123};
+    AudioGlitchInfo expected_glitch_info;
+
+    for (int i = 0; i < kMixerCycles; ++i) {
+      expected_glitch_info += glitch_info;
+      mixer_callback_->Render(base::TimeDelta(), base::TimeTicks::Now(),
+                              glitch_info, audio_bus_.get());
+    }
+
+    // If the output buffer duration is not divisible by all the input buffer
+    // durations, all glitch info will not necessarily have been propagated yet.
+    // We call Render with empty glitch info a few more times to flush out any
+    // remaining glitch info.
+    for (int i = 0; i < kMixerCycles; ++i) {
+      mixer_callback_->Render(base::TimeDelta(), base::TimeTicks::Now(), {},
+                              audio_bus_.get());
+    }
+
+    for (auto& callback : fake_callbacks_) {
+      EXPECT_EQ(callback->cumulative_glitch_info(), expected_glitch_info);
+    }
+
+    for (auto& mixer_input : mixer_inputs_) {
+      mixer_input->Stop();
+    }
+  }
+
   scoped_refptr<AudioRendererMixerInput> CreateMixerInput() {
     auto input = base::MakeRefCounted<AudioRendererMixerInput>(
         this, base::UnguessableToken::Create(),
@@ -452,6 +490,11 @@
   MixedStopPlayTest(kOddMixerInputs);
 }
 
+// Check that AudioGlitchInfo is propagated.
+TEST_P(AudioRendererMixerTest, PropagatesAudioGlitchInfo) {
+  GlitchInfoTest(kMixerInputs);
+}
+
 TEST_P(AudioRendererMixerBehavioralTest, OnRenderError) {
   InitializeInputs(kMixerInputs);
   for (size_t i = 0; i < mixer_inputs_.size(); ++i) {
@@ -500,7 +543,7 @@
   const base::TimeDelta kSleepTime = base::Milliseconds(100);
   base::TimeTicks start_time = base::TimeTicks::Now();
   while (!pause_event.IsSignaled()) {
-    mixer_callback_->Render(base::TimeDelta(), base::TimeTicks::Now(), 0,
+    mixer_callback_->Render(base::TimeDelta(), base::TimeTicks::Now(), {},
                             audio_bus_.get());
     base::PlatformThread::Sleep(kSleepTime);
     ASSERT_TRUE(base::TimeTicks::Now() - start_time < kTestTimeout);
@@ -516,7 +559,7 @@
   // Ensure once the input is paused the sink eventually pauses.
   start_time = base::TimeTicks::Now();
   while (!pause_event.IsSignaled()) {
-    mixer_callback_->Render(base::TimeDelta(), base::TimeTicks::Now(), 0,
+    mixer_callback_->Render(base::TimeDelta(), base::TimeTicks::Now(), {},
                             audio_bus_.get());
     base::PlatformThread::Sleep(kSleepTime);
     ASSERT_TRUE(base::TimeTicks::Now() - start_time < kTestTimeout);
diff --git a/media/base/audio_renderer_sink.h b/media/base/audio_renderer_sink.h
index 38e883c..fee63ad 100644
--- a/media/base/audio_renderer_sink.h
+++ b/media/base/audio_renderer_sink.h
@@ -17,6 +17,8 @@
 
 namespace media {
 
+struct AudioGlitchInfo;
+
 // AudioRendererSink is an interface representing the end-point for
 // rendered audio.  An implementation is expected to
 // periodically call Render() on a callback object.
@@ -34,7 +36,7 @@
     // |delay_timestamp| represents the time when |delay| was obtained.
     virtual int Render(base::TimeDelta delay,
                        base::TimeTicks delay_timestamp,
-                       int prior_frames_skipped,
+                       const AudioGlitchInfo& glitch_info,
                        AudioBus* dest) = 0;
     // Signals an error has occurred.
     virtual void OnRenderError() = 0;
diff --git a/media/base/fake_audio_render_callback.cc b/media/base/fake_audio_render_callback.cc
index d20f50b..63ce619 100644
--- a/media/base/fake_audio_render_callback.cc
+++ b/media/base/fake_audio_render_callback.cc
@@ -26,8 +26,9 @@
 
 int FakeAudioRenderCallback::Render(base::TimeDelta delay,
                                     base::TimeTicks delay_timestamp,
-                                    int prior_frames_skipped,
+                                    const AudioGlitchInfo& glitch_info,
                                     AudioBus* audio_bus) {
+  cumulative_glitch_info_ += glitch_info;
   return RenderInternal(audio_bus, delay, volume_);
 }
 
diff --git a/media/base/fake_audio_render_callback.h b/media/base/fake_audio_render_callback.h
index 603c17e..beb6bf3 100644
--- a/media/base/fake_audio_render_callback.h
+++ b/media/base/fake_audio_render_callback.h
@@ -34,7 +34,7 @@
   // is set, will only fill half the buffer.
   int Render(base::TimeDelta delay,
              base::TimeTicks delay_timestamp,
-             int prior_frames_skipped,
+             const AudioGlitchInfo& glitch_info,
              AudioBus* audio_bus) override;
   MOCK_METHOD0(OnRenderError, void());
 
diff --git a/media/base/fake_audio_renderer_sink.cc b/media/base/fake_audio_renderer_sink.cc
index 34a5632..ba33af0 100644
--- a/media/base/fake_audio_renderer_sink.cc
+++ b/media/base/fake_audio_renderer_sink.cc
@@ -8,6 +8,7 @@
 #include "base/location.h"
 #include "base/logging.h"
 #include "base/task/sequenced_task_runner.h"
+#include "media/base/audio_glitch_info.h"
 
 namespace media {
 
@@ -94,7 +95,7 @@
   if (state_ != kPlaying)
     return false;
 
-  *frames_written = callback_->Render(delay, base::TimeTicks::Now(), 0, dest);
+  *frames_written = callback_->Render(delay, base::TimeTicks::Now(), {}, dest);
   return true;
 }
 
diff --git a/media/base/media_switches.cc b/media/base/media_switches.cc
index 9e9fdd2a..29b9662 100644
--- a/media/base/media_switches.cc
+++ b/media/base/media_switches.cc
@@ -606,6 +606,10 @@
              "VaapiVideoDecoder",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
+BASE_FEATURE(kVaapiVideoDecodeLinuxGL,
+             "VaapiVideoDecodeLinuxGL",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 BASE_FEATURE(kVaapiVideoEncodeLinux,
              "VaapiVideoEncoder",
              base::FEATURE_DISABLED_BY_DEFAULT);
diff --git a/media/base/media_switches.h b/media/base/media_switches.h
index 5243e1f1..c0476ff 100644
--- a/media/base/media_switches.h
+++ b/media/base/media_switches.h
@@ -250,6 +250,7 @@
 MEDIA_EXPORT BASE_DECLARE_FEATURE(kUseR16Texture);
 #if BUILDFLAG(IS_LINUX)
 MEDIA_EXPORT BASE_DECLARE_FEATURE(kVaapiVideoDecodeLinux);
+MEDIA_EXPORT BASE_DECLARE_FEATURE(kVaapiVideoDecodeLinuxGL);
 MEDIA_EXPORT BASE_DECLARE_FEATURE(kVaapiVideoEncodeLinux);
 MEDIA_EXPORT BASE_DECLARE_FEATURE(kVaapiIgnoreDriverChecks);
 #endif  // BUILDFLAG(IS_LINUX)
diff --git a/media/base/silent_sink_suspender.cc b/media/base/silent_sink_suspender.cc
index bdc5829..2f74573 100644
--- a/media/base/silent_sink_suspender.cc
+++ b/media/base/silent_sink_suspender.cc
@@ -6,6 +6,7 @@
 
 #include "base/bind.h"
 #include "base/task/single_thread_task_runner.h"
+#include "media/base/audio_glitch_info.h"
 
 namespace media {
 
@@ -37,7 +38,7 @@
 
 int SilentSinkSuspender::Render(base::TimeDelta delay,
                                 base::TimeTicks delay_timestamp,
-                                int prior_frames_skipped,
+                                const AudioGlitchInfo& glitch_info,
                                 AudioBus* dest) {
   // Lock required since AudioRendererSink::Pause() is not synchronous, we need
   // to discard these calls during the transition to the fake sink.
@@ -53,7 +54,6 @@
   // the audio data for a future transition out of silence.
   if (!dest) {
     DCHECK(is_using_fake_sink_);
-    DCHECK_EQ(prior_frames_skipped, 0);
     // |delay_timestamp| contains the value cached at
     // |latest_output_delay_timestamp_|
     // so we simulate the real sink output, promoting |delay_timestamp| with
@@ -80,7 +80,7 @@
   }
 
   // Pass-through to client and request rendering.
-  callback_->Render(delay, delay_timestamp, prior_frames_skipped, dest);
+  callback_->Render(delay, delay_timestamp, glitch_info, dest);
 
   // Check for silence or real audio data and transition if necessary.
   if (!dest->AreFramesZero() || !detect_silence_) {
@@ -195,7 +195,7 @@
           // new timestamps being provided by FakeAudioWorker, in that it's call
           // to base::TimeTicks::Now() can be eliminated (use |now| instead),
           // along with its custom delay timestamp calculations.
-          suspender->Render(frozen_delay, frozen_delay_timestamp, 0, nullptr);
+          suspender->Render(frozen_delay, frozen_delay_timestamp, {}, nullptr);
         },
         this, latest_output_delay_, latest_output_delay_timestamp_));
   } else {
diff --git a/media/base/silent_sink_suspender.h b/media/base/silent_sink_suspender.h
index 969102be..58f78bf1 100644
--- a/media/base/silent_sink_suspender.h
+++ b/media/base/silent_sink_suspender.h
@@ -52,7 +52,7 @@
   // AudioRendererSink::RenderCallback implementation.
   int Render(base::TimeDelta delay,
              base::TimeTicks delay_timestamp,
-             int prior_frames_skipped,
+             const AudioGlitchInfo& glitch_info,
              AudioBus* dest) override;
   void OnRenderError() override;
 
diff --git a/media/base/silent_sink_suspender_unittest.cc b/media/base/silent_sink_suspender_unittest.cc
index 520cf26..fcd65c5 100644
--- a/media/base/silent_sink_suspender_unittest.cc
+++ b/media/base/silent_sink_suspender_unittest.cc
@@ -50,11 +50,15 @@
 TEST_F(SilentSinkSuspenderTest, BasicPassthough) {
   temp_bus_->Zero();
   auto delay = base::Milliseconds(20);
+  AudioGlitchInfo glitch_info{.duration = base::Milliseconds(100),
+                              .count = 123};
   EXPECT_EQ(temp_bus_->frames(),
-            suspender_.Render(delay, base::TimeTicks(), 0, temp_bus_.get()));
+            suspender_.Render(delay, base::TimeTicks(), glitch_info,
+                              temp_bus_.get()));
 
   // Delay should remain.
   EXPECT_EQ(delay, fake_callback_.last_delay());
+  EXPECT_EQ(glitch_info, fake_callback_.cumulative_glitch_info());
   EXPECT_FALSE(temp_bus_->AreFramesZero());
 }
 
@@ -63,7 +67,7 @@
   EXPECT_FALSE(suspender_.IsUsingFakeSinkForTesting());
   temp_bus_->Zero();
   EXPECT_EQ(temp_bus_->frames(),
-            suspender_.Render(base::TimeDelta(), base::TimeTicks(), 0,
+            suspender_.Render(base::TimeDelta(), base::TimeTicks(), {},
                               temp_bus_.get()));
   EXPECT_FALSE(temp_bus_->AreFramesZero());
   base::RunLoop().RunUntilIdle();
@@ -73,7 +77,7 @@
   fake_callback_.set_volume(0);
   temp_bus_->Zero();
   EXPECT_EQ(temp_bus_->frames(),
-            suspender_.Render(base::TimeDelta(), base::TimeTicks(), 0,
+            suspender_.Render(base::TimeDelta(), base::TimeTicks(), {},
                               temp_bus_.get()));
   EXPECT_TRUE(temp_bus_->AreFramesZero());
   {
@@ -100,11 +104,11 @@
   // not silent.
   fake_callback_.reset();
   std::unique_ptr<AudioBus> true_bus = AudioBus::Create(params_);
-  fake_callback_.Render(base::TimeDelta(), base::TimeTicks(), 0,
+  fake_callback_.Render(base::TimeDelta(), base::TimeTicks(), {},
                         true_bus.get());
   EXPECT_FALSE(true_bus->AreFramesZero());
   EXPECT_EQ(temp_bus_->frames(),
-            suspender_.Render(base::TimeDelta(), base::TimeTicks(), 0,
+            suspender_.Render(base::TimeDelta(), base::TimeTicks(), {},
                               temp_bus_.get()));
   EXPECT_EQ(memcmp(temp_bus_->channel(0), true_bus->channel(0),
                    temp_bus_->frames() * sizeof(float)),
@@ -116,13 +120,13 @@
   fake_callback_.set_volume(0);
   temp_bus_->Zero();
   EXPECT_EQ(temp_bus_->frames(),
-            suspender_.Render(base::TimeDelta(), base::TimeTicks(), 0,
+            suspender_.Render(base::TimeDelta(), base::TimeTicks(), {},
                               temp_bus_.get()));
   EXPECT_TRUE(temp_bus_->AreFramesZero());
 
   // A second render should only result in a single Pause() call.
   EXPECT_EQ(temp_bus_->frames(),
-            suspender_.Render(base::TimeDelta(), base::TimeTicks(), 0,
+            suspender_.Render(base::TimeDelta(), base::TimeTicks(), {},
                               temp_bus_.get()));
 
   EXPECT_CALL(*mock_sink_, Pause());
@@ -135,7 +139,7 @@
   fake_callback_.set_volume(0);
   temp_bus_->Zero();
   EXPECT_EQ(temp_bus_->frames(),
-            suspender_.Render(base::TimeDelta(), base::TimeTicks(), 0,
+            suspender_.Render(base::TimeDelta(), base::TimeTicks(), {},
                               temp_bus_.get()));
   EXPECT_TRUE(temp_bus_->AreFramesZero());
   EXPECT_CALL(*mock_sink_, Pause());
@@ -148,10 +152,10 @@
   // Prepare our equality testers.
   fake_callback_.reset();
   std::unique_ptr<AudioBus> true_bus1 = AudioBus::Create(params_);
-  fake_callback_.Render(base::TimeDelta(), base::TimeTicks(), 0,
+  fake_callback_.Render(base::TimeDelta(), base::TimeTicks(), {},
                         true_bus1.get());
   std::unique_ptr<AudioBus> true_bus2 = AudioBus::Create(params_);
-  fake_callback_.Render(base::TimeDelta(), base::TimeTicks(), 0,
+  fake_callback_.Render(base::TimeDelta(), base::TimeTicks(), {},
                         true_bus2.get());
   EXPECT_NE(memcmp(true_bus1->channel(0), true_bus2->channel(0),
                    true_bus1->frames() * sizeof(float)),
@@ -162,23 +166,23 @@
   fake_callback_.reset();
   EXPECT_EQ(
       temp_bus_->frames(),
-      suspender_.Render(base::TimeDelta(), base::TimeTicks(), 0, nullptr));
+      suspender_.Render(base::TimeDelta(), base::TimeTicks(), {}, nullptr));
   EXPECT_EQ(
       temp_bus_->frames(),
-      suspender_.Render(base::TimeDelta(), base::TimeTicks(), 0, nullptr));
+      suspender_.Render(base::TimeDelta(), base::TimeTicks(), {}, nullptr));
   EXPECT_CALL(*mock_sink_, Play());
   base::RunLoop().RunUntilIdle();
   EXPECT_FALSE(suspender_.IsUsingFakeSinkForTesting());
 
   // Each render after resuming should return one of the non-silent bus.
   EXPECT_EQ(temp_bus_->frames(),
-            suspender_.Render(base::TimeDelta(), base::TimeTicks(), 0,
+            suspender_.Render(base::TimeDelta(), base::TimeTicks(), {},
                               temp_bus_.get()));
   EXPECT_EQ(memcmp(temp_bus_->channel(0), true_bus1->channel(0),
                    temp_bus_->frames() * sizeof(float)),
             0);
   EXPECT_EQ(temp_bus_->frames(),
-            suspender_.Render(base::TimeDelta(), base::TimeTicks(), 0,
+            suspender_.Render(base::TimeDelta(), base::TimeTicks(), {},
                               temp_bus_.get()));
   EXPECT_EQ(memcmp(temp_bus_->channel(0), true_bus2->channel(0),
                    temp_bus_->frames() * sizeof(float)),
@@ -190,7 +194,7 @@
   EXPECT_FALSE(suspender_.IsUsingFakeSinkForTesting());
   temp_bus_->Zero();
   EXPECT_EQ(temp_bus_->frames(),
-            suspender_.Render(base::TimeDelta(), base::TimeTicks(), 0,
+            suspender_.Render(base::TimeDelta(), base::TimeTicks(), {},
                               temp_bus_.get()));
   EXPECT_FALSE(temp_bus_->AreFramesZero());
   base::RunLoop().RunUntilIdle();
@@ -200,7 +204,7 @@
   fake_callback_.set_volume(0);
   temp_bus_->Zero();
   EXPECT_EQ(temp_bus_->frames(),
-            suspender_.Render(base::TimeDelta(), base::TimeTicks(), 0,
+            suspender_.Render(base::TimeDelta(), base::TimeTicks(), {},
                               temp_bus_.get()));
   EXPECT_TRUE(temp_bus_->AreFramesZero());
   {
@@ -230,7 +234,7 @@
   // audio.
   temp_bus_->Zero();
   EXPECT_EQ(temp_bus_->frames(),
-            suspender_.Render(base::TimeDelta(), base::TimeTicks(), 0,
+            suspender_.Render(base::TimeDelta(), base::TimeTicks(), {},
                               temp_bus_.get()));
 
   base::RunLoop().RunUntilIdle();
@@ -241,7 +245,7 @@
   suspender_.SetDetectSilence(true);
   temp_bus_->Zero();
   EXPECT_EQ(temp_bus_->frames(),
-            suspender_.Render(base::TimeDelta(), base::TimeTicks(), 0,
+            suspender_.Render(base::TimeDelta(), base::TimeTicks(), {},
                               temp_bus_.get()));
   EXPECT_TRUE(temp_bus_->AreFramesZero());
   {
@@ -258,7 +262,7 @@
   EXPECT_FALSE(suspender_.IsUsingFakeSinkForTesting());
   temp_bus_->Zero();
   EXPECT_EQ(temp_bus_->frames(),
-            suspender_.Render(base::TimeDelta(), base::TimeTicks(), 0,
+            suspender_.Render(base::TimeDelta(), base::TimeTicks(), {},
                               temp_bus_.get()));
   EXPECT_FALSE(temp_bus_->AreFramesZero());
   base::RunLoop().RunUntilIdle();
@@ -271,7 +275,7 @@
   fake_callback_.set_volume(0);
   temp_bus_->Zero();
   EXPECT_EQ(temp_bus_->frames(),
-            suspender_.Render(base::TimeDelta(), base::TimeTicks(), 0,
+            suspender_.Render(base::TimeDelta(), base::TimeTicks(), {},
                               temp_bus_.get()));
   EXPECT_TRUE(temp_bus_->AreFramesZero());
   {
@@ -290,7 +294,7 @@
   // Render silence again, which should attempt to transition to the fake sink.
   temp_bus_->Zero();
   EXPECT_EQ(temp_bus_->frames(),
-            suspender_.Render(base::TimeDelta(), base::TimeTicks(), 0,
+            suspender_.Render(base::TimeDelta(), base::TimeTicks(), {},
                               temp_bus_.get()));
 
   // OnPaused() should cancel any pending transitions.
diff --git a/media/gpu/windows/dxva_video_decode_accelerator_win.cc b/media/gpu/windows/dxva_video_decode_accelerator_win.cc
index 79eac69..f6a4a52 100644
--- a/media/gpu/windows/dxva_video_decode_accelerator_win.cc
+++ b/media/gpu/windows/dxva_video_decode_accelerator_win.cc
@@ -3254,7 +3254,7 @@
     std::unique_ptr<gpu::SharedImageBacking> shared_image;
     // Check if this picture buffer has a DX11 texture.
     gl::GLImageDXGI* gl_image_dxgi =
-        gl::GLImageDXGI::FromGLImage(picture_buffer->gl_image().get());
+        gl::GLImage::ToGLImageDXGI(picture_buffer->gl_image().get());
     if (gl_image_dxgi) {
       shared_image = gpu::D3DImageBacking::CreateFromGLTexture(
           mailbox, viz_formats[texture_idx],
@@ -3264,7 +3264,7 @@
           std::move(gl_texture));
     } else {
       auto gl_image_pbuffer_ref = scoped_refptr<GLImagePbuffer>(
-          GLImagePbuffer::FromGLImage(picture_buffer->gl_image().get()));
+          gl::GLImage::ToGLImagePbuffer(picture_buffer->gl_image().get()));
       DCHECK(gl_image_pbuffer_ref.get());
 
       // GLImagePbuffer is only created by PbufferPictureBuffer, which itself
diff --git a/media/gpu/windows/gl_image_pbuffer.cc b/media/gpu/windows/gl_image_pbuffer.cc
index 87065d1..71cc792 100644
--- a/media/gpu/windows/gl_image_pbuffer.cc
+++ b/media/gpu/windows/gl_image_pbuffer.cc
@@ -13,13 +13,6 @@
 GLImagePbuffer::GLImagePbuffer(const gfx::Size& size, EGLSurface surface)
     : size_(size), surface_(surface) {}
 
-// static
-GLImagePbuffer* GLImagePbuffer::FromGLImage(GLImage* image) {
-  if (!image || image->GetType() != Type::PBUFFER)
-    return nullptr;
-  return static_cast<GLImagePbuffer*>(image);
-}
-
 gfx::Size GLImagePbuffer::GetSize() {
   return size_;
 }
diff --git a/media/gpu/windows/gl_image_pbuffer.h b/media/gpu/windows/gl_image_pbuffer.h
index 416c5e91..9d903e2b 100644
--- a/media/gpu/windows/gl_image_pbuffer.h
+++ b/media/gpu/windows/gl_image_pbuffer.h
@@ -19,9 +19,6 @@
  public:
   GLImagePbuffer(const gfx::Size& size, EGLSurface surface);
 
-  // Safe downcast. Returns nullptr on failure.
-  static GLImagePbuffer* FromGLImage(GLImage* image);
-
   // gl::GLImage implementation.
   gfx::Size GetSize() override;
   unsigned GetInternalFormat() override;
diff --git a/media/mojo/services/gpu_mojo_media_client_cros.cc b/media/mojo/services/gpu_mojo_media_client_cros.cc
index 72aa1af6..f1d7c63 100644
--- a/media/mojo/services/gpu_mojo_media_client_cros.cc
+++ b/media/mojo/services/gpu_mojo_media_client_cros.cc
@@ -78,11 +78,20 @@
     }
     case VideoDecoderType::kVaapi: {
       // Allow VaapiVideoDecoder on GL.
-      if (gpu_preferences.gr_context_type == gpu::GrContextType::kGL)
-        return VideoDecoderType::kVaapi;
+      if (gpu_preferences.gr_context_type == gpu::GrContextType::kGL) {
+        if (base::FeatureList::IsEnabled(kVaapiVideoDecodeLinuxGL)) {
+          return VideoDecoderType::kVaapi;
+        } else {
+          return VideoDecoderType::kUnknown;
+        }
+      }
 #if BUILDFLAG(ENABLE_VULKAN)
       if (gpu_preferences.gr_context_type != gpu::GrContextType::kVulkan)
         return VideoDecoderType::kUnknown;
+      if (!base::FeatureList::IsEnabled(features::kVulkanFromANGLE))
+        return VideoDecoderType::kUnknown;
+      if (!base::FeatureList::IsEnabled(features::kDefaultANGLEVulkan))
+        return VideoDecoderType::kUnknown;
       // If Vulkan is active, check Vulkan info if VaapiVideoDecoder is allowed.
       if (!gpu_info.vulkan_info.has_value())
         return VideoDecoderType::kUnknown;
diff --git a/media/remoting/renderer_controller.cc b/media/remoting/renderer_controller.cc
index 8d8da91..72ed8d7 100644
--- a/media/remoting/renderer_controller.cc
+++ b/media/remoting/renderer_controller.cc
@@ -253,7 +253,7 @@
   }
 
   if (!ok) {
-    LOG(ERROR) << "No audio nor video to establish data pipe";
+    VLOG(1) << "No audio nor video to establish data pipe";
     std::move(done_callback)
         .Run(mojo::NullRemote(), mojo::NullRemote(),
              mojo::ScopedDataPipeProducerHandle(),
@@ -279,6 +279,8 @@
 
   const bool was_audio_codec_supported = has_audio() && IsAudioCodecSupported();
   const bool was_video_codec_supported = has_video() && IsVideoCodecSupported();
+  const bool natural_size_changed =
+      pipeline_metadata_.natural_size != metadata.natural_size;
   pipeline_metadata_ = metadata;
   const bool is_audio_codec_supported = has_audio() && IsAudioCodecSupported();
   const bool is_video_codec_supported = has_video() && IsVideoCodecSupported();
@@ -301,6 +303,13 @@
                        : UNSUPPORTED_VIDEO_CODEC;
   }
 
+  // Reset and calculate pixel rate again when video size changes.
+  if (natural_size_changed) {
+    pixel_rate_timer_.Stop();
+    pixels_per_second_ = 0;
+    MaybeStartCalculatePixelRateTimer();
+  }
+
   UpdateRemotePlaybackAvailabilityMonitoringState();
 
   UpdateAndMaybeSwitch(start_trigger, stop_trigger);
@@ -364,14 +373,16 @@
 
   is_paused_ = false;
   UpdateAndMaybeSwitch(PLAY_COMMAND, UNKNOWN_STOP_TRIGGER);
+  MaybeStartCalculatePixelRateTimer();
 }
 
 void RendererController::OnPaused() {
   DCHECK(thread_checker_.CalledOnValidThread());
 
   is_paused_ = true;
-  // Cancel the start if in the middle of delayed start.
-  CancelDelayedStart();
+  // Cancel the timer since pixel rate cannot be calculated when media is
+  // paused.
+  pixel_rate_timer_.Stop();
 }
 
 void RendererController::OnFrozen() {
@@ -499,24 +510,13 @@
   DCHECK(thread_checker_.CalledOnValidThread());
 
   bool should_be_remoting = ShouldBeRemoting();
-  if (should_be_remoting) {
-    const RemotingCompatibility compatibility = GetCompatibility();
-    metrics_recorder_.RecordCompatibility(compatibility);
-    should_be_remoting = compatibility == RemotingCompatibility::kCompatible;
-  }
 
-  if ((remote_rendering_started_ ||
-       delayed_start_stability_timer_.IsRunning()) == should_be_remoting) {
+  if (should_be_remoting == remote_rendering_started_) {
     return;
   }
 
-  if (should_be_remoting) {
-    WaitForStabilityBeforeStart(start_trigger);
-  } else if (delayed_start_stability_timer_.IsRunning()) {
-    DCHECK(!remote_rendering_started_);
-    CancelDelayedStart();
-    is_media_remoting_requested_ = false;
-  } else {
+  // Stop Remoting.
+  if (!should_be_remoting) {
     remote_rendering_started_ = false;
     is_media_remoting_requested_ = false;
     DCHECK_NE(UNKNOWN_STOP_TRIGGER, stop_trigger);
@@ -525,78 +525,26 @@
       client_->SwitchToLocalRenderer(GetSwitchReason(stop_trigger));
     VLOG(2) << "Request to stop remoting: stop_trigger=" << stop_trigger;
     remoter_->Stop(mojom::RemotingStopReason::LOCAL_PLAYBACK);
-  }
-}
-
-void RendererController::WaitForStabilityBeforeStart(
-    StartTrigger start_trigger) {
-  DCHECK(!delayed_start_stability_timer_.IsRunning());
-  DCHECK(!remote_rendering_started_);
-  DCHECK(client_);
-  DCHECK(SinkSupportsRemoting());
-
-  delayed_start_stability_timer_.Start(
-      FROM_HERE, kDelayedStart,
-      base::BindOnce(&RendererController::OnDelayedStartTimerFired,
-                     base::Unretained(this), start_trigger,
-                     client_->DecodedFrameCount(), clock_->NowTicks()));
-}
-
-void RendererController::CancelDelayedStart() {
-  delayed_start_stability_timer_.Stop();
-}
-
-void RendererController::OnDelayedStartTimerFired(
-    StartTrigger start_trigger,
-    unsigned decoded_frame_count_before_delay,
-    base::TimeTicks delayed_start_time) {
-  DCHECK(is_dominant_content_ || is_media_remoting_requested_);
-  DCHECK(!remote_rendering_started_);
-  DCHECK(client_);  // This task is canceled otherwise.
-
-  base::TimeDelta elapsed = clock_->NowTicks() - delayed_start_time;
-  DCHECK(!elapsed.is_zero());
-  if (has_video()) {
-    const double frame_rate =
-        (client_->DecodedFrameCount() - decoded_frame_count_before_delay) /
-        elapsed.InSecondsF();
-    const double pixels_per_second =
-        frame_rate * pipeline_metadata_.natural_size.GetArea();
-    const bool supported = RecordPixelRateSupport(pixels_per_second);
-    if (!supported) {
-      permanently_disable_remoting_ = true;
-      return;
-    }
+    return;
   }
 
-  remote_rendering_started_ = true;
-  DCHECK_NE(UNKNOWN_START_TRIGGER, start_trigger);
-  metrics_recorder_.WillStartSession(start_trigger);
-  // |MediaObserverClient::SwitchToRemoteRenderer()| will be called after
-  // remoting is started successfully.
-  remoter_->Start();
-}
+  // Start remoting. First, check pixel rate compatibility.
+  if (pixels_per_second_ == 0) {
+    MaybeStartCalculatePixelRateTimer();
+    return;
+  }
 
-bool RendererController::RecordPixelRateSupport(double pixels_per_second) {
-  if (pixels_per_second <= kPixelsPerSec2k) {
-    metrics_recorder_.RecordVideoPixelRateSupport(
-        PixelRateSupport::k2kSupported);
-    return true;
+  auto pixel_rate_support = GetPixelRateSupport();
+  metrics_recorder_.RecordVideoPixelRateSupport(pixel_rate_support);
+  if (pixel_rate_support == PixelRateSupport::k2kSupported ||
+      pixel_rate_support == PixelRateSupport::k4kSupported) {
+    remote_rendering_started_ = true;
+    DCHECK_NE(UNKNOWN_START_TRIGGER, start_trigger);
+    metrics_recorder_.WillStartSession(start_trigger);
+    // |MediaObserverClient::SwitchToRemoteRenderer()| will be called after
+    // remoting is started successfully.
+    remoter_->Start();
   }
-  if (pixels_per_second <= kPixelsPerSec4k) {
-    if (HasVideoCapability(mojom::RemotingSinkVideoCapability::SUPPORT_4K)) {
-      metrics_recorder_.RecordVideoPixelRateSupport(
-          PixelRateSupport::k4kSupported);
-      return true;
-    } else {
-      metrics_recorder_.RecordVideoPixelRateSupport(
-          PixelRateSupport::k4kNotSupported);
-      return false;
-    }
-  }
-  metrics_recorder_.RecordVideoPixelRateSupport(
-      PixelRateSupport::kOver4kNotSupported);
-  return false;
 }
 
 void RendererController::OnRendererFatalError(StopTrigger stop_trigger) {
@@ -621,13 +569,12 @@
 
   client_ = client;
   if (!client_) {
-    CancelDelayedStart();
+    pixel_rate_timer_.Stop();
     if (remote_rendering_started_) {
       metrics_recorder_.WillStopSession(MEDIA_ELEMENT_DESTROYED);
       remoter_->Stop(mojom::RemotingStopReason::UNEXPECTED_FAILURE);
       remote_rendering_started_ = false;
     }
-    return;
   }
 }
 
@@ -657,7 +604,7 @@
   return HasFeatureCapability(RemotingSinkFeature::RENDERING);
 }
 
-bool RendererController::ShouldBeRemoting() const {
+bool RendererController::ShouldBeRemoting() {
   // Starts remote rendering when the media is the dominant content or the
   // browser has sent an explicit request.
   if (!is_dominant_content_ && !is_media_remoting_requested_) {
@@ -672,7 +619,52 @@
     return false;
   }
 
-  return true;
+  const RemotingCompatibility compatibility = GetCompatibility();
+  metrics_recorder_.RecordCompatibility(compatibility);
+  return compatibility == RemotingCompatibility::kCompatible;
+}
+
+void RendererController::MaybeStartCalculatePixelRateTimer() {
+  if (pixel_rate_timer_.IsRunning() || !client_ || is_paused_ ||
+      pixels_per_second_ != 0) {
+    return;
+  }
+
+  pixel_rate_timer_.Start(
+      FROM_HERE, kDelayedStart,
+      base::BindOnce(&RendererController::DoCalculatePixelRate,
+                     base::Unretained(this), client_->DecodedFrameCount(),
+                     clock_->NowTicks()));
+}
+
+void RendererController::DoCalculatePixelRate(
+    int decoded_frame_count_before_delay,
+    base::TimeTicks delayed_start_time) {
+  DCHECK(client_);
+  DCHECK(!is_paused_);
+
+  base::TimeDelta elapsed = clock_->NowTicks() - delayed_start_time;
+  const double frame_rate =
+      (client_->DecodedFrameCount() - decoded_frame_count_before_delay) /
+      elapsed.InSecondsF();
+  pixels_per_second_ = frame_rate * pipeline_metadata_.natural_size.GetArea();
+  UpdateAndMaybeSwitch(PIXEL_RATE_READY, UNKNOWN_STOP_TRIGGER);
+}
+
+PixelRateSupport RendererController::GetPixelRateSupport() const {
+  DCHECK(pixels_per_second_ != 0);
+
+  if (pixels_per_second_ <= kPixelsPerSec2k) {
+    return PixelRateSupport::k2kSupported;
+  }
+  if (pixels_per_second_ <= kPixelsPerSec4k) {
+    if (HasVideoCapability(mojom::RemotingSinkVideoCapability::SUPPORT_4K)) {
+      return PixelRateSupport::k4kSupported;
+    } else {
+      return PixelRateSupport::k4kNotSupported;
+    }
+  }
+  return PixelRateSupport::kOver4kNotSupported;
 }
 
 void RendererController::SendMessageToSink(std::vector<uint8_t> message) {
diff --git a/media/remoting/renderer_controller.h b/media/remoting/renderer_controller.h
index 24ec624..bfef68f 100644
--- a/media/remoting/renderer_controller.h
+++ b/media/remoting/renderer_controller.h
@@ -149,28 +149,18 @@
   // the element is compatible with Remote Playback API.
   void UpdateRemotePlaybackAvailabilityMonitoringState();
 
-  // Start |delayed_start_stability_timer_| to ensure all preconditions are met
-  // and held stable for a short time before starting remoting.
-  void WaitForStabilityBeforeStart(StartTrigger start_trigger);
-  // Cancel the start of remoting.
-  void CancelDelayedStart();
-  // Called when the delayed start ends. |decoded_frame_count_before_delay| is
-  // the total number of frames decoded before the delayed start began.
-  // |delayed_start_time| is the time that the delayed start began.
-  void OnDelayedStartTimerFired(StartTrigger start_trigger,
-                                unsigned decoded_frame_count_before_delay,
-                                base::TimeTicks delayed_start_time);
-
-  // Records in a histogram and returns whether the receiver supports the given
-  // pixel rate.
-  bool RecordPixelRateSupport(double pixels_per_second);
-
   // Queries on remoting sink capabilities.
   bool HasVideoCapability(mojom::RemotingSinkVideoCapability capability) const;
   bool HasAudioCapability(mojom::RemotingSinkAudioCapability capability) const;
   bool HasFeatureCapability(mojom::RemotingSinkFeature capability) const;
   bool SinkSupportsRemoting() const;
-  bool ShouldBeRemoting() const;
+  bool ShouldBeRemoting();
+
+  // Start `pixel_rate_timer_` to calculate pixel rate.
+  void MaybeStartCalculatePixelRateTimer();
+  void DoCalculatePixelRate(int decoded_frame_count_before_delay,
+                            base::TimeTicks delayed_start_time);
+  PixelRateSupport GetPixelRateSupport() const;
 
   // Callback from RpcMessenger when sending message to remote sink.
   void SendMessageToSink(std::vector<uint8_t> message);
@@ -221,10 +211,6 @@
   // visible content in the tab.
   bool encountered_renderer_fatal_error_ = false;
 
-  // When this is true, remoting will never start again for the lifetime of this
-  // controller.
-  bool permanently_disable_remoting_ = false;
-
   // This is used to check all the methods are called on the current thread in
   // debug builds.
   base::ThreadChecker thread_checker_;
@@ -248,12 +234,14 @@
   // Not owned by this class. Can only be set once by calling SetClient().
   raw_ptr<MediaObserverClient> client_ = nullptr;
 
-  // When this is running, it indicates that remoting will be started later
-  // when the timer gets fired. The start will be canceled if there is any
-  // precondition change that does not allow for remoting duting this period.
-  // TODO(xjz): Estimate whether the transmission bandwidth is sufficient to
-  // remote the content while this timer is running.
-  base::OneShotTimer delayed_start_stability_timer_;
+  // This timer is used to calculate the media content's pixel rate. The timer
+  // is stopped when the media playback is paused or when the media metadata
+  // changed.
+  base::OneShotTimer pixel_rate_timer_;
+
+  // Current pixel rate. Its value is reset to 0 when
+  // pipeline_metadata_.natural_size changes.
+  double pixels_per_second_ = 0;
 
   raw_ptr<const base::TickClock> clock_;
 
diff --git a/media/remoting/renderer_controller_unittest.cc b/media/remoting/renderer_controller_unittest.cc
index cff968e2..533e28f 100644
--- a/media/remoting/renderer_controller_unittest.cc
+++ b/media/remoting/renderer_controller_unittest.cc
@@ -53,7 +53,8 @@
 }
 
 constexpr base::TimeDelta kDelayedStartDuration = base::Seconds(5);
-
+constexpr double frame_rate = 30;
+constexpr double high_pixel_rate = 3840 * 2160 * 30;
 }  // namespace
 
 class RendererControllerTest : public ::testing::Test,
@@ -93,6 +94,10 @@
     is_remote_playback_compatible_ = is_compatible;
   }
 
+  void set_pixels_per_second_(double pixels) {
+    controller_->pixels_per_second_ = pixels;
+  }
+
   void InitializeControllerWithSink(
       const PipelineMetadata& pipeline_metadata,
       mojom::RemotingSinkMetadataPtr sink_metadata) {
@@ -101,23 +106,15 @@
     controller_->clock_ = &clock_;
     clock_.Advance(base::Seconds(1));
     controller_->SetClient(this);
-    RunUntilIdle();
-    EXPECT_FALSE(is_rendering_remotely_);
-    EXPECT_FALSE(disable_pipeline_suspend_);
     controller_->OnSinkAvailable(std::move(sink_metadata));
-    RunUntilIdle();
-    EXPECT_FALSE(is_rendering_remotely_);
-    EXPECT_FALSE(disable_pipeline_suspend_);
     controller_->OnRemotePlaybackDisabled(false);
-    RunUntilIdle();
-    EXPECT_FALSE(is_rendering_remotely_);
-    EXPECT_FALSE(disable_pipeline_suspend_);
     controller_->OnMetadataChanged(pipeline_metadata);
-    RunUntilIdle();
-    EXPECT_FALSE(is_rendering_remotely_);
-    EXPECT_FALSE(disable_pipeline_suspend_);
     controller_->OnPlaying();
     RunUntilIdle();
+    PixelRateTimerEnds();
+    RunUntilIdle();
+    EXPECT_FALSE(is_rendering_remotely_);
+    EXPECT_FALSE(disable_pipeline_suspend_);
   }
 
   void InitializeControllerAndBecomeDominant(
@@ -126,41 +123,25 @@
     InitializeControllerWithSink(pipeline_metadata, std::move(sink_metadata));
     controller_->OnBecameDominantVisibleContent(true);
     RunUntilIdle();
-    EXPECT_FALSE(is_rendering_remotely_);
-    EXPECT_FALSE(disable_pipeline_suspend_);
   }
 
-  bool IsInDelayedStart() const {
-    return controller_->delayed_start_stability_timer_.IsRunning();
-  }
-
-  void DelayedStartEnds(double frame_rate = 30) {
-    EXPECT_TRUE(IsInDelayedStart());
+  void PixelRateTimerEnds() {
+    EXPECT_TRUE(controller_->pixel_rate_timer_.IsRunning());
     decoded_frames_ = frame_rate * kDelayedStartDuration.InSeconds();
     clock_.Advance(kDelayedStartDuration);
-    RunUntilIdle();
-    controller_->delayed_start_stability_timer_.FireNow();
-  }
-
-  void ExpectInDelayedStart() const {
-    EXPECT_FALSE(is_rendering_remotely_);
-    EXPECT_FALSE(disable_pipeline_suspend_);
-    EXPECT_TRUE(sink_name_.empty());
-    EXPECT_TRUE(IsInDelayedStart());
+    controller_->pixel_rate_timer_.FireNow();
   }
 
   void ExpectInRemoting() const {
     EXPECT_TRUE(is_rendering_remotely_);
     EXPECT_TRUE(disable_pipeline_suspend_);
     EXPECT_EQ(kDefaultReceiver, sink_name_);
-    EXPECT_FALSE(IsInDelayedStart());
   }
 
   void ExpectInLocalRendering() const {
     EXPECT_FALSE(is_rendering_remotely_);
     EXPECT_FALSE(disable_pipeline_suspend_);
     EXPECT_TRUE(sink_name_.empty());
-    EXPECT_FALSE(IsInDelayedStart());
   }
 
   bool ShouldBeRemoting() const { return controller_->ShouldBeRemoting(); }
@@ -179,26 +160,32 @@
   double duration_in_sec_ = 120;  // 2m duration.
 };
 
-TEST_F(RendererControllerTest, ShouldBeRemoting) {
+TEST_F(RendererControllerTest, ShouldBeRemotingForDominantVisibleContent) {
   InitializeControllerAndBecomeDominant(DefaultMetadata(VideoCodec::kVP8),
                                         GetDefaultSinkMetadata(true));
   EXPECT_TRUE(ShouldBeRemoting());
 
   controller_->OnBecameDominantVisibleContent(false);
+  RunUntilIdle();
   EXPECT_FALSE(ShouldBeRemoting());
+}
 
+TEST_F(RendererControllerTest, ShouldBeRemotingForRequestFromBrowser) {
+  InitializeControllerAndBecomeDominant(DefaultMetadata(VideoCodec::kVP8),
+                                        GetDefaultSinkMetadata(true));
   controller_->OnMediaRemotingRequested();
+  RunUntilIdle();
   EXPECT_TRUE(ShouldBeRemoting());
 
   controller_->OnSinkGone();
+  RunUntilIdle();
   EXPECT_FALSE(ShouldBeRemoting());
+  RunUntilIdle();
 }
 
 TEST_F(RendererControllerTest, ToggleRendererOnDominantChange) {
   InitializeControllerAndBecomeDominant(DefaultMetadata(VideoCodec::kVP8),
                                         GetDefaultSinkMetadata(true));
-  DelayedStartEnds();
-  RunUntilIdle();
   ExpectInRemoting();  // All requirements now satisfied.
 
   // Leaving fullscreen should shut down remoting.
@@ -211,8 +198,6 @@
   EXPECT_FALSE(is_rendering_remotely_);
   InitializeControllerAndBecomeDominant(DefaultMetadata(VideoCodec::kVP8),
                                         GetDefaultSinkMetadata(true));
-  ExpectInDelayedStart();
-  DelayedStartEnds();
   RunUntilIdle();
   ExpectInRemoting();  // All requirements now satisfied.
 
@@ -244,8 +229,6 @@
   // toggle remote rendering on.
   controller_->OnSinkAvailable(GetDefaultSinkMetadata(true));
   RunUntilIdle();
-  ExpectInDelayedStart();
-  DelayedStartEnds();
   RunUntilIdle();
   ExpectInRemoting();  // All requirements now satisfied.
 }
@@ -262,8 +245,6 @@
   // Start media remoting when there are available sinks.
   controller_->OnSinkAvailable(GetDefaultSinkMetadata(false));
   RunUntilIdle();
-  ExpectInDelayedStart();
-  DelayedStartEnds();
   RunUntilIdle();
   ExpectInRemoting();
 }
@@ -283,8 +264,6 @@
   // toggle remote rendering on.
   controller_->OnSinkAvailable(std::move(sink_metadata));
   RunUntilIdle();
-  ExpectInDelayedStart();
-  DelayedStartEnds();
   RunUntilIdle();
   ExpectInRemoting();  // All requirements now satisfied.
 }
@@ -306,8 +285,6 @@
   // toggle remote rendering on.
   controller_->OnSinkAvailable(std::move(sink_metadata));
   RunUntilIdle();
-  ExpectInDelayedStart();
-  DelayedStartEnds();
   RunUntilIdle();
   ExpectInRemoting();  // All requirements now satisfied.
 }
@@ -334,8 +311,6 @@
   // toggle remote rendering on.
   controller_->OnSinkAvailable(std::move(sink_metadata));
   RunUntilIdle();
-  ExpectInDelayedStart();
-  DelayedStartEnds();
   RunUntilIdle();
   ExpectInRemoting();  // All requirements now satisfied.
 }
@@ -361,29 +336,29 @@
   // toggle remote rendering on.
   controller_->OnSinkAvailable(std::move(sink_metadata));
   RunUntilIdle();
-  ExpectInDelayedStart();
-  DelayedStartEnds();
   RunUntilIdle();
   ExpectInRemoting();  // All requirements now satisfied.
 }
 
-TEST_F(RendererControllerTest, StartFailedWithHighFrameRate) {
-  InitializeControllerAndBecomeDominant(DefaultMetadata(VideoCodec::kVP8),
-                                        GetDefaultSinkMetadata(true));
-  ExpectInDelayedStart();
-  DelayedStartEnds(60);
+TEST_F(RendererControllerTest, StartFailedWithHighPixelRate) {
+  InitializeControllerWithSink(DefaultMetadata(VideoCodec::kVP8),
+                               GetDefaultSinkMetadata(true));
+  set_pixels_per_second_(high_pixel_rate);
+
+  controller_->OnBecameDominantVisibleContent(true);
   RunUntilIdle();
   ExpectInLocalRendering();
 }
 
-TEST_F(RendererControllerTest, StartSuccessWithHighFrameRate) {
+TEST_F(RendererControllerTest, StartSuccessWithHighPixelRate) {
   mojom::RemotingSinkMetadataPtr sink_metadata = GetDefaultSinkMetadata(true);
   sink_metadata->video_capabilities.push_back(
       mojom::RemotingSinkVideoCapability::SUPPORT_4K);
-  InitializeControllerAndBecomeDominant(DefaultMetadata(VideoCodec::kVP8),
-                                        std::move(sink_metadata));
-  ExpectInDelayedStart();
-  DelayedStartEnds(60);
+  InitializeControllerWithSink(DefaultMetadata(VideoCodec::kVP8),
+                               std::move(sink_metadata));
+  set_pixels_per_second_(high_pixel_rate);
+
+  controller_->OnBecameDominantVisibleContent(true);
   RunUntilIdle();
   ExpectInRemoting();
 }
@@ -391,8 +366,6 @@
 TEST_F(RendererControllerTest, PacingTooSlowly) {
   InitializeControllerAndBecomeDominant(DefaultMetadata(VideoCodec::kVP8),
                                         GetDefaultSinkMetadata(true));
-  ExpectInDelayedStart();
-  DelayedStartEnds(false);
   RunUntilIdle();
   ExpectInRemoting();  // All requirements now satisfied.
   controller_->OnRendererFatalError(StopTrigger::PACING_TOO_SLOWLY);
@@ -405,15 +378,13 @@
   ExpectInLocalRendering();
   controller_->OnBecameDominantVisibleContent(true);
   RunUntilIdle();
-  ExpectInDelayedStart();  // Try start remoting again.
+  ExpectInRemoting();  // All requirements now satisfied.
 }
 
 TEST_F(RendererControllerTest, StartFailed) {
   controller_ = FakeRemoterFactory::CreateController(true);
   InitializeControllerAndBecomeDominant(DefaultMetadata(VideoCodec::kVP8),
                                         GetDefaultSinkMetadata(true));
-  ExpectInDelayedStart();
-  DelayedStartEnds();
   RunUntilIdle();
   ExpectInLocalRendering();
 }
@@ -430,8 +401,7 @@
 TEST_F(RendererControllerTest, OnFrozen) {
   InitializeControllerAndBecomeDominant(DefaultMetadata(VideoCodec::kVP8),
                                         GetDefaultSinkMetadata(true));
-  ExpectInDelayedStart();
-  DelayedStartEnds();
+
   RunUntilIdle();
   ExpectInRemoting();
 
diff --git a/media/remoting/triggers.h b/media/remoting/triggers.h
index ca6a292b..2789e81 100644
--- a/media/remoting/triggers.h
+++ b/media/remoting/triggers.h
@@ -33,9 +33,10 @@
   SUPPORTED_VIDEO_CODEC = 7,  // Stream began using a supported video codec.
   SUPPORTED_AUDIO_AND_VIDEO_CODECS = 8,  // Both now using a supported codec.
   CDM_READY = 9,  // The CDM required for decrypting the content became ready.
+  PIXEL_RATE_READY = 11,  // The pixel rate was calculated.
 
   // Change this to the highest value.
-  START_TRIGGER_MAX = 10,
+  START_TRIGGER_MAX = 11,
 };
 
 // Events and conditions that can result in a start failure, or trigger remoting
diff --git a/media/renderers/audio_renderer_impl.cc b/media/renderers/audio_renderer_impl.cc
index f601f26..6c80abe 100644
--- a/media/renderers/audio_renderer_impl.cc
+++ b/media/renderers/audio_renderer_impl.cc
@@ -1166,12 +1166,12 @@
 
 int AudioRendererImpl::Render(base::TimeDelta delay,
                               base::TimeTicks delay_timestamp,
-                              int prior_frames_skipped,
+                              const AudioGlitchInfo& glitch_info,
                               AudioBus* audio_bus) {
   TRACE_EVENT1("media", "AudioRendererImpl::Render", "id", player_id_);
   int frames_requested = audio_bus->frames();
-  DVLOG(4) << __func__ << " delay:" << delay
-           << " prior_frames_skipped:" << prior_frames_skipped
+  DVLOG(4) << __func__ << " delay:" << delay << " glitch_info:["
+           << glitch_info.ToString() << "]"
            << " frames_requested:" << frames_requested;
 
   // Since this information is coming from the OS or potentially a fake stream,
diff --git a/media/renderers/audio_renderer_impl.h b/media/renderers/audio_renderer_impl.h
index e9a88411..2bab087 100644
--- a/media/renderers/audio_renderer_impl.h
+++ b/media/renderers/audio_renderer_impl.h
@@ -187,7 +187,7 @@
   // should the filled buffer be played.
   int Render(base::TimeDelta delay,
              base::TimeTicks delay_timestamp,
-             int prior_frames_skipped,
+             const AudioGlitchInfo& glitch_info,
              AudioBus* dest) override;
   void OnRenderError() override;
 
diff --git a/mojo/public/cpp/base/thread_type_mojom_traits.cc b/mojo/public/cpp/base/thread_type_mojom_traits.cc
index bfbd219b..a66fa0a53 100644
--- a/mojo/public/cpp/base/thread_type_mojom_traits.cc
+++ b/mojo/public/cpp/base/thread_type_mojom_traits.cc
@@ -16,6 +16,8 @@
   switch (thread_type) {
     case base::ThreadType::kBackground:
       return mojo_base::mojom::ThreadType::kBackground;
+    case base::ThreadType::kUtility:
+      return mojo_base::mojom::ThreadType::kUtility;
     case base::ThreadType::kResourceEfficient:
       return mojo_base::mojom::ThreadType::kResourceEfficient;
     case base::ThreadType::kDefault:
@@ -39,6 +41,9 @@
     case mojo_base::mojom::ThreadType::kBackground:
       *out = base::ThreadType::kBackground;
       return true;
+    case mojo_base::mojom::ThreadType::kUtility:
+      *out = base::ThreadType::kUtility;
+      return true;
     case mojo_base::mojom::ThreadType::kResourceEfficient:
       *out = base::ThreadType::kResourceEfficient;
       return true;
diff --git a/mojo/public/mojom/base/thread_type.mojom b/mojo/public/mojom/base/thread_type.mojom
index 1d0a278..916342a 100644
--- a/mojo/public/mojom/base/thread_type.mojom
+++ b/mojo/public/mojom/base/thread_type.mojom
@@ -9,6 +9,9 @@
   // Suitable for threads that have the least urgency and lowest priority, and
   // can be interrupted or delayed by other types.
   kBackground,
+  // Suitable for threads that are less important than normal type, and can be
+  // interrupted or delayed by threads with kDefault type.
+  kUtility,
   // Suitable for threads that produce user-visible artifacts but aren't
   // latency sensitive. The underlying platform will try to be economic
   // in its usage of resources for this thread, if possible.
diff --git a/net/cert/pki/parsed_certificate.cc b/net/cert/pki/parsed_certificate.cc
index 4847794..f9d6193 100644
--- a/net/cert/pki/parsed_certificate.cc
+++ b/net/cert/pki/parsed_certificate.cc
@@ -71,7 +71,7 @@
   return true;
 }
 
-ParsedCertificate::ParsedCertificate() = default;
+ParsedCertificate::ParsedCertificate(PrivateConstructor) {}
 ParsedCertificate::~ParsedCertificate() = default;
 
 // static
@@ -85,8 +85,7 @@
   if (!errors)
     errors = &unused_errors;
 
-  std::shared_ptr<ParsedCertificate> result(
-      new ParsedCertificate, ParsedCertificate::ParsedCertificateDeleter());
+  auto result = std::make_shared<ParsedCertificate>(PrivateConstructor{});
   result->cert_data_ = std::move(backing_data);
   result->cert_ = der::Input(CRYPTO_BUFFER_data(result->cert_data_.get()),
                              CRYPTO_BUFFER_len(result->cert_data_.get()));
diff --git a/net/cert/pki/parsed_certificate.h b/net/cert/pki/parsed_certificate.h
index 07f2933..bb007ec 100644
--- a/net/cert/pki/parsed_certificate.h
+++ b/net/cert/pki/parsed_certificate.h
@@ -36,6 +36,15 @@
 // member are valid, unless otherwise specified. See the documentation for each
 // member or the documentation of the type it returns.
 class NET_EXPORT ParsedCertificate {
+ private:
+  // Used to make constructors private while still being compatible with
+  // |std::make_shared|.
+  class PrivateConstructor {
+   private:
+    friend ParsedCertificate;
+    PrivateConstructor() = default;
+  };
+
  public:
   // Map from OID to ParsedExtension.
   using ExtensionsMap = std::map<der::Input, ParsedExtension>;
@@ -62,6 +71,8 @@
       std::vector<std::shared_ptr<const net::ParsedCertificate>>* chain,
       CertErrors* errors);
 
+  explicit ParsedCertificate(PrivateConstructor);
+  ~ParsedCertificate();
   ParsedCertificate(const ParsedCertificate&) = delete;
   ParsedCertificate& operator=(const ParsedCertificate&) = delete;
 
@@ -244,14 +255,6 @@
                     ParsedExtension* parsed_extension) const;
 
  private:
-  ParsedCertificate();
-  ~ParsedCertificate();
-
-  class ParsedCertificateDeleter {
-   public:
-    void operator()(ParsedCertificate* p) { delete p; }
-  };
-
   // The backing store for the certificate data.
   bssl::UniquePtr<CRYPTO_BUFFER> cert_data_;
 
diff --git a/net/dns/host_resolver_manager.cc b/net/dns/host_resolver_manager.cc
index 868253b9..76dba3d 100644
--- a/net/dns/host_resolver_manager.cc
+++ b/net/dns/host_resolver_manager.cc
@@ -2560,11 +2560,19 @@
 
     if (error == OK) {
       DCHECK(task_type.has_value());
-      if (IsGoogleHostWithAlpnH3(GetHostname(key_.host))) {
+      // Record, for HTTPS-capable queries to a host known to serve HTTPS
+      // records, whether the HTTPS record was successfully received.
+      if (key_.query_types.Has(DnsQueryType::HTTPS) &&
+          // Skip http- and ws-schemed hosts. Although they query HTTPS records,
+          // successful queries are reported as errors, which would skew the
+          // metrics.
+          (GetScheme(key_.host) == url::kHttpsScheme ||
+           GetScheme(key_.host) == url::kWssScheme) &&
+          IsGoogleHostWithAlpnH3(GetHostname(key_.host))) {
         bool has_metadata =
             results.GetMetadatas() && !results.GetMetadatas()->empty();
         base::UmaHistogramExactLinear(
-            "Net.DNS.H3SupportedGoogleHost.TaskTypeMetadataAvailability",
+            "Net.DNS.H3SupportedGoogleHost.TaskTypeMetadataAvailability2",
             static_cast<int>(task_type.value()) * 2 + (has_metadata ? 1 : 0),
             (static_cast<int>(TaskType::kMaxValue) + 1) * 2);
       }
diff --git a/net/http/http_no_vary_search_data.cc b/net/http/http_no_vary_search_data.cc
index 8fec851..421c557 100644
--- a/net/http/http_no_vary_search_data.cc
+++ b/net/http/http_no_vary_search_data.cc
@@ -33,7 +33,12 @@
 HttpNoVarySearchData::HttpNoVarySearchData() = default;
 HttpNoVarySearchData::HttpNoVarySearchData(const HttpNoVarySearchData&) =
     default;
+HttpNoVarySearchData::HttpNoVarySearchData(HttpNoVarySearchData&&) = default;
 HttpNoVarySearchData::~HttpNoVarySearchData() = default;
+HttpNoVarySearchData& HttpNoVarySearchData::operator=(
+    const HttpNoVarySearchData&) = default;
+HttpNoVarySearchData& HttpNoVarySearchData::operator=(HttpNoVarySearchData&&) =
+    default;
 
 bool HttpNoVarySearchData::AreEquivalent(const GURL& a, const GURL& b) const {
   // Check urls without query and reference (fragment) for equality first.
@@ -69,6 +74,28 @@
 }
 
 // static
+HttpNoVarySearchData HttpNoVarySearchData::CreateFromNoVaryParams(
+    const std::vector<std::string>& no_vary_params,
+    bool vary_on_key_order) {
+  HttpNoVarySearchData no_vary_search;
+  no_vary_search.vary_on_key_order_ = vary_on_key_order;
+  no_vary_search.no_vary_params_.insert(no_vary_params.cbegin(),
+                                        no_vary_params.cend());
+  return no_vary_search;
+}
+
+// static
+HttpNoVarySearchData HttpNoVarySearchData::CreateFromVaryParams(
+    const std::vector<std::string>& vary_params,
+    bool vary_on_key_order) {
+  HttpNoVarySearchData no_vary_search;
+  no_vary_search.vary_on_key_order_ = vary_on_key_order;
+  no_vary_search.vary_by_default_ = false;
+  no_vary_search.vary_params_.insert(vary_params.cbegin(), vary_params.cend());
+  return no_vary_search;
+}
+
+// static
 absl::optional<HttpNoVarySearchData> HttpNoVarySearchData::ParseFromHeaders(
     const HttpResponseHeaders& response_headers) {
   std::string normalized_header;
diff --git a/net/http/http_no_vary_search_data.h b/net/http/http_no_vary_search_data.h
index bfe6e7b..cb231e2 100644
--- a/net/http/http_no_vary_search_data.h
+++ b/net/http/http_no_vary_search_data.h
@@ -24,7 +24,17 @@
 class NET_EXPORT_PRIVATE HttpNoVarySearchData {
  public:
   HttpNoVarySearchData(const HttpNoVarySearchData&);
+  HttpNoVarySearchData(HttpNoVarySearchData&&);
   ~HttpNoVarySearchData();
+  HttpNoVarySearchData& operator=(const HttpNoVarySearchData&);
+  HttpNoVarySearchData& operator=(HttpNoVarySearchData&&);
+
+  static HttpNoVarySearchData CreateFromNoVaryParams(
+      const std::vector<std::string>& no_vary_params,
+      bool vary_on_key_order);
+  static HttpNoVarySearchData CreateFromVaryParams(
+      const std::vector<std::string>& vary_params,
+      bool vary_on_key_order);
 
   // Parse No-Vary-Search from response headers.
   //
@@ -46,6 +56,7 @@
 
  private:
   HttpNoVarySearchData();
+
   static absl::optional<HttpNoVarySearchData> ParseNoVarySearchDictionary(
       const structured_headers::Dictionary& dict);
 
diff --git a/net/http/http_no_vary_search_data_unittest.cc b/net/http/http_no_vary_search_data_unittest.cc
index 67e0259..022d193 100644
--- a/net/http/http_no_vary_search_data_unittest.cc
+++ b/net/http/http_no_vary_search_data_unittest.cc
@@ -13,6 +13,7 @@
 #include "base/strings/string_util.h"
 #include "net/http/http_response_headers.h"
 #include "net/http/http_util.h"
+#include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
 
@@ -20,6 +21,84 @@
 
 namespace {
 
+using testing::IsEmpty;
+using testing::UnorderedElementsAreArray;
+
+TEST(HttpNoVarySearchCreateTest, CreateFromNoVaryParamsNonEmptyVaryOnKeyOrder) {
+  const auto no_vary_search =
+      HttpNoVarySearchData::CreateFromNoVaryParams({"a"}, true);
+  EXPECT_THAT(no_vary_search.no_vary_params(),
+              UnorderedElementsAreArray({"a"}));
+  EXPECT_THAT(no_vary_search.vary_params(), IsEmpty());
+  EXPECT_TRUE(no_vary_search.vary_on_key_order());
+  EXPECT_TRUE(no_vary_search.vary_by_default());
+}
+
+TEST(HttpNoVarySearchCreateTest,
+     CreateFromNoVaryParamsNonEmptyNoVaryOnKeyOrder) {
+  const auto no_vary_search =
+      HttpNoVarySearchData::CreateFromNoVaryParams({"a"}, false);
+  EXPECT_THAT(no_vary_search.no_vary_params(),
+              UnorderedElementsAreArray({"a"}));
+  EXPECT_THAT(no_vary_search.vary_params(), IsEmpty());
+  EXPECT_FALSE(no_vary_search.vary_on_key_order());
+  EXPECT_TRUE(no_vary_search.vary_by_default());
+}
+
+TEST(HttpNoVarySearchCreateTest, CreateFromNoVaryParamsEmptyNoVaryOnKeyOrder) {
+  const auto no_vary_search =
+      HttpNoVarySearchData::CreateFromNoVaryParams({}, false);
+  EXPECT_THAT(no_vary_search.no_vary_params(), IsEmpty());
+  EXPECT_THAT(no_vary_search.vary_params(), IsEmpty());
+  EXPECT_FALSE(no_vary_search.vary_on_key_order());
+  EXPECT_TRUE(no_vary_search.vary_by_default());
+}
+
+TEST(HttpNoVarySearchCreateTest, CreateFromNoVaryParamsEmptyVaryOnKeyOrder) {
+  const auto no_vary_search =
+      HttpNoVarySearchData::CreateFromNoVaryParams({}, true);
+  EXPECT_THAT(no_vary_search.no_vary_params(), IsEmpty());
+  EXPECT_THAT(no_vary_search.vary_params(), IsEmpty());
+  EXPECT_TRUE(no_vary_search.vary_on_key_order());
+  EXPECT_TRUE(no_vary_search.vary_by_default());
+}
+
+TEST(HttpNoVarySearchCreateTest, CreateFromVaryParamsNonEmptyVaryOnKeyOrder) {
+  const auto no_vary_search =
+      HttpNoVarySearchData::CreateFromVaryParams({"a"}, true);
+  EXPECT_THAT(no_vary_search.no_vary_params(), IsEmpty());
+  EXPECT_THAT(no_vary_search.vary_params(), UnorderedElementsAreArray({"a"}));
+  EXPECT_TRUE(no_vary_search.vary_on_key_order());
+  EXPECT_FALSE(no_vary_search.vary_by_default());
+}
+
+TEST(HttpNoVarySearchCreateTest, CreateFromVaryParamsNonEmptyNoVaryOnKeyOrder) {
+  const auto no_vary_search =
+      HttpNoVarySearchData::CreateFromVaryParams({"a"}, false);
+  EXPECT_THAT(no_vary_search.no_vary_params(), IsEmpty());
+  EXPECT_THAT(no_vary_search.vary_params(), UnorderedElementsAreArray({"a"}));
+  EXPECT_FALSE(no_vary_search.vary_on_key_order());
+  EXPECT_FALSE(no_vary_search.vary_by_default());
+}
+
+TEST(HttpNoVarySearchCreateTest, CreateFromVaryParamsEmptyNoVaryOnKeyOrder) {
+  const auto no_vary_search =
+      HttpNoVarySearchData::CreateFromVaryParams({}, false);
+  EXPECT_THAT(no_vary_search.no_vary_params(), IsEmpty());
+  EXPECT_THAT(no_vary_search.vary_params(), IsEmpty());
+  EXPECT_FALSE(no_vary_search.vary_on_key_order());
+  EXPECT_FALSE(no_vary_search.vary_by_default());
+}
+
+TEST(HttpNoVarySearchCreateTest, CreateFromVaryParamsEmptyVaryOnKeyOrder) {
+  const auto no_vary_search =
+      HttpNoVarySearchData::CreateFromVaryParams({}, true);
+  EXPECT_THAT(no_vary_search.no_vary_params(), IsEmpty());
+  EXPECT_THAT(no_vary_search.vary_params(), IsEmpty());
+  EXPECT_TRUE(no_vary_search.vary_on_key_order());
+  EXPECT_FALSE(no_vary_search.vary_by_default());
+}
+
 struct TestData {
   const char* raw_headers;
   const base::flat_set<std::string> expected_no_vary_params;
diff --git a/net/quic/quic_chromium_client_session.cc b/net/quic/quic_chromium_client_session.cc
index 51832bca..6424968 100644
--- a/net/quic/quic_chromium_client_session.cc
+++ b/net/quic/quic_chromium_client_session.cc
@@ -11,6 +11,7 @@
 #include "base/bind.h"
 #include "base/containers/contains.h"
 #include "base/feature_list.h"
+#include "base/functional/callback_helpers.h"
 #include "base/location.h"
 #include "base/memory/ptr_util.h"
 #include "base/metrics/histogram_functions.h"
@@ -1715,8 +1716,11 @@
 
   // Specifying handles::kInvalidNetworkHandle for the |network| parameter
   // causes the session to use the default network for the new socket.
+  // DoNothingAs is passed in as `migration_callback` because OnConfigNegotiated
+  // does not need to do anything directly with the migration result.
   Migrate(handles::kInvalidNetworkHandle, new_address,
-          /*close_session_on_error=*/true);
+          /*close_session_on_error=*/true,
+          base::DoNothingAs<void(MigrationResult)>());
 }
 
 void QuicChromiumClientSession::SetDefaultEncryptionLevel(
@@ -2187,8 +2191,9 @@
     int error_code,
     quic::QuicPacketWriter* writer) {
   DCHECK(migrate_session_on_network_change_v2_);
-  // If |writer| is no longer actively in use, abort this migration attempt.
-  if (writer != connection()->writer())
+  // If |writer| is no longer actively in use, or a session migration has
+  // started from MigrateNetworkImmediately, abort this migration attempt.
+  if (writer != connection()->writer() || pending_migrate_network_immediately_)
     return;
 
   most_recent_write_error_timestamp_ = tick_clock_->NowTicks();
@@ -2257,11 +2262,19 @@
   net_log_.BeginEventWithStringParams(
       NetLogEventType::QUIC_CONNECTION_MIGRATION_TRIGGERED, "trigger",
       "WriteError");
-  MigrationResult result =
-      Migrate(new_network, ToIPEndPoint(connection()->peer_address()),
-              /*close_session_on_error=*/false);
+  pending_migrate_session_on_write_error_ = true;
+  Migrate(new_network, ToIPEndPoint(connection()->peer_address()),
+          /*close_session_on_error=*/false,
+          base::BindOnce(
+              &QuicChromiumClientSession::FinishMigrateSessionOnWriteError,
+              weak_factory_.GetWeakPtr(), new_network));
   net_log_.EndEvent(NetLogEventType::QUIC_CONNECTION_MIGRATION_TRIGGERED);
+}
 
+void QuicChromiumClientSession::FinishMigrateSessionOnWriteError(
+    handles::NetworkHandle new_network,
+    MigrationResult result) {
+  pending_migrate_session_on_write_error_ = false;
   if (result == MigrationResult::FAILURE) {
     // Close the connection if migration failed. Do not cause a
     // connection close packet to be sent since socket may be borked.
@@ -2270,7 +2283,6 @@
                                   quic::ConnectionCloseBehavior::SILENT_CLOSE);
     return;
   }
-
   if (new_network != default_network_) {
     StartMigrateBackToDefaultNetworkTimer(
         base::Seconds(kMinRetryTimeForDefaultNetworkSecs));
@@ -2676,10 +2688,18 @@
       connection()->CancelPathValidation();
     }
   }
+  pending_migrate_network_immediately_ = true;
+  Migrate(network, ToIPEndPoint(connection()->peer_address()),
+          /*close_session_on_error=*/true,
+          base::BindOnce(
+              &QuicChromiumClientSession::FinishMigrateNetworkImmediately,
+              weak_factory_.GetWeakPtr(), network));
+}
 
-  MigrationResult result =
-      Migrate(network, ToIPEndPoint(connection()->peer_address()),
-              /*close_session_on_error=*/true);
+void QuicChromiumClientSession::FinishMigrateNetworkImmediately(
+    handles::NetworkHandle network,
+    MigrationResult result) {
+  pending_migrate_network_immediately_ = false;
   if (result == MigrationResult::FAILURE)
     return;
 
@@ -2912,7 +2932,10 @@
     return;
 
   // Probe a different port, session will migrate to the probed port on success.
-  StartProbing(default_network_, peer_address());
+  // DoNothingAs is passed in for `probing_callback` as the return value of
+  // StartProbing is not needed.
+  StartProbing(base::DoNothingAs<void(ProbingResult)>(), default_network_,
+               peer_address());
   net_log_.EndEvent(NetLogEventType::QUIC_PORT_MIGRATION_TRIGGERED);
 }
 
@@ -2962,16 +2985,23 @@
       "PathDegrading");
   // Probe the alternative network, session will migrate to the probed
   // network and decide whether it wants to migrate back to the default
-  // network on success.
-  MaybeStartProbing(alternate_network, peer_address());
+  // network on success. DoNothingAs is passed in for `probing_callback` as the
+  // return value of MaybeStartProbing is not needed.
+  MaybeStartProbing(base::DoNothingAs<void(ProbingResult)>(), alternate_network,
+                    peer_address());
   net_log_.EndEvent(NetLogEventType::QUIC_CONNECTION_MIGRATION_TRIGGERED);
 }
 
-ProbingResult QuicChromiumClientSession::MaybeStartProbing(
+void QuicChromiumClientSession::MaybeStartProbing(
+    ProbingCallback probing_callback,
     handles::NetworkHandle network,
     const quic::QuicSocketAddress& peer_address) {
-  if (!stream_factory_)
-    return ProbingResult::FAILURE;
+  if (!stream_factory_) {
+    task_runner_->PostTask(
+        FROM_HERE, base::BindOnce(std::move(probing_callback),
+                                  ProbingResult::DISABLED_WITH_IDLE_SESSION));
+    return;
+  }
 
   CHECK_NE(handles::kInvalidNetworkHandle, network);
 
@@ -2982,11 +3012,18 @@
         ERR_NETWORK_CHANGED,
         quic::QUIC_CONNECTION_MIGRATION_NO_MIGRATABLE_STREAMS,
         quic::ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
-    return ProbingResult::DISABLED_WITH_IDLE_SESSION;
+    task_runner_->PostTask(
+        FROM_HERE, base::BindOnce(std::move(probing_callback),
+                                  ProbingResult::DISABLED_WITH_IDLE_SESSION));
+    return;
   }
 
-  if (migrate_idle_session_ && CheckIdleTimeExceedsIdleMigrationPeriod())
-    return ProbingResult::DISABLED_WITH_IDLE_SESSION;
+  if (migrate_idle_session_ && CheckIdleTimeExceedsIdleMigrationPeriod()) {
+    task_runner_->PostTask(
+        FROM_HERE, base::BindOnce(std::move(probing_callback),
+                                  ProbingResult::DISABLED_WITH_IDLE_SESSION));
+    return;
+  }
 
   // Abort probing if connection migration is disabled by config.
   if (!connection()->connection_migration_use_new_cid()) {
@@ -2994,7 +3031,10 @@
     HistogramAndLogMigrationFailure(MIGRATION_STATUS_NOT_ENABLED,
                                     connection_id(),
                                     "IETF migration flag is false");
-    return ProbingResult::DISABLED_BY_CONFIG;
+    task_runner_->PostTask(FROM_HERE,
+                           base::BindOnce(std::move(probing_callback),
+                                          ProbingResult::DISABLED_BY_CONFIG));
+    return;
   }
   if (config()->DisableConnectionMigration()) {
     DVLOG(1) << "Client disables probing network with connection migration "
@@ -3002,10 +3042,13 @@
     HistogramAndLogMigrationFailure(MIGRATION_STATUS_DISABLED_BY_CONFIG,
                                     connection_id(),
                                     "Migration disabled by config");
-    return ProbingResult::DISABLED_BY_CONFIG;
+    task_runner_->PostTask(FROM_HERE,
+                           base::BindOnce(std::move(probing_callback),
+                                          ProbingResult::DISABLED_BY_CONFIG));
+    return;
   }
 
-  return StartProbing(network, peer_address);
+  StartProbing(std::move(probing_callback), network, peer_address);
 }
 
 std::unique_ptr<quic::QuicPathValidationContext>
@@ -3042,11 +3085,15 @@
       std::move(probing_reader));
 }
 
-ProbingResult QuicChromiumClientSession::StartProbing(
+void QuicChromiumClientSession::StartProbing(
+    ProbingCallback probing_callback,
     handles::NetworkHandle network,
     const quic::QuicSocketAddress& peer_address) {
   if (!connection()->connection_migration_use_new_cid()) {
-    return ProbingResult::DISABLED_BY_CONFIG;
+    task_runner_->PostTask(FROM_HERE,
+                           base::BindOnce(std::move(probing_callback),
+                                          ProbingResult::DISABLED_BY_CONFIG));
+    return;
   }
 
   // Check if probing manager is probing the same path.
@@ -3054,21 +3101,43 @@
       connection()->GetPathValidationContext());
   if (existing_context && existing_context->network() == network &&
       existing_context->peer_address() == peer_address) {
-    return ProbingResult::PENDING;
+    task_runner_->PostTask(FROM_HERE,
+                           base::BindOnce(std::move(probing_callback),
+                                          ProbingResult::DISABLED_BY_CONFIG));
+    return;
   }
 
   // Create and configure socket on |network|.
   std::unique_ptr<DatagramClientSocket> probing_socket =
       stream_factory_->CreateSocket(net_log_.net_log(), net_log_.source());
-  if (stream_factory_->ConfigureSocket(probing_socket.get(),
-                                       ToIPEndPoint(peer_address), network,
-                                       session_key_.socket_tag()) != OK) {
+  DatagramClientSocket* probing_socket_ptr = probing_socket.get();
+  CompletionOnceCallback configure_callback =
+      base::BindOnce(&QuicChromiumClientSession::FinishStartProbing,
+                     weak_factory_.GetWeakPtr(), std::move(probing_callback),
+                     std::move(probing_socket), network, peer_address);
+  stream_factory_->ConnectAndConfigureSocket(
+      std::move(configure_callback), probing_socket_ptr,
+      ToIPEndPoint(peer_address), network, session_key_.socket_tag());
+
+  return;
+}
+
+void QuicChromiumClientSession::FinishStartProbing(
+    ProbingCallback probing_callback,
+    std::unique_ptr<DatagramClientSocket> probing_socket,
+    handles::NetworkHandle network,
+    const quic::QuicSocketAddress& peer_address,
+    int rv) {
+  if (rv != OK) {
     HistogramAndLogMigrationFailure(MIGRATION_STATUS_INTERNAL_ERROR,
                                     connection_id(),
                                     "Socket configuration failed");
-    return ProbingResult::INTERNAL_ERROR;
-  }
+    task_runner_->PostTask(FROM_HERE,
+                           base::BindOnce(std::move(probing_callback),
+                                          ProbingResult::INTERNAL_ERROR));
 
+    return;
+  }
   // Create new packet writer and reader on the probing socket.
   auto probing_writer = std::make_unique<QuicChromiumPacketWriter>(
       probing_socket.get(), task_runner_);
@@ -3090,11 +3159,13 @@
     ValidatePath(
         std::move(context),
         std::make_unique<ConnectionMigrationValidationResultDelegate>(this));
-    return ProbingResult::PENDING;
+  } else {
+    ValidatePath(std::move(context),
+                 std::make_unique<PortMigrationValidationResultDelegate>(this));
   }
-  ValidatePath(std::move(context),
-               std::make_unique<PortMigrationValidationResultDelegate>(this));
-  return ProbingResult::PENDING;
+
+  task_runner_->PostTask(FROM_HERE, base::BindOnce(std::move(probing_callback),
+                                                   ProbingResult::PENDING));
 }
 
 void QuicChromiumClientSession::StartMigrateBackToDefaultNetworkTimer(
@@ -3130,11 +3201,16 @@
   // the same network, this will be a no-op. Otherwise, previous probe
   // will be cancelled and manager starts to probe |default_network_|
   // immediately.
-  ProbingResult result = MaybeStartProbing(default_network_, peer_address());
+  MaybeStartProbing(
+      base::BindOnce(
+          &QuicChromiumClientSession::FinishTryMigrateBackToDefaultNetwork,
+          weak_factory_.GetWeakPtr(), timeout),
+      default_network_, peer_address());
+}
 
-  if (result == ProbingResult::DISABLED_WITH_IDLE_SESSION)
-    return;
-
+void QuicChromiumClientSession::FinishTryMigrateBackToDefaultNetwork(
+    base::TimeDelta timeout,
+    ProbingResult result) {
   if (result != ProbingResult::PENDING) {
     // Session is not allowed to migrate, mark session as going away, cancel
     // migrate back to default timer.
@@ -3154,6 +3230,10 @@
 void QuicChromiumClientSession::MaybeRetryMigrateBackToDefaultNetwork() {
   base::TimeDelta retry_migrate_back_timeout =
       base::Seconds(UINT64_C(1) << retry_migrate_back_count_);
+  if (pending_migrate_session_on_write_error_) {
+    StartMigrateBackToDefaultNetworkTimer(base::TimeDelta());
+    return;
+  }
   if (default_network_ == GetCurrentNetwork()) {
     // If session has been back on the default already by other direct
     // migration attempt, cancel migrate back now.
@@ -3494,19 +3574,32 @@
   }
 }
 
-MigrationResult QuicChromiumClientSession::Migrate(
-    handles::NetworkHandle network,
-    IPEndPoint peer_address,
-    bool close_session_on_error) {
+void QuicChromiumClientSession::Migrate(handles::NetworkHandle network,
+                                        IPEndPoint peer_address,
+                                        bool close_session_on_error,
+                                        MigrationCallback migration_callback) {
   quic_connection_migration_attempted_ = true;
   quic_connection_migration_successful_ = false;
-  if (!stream_factory_)
-    return MigrationResult::FAILURE;
+  if (!stream_factory_) {
+    task_runner_->PostTask(
+        FROM_HERE,
+        base::BindOnce(&QuicChromiumClientSession::DoMigrationCallback,
+                       weak_factory_.GetWeakPtr(),
+                       std::move(migration_callback),
+                       MigrationResult::FAILURE));
+    return;
+  }
 
   if (network != handles::kInvalidNetworkHandle) {
     // This is a migration attempt from connection migration.
     ResetNonMigratableStreams();
     if (!migrate_idle_session_ && !HasActiveRequestStreams()) {
+      task_runner_->PostTask(
+          FROM_HERE,
+          base::BindOnce(&QuicChromiumClientSession::DoMigrationCallback,
+                         weak_factory_.GetWeakPtr(),
+                         std::move(migration_callback),
+                         MigrationResult::FAILURE));
       // If idle sessions can not be migrated, close the session if needed.
       if (close_session_on_error) {
         CloseSessionOnErrorLater(
@@ -3514,30 +3607,50 @@
             quic::QUIC_CONNECTION_MIGRATION_NO_MIGRATABLE_STREAMS,
             quic::ConnectionCloseBehavior::SILENT_CLOSE);
       }
-      return MigrationResult::FAILURE;
+      return;
     }
   }
 
   // Create and configure socket on |network|.
   std::unique_ptr<DatagramClientSocket> socket(
       stream_factory_->CreateSocket(net_log_.net_log(), net_log_.source()));
-  if (stream_factory_->ConfigureSocket(socket.get(), peer_address, network,
-                                       session_key_.socket_tag()) != OK) {
+  DatagramClientSocket* socket_ptr = socket.get();
+  DVLOG(1) << "Force blocking the packet writer";
+  static_cast<QuicChromiumPacketWriter*>(connection()->writer())
+      ->set_force_write_blocked(true);
+
+  CompletionOnceCallback connect_callback = base::BindOnce(
+      &QuicChromiumClientSession::FinishMigrate, weak_factory_.GetWeakPtr(),
+      std::move(socket), peer_address, close_session_on_error,
+      std::move(migration_callback));
+  stream_factory_->ConnectAndConfigureSocket(std::move(connect_callback),
+                                             socket_ptr, peer_address, network,
+                                             session_key_.socket_tag());
+}
+
+void QuicChromiumClientSession::FinishMigrate(
+    std::unique_ptr<DatagramClientSocket> socket,
+    IPEndPoint peer_address,
+    bool close_session_on_error,
+    MigrationCallback callback,
+    int rv) {
+  if (rv != OK) {
     HistogramAndLogMigrationFailure(MIGRATION_STATUS_INTERNAL_ERROR,
                                     connection_id(),
                                     "Socket configuration failed");
+    static_cast<QuicChromiumPacketWriter*>(connection()->writer())
+        ->set_force_write_blocked(false);
+    task_runner_->PostTask(
+        FROM_HERE,
+        base::BindOnce(&QuicChromiumClientSession::DoMigrationCallback,
+                       weak_factory_.GetWeakPtr(), std::move(callback),
+                       MigrationResult::FAILURE));
     if (close_session_on_error) {
-      if (migrate_session_on_network_change_v2_) {
-        CloseSessionOnErrorLater(ERR_NETWORK_CHANGED,
-                                 quic::QUIC_CONNECTION_MIGRATION_INTERNAL_ERROR,
-                                 quic::ConnectionCloseBehavior::SILENT_CLOSE);
-      } else {
-        CloseSessionOnError(ERR_NETWORK_CHANGED,
-                            quic::QUIC_CONNECTION_MIGRATION_INTERNAL_ERROR,
-                            quic::ConnectionCloseBehavior::SILENT_CLOSE);
-      }
+      CloseSessionOnErrorLater(ERR_NETWORK_CHANGED,
+                               quic::QUIC_CONNECTION_MIGRATION_INTERNAL_ERROR,
+                               quic::ConnectionCloseBehavior::SILENT_CLOSE);
     }
-    return MigrationResult::FAILURE;
+    return;
   }
 
   // Create new packet reader and writer on the new socket.
@@ -3558,23 +3671,29 @@
   if (!MigrateToSocket(ToQuicSocketAddress(self_address),
                        ToQuicSocketAddress(peer_address), std::move(socket),
                        std::move(new_reader), std::move(new_writer))) {
+    task_runner_->PostTask(
+        FROM_HERE,
+        base::BindOnce(&QuicChromiumClientSession::DoMigrationCallback,
+                       weak_factory_.GetWeakPtr(), std::move(callback),
+                       MigrationResult::FAILURE));
     if (close_session_on_error) {
-      if (migrate_session_on_network_change_v2_) {
-        CloseSessionOnErrorLater(
-            ERR_NETWORK_CHANGED,
-            quic::QUIC_CONNECTION_MIGRATION_TOO_MANY_CHANGES,
-            quic::ConnectionCloseBehavior::SILENT_CLOSE);
-      } else {
-        CloseSessionOnError(ERR_NETWORK_CHANGED,
-                            quic::QUIC_CONNECTION_MIGRATION_TOO_MANY_CHANGES,
-                            quic::ConnectionCloseBehavior::SILENT_CLOSE);
-      }
+      CloseSessionOnErrorLater(ERR_NETWORK_CHANGED,
+                               quic::QUIC_CONNECTION_MIGRATION_TOO_MANY_CHANGES,
+                               quic::ConnectionCloseBehavior::SILENT_CLOSE);
     }
-    return MigrationResult::FAILURE;
+    return;
   }
   quic_connection_migration_successful_ = true;
   HistogramAndLogMigrationSuccess(connection_id());
-  return MigrationResult::SUCCESS;
+  task_runner_->PostTask(
+      FROM_HERE, base::BindOnce(&QuicChromiumClientSession::DoMigrationCallback,
+                                weak_factory_.GetWeakPtr(), std::move(callback),
+                                MigrationResult::SUCCESS));
+}
+
+void QuicChromiumClientSession::DoMigrationCallback(MigrationCallback callback,
+                                                    MigrationResult rv) {
+  std::move(callback).Run(rv);
 }
 
 bool QuicChromiumClientSession::MigrateToSocket(
@@ -3597,7 +3716,7 @@
 
   packet_readers_.push_back(std::move(reader));
   sockets_.push_back(std::move(socket));
-  // Froce the writer to be blocked to prevent it being used until
+  // Force the writer to be blocked to prevent it being used until
   // WriteToNewSocket completes.
   DVLOG(1) << "Force blocking the packet writer";
   writer->set_force_write_blocked(true);
@@ -3609,7 +3728,6 @@
     DVLOG(1) << "MigratePath fails as there is no CID available";
     return false;
   }
-
   // Post task to write the pending packet or a PING packet to the new
   // socket. This avoids reentrancy issues if there is a write error
   // on the write to the new socket.
diff --git a/net/quic/quic_chromium_client_session.h b/net/quic/quic_chromium_client_session.h
index c594635..396364ef 100644
--- a/net/quic/quic_chromium_client_session.h
+++ b/net/quic/quic_chromium_client_session.h
@@ -80,7 +80,7 @@
 enum class MigrationResult {
   SUCCESS,         // Migration succeeded.
   NO_NEW_NETWORK,  // Migration failed since no new network was found.
-  FAILURE          // Migration failed for other reasons.
+  FAILURE,         // Migration failed for other reasons.
 };
 
 // Mode of connection migration.
@@ -768,6 +768,10 @@
   // If |writer| is no longer actively used, abort migration.
   void MigrateSessionOnWriteError(int error_code,
                                   quic::QuicPacketWriter* writer);
+  // Called when the Migrate() call from MigrateSessionOnWriteError completes.
+  // Always called asynchronously.
+  void FinishMigrateSessionOnWriteError(handles::NetworkHandle new_network,
+                                        MigrationResult result);
 
   // Helper method that completes connection/server migration.
   // Unblocks packet writer on network level. If the writer becomes unblocked
@@ -778,9 +782,20 @@
   // If |network| is handles::kInvalidNetworkHandle, default network is used. If
   // the migration fails and |close_session_on_error| is true, session will be
   // closed.
-  MigrationResult Migrate(handles::NetworkHandle network,
-                          IPEndPoint peer_address,
-                          bool close_session_on_error);
+  using MigrationCallback = base::OnceCallback<void(MigrationResult)>;
+  void Migrate(handles::NetworkHandle network,
+               IPEndPoint peer_address,
+               bool close_session_on_error,
+               MigrationCallback migration_callback);
+  // Helper to finish session migration once a socket has been opened. Always
+  // called asynchronously.
+  void FinishMigrate(std::unique_ptr<DatagramClientSocket> socket,
+                     IPEndPoint peer_address,
+                     bool close_session_on_error,
+                     MigrationCallback callback,
+                     int rv);
+
+  void DoMigrationCallback(MigrationCallback callback, MigrationResult rv);
 
   // Migrates session onto new socket, i.e., sets |writer| to be the new
   // default writer and post a task to write to |socket|. |reader| *must*
@@ -898,13 +913,24 @@
   // Probe on <network, peer_address>.
   // If <network, peer_addres> is identical to the current path, the probe
   // is sent on a different port.
-  ProbingResult StartProbing(handles::NetworkHandle network,
-                             const quic::QuicSocketAddress& peer_address);
+  using ProbingCallback = base::OnceCallback<void(ProbingResult)>;
+  void StartProbing(ProbingCallback probing_callback,
+                    handles::NetworkHandle network,
+                    const quic::QuicSocketAddress& peer_address);
+
+  // Helper to finish network probe once socket has been opened. Always called
+  // asynchronously.
+  void FinishStartProbing(ProbingCallback probing_callback,
+                          std::unique_ptr<DatagramClientSocket> probing_socket,
+                          handles::NetworkHandle network,
+                          const quic::QuicSocketAddress& peer_address,
+                          int rv);
 
   // Perform a few checks before StartProbing. If any of those checks fails,
   // StartProbing will be skipped.
-  ProbingResult MaybeStartProbing(handles::NetworkHandle network,
-                                  const quic::QuicSocketAddress& peer_address);
+  void MaybeStartProbing(ProbingCallback probing_callback,
+                         handles::NetworkHandle network,
+                         const quic::QuicSocketAddress& peer_address);
 
   // Helper method to perform a few checks and initiate connection migration
   // attempt when path degrading is detected.
@@ -924,9 +950,16 @@
   //    network.
   void MigrateNetworkImmediately(handles::NetworkHandle network);
 
+  // Called when Migrate() call from MigrateNetworkImmediately completes. Always
+  // called asynchronously.
+  void FinishMigrateNetworkImmediately(handles::NetworkHandle network,
+                                       MigrationResult result);
+
   void StartMigrateBackToDefaultNetworkTimer(base::TimeDelta delay);
   void CancelMigrateBackToDefaultNetworkTimer();
   void TryMigrateBackToDefaultNetwork(base::TimeDelta timeout);
+  void FinishTryMigrateBackToDefaultNetwork(base::TimeDelta timeout,
+                                            ProbingResult result);
   void MaybeRetryMigrateBackToDefaultNetwork();
 
   // If migrate idle session is enabled, returns true and post a task to close
@@ -969,6 +1002,10 @@
   bool require_confirmation_;
   bool migrate_session_early_v2_;
   bool migrate_session_on_network_change_v2_;
+  // True when session migration has started from MigrateSessionOnWriteError.
+  bool pending_migrate_session_on_write_error_ = false;
+  // True when a session migration starts from MigrateNetworkImmediately.
+  bool pending_migrate_network_immediately_ = false;
   bool migrate_idle_session_;
   bool allow_port_migration_;
   // Session can be migrated if its idle time is within this period.
diff --git a/net/quic/quic_stream_factory.cc b/net/quic/quic_stream_factory.cc
index b45aca0..b25879d 100644
--- a/net/quic/quic_stream_factory.cc
+++ b/net/quic/quic_stream_factory.cc
@@ -79,14 +79,6 @@
 
 namespace {
 
-enum CreateSessionFailure {
-  CREATION_ERROR_CONNECTING_SOCKET,
-  CREATION_ERROR_SETTING_RECEIVE_BUFFER,
-  CREATION_ERROR_SETTING_SEND_BUFFER,
-  CREATION_ERROR_SETTING_DO_NOT_FRAGMENT,
-  CREATION_ERROR_MAX
-};
-
 enum InitialRttEstimateSource {
   INITIAL_RTT_DEFAULT,
   INITIAL_RTT_CACHED,
@@ -1348,7 +1340,7 @@
       params_.race_stale_dns_on_connection, priority, use_dns_aliases,
       session_key.require_dns_https_alpn(), cert_verify_flags, net_log);
   int rv = job->Run(base::BindOnce(&QuicStreamFactory::OnJobComplete,
-                                   base::Unretained(this), job.get()));
+                                   weak_factory_.GetWeakPtr(), job.get()));
   if (rv == ERR_IO_PENDING) {
     job->AddRequest(request);
     active_jobs_[session_key] = std::move(job);
@@ -1476,6 +1468,117 @@
   }
 }
 
+int QuicStreamFactory::ConnectAndConfigureSocket(
+    CompletionOnceCallback callback,
+    DatagramClientSocket* socket,
+    IPEndPoint addr,
+    handles::NetworkHandle network,
+    const SocketTag& socket_tag) {
+  socket->UseNonBlockingIO();
+
+  int rv;
+  auto split_callback = base::SplitOnceCallback(std::move(callback));
+  CompletionOnceCallback connect_callback =
+      base::BindOnce(&QuicStreamFactory::FinishConnectAndConfigureSocket,
+                     weak_factory_.GetWeakPtr(),
+                     std::move(split_callback.first), socket, socket_tag);
+  if (!params_.migrate_sessions_on_network_change_v2) {
+    rv = socket->ConnectAsync(addr, std::move(connect_callback));
+  } else if (network == handles::kInvalidNetworkHandle) {
+    // If caller leaves network unspecified, use current default network.
+    rv = socket->ConnectUsingDefaultNetworkAsync(addr,
+                                                 std::move(connect_callback));
+  } else {
+    rv = socket->ConnectUsingNetworkAsync(network, addr,
+                                          std::move(connect_callback));
+  }
+  // Both callbacks within `split_callback` will always be run asynchronously,
+  // even if a Connect call returns synchronously. Therefore we always return
+  // ERR_IO_PENDING.
+  if (rv != ERR_IO_PENDING) {
+    FinishConnectAndConfigureSocket(std::move(split_callback.second), socket,
+                                    socket_tag, rv);
+  }
+  return ERR_IO_PENDING;
+}
+
+void QuicStreamFactory::FinishConnectAndConfigureSocket(
+    CompletionOnceCallback callback,
+    DatagramClientSocket* socket,
+    const SocketTag& socket_tag,
+    int rv) {
+  if (rv != OK) {
+    OnFinishConnectAndConfigureSocketError(
+        std::move(callback), CREATION_ERROR_CONNECTING_SOCKET, rv);
+    return;
+  }
+
+  socket->ApplySocketTag(socket_tag);
+
+  rv = socket->SetReceiveBufferSize(kQuicSocketReceiveBufferSize);
+  if (rv != OK) {
+    OnFinishConnectAndConfigureSocketError(
+        std::move(callback), CREATION_ERROR_SETTING_RECEIVE_BUFFER, rv);
+    return;
+  }
+
+  rv = socket->SetDoNotFragment();
+  // SetDoNotFragment is not implemented on all platforms, so ignore errors.
+  if (rv != OK && rv != ERR_NOT_IMPLEMENTED) {
+    OnFinishConnectAndConfigureSocketError(
+        std::move(callback), CREATION_ERROR_SETTING_DO_NOT_FRAGMENT, rv);
+    return;
+  }
+
+  // Set a buffer large enough to contain the initial CWND's worth of packet
+  // to work around the problem with CHLO packets being sent out with the
+  // wrong encryption level, when the send buffer is full.
+  rv = socket->SetSendBufferSize(quic::kMaxOutgoingPacketSize * 20);
+  if (rv != OK) {
+    OnFinishConnectAndConfigureSocketError(
+        std::move(callback), CREATION_ERROR_SETTING_SEND_BUFFER, rv);
+    return;
+  }
+
+  if (params_.ios_network_service_type > 0) {
+    socket->SetIOSNetworkServiceType(params_.ios_network_service_type);
+  }
+
+  socket->GetLocalAddress(&local_address_);
+  if (need_to_check_persisted_supports_quic_) {
+    need_to_check_persisted_supports_quic_ = false;
+    if (http_server_properties_->WasLastLocalAddressWhenQuicWorked(
+            local_address_.address())) {
+      is_quic_known_to_work_on_current_network_ = true;
+      // Clear the persisted IP address, in case the network no longer supports
+      // QUIC so the next restart will require confirmation. It will be
+      // re-persisted when the first job completes successfully.
+      http_server_properties_->ClearLastLocalAddressWhenQuicWorked();
+    }
+  }
+
+  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE,
+      base::BindOnce(&QuicStreamFactory::DoCallback, weak_factory_.GetWeakPtr(),
+                     std::move(callback), rv));
+}
+
+void QuicStreamFactory::OnFinishConnectAndConfigureSocketError(
+    CompletionOnceCallback callback,
+    enum CreateSessionFailure error,
+    int rv) {
+  DCHECK(callback);
+  HistogramCreateSessionFailure(error);
+  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE,
+      base::BindOnce(&QuicStreamFactory::DoCallback, weak_factory_.GetWeakPtr(),
+                     std::move(callback), rv));
+}
+
+void QuicStreamFactory::DoCallback(CompletionOnceCallback callback, int rv) {
+  std::move(callback).Run(rv);
+}
+
 int QuicStreamFactory::ConfigureSocket(DatagramClientSocket* socket,
                                        IPEndPoint addr,
                                        handles::NetworkHandle network,
diff --git a/net/quic/quic_stream_factory.h b/net/quic/quic_stream_factory.h
index c7d074a..6693adb 100644
--- a/net/quic/quic_stream_factory.h
+++ b/net/quic/quic_stream_factory.h
@@ -110,6 +110,14 @@
   kCertDBChanged
 };
 
+enum CreateSessionFailure {
+  CREATION_ERROR_CONNECTING_SOCKET,
+  CREATION_ERROR_SETTING_RECEIVE_BUFFER,
+  CREATION_ERROR_SETTING_SEND_BUFFER,
+  CREATION_ERROR_SETTING_DO_NOT_FRAGMENT,
+  CREATION_ERROR_MAX
+};
+
 // Encapsulates a pending request for a QuicChromiumClientSession.
 // If the request is still pending when it is destroyed, it will
 // cancel the request with the factory.
@@ -321,10 +329,38 @@
   void ClearCachedStatesInCryptoConfig(
       const base::RepeatingCallback<bool(const GURL&)>& origin_filter);
 
+  // Helper method that connects a DatagramClientSocket. Socket is
+  // bound to the default network if the |network| param is
+  // handles::kInvalidNetworkHandle. This method calls
+  // DatagramClientSocket::ConnectAsync and completes asynchronously. Returns
+  // ERR_IO_PENDING.
+  int ConnectAndConfigureSocket(CompletionOnceCallback callback,
+                                DatagramClientSocket* socket,
+                                IPEndPoint addr,
+                                handles::NetworkHandle network,
+                                const SocketTag& socket_tag);
+
+  // Helper method that configures a DatagramClientSocket once
+  // DatagramClientSocket::ConnectAsync completes. Posts a task to run
+  // `callback` with a net_error code.
+  void FinishConnectAndConfigureSocket(CompletionOnceCallback callback,
+                                       DatagramClientSocket* socket,
+                                       const SocketTag& socket_tag,
+                                       int rv);
+
+  void OnFinishConnectAndConfigureSocketError(CompletionOnceCallback callback,
+                                              enum CreateSessionFailure error,
+                                              int rv);
+
+  void DoCallback(CompletionOnceCallback callback, int rv);
+
   // Helper method that configures a DatagramClientSocket. Socket is
   // bound to the default network if the |network| param is
-  // handles::kInvalidNetworkHandle.
-  // Returns net_error code.
+  // handles::kInvalidNetworkHandle. This method calls
+  // DatagramClientSocket::Connect and completes synchronously. Returns
+  // net_error code.
+  // TODO(liza): Remove this once QuicStreamFactory::Job calls
+  // ConnectAndConfigureSocket.
   int ConfigureSocket(DatagramClientSocket* socket,
                       IPEndPoint addr,
                       handles::NetworkHandle network,
diff --git a/net/quic/quic_stream_factory_test.cc b/net/quic/quic_stream_factory_test.cc
index f790ce3..bc42a5a0f 100644
--- a/net/quic/quic_stream_factory_test.cc
+++ b/net/quic/quic_stream_factory_test.cc
@@ -17,6 +17,7 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
+#include "base/test/bind.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/simple_test_tick_clock.h"
 #include "base/test/test_mock_time_task_runner.h"
@@ -3355,6 +3356,7 @@
   scoped_mock_network_change_notifier_->mock_network_change_notifier()
       ->NotifyNetworkMadeDefault(kNewNetworkForTests);
 
+  base::RunLoop().RunUntilIdle();
   EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
   EXPECT_FALSE(HasActiveSession(scheme_host_port_));
   EXPECT_EQ(1u, session->GetNumActiveStreams());
@@ -3844,7 +3846,7 @@
   // Trigger connection migration.
   scoped_mock_network_change_notifier_->mock_network_change_notifier()
       ->NotifyNetworkDisconnected(kDefaultNetworkForTests);
-
+  base::RunLoop().RunUntilIdle();
   // The connection should still be alive, not marked as going away.
   EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
   EXPECT_TRUE(HasActiveSession(scheme_host_port_));
@@ -4144,6 +4146,7 @@
   // Cause the connection to report path degrading to the session.
   // Session will start to probe the alternate network.
   session->connection()->OnPathDegradingDetected();
+  base::RunLoop().RunUntilIdle();
   EXPECT_EQ(1u, QuicStreamFactoryPeer::GetNumDegradingSessions(factory_.get()));
 
   // The connection should still be alive, and not marked as going away.
@@ -4316,6 +4319,7 @@
   // Cause the connection to report path degrading to the session.
   // Session will start to probe the alternate network.
   session->connection()->OnPathDegradingDetected();
+  base::RunLoop().RunUntilIdle();
   EXPECT_EQ(1u, QuicStreamFactoryPeer::GetNumDegradingSessions(factory_.get()));
 
   base::TimeDelta next_task_delay;
@@ -4471,6 +4475,7 @@
   // However, the probing writer will fail. This should result in a failed probe
   // but no connection close.
   session->connection()->OnPathDegradingDetected();
+  base::RunLoop().RunUntilIdle();
   EXPECT_EQ(1u, QuicStreamFactoryPeer::GetNumDegradingSessions(factory_.get()));
 
   // The connection should still be alive, and not marked as going away.
@@ -4479,9 +4484,10 @@
   EXPECT_EQ(1u, session->GetNumActiveStreams());
   EXPECT_EQ(ERR_IO_PENDING, stream->ReadResponseHeaders(callback_.callback()));
 
-  // There should be one task of notifying the session that probing failed.
+  // There should be one task of notifying the session that probing failed, and
+  // a second as a DoNothingAs callback.
   EXPECT_TRUE(session->connection()->HasPendingPathValidation());
-  EXPECT_EQ(1u, task_runner->GetPendingTaskCount());
+  EXPECT_EQ(2u, task_runner->GetPendingTaskCount());
   base::TimeDelta next_task_delay = task_runner->NextPendingTaskDelay();
   EXPECT_EQ(base::TimeDelta(), next_task_delay);
   task_runner->FastForwardBy(next_task_delay);
@@ -4522,6 +4528,7 @@
 
   scoped_mock_network_change_notifier_->mock_network_change_notifier()
       ->QueueNetworkMadeDefault(kDefaultNetworkForTests);
+  base::RunLoop().RunUntilIdle();
 
   quic::QuicConnectionId cid_on_path1 =
       quic::QuicUtils::CreateRandomConnectionId(context_.random_generator());
@@ -4557,10 +4564,9 @@
 
   // Connection ID is retired on the old path.
   client_maker_.set_connection_id(cid_on_path1);
-  quic_data1.AddWrite(SYNCHRONOUS,
-                      client_maker_.MakeRetireConnectionIdPacket(
-                          packet_number++, /*include_version=*/false,
-                          /*sequence_number=*/1u));
+  quic_data1.AddWrite(ASYNC, client_maker_.MakeRetireConnectionIdPacket(
+                                 packet_number++, /*include_version=*/false,
+                                 /*sequence_number=*/1u));
 
   // A socket will be created for a new path, but there would be no write
   // due to lack of new connection ID.
@@ -4599,7 +4605,7 @@
   EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
   EXPECT_TRUE(HasActiveSession(scheme_host_port_));
   MaybeMakeNewConnectionIdAvailableToSession(cid_on_path2, session);
-
+  base::RunLoop().RunUntilIdle();
   // Send GET request on stream.
   HttpResponseInfo response;
   HttpRequestHeaders request_headers;
@@ -4612,6 +4618,7 @@
   // However, the probing writer will fail. This should result in a failed probe
   // but no connection close.
   session->connection()->OnPathDegradingDetected();
+  base::RunLoop().RunUntilIdle();
   EXPECT_EQ(1u, QuicStreamFactoryPeer::GetNumDegradingSessions(factory_.get()));
 
   // The connection should still be alive, and not marked as going away.
@@ -4620,14 +4627,16 @@
   EXPECT_EQ(1u, session->GetNumActiveStreams());
   EXPECT_EQ(ERR_IO_PENDING, stream->ReadResponseHeaders(callback_.callback()));
 
-  // There should be one task of notifying the session that probing failed.
+  // There should be one task of notifying the session that probing failed, and
+  // one that was posted as a DoNothingAs callback.
   EXPECT_TRUE(session->connection()->HasPendingPathValidation());
-  EXPECT_EQ(1u, task_runner->GetPendingTaskCount());
+  EXPECT_EQ(2u, task_runner->GetPendingTaskCount());
 
   // Trigger another path degrading, but this time another network is available.
   scoped_mock_network_change_notifier_->mock_network_change_notifier()
       ->SetConnectedNetworksList({kDefaultNetworkForTests, 3});
   session->connection()->OnPathDegradingDetected();
+  base::RunLoop().RunUntilIdle();
 
   base::TimeDelta next_task_delay = task_runner->NextPendingTaskDelay();
   EXPECT_EQ(base::TimeDelta(), next_task_delay);
@@ -4643,6 +4652,7 @@
   EXPECT_THAT(callback_.WaitForResult(), IsOk());
   EXPECT_EQ(200, response.headers->response_code());
 
+  base::RunLoop().RunUntilIdle();
   // Verify that the session is still alive.
   EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
   EXPECT_TRUE(HasActiveSession(scheme_host_port_));
@@ -4748,6 +4758,7 @@
 
   // This will trigger multi-port path creation.
   MaybeMakeNewConnectionIdAvailableToSession(cid_on_new_path, session);
+  base::RunLoop().RunUntilIdle();
 
   // Send GET request on stream.
   HttpResponseInfo response;
@@ -4887,6 +4898,7 @@
   // Cause the connection to report path degrading to the session.
   // Session will start to probe a different port.
   session->connection()->OnPathDegradingDetected();
+  base::RunLoop().RunUntilIdle();
 
   // The session should stay alive as if nothing happened.
   EXPECT_EQ(1u, QuicStreamFactoryPeer::GetNumDegradingSessions(factory_.get()));
@@ -4999,6 +5011,7 @@
   // Cause the connection to report path degrading to the session.
   // Session will start to probe a different port.
   session->connection()->OnPathDegradingDetected();
+  base::RunLoop().RunUntilIdle();
 
   EXPECT_EQ(1u, QuicStreamFactoryPeer::GetNumDegradingSessions(factory_.get()));
 
@@ -5199,11 +5212,14 @@
   // Cause the connection to report path degrading to the session.
   // Session will start to probe a different port.
   session->connection()->OnPathDegradingDetected();
+  base::RunLoop().RunUntilIdle();
 
   EXPECT_EQ(1u, QuicStreamFactoryPeer::GetNumDegradingSessions(factory_.get()));
 
-  // The retry mechanism is internal to path validator.
-  EXPECT_EQ(0u, task_runner->GetPendingTaskCount());
+  // There should be one pending task as the probe posted a DoNothingAs
+  // callback.
+  EXPECT_EQ(1u, task_runner->GetPendingTaskCount());
+  task_runner->ClearPendingTasks();
 
   // The connection should still be alive, and not marked as going away.
   EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
@@ -5392,12 +5408,13 @@
     // Cause the connection to report path degrading to the session.
     // Session will start to probe a different port.
     session->connection()->OnPathDegradingDetected();
+    base::RunLoop().RunUntilIdle();
 
     EXPECT_EQ(1u,
               QuicStreamFactoryPeer::GetNumDegradingSessions(factory_.get()));
 
     // The retry mechanism is internal to path validator.
-    EXPECT_EQ(0u, task_runner->GetPendingTaskCount());
+    EXPECT_EQ(1u, task_runner->GetPendingTaskCount());
 
     // The connection should still be alive, and not marked as going away.
     EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
@@ -5566,6 +5583,7 @@
   // Cause the connection to report path degrading to the session.
   // Session will start to probe a different port.
   session->connection()->OnPathDegradingDetected();
+  base::RunLoop().RunUntilIdle();
   EXPECT_EQ(1u, session->GetNumActiveStreams());
   EXPECT_EQ(ERR_IO_PENDING, stream->ReadResponseHeaders(callback_.callback()));
   // A response will be received on the current path and closes the request
@@ -5578,8 +5596,10 @@
 
   EXPECT_EQ(1u, QuicStreamFactoryPeer::GetNumDegradingSessions(factory_.get()));
 
-  // The retry mechanism is internal to path validator.
-  EXPECT_EQ(0u, task_runner->GetPendingTaskCount());
+  // There should be one pending task as the probe posted a DoNothingAs
+  // callback.
+  EXPECT_EQ(1u, task_runner->GetPendingTaskCount());
+  task_runner->ClearPendingTasks();
 
   // The connection should still be alive, and not marked as going away.
   EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
@@ -5703,6 +5723,7 @@
   // Cause the connection to report path degrading to the session.
   // Session will start to probe the alternate network.
   session->connection()->OnPathDegradingDetected();
+  base::RunLoop().RunUntilIdle();
   EXPECT_EQ(1u, QuicStreamFactoryPeer::GetNumDegradingSessions(factory_.get()));
 
   // The connection should still be alive, and not marked as going away.
@@ -5715,9 +5736,9 @@
   quic_data.Resume();
   EXPECT_THAT(callback_.WaitForResult(), IsOk());
   EXPECT_EQ(200, response.headers->response_code());
-
-  // Verify there is no pending task as probing alternate network is halted.
-  EXPECT_EQ(0u, task_runner->GetPendingTaskCount());
+  // There should be one pending task left as the probe posted a
+  // DoNothingAsCallback.
+  EXPECT_EQ(1u, task_runner->GetPendingTaskCount());
 
   // Verify that the session is still alive.
   EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
@@ -5858,6 +5879,7 @@
   // Cause the connection to report path degrading to the session.
   // Session should still start to probe the alternate network.
   session->connection()->OnPathDegradingDetected();
+  base::RunLoop().RunUntilIdle();
   EXPECT_TRUE(HasActiveSession(scheme_host_port_));
   EXPECT_EQ(1u, QuicStreamFactoryPeer::GetNumDegradingSessions(factory_.get()));
 
@@ -6009,6 +6031,7 @@
   EXPECT_EQ(0u, task_runner->GetPendingTaskCount());
   EXPECT_EQ(0u, QuicStreamFactoryPeer::GetNumDegradingSessions(factory_.get()));
   session->connection()->OnPathDegradingDetected();
+  base::RunLoop().RunUntilIdle();
   EXPECT_EQ(1u, QuicStreamFactoryPeer::GetNumDegradingSessions(factory_.get()));
   EXPECT_EQ(0u, task_runner->GetPendingTaskCount());
 
@@ -6272,6 +6295,7 @@
   // as going away.
   EXPECT_EQ(0u, QuicStreamFactoryPeer::GetNumDegradingSessions(factory_.get()));
   session->connection()->OnPathDegradingDetected();
+  base::RunLoop().RunUntilIdle();
   EXPECT_TRUE(session->connection()->IsPathDegrading());
   EXPECT_EQ(1u, QuicStreamFactoryPeer::GetNumDegradingSessions(factory_.get()));
 
@@ -6923,6 +6947,7 @@
   // Session will ignore the signal as handshake is not completed.
   EXPECT_EQ(0u, QuicStreamFactoryPeer::GetNumDegradingSessions(factory_.get()));
   session->connection()->OnPathDegradingDetected();
+  base::RunLoop().RunUntilIdle();
   EXPECT_EQ(0u, task_runner->GetPendingTaskCount());
   EXPECT_EQ(1u, QuicStreamFactoryPeer::GetNumDegradingSessions(factory_.get()));
 
@@ -6990,6 +7015,7 @@
   // Cause the connection to report path degrading to the session.
   // Session will ignore the signal as handshake is not completed.
   session->connection()->OnPathDegradingDetected();
+  base::RunLoop().RunUntilIdle();
   EXPECT_EQ(1u, QuicStreamFactoryPeer::GetNumDegradingSessions(factory_.get()));
   EXPECT_EQ(0u, task_runner->GetPendingTaskCount());
   EXPECT_FALSE(HasActiveSession(scheme_host_port_));
@@ -8770,6 +8796,7 @@
   HttpRequestHeaders request_headers;
   EXPECT_EQ(OK, stream->SendRequest(request_headers, &response,
                                     callback_.callback()));
+  base::RunLoop().RunUntilIdle();
 
   // Now queue a network change notification in the message loop behind
   // the migration attempt.
@@ -11135,9 +11162,14 @@
 
   const uint8_t kTestIpAddress[] = {1, 2, 3, 4};
   const uint16_t kTestPort = 123;
+  base::RunLoop run_loop;
+  QuicChromiumClientSession::MigrationCallback migration_callback =
+      base::BindLambdaForTesting(
+          [&run_loop](MigrationResult result) { run_loop.Quit(); });
   session->Migrate(handles::kInvalidNetworkHandle,
-                   IPEndPoint(IPAddress(kTestIpAddress), kTestPort), true);
-
+                   IPEndPoint(IPAddress(kTestIpAddress), kTestPort), true,
+                   std::move(migration_callback));
+  run_loop.Run();
   session->GetDefaultSocket()->GetPeerAddress(&ip);
   DVLOG(1) << "Socket migrated to: " << ip.address().ToString() << " "
            << ip.port();
@@ -11258,9 +11290,14 @@
   constexpr handles::NetworkHandle kNonDefaultNetwork = 1;
   constexpr uint8_t kTestIpAddress[] = {1, 2, 3, 4};
   constexpr uint16_t kTestPort = 123;
+  base::RunLoop run_loop;
+  QuicChromiumClientSession::MigrationCallback migration_callback =
+      base::BindLambdaForTesting(
+          [&run_loop](MigrationResult result) { run_loop.Quit(); });
   session->Migrate(kNonDefaultNetwork,
-                   IPEndPoint(IPAddress(kTestIpAddress), kTestPort), true);
-
+                   IPEndPoint(IPAddress(kTestIpAddress), kTestPort), true,
+                   std::move(migration_callback));
+  run_loop.Run();
   // The session should exist but no longer be active since its only stream has
   // been reset.
   EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
diff --git a/pdf/pdfium/pdfium_engine.cc b/pdf/pdfium/pdfium_engine.cc
index d102020..4553741 100644
--- a/pdf/pdfium/pdfium_engine.cc
+++ b/pdf/pdfium/pdfium_engine.cc
@@ -2425,7 +2425,7 @@
     std::string uri = CallPDFiumStringBufferApi(
         base::BindRepeating(&FPDFAction_GetURIPath, doc(), action),
         /*check_expected_size=*/true);
-    if (!uri.empty())
+    if (!uri.empty() && base::IsStringUTF8AllowingNoncharacters(uri))
       dict.Set("uri", uri);
   }
 
diff --git a/pdf/pdfium/pdfium_page.cc b/pdf/pdfium/pdfium_page.cc
index d43a6da..1456580 100644
--- a/pdf/pdfium/pdfium_page.cc
+++ b/pdf/pdfium/pdfium_page.cc
@@ -922,7 +922,7 @@
     std::string url = CallPDFiumStringBufferApi(
         base::BindRepeating(&FPDFAction_GetURIPath, engine_->doc(), uri_action),
         /*check_expected_size=*/true);
-    if (!url.empty())
+    if (!url.empty() && base::IsStringUTF8AllowingNoncharacters(url))
       target->url = url;
   }
   return WEBLINK_AREA;
diff --git a/pdf/pdfium/pdfium_page.h b/pdf/pdfium/pdfium_page.h
index 6bdbfb4..3c1674f 100644
--- a/pdf/pdfium/pdfium_page.h
+++ b/pdf/pdfium/pdfium_page.h
@@ -227,6 +227,7 @@
   FRIEND_TEST_ALL_PREFIXES(PDFiumPageImageDataTest, ImageData);
   FRIEND_TEST_ALL_PREFIXES(PDFiumPageLinkTest, AnnotLinkGeneration);
   FRIEND_TEST_ALL_PREFIXES(PDFiumPageLinkTest, GetLinkTarget);
+  FRIEND_TEST_ALL_PREFIXES(PDFiumPageLinkTest, GetUTF8LinkTarget);
   FRIEND_TEST_ALL_PREFIXES(PDFiumPageLinkTest, LinkGeneration);
   FRIEND_TEST_ALL_PREFIXES(PDFiumPageOverlappingTest, CountCompleteOverlaps);
   FRIEND_TEST_ALL_PREFIXES(PDFiumPageOverlappingTest, CountPartialOverlaps);
diff --git a/pdf/pdfium/pdfium_page_unittest.cc b/pdf/pdfium/pdfium_page_unittest.cc
index 782c373..a4f4624 100644
--- a/pdf/pdfium/pdfium_page_unittest.cc
+++ b/pdf/pdfium/pdfium_page_unittest.cc
@@ -269,7 +269,8 @@
   PDFiumPage::Area area = first_page.GetLinkTarget(link, &target);
 
   EXPECT_EQ(PDFiumPage::Area::DOCLINK_AREA, area);
-  EXPECT_EQ(1, target.page);
+  EXPECT_TRUE(target.url.empty());
+  ASSERT_EQ(1, target.page);
 
   // Make sure the target page's size is different from the first page's. This
   // guarantees that the in-screen coordinates are calculated based on the
@@ -279,9 +280,40 @@
   ASSERT_TRUE(first_page.available());
   EXPECT_NE(GetPageSizeHelper(first_page), GetPageSizeHelper(target_page));
 
+  ASSERT_TRUE(target.x_in_pixels.has_value());
+  ASSERT_TRUE(target.y_in_pixels.has_value());
   EXPECT_FLOAT_EQ(74.666664f, target.x_in_pixels.value());
   EXPECT_FLOAT_EQ(120.f, target.y_in_pixels.value());
-  EXPECT_FALSE(target.zoom);
+  EXPECT_FALSE(target.zoom.has_value());
+}
+
+// Regression test for crbug.com/1396248
+TEST_P(PDFiumPageLinkTest, GetUTF8LinkTarget) {
+  TestClient client;
+  std::unique_ptr<PDFiumEngine> engine =
+      InitializeEngine(&client, FILE_PATH_LITERAL("uri_action_utf8.pdf"));
+  ASSERT_EQ(1, engine->GetNumberOfPages());
+
+  const std::vector<PDFiumPage::Link>& links = GetLinks(*engine, 0);
+  ASSERT_EQ(1u, links.size());
+
+  // Get the only link in the document.
+  PDFiumPage& first_page = GetPDFiumPageForTest(*engine, 0);
+  FPDF_LINK link = FPDFLink_GetLinkAtPoint(first_page.GetPage(), 100, 100);
+  ASSERT_TRUE(link);
+  FPDF_DEST dest_link = FPDFLink_GetDest(engine->doc(), link);
+  EXPECT_FALSE(dest_link);
+
+  PDFiumPage::LinkTarget target;
+  PDFiumPage::Area area = first_page.GetLinkTarget(link, &target);
+
+  EXPECT_EQ(PDFiumPage::Area::WEBLINK_AREA, area);
+  EXPECT_EQ("https://site.test/hello_你好.html", target.url);
+  EXPECT_EQ(-1, target.page);
+
+  EXPECT_FALSE(target.x_in_pixels.has_value());
+  EXPECT_FALSE(target.y_in_pixels.has_value());
+  EXPECT_FALSE(target.zoom.has_value());
 }
 
 INSTANTIATE_TEST_SUITE_P(All, PDFiumPageLinkTest, testing::Bool());
diff --git a/pdf/test/data/uri_action_utf8.in b/pdf/test/data/uri_action_utf8.in
new file mode 100644
index 0000000..0484111
--- /dev/null
+++ b/pdf/test/data/uri_action_utf8.in
@@ -0,0 +1,44 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+  /MediaBox [0 0 200 200]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Annots [4 0 R]
+>>
+endobj
+{{object 4 0}} <<
+  /Type /Annot
+  /Subtype /Link
+  /FT /Tx
+  /A 5 0 R
+  /BS  <<
+    /W 1
+    /S /S
+  >>
+  /DA (/Helv 0 Tf 0 0 0 rg)
+  /Ff 29360128
+  /F 4
+  /Rect [1 1 199 199]
+  /V ()
+>>
+endobj
+{{object 5 0}} <<
+  /S /URI
+  /URI (https://site.test/hello_\344\275\240\345\245\275.html)
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/pdf/test/data/uri_action_utf8.pdf b/pdf/test/data/uri_action_utf8.pdf
new file mode 100644
index 0000000..f05d212
--- /dev/null
+++ b/pdf/test/data/uri_action_utf8.pdf
@@ -0,0 +1,56 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+  /MediaBox [0 0 200 200]
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /Annots [4 0 R]
+>>
+endobj
+4 0 obj <<
+  /Type /Annot
+  /Subtype /Link
+  /FT /Tx
+  /A 5 0 R
+  /BS  <<
+    /W 1
+    /S /S
+  >>
+  /DA (/Helv 0 Tf 0 0 0 rg)
+  /Ff 29360128
+  /F 4
+  /Rect [1 1 199 199]
+  /V ()
+>>
+endobj
+5 0 obj <<
+  /S /URI
+  /URI (https://site.test/hello_\344\275\240\345\245\275.html)
+>>
+endobj
+xref
+0 6
+0000000000 65535 f 
+0000000015 00000 n 
+0000000068 00000 n 
+0000000157 00000 n 
+0000000226 00000 n 
+0000000414 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 6
+>>
+startxref
+508
+%%EOF
diff --git a/printing/metafile_skia_unittest.cc b/printing/metafile_skia_unittest.cc
index 1457bdb..c47faa9 100644
--- a/printing/metafile_skia_unittest.cc
+++ b/printing/metafile_skia_unittest.cc
@@ -7,6 +7,7 @@
 #include <utility>
 
 #include "build/build_config.h"
+#include "cc/paint/paint_op.h"
 #include "cc/paint/paint_record.h"
 #include "printing/common/metafile_utils.h"
 #include "printing/mojom/print.mojom.h"
diff --git a/printing/printing_context_chromeos_unittest.cc b/printing/printing_context_chromeos_unittest.cc
index f2d99f7..39ec0c0e 100644
--- a/printing/printing_context_chromeos_unittest.cc
+++ b/printing/printing_context_chromeos_unittest.cc
@@ -19,8 +19,10 @@
 using ::testing::_;
 using ::testing::ByMove;
 using ::testing::DoAll;
+using ::testing::NiceMock;
 using ::testing::Return;
 using ::testing::SaveArg;
+using ::testing::SetArgPointee;
 
 constexpr char kPrinterName[] = "printer";
 constexpr char16_t kPrinterName16[] = u"printer";
@@ -95,7 +97,7 @@
   void SetDefaultSettings(bool send_user_info, const std::string& uri) {
     auto unique_connection = std::make_unique<MockCupsConnection>();
     auto* connection = unique_connection.get();
-    auto unique_printer = std::make_unique<MockCupsPrinter>();
+    auto unique_printer = std::make_unique<NiceMock<MockCupsPrinter>>();
     printer_ = unique_printer.get();
     EXPECT_CALL(*printer_, GetUri()).WillRepeatedly(Return(uri));
     EXPECT_CALL(*connection, GetPrinter(kPrinterName))
@@ -198,7 +200,8 @@
   std::string start_document_document_name;
   std::string start_document_username;
   EXPECT_CALL(*printer_, CreateJob)
-      .WillOnce(DoAll(SaveArg<1>(&create_job_document_name),
+      .WillOnce(DoAll(SetArgPointee<0>(/*job_id=*/1),
+                      SaveArg<1>(&create_job_document_name),
                       SaveArg<2>(&create_job_username), Return(status)));
   EXPECT_CALL(*printer_, StartDocument)
       .WillOnce(DoAll(SaveArg<1>(&start_document_document_name),
@@ -223,7 +226,8 @@
   std::string start_document_document_name;
   std::string start_document_username;
   EXPECT_CALL(*printer_, CreateJob)
-      .WillOnce(DoAll(SaveArg<1>(&create_job_document_name),
+      .WillOnce(DoAll(SetArgPointee<0>(/*job_id=*/1),
+                      SaveArg<1>(&create_job_document_name),
                       SaveArg<2>(&create_job_username), Return(status)));
   EXPECT_CALL(*printer_, StartDocument)
       .WillOnce(DoAll(SaveArg<1>(&start_document_document_name),
@@ -246,7 +250,8 @@
   std::string start_document_document_name;
   std::string start_document_username;
   EXPECT_CALL(*printer_, CreateJob)
-      .WillOnce(DoAll(SaveArg<1>(&create_job_document_name),
+      .WillOnce(DoAll(SetArgPointee<0>(/*job_id=*/1),
+                      SaveArg<1>(&create_job_document_name),
                       SaveArg<2>(&create_job_username), Return(status)));
   EXPECT_CALL(*printer_, StartDocument)
       .WillOnce(DoAll(SaveArg<1>(&start_document_document_name),
diff --git a/services/audio/loopback_stream.cc b/services/audio/loopback_stream.cc
index 9637327e..418a7c0 100644
--- a/services/audio/loopback_stream.cc
+++ b/services/audio/loopback_stream.cc
@@ -402,7 +402,8 @@
   // started below will just run immediately and there will be no harmful
   // effects in the next GenerateMoreAudio() call. http://crbug.com/847487
   timer_->Start(FROM_HERE, next_generate_time_, this,
-                &FlowNetwork::GenerateMoreAudio, base::ExactDeadline(true));
+                &FlowNetwork::GenerateMoreAudio,
+                base::subtle::DelayPolicy::kPrecise);
 }
 
 }  // namespace audio
diff --git a/services/audio/public/cpp/output_device_unittest.cc b/services/audio/public/cpp/output_device_unittest.cc
index ce9e0527..c574baf 100644
--- a/services/audio/public/cpp/output_device_unittest.cc
+++ b/services/audio/public/cpp/output_device_unittest.cc
@@ -56,7 +56,7 @@
   MOCK_METHOD4(Render,
                int(base::TimeDelta delay,
                    base::TimeTicks timestamp,
-                   int prior_frames_skipped,
+                   const media::AudioGlitchInfo& glitch_info,
                    media::AudioBus* dest));
   void OnRenderError() override {}
 };
@@ -234,8 +234,10 @@
   auto test_bus = media::AudioBus::Create(params);
   for (int i = 0; i < 10; ++i) {
     test_bus->Zero();
+    media::AudioGlitchInfo glitch_info{.duration = base::Milliseconds(100),
+                                       .count = 123};
     EXPECT_CALL(env.render_callback,
-                Render(kDelay, env.time_stamp, 0, NotNull()))
+                Render(kDelay, env.time_stamp, glitch_info, NotNull()))
         .WillOnce(WithArg<3>(Invoke([](media::AudioBus* client_bus) -> int {
           // Place some test data in the bus so that we can check that it was
           // copied to the audio service side.
@@ -243,7 +245,7 @@
           std::fill_n(client_bus->channel(1), client_bus->frames(), kAudioData);
           return client_bus->frames();
         })));
-    env.reader->RequestMoreData(kDelay, env.time_stamp, {});
+    env.reader->RequestMoreData(kDelay, env.time_stamp, glitch_info);
     env.reader->Read(test_bus.get(), false);
 
     Mock::VerifyAndClear(&env.render_callback);
@@ -290,8 +292,10 @@
   auto test_bus = media::AudioBus::Create(params);
   for (int i = 0; i < 10; ++i) {
     test_bus->Zero();
+    media::AudioGlitchInfo glitch_info{.duration = base::Milliseconds(100),
+                                       .count = 123};
     EXPECT_CALL(env.render_callback,
-                Render(kDelay, env.time_stamp, 0, NotNull()))
+                Render(kDelay, env.time_stamp, glitch_info, NotNull()))
         .WillOnce(WithArg<3>(Invoke([](media::AudioBus* renderer_bus) -> int {
           EXPECT_TRUE(renderer_bus->is_bitstream_format());
           // Place some test data in the bus so that we can check that it was
@@ -302,7 +306,7 @@
           renderer_bus->SetBitstreamDataSize(kBitstreamDataSize);
           return renderer_bus->frames();
         })));
-    env.reader->RequestMoreData(kDelay, env.time_stamp, {});
+    env.reader->RequestMoreData(kDelay, env.time_stamp, glitch_info);
     env.reader->Read(test_bus.get(), false);
 
     Mock::VerifyAndClear(&env.render_callback);
diff --git a/services/audio/public/cpp/sounds/audio_stream_handler.cc b/services/audio/public/cpp/sounds/audio_stream_handler.cc
index 1670523..646bc96 100644
--- a/services/audio/public/cpp/sounds/audio_stream_handler.cc
+++ b/services/audio/public/cpp/sounds/audio_stream_handler.cc
@@ -119,7 +119,7 @@
   // Following methods could be called from *ANY* thread.
   int Render(base::TimeDelta /* delay */,
              base::TimeTicks /* delay_timestamp */,
-             int /* prior_frames_skipped */,
+             const media::AudioGlitchInfo& /* glitch_info */,
              media::AudioBus* dest) override {
     base::AutoLock al(state_lock_);
     size_t bytes_written = 0;
diff --git a/services/audio/public/cpp/sounds/test_data.cc b/services/audio/public/cpp/sounds/test_data.cc
index 05086a1..b7f751c 100644
--- a/services/audio/public/cpp/sounds/test_data.cc
+++ b/services/audio/public/cpp/sounds/test_data.cc
@@ -33,7 +33,7 @@
 void TestObserver::Render() {
   if (!is_playing)
     return;
-  if (callback_->Render(base::Seconds(0), base::TimeTicks::Now(), 0,
+  if (callback_->Render(base::Seconds(0), base::TimeTicks::Now(), {},
                         bus_.get())) {
     task_runner_->PostTask(FROM_HERE, base::BindOnce(&TestObserver::Render,
                                                      base::Unretained(this)));
diff --git a/services/device/OWNERS b/services/device/OWNERS
index d78bf3c..2abd7572 100644
--- a/services/device/OWNERS
+++ b/services/device/OWNERS
@@ -2,3 +2,4 @@
 mattreynolds@chromium.org
 reillyg@chromium.org
 rockot@google.com
+chengweih@chromium.org
diff --git a/services/device/geolocation/wifi_data_provider_chromeos_unittest.cc b/services/device/geolocation/wifi_data_provider_chromeos_unittest.cc
index 6537cc1..9689c30 100644
--- a/services/device/geolocation/wifi_data_provider_chromeos_unittest.cc
+++ b/services/device/geolocation/wifi_data_provider_chromeos_unittest.cc
@@ -9,6 +9,7 @@
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/task_environment.h"
+#include "base/values.h"
 #include "chromeos/ash/components/dbus/shill/shill_clients.h"
 #include "chromeos/ash/components/dbus/shill/shill_manager_client.h"
 #include "chromeos/ash/components/network/geolocation_handler.h"
@@ -45,16 +46,14 @@
   void AddAccessPoints(int ssids, int aps_per_ssid) {
     for (int i = 0; i < ssids; ++i) {
       for (int j = 0; j < aps_per_ssid; ++j) {
-        base::DictionaryValue properties;
         std::string mac_address = base::StringPrintf(
             "%02X:%02X:%02X:%02X:%02X:%02X", i, j, 3, 4, 5, 6);
         std::string channel = base::NumberToString(i * 10 + j);
         std::string strength = base::NumberToString(i * 100 + j);
-        properties.SetKey(shill::kGeoMacAddressProperty,
-                          base::Value(mac_address));
-        properties.SetKey(shill::kGeoChannelProperty, base::Value(channel));
-        properties.SetKey(shill::kGeoSignalStrengthProperty,
-                          base::Value(strength));
+        base::Value::Dict properties;
+        properties.Set(shill::kGeoMacAddressProperty, mac_address);
+        properties.Set(shill::kGeoChannelProperty, channel);
+        properties.Set(shill::kGeoSignalStrengthProperty, strength);
         network_handler_test_helper_.manager_test()->AddGeoNetwork(
             shill::kGeoWifiAccessPointsProperty, properties);
       }
diff --git a/services/network/public/cpp/content_security_policy/content_security_policy.cc b/services/network/public/cpp/content_security_policy/content_security_policy.cc
index 633c233..dae74b9 100644
--- a/services/network/public/cpp/content_security_policy/content_security_policy.cc
+++ b/services/network/public/cpp/content_security_policy/content_security_policy.cc
@@ -10,6 +10,7 @@
 #include "base/base64url.h"
 #include "base/containers/contains.h"
 #include "base/containers/flat_set.h"
+#include "base/feature_list.h"
 #include "base/ranges/algorithm.h"
 #include "base/strings/strcat.h"
 #include "base/strings/string_number_conversions.h"
@@ -22,6 +23,7 @@
 #include "services/network/public/cpp/content_security_policy/csp_context.h"
 #include "services/network/public/cpp/content_security_policy/csp_source.h"
 #include "services/network/public/cpp/content_security_policy/csp_source_list.h"
+#include "services/network/public/cpp/features.h"
 #include "services/network/public/cpp/is_potentially_trustworthy.h"
 #include "services/network/public/cpp/web_sandbox_flags.h"
 #include "services/network/public/mojom/content_security_policy.mojom.h"
@@ -645,6 +647,28 @@
       continue;
     }
 
+    // Discussed at https://github.com/WICG/nav-speculation/pull/209, and merged
+    // to the speculationrules explainer,
+    // https://github.com/WICG/nav-speculation/blob/main/triggers.md#content-security-policy.
+    // TODO(https://crbug.com/1382361): Have a patch spec and merge it to the
+    // upstream CSP spec.
+    if (base::FeatureList::IsEnabled(
+            features::kPrerender2ContentSecurityPolicyExtensions) &&
+        base::EqualsCaseInsensitiveASCII(expression,
+                                         "'inline-speculation-rules'")) {
+      if (directive_name == CSPDirectiveName::ScriptSrc) {
+        directive->allow_inline_speculation_rules = true;
+        continue;
+      } else {
+        parsing_errors.emplace_back(base::StringPrintf(
+            "The Content-Security-Policy directive '%s' contains '%s' as a "
+            "source expression that is permitted only for 'script-src' "
+            "directive. It will be ignored.",
+            ToString(directive_name).c_str(), std::string(expression).c_str()));
+        continue;
+      }
+    }
+
     if (base::EqualsCaseInsensitiveASCII(expression, "'unsafe-eval'")) {
       directive->allow_eval = true;
       continue;
diff --git a/services/network/public/cpp/content_security_policy/content_security_policy_unittest.cc b/services/network/public/cpp/content_security_policy/content_security_policy_unittest.cc
index de697b0..7ac380d40 100644
--- a/services/network/public/cpp/content_security_policy/content_security_policy_unittest.cc
+++ b/services/network/public/cpp/content_security_policy/content_security_policy_unittest.cc
@@ -8,8 +8,10 @@
 #include "base/memory/raw_ref.h"
 #include "base/strings/string_piece.h"
 #include "base/strings/stringprintf.h"
+#include "base/test/scoped_feature_list.h"
 #include "net/http/http_response_headers.h"
 #include "services/network/public/cpp/content_security_policy/csp_context.h"
+#include "services/network/public/cpp/features.h"
 #include "services/network/public/cpp/web_sandbox_flags.h"
 #include "services/network/public/mojom/content_security_policy.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -1583,6 +1585,37 @@
   }
 }
 
+TEST(ContentSecurityPolicy, ParseInlineSpeculationRules) {
+  base::test::ScopedFeatureList scoped_feature_list(
+      features::kPrerender2ContentSecurityPolicyExtensions);
+  std::vector<mojom::ContentSecurityPolicyPtr> ok_policies =
+      ParseCSP("script-src 'inline-speculation-rules'");
+  ASSERT_EQ(1u, ok_policies.size());
+  ASSERT_EQ(1u, ok_policies[0]->directives.size());
+  ASSERT_TRUE(
+      ok_policies[0]->directives.contains(mojom::CSPDirectiveName::ScriptSrc));
+  EXPECT_TRUE(ok_policies[0]
+                  ->directives[mojom::CSPDirectiveName::ScriptSrc]
+                  ->allow_inline_speculation_rules);
+  EXPECT_EQ(0u, ok_policies[0]->parsing_errors.size());
+
+  std::vector<mojom::ContentSecurityPolicyPtr> ng_policies =
+      ParseCSP("img-src 'inline-speculation-rules'");
+  ASSERT_EQ(1u, ng_policies.size());
+  ASSERT_EQ(1u, ng_policies[0]->directives.size());
+  ASSERT_TRUE(
+      ng_policies[0]->directives.contains(mojom::CSPDirectiveName::ImgSrc));
+  EXPECT_FALSE(ng_policies[0]
+                   ->directives[mojom::CSPDirectiveName::ImgSrc]
+                   ->allow_inline_speculation_rules);
+  ASSERT_EQ(1u, ng_policies[0]->parsing_errors.size());
+  EXPECT_EQ(
+      "The Content-Security-Policy directive 'img-src' contains "
+      "''inline-speculation-rules'' as a source expression that is permitted "
+      "only for 'script-src' directive. It will be ignored.",
+      ng_policies[0]->parsing_errors[0]);
+}
+
 TEST(ContentSecurityPolicy, IsValidRequiredCSPAttr) {
   struct TestCase {
     const char* csp;
diff --git a/services/network/public/cpp/features.cc b/services/network/public/cpp/features.cc
index 3592212a..de04861b 100644
--- a/services/network/public/cpp/features.cc
+++ b/services/network/public/cpp/features.cc
@@ -327,4 +327,8 @@
              "PrefetchNoVarySearch",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+BASE_FEATURE(kPrerender2ContentSecurityPolicyExtensions,
+             "Prerender2ContentSecurityPolicyExtensions",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 }  // namespace network::features
diff --git a/services/network/public/cpp/features.h b/services/network/public/cpp/features.h
index 4c10633..3f16f5f 100644
--- a/services/network/public/cpp/features.h
+++ b/services/network/public/cpp/features.h
@@ -114,6 +114,12 @@
 COMPONENT_EXPORT(NETWORK_CPP)
 BASE_DECLARE_FEATURE(kPrefetchNoVarySearch);
 
+// Enables the `inline-speculation-rules` source support in the
+// Content-Security-Policy for Prerender2.
+// https://crbug.com/1382361
+COMPONENT_EXPORT(NETWORK_CPP)
+BASE_DECLARE_FEATURE(kPrerender2ContentSecurityPolicyExtensions);
+
 }  // namespace features
 }  // namespace network
 
diff --git a/services/network/public/mojom/content_security_policy.mojom b/services/network/public/mojom/content_security_policy.mojom
index ff437771..ddea1e4 100644
--- a/services/network/public/mojom/content_security_policy.mojom
+++ b/services/network/public/mojom/content_security_policy.mojom
@@ -82,6 +82,7 @@
   bool allow_star = false;
   bool allow_response_redirects = false;
   bool allow_inline = false;
+  bool allow_inline_speculation_rules = false;
   bool allow_eval = false;
   bool allow_wasm_eval = false;
   bool allow_wasm_unsafe_eval = false;
diff --git a/services/tracing/perfetto/consumer_host.cc b/services/tracing/perfetto/consumer_host.cc
index 2bdbaee..61b40b47 100644
--- a/services/tracing/perfetto/consumer_host.cc
+++ b/services/tracing/perfetto/consumer_host.cc
@@ -12,6 +12,7 @@
 #include "base/containers/contains.h"
 #include "base/containers/cxx20_erase.h"
 #include "base/logging.h"
+#include "base/notreached.h"
 #include "base/ranges/algorithm.h"
 #include "base/strings/strcat.h"
 #include "base/strings/string_number_conversions.h"
@@ -182,9 +183,15 @@
   }
 #endif
 
+#if BUILDFLAG(USE_PERFETTO_CLIENT_LIBRARY)
+  const std::string kDataSourceName = "track_event";
+#else
+  const std::string kDataSourceName = mojom::kTraceEventDataSourceName;
+#endif
+
   filtered_pids_.clear();
   for (const auto& ds_config : trace_config.data_sources()) {
-    if (ds_config.config().name() == mojom::kTraceEventDataSourceName) {
+    if (ds_config.config().name() == kDataSourceName) {
       for (const auto& filter : ds_config.producer_name_filter()) {
         base::ProcessId pid;
         if (PerfettoService::ParsePidFromProducerName(filter, &pid)) {
@@ -729,6 +736,10 @@
   }
 }
 
+void ConsumerHost::OnSessionCloned(bool, const std::string&) {
+  NOTREACHED();
+}
+
 void ConsumerHost::DestructTracingSession() {
   tracing_session_.reset();
 }
diff --git a/services/tracing/perfetto/consumer_host.h b/services/tracing/perfetto/consumer_host.h
index 17c41dc9..58fa578c 100644
--- a/services/tracing/perfetto/consumer_host.h
+++ b/services/tracing/perfetto/consumer_host.h
@@ -159,6 +159,7 @@
                    bool has_more) override;
   void OnObservableEvents(const perfetto::ObservableEvents&) override;
   void OnTraceStats(bool success, const perfetto::TraceStats&) override;
+  void OnSessionCloned(bool, const std::string&) override;
 
   // Unused in Chrome.
   void OnDetach(bool success) override {}
diff --git a/services/tracing/perfetto/consumer_host_unittest.cc b/services/tracing/perfetto/consumer_host_unittest.cc
index e230094..43add43 100644
--- a/services/tracing/perfetto/consumer_host_unittest.cc
+++ b/services/tracing/perfetto/consumer_host_unittest.cc
@@ -34,6 +34,12 @@
 
 namespace tracing {
 
+#if BUILDFLAG(USE_PERFETTO_CLIENT_LIBRARY)
+const std::string kDataSourceName = "track_event";
+#else
+const std::string kDataSourceName = mojom::kTraceEventDataSourceName;
+#endif
+
 constexpr base::ProcessId kProducerPid = 1234;
 
 // This is here so we can properly simulate this running on three
@@ -457,7 +463,7 @@
 };
 
 TEST_F(TracingConsumerTest, EnableAndDisableTracing) {
-  EnableTracingWithDataSourceName(mojom::kTraceEventDataSourceName);
+  EnableTracingWithDataSourceName(kDataSourceName);
 
   base::RunLoop no_more_data;
   ExpectPackets(kPerfettoTestString, no_more_data.QuitClosure());
@@ -471,12 +477,11 @@
 }
 
 TEST_F(TracingConsumerTest, ReceiveTestPackets) {
-  EnableTracingWithDataSourceName(mojom::kTraceEventDataSourceName);
+  EnableTracingWithDataSourceName(kDataSourceName);
 
   base::RunLoop wait_for_tracing_start;
   threaded_perfetto_service()->CreateProducer(
-      mojom::kTraceEventDataSourceName, 10u,
-      wait_for_tracing_start.QuitClosure());
+      kDataSourceName, 10u, wait_for_tracing_start.QuitClosure());
 
   wait_for_tracing_start.Run();
 
@@ -492,12 +497,11 @@
 }
 
 TEST_F(TracingConsumerTest, DeleteConsumerWhenReceiving) {
-  EnableTracingWithDataSourceName(mojom::kTraceEventDataSourceName);
+  EnableTracingWithDataSourceName(kDataSourceName);
 
   base::RunLoop wait_for_tracing_start;
   threaded_perfetto_service()->CreateProducer(
-      mojom::kTraceEventDataSourceName, 100u,
-      wait_for_tracing_start.QuitClosure());
+      kDataSourceName, 100u, wait_for_tracing_start.QuitClosure());
 
   wait_for_tracing_start.Run();
 
@@ -512,12 +516,11 @@
 }
 
 TEST_F(TracingConsumerTest, FlushProducers) {
-  EnableTracingWithDataSourceName(mojom::kTraceEventDataSourceName);
+  EnableTracingWithDataSourceName(kDataSourceName);
 
   base::RunLoop wait_for_tracing_start;
   threaded_perfetto_service()->CreateProducer(
-      mojom::kTraceEventDataSourceName, 10u,
-      wait_for_tracing_start.QuitClosure());
+      kDataSourceName, 10u, wait_for_tracing_start.QuitClosure());
 
   wait_for_tracing_start.Run();
 
@@ -535,12 +538,11 @@
 }
 
 TEST_F(TracingConsumerTest, LargeDataSize) {
-  EnableTracingWithDataSourceName(mojom::kTraceEventDataSourceName);
+  EnableTracingWithDataSourceName(kDataSourceName);
 
   base::RunLoop wait_for_tracing_start;
   threaded_perfetto_service()->CreateProducer(
-      mojom::kTraceEventDataSourceName, 0u,
-      wait_for_tracing_start.QuitClosure());
+      kDataSourceName, 0u, wait_for_tracing_start.QuitClosure());
 
   wait_for_tracing_start.Run();
 
@@ -560,7 +562,7 @@
 TEST_F(TracingConsumerTest, NotifiesOnTracingEnabled) {
   threaded_perfetto_service()->SetPidsInitialized();
 
-  EnableTracingWithDataSourceName(mojom::kTraceEventDataSourceName);
+  EnableTracingWithDataSourceName(kDataSourceName);
   EXPECT_TRUE(IsTracingEnabled());
 }
 
@@ -568,7 +570,7 @@
   threaded_perfetto_service()->ExpectPid(kProducerPid);
   threaded_perfetto_service()->SetPidsInitialized();
 
-  EnableTracingWithDataSourceName(mojom::kTraceEventDataSourceName);
+  EnableTracingWithDataSourceName(kDataSourceName);
 
   // Tracing is only marked as enabled once the expected producer has acked that
   // its data source has started.
@@ -576,8 +578,7 @@
 
   base::RunLoop wait_for_tracing_start;
   threaded_perfetto_service()->CreateProducer(
-      mojom::kTraceEventDataSourceName, 0u,
-      wait_for_tracing_start.QuitClosure());
+      kDataSourceName, 0u, wait_for_tracing_start.QuitClosure());
   wait_for_tracing_start.Run();
 
   EXPECT_TRUE(IsTracingEnabled());
@@ -588,7 +589,7 @@
   threaded_perfetto_service()->SetPidsInitialized();
 
   // Filter for the expected producer.
-  auto config = GetDefaultTraceConfig(mojom::kTraceEventDataSourceName);
+  auto config = GetDefaultTraceConfig(kDataSourceName);
   *config.mutable_data_sources()->front().add_producer_name_filter() =
       base::StrCat({mojom::kPerfettoProducerNamePrefix,
                     base::NumberToString(kProducerPid)});
@@ -600,8 +601,7 @@
 
   base::RunLoop wait_for_tracing_start;
   threaded_perfetto_service()->CreateProducer(
-      mojom::kTraceEventDataSourceName, 0u,
-      wait_for_tracing_start.QuitClosure());
+      kDataSourceName, 0u, wait_for_tracing_start.QuitClosure());
   wait_for_tracing_start.Run();
 
   EXPECT_TRUE(IsTracingEnabled());
@@ -613,7 +613,7 @@
   threaded_perfetto_service()->SetPidsInitialized();
 
   // Filter for an unexpected producer whose PID is not active.
-  auto config = GetDefaultTraceConfig(mojom::kTraceEventDataSourceName);
+  auto config = GetDefaultTraceConfig(kDataSourceName);
   *config.mutable_data_sources()->front().add_producer_name_filter() =
       base::StrCat({mojom::kPerfettoProducerNamePrefix,
                     base::NumberToString(kProducerPid + 1)});
@@ -629,7 +629,7 @@
        NotifiesOnTracingEnabledWaitsForProducerAndInitializedPids) {
   threaded_perfetto_service()->ExpectPid(kProducerPid);
 
-  EnableTracingWithDataSourceName(mojom::kTraceEventDataSourceName);
+  EnableTracingWithDataSourceName(kDataSourceName);
 
   // Tracing is only marked as enabled once the expected producer has acked that
   // its data source has started and once the PIDs are initialized.
@@ -637,8 +637,7 @@
 
   base::RunLoop wait_for_tracing_start;
   threaded_perfetto_service()->CreateProducer(
-      mojom::kTraceEventDataSourceName, 0u,
-      wait_for_tracing_start.QuitClosure());
+      kDataSourceName, 0u, wait_for_tracing_start.QuitClosure());
   wait_for_tracing_start.Run();
 
   EXPECT_FALSE(IsTracingEnabled());
@@ -648,14 +647,13 @@
 }
 
 TEST_F(TracingConsumerTest, PrivacyFilterConfig) {
-  EnableTracingWithDataSourceName(mojom::kTraceEventDataSourceName,
+  EnableTracingWithDataSourceName(kDataSourceName,
                                   /* enable_privacy_filtering =*/true,
                                   /* convert_to_legacy_json =*/false);
 
   base::RunLoop wait_for_tracing_start;
   threaded_perfetto_service()->CreateProducer(
-      mojom::kTraceEventDataSourceName, 10u,
-      wait_for_tracing_start.QuitClosure());
+      kDataSourceName, 10u, wait_for_tracing_start.QuitClosure());
 
   wait_for_tracing_start.Run();
   EXPECT_TRUE(threaded_perfetto_service()
@@ -670,14 +668,13 @@
 }
 
 TEST_F(TracingConsumerTest, NoPrivacyFilterWithJsonConversion) {
-  EnableTracingWithDataSourceName(mojom::kTraceEventDataSourceName,
+  EnableTracingWithDataSourceName(kDataSourceName,
                                   /* enable_privacy_filtering =*/false,
                                   /* convert_to_legacy_json =*/true);
 
   base::RunLoop wait_for_tracing_start;
   threaded_perfetto_service()->CreateProducer(
-      mojom::kTraceEventDataSourceName, 10u,
-      wait_for_tracing_start.QuitClosure());
+      kDataSourceName, 10u, wait_for_tracing_start.QuitClosure());
 
   wait_for_tracing_start.Run();
 
@@ -693,14 +690,13 @@
 }
 
 TEST_F(TracingConsumerTest, PrivacyFilterConfigInJson) {
-  EnableTracingWithDataSourceName(mojom::kTraceEventDataSourceName,
+  EnableTracingWithDataSourceName(kDataSourceName,
                                   /* enable_privacy_filtering =*/true,
                                   /* convert_to_legacy_json =*/true);
 
   base::RunLoop wait_for_tracing_start;
   threaded_perfetto_service()->CreateProducer(
-      mojom::kTraceEventDataSourceName, 10u,
-      wait_for_tracing_start.QuitClosure());
+      kDataSourceName, 10u, wait_for_tracing_start.QuitClosure());
 
   wait_for_tracing_start.Run();
 
@@ -782,12 +778,10 @@
 
 TEST_F(TracingConsumerTest, TestConsumerPriority) {
   PerfettoService::GetInstance()->SetActiveServicePidsInitialized();
-  auto trace_config_background =
-      GetDefaultTraceConfig(mojom::kTraceEventDataSourceName,
-                            perfetto::protos::gen ::ChromeConfig::BACKGROUND);
+  auto trace_config_background = GetDefaultTraceConfig(
+      kDataSourceName, perfetto::protos::gen ::ChromeConfig::BACKGROUND);
   auto trace_config_user_initiated = GetDefaultTraceConfig(
-      mojom::kTraceEventDataSourceName,
-      perfetto::protos::gen ::ChromeConfig::USER_INITIATED);
+      kDataSourceName, perfetto::protos::gen ::ChromeConfig::USER_INITIATED);
 
   MockConsumerHost background_consumer_1(PerfettoService::GetInstance());
   background_consumer_1.EnableTracing(trace_config_background);
diff --git a/services/tracing/perfetto/test_utils.cc b/services/tracing/perfetto/test_utils.cc
index 399b9df..d2b17c9 100644
--- a/services/tracing/perfetto/test_utils.cc
+++ b/services/tracing/perfetto/test_utils.cc
@@ -278,6 +278,8 @@
 void MockConsumer::OnAttach(bool /*success*/, const perfetto::TraceConfig&) {}
 void MockConsumer::OnTraceStats(bool /*success*/, const perfetto::TraceStats&) {
 }
+void MockConsumer::OnSessionCloned(bool /*success*/,
+                                   const std::string& /*error*/) {}
 
 void MockConsumer::OnObservableEvents(
     const perfetto::ObservableEvents& events) {
diff --git a/services/tracing/perfetto/test_utils.h b/services/tracing/perfetto/test_utils.h
index eecbf317..1a44d209 100644
--- a/services/tracing/perfetto/test_utils.h
+++ b/services/tracing/perfetto/test_utils.h
@@ -152,8 +152,9 @@
   void OnDetach(bool success) override;
   void OnAttach(bool success, const perfetto::TraceConfig&) override;
   void OnTraceStats(bool success, const perfetto::TraceStats&) override;
-
   void OnObservableEvents(const perfetto::ObservableEvents&) override;
+  void OnSessionCloned(bool, const std::string&) override;
+
   void WaitForAllDataSourcesStarted();
   void WaitForAllDataSourcesStopped();
 
diff --git a/services/tracing/public/cpp/perfetto/perfetto_tracing_backend.cc b/services/tracing/public/cpp/perfetto/perfetto_tracing_backend.cc
index 627a33f..204cda1f 100644
--- a/services/tracing/public/cpp/perfetto/perfetto_tracing_backend.cc
+++ b/services/tracing/public/cpp/perfetto/perfetto_tracing_backend.cc
@@ -495,6 +495,12 @@
     NOTREACHED();
   }
 
+  void CloneSession(perfetto::TracingSessionID) override {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    // Not implemented yet.
+    NOTREACHED();
+  }
+
   // tracing::mojom::TracingSessionClient implementation:
   void OnTracingEnabled() override {
     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
diff --git a/skia/config/SkUserConfig.h b/skia/config/SkUserConfig.h
index 0c63a28..abc3086 100644
--- a/skia/config/SkUserConfig.h
+++ b/skia/config/SkUserConfig.h
@@ -216,6 +216,10 @@
 
 #define SK_USE_LEGACY_MIPMAP_BUILDER
 
+// To ensure chrome only has access to legacy vma memory query apis until all
+// skia changes to implement new api is completed.
+#define SK_USE_LEGACY_VMA_MEMORY_QUERY
+
 ///////////////////////// Imported from BUILD.gn and skia_common.gypi
 
 /* In some places Skia can use static initializers for global initialization,
diff --git a/testing/buildbot/chromium.android.json b/testing/buildbot/chromium.android.json
index fba3088..35da04f 100644
--- a/testing/buildbot/chromium.android.json
+++ b/testing/buildbot/chromium.android.json
@@ -25919,6 +25919,36 @@
     "isolated_scripts": [
       {
         "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/android.nougat-x86-rel.chrome_junit_tests.filter"
+        ],
+        "isolate_name": "chrome_junit_tests",
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "chrome_junit_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cores": "8",
+              "cpu": "x86-64",
+              "os": "Ubuntu-18.04",
+              "pool": "chromium.tests"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://chrome/android:chrome_junit_tests/",
+        "use_isolated_scripts_api": true
+      },
+      {
+        "args": [
           "--gtest-benchmark-name=components_perftests",
           "--avd-config=../../tools/android/avd/proto/generic_android24.textpb"
         ],
@@ -26208,17 +26238,6 @@
       },
       {
         "isolate_profile_data": true,
-        "name": "chrome_junit_tests",
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {},
-        "test": "chrome_junit_tests",
-        "test_id_prefix": "ninja://chrome/android:chrome_junit_tests/"
-      },
-      {
-        "isolate_profile_data": true,
         "name": "components_junit_tests",
         "resultdb": {
           "enable": true,
diff --git a/testing/buildbot/chromium.cft.json b/testing/buildbot/chromium.cft.json
new file mode 100644
index 0000000..ffea668
--- /dev/null
+++ b/testing/buildbot/chromium.cft.json
@@ -0,0 +1,1899 @@
+{
+  "AAAAA1 AUTOGENERATED FILE DO NOT EDIT": {},
+  "AAAAA2 See generate_buildbot_json.py to make changes": {},
+  "mac-rel-cft": {
+    "additional_compile_targets": [
+      "all"
+    ],
+    "gtest_tests": [
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "absl_hardening_tests",
+        "test_id_prefix": "ninja://third_party/abseil-cpp:absl_hardening_tests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "accessibility_unittests",
+        "test_id_prefix": "ninja://ui/accessibility:accessibility_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_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": "angle_unittests",
+        "test_id_prefix": "ninja://third_party/angle/src/tests:angle_unittests/",
+        "use_isolated_scripts_api": true
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "app_shell_unittests",
+        "test_id_prefix": "ninja://extensions/shell:app_shell_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "blink_common_unittests",
+        "test_id_prefix": "ninja://third_party/blink/common:blink_common_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "blink_fuzzer_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/platform:blink_fuzzer_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "blink_heap_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "blink_platform_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/platform:blink_platform_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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-12"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/controller:blink_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "boringssl_crypto_tests",
+        "test_id_prefix": "ninja://third_party/boringssl:boringssl_crypto_tests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "boringssl_ssl_tests",
+        "test_id_prefix": "ninja://third_party/boringssl:boringssl_ssl_tests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": [
+          "--gtest_filter=-*UsingRealWebcam*"
+        ],
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "capture_unittests",
+        "test_id_prefix": "ninja://media/capture:capture_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "cast_unittests",
+        "test_id_prefix": "ninja://media/cast:cast_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "cc_unittests",
+        "test_id_prefix": "ninja://cc:cc_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "chrome_app_unittests",
+        "test_id_prefix": "ninja://chrome/test:chrome_app_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "chromedriver_unittests",
+        "test_id_prefix": "ninja://chrome/test/chromedriver:chromedriver_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "color_unittests",
+        "test_id_prefix": "ninja://ui/color:color_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "components_browsertests",
+        "test_id_prefix": "ninja://components:components_browsertests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "components_unittests",
+        "test_id_prefix": "ninja://components:components_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": 8
+        },
+        "test": "content_browsertests",
+        "test_id_prefix": "ninja://content/test:content_browsertests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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_nocompile_tests",
+        "test_id_prefix": "ninja://content/test:content_nocompile_tests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "crashpad_tests",
+        "test_id_prefix": "ninja://third_party/crashpad/crashpad:crashpad_tests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "cronet_tests",
+        "test_id_prefix": "ninja://components/cronet:cronet_tests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "cronet_unittests",
+        "test_id_prefix": "ninja://components/cronet:cronet_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "crypto_unittests",
+        "test_id_prefix": "ninja://crypto:crypto_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "device_unittests",
+        "test_id_prefix": "ninja://device:device_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "display_unittests",
+        "test_id_prefix": "ninja://ui/display:display_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "events_unittests",
+        "test_id_prefix": "ninja://ui/events:events_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "extensions_browsertests",
+        "test_id_prefix": "ninja://extensions:extensions_browsertests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "extensions_unittests",
+        "test_id_prefix": "ninja://extensions:extensions_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "filesystem_service_unittests",
+        "test_id_prefix": "ninja://components/services/filesystem:filesystem_service_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "gcm_unit_tests",
+        "test_id_prefix": "ninja://google_apis/gcm:gcm_unit_tests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "gfx_unittests",
+        "test_id_prefix": "ninja://ui/gfx:gfx_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "gin_unittests",
+        "test_id_prefix": "ninja://gin:gin_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "google_apis_unittests",
+        "test_id_prefix": "ninja://google_apis:google_apis_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "gpu_unittests",
+        "test_id_prefix": "ninja://gpu:gpu_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "gwp_asan_unittests",
+        "test_id_prefix": "ninja://components/gwp_asan:gwp_asan_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "headless_browsertests",
+        "test_id_prefix": "ninja://headless:headless_browsertests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "headless_unittests",
+        "test_id_prefix": "ninja://headless:headless_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": 3
+        },
+        "test": "interactive_ui_tests",
+        "test_id_prefix": "ninja://chrome/test:interactive_ui_tests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "ipc_tests",
+        "test_id_prefix": "ninja://ipc:ipc_tests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "latency_unittests",
+        "test_id_prefix": "ninja://ui/latency:latency_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "libjingle_xmpp_unittests",
+        "test_id_prefix": "ninja://third_party/libjingle_xmpp:libjingle_xmpp_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "liburlpattern_unittests",
+        "test_id_prefix": "ninja://third_party/liburlpattern:liburlpattern_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "media_unittests",
+        "test_id_prefix": "ninja://media:media_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "message_center_unittests",
+        "test_id_prefix": "ninja://ui/message_center:message_center_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "midi_unittests",
+        "test_id_prefix": "ninja://media/midi:midi_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "mojo_unittests",
+        "test_id_prefix": "ninja://mojo:mojo_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "native_theme_unittests",
+        "test_id_prefix": "ninja://ui/native_theme:native_theme_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "net_unittests",
+        "test_id_prefix": "ninja://net:net_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "openscreen_unittests",
+        "test_id_prefix": "ninja://chrome/browser/media/router:openscreen_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "pdf_unittests",
+        "test_id_prefix": "ninja://pdf:pdf_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "perfetto_unittests",
+        "test_id_prefix": "ninja://third_party/perfetto:perfetto_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "power_sampler_unittests",
+        "test_id_prefix": "ninja://tools/mac/power:power_sampler_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "ppapi_unittests",
+        "test_id_prefix": "ninja://ppapi:ppapi_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "printing_unittests",
+        "test_id_prefix": "ninja://printing:printing_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "remoting_unittests",
+        "test_id_prefix": "ninja://remoting:remoting_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "sandbox_unittests",
+        "test_id_prefix": "ninja://sandbox:sandbox_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "service_manager_unittests",
+        "test_id_prefix": "ninja://services/service_manager/tests:service_manager_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "services_unittests",
+        "test_id_prefix": "ninja://services:services_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "shell_dialogs_unittests",
+        "test_id_prefix": "ninja://ui/shell_dialogs:shell_dialogs_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "skia_unittests",
+        "test_id_prefix": "ninja://skia:skia_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "snapshot_unittests",
+        "test_id_prefix": "ninja://ui/snapshot:snapshot_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "sql_unittests",
+        "test_id_prefix": "ninja://sql:sql_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "storage_unittests",
+        "test_id_prefix": "ninja://storage:storage_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "sync_integration_tests",
+        "test_id_prefix": "ninja://chrome/test:sync_integration_tests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "ui_base_unittests",
+        "test_id_prefix": "ninja://ui/base:ui_base_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "ui_touch_selection_unittests",
+        "test_id_prefix": "ninja://ui/touch_selection:ui_touch_selection_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "unit_tests",
+        "test_id_prefix": "ninja://chrome/test:unit_tests/"
+      },
+      {
+        "args": [
+          "--test-launcher-timeout=90000",
+          "--ui-test-action-max-timeout=45000",
+          "--ui-test-action-timeout=40000"
+        ],
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "updater_tests",
+        "test_id_prefix": "ninja://chrome/updater:updater_tests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "url_unittests",
+        "test_id_prefix": "ninja://url:url_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "viz_unittests",
+        "test_id_prefix": "ninja://components/viz:viz_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "wtf_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/platform/wtf:wtf_unittests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "xr_browser_tests",
+        "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": "xr_browser_tests",
+        "test_id_prefix": "ninja://chrome/test:xr_browser_tests/"
+      },
+      {
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "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": "zlib_unittests",
+        "test_id_prefix": "ninja://third_party/zlib:zlib_unittests/"
+      }
+    ],
+    "isolated_scripts": [
+      {
+        "isolate_name": "blink_python_tests",
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "blink_python_tests",
+        "resultdb": {
+          "enable": true
+        },
+        "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_id_prefix": "ninja://:blink_python_tests/"
+      },
+      {
+        "args": [
+          "--num-retries=3",
+          "--write-run-histories-to=${ISOLATED_OUTDIR}/run_histories.json",
+          "--git-revision=${got_revision}"
+        ],
+        "check_flakiness_for_new_tests": false,
+        "isolate_name": "blink_web_tests",
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [
+            "--verbose"
+          ],
+          "script": "//third_party/blink/tools/merge_web_test_results.py"
+        },
+        "name": "blink_web_tests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
+        "resultdb": {
+          "enable": true
+        },
+        "results_handler": "layout tests",
+        "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": 5
+        },
+        "test_id_prefix": "ninja://:blink_web_tests/"
+      },
+      {
+        "args": [
+          "--num-retries=3",
+          "--write-run-histories-to=${ISOLATED_OUTDIR}/run_histories.json",
+          "--git-revision=${got_revision}"
+        ],
+        "check_flakiness_for_new_tests": false,
+        "isolate_name": "blink_wpt_tests",
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [
+            "--verbose"
+          ],
+          "script": "//third_party/blink/tools/merge_web_test_results.py"
+        },
+        "name": "blink_wpt_tests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
+        "resultdb": {
+          "enable": true
+        },
+        "results_handler": "layout tests",
+        "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": 7
+        },
+        "test_id_prefix": "ninja://:blink_wpt_tests/"
+      },
+      {
+        "args": [
+          "--test-type=integration"
+        ],
+        "check_flakiness_for_new_tests": false,
+        "isolate_name": "chromedriver_py_tests",
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "chromedriver_py_tests",
+        "resultdb": {
+          "enable": true
+        },
+        "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_id_prefix": "ninja://chrome/test/chromedriver:chromedriver_py_tests/"
+      },
+      {
+        "isolate_name": "chromedriver_replay_unittests",
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "chromedriver_replay_unittests",
+        "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_id_prefix": "ninja://chrome/test/chromedriver:chromedriver_replay_unittests/"
+      },
+      {
+        "args": [
+          "--gtest-benchmark-name=components_perftests"
+        ],
+        "isolate_name": "components_perftests",
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [
+            "--smoke-test-mode"
+          ],
+          "script": "//tools/perf/process_perf_results.py"
+        },
+        "name": "components_perftests",
+        "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_id_prefix": "ninja://components:components_perftests/"
+      },
+      {
+        "isolate_name": "content_shell_crash_test",
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "content_shell_crash_test",
+        "resultdb": {
+          "enable": true,
+          "result_format": "single"
+        },
+        "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_id_prefix": "ninja://content/shell:content_shell_crash_test/"
+      },
+      {
+        "isolate_name": "flatbuffers_unittests",
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "flatbuffers_unittests",
+        "resultdb": {
+          "enable": true,
+          "result_format": "single"
+        },
+        "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_id_prefix": "ninja://third_party/flatbuffers:flatbuffers_unittests/"
+      },
+      {
+        "isolate_name": "grit_python_unittests",
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "grit_python_unittests",
+        "resultdb": {
+          "enable": true
+        },
+        "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_id_prefix": "ninja://tools/grit:grit_python_unittests/"
+      },
+      {
+        "isolate_name": "mojo_python_unittests",
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "mojo_python_unittests",
+        "resultdb": {
+          "enable": true
+        },
+        "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_id_prefix": "ninja://mojo/public/tools:mojo_python_unittests/"
+      },
+      {
+        "isolate_name": "telemetry_gpu_unittests",
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "telemetry_gpu_unittests",
+        "resultdb": {
+          "enable": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Mac-12"
+            }
+          ],
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_unittests/"
+      },
+      {
+        "args": [
+          "--extra-browser-args=--enable-crashpad"
+        ],
+        "isolate_name": "telemetry_perf_unittests",
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "telemetry_perf_unittests",
+        "resultdb": {
+          "enable": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Mac-12"
+            }
+          ],
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 12
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_perf_unittests/"
+      },
+      {
+        "args": [
+          "--jobs=1",
+          "--extra-browser-args=--disable-gpu"
+        ],
+        "isolate_name": "telemetry_unittests",
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "telemetry_unittests",
+        "resultdb": {
+          "enable": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Mac-12"
+            }
+          ],
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 8
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_unittests/"
+      },
+      {
+        "args": [
+          "--gtest-benchmark-name=views_perftests"
+        ],
+        "isolate_name": "views_perftests",
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [
+            "--smoke-test-mode"
+          ],
+          "script": "//tools/perf/process_perf_results.py"
+        },
+        "name": "views_perftests",
+        "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_id_prefix": "ninja://ui/views:views_perftests/"
+      }
+    ]
+  }
+}
diff --git a/testing/buildbot/chromium.chromiumos.json b/testing/buildbot/chromium.chromiumos.json
index 95c4046..537d922 100644
--- a/testing/buildbot/chromium.chromiumos.json
+++ b/testing/buildbot/chromium.chromiumos.json
@@ -5750,9 +5750,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_v110.0.5465.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5467.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 110.0.5465.0",
+        "description": "Run with ash-chrome version 110.0.5467.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -5764,8 +5764,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5465.0",
-              "revision": "version:110.0.5465.0"
+              "location": "lacros_version_skew_tests_v110.0.5467.0",
+              "revision": "version:110.0.5467.0"
             }
           ],
           "dimension_sets": [
@@ -5921,9 +5921,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5465.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5467.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 110.0.5465.0",
+        "description": "Run with ash-chrome version 110.0.5467.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -5935,8 +5935,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5465.0",
-              "revision": "version:110.0.5465.0"
+              "location": "lacros_version_skew_tests_v110.0.5467.0",
+              "revision": "version:110.0.5467.0"
             }
           ],
           "dimension_sets": [
@@ -6073,9 +6073,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5465.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5467.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 110.0.5465.0",
+        "description": "Run with ash-chrome version 110.0.5467.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -6087,8 +6087,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5465.0",
-              "revision": "version:110.0.5465.0"
+              "location": "lacros_version_skew_tests_v110.0.5467.0",
+              "revision": "version:110.0.5467.0"
             }
           ],
           "dimension_sets": [
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index c07bd93ea..dc97953 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -85321,9 +85321,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_v110.0.5465.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5467.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 110.0.5465.0",
+        "description": "Run with ash-chrome version 110.0.5467.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -85335,8 +85335,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5465.0",
-              "revision": "version:110.0.5465.0"
+              "location": "lacros_version_skew_tests_v110.0.5467.0",
+              "revision": "version:110.0.5467.0"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -85462,9 +85462,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5465.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5467.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 110.0.5465.0",
+        "description": "Run with ash-chrome version 110.0.5467.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -85476,8 +85476,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5465.0",
-              "revision": "version:110.0.5465.0"
+              "location": "lacros_version_skew_tests_v110.0.5467.0",
+              "revision": "version:110.0.5467.0"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -85589,9 +85589,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5465.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5467.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 110.0.5465.0",
+        "description": "Run with ash-chrome version 110.0.5467.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -85603,8 +85603,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5465.0",
-              "revision": "version:110.0.5465.0"
+              "location": "lacros_version_skew_tests_v110.0.5467.0",
+              "revision": "version:110.0.5467.0"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -86937,9 +86937,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_v110.0.5465.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5467.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 110.0.5465.0",
+        "description": "Run with ash-chrome version 110.0.5467.0",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -86950,8 +86950,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5465.0",
-              "revision": "version:110.0.5465.0"
+              "location": "lacros_version_skew_tests_v110.0.5467.0",
+              "revision": "version:110.0.5467.0"
             }
           ],
           "dimension_sets": [
@@ -87108,9 +87108,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5465.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5467.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 110.0.5465.0",
+        "description": "Run with ash-chrome version 110.0.5467.0",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -87121,8 +87121,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5465.0",
-              "revision": "version:110.0.5465.0"
+              "location": "lacros_version_skew_tests_v110.0.5467.0",
+              "revision": "version:110.0.5467.0"
             }
           ],
           "dimension_sets": [
@@ -87260,9 +87260,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5465.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5467.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 110.0.5465.0",
+        "description": "Run with ash-chrome version 110.0.5467.0",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -87273,8 +87273,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5465.0",
-              "revision": "version:110.0.5465.0"
+              "location": "lacros_version_skew_tests_v110.0.5467.0",
+              "revision": "version:110.0.5467.0"
             }
           ],
           "dimension_sets": [
@@ -88798,9 +88798,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_v110.0.5465.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5467.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 110.0.5465.0",
+        "description": "Run with ash-chrome version 110.0.5467.0",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -88811,8 +88811,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5465.0",
-              "revision": "version:110.0.5465.0"
+              "location": "lacros_version_skew_tests_v110.0.5467.0",
+              "revision": "version:110.0.5467.0"
             }
           ],
           "dimension_sets": [
@@ -88969,9 +88969,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5465.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5467.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 110.0.5465.0",
+        "description": "Run with ash-chrome version 110.0.5467.0",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -88982,8 +88982,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5465.0",
-              "revision": "version:110.0.5465.0"
+              "location": "lacros_version_skew_tests_v110.0.5467.0",
+              "revision": "version:110.0.5467.0"
             }
           ],
           "dimension_sets": [
@@ -89121,9 +89121,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5465.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5467.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 110.0.5465.0",
+        "description": "Run with ash-chrome version 110.0.5467.0",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -89134,8 +89134,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5465.0",
-              "revision": "version:110.0.5465.0"
+              "location": "lacros_version_skew_tests_v110.0.5467.0",
+              "revision": "version:110.0.5467.0"
             }
           ],
           "dimension_sets": [
@@ -89907,9 +89907,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_v110.0.5465.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5467.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 110.0.5465.0",
+        "description": "Run with ash-chrome version 110.0.5467.0",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -89920,8 +89920,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5465.0",
-              "revision": "version:110.0.5465.0"
+              "location": "lacros_version_skew_tests_v110.0.5467.0",
+              "revision": "version:110.0.5467.0"
             }
           ],
           "dimension_sets": [
@@ -105976,7 +105976,9 @@
       {
         "args": [
           "--log-wptreport",
-          "--xvfb"
+          "--xvfb",
+          "--target",
+          "Release_x64"
         ],
         "experiment_percentage": 100,
         "isolate_name": "wpt_tests_isolate_content_shell",
diff --git a/testing/buildbot/chromium.gpu.fyi.json b/testing/buildbot/chromium.gpu.fyi.json
index b0bd696..d154e0d 100644
--- a/testing/buildbot/chromium.gpu.fyi.json
+++ b/testing/buildbot/chromium.gpu.fyi.json
@@ -5363,7 +5363,7 @@
         "autotest_name": "chromium_GPU",
         "bucket": "chromiumos-image-archive",
         "cros_board": "kevin",
-        "cros_img": "kevin-public/R109-15237.0.0",
+        "cros_img": "kevin-public/R110-15271.0.0",
         "extra_browser_args": "--log-level=0 --js-flags=--expose-gc --use-cmd-decoder=validating",
         "merge": {
           "args": [],
@@ -5379,7 +5379,8 @@
           "can_use_on_swarming_builders": false
         },
         "test": "telemetry_gpu_integration_test",
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/",
+        "timeout_sec": 10800
       },
       {
         "args": [
@@ -5393,7 +5394,7 @@
         "autotest_name": "chromium_GPU",
         "bucket": "chromiumos-image-archive",
         "cros_board": "kevin",
-        "cros_img": "kevin-public/R109-15237.0.0",
+        "cros_img": "kevin-public/R110-15271.0.0",
         "extra_browser_args": "--log-level=0 --js-flags=--expose-gc",
         "merge": {
           "args": [],
@@ -5409,7 +5410,8 @@
           "can_use_on_swarming_builders": false
         },
         "test": "telemetry_gpu_integration_test",
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/",
+        "timeout_sec": 10800
       },
       {
         "args": [
@@ -5423,7 +5425,7 @@
         "autotest_name": "chromium_GPU",
         "bucket": "chromiumos-image-archive",
         "cros_board": "kevin",
-        "cros_img": "kevin-public/R109-15237.0.0",
+        "cros_img": "kevin-public/R110-15271.0.0",
         "extra_browser_args": "--log-level=0 --js-flags=--expose-gc",
         "merge": {
           "args": [],
@@ -5439,7 +5441,8 @@
           "can_use_on_swarming_builders": false
         },
         "test": "telemetry_gpu_integration_test",
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/",
+        "timeout_sec": 10800
       },
       {
         "args": [
@@ -5457,7 +5460,7 @@
         "autotest_name": "chromium_GPU",
         "bucket": "chromiumos-image-archive",
         "cros_board": "kevin",
-        "cros_img": "kevin-public/R109-15237.0.0",
+        "cros_img": "kevin-public/R110-15271.0.0",
         "extra_browser_args": "--log-level=0 --js-flags=--expose-gc --force_high_performance_gpu",
         "merge": {
           "args": [],
@@ -5473,7 +5476,8 @@
           "can_use_on_swarming_builders": false
         },
         "test": "telemetry_gpu_integration_test",
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/",
+        "timeout_sec": 10800
       },
       {
         "args": [
@@ -5491,7 +5495,7 @@
         "autotest_name": "chromium_GPU",
         "bucket": "chromiumos-image-archive",
         "cros_board": "kevin",
-        "cros_img": "kevin-public/R109-15237.0.0",
+        "cros_img": "kevin-public/R110-15271.0.0",
         "extra_browser_args": "--log-level=0 --js-flags=--expose-gc --use-cmd-decoder=validating",
         "merge": {
           "args": [],
@@ -5512,7 +5516,8 @@
           "can_use_on_swarming_builders": false
         },
         "test": "telemetry_gpu_integration_test",
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/",
+        "timeout_sec": 10800
       },
       {
         "args": [
@@ -5526,7 +5531,7 @@
         "autotest_name": "chromium_GPU",
         "bucket": "chromiumos-image-archive",
         "cros_board": "kevin",
-        "cros_img": "kevin-public/R109-15237.0.0",
+        "cros_img": "kevin-public/R110-15271.0.0",
         "extra_browser_args": "--log-level=0 --js-flags=--expose-gc --force_higher_performance_gpu --use-cmd-decoder=validating",
         "merge": {
           "args": [],
@@ -5542,7 +5547,8 @@
           "can_use_on_swarming_builders": false
         },
         "test": "telemetry_gpu_integration_test",
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/",
+        "timeout_sec": 10800
       },
       {
         "args": [
@@ -5560,7 +5566,7 @@
         "autotest_name": "chromium_GPU",
         "bucket": "chromiumos-image-archive",
         "cros_board": "kevin",
-        "cros_img": "kevin-public/R109-15237.0.0",
+        "cros_img": "kevin-public/R110-15271.0.0",
         "extra_browser_args": "--log-level=0 --js-flags=--expose-gc --use-cmd-decoder=validating",
         "merge": {
           "args": [],
@@ -5581,7 +5587,8 @@
           "can_use_on_swarming_builders": false
         },
         "test": "telemetry_gpu_integration_test",
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/",
+        "timeout_sec": 10800
       },
       {
         "args": [
@@ -5596,7 +5603,7 @@
         "autotest_name": "chromium_GPU",
         "bucket": "chromiumos-image-archive",
         "cros_board": "kevin",
-        "cros_img": "kevin-public/R109-15237.0.0",
+        "cros_img": "kevin-public/R110-15271.0.0",
         "extra_browser_args": "--log-level=0 --js-flags=--expose-gc --use-cmd-decoder=validating",
         "merge": {
           "args": [],
@@ -5612,7 +5619,8 @@
           "can_use_on_swarming_builders": false
         },
         "test": "telemetry_gpu_integration_test",
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/",
+        "timeout_sec": 10800
       },
       {
         "args": [
@@ -5626,7 +5634,7 @@
         "autotest_name": "chromium_GPU",
         "bucket": "chromiumos-image-archive",
         "cros_board": "kevin",
-        "cros_img": "kevin-public/R109-15237.0.0",
+        "cros_img": "kevin-public/R110-15271.0.0",
         "extra_browser_args": "--log-level=0 --js-flags=--expose-gc",
         "merge": {
           "args": [],
@@ -5642,7 +5650,8 @@
           "can_use_on_swarming_builders": false
         },
         "test": "telemetry_gpu_integration_test",
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/",
+        "timeout_sec": 10800
       },
       {
         "args": [
@@ -5656,7 +5665,7 @@
         "autotest_name": "chromium_GPU",
         "bucket": "chromiumos-image-archive",
         "cros_board": "kevin",
-        "cros_img": "kevin-public/R109-15237.0.0",
+        "cros_img": "kevin-public/R110-15271.0.0",
         "extra_browser_args": "--log-level=0 --js-flags=--expose-gc --use-cmd-decoder=validating",
         "merge": {
           "args": [],
@@ -5672,7 +5681,8 @@
           "can_use_on_swarming_builders": false
         },
         "test": "telemetry_gpu_integration_test",
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/",
+        "timeout_sec": 10800
       },
       {
         "args": [
@@ -5689,7 +5699,7 @@
         "autotest_name": "chromium_GPU",
         "bucket": "chromiumos-image-archive",
         "cros_board": "kevin",
-        "cros_img": "kevin-public/R109-15237.0.0",
+        "cros_img": "kevin-public/R110-15271.0.0",
         "extra_browser_args": "--log-level=0 --js-flags=--expose-gc --use-gl=angle --use-angle=gles --use-cmd-decoder=passthrough --force_high_performance_gpu",
         "merge": {
           "args": [],
@@ -5705,7 +5715,8 @@
           "can_use_on_swarming_builders": false
         },
         "test": "telemetry_gpu_integration_test",
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/",
+        "timeout_sec": 10800
       },
       {
         "args": [
@@ -5722,7 +5733,7 @@
         "autotest_name": "chromium_GPU",
         "bucket": "chromiumos-image-archive",
         "cros_board": "kevin",
-        "cros_img": "kevin-public/R109-15237.0.0",
+        "cros_img": "kevin-public/R110-15271.0.0",
         "extra_browser_args": "--log-level=0 --js-flags=--expose-gc --use-cmd-decoder=validating --force_high_performance_gpu",
         "merge": {
           "args": [],
@@ -5738,7 +5749,8 @@
           "can_use_on_swarming_builders": false
         },
         "test": "telemetry_gpu_integration_test",
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/",
+        "timeout_sec": 10800
       },
       {
         "args": [
@@ -5754,7 +5766,7 @@
         "autotest_name": "chromium_GPU",
         "bucket": "chromiumos-image-archive",
         "cros_board": "kevin",
-        "cros_img": "kevin-public/R109-15237.0.0",
+        "cros_img": "kevin-public/R110-15271.0.0",
         "extra_browser_args": "--log-level=0 --js-flags=--expose-gc --use-cmd-decoder=validating --force_high_performance_gpu",
         "merge": {
           "args": [],
@@ -5770,7 +5782,8 @@
           "can_use_on_swarming_builders": false
         },
         "test": "telemetry_gpu_integration_test",
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/",
+        "timeout_sec": 10800
       }
     ]
   },
diff --git a/testing/buildbot/chromium.memory.json b/testing/buildbot/chromium.memory.json
index 2762e1fe..4496b91 100644
--- a/testing/buildbot/chromium.memory.json
+++ b/testing/buildbot/chromium.memory.json
@@ -18656,12 +18656,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_v110.0.5465.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5467.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 110.0.5465.0",
+        "description": "Run with ash-chrome version 110.0.5467.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -18673,8 +18673,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5465.0",
-              "revision": "version:110.0.5465.0"
+              "location": "lacros_version_skew_tests_v110.0.5467.0",
+              "revision": "version:110.0.5467.0"
             }
           ],
           "dimension_sets": [
@@ -18847,12 +18847,12 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5465.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5467.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 110.0.5465.0",
+        "description": "Run with ash-chrome version 110.0.5467.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -18864,8 +18864,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5465.0",
-              "revision": "version:110.0.5465.0"
+              "location": "lacros_version_skew_tests_v110.0.5467.0",
+              "revision": "version:110.0.5467.0"
             }
           ],
           "dimension_sets": [
@@ -19014,12 +19014,12 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5465.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5467.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 110.0.5465.0",
+        "description": "Run with ash-chrome version 110.0.5467.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -19031,8 +19031,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5465.0",
-              "revision": "version:110.0.5465.0"
+              "location": "lacros_version_skew_tests_v110.0.5467.0",
+              "revision": "version:110.0.5467.0"
             }
           ],
           "dimension_sets": [
diff --git a/testing/buildbot/filters/BUILD.gn b/testing/buildbot/filters/BUILD.gn
index d92e17f..b2ba859 100644
--- a/testing/buildbot/filters/BUILD.gn
+++ b/testing/buildbot/filters/BUILD.gn
@@ -72,7 +72,7 @@
 source_set("chrome_junit_tests_filters") {
   testonly = true
 
-  data = []
+  data = [ "//testing/buildbot/filters/android.nougat-x86-rel.chrome_junit_tests.filter" ]
 }
 
 source_set("chrome_public_test_apk_filters") {
diff --git a/testing/buildbot/filters/android.nougat-x86-rel.chrome_junit_tests.filter b/testing/buildbot/filters/android.nougat-x86-rel.chrome_junit_tests.filter
new file mode 100644
index 0000000..c4215eb6
--- /dev/null
+++ b/testing/buildbot/filters/android.nougat-x86-rel.chrome_junit_tests.filter
@@ -0,0 +1,6 @@
+# crbug.com/1378315
+-org.chromium.chrome.browser.subscriptions.ImplicitPriceDropSubscriptionsManagerUnitTest*
+
+# crbug.com/1378779
+-org.chromium.chrome.browser.share.link_to_text.LinkToTextHelperTest.hasTextFragment
+-org.chromium.chrome.browser.share.link_to_text.LinkToTextHelperTest.hasTextFragment_URLWithNoTextSelector
diff --git a/testing/buildbot/filters/mac.mac12-arm64-rel.browser_tests.filter b/testing/buildbot/filters/mac.mac12-arm64-rel.browser_tests.filter
index fce0a18..573b2b37 100644
--- a/testing/buildbot/filters/mac.mac12-arm64-rel.browser_tests.filter
+++ b/testing/buildbot/filters/mac.mac12-arm64-rel.browser_tests.filter
@@ -34,9 +34,6 @@
 -All/SearchPrefetchServiceEnabledBrowserTest.RemovingMatchCancelsInFlight/1
 -All/PromotionalTabsEnabledPolicyWhatsNewTest.RunTest/0
 -All/PromotionalTabsEnabledPolicyWhatsNewTest.RunTest/2
--AccessCodeCastHandlerBrowserTest.ExpectGenericErrorWhenNoSync
--AccessCodeCastHandlerBrowserTest.ExpectNetworkErrorWhenNoNetwork
--AccessCodeCastHandlerBrowserTest.ReturnSuccessfulResponse
 -AmbientAuthenticationTestWithPolicy.GuestAndRegular
 -AmbientAuthenticationTestWithPolicy.IncognitoAndRegular
 -AmbientAuthenticationTestWithPolicy.RegularOnly
diff --git a/testing/buildbot/gn_isolate_map.pyl b/testing/buildbot/gn_isolate_map.pyl
index 5894b18a..ab57f9d 100644
--- a/testing/buildbot/gn_isolate_map.pyl
+++ b/testing/buildbot/gn_isolate_map.pyl
@@ -447,7 +447,7 @@
   },
   "chrome_junit_tests": {
     "label": "//chrome/android:chrome_junit_tests",
-    "type": "junit_test",
+    "type": "generated_script",
   },
   "chrome_modern_public_bundle_smoke_test": {
     "label": "//chrome/android:chrome_modern_public_bundle_smoke_test",
diff --git a/testing/buildbot/mixins.pyl b/testing/buildbot/mixins.pyl
index 619c712..0c950a1f 100644
--- a/testing/buildbot/mixins.pyl
+++ b/testing/buildbot/mixins.pyl
@@ -360,8 +360,9 @@
   },
   'chromeos-kevin-skylab': {
     'cros_board': 'kevin',
-    'cros_img': 'kevin-public/R109-15237.0.0',
+    'cros_img': 'kevin-public/R110-15271.0.0',
     'bucket': 'chromiumos-image-archive',
+    'timeout_sec': 10800,
   },
   'chromeos-octopus': {
     'swarming': {
@@ -628,6 +629,14 @@
   'isolate_profile_data': {
     'isolate_profile_data': True,
   },
+  'junit-swarming': {
+    'swarming': {
+      'dimensions': {
+        'cores': '8',
+        'pool': 'chromium.tests',
+      },
+    },
+  },
   'kitkat-x86-emulator': {
     '$mixin_append': {
       'args': [
diff --git a/testing/buildbot/test_suite_exceptions.pyl b/testing/buildbot/test_suite_exceptions.pyl
index be8b1da..74243bf 100644
--- a/testing/buildbot/test_suite_exceptions.pyl
+++ b/testing/buildbot/test_suite_exceptions.pyl
@@ -2550,6 +2550,13 @@
       },
     },
   },
+  'mac_signing_tests': {
+    'remove_from': [
+      # This is intentional and explained in the waterfalls.pyl comment for
+      # this builder.
+      'mac-rel-cft',
+    ],
+  },
   'maps_pixel_passthrough_test': {
     'modifications': {
       'Android FYI Release (Pixel 4)': {
@@ -4024,6 +4031,12 @@
           'Release_x64',
         ],
       },
+      'win11-wpt-content-shell-fyi-rel': {
+        'args': [
+          '--target',
+          'Release_x64',
+        ],
+      },
     },
   },
 }
diff --git a/testing/buildbot/test_suites.pyl b/testing/buildbot/test_suites.pyl
index 708e62d..a53897a 100644
--- a/testing/buildbot/test_suites.pyl
+++ b/testing/buildbot/test_suites.pyl
@@ -993,7 +993,7 @@
     'chromium_gtests_for_win_and_linux_only': {
     },
 
-    'chromium_junit_tests': {
+    'chromium_junit_tests_no_swarming': {
       'android_webview_junit_tests': {
         'remove_mixins': [
           'nougat-x86-emulator',
@@ -1106,6 +1106,133 @@
       },
     },
 
+    'chromium_junit_tests_scripts': {
+      'chrome_junit_tests': {
+        'args': [
+          '--test-launcher-filter-file=../../testing/buildbot/filters/android.nougat-x86-rel.chrome_junit_tests.filter',
+        ],
+        'mixins': [
+          'x86-64',
+          'linux-bionic',
+          'junit-swarming',
+        ],
+        'remove_mixins': [
+          'emulator-4-cores',
+          'nougat-x86-emulator',
+        ],
+        'use_isolated_scripts_api': True,
+      },
+    },
+
+    # TODO(crbug.com/1320434): remove this once all junit tests are migrated
+    'chromium_junit_tests_some_swarming': {
+      'android_webview_junit_tests': {
+        'remove_mixins': [
+          'nougat-x86-emulator',
+        ],
+      },
+      'base_junit_tests': {
+        'remove_mixins': [
+          'nougat-x86-emulator',
+        ],
+      },
+      'build_junit_tests':{
+        'remove_mixins': [
+          'nougat-x86-emulator',
+        ],
+      },
+      'chrome_java_test_pagecontroller_junit_tests': {
+        'remove_mixins': [
+          'nougat-x86-emulator',
+        ],
+      },
+      'components_junit_tests': {
+        'remove_mixins': [
+          'nougat-x86-emulator',
+        ],
+      },
+      'content_junit_tests': {
+        'remove_mixins': [
+          'nougat-x86-emulator',
+        ],
+      },
+      'device_junit_tests': {
+        'remove_mixins': [
+          'nougat-x86-emulator',
+        ],
+      },
+      'junit_unit_tests': {
+        'remove_mixins': [
+          'nougat-x86-emulator',
+        ],
+      },
+      'keyboard_accessory_junit_tests': {
+        'remove_mixins': [
+          'nougat-x86-emulator',
+        ],
+      },
+      'media_base_junit_tests': {
+        'remove_mixins': [
+          'nougat-x86-emulator',
+        ],
+      },
+      'module_installer_junit_tests': {
+        'remove_mixins': [
+          'nougat-x86-emulator',
+        ],
+      },
+      'net_junit_tests': {
+        'remove_mixins': [
+          'nougat-x86-emulator',
+        ],
+      },
+      'paint_preview_junit_tests': {
+        'remove_mixins': [
+          'nougat-x86-emulator',
+        ],
+      },
+      'password_check_junit_tests': {
+        'remove_mixins': [
+          'nougat-x86-emulator',
+        ],
+      },
+      'password_manager_junit_tests': {
+        'remove_mixins': [
+          'nougat-x86-emulator',
+        ],
+      },
+      'services_junit_tests': {
+        'remove_mixins': [
+          'nougat-x86-emulator',
+        ],
+      },
+      'touch_to_fill_junit_tests': {
+        'remove_mixins': [
+          'nougat-x86-emulator',
+        ],
+      },
+      'ui_junit_tests': {
+        'remove_mixins': [
+          'nougat-x86-emulator',
+        ],
+      },
+      'webapk_client_junit_tests': {
+        'remove_mixins': [
+          'nougat-x86-emulator',
+        ],
+      },
+      'webapk_shell_apk_h2o_junit_tests': {
+        'remove_mixins': [
+          'nougat-x86-emulator',
+        ],
+      },
+      'webapk_shell_apk_junit_tests': {
+        'remove_mixins': [
+          'nougat-x86-emulator',
+        ],
+      },
+    },
+
     'chromium_linux_scripts': {
       'check_network_annotations': {
         'script': 'check_network_annotations.py',
@@ -6357,6 +6484,7 @@
 
     'nougat_isolated_scripts': [
       'android_isolated_scripts',
+      'chromium_junit_tests_scripts',
       'components_perftests_isolated_scripts',
       'monochrome_public_apk_checker_isolated_script',
       'telemetry_android_minidump_unittests_isolated_scripts',
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl
index 78387735..2b377b8a 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_v110.0.5465.0/test_ash_chrome',
+      '--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5467.0/test_ash_chrome',
     ],
-    'description': 'Run with ash-chrome version 110.0.5465.0',
+    'description': 'Run with ash-chrome version 110.0.5467.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_v110.0.5465.0',
-          'revision': 'version:110.0.5465.0',
+          'location': 'lacros_version_skew_tests_v110.0.5467.0',
+          'revision': 'version:110.0.5467.0',
         },
       ],
     },
diff --git a/testing/buildbot/waterfalls.pyl b/testing/buildbot/waterfalls.pyl
index 70eb1497..1fab9c5 100644
--- a/testing/buildbot/waterfalls.pyl
+++ b/testing/buildbot/waterfalls.pyl
@@ -575,7 +575,7 @@
           'microdump_stackwalk',
         ],
         'test_suites': {
-          'junit_tests': 'chromium_junit_tests',
+          'junit_tests': 'chromium_junit_tests_no_swarming',
         },
       },
       'Android arm64 Builder (dbg)': {
@@ -715,7 +715,7 @@
         ],
         'test_suites': {
           'gtest_tests': 'android_marshmallow_gtests',
-          'junit_tests': 'chromium_junit_tests',
+          'junit_tests': 'chromium_junit_tests_no_swarming',
           'isolated_scripts': 'monochrome_public_apk_checker_isolated_script',
         },
         'os_type': 'android',
@@ -943,7 +943,7 @@
         'os_type': 'android',
         'test_suites': {
           'gtest_tests': 'android_nougat_emulator_gtests',
-          'junit_tests': 'chromium_junit_tests',
+          'junit_tests': 'chromium_junit_tests_some_swarming',
           'isolated_scripts': 'nougat_isolated_scripts',
           'scripts': 'chromium_android_scripts',
         },
@@ -1257,6 +1257,30 @@
     },
   },
   {
+    # CfT == Chrome for Testing
+    'name': 'chromium.cft',
+    'mixins': ['chromium-tester-service-account'],
+    'machines': {
+      # mac-rel-cft is the CfT mirror of mac-rel. It is intended to
+      # exactly match mac-rel except that we don't build the installers
+      # and don't need any of the installer tests. test_suite_exceptions.pyl
+      # contains the exceptions for the installer tests.
+      'mac-rel-cft': {
+        'mixins': [
+          'mac_12_x64',
+          'isolate_profile_data',
+        ],
+        'additional_compile_targets': [
+          'all',
+        ],
+        'test_suites': {
+          'gtest_tests': 'chromium_mac_gtests_no_nacl',
+          'isolated_scripts': 'chromium_mac_rel_isolated_scripts',
+        },
+      },
+    },
+  },
+  {
     'name': 'chromium.chromiumos',
     'mixins': ['chromium-tester-service-account'],
     'machines': {
@@ -1508,7 +1532,7 @@
         # This mirrors 'android-lollipop-arm-rel'.
         'test_suites': {
           'gtest_tests': 'chromium_android_gtests',
-          'junit_tests': 'chromium_junit_tests',
+          'junit_tests': 'chromium_junit_tests_no_swarming',
           'isolated_scripts': 'monochrome_public_apk_checker_isolated_script_and_sizes',
         },
         'os_type': 'android',
@@ -2938,7 +2962,7 @@
         ],
         'test_suites': {
           'gtest_tests': 'android_pie_coverage_gtests',
-          'junit_tests': 'chromium_junit_tests',
+          'junit_tests': 'chromium_junit_tests_no_swarming',
         },
         'os_type': 'android',
       },
diff --git a/testing/scripts/run_performance_tests.py b/testing/scripts/run_performance_tests.py
index f67cd60..b7905ac8 100755
--- a/testing/scripts/run_performance_tests.py
+++ b/testing/scripts/run_performance_tests.py
@@ -560,10 +560,9 @@
   if return_code in (111, -1, 255):
     print('Exit code %s indicates that no stories were run, so we are marking '
           'this as a success.' % return_code)
-  if return_code == 1:
-    print ('run_benchmark returned exit code 1 which indicates there were '
-           'test failures in the run.')
-
+    return 0
+  if return_code:
+    return return_code
   return 0
 
 def parse_arguments(args):
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 8844dd4..19e7ddec 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -1609,12 +1609,10 @@
             ],
             "experiments": [
                 {
-                    "name": "EnabledPolicy_CacheSize6_ModerateCacheSize4-2_20220921",
+                    "name": "EnabledPolicy_CacheSize6_20220921",
                     "params": {
-                        "background_cache_size_on_moderate_pressure": "2",
                         "cache_size": "6",
-                        "foreground_cache_size": "0",
-                        "foreground_cache_size_on_moderate_pressure": "4"
+                        "foreground_cache_size": "0"
                     },
                     "enable_features": [
                         "BFCachePerformanceManagerPolicy",
@@ -3445,6 +3443,28 @@
             ]
         }
     ],
+    "DIPSPersistedDatabase": [
+        {
+            "platforms": [
+                "android",
+                "chromeos",
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "params": {
+                        "persist_database": "true"
+                    },
+                    "enable_features": [
+                        "DIPS"
+                    ]
+                }
+            ]
+        }
+    ],
     "DefaultOfflineExperience": [
         {
             "platforms": [
@@ -4403,6 +4423,21 @@
             ]
         }
     ],
+    "EnableKidsManagementService": [
+        {
+            "platforms": [
+                "android"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "EnableKidsManagementService"
+                    ]
+                }
+            ]
+        }
+    ],
     "EnablePDPMetricsUSDesktopIOS": [
         {
             "platforms": [
@@ -5723,6 +5758,21 @@
             ]
         }
     ],
+    "IOSSFSymbols": [
+        {
+            "platforms": [
+                "ios"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "UseSFSymbols"
+                    ]
+                }
+            ]
+        }
+    ],
     "IOSShareChrome": [
         {
             "platforms": [
@@ -6117,6 +6167,26 @@
             ]
         }
     ],
+    "JourneysNonSupportedLocales": [
+        {
+            "platforms": [
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled_20221206",
+                    "params": {
+                        "JourneysLocaleOrLanguageAllowlist": "*"
+                    },
+                    "enable_features": [
+                        "Journeys"
+                    ]
+                }
+            ]
+        }
+    ],
     "JourneysPersistence": [
         {
             "platforms": [
@@ -6856,10 +6926,10 @@
             ],
             "experiments": [
                 {
-                    "name": "NoBackground-ForYou2",
+                    "name": "NoBackground-ForYou3",
                     "params": {
                         "new-user-modules": "nux-google-apps,nux-set-as-default,signin-view",
-                        "onboarding-group": "NoBackgroundSynthetic-ForYou2"
+                        "onboarding-group": "NoBackgroundSynthetic-ForYou3"
                     },
                     "enable_features": [
                         "NuxOnboarding"
@@ -10124,8 +10194,7 @@
                 {
                     "name": "Enabled",
                     "enable_features": [
-                        "DesktopScreenshots",
-                        "SharingDesktopScreenshotsEdit"
+                        "DesktopScreenshots"
                     ]
                 }
             ]
@@ -10652,26 +10721,6 @@
             ]
         }
     ],
-    "SyncAndroidPromosRevamp": [
-        {
-            "platforms": [
-                "android"
-            ],
-            "experiments": [
-                {
-                    "name": "EnabledWithIllustrationAndTitle_3",
-                    "enable_features": [
-                        "SyncAndroidPromosWithIllustration",
-                        "SyncAndroidPromosWithTitle"
-                    ],
-                    "disable_features": [
-                        "SyncAndroidPromosWithAlternativeTitle",
-                        "SyncAndroidPromosWithSingleButton"
-                    ]
-                }
-            ]
-        }
-    ],
     "SyncPromoAfterSigninInterceptStudy": [
         {
             "platforms": [
diff --git a/third_party/.gitignore b/third_party/.gitignore
index a5200479..e571aef 100644
--- a/third_party/.gitignore
+++ b/third_party/.gitignore
@@ -28,24 +28,24 @@
 /androidx_javascriptengine/src/
 /angle
 /angle_dx11
-/apache-mac/LICENSE
-/apache-mac/bin/*
-/apache-mac/lib/*
-/apache-mac/libexec/*
-/apache-mac-arm64/LICENSE
-/apache-mac-arm64/bin/*
-/apache-mac-arm64/lib/*
-/apache-mac-arm64/libexec/*
 /apache-linux/LICENSE
 /apache-linux/bin/*
 /apache-linux/lib/*
 /apache-linux/libexec/*
+/apache-mac-arm64/LICENSE
+/apache-mac-arm64/bin/*
+/apache-mac-arm64/lib/*
+/apache-mac-arm64/libexec/*
+/apache-mac/LICENSE
+/apache-mac/bin/*
+/apache-mac/lib/*
+/apache-mac/libexec/*
 /apache-portable-runtime/src
-/apache-win32/bin/*.exe
 /apache-win32/bin/*.dll
+/apache-win32/bin/*.exe
 /apache-win32/bin/iconv/*.so
-/apache-win32/modules/*.so
 /apache-win32/modules/*.dll
+/apache-win32/modules/*.so
 /arcore-android-sdk/src/
 /arcore-android-sdk/test-apks/arcore/*.apk
 /asan
@@ -81,14 +81,14 @@
 /dav1d/libdav1d
 /dawn
 /depot_tools
-/devtools-frontend/src
 /devtools-frontend-internal
+/devtools-frontend/src
 /directxsdk
 /dom_distiller_js/dist
 /eigen3/src
 /elfutils/src
-/emoji-segmenter/src
 /emoji-metadata/src
+/emoji-segmenter/src
 /espresso/lib/
 /expat/src
 /eyesfree/src
@@ -109,20 +109,20 @@
 /glfw/src
 /gn/
 /gnu_binutils/
+/google-truth/lib/
 /google_benchmark/src
 /google_toolbox_for_mac/src
 /googlemac
+/gperf
 /grpc/src
+/guava/lib/
 /gvr-android-sdk/common_library.aar
-/gvr-android-sdk/test-libraries/controller_test_api.aar
 /gvr-android-sdk/libgvr_shim_static_*.a
 /gvr-android-sdk/src
 /gvr-android-sdk/test-apks/daydream_home/*.apk
-/gvr-android-sdk/test-apks/vr_services/*.apk
 /gvr-android-sdk/test-apks/vr_keyboard/*.apk
-/gperf
-/google-truth/lib/
-/guava/lib/
+/gvr-android-sdk/test-apks/vr_services/*.apk
+/gvr-android-sdk/test-libraries/controller_test_api.aar
 /hamcrest/lib/
 /harfbuzz-ng/src
 /highway/src
@@ -131,36 +131,38 @@
 /icu4j/lib/
 /instrumented_libraries/scripts/*.tgz
 /instrumented_libraries/scripts/out/*
-/javax_inject/lib/
 /jacoco/lib/
 /javalang/src/
+/javax_inject/lib/
 /jdk/current
 /jdk/extras/java_8
+/js_code_coverage/*.tar.gz
+/js_code_coverage/node_modules
 /jsr-305/src
 /junit/src
 /khronos_glcts
 /leveldatabase/src
 /leveldb
-/libaom/source/libaom
+/libFuzzer/src
 /libaddressinput/src
+/libaom/source/libaom
 /libassistant
 /libavif/src
 /libc++-static/libc++.a
 /libdrm/src
-/libFuzzer/src
 /libgav1/src
 /libgifcodec
-/libprotobuf-mutator/src
 /libipp/libipp
 /libjingle/source
 /libjpeg_turbo
 /libjxl/src
 /liblouis/src
 /libphonenumber/dist
+/libprotobuf-mutator/src
 /libsrtp
 /libsync/src
-/libupnp
 /libunwindstack
+/libupnp
 /libvpx/source/libvpx
 /libwebm/source
 /libwebp/src
@@ -181,8 +183,6 @@
 /minizip/src
 /mkl
 /mocha
-/js_code_coverage/*.tar.gz
-/js_code_coverage/node_modules
 /mockito/src
 /nacl_sdk_binaries/
 /nasm
@@ -190,7 +190,7 @@
 /neon_2_sse/src
 /netty-tcnative/src
 /netty4/src
-/ninja
+/ninja/ninja*
 /node/*.tar.gz
 /node/linux
 /node/mac
@@ -203,8 +203,8 @@
 /openscreen/src
 /openxr/src
 /ots/src
-/pdfsqueeze
 /pdfium
+/pdfsqueeze
 /pefile
 /perfetto
 /perl
@@ -225,8 +225,8 @@
 /qemu-linux-x64
 /qemu-mac-x64
 /quic_trace/src
-/r8/lib
 /r8/d8/lib
+/r8/lib
 /re2/src
 /requests/src
 /robolectric/lib/
@@ -250,8 +250,8 @@
 /sqlite4java/lib/
 /subresource-filter-ruleset/data/UnindexedRules
 /swift-format
-/swiftshader/
 /swift-toolchain
+/swiftshader/
 /syzygy
 /syzygy/binaries
 /text-fragments-polyfill/src
@@ -265,10 +265,10 @@
 /valgrind
 /vulkan-deps
 /vulkan_memory_allocator
-/wayland/src
 /wayland-protocols/gtk
 /wayland-protocols/kde
 /wayland-protocols/src
+/wayland/src
 /webdriver/pylib
 /webdriver/python/selenium
 /webgl
diff --git a/third_party/android_deps/buildSrc/src/main/groovy/BuildConfigGenerator.groovy b/third_party/android_deps/buildSrc/src/main/groovy/BuildConfigGenerator.groovy
index 8d37959..81f0fa7 100644
--- a/third_party/android_deps/buildSrc/src/main/groovy/BuildConfigGenerator.groovy
+++ b/third_party/android_deps/buildSrc/src/main/groovy/BuildConfigGenerator.groovy
@@ -625,7 +625,9 @@
         }
 
         switch (dependencyId) {
-            case 'androidx_annotation_annotation':
+            case 'androidx_annotation_annotation_jvm':
+                sb.append('  # https://crbug.com/989505\n')
+                sb.append('  jar_excluded_patterns = ["META-INF/proguard/*"]\n')
                 break
             case 'androidx_core_core':
                 sb.with {
@@ -712,10 +714,6 @@
                     append('  ]\n')
                 }
                 break
-            case 'com_android_support_support_annotations':
-                sb.append('  # https://crbug.com/989505\n')
-                sb.append('  jar_excluded_patterns = ["META-INF/proguard/*"]\n')
-                break
             case 'com_android_support_support_compat':
                 sb.append('\n')
                 sb.append('  # Target has AIDL, but we do not support it yet: http://crbug.com/644439\n')
diff --git a/third_party/blink/common/features.cc b/third_party/blink/common/features.cc
index da1edcb7..c176cf5 100644
--- a/third_party/blink/common/features.cc
+++ b/third_party/blink/common/features.cc
@@ -1666,5 +1666,13 @@
                  kThrottleDisplayNoneAndVisibilityHiddenCrossOriginIframes);
 }
 
+BASE_FEATURE(kSpeculationRulesHeaderEnableThirdPartyOriginTrial,
+             "SpeculationRulesHeaderEnableThirdPartyOriginTrial",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+
+BASE_FEATURE(kSpeculationRulesPrefetchFuture,
+             "SpeculationRulesPrefetchFuture",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+
 }  // namespace features
 }  // namespace blink
diff --git a/third_party/blink/common/messaging/string_message_codec.cc b/third_party/blink/common/messaging/string_message_codec.cc
index f18b0c29..249b797 100644
--- a/third_party/blink/common/messaging/string_message_codec.cc
+++ b/third_party/blink/common/messaging/string_message_codec.cc
@@ -147,6 +147,12 @@
 
 // static
 std::unique_ptr<WebMessageArrayBufferPayload>
+WebMessageArrayBufferPayload::CreateFromBigBuffer(mojo_base::BigBuffer buffer) {
+  return std::make_unique<BigBufferArrayBuffer>(std::move(buffer));
+}
+
+// static
+std::unique_ptr<WebMessageArrayBufferPayload>
 WebMessageArrayBufferPayload::CreateForTesting(std::vector<uint8_t> data) {
   auto size = data.size();
   return std::make_unique<VectorArrayBuffer>(std::move(data), 0, size);
diff --git a/third_party/blink/common/navigation/navigation_policy.cc b/third_party/blink/common/navigation/navigation_policy.cc
index 42c29d43..fd67476 100644
--- a/third_party/blink/common/navigation/navigation_policy.cc
+++ b/third_party/blink/common/navigation/navigation_policy.cc
@@ -120,4 +120,17 @@
   }
 }
 
+blink::mojom::NavigationInitiatorActivationAndAdStatus
+GetNavigationInitiatorActivationAndAdStatus(bool has_user_activation,
+                                            bool is_ad_script_in_stack) {
+  return has_user_activation
+             ? (is_ad_script_in_stack
+                    ? blink::mojom::NavigationInitiatorActivationAndAdStatus::
+                          kStartedWithTransientActivationFromAd
+                    : blink::mojom::NavigationInitiatorActivationAndAdStatus::
+                          kStartedWithTransientActivationFromNonAd)
+             : blink::mojom::NavigationInitiatorActivationAndAdStatus::
+                   kDidNotStartWithTransientActivation;
+}
+
 }  // namespace blink
diff --git a/third_party/blink/public/common/attribution_reporting/mojom_traits.h b/third_party/blink/public/common/attribution_reporting/mojom_traits.h
index ab3d39f..53ff06d2 100644
--- a/third_party/blink/public/common/attribution_reporting/mojom_traits.h
+++ b/third_party/blink/public/common/attribution_reporting/mojom_traits.h
@@ -191,11 +191,6 @@
     return source.destination;
   }
 
-  static const attribution_reporting::SuitableOrigin& reporting_origin(
-      const attribution_reporting::SourceRegistration& source) {
-    return source.reporting_origin;
-  }
-
   static uint64_t source_event_id(
       const attribution_reporting::SourceRegistration& source) {
     return source.source_event_id;
@@ -247,9 +242,6 @@
     if (!data.ReadDestination(&out->destination))
       return false;
 
-    if (!data.ReadReportingOrigin(&out->reporting_origin))
-      return false;
-
     if (!data.ReadExpiry(&out->expiry))
       return false;
 
diff --git a/third_party/blink/public/common/features.h b/third_party/blink/public/common/features.h
index 2762fb9..b5fc94715 100644
--- a/third_party/blink/public/common/features.h
+++ b/third_party/blink/public/common/features.h
@@ -958,6 +958,15 @@
 BLINK_COMMON_EXPORT bool
 IsThrottleDisplayNoneAndVisibilityHiddenCrossOriginIframesEnabled();
 
+// Allows certain origin trials to be enabled using third-party tokens
+// associated with the origin of external speculation rules.
+BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(
+    kSpeculationRulesHeaderEnableThirdPartyOriginTrial);
+
+// Controls whether the SpeculationRulesPrefetchFuture origin trial can be
+// enabled.
+BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kSpeculationRulesPrefetchFuture);
+
 }  // namespace features
 }  // namespace blink
 
diff --git a/third_party/blink/public/common/messaging/string_message_codec.h b/third_party/blink/public/common/messaging/string_message_codec.h
index ed438f58e..07abce9 100644
--- a/third_party/blink/public/common/messaging/string_message_codec.h
+++ b/third_party/blink/public/common/messaging/string_message_codec.h
@@ -14,6 +14,10 @@
 #include "third_party/blink/public/common/common_export.h"
 #include "third_party/blink/public/common/messaging/transferable_message.h"
 
+namespace mojo_base {
+class BigBuffer;
+}
+
 namespace blink {
 
 // A interface represents ArrayBuffer payload type in WebMessage.
@@ -36,6 +40,10 @@
   // regarding various backing stores.
   virtual void CopyInto(base::span<uint8_t> dest) const = 0;
 
+  // Create a new WebMessageArrayBufferPayload from BigBuffer.
+  static std::unique_ptr<WebMessageArrayBufferPayload> CreateFromBigBuffer(
+      mojo_base::BigBuffer buffer);
+
   // Create a new WebMessageArrayBufferPayload from vector for testing.
   static std::unique_ptr<WebMessageArrayBufferPayload> CreateForTesting(
       std::vector<uint8_t> data);
diff --git a/third_party/blink/public/common/navigation/navigation_policy.h b/third_party/blink/public/common/navigation/navigation_policy.h
index 76b08b5..a94c52c 100644
--- a/third_party/blink/public/common/navigation/navigation_policy.h
+++ b/third_party/blink/public/common/navigation/navigation_policy.h
@@ -9,6 +9,7 @@
 
 #include "third_party/blink/public/common/common_export.h"
 #include "third_party/blink/public/common/navigation/resource_intercept_policy.h"
+#include "third_party/blink/public/mojom/navigation/navigation_initiator_activation_and_ad_status.mojom.h"
 
 // A centralized file for base helper methods and policy decisions about
 // navigations.
@@ -97,6 +98,13 @@
   NavigationDownloadTypes disallowed_types;
 };
 
+// Construct a `NavigationInitiatorActivationAndAdStatus` based on the user
+// activation and ad status.
+BLINK_COMMON_EXPORT
+blink::mojom::NavigationInitiatorActivationAndAdStatus
+GetNavigationInitiatorActivationAndAdStatus(bool has_user_activation,
+                                            bool is_ad_script_in_stack);
+
 }  // namespace blink
 
 #endif  // THIRD_PARTY_BLINK_PUBLIC_COMMON_NAVIGATION_NAVIGATION_POLICY_H_
diff --git a/third_party/blink/public/devtools_protocol/browser_protocol.pdl b/third_party/blink/public/devtools_protocol/browser_protocol.pdl
index 52eea12b..f3e4159 100644
--- a/third_party/blink/public/devtools_protocol/browser_protocol.pdl
+++ b/third_party/blink/public/devtools_protocol/browser_protocol.pdl
@@ -7220,6 +7220,7 @@
       serial
       shared-autofill
       shared-storage
+      smart-card
       storage-access
       sync-xhr
       trust-token-redemption
@@ -9569,6 +9570,13 @@
       # supported.
       string commandLine
 
+  # Returns information about the feature state.
+  command getFeatureState
+    parameters
+      string featureState
+    returns
+      boolean featureEnabled
+
   # Returns information about all running processes.
   command getProcessInfo
     returns
diff --git a/third_party/blink/public/mojom/BUILD.gn b/third_party/blink/public/mojom/BUILD.gn
index 3900e83..b5751eac 100644
--- a/third_party/blink/public/mojom/BUILD.gn
+++ b/third_party/blink/public/mojom/BUILD.gn
@@ -138,6 +138,7 @@
     "mime/mime_registry.mojom",
     "native_io/native_io.mojom",
     "navigation/navigation_api_history_entry_arrays.mojom",
+    "navigation/navigation_initiator_activation_and_ad_status.mojom",
     "navigation/navigation_policy.mojom",
     "navigation/prefetched_signed_exchange_info.mojom",
     "navigation/renderer_eviction_reason.mojom",
@@ -549,6 +550,10 @@
         {
           mojom = "blink.mojom.AttributionSuitableOrigin"
           cpp = "::attribution_reporting::SuitableOrigin"
+
+          # Avoid expensive copies by forcing Mojo methods to take the type by
+          # value, not const ref
+          move_only = true
         },
         {
           mojom = "blink.mojom.AttributionFilterData"
diff --git a/third_party/blink/public/mojom/conversions/attribution_data_host.mojom b/third_party/blink/public/mojom/conversions/attribution_data_host.mojom
index fe5b986..b6dd07a 100644
--- a/third_party/blink/public/mojom/conversions/attribution_data_host.mojom
+++ b/third_party/blink/public/mojom/conversions/attribution_data_host.mojom
@@ -60,9 +60,6 @@
   // same-site, this source will be ignored by the browser.
   AttributionSuitableOrigin destination;
 
-  // Origin that will receive all attribution reports associated with this source.
-  AttributionSuitableOrigin reporting_origin;
-
   // Data that will be sent in attribution reports to identify this source.
   uint64 source_event_id = 0;
 
@@ -192,7 +189,8 @@
 interface AttributionDataHost {
   // Called when data from the renderer is available for a given attributionsrc
   // request.
-  SourceDataAvailable(AttributionSourceData data);
+  SourceDataAvailable(AttributionSuitableOrigin reporting_origin,
+                      AttributionSourceData data);
 
   // Called when trigger data from the renderer is available for a given
   // attributionsrc request.
diff --git a/third_party/blink/public/mojom/frame/remote_frame.mojom b/third_party/blink/public/mojom/frame/remote_frame.mojom
index 05e342cd..c66112a 100644
--- a/third_party/blink/public/mojom/frame/remote_frame.mojom
+++ b/third_party/blink/public/mojom/frame/remote_frame.mojom
@@ -16,6 +16,7 @@
 import "third_party/blink/public/mojom/conversions/conversions.mojom";
 import "third_party/blink/public/mojom/frame/frame_owner_properties.mojom";
 import "third_party/blink/public/mojom/frame/frame_policy.mojom";
+import "third_party/blink/public/mojom/navigation/navigation_initiator_activation_and_ad_status.mojom";
 import "third_party/blink/public/mojom/frame/frame_replication_state.mojom";
 import "third_party/blink/public/mojom/frame/frame_visual_properties.mojom";
 import "third_party/blink/public/mojom/frame/fullscreen.mojom";
@@ -114,6 +115,10 @@
   // mojo::NullRemote here.
   pending_remote<blink.mojom.PolicyContainerHostKeepAliveHandle>?
       initiator_policy_container_keep_alive_handle;
+
+  // The navigation initiator's user activation and ad status.
+  blink.mojom.NavigationInitiatorActivationAndAdStatus
+      initiator_activation_and_ad_status;
 };
 
 // Implemented in Browser, this interface defines frame-specific methods that
diff --git a/third_party/blink/public/mojom/mediastream/media_devices.mojom b/third_party/blink/public/mojom/mediastream/media_devices.mojom
index 493299d..27a7d23 100644
--- a/third_party/blink/public/mojom/mediastream/media_devices.mojom
+++ b/third_party/blink/public/mojom/mediastream/media_devices.mojom
@@ -49,37 +49,28 @@
   mojo_base.mojom.TimeDelta latency;
 };
 
-// Response to an EnumerateDevices request.
-// It the request failed, the result_code contains a specific value other than
-// kSuccess and the other fields aren't set.
-// If result_code is kSuccess, the |enumeration| array always
-// has NUM_MEDIA_DEVICE_TYPES elements indexed by device type as defined in
-// MediaDeviceType. Each element of |enumeration| is an array with as many
-// elements as devices of the corresponding type exist in the system, or zero
-// if the device types was not requested. Similarly, the number of elements in
-// |audio_input_device_capabilities| and |video_input_device_capabilities| is
-// equal to respectively the number of video and audio input devices in the
-// system, or zero if not requested.
-struct EnumerationResponse {
-  media.mojom.DeviceEnumerationResult result_code;
-
-  // The following are non-empty only if result_code is kSuccess.
-  array<array<MediaDeviceInfo>> enumeration;
-  array<VideoInputDeviceCapabilities> video_input_device_capabilities;
-  array<AudioInputDeviceCapabilities> audio_input_device_capabilities;
-};
-
 // This object lives in the browser and is responsible for processing device
 // enumeration requests and managing subscriptions for device-change
 // notifications.
 interface MediaDevicesDispatcherHost {
-  // Enumerates media devices and capabilities.
+  // Enumerates media devices and capabilities. The reply contains the
+  // |enumeration|, |video_input_device_capabilities|, and
+  // |audio_input_device_capabilities| arrays. The |enumeration| array always
+  // has NUM_MEDIA_DEVICE_TYPES elements indexed by device type as defined in
+  // MediaDeviceType. Each element of |enumeration| is an array with as many
+  // elements as devices of the corresponding type exist in the system, or zero
+  // if the device types was not requested. Similarly, the number of elements in
+  // |audio_input_device_capabilities| and |video_input_device_capabilities| is
+  // equal to respectively the number of video and audio input devices in the
+  // system, or zero if not requested.
   EnumerateDevices(bool request_audio_input,
                    bool request_video_input,
                    bool request_audio_output,
                    bool request_video_input_capabilities,
                    bool request_audio_input_capabilities)
-      => (EnumerationResponse response);
+      => (array<array<MediaDeviceInfo>> enumeration,
+          array<VideoInputDeviceCapabilities> video_input_device_capabilities,
+          array<AudioInputDeviceCapabilities> audio_input_device_capabilities);
 
   // Returns a list of video devices and their capabilities.
   // If there is a user-preferred device, it is the first in the result.
diff --git a/third_party/blink/public/mojom/navigation/navigation_initiator_activation_and_ad_status.mojom b/third_party/blink/public/mojom/navigation/navigation_initiator_activation_and_ad_status.mojom
new file mode 100644
index 0000000..a0474da
--- /dev/null
+++ b/third_party/blink/public/mojom/navigation/navigation_initiator_activation_and_ad_status.mojom
@@ -0,0 +1,28 @@
+// 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.
+
+module blink.mojom;
+
+// Provided by the renderer ----------------------------------------------------
+//
+// This enum represents the navigation initiator's user activation and ad
+// status. This is used by FrameHost::CreateNewWindow
+// FrameHost::BeginNavigation, FrameHost::OpenURL and RemoteFrameHost::OpenURL.
+// This enum will be propagated to the NavigationHandle without being filtered,
+// and should not be propagated to the committed document in the renderer (in
+// contrast to CommonNavigationParams::has_user_gesture, which is propagated to
+// the committed document in the renderer but may have been filtered out when
+// navigating from proxies).
+enum NavigationInitiatorActivationAndAdStatus {
+  // The navigation did not start with a transient user activation.
+  kDidNotStartWithTransientActivation,
+
+  // The navigation started with a transient user activation, and is not related
+  // to ad.
+  kStartedWithTransientActivationFromNonAd,
+
+  // The navigation started with a transient user activation, and is related to
+  // ad (i.e. from an ad iframe or has ad script in stack).
+  kStartedWithTransientActivationFromAd,
+};
diff --git a/third_party/blink/public/mojom/navigation/navigation_params.mojom b/third_party/blink/public/mojom/navigation/navigation_params.mojom
index ff1d968..85a934b1 100644
--- a/third_party/blink/public/mojom/navigation/navigation_params.mojom
+++ b/third_party/blink/public/mojom/navigation/navigation_params.mojom
@@ -18,6 +18,7 @@
 import "services/network/public/mojom/web_sandbox_flags.mojom";
 import "third_party/blink/public/mojom/back_forward_cache_not_restored_reasons.mojom";
 import "third_party/blink/public/mojom/fetch/fetch_api_request.mojom";
+import "third_party/blink/public/mojom/navigation/navigation_initiator_activation_and_ad_status.mojom";
 import "third_party/blink/public/mojom/frame/view_transition_state.mojom";
 import "third_party/blink/public/mojom/fenced_frame/fenced_frame_config.mojom";
 import "third_party/blink/public/mojom/frame/frame_policy.mojom";
@@ -137,6 +138,10 @@
   // Token to identify a WebBundle. Set on navigation to a resource in a
   // WebBundle.
   network.mojom.WebBundleTokenParams? web_bundle_token;
+
+  // The navigation initiator's user activation and ad status.
+  blink.mojom.NavigationInitiatorActivationAndAdStatus
+      initiator_activation_and_ad_status;
 };
 
 // Provided by the browser or the renderer -------------------------------------
diff --git a/third_party/blink/public/mojom/permissions_policy/permissions_policy_feature.mojom b/third_party/blink/public/mojom/permissions_policy/permissions_policy_feature.mojom
index 80a0a9d..6dac562 100644
--- a/third_party/blink/public/mojom/permissions_policy/permissions_policy_feature.mojom
+++ b/third_party/blink/public/mojom/permissions_policy/permissions_policy_feature.mojom
@@ -224,7 +224,10 @@
 
   // Controls use of Compute Pressure API.
   // https://wicg.github.io/compute-pressure/#policy-control
-  kComputePressure = 106
+  kComputePressure = 106,
+
+  // Web Smart Card API
+  kSmartCard = 107
 
   // Don't change assigned numbers of any item, and don't reuse removed slots.
   // Add new features at the end of the enum.
diff --git a/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom b/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom
index 6ae0412..4e2ddcb 100644
--- a/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom
+++ b/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom
@@ -3760,6 +3760,9 @@
   kScrollend = 4419,
   kDOMWindowOpenCrossOriginIframe = 4420,
   kStreamingDeclarativeShadowDOM = 4421,
+  kDialogCloseWatcherCancelSkipped = 4422,
+  kDialogCloseWatcherCancelSkippedAndDefaultPrevented = 4423,
+  kDialogCloseWatcherCloseSignalClosedMultiple = 4424,
 
   // Add new features immediately above this line. Don't change assigned
   // numbers of any item, and don't reuse removed slots.
diff --git a/third_party/blink/public/platform/web_content_security_policy_struct.h b/third_party/blink/public/platform/web_content_security_policy_struct.h
index 1e49abb2..d8b8b367 100644
--- a/third_party/blink/public/platform/web_content_security_policy_struct.h
+++ b/third_party/blink/public/platform/web_content_security_policy_struct.h
@@ -60,6 +60,7 @@
   bool allow_star;
   bool allow_response_redirects;
   bool allow_inline;
+  bool allow_inline_speculation_rules;
   bool allow_eval;
   bool allow_wasm_eval;
   bool allow_wasm_unsafe_eval;
diff --git a/third_party/blink/public/web/web_navigation_params.h b/third_party/blink/public/web/web_navigation_params.h
index 27bb97ec..3a4d46b 100644
--- a/third_party/blink/public/web/web_navigation_params.h
+++ b/third_party/blink/public/web/web_navigation_params.h
@@ -106,6 +106,10 @@
   // Whether the navigation initiator frame is an ad frame.
   bool initiator_frame_is_ad = false;
 
+  // Whether there is ad script in stack when the navigation is initiated. Note
+  // that will also be true if the initiator frame is ad.
+  bool is_ad_script_in_stack = false;
+
   // Whether this is a navigation in the opener frame initiated
   // by the window.open'd frame.
   bool is_opener_navigation = false;
diff --git a/third_party/blink/renderer/core/animation/element_animations.cc b/third_party/blink/renderer/core/animation/element_animations.cc
index cdf15f9f..beda2bf 100644
--- a/third_party/blink/renderer/core/animation/element_animations.cc
+++ b/third_party/blink/renderer/core/animation/element_animations.cc
@@ -56,6 +56,7 @@
   visitor->Trace(effect_stack_);
   visitor->Trace(animations_);
   visitor->Trace(worklet_animations_);
+  ElementRareDataField::Trace(visitor);
 }
 
 bool ElementAnimations::UpdateBoxSizeAndCheckTransformAxisAlignment(
diff --git a/third_party/blink/renderer/core/animation/element_animations.h b/third_party/blink/renderer/core/animation/element_animations.h
index 863ca14..ecb94db 100644
--- a/third_party/blink/renderer/core/animation/element_animations.h
+++ b/third_party/blink/renderer/core/animation/element_animations.h
@@ -38,6 +38,7 @@
 #include "third_party/blink/renderer/core/animation/worklet_animation_base.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/css/properties/css_bitset.h"
+#include "third_party/blink/renderer/core/dom/element_rare_data_field.h"
 #include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_counted_set.h"
 #include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_set.h"
 #include "third_party/blink/renderer/platform/wtf/hash_counted_set.h"
@@ -50,7 +51,8 @@
 using WorkletAnimationSet = HeapHashSet<WeakMember<WorkletAnimationBase>>;
 
 class CORE_EXPORT ElementAnimations final
-    : public GarbageCollected<ElementAnimations> {
+    : public GarbageCollected<ElementAnimations>,
+      public ElementRareDataField {
  public:
   ElementAnimations();
   ElementAnimations(const ElementAnimations&) = delete;
@@ -128,7 +130,7 @@
     composited_clip_path_status_ = static_cast<unsigned>(status);
   }
 
-  void Trace(Visitor*) const;
+  void Trace(Visitor*) const override;
 
  private:
   EffectStack effect_stack_;
diff --git a/third_party/blink/renderer/core/aom/accessible_node.cc b/third_party/blink/renderer/core/aom/accessible_node.cc
index 28e34f10..7220068b 100644
--- a/third_party/blink/renderer/core/aom/accessible_node.cc
+++ b/third_party/blink/renderer/core/aom/accessible_node.cc
@@ -1218,6 +1218,7 @@
   visitor->Trace(children_);
   visitor->Trace(parent_);
   EventTargetWithInlineData::Trace(visitor);
+  ElementRareDataField::Trace(visitor);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/aom/accessible_node.h b/third_party/blink/renderer/core/aom/accessible_node.h
index 187d3e4..16c24d61 100644
--- a/third_party/blink/renderer/core/aom/accessible_node.h
+++ b/third_party/blink/renderer/core/aom/accessible_node.h
@@ -7,6 +7,7 @@
 
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/core/dom/element_rare_data_field.h"
 #include "third_party/blink/renderer/core/dom/events/event_target.h"
 #include "third_party/blink/renderer/core/dom/qualified_name.h"
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
@@ -107,7 +108,8 @@
 // Accessibility Object Model node
 // Explainer: https://github.com/WICG/aom/blob/gh-pages/explainer.md
 // Spec: https://wicg.github.io/aom/spec/
-class CORE_EXPORT AccessibleNode : public EventTargetWithInlineData {
+class CORE_EXPORT AccessibleNode : public EventTargetWithInlineData,
+                                   public ElementRareDataField {
   DEFINE_WRAPPERTYPEINFO();
 
  public:
diff --git a/third_party/blink/renderer/core/css/container_query_data.cc b/third_party/blink/renderer/core/css/container_query_data.cc
index 8c02f5f..982869f4 100644
--- a/third_party/blink/renderer/core/css/container_query_data.cc
+++ b/third_party/blink/renderer/core/css/container_query_data.cc
@@ -10,6 +10,7 @@
 
 void ContainerQueryData::Trace(Visitor* visitor) const {
   visitor->Trace(container_query_evaluator_);
+  ElementRareDataField::Trace(visitor);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/container_query_data.h b/third_party/blink/renderer/core/css/container_query_data.h
index c774ad2..dcd035c 100644
--- a/third_party/blink/renderer/core/css/container_query_data.h
+++ b/third_party/blink/renderer/core/css/container_query_data.h
@@ -7,6 +7,7 @@
 
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/renderer/core/css/style_recalc_change.h"
+#include "third_party/blink/renderer/core/dom/element_rare_data_field.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/heap/member.h"
 
@@ -15,7 +16,8 @@
 class ContainerQueryEvaluator;
 
 // Class for storing Container Query data on ElementRareData.
-class ContainerQueryData final : public GarbageCollected<ContainerQueryData> {
+class ContainerQueryData final : public GarbageCollected<ContainerQueryData>,
+                                 public ElementRareDataField {
  public:
   StyleRecalcChange ClearAndReturnRecalcChangeForChildren() {
     DCHECK(child_change_.has_value());
@@ -40,7 +42,7 @@
     container_query_evaluator_ = evaluator;
   }
 
-  void Trace(Visitor*) const;
+  void Trace(Visitor*) const override;
 
  private:
   Member<ContainerQueryEvaluator> container_query_evaluator_;
diff --git a/third_party/blink/renderer/core/css/cssom/css_numeric_value.cc b/third_party/blink/renderer/core/css/cssom/css_numeric_value.cc
index d442064..73c4d39 100644
--- a/third_party/blink/renderer/core/css/cssom/css_numeric_value.cc
+++ b/third_party/blink/renderer/core/css/cssom/css_numeric_value.cc
@@ -86,9 +86,17 @@
 }
 
 CSSMathOperator CanonicalOperator(CSSMathOperator op) {
-  if (op == CSSMathOperator::kAdd || op == CSSMathOperator::kSubtract)
-    return CSSMathOperator::kAdd;
-  return CSSMathOperator::kMultiply;
+  switch (op) {
+    case CSSMathOperator::kAdd:
+    case CSSMathOperator::kSubtract:
+      return CSSMathOperator::kAdd;
+    case CSSMathOperator::kMultiply:
+    case CSSMathOperator::kDivide:
+      return CSSMathOperator::kMultiply;
+    default:
+      NOTREACHED();
+      return CSSMathOperator::kInvalid;
+  }
 }
 
 bool CanCombineNodes(const CSSMathExpressionNode& root,
@@ -98,9 +106,12 @@
     return false;
   if (node.IsNestedCalc())
     return false;
+  const auto& node_exp = To<CSSMathExpressionOperation>(node);
+  if (node_exp.IsMinOrMax() || node_exp.IsClamp())
+    return false;
   return CanonicalOperator(
              To<CSSMathExpressionOperation>(root).OperatorType()) ==
-         CanonicalOperator(To<CSSMathExpressionOperation>(node).OperatorType());
+         CanonicalOperator(node_exp.OperatorType());
 }
 
 CSSNumericValue* NegateOrInvertIfRequired(CSSMathOperator parent_op,
diff --git a/third_party/blink/renderer/core/css/cssom/inline_style_property_map.h b/third_party/blink/renderer/core/css/cssom/inline_style_property_map.h
index f3a384d..f541bdc 100644
--- a/third_party/blink/renderer/core/css/cssom/inline_style_property_map.h
+++ b/third_party/blink/renderer/core/css/cssom/inline_style_property_map.h
@@ -8,10 +8,12 @@
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/css/cssom/style_property_map.h"
 #include "third_party/blink/renderer/core/dom/element.h"
+#include "third_party/blink/renderer/core/dom/element_rare_data_field.h"
 
 namespace blink {
 
-class CORE_EXPORT InlineStylePropertyMap final : public StylePropertyMap {
+class CORE_EXPORT InlineStylePropertyMap final : public StylePropertyMap,
+                                                 public ElementRareDataField {
  public:
   explicit InlineStylePropertyMap(Element* owner_element)
       : owner_element_(owner_element) {}
@@ -21,6 +23,7 @@
   void Trace(Visitor* visitor) const override {
     visitor->Trace(owner_element_);
     StylePropertyMap::Trace(visitor);
+    ElementRareDataField::Trace(visitor);
   }
 
   unsigned int size() const final;
diff --git a/third_party/blink/renderer/core/css/inline_css_style_declaration.cc b/third_party/blink/renderer/core/css/inline_css_style_declaration.cc
index 1738e43..f15c456 100644
--- a/third_party/blink/renderer/core/css/inline_css_style_declaration.cc
+++ b/third_party/blink/renderer/core/css/inline_css_style_declaration.cc
@@ -60,6 +60,7 @@
 void InlineCSSStyleDeclaration::Trace(Visitor* visitor) const {
   visitor->Trace(parent_element_);
   AbstractPropertySetCSSStyleDeclaration::Trace(visitor);
+  ElementRareDataField::Trace(visitor);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/inline_css_style_declaration.h b/third_party/blink/renderer/core/css/inline_css_style_declaration.h
index 867f346..0c770a5b 100644
--- a/third_party/blink/renderer/core/css/inline_css_style_declaration.h
+++ b/third_party/blink/renderer/core/css/inline_css_style_declaration.h
@@ -29,11 +29,13 @@
 #include "third_party/blink/renderer/core/css/abstract_property_set_css_style_declaration.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_rare_data_field.h"
 
 namespace blink {
 
 class InlineCSSStyleDeclaration final
-    : public AbstractPropertySetCSSStyleDeclaration {
+    : public AbstractPropertySetCSSStyleDeclaration,
+      public ElementRareDataField {
  public:
   explicit InlineCSSStyleDeclaration(Element* parent_element)
       : AbstractPropertySetCSSStyleDeclaration(
diff --git a/third_party/blink/renderer/core/css/parser/css_parser_impl.cc b/third_party/blink/renderer/core/css/parser/css_parser_impl.cc
index 1b37007..4223dac 100644
--- a/third_party/blink/renderer/core/css/parser/css_parser_impl.cc
+++ b/third_party/blink/renderer/core/css/parser/css_parser_impl.cc
@@ -1559,11 +1559,14 @@
   if (!prelude.AtEnd())
     return nullptr;
 
-  String name;  // <dashed-ident>
+  // <dashed-ident>, and -internal-* for UA sheets only.
+  String name;
   if (name_token.GetType() == kIdentToken) {
     name = name_token.Value().ToString();
-    if (!name.StartsWith("--"))
+    if (!name.StartsWith("--") &&
+        !(context_->Mode() == kUASheetMode && name.StartsWith("-internal-"))) {
       return nullptr;
+    }
   } else {
     return nullptr;
   }
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 6df9a8fd..dbf7e620 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
@@ -824,7 +824,8 @@
 
 // anchor() and anchor-size() shouldn't parse when the feature is disabled.
 TEST(CSSPropertyParserTest, AnchorPositioningDisabled) {
-  ScopedCSSAnchorPositioningForTest disabled_scope(false);
+  ScopedHTMLSelectMenuElementForTest select_menu_disabled(false);
+  ScopedCSSAnchorPositioningForTest anchor_positioning_disabled(false);
 
   auto* context = MakeGarbageCollected<CSSParserContext>(
       kHTMLStandardMode, SecureContextMode::kInsecureContext);
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 ea631eee..7133345 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
@@ -185,8 +185,9 @@
     bool allow_visited_style) const {
   if (!style.AnchorScroll())
     return CSSIdentifierValue::Create(CSSValueID::kNone);
-  if (style.AnchorScroll()->IsImplicit())
+  if (style.AnchorScroll()->IsImplicit()) {
     return CSSIdentifierValue::Create(CSSValueID::kImplicit);
+  }
   return MakeGarbageCollected<CSSCustomIdentValue>(
       style.AnchorScroll()->GetName().GetName());
 }
@@ -5949,7 +5950,17 @@
           css_parsing_utils::ConsumeIdent<CSSValueID::kNone>(range)) {
     return value;
   }
-  return css_parsing_utils::ConsumeDashedIdent(range, context);
+  if (CSSValue* value = css_parsing_utils::ConsumeDashedIdent(range, context)) {
+    return value;
+  }
+  if (context.Mode() == kUASheetMode) {
+    CSSCustomIdentValue* value =
+        css_parsing_utils::ConsumeCustomIdent(range, context);
+    if (value && value->Value().StartsWith("-internal-")) {
+      return value;
+    }
+  }
+  return nullptr;
 }
 const CSSValue* PositionFallback::CSSValueFromComputedStyleInternal(
     const ComputedStyle& style,
diff --git a/third_party/blink/renderer/core/css/resolver/style_resolver.cc b/third_party/blink/renderer/core/css/resolver/style_resolver.cc
index a6bd988d..98a4999c 100644
--- a/third_party/blink/renderer/core/css/resolver/style_resolver.cc
+++ b/third_party/blink/renderer/core/css/resolver/style_resolver.cc
@@ -613,8 +613,17 @@
     // Consider the ::part rules for the given scope.
     TreeScope& tree_scope = element->GetTreeScope();
     if (ScopedStyleResolver* resolver = tree_scope.GetScopedStyleResolver()) {
-      ElementRuleCollector::PartRulesScope scope(
-          collector, const_cast<Element&>(*element));
+      // PartRulesScope must be provided with the host where we want to start
+      // the search for container query containers. For the first iteration of
+      // this loop, `element` is the `part_matching_element`, but we want to
+      // start the search at `part_matching_element`'s host. For subsequent
+      // iterations, `element` is the correct starting element/host.
+      const Element* host = (element == &part_matching_element)
+                                ? element->OwnerShadowHost()
+                                : element;
+      DCHECK(IsShadowHost(host));
+      ElementRuleCollector::PartRulesScope scope(collector,
+                                                 const_cast<Element&>(*host));
       collector.ClearMatchedRules();
       resolver->CollectMatchingPartPseudoRules(collector, current_names,
                                                for_shadow_pseudo);
@@ -2638,32 +2647,46 @@
   if (!tree_scope)
     tree_scope = &GetDocument();
 
+  StyleRulePositionFallback* position_fallback_rule = nullptr;
   for (; tree_scope; tree_scope = tree_scope->ParentTreeScope()) {
     if (ScopedStyleResolver* resolver = tree_scope->GetScopedStyleResolver()) {
-      StyleRulePositionFallback* position_fallback_rule =
+      position_fallback_rule =
           resolver->PositionFallbackForName(position_fallback->GetName());
-      if (!position_fallback_rule)
-        continue;
-      if (index >= position_fallback_rule->TryRules().size())
-        return nullptr;
-      StyleRuleTry* try_rule = position_fallback_rule->TryRules()[index];
-      StyleResolverState state(GetDocument(), element);
-      state.SetStyle(ComputedStyle::Clone(base_style));
-      const CSSPropertyValueSet& properties = try_rule->Properties();
-
-      STACK_UNINITIALIZED StyleCascade cascade(state);
-      cascade.MutableMatchResult().FinishAddingUARules();
-      cascade.MutableMatchResult().FinishAddingUserRules();
-      cascade.MutableMatchResult().FinishAddingPresentationalHints();
-      cascade.MutableMatchResult().AddMatchedProperties(&properties);
-      cascade.MutableMatchResult().FinishAddingAuthorRulesForTreeScope(
-          *tree_scope);
-      cascade.Apply();
-
-      return state.TakeStyle();
+      if (position_fallback_rule)
+        break;
     }
   }
-  return nullptr;
+
+  // Try UA rules if no author rule matches
+  if (!position_fallback_rule) {
+    for (const auto& rule : CSSDefaultStyleSheets::Instance()
+                                .DefaultHtmlStyle()
+                                ->PositionFallbackRules()) {
+      if (position_fallback->GetName() == rule->Name()) {
+        position_fallback_rule = rule;
+        break;
+      }
+    }
+  }
+
+  if (!position_fallback_rule ||
+      index >= position_fallback_rule->TryRules().size())
+    return nullptr;
+
+  StyleRuleTry* try_rule = position_fallback_rule->TryRules()[index];
+  StyleResolverState state(GetDocument(), element);
+  state.SetStyle(ComputedStyle::Clone(base_style));
+  const CSSPropertyValueSet& properties = try_rule->Properties();
+
+  STACK_UNINITIALIZED StyleCascade cascade(state);
+  cascade.MutableMatchResult().FinishAddingUARules();
+  cascade.MutableMatchResult().FinishAddingUserRules();
+  cascade.MutableMatchResult().FinishAddingPresentationalHints();
+  cascade.MutableMatchResult().AddMatchedProperties(&properties);
+  cascade.MutableMatchResult().FinishAddingAuthorRulesForTreeScope(*tree_scope);
+  cascade.Apply();
+
+  return state.TakeStyle();
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/style_recalc_context.cc b/third_party/blink/renderer/core/css/style_recalc_context.cc
index 89b3319b..301cf54 100644
--- a/third_party/blink/renderer/core/css/style_recalc_context.cc
+++ b/third_party/blink/renderer/core/css/style_recalc_context.cc
@@ -71,6 +71,8 @@
 }
 
 StyleRecalcContext StyleRecalcContext::ForPartRules(Element& host) const {
+  DCHECK(IsShadowHost(host));
+
   if (!container)
     return *this;
 
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 ca41fc8..ccdcd3c 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
@@ -1315,6 +1315,7 @@
 void DisplayLockContext::Trace(Visitor* visitor) const {
   visitor->Trace(element_);
   visitor->Trace(document_);
+  ElementRareDataField::Trace(visitor);
 }
 
 void DisplayLockContext::SetShouldUnlockAutoForPrint(bool flag) {
diff --git a/third_party/blink/renderer/core/display_lock/display_lock_context.h b/third_party/blink/renderer/core/display_lock/display_lock_context.h
index 89eb156..d9d4a3a 100644
--- a/third_party/blink/renderer/core/display_lock/display_lock_context.h
+++ b/third_party/blink/renderer/core/display_lock/display_lock_context.h
@@ -9,6 +9,7 @@
 
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/css/style_recalc_change.h"
+#include "third_party/blink/renderer/core/dom/element_rare_data_field.h"
 #include "third_party/blink/renderer/core/frame/local_frame_view.h"
 #include "third_party/blink/renderer/core/scroll/scroll_types.h"
 #include "third_party/blink/renderer/core/style/computed_style_base_constants.h"
@@ -87,7 +88,8 @@
 
 class CORE_EXPORT DisplayLockContext final
     : public GarbageCollected<DisplayLockContext>,
-      public LocalFrameView::LifecycleNotificationObserver {
+      public LocalFrameView::LifecycleNotificationObserver,
+      public ElementRareDataField {
  public:
   // Note the order of the phases matters. Each phase implies all previous ones
   // as well.
diff --git a/third_party/blink/renderer/core/dom/build.gni b/third_party/blink/renderer/core/dom/build.gni
index 664811e..902bda2 100644
--- a/third_party/blink/renderer/core/dom/build.gni
+++ b/third_party/blink/renderer/core/dom/build.gni
@@ -111,6 +111,9 @@
   "element_rare_data.cc",
   "element_rare_data.h",
   "element_rare_data_base.h",
+  "element_rare_data_field.h",
+  "element_rare_data_vector.cc",
+  "element_rare_data_vector.h",
   "element_traversal.h",
   "empty_node_list.cc",
   "empty_node_list.h",
@@ -296,6 +299,7 @@
   "document_statistics_collector_test.cc",
   "document_test.cc",
   "dom_node_ids_test.cc",
+  "element_rare_data_vector_test.cc",
   "element_test.cc",
   "events/event_path_test.cc",
   "events/event_target_test.cc",
diff --git a/third_party/blink/renderer/core/dom/css_toggle_map.cc b/third_party/blink/renderer/core/dom/css_toggle_map.cc
index 6f4ba97..945da76 100644
--- a/third_party/blink/renderer/core/dom/css_toggle_map.cc
+++ b/third_party/blink/renderer/core/dom/css_toggle_map.cc
@@ -5,6 +5,7 @@
 #include "third_party/blink/renderer/core/dom/css_toggle_map.h"
 
 #include "third_party/blink/renderer/core/dom/element.h"
+#include "third_party/blink/renderer/core/dom/element_rare_data_field.h"
 #include "third_party/blink/renderer/core/style/toggle_root.h"
 #include "third_party/blink/renderer/core/style/toggle_root_list.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
@@ -19,6 +20,7 @@
   visitor->Trace(toggles_);
 
   ScriptWrappable::Trace(visitor);
+  ElementRareDataField::Trace(visitor);
 }
 
 void CSSToggleMap::CreateToggles(const ToggleRootList* toggle_roots) {
diff --git a/third_party/blink/renderer/core/dom/css_toggle_map.h b/third_party/blink/renderer/core/dom/css_toggle_map.h
index e2ca0739..a61ef091 100644
--- a/third_party/blink/renderer/core/dom/css_toggle_map.h
+++ b/third_party/blink/renderer/core/dom/css_toggle_map.h
@@ -7,6 +7,7 @@
 
 #include "third_party/blink/renderer/bindings/core/v8/maplike.h"
 #include "third_party/blink/renderer/core/dom/css_toggle.h"
+#include "third_party/blink/renderer/core/dom/element_rare_data_field.h"
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
 #include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_map.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
@@ -27,7 +28,8 @@
     Maplike<AtomicString, IDLString, Member<CSSToggle>, CSSToggle>;
 
 class CORE_EXPORT CSSToggleMap : public ScriptWrappable,
-                                 public CSSToggleMapMaplike {
+                                 public CSSToggleMapMaplike,
+                                 public ElementRareDataField {
   DEFINE_WRAPPERTYPEINFO();
 
  public:
diff --git a/third_party/blink/renderer/core/dom/dataset_dom_string_map.cc b/third_party/blink/renderer/core/dom/dataset_dom_string_map.cc
index fda27a20..629c98f 100644
--- a/third_party/blink/renderer/core/dom/dataset_dom_string_map.cc
+++ b/third_party/blink/renderer/core/dom/dataset_dom_string_map.cc
@@ -202,6 +202,7 @@
 
 void DatasetDOMStringMap::Trace(Visitor* visitor) const {
   visitor->Trace(element_);
+  ElementRareDataField::Trace(visitor);
   DOMStringMap::Trace(visitor);
 }
 
diff --git a/third_party/blink/renderer/core/dom/dataset_dom_string_map.h b/third_party/blink/renderer/core/dom/dataset_dom_string_map.h
index 53734f3..0e10648f 100644
--- a/third_party/blink/renderer/core/dom/dataset_dom_string_map.h
+++ b/third_party/blink/renderer/core/dom/dataset_dom_string_map.h
@@ -27,13 +27,15 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_DOM_DATASET_DOM_STRING_MAP_H_
 
 #include "third_party/blink/renderer/core/dom/dom_string_map.h"
+#include "third_party/blink/renderer/core/dom/element_rare_data_field.h"
 
 namespace blink {
 
 class Element;
 class ExceptionState;
 
-class DatasetDOMStringMap final : public DOMStringMap {
+class DatasetDOMStringMap final : public DOMStringMap,
+                                  public ElementRareDataField {
  public:
   explicit DatasetDOMStringMap(Element* element) : element_(element) {}
 
diff --git a/third_party/blink/renderer/core/dom/dom_token_list.cc b/third_party/blink/renderer/core/dom/dom_token_list.cc
index 23a5e2ab..8ff0a4ed 100644
--- a/third_party/blink/renderer/core/dom/dom_token_list.cc
+++ b/third_party/blink/renderer/core/dom/dom_token_list.cc
@@ -81,6 +81,7 @@
 void DOMTokenList::Trace(Visitor* visitor) const {
   visitor->Trace(element_);
   ScriptWrappable::Trace(visitor);
+  ElementRareDataField::Trace(visitor);
 }
 
 // https://dom.spec.whatwg.org/#concept-domtokenlist-validation
diff --git a/third_party/blink/renderer/core/dom/dom_token_list.h b/third_party/blink/renderer/core/dom/dom_token_list.h
index 9ebe5e2..d8e1a64 100644
--- a/third_party/blink/renderer/core/dom/dom_token_list.h
+++ b/third_party/blink/renderer/core/dom/dom_token_list.h
@@ -26,6 +26,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_DOM_DOM_TOKEN_LIST_H_
 
 #include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/core/dom/element_rare_data_field.h"
 #include "third_party/blink/renderer/core/dom/qualified_name.h"
 #include "third_party/blink/renderer/core/dom/space_split_string.h"
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
@@ -39,7 +40,8 @@
 class Element;
 class ExceptionState;
 
-class CORE_EXPORT DOMTokenList : public ScriptWrappable {
+class CORE_EXPORT DOMTokenList : public ScriptWrappable,
+                                 public ElementRareDataField {
   DEFINE_WRAPPERTYPEINFO();
 
  public:
diff --git a/third_party/blink/renderer/core/dom/element.cc b/third_party/blink/renderer/core/dom/element.cc
index c5ff169..ff4f1b3 100644
--- a/third_party/blink/renderer/core/dom/element.cc
+++ b/third_party/blink/renderer/core/dom/element.cc
@@ -84,6 +84,7 @@
 #include "third_party/blink/renderer/core/dom/dom_token_list.h"
 #include "third_party/blink/renderer/core/dom/element_data_cache.h"
 #include "third_party/blink/renderer/core/dom/element_rare_data.h"
+#include "third_party/blink/renderer/core/dom/element_rare_data_vector.h"
 #include "third_party/blink/renderer/core/dom/element_traversal.h"
 #include "third_party/blink/renderer/core/dom/events/event_dispatch_forbidden_scope.h"
 #include "third_party/blink/renderer/core/dom/events/event_dispatch_result.h"
@@ -139,6 +140,7 @@
 #include "third_party/blink/renderer/core/html/forms/html_input_element.h"
 #include "third_party/blink/renderer/core/html/forms/html_options_collection.h"
 #include "third_party/blink/renderer/core/html/forms/html_select_element.h"
+#include "third_party/blink/renderer/core/html/forms/html_select_menu_element.h"
 #include "third_party/blink/renderer/core/html/html_body_element.h"
 #include "third_party/blink/renderer/core/html/html_collection.h"
 #include "third_party/blink/renderer/core/html/html_document.h"
@@ -800,7 +802,7 @@
 }
 
 Attr* Element::DetachAttribute(wtf_size_t index) {
-  DCHECK(GetElementData());
+  DCHECK(HasElementData());
   const Attribute& attribute = GetElementData()->Attributes().at(index);
   Attr* attr_node = AttrIfExists(attribute.GetName());
   if (attr_node) {
@@ -815,7 +817,7 @@
 
 void Element::DetachAttrNodeAtIndex(Attr* attr, wtf_size_t index) {
   DCHECK(attr);
-  DCHECK(GetElementData());
+  DCHECK(HasElementData());
 
   const Attribute& attribute = GetElementData()->Attributes().at(index);
   DCHECK(attribute.GetName() == attr->GetQualifiedName());
@@ -1026,11 +1028,11 @@
 
 inline ElementRareDataBase* Element::GetElementRareData() const {
   DCHECK(HasRareData());
-  return static_cast<ElementRareData*>(RareData());
+  return static_cast<ElementRareDataBase*>(RareData());
 }
 
 inline ElementRareDataBase& Element::EnsureElementRareData() {
-  return static_cast<ElementRareData&>(EnsureRareData());
+  return static_cast<ElementRareDataBase&>(EnsureRareData());
 }
 
 void Element::RemovePopoverData() {
@@ -1046,7 +1048,7 @@
 }
 
 inline void Element::SynchronizeAttribute(const QualifiedName& name) const {
-  if (!GetElementData())
+  if (!HasElementData())
     return;
   if (UNLIKELY(name == html_names::kStyleAttr &&
                GetElementData()->style_attribute_is_dirty())) {
@@ -1089,7 +1091,7 @@
 
 bool Element::HasAttributeIgnoringNamespace(
     const AtomicString& local_name) const {
-  if (!GetElementData())
+  if (!HasElementData())
     return false;
   WTF::AtomicStringTable::WeakResult hint =
       WeakLowercaseIfNecessary(local_name);
@@ -1105,7 +1107,7 @@
 }
 
 void Element::SynchronizeAllAttributes() const {
-  if (!GetElementData())
+  if (!HasElementData())
     return;
   // NOTE: AnyAttributeMatches in selector_checker.cc currently assumes that all
   // lazy attributes have a null namespace.  If that ever changes we'll need to
@@ -1118,14 +1120,14 @@
 }
 
 void Element::SynchronizeAllAttributesExceptStyle() const {
-  if (!GetElementData())
+  if (!HasElementData())
     return;
   if (GetElementData()->svg_attributes_are_dirty())
     To<SVGElement>(this)->SynchronizeSVGAttribute(AnyQName());
 }
 
 const AtomicString& Element::getAttribute(const QualifiedName& name) const {
-  if (!GetElementData())
+  if (!HasElementData())
     return g_null_atom;
   SynchronizeAttribute(name);
   if (const Attribute* attribute = GetElementData()->Attributes().Find(name))
@@ -2420,7 +2422,7 @@
 }
 
 void Element::ClassAttributeChanged(const AtomicString& new_class_string) {
-  DCHECK(GetElementData());
+  DCHECK(HasElementData());
   ClassStringContent class_string_content_type =
       ClassStringHasClassName(new_class_string);
   const bool should_fold_case = GetDocument().InQuirksMode();
@@ -2522,9 +2524,9 @@
   other.SynchronizeAllAttributes();
   if (GetElementData() == other.GetElementData())
     return true;
-  if (GetElementData())
+  if (HasElementData())
     return GetElementData()->IsEquivalent(other.GetElementData());
-  if (other.GetElementData())
+  if (other.HasElementData())
     return other.GetElementData()->IsEquivalent(GetElementData());
   return true;
 }
@@ -4641,7 +4643,7 @@
 }
 
 Attr* Element::getAttributeNode(const AtomicString& local_name) {
-  if (!GetElementData())
+  if (!HasElementData())
     return nullptr;
   WTF::AtomicStringTable::WeakResult hint =
       WeakLowercaseIfNecessary(local_name);
@@ -4655,7 +4657,7 @@
 
 Attr* Element::getAttributeNodeNS(const AtomicString& namespace_uri,
                                   const AtomicString& local_name) {
-  if (!GetElementData())
+  if (!HasElementData())
     return nullptr;
   QualifiedName q_name(g_null_atom, local_name, namespace_uri);
   SynchronizeAttribute(q_name);
@@ -4666,7 +4668,7 @@
 }
 
 bool Element::hasAttribute(const AtomicString& local_name) const {
-  if (!GetElementData())
+  if (!HasElementData())
     return false;
   WTF::AtomicStringTable::WeakResult hint =
       WeakLowercaseIfNecessary(local_name);
@@ -4677,7 +4679,7 @@
 
 bool Element::hasAttributeNS(const AtomicString& namespace_uri,
                              const AtomicString& local_name) const {
-  if (!GetElementData())
+  if (!HasElementData())
     return false;
   QualifiedName q_name(g_null_atom, local_name, namespace_uri);
   SynchronizeAttribute(q_name);
@@ -6606,7 +6608,7 @@
 
 KURL Element::GetURLAttribute(const QualifiedName& name) const {
 #if DCHECK_IS_ON()
-  if (GetElementData()) {
+  if (HasElementData()) {
     if (const Attribute* attribute = Attributes().Find(name))
       DCHECK(IsURLAttribute(*attribute));
   }
@@ -6617,7 +6619,7 @@
 
 KURL Element::GetNonEmptyURLAttribute(const QualifiedName& name) const {
 #if DCHECK_IS_ON()
-  if (GetElementData()) {
+  if (HasElementData()) {
     if (const Attribute* attribute = Attributes().Find(name))
       DCHECK(IsURLAttribute(*attribute));
   }
@@ -7249,7 +7251,7 @@
 
 void Element::SynchronizeStyleAttributeInternal() const {
   DCHECK(IsStyledElement());
-  DCHECK(GetElementData());
+  DCHECK(HasElementData());
   DCHECK(GetElementData()->style_attribute_is_dirty());
   GetElementData()->SetStyleAttributeIsDirty(false);
   const CSSPropertyValueSet* inline_style = InlineStyle();
@@ -7747,7 +7749,7 @@
 
 void Element::InvalidateStyleAttribute(
     bool only_changed_independent_properties) {
-  DCHECK(GetElementData());
+  DCHECK(HasElementData());
   GetElementData()->SetStyleAttributeIsDirty(true);
   SetNeedsStyleRecalc(only_changed_independent_properties
                           ? kInlineIndependentStyleChange
@@ -7913,7 +7915,7 @@
     WTF::AtomicStringTable::WeakResult hint) const {
   // This version of SynchronizeAttribute() is streamlined for the case where
   // you don't have a full QualifiedName, e.g when called from DOM API.
-  if (!GetElementData())
+  if (!HasElementData())
     return;
   // TODO(ajwong): Does this unnecessarily synchronize style attributes on
   // SVGElements?
@@ -7941,7 +7943,7 @@
 const AtomicString& Element::GetAttributeHinted(
     const AtomicString& name,
     WTF::AtomicStringTable::WeakResult hint) const {
-  if (!GetElementData())
+  if (!HasElementData())
     return g_null_atom;
   SynchronizeAttributeHinted(name, hint);
   if (const Attribute* attribute =
@@ -7953,7 +7955,7 @@
 std::pair<wtf_size_t, const QualifiedName> Element::LookupAttributeQNameHinted(
     AtomicString name,
     WTF::AtomicStringTable::WeakResult hint) const {
-  if (!GetElementData()) {
+  if (!HasElementData()) {
     return std::make_pair(
         kNotFound,
         QualifiedName(g_null_atom, LowercaseIfNecessary(std::move(name)),
@@ -8048,7 +8050,7 @@
 }
 
 wtf_size_t Element::FindAttributeIndex(const QualifiedName& name) {
-  if (GetElementData())
+  if (HasElementData())
     return GetElementData()->Attributes().FindIndex(name);
   return kNotFound;
 }
@@ -8159,7 +8161,7 @@
 
 void Element::RemoveAttributeHinted(const AtomicString& name,
                                     WTF::AtomicStringTable::WeakResult hint) {
-  if (!GetElementData())
+  if (!HasElementData())
     return;
 
   wtf_size_t index = GetElementData()->Attributes().FindIndexHinted(name, hint);
@@ -8255,14 +8257,23 @@
   EnsureElementRareData().DecrementAnchoredPopoverCount();
 }
 bool Element::HasAnchoredPopover() const {
-  return HasRareData() && GetElementRareData()->HasAnchoredPopover();
+  return (HasRareData() && GetElementRareData()->HasAnchoredPopover()) ||
+         IsA<HTMLSelectMenuElement>(this);
 }
 
 Element* Element::ImplicitAnchorElement() const {
   if (!RuntimeEnabledFeatures::CSSAnchorPositioningEnabled())
     return nullptr;
-  if (const HTMLElement* html_element = DynamicTo<HTMLElement>(this))
-    return html_element->anchorElement();
+  const HTMLElement* html_element = DynamicTo<HTMLElement>(this);
+  if (!html_element) {
+    return nullptr;
+  }
+  if (Element* anchor = html_element->anchorElement()) {
+    return anchor;
+  }
+  if (Element* select_menu = html_element->ownerSelectMenuElement()) {
+    return select_menu;
+  }
   return nullptr;
 }
 
diff --git a/third_party/blink/renderer/core/dom/element.h b/third_party/blink/renderer/core/dom/element.h
index 835035b..8ee09ee 100644
--- a/third_party/blink/renderer/core/dom/element.h
+++ b/third_party/blink/renderer/core/dom/element.h
@@ -473,7 +473,7 @@
   void InvalidateStyleAttribute(bool only_changed_independent_properties);
 
   const CSSPropertyValueSet* InlineStyle() const {
-    return GetElementData() ? GetElementData()->inline_style_.Get() : nullptr;
+    return HasElementData() ? GetElementData()->inline_style_.Get() : nullptr;
   }
 
   void SetInlineStyleProperty(CSSPropertyID,
@@ -1182,6 +1182,7 @@
   Element* ImplicitAnchorElement() const;
 
  protected:
+  bool HasElementData() const { return element_data_; }
   const ElementData* GetElementData() const { return element_data_.Get(); }
   UniqueElementData& EnsureUniqueElementData();
 
@@ -1615,7 +1616,7 @@
   DCHECK(FastAttributeLookupAllowed(name))
       << TagQName().ToString().Utf8() << "/@" << name.ToString().Utf8();
 #endif
-  return GetElementData() &&
+  return HasElementData() &&
          GetElementData()->Attributes().FindIndex(name) != kNotFound;
 }
 
@@ -1625,7 +1626,7 @@
   DCHECK(FastAttributeLookupAllowed(name))
       << TagQName().ToString().Utf8() << "/@" << name.ToString().Utf8();
 #endif
-  if (GetElementData()) {
+  if (HasElementData()) {
     if (const Attribute* attribute = GetElementData()->Attributes().Find(name))
       return attribute->Value();
   }
@@ -1633,20 +1634,20 @@
 }
 
 inline AttributeCollection Element::Attributes() const {
-  if (!GetElementData())
+  if (!HasElementData())
     return AttributeCollection();
   SynchronizeAllAttributes();
   return GetElementData()->Attributes();
 }
 
 inline AttributeCollection Element::AttributesWithoutUpdate() const {
-  if (!GetElementData())
+  if (!HasElementData())
     return AttributeCollection();
   return GetElementData()->Attributes();
 }
 
 inline AttributeCollection Element::AttributesWithoutStyleUpdate() const {
-  if (!GetElementData())
+  if (!HasElementData())
     return AttributeCollection();
   SynchronizeAllAttributesExceptStyle();
   return GetElementData()->Attributes();
@@ -1683,7 +1684,7 @@
 
 inline const SpaceSplitString& Element::ClassNames() const {
   DCHECK(HasClass());
-  DCHECK(GetElementData());
+  DCHECK(HasElementData());
   return GetElementData()->ClassNames();
 }
 
@@ -1692,21 +1693,21 @@
 }
 
 inline bool Element::HasID() const {
-  return GetElementData() && GetElementData()->HasID();
+  return HasElementData() && GetElementData()->HasID();
 }
 
 inline bool Element::HasClass() const {
-  return GetElementData() && GetElementData()->HasClass();
+  return HasElementData() && GetElementData()->HasClass();
 }
 
 inline UniqueElementData& Element::EnsureUniqueElementData() {
-  if (!GetElementData() || !GetElementData()->IsUnique())
+  if (!HasElementData() || !GetElementData()->IsUnique())
     CreateUniqueElementData();
   return To<UniqueElementData>(*element_data_);
 }
 
 inline const CSSPropertyValueSet* Element::PresentationAttributeStyle() {
-  if (!GetElementData())
+  if (!HasElementData())
     return nullptr;
   if (GetElementData()->presentation_attribute_style_is_dirty())
     UpdatePresentationAttributeStyle();
diff --git a/third_party/blink/renderer/core/dom/element_rare_data.cc b/third_party/blink/renderer/core/dom/element_rare_data.cc
index e944622c..4c1a7ac 100644
--- a/third_party/blink/renderer/core/dom/element_rare_data.cc
+++ b/third_party/blink/renderer/core/dom/element_rare_data.cc
@@ -44,24 +44,15 @@
 
 namespace blink {
 
-struct SameSizeAsElementRareData : NodeRareData, ElementRareDataBase {
-  gfx::Vector2dF scroll_offset;
+struct SameSizeAsElementRareData : ElementRareDataBase {
   void* pointers_or_strings[4];
   Member<void*> members[22];
-  FocusgroupFlags focusgroup_flags;
-  HasInvalidationFlags has_invalidation_flags;
+  gfx::Vector2dF scroll_offset;
   wtf_size_t anchored_popover_count;
-  unsigned flags[1];
 };
 
 ElementRareData::ElementRareData(NodeRenderingData* node_layout_data)
-    : NodeRareData(ClassType::kElementRareData, node_layout_data),
-      class_list_(nullptr),
-      did_attach_internals_(false),
-      should_force_legacy_layout_for_child_(false),
-      style_should_force_legacy_layout_(false),
-      has_undo_stack_(false),
-      scrollbar_pseudo_element_styles_depend_on_font_metrics_(false) {}
+    : ElementRareDataBase(node_layout_data), class_list_(nullptr) {}
 
 ElementRareData::~ElementRareData() {
   DCHECK(!pseudo_element_data_);
diff --git a/third_party/blink/renderer/core/dom/element_rare_data.h b/third_party/blink/renderer/core/dom/element_rare_data.h
index e375b1e..0dd531a 100644
--- a/third_party/blink/renderer/core/dom/element_rare_data.h
+++ b/third_party/blink/renderer/core/dom/element_rare_data.h
@@ -61,7 +61,7 @@
 class ResizeObservation;
 class ResizeObserver;
 
-class ElementRareData final : public NodeRareData, public ElementRareDataBase {
+class ElementRareData final : public ElementRareDataBase {
  public:
   explicit ElementRareData(NodeRenderingData*);
   ~ElementRareData() override;
@@ -114,30 +114,6 @@
   bool HasPseudoElements() const override;
   void ClearPseudoElements() override;
 
-  void SetDidAttachInternals() override { did_attach_internals_ = true; }
-  bool DidAttachInternals() const override { return did_attach_internals_; }
-
-  void SetStyleShouldForceLegacyLayout(bool force) override {
-    style_should_force_legacy_layout_ = force;
-  }
-  bool StyleShouldForceLegacyLayout() const override {
-    return style_should_force_legacy_layout_;
-  }
-  void SetShouldForceLegacyLayoutForChild(bool force) override {
-    should_force_legacy_layout_for_child_ = force;
-  }
-  bool ShouldForceLegacyLayoutForChild() const override {
-    return should_force_legacy_layout_for_child_;
-  }
-  bool HasUndoStack() const override { return has_undo_stack_; }
-  void SetHasUndoStack(bool value) override { has_undo_stack_ = value; }
-  bool ScrollbarPseudoElementStylesDependOnFontMetrics() const override {
-    return scrollbar_pseudo_element_styles_depend_on_font_metrics_;
-  }
-  void SetScrollbarPseudoElementStylesDependOnFontMetrics(bool value) override {
-    scrollbar_pseudo_element_styles_depend_on_font_metrics_ = value;
-  }
-
   AttrNodeList& EnsureAttrNodeList() override;
   AttrNodeList* GetAttrNodeList() override { return attr_node_list_.Get(); }
   void RemoveAttrNodeList() override { attr_node_list_.Clear(); }
@@ -287,96 +263,6 @@
   void RemoveAnchorScrollData() override { anchor_scroll_data_ = nullptr; }
   AnchorScrollData& EnsureAnchorScrollData(Element*) override;
 
-  FocusgroupFlags GetFocusgroupFlags() const override {
-    return focusgroup_flags_;
-  }
-  void SetFocusgroupFlags(FocusgroupFlags flags) override {
-    focusgroup_flags_ = flags;
-  }
-  void ClearFocusgroupFlags() override {
-    focusgroup_flags_ = FocusgroupFlags::kNone;
-  }
-
-  bool AffectedBySubjectHas() const override {
-    return has_invalidation_flags_.affected_by_subject_has;
-  }
-  void SetAffectedBySubjectHas() override {
-    has_invalidation_flags_.affected_by_subject_has = true;
-  }
-  bool AffectedByNonSubjectHas() const override {
-    return has_invalidation_flags_.affected_by_non_subject_has;
-  }
-  void SetAffectedByNonSubjectHas() override {
-    has_invalidation_flags_.affected_by_non_subject_has = true;
-  }
-  bool AncestorsOrAncestorSiblingsAffectedByHas() const override {
-    return has_invalidation_flags_
-        .ancestors_or_ancestor_siblings_affected_by_has;
-  }
-  void SetAncestorsOrAncestorSiblingsAffectedByHas() override {
-    has_invalidation_flags_.ancestors_or_ancestor_siblings_affected_by_has =
-        true;
-  }
-  unsigned GetSiblingsAffectedByHasFlags() const override {
-    return has_invalidation_flags_.siblings_affected_by_has;
-  }
-  bool HasSiblingsAffectedByHasFlags(unsigned flags) const override {
-    return has_invalidation_flags_.siblings_affected_by_has & flags;
-  }
-  void SetSiblingsAffectedByHasFlags(unsigned flags) override {
-    has_invalidation_flags_.siblings_affected_by_has |= flags;
-  }
-  bool AffectedByPseudoInHas() const override {
-    return has_invalidation_flags_.affected_by_pseudos_in_has;
-  }
-  void SetAffectedByPseudoInHas() override {
-    has_invalidation_flags_.affected_by_pseudos_in_has = true;
-  }
-  bool AncestorsOrSiblingsAffectedByHoverInHas() const override {
-    return has_invalidation_flags_
-        .ancestors_or_siblings_affected_by_hover_in_has;
-  }
-  void SetAncestorsOrSiblingsAffectedByHoverInHas() override {
-    has_invalidation_flags_.ancestors_or_siblings_affected_by_hover_in_has =
-        true;
-  }
-  bool AncestorsOrSiblingsAffectedByActiveInHas() const override {
-    return has_invalidation_flags_
-        .ancestors_or_siblings_affected_by_active_in_has;
-  }
-  void SetAncestorsOrSiblingsAffectedByActiveInHas() override {
-    has_invalidation_flags_.ancestors_or_siblings_affected_by_active_in_has =
-        true;
-  }
-  bool AncestorsOrSiblingsAffectedByFocusInHas() const override {
-    return has_invalidation_flags_
-        .ancestors_or_siblings_affected_by_focus_in_has;
-  }
-  void SetAncestorsOrSiblingsAffectedByFocusInHas() override {
-    has_invalidation_flags_.ancestors_or_siblings_affected_by_focus_in_has =
-        true;
-  }
-  bool AncestorsOrSiblingsAffectedByFocusVisibleInHas() const override {
-    return has_invalidation_flags_
-        .ancestors_or_siblings_affected_by_focus_visible_in_has;
-  }
-  void SetAncestorsOrSiblingsAffectedByFocusVisibleInHas() override {
-    has_invalidation_flags_
-        .ancestors_or_siblings_affected_by_focus_visible_in_has = true;
-  }
-  bool AffectedByLogicalCombinationsInHas() const override {
-    return has_invalidation_flags_.affected_by_logical_combinations_in_has;
-  }
-  void SetAffectedByLogicalCombinationsInHas() override {
-    has_invalidation_flags_.affected_by_logical_combinations_in_has = true;
-  }
-  bool AffectedByMultipleHas() const override {
-    return has_invalidation_flags_.affected_by_multiple_has;
-  }
-  void SetAffectedByMultipleHas() override {
-    has_invalidation_flags_.affected_by_multiple_has = true;
-  }
-
   bool HasElementFlag(ElementFlags mask) const override {
     return element_flags_ & static_cast<uint16_t>(mask);
   }
@@ -388,6 +274,7 @@
   void ClearElementFlag(ElementFlags mask) override {
     element_flags_ &= ~static_cast<uint16_t>(mask);
   }
+
   bool HasRestyleFlags() const override {
     return bit_field_.get<RestyleFlags>();
   }
@@ -396,7 +283,6 @@
   void SetTabIndexExplicitly() override {
     SetElementFlag(ElementFlags::kTabIndexWasSetExplicitly, true);
   }
-
   void ClearTabIndexExplicitly() override {
     ClearElementFlag(ElementFlags::kTabIndexWasSetExplicitly);
   }
@@ -411,8 +297,6 @@
   void Trace(blink::Visitor*) const override;
 
  private:
-  ScrollOffset saved_layer_scroll_offset_;
-
   AtomicString nonce_;
   AtomicString is_value_;
 
@@ -442,16 +326,8 @@
   Member<CSSToggleMap> toggle_map_;
   Member<AnchorScrollData> anchor_scroll_data_;
 
-  FocusgroupFlags focusgroup_flags_ = FocusgroupFlags::kNone;
-  HasInvalidationFlags has_invalidation_flags_;
-
+  ScrollOffset saved_layer_scroll_offset_;
   wtf_size_t anchored_popover_count_ = 0;
-
-  unsigned did_attach_internals_ : 1;
-  unsigned should_force_legacy_layout_for_child_ : 1;
-  unsigned style_should_force_legacy_layout_ : 1;
-  unsigned has_undo_stack_ : 1;
-  unsigned scrollbar_pseudo_element_styles_depend_on_font_metrics_ : 1;
 };
 
 inline LayoutSize DefaultMinimumSizeForResizing() {
diff --git a/third_party/blink/renderer/core/dom/element_rare_data_base.h b/third_party/blink/renderer/core/dom/element_rare_data_base.h
index a2e87b9..62506c6e9 100644
--- a/third_party/blink/renderer/core/dom/element_rare_data_base.h
+++ b/third_party/blink/renderer/core/dom/element_rare_data_base.h
@@ -6,6 +6,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_DOM_ELEMENT_RARE_DATA_BASE_H_
 
 #include "third_party/blink/renderer/core/dom/focusgroup_flags.h"
+#include "third_party/blink/renderer/core/dom/node_rare_data.h"
 #include "third_party/blink/renderer/core/dom/pseudo_element.h"
 #include "third_party/blink/renderer/core/dom/pseudo_element_data.h"
 #include "third_party/blink/renderer/platform/region_capture_crop_id.h"
@@ -35,11 +36,15 @@
 class ResizeObserverSize;
 class PopoverData;
 class CSSToggleMap;
+class HTMLElement;
 
 enum class ElementFlags;
 
-class ElementRareDataBase : public GarbageCollectedMixin {
+class ElementRareDataBase : public NodeRareData {
  public:
+  explicit ElementRareDataBase(NodeRenderingData* node_layout_data)
+      : NodeRareData(ClassType::kElementRareData, node_layout_data) {}
+
   virtual void SetPseudoElement(
       PseudoId,
       PseudoElement*,
@@ -73,19 +78,6 @@
   virtual bool HasPseudoElements() const = 0;
   virtual void ClearPseudoElements() = 0;
 
-  virtual void SetDidAttachInternals() = 0;
-  virtual bool DidAttachInternals() const = 0;
-
-  virtual void SetStyleShouldForceLegacyLayout(bool force) = 0;
-  virtual bool StyleShouldForceLegacyLayout() const = 0;
-  virtual void SetShouldForceLegacyLayoutForChild(bool force) = 0;
-  virtual bool ShouldForceLegacyLayoutForChild() const = 0;
-  virtual bool HasUndoStack() const = 0;
-  virtual void SetHasUndoStack(bool value) = 0;
-  virtual bool ScrollbarPseudoElementStylesDependOnFontMetrics() const = 0;
-  virtual void SetScrollbarPseudoElementStylesDependOnFontMetrics(
-      bool value) = 0;
-
   virtual AttrNodeList& EnsureAttrNodeList() = 0;
   virtual AttrNodeList* GetAttrNodeList() = 0;
   virtual void RemoveAttrNodeList() = 0;
@@ -159,33 +151,89 @@
   virtual CSSToggleMap* GetToggleMap() const = 0;
   virtual CSSToggleMap& EnsureToggleMap(Element* owner_element) = 0;
 
-  virtual FocusgroupFlags GetFocusgroupFlags() const = 0;
-  virtual void SetFocusgroupFlags(FocusgroupFlags flags) = 0;
-  virtual void ClearFocusgroupFlags() = 0;
+  FocusgroupFlags GetFocusgroupFlags() const { return focusgroup_flags_; }
+  void SetFocusgroupFlags(FocusgroupFlags flags) { focusgroup_flags_ = flags; }
+  void ClearFocusgroupFlags() { focusgroup_flags_ = FocusgroupFlags::kNone; }
 
-  virtual bool AffectedBySubjectHas() const = 0;
-  virtual void SetAffectedBySubjectHas() = 0;
-  virtual bool AffectedByNonSubjectHas() const = 0;
-  virtual void SetAffectedByNonSubjectHas() = 0;
-  virtual bool AncestorsOrAncestorSiblingsAffectedByHas() const = 0;
-  virtual void SetAncestorsOrAncestorSiblingsAffectedByHas() = 0;
-  virtual unsigned GetSiblingsAffectedByHasFlags() const = 0;
-  virtual bool HasSiblingsAffectedByHasFlags(unsigned flags) const = 0;
-  virtual void SetSiblingsAffectedByHasFlags(unsigned flags) = 0;
-  virtual bool AffectedByPseudoInHas() const = 0;
-  virtual void SetAffectedByPseudoInHas() = 0;
-  virtual bool AncestorsOrSiblingsAffectedByHoverInHas() const = 0;
-  virtual void SetAncestorsOrSiblingsAffectedByHoverInHas() = 0;
-  virtual bool AncestorsOrSiblingsAffectedByActiveInHas() const = 0;
-  virtual void SetAncestorsOrSiblingsAffectedByActiveInHas() = 0;
-  virtual bool AncestorsOrSiblingsAffectedByFocusInHas() const = 0;
-  virtual void SetAncestorsOrSiblingsAffectedByFocusInHas() = 0;
-  virtual bool AncestorsOrSiblingsAffectedByFocusVisibleInHas() const = 0;
-  virtual void SetAncestorsOrSiblingsAffectedByFocusVisibleInHas() = 0;
-  virtual bool AffectedByLogicalCombinationsInHas() const = 0;
-  virtual void SetAffectedByLogicalCombinationsInHas() = 0;
-  virtual bool AffectedByMultipleHas() const = 0;
-  virtual void SetAffectedByMultipleHas() = 0;
+  bool AffectedBySubjectHas() const {
+    return has_invalidation_flags_.affected_by_subject_has;
+  }
+  void SetAffectedBySubjectHas() {
+    has_invalidation_flags_.affected_by_subject_has = true;
+  }
+  bool AffectedByNonSubjectHas() const {
+    return has_invalidation_flags_.affected_by_non_subject_has;
+  }
+  void SetAffectedByNonSubjectHas() {
+    has_invalidation_flags_.affected_by_non_subject_has = true;
+  }
+  bool AncestorsOrAncestorSiblingsAffectedByHas() const {
+    return has_invalidation_flags_
+        .ancestors_or_ancestor_siblings_affected_by_has;
+  }
+  void SetAncestorsOrAncestorSiblingsAffectedByHas() {
+    has_invalidation_flags_.ancestors_or_ancestor_siblings_affected_by_has =
+        true;
+  }
+  unsigned GetSiblingsAffectedByHasFlags() const {
+    return has_invalidation_flags_.siblings_affected_by_has;
+  }
+  bool HasSiblingsAffectedByHasFlags(unsigned flags) const {
+    return has_invalidation_flags_.siblings_affected_by_has & flags;
+  }
+  void SetSiblingsAffectedByHasFlags(unsigned flags) {
+    has_invalidation_flags_.siblings_affected_by_has |= flags;
+  }
+  bool AffectedByPseudoInHas() const {
+    return has_invalidation_flags_.affected_by_pseudos_in_has;
+  }
+  void SetAffectedByPseudoInHas() {
+    has_invalidation_flags_.affected_by_pseudos_in_has = true;
+  }
+  bool AncestorsOrSiblingsAffectedByHoverInHas() const {
+    return has_invalidation_flags_
+        .ancestors_or_siblings_affected_by_hover_in_has;
+  }
+  void SetAncestorsOrSiblingsAffectedByHoverInHas() {
+    has_invalidation_flags_.ancestors_or_siblings_affected_by_hover_in_has =
+        true;
+  }
+  bool AncestorsOrSiblingsAffectedByActiveInHas() const {
+    return has_invalidation_flags_
+        .ancestors_or_siblings_affected_by_active_in_has;
+  }
+  void SetAncestorsOrSiblingsAffectedByActiveInHas() {
+    has_invalidation_flags_.ancestors_or_siblings_affected_by_active_in_has =
+        true;
+  }
+  bool AncestorsOrSiblingsAffectedByFocusInHas() const {
+    return has_invalidation_flags_
+        .ancestors_or_siblings_affected_by_focus_in_has;
+  }
+  void SetAncestorsOrSiblingsAffectedByFocusInHas() {
+    has_invalidation_flags_.ancestors_or_siblings_affected_by_focus_in_has =
+        true;
+  }
+  bool AncestorsOrSiblingsAffectedByFocusVisibleInHas() const {
+    return has_invalidation_flags_
+        .ancestors_or_siblings_affected_by_focus_visible_in_has;
+  }
+  void SetAncestorsOrSiblingsAffectedByFocusVisibleInHas() {
+    has_invalidation_flags_
+        .ancestors_or_siblings_affected_by_focus_visible_in_has = true;
+  }
+  bool AffectedByLogicalCombinationsInHas() const {
+    return has_invalidation_flags_.affected_by_logical_combinations_in_has;
+  }
+  void SetAffectedByLogicalCombinationsInHas() {
+    has_invalidation_flags_.affected_by_logical_combinations_in_has = true;
+  }
+  bool AffectedByMultipleHas() const {
+    return has_invalidation_flags_.affected_by_multiple_has;
+  }
+  void SetAffectedByMultipleHas() {
+    has_invalidation_flags_.affected_by_multiple_has = true;
+  }
 
   virtual void SetTabIndexExplicitly() = 0;
   virtual void ClearTabIndexExplicitly() = 0;
@@ -204,6 +252,38 @@
   virtual void ClearElementFlag(ElementFlags mask) = 0;
   virtual bool HasRestyleFlags() const = 0;
   virtual void ClearRestyleFlags() = 0;
+
+  void SetDidAttachInternals() { did_attach_internals_ = true; }
+  bool DidAttachInternals() const { return did_attach_internals_; }
+  void SetStyleShouldForceLegacyLayout(bool force) {
+    style_should_force_legacy_layout_ = force;
+  }
+  bool StyleShouldForceLegacyLayout() const {
+    return style_should_force_legacy_layout_;
+  }
+  void SetShouldForceLegacyLayoutForChild(bool force) {
+    should_force_legacy_layout_for_child_ = force;
+  }
+  bool ShouldForceLegacyLayoutForChild() const {
+    return should_force_legacy_layout_for_child_;
+  }
+  bool HasUndoStack() const { return has_undo_stack_; }
+  void SetHasUndoStack(bool value) { has_undo_stack_ = value; }
+  bool ScrollbarPseudoElementStylesDependOnFontMetrics() const {
+    return scrollbar_pseudo_element_styles_depend_on_font_metrics_;
+  }
+  void SetScrollbarPseudoElementStylesDependOnFontMetrics(bool value) {
+    scrollbar_pseudo_element_styles_depend_on_font_metrics_ = value;
+  }
+
+ private:
+  unsigned did_attach_internals_ : 1;
+  unsigned should_force_legacy_layout_for_child_ : 1;
+  unsigned style_should_force_legacy_layout_ : 1;
+  unsigned has_undo_stack_ : 1;
+  unsigned scrollbar_pseudo_element_styles_depend_on_font_metrics_ : 1;
+  HasInvalidationFlags has_invalidation_flags_;
+  FocusgroupFlags focusgroup_flags_ = FocusgroupFlags::kNone;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/dom/element_rare_data_field.h b/third_party/blink/renderer/core/dom/element_rare_data_field.h
new file mode 100644
index 0000000..11c0cb21
--- /dev/null
+++ b/third_party/blink/renderer/core/dom/element_rare_data_field.h
@@ -0,0 +1,20 @@
+// Copyright 2022 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.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_DOM_ELEMENT_RARE_DATA_FIELD_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_DOM_ELEMENT_RARE_DATA_FIELD_H_
+
+#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
+#include "third_party/blink/renderer/platform/wtf/casting.h"
+
+namespace blink {
+
+class ElementRareDataField : public GarbageCollectedMixin {
+ public:
+  void Trace(Visitor* visitor) const override {}
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_DOM_ELEMENT_RARE_DATA_FIELD_H_
diff --git a/third_party/blink/renderer/core/dom/element_rare_data_vector.cc b/third_party/blink/renderer/core/dom/element_rare_data_vector.cc
new file mode 100644
index 0000000..2dcb62f
--- /dev/null
+++ b/third_party/blink/renderer/core/dom/element_rare_data_vector.cc
@@ -0,0 +1,404 @@
+// Copyright 2022 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.
+
+#include "third_party/blink/renderer/core/dom/element_rare_data_vector.h"
+
+#include "third_party/blink/renderer/core/animation/element_animations.h"
+#include "third_party/blink/renderer/core/aom/accessible_node.h"
+#include "third_party/blink/renderer/core/css/container_query_data.h"
+#include "third_party/blink/renderer/core/css/cssom/inline_style_property_map.h"
+#include "third_party/blink/renderer/core/css/inline_css_style_declaration.h"
+#include "third_party/blink/renderer/core/display_lock/display_lock_context.h"
+#include "third_party/blink/renderer/core/dom/attr.h"
+#include "third_party/blink/renderer/core/dom/css_toggle_map.h"
+#include "third_party/blink/renderer/core/dom/dataset_dom_string_map.h"
+#include "third_party/blink/renderer/core/dom/dom_token_list.h"
+#include "third_party/blink/renderer/core/dom/element_rare_data_base.h"
+#include "third_party/blink/renderer/core/dom/has_invalidation_flags.h"
+#include "third_party/blink/renderer/core/dom/named_node_map.h"
+#include "third_party/blink/renderer/core/dom/names_map.h"
+#include "third_party/blink/renderer/core/dom/node_rare_data.h"
+#include "third_party/blink/renderer/core/dom/popover_data.h"
+#include "third_party/blink/renderer/core/dom/shadow_root.h"
+#include "third_party/blink/renderer/core/dom/space_split_string.h"
+#include "third_party/blink/renderer/core/editing/ime/edit_context.h"
+#include "third_party/blink/renderer/core/html/custom/custom_element_definition.h"
+#include "third_party/blink/renderer/core/html/custom/element_internals.h"
+#include "third_party/blink/renderer/core/html/html_element.h"
+#include "third_party/blink/renderer/core/intersection_observer/element_intersection_observer_data.h"
+#include "third_party/blink/renderer/core/layout/anchor_scroll_data.h"
+#include "third_party/blink/renderer/core/resize_observer/resize_observation.h"
+#include "third_party/blink/renderer/core/resize_observer/resize_observer.h"
+#include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_map.h"
+#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
+
+namespace blink {
+
+ElementRareDataVector::ElementRareDataVector(
+    NodeRenderingData* node_layout_data)
+    : ElementRareDataBase(node_layout_data) {}
+
+ElementRareDataVector::~ElementRareDataVector() {
+  DCHECK(!GetField(FieldId::kPseudoElementData));
+}
+
+unsigned ElementRareDataVector::GetFieldIndex(FieldId field_id) const {
+  unsigned field_id_int = static_cast<unsigned>(field_id);
+  DCHECK(fields_bitfield_ & (static_cast<BitfieldType>(1) << field_id_int));
+  return __builtin_popcount(fields_bitfield_ &
+                            ~(~static_cast<BitfieldType>(0) << field_id_int));
+}
+
+ElementRareDataField* ElementRareDataVector::GetField(FieldId field_id) const {
+  if (fields_bitfield_ &
+      (static_cast<BitfieldType>(1) << static_cast<unsigned>(field_id)))
+    return fields_[GetFieldIndex(field_id)];
+  return nullptr;
+}
+
+void ElementRareDataVector::SetField(FieldId field_id,
+                                     ElementRareDataField* field) {
+  unsigned field_id_int = static_cast<unsigned>(field_id);
+  if (fields_bitfield_ & (static_cast<BitfieldType>(1) << field_id_int)) {
+    if (field) {
+      fields_[GetFieldIndex(field_id)] = field;
+    } else {
+      fields_.EraseAt(GetFieldIndex(field_id));
+      fields_bitfield_ =
+          fields_bitfield_ & ~(static_cast<BitfieldType>(1) << field_id_int);
+    }
+  } else if (field) {
+    fields_bitfield_ =
+        fields_bitfield_ | (static_cast<BitfieldType>(1) << field_id_int);
+    fields_.insert(GetFieldIndex(field_id), field);
+  }
+}
+
+bool ElementRareDataVector::HasPseudoElements() const {
+  PseudoElementData* data =
+      static_cast<PseudoElementData*>(GetField(FieldId::kPseudoElementData));
+  if (!data)
+    return false;
+  return data->HasPseudoElements();
+}
+void ElementRareDataVector::ClearPseudoElements() {
+  PseudoElementData* data =
+      static_cast<PseudoElementData*>(GetField(FieldId::kPseudoElementData));
+  if (data) {
+    data->ClearPseudoElements();
+    SetField(FieldId::kPseudoElementData, nullptr);
+  }
+}
+void ElementRareDataVector::SetPseudoElement(
+    PseudoId pseudo_id,
+    PseudoElement* element,
+    const AtomicString& document_transition_tag) {
+  PseudoElementData* data =
+      static_cast<PseudoElementData*>(GetField(FieldId::kPseudoElementData));
+  if (!data) {
+    if (!element)
+      return;
+    data = MakeGarbageCollected<PseudoElementData>();
+    SetField(FieldId::kPseudoElementData, data);
+  }
+  data->SetPseudoElement(pseudo_id, element, document_transition_tag);
+}
+PseudoElement* ElementRareDataVector::GetPseudoElement(
+    PseudoId pseudo_id,
+    const AtomicString& document_transition_tag) const {
+  PseudoElementData* data =
+      static_cast<PseudoElementData*>(GetField(FieldId::kPseudoElementData));
+  if (!data)
+    return nullptr;
+  return data->GetPseudoElement(pseudo_id, document_transition_tag);
+}
+PseudoElementData::PseudoElementVector
+ElementRareDataVector::GetPseudoElements() const {
+  PseudoElementData* data =
+      static_cast<PseudoElementData*>(GetField(FieldId::kPseudoElementData));
+  if (!data)
+    return {};
+  return data->GetPseudoElements();
+}
+
+CSSStyleDeclaration& ElementRareDataVector::EnsureInlineCSSStyleDeclaration(
+    Element* owner_element) {
+  return EnsureField<InlineCSSStyleDeclaration>(FieldId::kCssomWrapper,
+                                                owner_element);
+}
+
+ShadowRoot* ElementRareDataVector::GetShadowRoot() const {
+  return static_cast<ShadowRoot*>(GetField(FieldId::kShadowRoot));
+}
+void ElementRareDataVector::SetShadowRoot(ShadowRoot& shadow_root) {
+  DCHECK(!GetField(FieldId::kShadowRoot));
+  SetField(FieldId::kShadowRoot, &shadow_root);
+}
+
+NamedNodeMap* ElementRareDataVector::AttributeMap() const {
+  return static_cast<NamedNodeMap*>(GetField(FieldId::kAttributeMap));
+}
+void ElementRareDataVector::SetAttributeMap(NamedNodeMap* attribute_map) {
+  SetField(FieldId::kAttributeMap, attribute_map);
+}
+
+DOMTokenList* ElementRareDataVector::GetClassList() const {
+  return static_cast<DOMTokenList*>(GetField(FieldId::kClassList));
+}
+void ElementRareDataVector::SetClassList(DOMTokenList* class_list) {
+  SetField(FieldId::kClassList, class_list);
+}
+
+DatasetDOMStringMap* ElementRareDataVector::Dataset() const {
+  return static_cast<DatasetDOMStringMap*>(GetField(FieldId::kDataset));
+}
+void ElementRareDataVector::SetDataset(DatasetDOMStringMap* dataset) {
+  SetField(FieldId::kDataset, dataset);
+}
+
+ScrollOffset ElementRareDataVector::SavedLayerScrollOffset() const {
+  if (auto* value =
+          GetWrappedField<ScrollOffset>(FieldId::kSavedLayerScrollOffset)) {
+    return *value;
+  }
+  static ScrollOffset offset;
+  return offset;
+}
+void ElementRareDataVector::SetSavedLayerScrollOffset(ScrollOffset offset) {
+  SetWrappedField<ScrollOffset>(FieldId::kSavedLayerScrollOffset, offset);
+}
+
+ElementAnimations* ElementRareDataVector::GetElementAnimations() {
+  return static_cast<ElementAnimations*>(GetField(FieldId::kElementAnimations));
+}
+void ElementRareDataVector::SetElementAnimations(
+    ElementAnimations* element_animations) {
+  SetField(FieldId::kElementAnimations, element_animations);
+}
+
+AttrNodeList& ElementRareDataVector::EnsureAttrNodeList() {
+  return EnsureWrappedField<AttrNodeList>(FieldId::kAttrNodeList);
+}
+AttrNodeList* ElementRareDataVector::GetAttrNodeList() {
+  return GetWrappedField<AttrNodeList>(FieldId::kAttrNodeList);
+}
+void ElementRareDataVector::RemoveAttrNodeList() {
+  SetField(FieldId::kAttrNodeList, nullptr);
+}
+void ElementRareDataVector::AddAttr(Attr* attr) {
+  EnsureAttrNodeList().push_back(attr);
+}
+
+ElementIntersectionObserverData*
+ElementRareDataVector::IntersectionObserverData() const {
+  return static_cast<ElementIntersectionObserverData*>(
+      GetField(FieldId::kIntersectionObserverData));
+}
+ElementIntersectionObserverData&
+ElementRareDataVector::EnsureIntersectionObserverData() {
+  return EnsureField<ElementIntersectionObserverData>(
+      FieldId::kIntersectionObserverData);
+}
+
+ContainerQueryEvaluator* ElementRareDataVector::GetContainerQueryEvaluator()
+    const {
+  ContainerQueryData* container_query_data = GetContainerQueryData();
+  if (!container_query_data)
+    return nullptr;
+  return container_query_data->GetContainerQueryEvaluator();
+}
+void ElementRareDataVector::SetContainerQueryEvaluator(
+    ContainerQueryEvaluator* evaluator) {
+  ContainerQueryData* container_query_data = GetContainerQueryData();
+  if (container_query_data)
+    container_query_data->SetContainerQueryEvaluator(evaluator);
+  else if (evaluator)
+    EnsureContainerQueryData().SetContainerQueryEvaluator(evaluator);
+}
+
+const AtomicString& ElementRareDataVector::GetNonce() const {
+  auto* value = GetWrappedField<AtomicString>(FieldId::kNonce);
+  return value ? *value : g_null_atom;
+}
+void ElementRareDataVector::SetNonce(const AtomicString& nonce) {
+  SetWrappedField<AtomicString>(FieldId::kNonce, nonce);
+}
+
+const AtomicString& ElementRareDataVector::IsValue() const {
+  auto* value = GetWrappedField<AtomicString>(FieldId::kIsValue);
+  return value ? *value : g_null_atom;
+}
+void ElementRareDataVector::SetIsValue(const AtomicString& is_value) {
+  SetWrappedField<AtomicString>(FieldId::kIsValue, is_value);
+}
+
+EditContext* ElementRareDataVector::GetEditContext() const {
+  return static_cast<EditContext*>(GetField(FieldId::kEditContext));
+}
+void ElementRareDataVector::SetEditContext(EditContext* edit_context) {
+  SetField(FieldId::kEditContext, edit_context);
+}
+
+void ElementRareDataVector::SetPart(DOMTokenList* part) {
+  SetField(FieldId::kPart, part);
+}
+DOMTokenList* ElementRareDataVector::GetPart() const {
+  return static_cast<DOMTokenList*>(GetField(FieldId::kPart));
+}
+
+void ElementRareDataVector::SetPartNamesMap(const AtomicString part_names) {
+  EnsureWrappedField<NamesMap>(FieldId::kPartNamesMap).Set(part_names);
+}
+const NamesMap* ElementRareDataVector::PartNamesMap() const {
+  return GetWrappedField<NamesMap>(FieldId::kPartNamesMap);
+}
+
+InlineStylePropertyMap& ElementRareDataVector::EnsureInlineStylePropertyMap(
+    Element* owner_element) {
+  return EnsureField<InlineStylePropertyMap>(FieldId::kCssomMapWrapper,
+                                             owner_element);
+}
+InlineStylePropertyMap* ElementRareDataVector::GetInlineStylePropertyMap() {
+  return static_cast<InlineStylePropertyMap*>(
+      GetField(FieldId::kCssomMapWrapper));
+}
+
+const ElementInternals* ElementRareDataVector::GetElementInternals() const {
+  return static_cast<ElementInternals*>(GetField(FieldId::kElementInternals));
+}
+ElementInternals& ElementRareDataVector::EnsureElementInternals(
+    HTMLElement& target) {
+  return EnsureField<ElementInternals>(FieldId::kElementInternals, target);
+}
+
+AccessibleNode* ElementRareDataVector::GetAccessibleNode() const {
+  return static_cast<AccessibleNode*>(GetField(FieldId::kAccessibleNode));
+}
+AccessibleNode* ElementRareDataVector::EnsureAccessibleNode(
+    Element* owner_element) {
+  return &EnsureField<AccessibleNode>(FieldId::kAccessibleNode, owner_element);
+}
+void ElementRareDataVector::ClearAccessibleNode() {
+  SetField(FieldId::kAccessibleNode, nullptr);
+}
+
+DisplayLockContext* ElementRareDataVector::EnsureDisplayLockContext(
+    Element* element) {
+  return &EnsureField<DisplayLockContext>(FieldId::kDisplayLockContext,
+                                          element);
+}
+DisplayLockContext* ElementRareDataVector::GetDisplayLockContext() const {
+  return static_cast<DisplayLockContext*>(
+      GetField(FieldId::kDisplayLockContext));
+}
+
+ContainerQueryData& ElementRareDataVector::EnsureContainerQueryData() {
+  return EnsureField<ContainerQueryData>(FieldId::kContainerQueryData);
+}
+ContainerQueryData* ElementRareDataVector::GetContainerQueryData() const {
+  return static_cast<ContainerQueryData*>(
+      GetField(FieldId::kContainerQueryData));
+}
+void ElementRareDataVector::ClearContainerQueryData() {
+  SetField(FieldId::kContainerQueryData, nullptr);
+}
+
+const RegionCaptureCropId* ElementRareDataVector::GetRegionCaptureCropId()
+    const {
+  auto* value = GetWrappedField<std::unique_ptr<RegionCaptureCropId>>(
+      FieldId::kRegionCaptureCropId);
+  return value ? value->get() : nullptr;
+}
+void ElementRareDataVector::SetRegionCaptureCropId(
+    std::unique_ptr<RegionCaptureCropId> crop_id) {
+  DCHECK(!GetRegionCaptureCropId());
+  DCHECK(crop_id);
+  DCHECK(!crop_id->value().is_zero());
+  SetWrappedField<std::unique_ptr<RegionCaptureCropId>>(
+      FieldId::kRegionCaptureCropId, std::move(crop_id));
+}
+
+ElementRareDataVector::ResizeObserverDataMap*
+ElementRareDataVector::ResizeObserverData() const {
+  return GetWrappedField<ElementRareDataVector::ResizeObserverDataMap>(
+      FieldId::kResizeObserverData);
+}
+ElementRareDataVector::ResizeObserverDataMap&
+ElementRareDataVector::EnsureResizeObserverData() {
+  return EnsureWrappedField<ElementRareDataVector::ResizeObserverDataMap>(
+      FieldId::kResizeObserverData);
+}
+
+void ElementRareDataVector::SetCustomElementDefinition(
+    CustomElementDefinition* definition) {
+  SetField(FieldId::kCustomElementDefinition, definition);
+}
+CustomElementDefinition* ElementRareDataVector::GetCustomElementDefinition()
+    const {
+  return static_cast<CustomElementDefinition*>(
+      GetField(FieldId::kCustomElementDefinition));
+}
+
+void ElementRareDataVector::SaveLastIntrinsicSize(ResizeObserverSize* size) {
+  SetField(FieldId::kLastIntrinsicSize, size);
+}
+const ResizeObserverSize* ElementRareDataVector::LastIntrinsicSize() const {
+  return static_cast<ResizeObserverSize*>(
+      GetField(FieldId::kLastIntrinsicSize));
+}
+
+PopoverData* ElementRareDataVector::GetPopoverData() const {
+  return static_cast<PopoverData*>(GetField(FieldId::kPopoverData));
+}
+PopoverData& ElementRareDataVector::EnsurePopoverData() {
+  return EnsureField<PopoverData>(FieldId::kPopoverData);
+}
+void ElementRareDataVector::RemovePopoverData() {
+  SetField(FieldId::kPopoverData, nullptr);
+}
+
+CSSToggleMap* ElementRareDataVector::GetToggleMap() const {
+  return static_cast<CSSToggleMap*>(GetField(FieldId::kToggleMap));
+}
+CSSToggleMap& ElementRareDataVector::EnsureToggleMap(Element* owner_element) {
+  DCHECK(!GetToggleMap() || GetToggleMap()->OwnerElement() == owner_element);
+  return EnsureField<CSSToggleMap>(FieldId::kToggleMap, owner_element);
+}
+
+AnchorScrollData* ElementRareDataVector::GetAnchorScrollData() const {
+  return static_cast<AnchorScrollData*>(GetField(FieldId::kAnchorScrollData));
+}
+void ElementRareDataVector::RemoveAnchorScrollData() {
+  SetField(FieldId::kAnchorScrollData, nullptr);
+}
+AnchorScrollData& ElementRareDataVector::EnsureAnchorScrollData(
+    Element* owner_element) {
+  DCHECK(!GetAnchorScrollData() ||
+         GetAnchorScrollData()->OwnerElement() == owner_element);
+  return EnsureField<AnchorScrollData>(FieldId::kAnchorScrollData,
+                                       owner_element);
+}
+
+void ElementRareDataVector::IncrementAnchoredPopoverCount() {
+  EnsureWrappedField<wtf_size_t>(FieldId::kAnchoredPopoverCount)++;
+}
+void ElementRareDataVector::DecrementAnchoredPopoverCount() {
+  wtf_size_t& popover_count =
+      EnsureWrappedField<wtf_size_t>(FieldId::kAnchoredPopoverCount);
+  DCHECK(popover_count);
+  popover_count--;
+}
+bool ElementRareDataVector::HasAnchoredPopover() const {
+  wtf_size_t* popover_count =
+      GetWrappedField<wtf_size_t>(FieldId::kAnchoredPopoverCount);
+  return popover_count ? *popover_count : false;
+}
+
+void ElementRareDataVector::Trace(blink::Visitor* visitor) const {
+  visitor->Trace(fields_);
+  NodeRareData::Trace(visitor);
+  ElementRareDataBase::Trace(visitor);
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/core/dom/element_rare_data_vector.h b/third_party/blink/renderer/core/dom/element_rare_data_vector.h
new file mode 100644
index 0000000..4c4ffe9
--- /dev/null
+++ b/third_party/blink/renderer/core/dom/element_rare_data_vector.h
@@ -0,0 +1,264 @@
+// Copyright 2022 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.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_DOM_ELEMENT_RARE_DATA_VECTOR_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_DOM_ELEMENT_RARE_DATA_VECTOR_H_
+
+#include "third_party/blink/renderer/core/dom/element_rare_data_base.h"
+#include "third_party/blink/renderer/core/dom/element_rare_data_field.h"
+#include "third_party/blink/renderer/platform/heap/trace_traits.h"
+
+namespace blink {
+
+// This class stores lazily-initialized state associated with Elements, each of
+// which is identified in the FieldId enum. Since storing pointers to all of
+// these classes would take up too much memory, we use a Vector and only include
+// the types that have actually been requested. In order to determine which
+// index into the vector each type has, an additional bitfield is used to
+// indicate which types are currently included in the vector.
+//
+// Here is an example of what the vector and bitfield would look like if this
+// class has initialized a ShadowRoot and an EditContext. We can figure out that
+// the first item in the vector is a ShadowRoot because ShadowRoot's spot in the
+// bitfield is 1 and everything to the right is a 0. We can figure out that the
+// second item is an EditContext because EditContext's spot in the bitfield is a
+// 1 and there is one 1 in all of the bits to the right.
+// Vector:
+//   0: Member<ShadowRoot>
+//   1: Member<EditContext>
+// Bitfield: 0b00000000000000000000001000000010
+class CORE_EXPORT ElementRareDataVector final : public ElementRareDataBase {
+ private:
+  friend class ElementRareDataVectorTest;
+  enum class FieldId : unsigned {
+    kDataset = 0,
+    kShadowRoot = 1,
+    kClassList = 2,
+    kAttributeMap = 3,
+    kAttrNodeList = 4,
+    kCssomWrapper = 5,
+    kElementAnimations = 6,
+    kIntersectionObserverData = 7,
+    kPseudoElementData = 8,
+    kEditContext = 9,
+    kPart = 10,
+    kCssomMapWrapper = 11,
+    kElementInternals = 12,
+    kAccessibleNode = 13,
+    kDisplayLockContext = 14,
+    kContainerQueryData = 15,
+    kRegionCaptureCropId = 16,
+    kResizeObserverData = 17,
+    kCustomElementDefinition = 18,
+    kLastIntrinsicSize = 19,
+    kPopoverData = 20,
+    kToggleMap = 21,
+    kPartNamesMap = 22,
+    kNonce = 23,
+    kIsValue = 24,
+    kSavedLayerScrollOffset = 25,
+    kAnchorScrollData = 26,
+    kAnchoredPopoverCount = 27,
+
+    kNumFields = 28,
+  };
+
+  ElementRareDataField* GetField(FieldId field_id) const;
+  // GetFieldIndex returns the index in |fields_| that |field_id| is stored in.
+  // If |fields_| isn't storing a field for |field_id|, then this returns the
+  // index which the data for |field_id| should be inserted into.
+  unsigned GetFieldIndex(FieldId field_id) const;
+  void SetField(FieldId field_id, ElementRareDataField* field);
+
+  HeapVector<Member<ElementRareDataField>> fields_;
+  using BitfieldType = uint32_t;
+  BitfieldType fields_bitfield_;
+  static_assert(sizeof(fields_bitfield_) * 8 >=
+                    static_cast<unsigned>(FieldId::kNumFields),
+                "field_bitfield_ must be big enough to have a bit for each "
+                "field in FieldId.");
+
+  template <typename T>
+  class DataFieldWrapper final : public GarbageCollected<DataFieldWrapper<T>>,
+                                 public ElementRareDataField {
+   public:
+    T& Get() { return data_; }
+    void Trace(Visitor* visitor) const override {
+      ElementRareDataField::Trace(visitor);
+      TraceIfNeeded<T>::Trace(visitor, data_);
+    }
+
+   private:
+    GC_PLUGIN_IGNORE("Why is std::unique_ptr failing? http://crbug.com/1395024")
+    T data_;
+  };
+
+  template <typename T, typename... Args>
+  T& EnsureField(FieldId field_id, Args&&... args) {
+    T* field = static_cast<T*>(GetField(field_id));
+    if (!field) {
+      field = MakeGarbageCollected<T>(std::forward<Args>(args)...);
+      SetField(field_id, field);
+    }
+    return *field;
+  }
+
+  template <typename T>
+  T& EnsureWrappedField(FieldId field_id) {
+    return EnsureField<DataFieldWrapper<T>>(field_id).Get();
+  }
+
+  template <typename T, typename U>
+  void SetWrappedField(FieldId field_id, U data) {
+    EnsureWrappedField<T>(field_id) = std::move(data);
+  }
+
+  template <typename T>
+  T* GetWrappedField(FieldId field_id) const {
+    auto* wrapper = static_cast<DataFieldWrapper<T>*>(GetField(field_id));
+    return wrapper ? &wrapper->Get() : nullptr;
+  }
+
+ public:
+  explicit ElementRareDataVector(NodeRenderingData*);
+  ~ElementRareDataVector() override;
+
+  void SetPseudoElement(
+      PseudoId,
+      PseudoElement*,
+      const AtomicString& document_transition_tag = g_null_atom) override;
+  PseudoElement* GetPseudoElement(
+      PseudoId,
+      const AtomicString& document_transition_tag = g_null_atom) const override;
+  PseudoElementData::PseudoElementVector GetPseudoElements() const override;
+
+  CSSStyleDeclaration& EnsureInlineCSSStyleDeclaration(
+      Element* owner_element) override;
+
+  ShadowRoot* GetShadowRoot() const override;
+  void SetShadowRoot(ShadowRoot& shadow_root) override;
+
+  NamedNodeMap* AttributeMap() const override;
+  void SetAttributeMap(NamedNodeMap* attribute_map) override;
+
+  DOMTokenList* GetClassList() const override;
+  void SetClassList(DOMTokenList* class_list) override;
+
+  DatasetDOMStringMap* Dataset() const override;
+  void SetDataset(DatasetDOMStringMap* dataset) override;
+
+  ScrollOffset SavedLayerScrollOffset() const override;
+  void SetSavedLayerScrollOffset(ScrollOffset offset) override;
+
+  ElementAnimations* GetElementAnimations() override;
+  void SetElementAnimations(ElementAnimations* element_animations) override;
+
+  bool HasPseudoElements() const override;
+  void ClearPseudoElements() override;
+
+  AttrNodeList& EnsureAttrNodeList() override;
+  AttrNodeList* GetAttrNodeList() override;
+  void RemoveAttrNodeList() override;
+  void AddAttr(Attr* attr) override;
+
+  ElementIntersectionObserverData* IntersectionObserverData() const override;
+  ElementIntersectionObserverData& EnsureIntersectionObserverData() override;
+
+  ContainerQueryEvaluator* GetContainerQueryEvaluator() const override;
+  void SetContainerQueryEvaluator(ContainerQueryEvaluator* evaluator) override;
+
+  const AtomicString& GetNonce() const override;
+  void SetNonce(const AtomicString& nonce) override;
+
+  const AtomicString& IsValue() const override;
+  void SetIsValue(const AtomicString& is_value) override;
+
+  EditContext* GetEditContext() const override;
+  void SetEditContext(EditContext* edit_context) override;
+
+  void SetPart(DOMTokenList* part) override;
+  DOMTokenList* GetPart() const override;
+
+  void SetPartNamesMap(const AtomicString part_names) override;
+  const NamesMap* PartNamesMap() const override;
+
+  InlineStylePropertyMap& EnsureInlineStylePropertyMap(
+      Element* owner_element) override;
+  InlineStylePropertyMap* GetInlineStylePropertyMap() override;
+
+  const ElementInternals* GetElementInternals() const override;
+  ElementInternals& EnsureElementInternals(HTMLElement& target) override;
+
+  AccessibleNode* GetAccessibleNode() const override;
+  AccessibleNode* EnsureAccessibleNode(Element* owner_element) override;
+  void ClearAccessibleNode() override;
+
+  DisplayLockContext* EnsureDisplayLockContext(Element* element) override;
+  DisplayLockContext* GetDisplayLockContext() const override;
+
+  ContainerQueryData& EnsureContainerQueryData() override;
+  ContainerQueryData* GetContainerQueryData() const override;
+  void ClearContainerQueryData() override;
+
+  // Returns the crop-ID if one was set, or nullptr otherwise.
+  const RegionCaptureCropId* GetRegionCaptureCropId() const override;
+  // Sets a crop-ID on the item. Must be called at most once. Cannot be used
+  // to unset a previously set crop-ID.
+  void SetRegionCaptureCropId(
+      std::unique_ptr<RegionCaptureCropId> crop_id) override;
+
+  ResizeObserverDataMap* ResizeObserverData() const override;
+  ResizeObserverDataMap& EnsureResizeObserverData() override;
+
+  void SetCustomElementDefinition(CustomElementDefinition* definition) override;
+  CustomElementDefinition* GetCustomElementDefinition() const override;
+
+  void SaveLastIntrinsicSize(ResizeObserverSize* size) override;
+  const ResizeObserverSize* LastIntrinsicSize() const override;
+
+  PopoverData* GetPopoverData() const override;
+  PopoverData& EnsurePopoverData() override;
+  void RemovePopoverData() override;
+
+  CSSToggleMap* GetToggleMap() const override;
+  CSSToggleMap& EnsureToggleMap(Element* owner_element) override;
+
+  bool HasElementFlag(ElementFlags mask) const override {
+    return element_flags_ & static_cast<uint16_t>(mask);
+  }
+  void SetElementFlag(ElementFlags mask, bool value) override {
+    element_flags_ =
+        (element_flags_ & ~static_cast<uint16_t>(mask)) |
+        (-static_cast<uint16_t>(value) & static_cast<uint16_t>(mask));
+  }
+  void ClearElementFlag(ElementFlags mask) override {
+    element_flags_ &= ~static_cast<uint16_t>(mask);
+  }
+
+  bool HasRestyleFlags() const override {
+    return bit_field_.get<RestyleFlags>();
+  }
+  void ClearRestyleFlags() override { bit_field_.set<RestyleFlags>(0); }
+
+  void SetTabIndexExplicitly() override {
+    SetElementFlag(ElementFlags::kTabIndexWasSetExplicitly, true);
+  }
+  void ClearTabIndexExplicitly() override {
+    ClearElementFlag(ElementFlags::kTabIndexWasSetExplicitly);
+  }
+
+  AnchorScrollData* GetAnchorScrollData() const override;
+  void RemoveAnchorScrollData() override;
+  AnchorScrollData& EnsureAnchorScrollData(Element*) override;
+
+  void IncrementAnchoredPopoverCount() override;
+  void DecrementAnchoredPopoverCount() override;
+  bool HasAnchoredPopover() const override;
+
+  void Trace(blink::Visitor*) const override;
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_DOM_ELEMENT_RARE_DATA_VECTOR_H_
diff --git a/third_party/blink/renderer/core/dom/element_rare_data_vector_test.cc b/third_party/blink/renderer/core/dom/element_rare_data_vector_test.cc
new file mode 100644
index 0000000..c53506a5
--- /dev/null
+++ b/third_party/blink/renderer/core/dom/element_rare_data_vector_test.cc
@@ -0,0 +1,88 @@
+// Copyright 2022 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.
+
+#include "third_party/blink/renderer/core/dom/element_rare_data_vector.h"
+#include "third_party/blink/renderer/core/style/computed_style.h"
+#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
+#include "third_party/blink/renderer/platform/heap/persistent.h"
+#include "third_party/googletest/src/googletest/include/gtest/gtest.h"
+
+namespace blink {
+
+class TestDataField : public GarbageCollected<TestDataField>,
+                      public ElementRareDataField {
+ public:
+  int value;
+};
+
+class ElementRareDataVectorTest : public testing::Test {
+ public:
+  void SetUp() override {
+    rare_data_ = MakeGarbageCollected<ElementRareDataVector>(
+        MakeGarbageCollected<NodeRenderingData>(nullptr, nullptr));
+  }
+
+  void SetField(unsigned field_id, ElementRareDataField* field) {
+    rare_data_->SetField(static_cast<ElementRareDataVector::FieldId>(field_id),
+                         field);
+  }
+
+  TestDataField* GetField(unsigned field_id) {
+    return static_cast<TestDataField*>(rare_data_->GetField(
+        static_cast<ElementRareDataVector::FieldId>(field_id)));
+  }
+
+  unsigned GetFieldIndex(unsigned field_id) {
+    return rare_data_->GetFieldIndex(
+        static_cast<ElementRareDataVector::FieldId>(field_id));
+  }
+
+  Persistent<ElementRareDataVector> rare_data_;
+};
+
+TEST_F(ElementRareDataVectorTest, Basic) {
+  TestDataField* d1 = MakeGarbageCollected<TestDataField>();
+  d1->value = 101;
+  TestDataField* d2 = MakeGarbageCollected<TestDataField>();
+  d2->value = 202;
+
+  SetField(1, d1);
+  EXPECT_EQ(GetField(1)->value, d1->value);
+
+  SetField(2, d2);
+  EXPECT_EQ(GetField(1)->value, d1->value);
+  EXPECT_EQ(GetField(2)->value, d2->value);
+}
+
+TEST_F(ElementRareDataVectorTest, MutateValue) {
+  TestDataField* d1 = MakeGarbageCollected<TestDataField>();
+  d1->value = 101;
+  TestDataField* d2 = MakeGarbageCollected<TestDataField>();
+  d2->value = 202;
+
+  SetField(1, d1);
+  EXPECT_EQ(GetField(1)->value, d1->value);
+
+  SetField(1, d2);
+  EXPECT_EQ(GetField(1)->value, d2->value);
+}
+
+TEST_F(ElementRareDataVectorTest, GetFieldIndex) {
+  TestDataField* d1 = MakeGarbageCollected<TestDataField>();
+  d1->value = 101;
+  TestDataField* d2 = MakeGarbageCollected<TestDataField>();
+  d2->value = 202;
+
+  SetField(3, d1);
+  EXPECT_EQ(GetFieldIndex(3), 0u);
+  EXPECT_EQ(GetField(3)->value, d1->value);
+
+  SetField(5, d2);
+  EXPECT_EQ(GetFieldIndex(3), 0u);
+  EXPECT_EQ(GetField(3)->value, d1->value);
+  EXPECT_EQ(GetFieldIndex(5), 1u);
+  EXPECT_EQ(GetField(5)->value, d2->value);
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/core/dom/named_node_map.cc b/third_party/blink/renderer/core/dom/named_node_map.cc
index a1f061e..467a7f5e 100644
--- a/third_party/blink/renderer/core/dom/named_node_map.cc
+++ b/third_party/blink/renderer/core/dom/named_node_map.cc
@@ -131,6 +131,7 @@
 void NamedNodeMap::Trace(Visitor* visitor) const {
   visitor->Trace(element_);
   ScriptWrappable::Trace(visitor);
+  ElementRareDataField::Trace(visitor);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/dom/named_node_map.h b/third_party/blink/renderer/core/dom/named_node_map.h
index f05330e..5fddabf 100644
--- a/third_party/blink/renderer/core/dom/named_node_map.h
+++ b/third_party/blink/renderer/core/dom/named_node_map.h
@@ -27,6 +27,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_DOM_NAMED_NODE_MAP_H_
 
 #include "third_party/blink/renderer/core/dom/element.h"
+#include "third_party/blink/renderer/core/dom/element_rare_data_field.h"
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
 #include "third_party/blink/renderer/platform/wtf/text/atomic_string.h"
 
@@ -35,7 +36,7 @@
 class Attr;
 class ExceptionState;
 
-class NamedNodeMap final : public ScriptWrappable {
+class NamedNodeMap final : public ScriptWrappable, public ElementRareDataField {
   DEFINE_WRAPPERTYPEINFO();
   friend class Element;
 
diff --git a/third_party/blink/renderer/core/dom/node.cc b/third_party/blink/renderer/core/dom/node.cc
index f531a4f..f77e245 100644
--- a/third_party/blink/renderer/core/dom/node.cc
+++ b/third_party/blink/renderer/core/dom/node.cc
@@ -50,6 +50,7 @@
 #include "third_party/blink/renderer/core/dom/dom_node_ids.h"
 #include "third_party/blink/renderer/core/dom/element.h"
 #include "third_party/blink/renderer/core/dom/element_rare_data.h"
+#include "third_party/blink/renderer/core/dom/element_rare_data_vector.h"
 #include "third_party/blink/renderer/core/dom/element_traversal.h"
 #include "third_party/blink/renderer/core/dom/events/add_event_listener_options_resolved.h"
 #include "third_party/blink/renderer/core/dom/events/event.h"
@@ -335,7 +336,12 @@
 
 NodeRareData& Node::CreateRareData() {
   if (IsElementNode()) {
-    data_ = MakeGarbageCollected<ElementRareData>(DataAsNodeRenderingData());
+    if (RuntimeEnabledFeatures::ElementSuperRareDataEnabled()) {
+      data_ = MakeGarbageCollected<ElementRareDataVector>(
+          DataAsNodeRenderingData());
+    } else {
+      data_ = MakeGarbageCollected<ElementRareData>(DataAsNodeRenderingData());
+    }
   } else {
     data_ = MakeGarbageCollected<NodeRareData>(DataAsNodeRenderingData());
   }
diff --git a/third_party/blink/renderer/core/dom/node_rare_data.h b/third_party/blink/renderer/core/dom/node_rare_data.h
index d3a1539..56e1ec3 100644
--- a/third_party/blink/renderer/core/dom/node_rare_data.h
+++ b/third_party/blink/renderer/core/dom/node_rare_data.h
@@ -23,6 +23,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_DOM_NODE_RARE_DATA_H_
 
 #include "base/check_op.h"
+#include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_set.h"
 #include "third_party/blink/renderer/platform/heap/collection_support/heap_vector.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
@@ -142,7 +143,7 @@
   }
 };
 
-class NodeRenderingData final : public NodeData {
+class CORE_EXPORT NodeRenderingData final : public NodeData {
  public:
   NodeRenderingData(LayoutObject*,
                     scoped_refptr<const ComputedStyle> computed_style);
diff --git a/third_party/blink/renderer/core/dom/popover_data.h b/third_party/blink/renderer/core/dom/popover_data.h
index a6d3544..c5d0ca6 100644
--- a/third_party/blink/renderer/core/dom/popover_data.h
+++ b/third_party/blink/renderer/core/dom/popover_data.h
@@ -7,6 +7,7 @@
 
 #include "base/check_op.h"
 #include "third_party/blink/renderer/core/dom/element.h"
+#include "third_party/blink/renderer/core/dom/element_rare_data_field.h"
 #include "third_party/blink/renderer/core/dom/id_target_observer.h"
 #include "third_party/blink/renderer/core/dom/popover_animation_finished_event_listener.h"
 #include "third_party/blink/renderer/core/html/forms/html_select_menu_element.h"
@@ -39,7 +40,8 @@
   Member<HTMLElement> element_;
 };
 
-class PopoverData final : public GarbageCollected<PopoverData> {
+class PopoverData final : public GarbageCollected<PopoverData>,
+                          public ElementRareDataField {
  public:
   PopoverData() = default;
   PopoverData(const PopoverData&) = delete;
@@ -60,13 +62,6 @@
   Element* invoker() const { return invoker_; }
   void setInvoker(Element* element) { invoker_ = element; }
 
-  void setNeedsRepositioningForSelectMenu(bool flag) {
-    needs_repositioning_for_select_menu_ = flag;
-  }
-  bool needsRepositioningForSelectMenu() {
-    return needs_repositioning_for_select_menu_;
-  }
-
   Element* previouslyFocusedElement() const {
     return previously_focused_element_;
   }
@@ -103,13 +98,14 @@
     owner_select_menu_element_ = element;
   }
 
-  void Trace(Visitor* visitor) const {
+  void Trace(Visitor* visitor) const override {
     visitor->Trace(invoker_);
     visitor->Trace(previously_focused_element_);
     visitor->Trace(animation_finished_listener_);
     visitor->Trace(anchor_element_);
     visitor->Trace(anchor_observer_);
     visitor->Trace(owner_select_menu_element_);
+    ElementRareDataField::Trace(visitor);
   }
 
  private:
@@ -125,9 +121,6 @@
   Member<Element> anchor_element_;
   Member<PopoverAnchorObserver> anchor_observer_;
 
-  // TODO(crbug.com/1197720): The popover position should be provided by the new
-  // anchored positioning scheme.
-  bool needs_repositioning_for_select_menu_ = false;
   WeakMember<HTMLSelectMenuElement> owner_select_menu_element_;
 };
 
diff --git a/third_party/blink/renderer/core/dom/pseudo_element_data.h b/third_party/blink/renderer/core/dom/pseudo_element_data.h
index e9247f5..da0d424 100644
--- a/third_party/blink/renderer/core/dom/pseudo_element_data.h
+++ b/third_party/blink/renderer/core/dom/pseudo_element_data.h
@@ -7,12 +7,14 @@
 
 #include "base/notreached.h"
 #include "build/build_config.h"
+#include "third_party/blink/renderer/core/dom/element_rare_data_field.h"
 #include "third_party/blink/renderer/core/dom/transition_pseudo_element_data.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 
 namespace blink {
 
-class PseudoElementData final : public GarbageCollected<PseudoElementData> {
+class PseudoElementData final : public GarbageCollected<PseudoElementData>,
+                                public ElementRareDataField {
  public:
   PseudoElementData() = default;
   PseudoElementData(const PseudoElementData&) = delete;
@@ -30,13 +32,14 @@
 
   bool HasPseudoElements() const;
   void ClearPseudoElements();
-  void Trace(Visitor* visitor) const {
+  void Trace(Visitor* visitor) const override {
     visitor->Trace(generated_before_);
     visitor->Trace(generated_after_);
     visitor->Trace(generated_marker_);
     visitor->Trace(generated_first_letter_);
     visitor->Trace(backdrop_);
     visitor->Trace(transition_data_);
+    ElementRareDataField::Trace(visitor);
   }
 
  private:
diff --git a/third_party/blink/renderer/core/dom/shadow_root.cc b/third_party/blink/renderer/core/dom/shadow_root.cc
index 383f2ce3..dc240a9 100644
--- a/third_party/blink/renderer/core/dom/shadow_root.cc
+++ b/third_party/blink/renderer/core/dom/shadow_root.cc
@@ -48,7 +48,9 @@
 
 namespace blink {
 
-struct SameSizeAsShadowRoot : public DocumentFragment, public TreeScope {
+struct SameSizeAsShadowRoot : public DocumentFragment,
+                              public TreeScope,
+                              public ElementRareDataField {
   Member<void*> member[3];
   unsigned flags[1];
 };
@@ -262,6 +264,7 @@
   visitor->Trace(style_sheet_list_);
   visitor->Trace(slot_assignment_);
   visitor->Trace(registry_);
+  ElementRareDataField::Trace(visitor);
   TreeScope::Trace(visitor);
   DocumentFragment::Trace(visitor);
 }
diff --git a/third_party/blink/renderer/core/dom/shadow_root.h b/third_party/blink/renderer/core/dom/shadow_root.h
index 4043b58..444412f 100644
--- a/third_party/blink/renderer/core/dom/shadow_root.h
+++ b/third_party/blink/renderer/core/dom/shadow_root.h
@@ -34,6 +34,7 @@
 #include "third_party/blink/renderer/core/dom/container_node.h"
 #include "third_party/blink/renderer/core/dom/document_fragment.h"
 #include "third_party/blink/renderer/core/dom/element.h"
+#include "third_party/blink/renderer/core/dom/element_rare_data_field.h"
 #include "third_party/blink/renderer/core/dom/tree_scope.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
 #include "third_party/blink/renderer/platform/wtf/casting.h"
@@ -49,7 +50,9 @@
 
 enum class ShadowRootType { kOpen, kClosed, kUserAgent };
 
-class CORE_EXPORT ShadowRoot final : public DocumentFragment, public TreeScope {
+class CORE_EXPORT ShadowRoot final : public DocumentFragment,
+                                     public TreeScope,
+                                     public ElementRareDataField {
   DEFINE_WRAPPERTYPEINFO();
 
  public:
diff --git a/third_party/blink/renderer/core/editing/ime/edit_context.cc b/third_party/blink/renderer/core/editing/ime/edit_context.cc
index 61bf8a5..e946cd2 100644
--- a/third_party/blink/renderer/core/editing/ime/edit_context.cc
+++ b/third_party/blink/renderer/core/editing/ime/edit_context.cc
@@ -870,6 +870,7 @@
   ActiveScriptWrappable::Trace(visitor);
   ExecutionContextClient::Trace(visitor);
   EventTargetWithInlineData::Trace(visitor);
+  ElementRareDataField::Trace(visitor);
   visitor->Trace(attached_elements_);
 }
 
diff --git a/third_party/blink/renderer/core/editing/ime/edit_context.h b/third_party/blink/renderer/core/editing/ime/edit_context.h
index 1c9d3921..20a1754 100644
--- a/third_party/blink/renderer/core/editing/ime/edit_context.h
+++ b/third_party/blink/renderer/core/editing/ime/edit_context.h
@@ -11,6 +11,7 @@
 #include "third_party/blink/renderer/bindings/core/v8/active_script_wrappable.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_edit_context_input_panel_policy.h"
 #include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/core/dom/element_rare_data_field.h"
 #include "third_party/blink/renderer/core/dom/events/event_target.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context_lifecycle_observer.h"
 #include "ui/base/ime/ime_text_span.h"
@@ -37,7 +38,8 @@
 class CORE_EXPORT EditContext final : public EventTargetWithInlineData,
                                       public ActiveScriptWrappable<EditContext>,
                                       public ExecutionContextClient,
-                                      public WebInputMethodController {
+                                      public WebInputMethodController,
+                                      public ElementRareDataField {
   DEFINE_WRAPPERTYPEINFO();
 
  public:
diff --git a/third_party/blink/renderer/core/frame/attribution_src_loader.cc b/third_party/blink/renderer/core/frame/attribution_src_loader.cc
index 7d726f6..711a30b 100644
--- a/third_party/blink/renderer/core/frame/attribution_src_loader.cc
+++ b/third_party/blink/renderer/core/frame/attribution_src_loader.cc
@@ -690,7 +690,7 @@
   DCHECK(!json.IsNull());
 
   auto source_data = attribution_reporting::SourceRegistration::Parse(
-      StringUTF8Adaptor(json).AsStringPiece(), std::move(reporting_origin));
+      StringUTF8Adaptor(json).AsStringPiece());
   if (!source_data.has_value()) {
     LogAuditIssue(loader_->local_frame_->DomWindow(),
                   AttributionReportingIssueType::kInvalidRegisterSourceHeader,
@@ -699,7 +699,8 @@
     return;
   }
 
-  data_host_->SourceDataAvailable(std::move(*source_data));
+  data_host_->SourceDataAvailable(std::move(reporting_origin),
+                                  std::move(*source_data));
 }
 
 void AttributionSrcLoader::ResourceClient::HandleTriggerRegistration(
diff --git a/third_party/blink/renderer/core/frame/attribution_src_loader_test.cc b/third_party/blink/renderer/core/frame/attribution_src_loader_test.cc
index 1c29568..0649e6d 100644
--- a/third_party/blink/renderer/core/frame/attribution_src_loader_test.cc
+++ b/third_party/blink/renderer/core/frame/attribution_src_loader_test.cc
@@ -11,6 +11,7 @@
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "components/attribution_reporting/source_registration.h"
+#include "components/attribution_reporting/suitable_origin.h"
 #include "components/attribution_reporting/trigger_registration.h"
 #include "mojo/public/cpp/bindings/associated_receiver.h"
 #include "net/http/structured_headers.h"
@@ -99,6 +100,7 @@
 
   // mojom::blink::AttributionDataHost:
   void SourceDataAvailable(
+      attribution_reporting::SuitableOrigin reporting_origin,
       attribution_reporting::SourceRegistration data) override {
     source_data_.push_back(std::move(data));
   }
diff --git a/third_party/blink/renderer/core/frame/csp/content_security_policy.cc b/third_party/blink/renderer/core/frame/csp/content_security_policy.cc
index 6c890d8..167454c 100644
--- a/third_party/blink/renderer/core/frame/csp/content_security_policy.cc
+++ b/third_party/blink/renderer/core/frame/csp/content_security_policy.cc
@@ -483,6 +483,7 @@
 bool ContentSecurityPolicy::IsScriptInlineType(InlineType inline_type) {
   switch (inline_type) {
     case ContentSecurityPolicy::InlineType::kNavigation:
+    case ContentSecurityPolicy::InlineType::kScriptSpeculationRules:
     case ContentSecurityPolicy::InlineType::kScriptAttribute:
     case ContentSecurityPolicy::InlineType::kScript:
       return true;
diff --git a/third_party/blink/renderer/core/frame/csp/content_security_policy.h b/third_party/blink/renderer/core/frame/csp/content_security_policy.h
index 0f66c3c..1b071de 100644
--- a/third_party/blink/renderer/core/frame/csp/content_security_policy.h
+++ b/third_party/blink/renderer/core/frame/csp/content_security_policy.h
@@ -140,6 +140,7 @@
     kNavigation,
     kScript,
     kScriptAttribute,
+    kScriptSpeculationRules,  // TODO(https://crbug.com/1382361): Standardize.
     kStyle,
     kStyleAttribute
   };
diff --git a/third_party/blink/renderer/core/frame/csp/conversion_util.cc b/third_party/blink/renderer/core/frame/csp/conversion_util.cc
index 1c1fc82a..5e28cf0 100644
--- a/third_party/blink/renderer/core/frame/csp/conversion_util.cc
+++ b/third_party/blink/renderer/core/frame/csp/conversion_util.cc
@@ -44,6 +44,7 @@
           source_list->allow_star,
           source_list->allow_response_redirects,
           source_list->allow_inline,
+          source_list->allow_inline_speculation_rules,
           source_list->allow_eval,
           source_list->allow_wasm_eval,
           source_list->allow_wasm_unsafe_eval,
@@ -107,9 +108,10 @@
       std::move(sources), ConvertToWTF(source_list.nonces), std::move(hashes),
       source_list.allow_self, source_list.allow_star,
       source_list.allow_response_redirects, source_list.allow_inline,
-      source_list.allow_eval, source_list.allow_wasm_eval,
-      source_list.allow_wasm_unsafe_eval, source_list.allow_dynamic,
-      source_list.allow_unsafe_hashes, source_list.report_sample);
+      source_list.allow_inline_speculation_rules, source_list.allow_eval,
+      source_list.allow_wasm_eval, source_list.allow_wasm_unsafe_eval,
+      source_list.allow_dynamic, source_list.allow_unsafe_hashes,
+      source_list.report_sample);
 }
 
 }  // namespace
diff --git a/third_party/blink/renderer/core/frame/csp/csp_directive_list.cc b/third_party/blink/renderer/core/frame/csp/csp_directive_list.cc
index ad4003d..c67ca98 100644
--- a/third_party/blink/renderer/core/frame/csp/csp_directive_list.cc
+++ b/third_party/blink/renderer/core/frame/csp/csp_directive_list.cc
@@ -102,6 +102,7 @@
     // "navigation":
     // 1. Return script-src-elem. [spec text]
     case ContentSecurityPolicy::InlineType::kScript:
+    case ContentSecurityPolicy::InlineType::kScriptSpeculationRules:
     case ContentSecurityPolicy::InlineType::kNavigation:
       return CSPDirectiveName::ScriptSrcElem;
 
@@ -294,6 +295,7 @@
       return CheckUnsafeHashesAllowed(directive);
 
     case ContentSecurityPolicy::InlineType::kScript:
+    case ContentSecurityPolicy::InlineType::kScriptSpeculationRules:
     case ContentSecurityPolicy::InlineType::kStyle:
       return true;
   }
@@ -429,8 +431,10 @@
     const String& hash_value,
     CSPDirectiveName effective_type) {
   if (!directive.source_list ||
-      CSPSourceListAllowAllInline(directive.type, *directive.source_list))
+      CSPSourceListAllowAllInline(directive.type, inline_type,
+                                  *directive.source_list)) {
     return true;
+  }
 
   bool is_script = ContentSecurityPolicy::IsScriptInlineType(inline_type);
 
@@ -670,6 +674,7 @@
         break;
 
       case ContentSecurityPolicy::InlineType::kScript:
+      case ContentSecurityPolicy::InlineType::kScriptSpeculationRules:
       case ContentSecurityPolicy::InlineType::kStyleAttribute:
       case ContentSecurityPolicy::InlineType::kStyle:
         hash_value = GetSha256String(content);
@@ -682,6 +687,10 @@
         message = "run the JavaScript URL";
         break;
 
+      case ContentSecurityPolicy::InlineType::kScriptSpeculationRules:
+        message = "assert inline speculation-rules";
+        break;
+
       case ContentSecurityPolicy::InlineType::kScriptAttribute:
         message = "execute inline event handler";
         break;
@@ -706,7 +715,8 @@
   }
 
   return !directive.source_list ||
-         CSPSourceListAllowAllInline(directive.type, *directive.source_list);
+         CSPSourceListAllowAllInline(directive.type, inline_type,
+                                     *directive.source_list);
 }
 
 bool CSPDirectiveListShouldCheckEval(
@@ -955,8 +965,11 @@
   // If no `script-src` enforcement occurs, or it allows any and all inline
   // script, the restriction is not reasonable.
   if (!script_src.source_list ||
-      CSPSourceListAllowAllInline(script_src.type, *script_src.source_list))
+      CSPSourceListAllowAllInline(script_src.type,
+                                  ContentSecurityPolicy::InlineType::kScript,
+                                  *script_src.source_list)) {
     return false;
+  }
 
   if (CSPSourceListIsNone(*script_src.source_list))
     return true;
diff --git a/third_party/blink/renderer/core/frame/csp/source_list_directive.cc b/third_party/blink/renderer/core/frame/csp/source_list_directive.cc
index 25d9bd56..3fc1971c 100644
--- a/third_party/blink/renderer/core/frame/csp/source_list_directive.cc
+++ b/third_party/blink/renderer/core/frame/csp/source_list_directive.cc
@@ -133,16 +133,21 @@
 
 bool CSPSourceListAllowAllInline(
     CSPDirectiveName directive_type,
+    ContentSecurityPolicy::InlineType inline_type,
     const network::mojom::blink::CSPSourceList& source_list) {
   if (!IsScriptDirective(directive_type) &&
       !IsStyleDirective(directive_type)) {
     return false;
   }
 
-  return source_list.allow_inline &&
-         !CSPSourceListIsHashOrNoncePresent(source_list) &&
-         (!IsScriptDirective(directive_type) ||
-          !source_list.allow_dynamic);
+  bool allow_inline = source_list.allow_inline;
+  if (inline_type ==
+      ContentSecurityPolicy::InlineType::kScriptSpeculationRules) {
+    allow_inline |= source_list.allow_inline_speculation_rules;
+  }
+
+  return allow_inline && !CSPSourceListIsHashOrNoncePresent(source_list) &&
+         (!IsScriptDirective(directive_type) || !source_list.allow_dynamic);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/frame/csp/source_list_directive.h b/third_party/blink/renderer/core/frame/csp/source_list_directive.h
index b39838e..20cad84 100644
--- a/third_party/blink/renderer/core/frame/csp/source_list_directive.h
+++ b/third_party/blink/renderer/core/frame/csp/source_list_directive.h
@@ -6,6 +6,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_CSP_SOURCE_LIST_DIRECTIVE_H_
 
 #include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/core/frame/csp/content_security_policy.h"
 #include "third_party/blink/renderer/core/frame/csp/csp_source.h"
 #include "third_party/blink/renderer/platform/loader/fetch/resource_request.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
@@ -47,6 +48,7 @@
 CORE_EXPORT
 bool CSPSourceListAllowAllInline(
     network::mojom::blink::CSPDirectiveName directive_type,
+    ContentSecurityPolicy::InlineType inline_type,
     const network::mojom::blink::CSPSourceList& source_list);
 
 CORE_EXPORT
diff --git a/third_party/blink/renderer/core/frame/csp/source_list_directive_test.cc b/third_party/blink/renderer/core/frame/csp/source_list_directive_test.cc
index e7f881d..dcfb2ff 100644
--- a/third_party/blink/renderer/core/frame/csp/source_list_directive_test.cc
+++ b/third_party/blink/renderer/core/frame/csp/source_list_directive_test.cc
@@ -312,30 +312,35 @@
   // Script-src and style-src differently handle presence of 'strict-dynamic'.
   network::mojom::blink::CSPSourceListPtr script_src =
       ParseSourceList("script-src", "'strict-dynamic' 'unsafe-inline'");
-  EXPECT_FALSE(
-      CSPSourceListAllowAllInline(CSPDirectiveName::ScriptSrc, *script_src));
+  EXPECT_FALSE(CSPSourceListAllowAllInline(
+      CSPDirectiveName::ScriptSrc, ContentSecurityPolicy::InlineType::kScript,
+      *script_src));
 
   network::mojom::blink::CSPSourceListPtr style_src =
       ParseSourceList("style-src", "'strict-dynamic' 'unsafe-inline'");
-  EXPECT_TRUE(
-      CSPSourceListAllowAllInline(CSPDirectiveName::StyleSrc, *style_src));
+  EXPECT_TRUE(CSPSourceListAllowAllInline(
+      CSPDirectiveName::StyleSrc, ContentSecurityPolicy::InlineType::kStyle,
+      *style_src));
 
   for (const auto& test : cases) {
     script_src = ParseSourceList("script-src", test.sources);
-    EXPECT_EQ(
-        CSPSourceListAllowAllInline(CSPDirectiveName::ScriptSrc, *script_src),
-        test.expected);
+    EXPECT_EQ(CSPSourceListAllowAllInline(
+                  CSPDirectiveName::ScriptSrc,
+                  ContentSecurityPolicy::InlineType::kScript, *script_src),
+              test.expected);
 
     style_src = ParseSourceList("style-src", test.sources);
-    EXPECT_EQ(
-        CSPSourceListAllowAllInline(CSPDirectiveName::StyleSrc, *style_src),
-        test.expected);
+    EXPECT_EQ(CSPSourceListAllowAllInline(
+                  CSPDirectiveName::StyleSrc,
+                  ContentSecurityPolicy::InlineType::kStyle, *style_src),
+              test.expected);
 
     // If source list doesn't have a valid type, it must not allow all inline.
     network::mojom::blink::CSPSourceListPtr img_src =
         ParseSourceList("img-src", test.sources);
-    EXPECT_FALSE(
-        CSPSourceListAllowAllInline(CSPDirectiveName::ImgSrc, *img_src));
+    EXPECT_FALSE(CSPSourceListAllowAllInline(
+        CSPDirectiveName::ImgSrc, ContentSecurityPolicy::InlineType::kScript,
+        *img_src));
   }
 }
 
diff --git a/third_party/blink/renderer/core/frame/local_frame_client_impl.cc b/third_party/blink/renderer/core/frame/local_frame_client_impl.cc
index f2a3fba..535cff8 100644
--- a/third_party/blink/renderer/core/frame/local_frame_client_impl.cc
+++ b/third_party/blink/renderer/core/frame/local_frame_client_impl.cc
@@ -604,6 +604,7 @@
         origin_window->IsSandboxed(
             network::mojom::blink::WebSandboxFlags::kDownloads);
     navigation_info->initiator_frame_is_ad = frame->IsAdFrame();
+    navigation_info->is_ad_script_in_stack = frame->IsAdScriptInStack();
   }
 
   // The frame has navigated either by itself or by the action of the
diff --git a/third_party/blink/renderer/core/frame/remote_frame.cc b/third_party/blink/renderer/core/frame/remote_frame.cc
index feef4c3..a0f15e1 100644
--- a/third_party/blink/renderer/core/frame/remote_frame.cc
+++ b/third_party/blink/renderer/core/frame/remote_frame.cc
@@ -213,6 +213,7 @@
   bool is_opener_navigation = false;
   bool initiator_frame_has_download_sandbox_flag = false;
   bool initiator_frame_is_ad = false;
+  bool is_ad_script_in_stack = false;
 
   absl::optional<LocalFrameToken> initiator_frame_token =
       base::OptionalFromPtr(frame_request.GetInitiatorFrameToken());
@@ -230,6 +231,8 @@
   if (window->GetFrame()) {
     is_opener_navigation = window->GetFrame()->Opener() == this;
     initiator_frame_is_ad = window->GetFrame()->IsAdFrame();
+    is_ad_script_in_stack = window->GetFrame()->IsAdScriptInStack();
+
     if (frame_request.ClientRedirectReason() != ClientNavigationReason::kNone) {
       probe::FrameRequestedNavigation(window->GetFrame(), this, url,
                                       frame_request.ClientRedirectReason(),
@@ -296,6 +299,10 @@
       initiator_frame_has_download_sandbox_flag,
       initiator_frame_is_ad);
 
+  params->initiator_activation_and_ad_status =
+      GetNavigationInitiatorActivationAndAdStatus(request.HasUserGesture(),
+                                                  is_ad_script_in_stack);
+
   GetRemoteFrameHostRemote().OpenURL(std::move(params));
 }
 
diff --git a/third_party/blink/renderer/core/frame/web_frame_test.cc b/third_party/blink/renderer/core/frame/web_frame_test.cc
index e61fb1a..25372e92 100644
--- a/third_party/blink/renderer/core/frame/web_frame_test.cc
+++ b/third_party/blink/renderer/core/frame/web_frame_test.cc
@@ -42,7 +42,8 @@
 #include "cc/base/features.h"
 #include "cc/input/overscroll_behavior.h"
 #include "cc/layers/picture_layer.h"
-#include "cc/paint/paint_op_buffer.h"
+#include "cc/paint/paint_op.h"
+#include "cc/paint/paint_op_buffer_iterator.h"
 #include "cc/paint/paint_recorder.h"
 #include "cc/trees/layer_tree_host.h"
 #include "cc/trees/scroll_node.h"
diff --git a/third_party/blink/renderer/core/frame/web_frame_widget_test.cc b/third_party/blink/renderer/core/frame/web_frame_widget_test.cc
index 7466cc5a..51377b00 100644
--- a/third_party/blink/renderer/core/frame/web_frame_widget_test.cc
+++ b/third_party/blink/renderer/core/frame/web_frame_widget_test.cc
@@ -921,7 +921,7 @@
     }
 
     DidNotSwapAction DidNotSwap(DidNotSwapReason reason,
-                                base::TimeTicks ts) override {
+                                base::TimeTicks) override {
       DCHECK_EQ(State::kPending, *state_);
       *state_ = State::kBroken;
       return DidNotSwapAction::BREAK_PROMISE;
diff --git a/third_party/blink/renderer/core/html/closewatcher/close_watcher.cc b/third_party/blink/renderer/core/html/closewatcher/close_watcher.cc
index 89e8b976..10cb9bd 100644
--- a/third_party/blink/renderer/core/html/closewatcher/close_watcher.cc
+++ b/third_party/blink/renderer/core/html/closewatcher/close_watcher.cc
@@ -11,11 +11,14 @@
 #include "third_party/blink/renderer/core/dom/abort_signal.h"
 #include "third_party/blink/renderer/core/dom/events/event.h"
 #include "third_party/blink/renderer/core/event_target_names.h"
+#include "third_party/blink/renderer/core/event_type_names.h"
 #include "third_party/blink/renderer/core/events/keyboard_event.h"
 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
 #include "third_party/blink/renderer/core/frame/local_frame.h"
+#include "third_party/blink/renderer/core/html/html_dialog_element.h"
 #include "third_party/blink/renderer/core/input/event_handler.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
+#include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
 #include "third_party/blink/renderer/platform/keyboard_codes.h"
 
 namespace blink {
@@ -64,21 +67,36 @@
   visitor->Trace(window_);
 }
 
-void CloseWatcher::WatcherStack::EscapeKeyHandler(KeyboardEvent* event) {
+void CloseWatcher::WatcherStack::EscapeKeyHandler(KeyboardEvent* event,
+                                                  bool* cancel_skipped) {
   if (!watchers_.empty() && !event->DefaultHandled() && event->isTrusted() &&
       event->keyCode() == VKEY_ESCAPE) {
-    Signal();
+    SignalInternal(cancel_skipped);
   }
 }
 
 void CloseWatcher::WatcherStack::Signal() {
+  SignalInternal(/*cancel_skipped=*/nullptr);
+}
+
+void CloseWatcher::WatcherStack::SignalInternal(bool* cancel_skipped) {
+  int num_dialogs_closed = 0;
   while (!watchers_.empty()) {
     CloseWatcher* watcher = watchers_.back();
-    watcher->close();
+    watcher->close(cancel_skipped);
+    if (watcher->dialog_for_use_counters_) {
+      ++num_dialogs_closed;
+    }
+
     if (!watcher->IsGroupedWithPrevious()) {
       break;
     }
   }
+
+  if (num_dialogs_closed > 1) {
+    UseCounter::Count(window_,
+                      WebFeature::kDialogCloseWatcherCloseSignalClosedMultiple);
+  }
 }
 
 bool CloseWatcher::WatcherStack::HasConsumedFreeWatcher() const {
@@ -91,12 +109,13 @@
 }
 
 // static
-CloseWatcher* CloseWatcher::Create(LocalDOMWindow* window) {
+CloseWatcher* CloseWatcher::Create(LocalDOMWindow* window,
+                                   HTMLDialogElement* dialog_for_use_counters) {
   if (!window->GetFrame())
     return nullptr;
 
   WatcherStack& stack = *window->closewatcher_stack();
-  return CreateInternal(window, stack, nullptr);
+  return CreateInternal(window, stack, nullptr, dialog_for_use_counters);
 }
 
 // static
@@ -112,14 +131,17 @@
   }
 
   WatcherStack& stack = *window->closewatcher_stack();
-  return CreateInternal(window, stack, options);
+  return CreateInternal(window, stack, options, nullptr);
 }
 
 // static
-CloseWatcher* CloseWatcher::CreateInternal(LocalDOMWindow* window,
-                                           WatcherStack& stack,
-                                           CloseWatcherOptions* options) {
-  CloseWatcher* watcher = MakeGarbageCollected<CloseWatcher>(window);
+CloseWatcher* CloseWatcher::CreateInternal(
+    LocalDOMWindow* window,
+    WatcherStack& stack,
+    CloseWatcherOptions* options,
+    HTMLDialogElement* dialog_for_use_counters) {
+  CloseWatcher* watcher =
+      MakeGarbageCollected<CloseWatcher>(window, dialog_for_use_counters);
 
   if (window->history_user_activation_state().IsActive()) {
     window->history_user_activation_state().Consume();
@@ -147,10 +169,12 @@
   return watcher;
 }
 
-CloseWatcher::CloseWatcher(LocalDOMWindow* window)
-    : ExecutionContextClient(window) {}
+CloseWatcher::CloseWatcher(LocalDOMWindow* window,
+                           HTMLDialogElement* dialog_for_use_counters)
+    : ExecutionContextClient(window),
+      dialog_for_use_counters_(dialog_for_use_counters) {}
 
-void CloseWatcher::close() {
+void CloseWatcher::close(bool* cancel_skipped) {
   if (IsClosed() || dispatching_cancel_ || !DomWindow())
     return;
 
@@ -164,13 +188,21 @@
       DomWindow()->history_user_activation_state().Consume();
       return;
     }
+  } else if (dialog_for_use_counters_ &&
+             dialog_for_use_counters_->HasEventListeners(
+                 event_type_names::kCancel)) {
+    UseCounter::Count(DomWindow(),
+                      WebFeature::kDialogCloseWatcherCancelSkipped);
+    if (cancel_skipped)
+      *cancel_skipped = true;
   }
 
   // These might have changed because of the event firing.
   if (IsClosed())
     return;
-  if (DomWindow())
+  if (DomWindow()) {
     DomWindow()->closewatcher_stack()->Remove(this);
+  }
 
   abort_handle_.Clear();
   state_ = State::kClosed;
@@ -191,6 +223,7 @@
 
 void CloseWatcher::Trace(Visitor* visitor) const {
   visitor->Trace(abort_handle_);
+  visitor->Trace(dialog_for_use_counters_);
   EventTargetWithInlineData::Trace(visitor);
   ExecutionContextClient::Trace(visitor);
 }
diff --git a/third_party/blink/renderer/core/html/closewatcher/close_watcher.h b/third_party/blink/renderer/core/html/closewatcher/close_watcher.h
index 91859e1e..425c4a6 100644
--- a/third_party/blink/renderer/core/html/closewatcher/close_watcher.h
+++ b/third_party/blink/renderer/core/html/closewatcher/close_watcher.h
@@ -19,6 +19,7 @@
 class CloseWatcherOptions;
 class LocalDOMWindow;
 class KeyboardEvent;
+class HTMLDialogElement;
 
 class CloseWatcher final : public EventTargetWithInlineData,
                            public ExecutionContextClient {
@@ -29,15 +30,24 @@
                               CloseWatcherOptions*,
                               ExceptionState&);
 
-  static CloseWatcher* Create(LocalDOMWindow*);
+  // We have a few use counters which we trigger only for the <dialog> case,
+  // where we're trying to determine whether it's web-compatible or not to use
+  // CloseWatcher rules for <dialog>s. (Namely, sometimes closing multiple
+  // <dialog>s with a single close signal, and sometimes skipping cancel
+  // events.) This argument should be removed after web-compatibility is
+  // determined; ultimately the CloseWatcher code should not be aware of the
+  // existence of <dialog>, for good layering.
+  static CloseWatcher* Create(LocalDOMWindow*,
+                              HTMLDialogElement* dialog_for_use_counters);
 
-  explicit CloseWatcher(LocalDOMWindow*);
+  explicit CloseWatcher(LocalDOMWindow*,
+                        HTMLDialogElement* dialog_for_use_counters);
   void Trace(Visitor*) const override;
 
   bool IsClosed() const { return state_ == State::kClosed; }
   bool IsGroupedWithPrevious() const { return grouped_with_previous_; }
 
-  void close();
+  void close(bool* cancel_skipped = nullptr);
   void destroy();
 
   DEFINE_ATTRIBUTE_EVENT_LISTENER(cancel, kCancel)
@@ -65,11 +75,12 @@
 
     void Trace(Visitor*) const;
 
-    void EscapeKeyHandler(KeyboardEvent*);
+    void EscapeKeyHandler(KeyboardEvent*, bool* cancel_skipped);
 
    private:
     // mojom::blink::CloseListener override:
     void Signal() final;
+    void SignalInternal(bool* cancel_skipped);
 
     HeapLinkedHashSet<Member<CloseWatcher>> watchers_;
 
@@ -80,9 +91,11 @@
   };
 
  private:
-  static CloseWatcher* CreateInternal(LocalDOMWindow*,
-                                      WatcherStack&,
-                                      CloseWatcherOptions*);
+  static CloseWatcher* CreateInternal(
+      LocalDOMWindow*,
+      WatcherStack&,
+      CloseWatcherOptions*,
+      HTMLDialogElement* dialog_for_use_counters);
 
   enum class State { kActive, kClosed };
   State state_ = State::kActive;
@@ -90,6 +103,7 @@
   bool grouped_with_previous_ = false;
   bool created_with_user_activation_ = false;
   Member<AbortSignal::AlgorithmHandle> abort_handle_;
+  Member<HTMLDialogElement> dialog_for_use_counters_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/html/custom/custom_element_definition.cc b/third_party/blink/renderer/core/html/custom/custom_element_definition.cc
index e2b6fd2..40f0a754 100644
--- a/third_party/blink/renderer/core/html/custom/custom_element_definition.cc
+++ b/third_party/blink/renderer/core/html/custom/custom_element_definition.cc
@@ -38,6 +38,7 @@
 
 void CustomElementDefinition::Trace(Visitor* visitor) const {
   visitor->Trace(construction_stack_);
+  ElementRareDataField::Trace(visitor);
 }
 
 static String ErrorMessageForConstructorResult(Element& element,
diff --git a/third_party/blink/renderer/core/html/custom/custom_element_definition.h b/third_party/blink/renderer/core/html/custom/custom_element_definition.h
index d368227..e4637d0 100644
--- a/third_party/blink/renderer/core/html/custom/custom_element_definition.h
+++ b/third_party/blink/renderer/core/html/custom/custom_element_definition.h
@@ -9,6 +9,7 @@
 #include "third_party/blink/renderer/bindings/core/v8/v8_typedefs.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/dom/create_element_flags.h"
+#include "third_party/blink/renderer/core/dom/element_rare_data_field.h"
 #include "third_party/blink/renderer/core/html/custom/custom_element_descriptor.h"
 #include "third_party/blink/renderer/platform/bindings/name_client.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
@@ -32,13 +33,14 @@
 
 class CORE_EXPORT CustomElementDefinition
     : public GarbageCollected<CustomElementDefinition>,
-      public NameClient {
+      public NameClient,
+      public ElementRareDataField {
  public:
   CustomElementDefinition(const CustomElementDefinition&) = delete;
   CustomElementDefinition& operator=(const CustomElementDefinition&) = delete;
   ~CustomElementDefinition() override;
 
-  virtual void Trace(Visitor*) const;
+  void Trace(Visitor*) const override;
   const char* NameInHeapSnapshot() const override {
     return "CustomElementDefinition";
   }
diff --git a/third_party/blink/renderer/core/html/custom/element_internals.cc b/third_party/blink/renderer/core/html/custom/element_internals.cc
index 3353582..8385e8c 100644
--- a/third_party/blink/renderer/core/html/custom/element_internals.cc
+++ b/third_party/blink/renderer/core/html/custom/element_internals.cc
@@ -48,6 +48,7 @@
   visitor->Trace(explicitly_set_attr_elements_map_);
   ListedElement::Trace(visitor);
   ScriptWrappable::Trace(visitor);
+  ElementRareDataField::Trace(visitor);
 }
 
 void ElementInternals::setFormValue(const V8ControlValue* value,
diff --git a/third_party/blink/renderer/core/html/custom/element_internals.h b/third_party/blink/renderer/core/html/custom/element_internals.h
index 944f9c1..feedde3e 100644
--- a/third_party/blink/renderer/core/html/custom/element_internals.h
+++ b/third_party/blink/renderer/core/html/custom/element_internals.h
@@ -7,6 +7,7 @@
 
 #include "third_party/blink/renderer/bindings/core/v8/v8_typedefs.h"
 #include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/core/dom/element_rare_data_field.h"
 #include "third_party/blink/renderer/core/dom/qualified_name.h"
 #include "third_party/blink/renderer/core/html/forms/labels_node_list.h"
 #include "third_party/blink/renderer/core/html/forms/listed_element.h"
@@ -22,7 +23,8 @@
 class ValidityStateFlags;
 
 class CORE_EXPORT ElementInternals : public ScriptWrappable,
-                                     public ListedElement {
+                                     public ListedElement,
+                                     public ElementRareDataField {
   DEFINE_WRAPPERTYPEINFO();
 
  public:
diff --git a/third_party/blink/renderer/core/html/forms/html_input_element.cc b/third_party/blink/renderer/core/html/forms/html_input_element.cc
index 60d94c1..fb408cf 100644
--- a/third_party/blink/renderer/core/html/forms/html_input_element.cc
+++ b/third_party/blink/renderer/core/html/forms/html_input_element.cc
@@ -555,7 +555,7 @@
 
   if (did_respect_height_and_width !=
       input_type_->ShouldRespectHeightAndWidthAttributes()) {
-    DCHECK(GetElementData());
+    DCHECK(HasElementData());
     AttributeCollection attributes = AttributesWithoutUpdate();
     if (const Attribute* height = attributes.Find(html_names::kHeightAttr)) {
       TextControlElement::AttributeChanged(AttributeModificationParams(
diff --git a/third_party/blink/renderer/core/html/forms/html_select_menu_element.cc b/third_party/blink/renderer/core/html/forms/html_select_menu_element.cc
index 5ee9f1da..3f1f7c0 100644
--- a/third_party/blink/renderer/core/html/forms/html_select_menu_element.cc
+++ b/third_party/blink/renderer/core/html/forms/html_select_menu_element.cc
@@ -342,7 +342,6 @@
 
 void HTMLSelectMenuElement::OpenListbox() {
   if (listbox_part_ && !open()) {
-    listbox_part_->SetNeedsRepositioningForSelectMenu(true);
     listbox_part_->showPopover(ASSERT_NO_EXCEPTION);
     if (selectedOption()) {
       selectedOption()->Focus();
@@ -373,7 +372,6 @@
 
   if (listbox_part_) {
     listbox_part_->SetOwnerSelectMenuElement(nullptr);
-    listbox_part_->SetNeedsRepositioningForSelectMenu(false);
   }
 
   if (new_listbox_part) {
diff --git a/third_party/blink/renderer/core/html/html_dialog_element.cc b/third_party/blink/renderer/core/html/html_dialog_element.cc
index ce4a823..537eda7 100644
--- a/third_party/blink/renderer/core/html/html_dialog_element.cc
+++ b/third_party/blink/renderer/core/html/html_dialog_element.cc
@@ -279,17 +279,15 @@
   InertSubtreesChanged(document, old_modal_dialog);
   document.UpdateStyleAndLayout(DocumentUpdateReason::kJavaScript);
 
-  if (RuntimeEnabledFeatures::CloseWatcherEnabled()) {
-    if (LocalDOMWindow* window = GetDocument().domWindow()) {
-      close_watcher_ = CloseWatcher::Create(window);
-      if (close_watcher_) {
-        auto* event_listener =
-            MakeGarbageCollected<DialogCloseWatcherEventListener>(this);
-        close_watcher_->addEventListener(event_type_names::kClose,
-                                         event_listener);
-        close_watcher_->addEventListener(event_type_names::kCancel,
-                                         event_listener);
-      }
+  if (LocalDOMWindow* window = GetDocument().domWindow()) {
+    close_watcher_ = CloseWatcher::Create(window, this);
+    if (close_watcher_) {
+      auto* event_listener =
+          MakeGarbageCollected<DialogCloseWatcherEventListener>(this);
+      close_watcher_->addEventListener(event_type_names::kClose,
+                                       event_listener);
+      close_watcher_->addEventListener(event_type_names::kCancel,
+                                       event_listener);
     }
   }
 
@@ -324,6 +322,8 @@
 }
 
 void HTMLDialogElement::CloseWatcherFiredCancel(Event* close_watcher_event) {
+  if (!RuntimeEnabledFeatures::CloseWatcherEnabled())
+    return;
   // https://wicg.github.io/close-watcher/#patch-dialog cancelAction
 
   Event* dialog_event = Event::CreateCancelable(event_type_names::kCancel);
@@ -334,6 +334,8 @@
 }
 
 void HTMLDialogElement::CloseWatcherFiredClose() {
+  if (!RuntimeEnabledFeatures::CloseWatcherEnabled())
+    return;
   // https://wicg.github.io/close-watcher/#patch-dialog closeAction
 
   close();
diff --git a/third_party/blink/renderer/core/html/html_element.cc b/third_party/blink/renderer/core/html/html_element.cc
index 1bd55b3..387f9a4 100644
--- a/third_party/blink/renderer/core/html/html_element.cc
+++ b/third_party/blink/renderer/core/html/html_element.cc
@@ -1508,7 +1508,6 @@
   }
 
   GetPopoverData()->setInvoker(nullptr);
-  GetPopoverData()->setNeedsRepositioningForSelectMenu(false);
 
   // Fire the "closing" beforetoggle event.
   auto* event = BeforeToggleEvent::CreateBubble(
@@ -1901,23 +1900,6 @@
   }
 }
 
-void HTMLElement::SetNeedsRepositioningForSelectMenu(bool flag) {
-  DCHECK(RuntimeEnabledFeatures::HTMLSelectMenuElementEnabled());
-  DCHECK(RuntimeEnabledFeatures::HTMLPopoverAttributeEnabled(
-      GetDocument().GetExecutionContext()));
-  DCHECK(HasPopoverAttribute());
-  auto* popover_data = GetPopoverData();
-  if (popover_data->needsRepositioningForSelectMenu() == flag)
-    return;
-  popover_data->setNeedsRepositioningForSelectMenu(flag);
-  if (flag) {
-    SetHasCustomStyleCallbacks();
-    SetNeedsStyleRecalc(kLocalStyleChange,
-                        StyleChangeReasonForTracing::Create(
-                            style_change_reason::kPopoverVisibilityChange));
-  }
-}
-
 void HTMLElement::SetOwnerSelectMenuElement(HTMLSelectMenuElement* element) {
   DCHECK(RuntimeEnabledFeatures::HTMLSelectMenuElementEnabled());
   DCHECK(RuntimeEnabledFeatures::HTMLPopoverAttributeEnabled(
@@ -1926,91 +1908,9 @@
   GetPopoverData()->setOwnerSelectMenuElement(element);
 }
 
-// TODO(crbug.com/1197720): The popover position should be provided by the new
-// anchored positioning scheme.
-scoped_refptr<ComputedStyle> HTMLElement::StyleForSelectMenuPopoverstyle(
-    const StyleRecalcContext& style_recalc_context) {
-  DCHECK(RuntimeEnabledFeatures::HTMLSelectMenuElementEnabled());
-  DCHECK(HasPopoverAttribute());
-  DCHECK(GetPopoverData()->needsRepositioningForSelectMenu());
-  auto* owner_select = GetPopoverData()->ownerSelectMenuElement();
-  DCHECK(owner_select);
-
-  scoped_refptr<ComputedStyle> original_style =
-      OriginalStyleForLayoutObject(style_recalc_context);
-
-  LocalDOMWindow* window = GetDocument().domWindow();
-  if (!window)
-    return original_style;
-
-  ComputedStyleBuilder style_builder(*original_style);
-
-  gfx::RectF anchor_rect_in_screen =
-      owner_select->GetBoundingClientRectNoLifecycleUpdate();
-  const float anchor_zoom =
-      owner_select->GetLayoutObject()
-          ? owner_select->GetLayoutObject()->StyleRef().EffectiveZoom()
-          : 1;
-  anchor_rect_in_screen.Scale(anchor_zoom);
-  // Don't use the LocalDOMWindow innerHeight/innerWidth getters, as those can
-  // trigger a re-entrant style and layout update.
-  int avail_width = GetDocument().View()->Size().width();
-  int avail_height = GetDocument().View()->Size().height();
-  gfx::Rect avail_rect = gfx::Rect(0, 0, avail_width, avail_height);
-
-  // Remove any margins on the listbox part, so we can position it correctly.
-  style_builder.SetMarginTop(Length::Fixed(0));
-  style_builder.SetMarginLeft(Length::Fixed(0));
-  style_builder.SetMarginRight(Length::Fixed(0));
-  style_builder.SetMarginBottom(Length::Fixed(0));
-
-  // Position the listbox part where is more space available.
-  const float available_space_above =
-      anchor_rect_in_screen.y() - avail_rect.y();
-  const float available_space_below =
-      avail_rect.bottom() - anchor_rect_in_screen.bottom();
-  if (available_space_below < available_space_above) {
-    style_builder.SetMaxHeight(Length::Fixed(available_space_above));
-    style_builder.SetBottom(
-        Length::Fixed(avail_rect.bottom() - anchor_rect_in_screen.y()));
-    style_builder.SetTop(Length::Auto());
-  } else {
-    style_builder.SetMaxHeight(Length::Fixed(available_space_below));
-    style_builder.SetTop(Length::Fixed(anchor_rect_in_screen.bottom()));
-  }
-
-  const float available_space_if_left_anchored =
-      avail_rect.right() - anchor_rect_in_screen.x();
-  const float available_space_if_right_anchored =
-      anchor_rect_in_screen.right() - avail_rect.x();
-  style_builder.SetMinWidth(Length::Fixed(anchor_rect_in_screen.width()));
-  if (available_space_if_left_anchored > anchor_rect_in_screen.width() ||
-      available_space_if_left_anchored > available_space_if_right_anchored) {
-    style_builder.SetLeft(Length::Fixed(anchor_rect_in_screen.x()));
-    style_builder.SetMaxWidth(Length::Fixed(available_space_if_left_anchored));
-  } else {
-    style_builder.SetRight(
-        Length::Fixed(avail_rect.right() - anchor_rect_in_screen.right()));
-    style_builder.SetLeft(Length::Auto());
-    style_builder.SetMaxWidth(Length::Fixed(available_space_if_right_anchored));
-  }
-
-  return style_builder.TakeStyle();
-}
-
-scoped_refptr<ComputedStyle> HTMLElement::CustomStyleForLayoutObject(
-    const StyleRecalcContext& style_recalc_context) {
-  DCHECK(HasCustomStyleCallbacks());
-  // TODO(crbug.com/1197720): This logic is for positioning the selectmenu
-  // popover. This should be replaced by the new anchored positioning scheme.
-  if (HasPopoverAttribute() &&
-      GetPopoverData()->needsRepositioningForSelectMenu()) {
-    DCHECK(RuntimeEnabledFeatures::HTMLSelectMenuElementEnabled());
-    DCHECK(RuntimeEnabledFeatures::HTMLPopoverAttributeEnabled(
-        GetDocument().GetExecutionContext()));
-    return StyleForSelectMenuPopoverstyle(style_recalc_context);
-  }
-  return Element::CustomStyleForLayoutObject(style_recalc_context);
+HTMLSelectMenuElement* HTMLElement::ownerSelectMenuElement() const {
+  return GetPopoverData() ? GetPopoverData()->ownerSelectMenuElement()
+                          : nullptr;
 }
 
 bool HTMLElement::DispatchFocusEvent(
diff --git a/third_party/blink/renderer/core/html/html_element.h b/third_party/blink/renderer/core/html/html_element.h
index b984445..a8b3858c6 100644
--- a/third_party/blink/renderer/core/html/html_element.h
+++ b/third_party/blink/renderer/core/html/html_element.h
@@ -247,12 +247,8 @@
                                    HidePopoverFocusBehavior,
                                    HidePopoverForcingLevel);
 
-  // TODO(crbug.com/1197720): The popover position should be provided by the new
-  // anchored positioning scheme.
-  void SetNeedsRepositioningForSelectMenu(bool flag);
   void SetOwnerSelectMenuElement(HTMLSelectMenuElement* element);
-  scoped_refptr<ComputedStyle> StyleForSelectMenuPopoverstyle(
-      const StyleRecalcContext&);
+  HTMLSelectMenuElement* ownerSelectMenuElement() const;
 
   bool DispatchFocusEvent(
       Element* old_focused_element,
@@ -301,9 +297,6 @@
       MutableCSSPropertyValueSet*) override;
   unsigned ParseBorderWidthAttribute(const AtomicString&) const;
 
-  scoped_refptr<ComputedStyle> CustomStyleForLayoutObject(
-      const StyleRecalcContext&) override;
-
   void ChildrenChanged(const ChildrenChange&) override;
   bool CalculateAndAdjustAutoDirectionality(Node* stay_within);
 
diff --git a/third_party/blink/renderer/core/html/html_script_element.cc b/third_party/blink/renderer/core/html/html_script_element.cc
index 487f29cd..8028e58 100644
--- a/third_party/blink/renderer/core/html/html_script_element.cc
+++ b/third_party/blink/renderer/core/html/html_script_element.cc
@@ -298,10 +298,19 @@
     const AtomicString& nonce,
     const WTF::OrdinalNumber& context_line,
     const String& script_content) {
+  // Support 'inline-speculation-rules' source.
+  // https://github.com/WICG/nav-speculation/blob/main/triggers.md#content-security-policy
+  // TODO(http://crbug.com/1382361): Standardize it officially.
+  DCHECK(loader_);
+  ContentSecurityPolicy::InlineType inline_type =
+      loader_->GetScriptType() ==
+              ScriptLoader::ScriptTypeAtPrepare::kSpeculationRules
+          ? ContentSecurityPolicy::InlineType::kScriptSpeculationRules
+          : ContentSecurityPolicy::InlineType::kScript;
   return GetExecutionContext()
       ->GetContentSecurityPolicyForCurrentWorld()
-      ->AllowInline(ContentSecurityPolicy::InlineType::kScript, this,
-                    script_content, nonce, GetDocument().Url(), context_line);
+      ->AllowInline(inline_type, this, script_content, nonce,
+                    GetDocument().Url(), context_line);
 }
 
 Document& HTMLScriptElement::GetDocument() const {
diff --git a/third_party/blink/renderer/core/html/resources/selectmenu.css b/third_party/blink/renderer/core/html/resources/selectmenu.css
index efd25f5..79a44cea 100644
--- a/third_party/blink/renderer/core/html/resources/selectmenu.css
+++ b/third_party/blink/renderer/core/html/resources/selectmenu.css
@@ -70,3 +70,74 @@
   overflow: auto;
   padding: 4px;
 }
+
+/* TODO(github.com/openui/open-ui/issues/645): We need a better way to correctly
+ * select author-provided listboxes. This doesn't work if the listbox is
+ * provided from an outer tree scope. */
+selectmenu [behavior=listbox],
+selectmenu::-internal-selectmenu-listbox {
+  margin: 0;
+  inset: auto;
+  min-inline-size: anchor-size(self-inline);
+  min-block-size: 1lh;
+  anchor-scroll: implicit;
+  position-fallback: -internal-selectmenu-listbox-default-fallbacks;
+}
+
+@position-fallback -internal-selectmenu-listbox-default-fallbacks {
+  /* Below the anchor, left-aligned, no vertical scrolling */
+  @try {
+    inset-block-start: anchor(self-end);
+    /* TODO(github.com/w3c/csswg-drafts/issues/8059): This is a workaround to
+     * enlarge the inset-modified containing block when the anchor is off the
+     * viewport. Ideally, we should just use 'auto'. Same below.
+     */
+    inset-block-end: calc(anchor(self-end) - 100vb);
+    inset-inline-start: anchor(self-start);
+    inset-inline-end: calc(anchor(self-end) - 100vi);
+  }
+  /* Below the anchor, right-aligned, no vertical scrolling */
+  @try {
+    inset-block-start: anchor(self-end);
+    inset-block-end: calc(anchor(self-end) - 100vb);
+    inset-inline-end: anchor(self-end);
+  }
+  /* Over the anchor, left-aligned, no vertical scrolling */
+  @try {
+    inset-block-end: anchor(self-start);
+    inset-inline-start: anchor(self-start);
+    inset-inline-end: calc(anchor(self-end) - 100vi);
+  }
+  /* Over the anchor, right-aligned, no vertical scrolling */
+  @try {
+    inset-block-end: anchor(self-start);
+    inset-inline-end: anchor(self-end);
+  }
+  /* Below the anchor, left-aligned, fill up the available vertical space */
+  @try {
+    inset-block-start: anchor(self-end);
+    block-size: -webkit-fill-available;
+    inset-inline-start: anchor(self-start);
+    inset-inline-end: calc(anchor(self-end) - 100vi);
+  }
+  /* Below the anchor, right-aligned, fill up the available vertical space */
+  @try {
+    inset-block-start: anchor(self-end);
+    block-size: -webkit-fill-available;
+    inset-inline-end: anchor(self-end);
+  }
+  /* Over the anchor, left-aligned, fill up the available vertical space */
+  @try {
+    inset-block-end: anchor(self-start);
+    block-size: -webkit-fill-available;
+    inset-inline-start: anchor(self-start);
+    inset-inline-end: calc(anchor(self-end) - 100vi);
+  }
+  /* Over the anchor, right-aligned, fill up the available vertical space */
+  @try {
+    inset-block-end: anchor(self-start);
+    block-size: -webkit-fill-available;
+    inset-inline-end: anchor(self-end);
+  }
+}
+
diff --git a/third_party/blink/renderer/core/input/keyboard_event_manager.cc b/third_party/blink/renderer/core/input/keyboard_event_manager.cc
index 847aeb5..c16a539 100644
--- a/third_party/blink/renderer/core/input/keyboard_event_manager.cc
+++ b/third_party/blink/renderer/core/input/keyboard_event_manager.cc
@@ -568,16 +568,24 @@
     page->GetSpatialNavigationController().HandleEscapeKeyboardEvent(event);
   }
 
+  bool cancel_skipped = false;
+  frame_->DomWindow()->closewatcher_stack()->EscapeKeyHandler(event,
+                                                              &cancel_skipped);
+
   HTMLDialogElement* dialog = frame_->GetDocument()->ActiveModalDialog();
   if (dialog && !RuntimeEnabledFeatures::CloseWatcherEnabled()) {
-    dialog->DispatchEvent(*Event::CreateCancelable(event_type_names::kCancel));
+    auto* cancel_event = Event::CreateCancelable(event_type_names::kCancel);
+    dialog->DispatchEvent(*cancel_event);
+    if (cancel_event->defaultPrevented() && cancel_skipped) {
+      UseCounter::Count(
+          frame_->GetDocument(),
+          WebFeature::kDialogCloseWatcherCancelSkippedAndDefaultPrevented);
+    }
   }
 
   auto* target_node = event->GetEventPath()[0].Target()->ToNode();
   DCHECK(target_node);
   HTMLElement::HandlePopoverLightDismiss(*event, *target_node);
-
-  frame_->DomWindow()->closewatcher_stack()->EscapeKeyHandler(event);
 }
 
 void KeyboardEventManager::DefaultEnterEventHandler(KeyboardEvent* event) {
diff --git a/third_party/blink/renderer/core/intersection_observer/element_intersection_observer_data.cc b/third_party/blink/renderer/core/intersection_observer/element_intersection_observer_data.cc
index d3959c3..180e0f2 100644
--- a/third_party/blink/renderer/core/intersection_observer/element_intersection_observer_data.cc
+++ b/third_party/blink/renderer/core/intersection_observer/element_intersection_observer_data.cc
@@ -88,6 +88,7 @@
 void ElementIntersectionObserverData::Trace(Visitor* visitor) const {
   visitor->Trace(observations_);
   visitor->Trace(observers_);
+  ElementRareDataField::Trace(visitor);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/intersection_observer/element_intersection_observer_data.h b/third_party/blink/renderer/core/intersection_observer/element_intersection_observer_data.h
index 05bf0b8f..1be2e25 100644
--- a/third_party/blink/renderer/core/intersection_observer/element_intersection_observer_data.h
+++ b/third_party/blink/renderer/core/intersection_observer/element_intersection_observer_data.h
@@ -7,6 +7,7 @@
 
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/dom/dom_high_res_time_stamp.h"
+#include "third_party/blink/renderer/core/dom/element_rare_data_field.h"
 #include "third_party/blink/renderer/platform/bindings/name_client.h"
 #include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_map.h"
 #include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_set.h"
@@ -20,7 +21,8 @@
 
 class CORE_EXPORT ElementIntersectionObserverData final
     : public GarbageCollected<ElementIntersectionObserverData>,
-      public NameClient {
+      public NameClient,
+      public ElementRareDataField {
  public:
   ElementIntersectionObserverData();
   ~ElementIntersectionObserverData() final = default;
@@ -47,7 +49,7 @@
   // algorithm is invalid and must be recomputed.
   void InvalidateCachedRects();
 
-  void Trace(Visitor*) const;
+  void Trace(Visitor*) const override;
   const char* NameInHeapSnapshot() const override {
     return "ElementIntersectionObserverData";
   }
diff --git a/third_party/blink/renderer/core/layout/anchor_scroll_data.cc b/third_party/blink/renderer/core/layout/anchor_scroll_data.cc
index 7e6789c..71189f44 100644
--- a/third_party/blink/renderer/core/layout/anchor_scroll_data.cc
+++ b/third_party/blink/renderer/core/layout/anchor_scroll_data.cc
@@ -36,24 +36,49 @@
   if (!anchor_query)
     return nullptr;
 
+  const Element* element = DynamicTo<Element>(layout_object->GetNode());
+  const bool is_in_top_layer = element ? element->IsInTopLayer() : false;
+
   const NGPhysicalFragment* fragment = nullptr;
   if (value->IsImplicit()) {
-    Element* element = DynamicTo<Element>(layout_object->GetNode());
     Element* anchor = element ? element->ImplicitAnchorElement() : nullptr;
     LayoutObject* anchor_layout_object =
         anchor ? anchor->GetLayoutObject() : nullptr;
     if (anchor_layout_object)
-      fragment = anchor_query->Fragment(anchor_layout_object);
+      fragment = anchor_query->Fragment(anchor_layout_object, is_in_top_layer);
   } else {
     DCHECK(value->IsNamed());
-    fragment = anchor_query->Fragment(&value->GetName());
+    fragment = anchor_query->Fragment(&value->GetName(), is_in_top_layer);
+  }
+
+  // |is_in_top_layer| allows NGPhysicalAnchorQuery to return elements that are
+  // rendered after, and hence, can't be used as anchors for |layout_object|.
+  if (is_in_top_layer && fragment &&
+      layout_object->IsBeforeInPreOrder(*fragment->GetLayoutObject())) {
+    return nullptr;
   }
 
   return fragment ? fragment->GetLayoutObject() : nullptr;
 }
 
+// Returns the PaintLayer of the scroll container of |anchor|.
+const PaintLayer* ContainingScrollContainerForAnchor(
+    const LayoutObject* anchor) {
+  if (!anchor->HasLayer())
+    return anchor->ContainingScrollContainer()->Layer();
+  // Normally, |scroller_layer| is the result. There's only one special case
+  // where |anchor| is fixed-positioned and |scroller_layer| is the LayoutView,
+  // then |anchor| doesn't actually scroll with |scroller_layer|, and null
+  // should be returned.
+  bool is_fixed_to_view = false;
+  const PaintLayer* scroller_layer =
+      To<LayoutBoxModelObject>(anchor)->Layer()->ContainingScrollContainerLayer(
+          &is_fixed_to_view);
+  return is_fixed_to_view ? nullptr : scroller_layer;
+}
+
 // Returns the PaintLayer of the scroll container of an anchor-positioned |box|.
-const PaintLayer* ContainingScrollContainerLayerForAnchorScroll(
+const PaintLayer* ContainingScrollContainerLayerForAnchorPositionedBox(
     const LayoutBox* box) {
   // Normally, |scroller_layer| is the result. There's only one special case
   // where |box| is fixed-positioned and |scroller_layer| is the LayoutView,
@@ -88,9 +113,10 @@
   if (const LayoutObject* anchor =
           AnchorScrollObject(owner_->GetLayoutObject())) {
     const PaintLayer* starting_layer =
-        anchor->ContainingScrollContainer()->Layer();
+        ContainingScrollContainerForAnchor(anchor);
     const PaintLayer* bounding_layer =
-        ContainingScrollContainerLayerForAnchorScroll(owner_->GetLayoutBox());
+        ContainingScrollContainerLayerForAnchorPositionedBox(
+            owner_->GetLayoutBox());
     for (const PaintLayer* layer = starting_layer; layer != bounding_layer;
          layer = layer->ContainingScrollContainerLayer()) {
       // |bounding_layer| must be either null (for fixed-positioned |owner_|) or
@@ -207,6 +233,7 @@
   visitor->Trace(owner_);
   visitor->Trace(scroll_container_layers_);
   ScrollSnapshotClient::Trace(visitor);
+  ElementRareDataField::Trace(visitor);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/layout/anchor_scroll_data.h b/third_party/blink/renderer/core/layout/anchor_scroll_data.h
index b13f2a6..1e8f5c4 100644
--- a/third_party/blink/renderer/core/layout/anchor_scroll_data.h
+++ b/third_party/blink/renderer/core/layout/anchor_scroll_data.h
@@ -5,6 +5,7 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_ANCHOR_SCROLL_DATA_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_ANCHOR_SCROLL_DATA_H_
 
+#include "third_party/blink/renderer/core/dom/element_rare_data_field.h"
 #include "third_party/blink/renderer/core/layout/geometry/physical_offset.h"
 #include "third_party/blink/renderer/core/layout/geometry/physical_rect.h"
 #include "third_party/blink/renderer/core/scroll/scroll_snapshot_client.h"
@@ -39,7 +40,8 @@
 // Whenever taking a snapshot, we also check if the above still holds for the
 // current fallback position. If not, a layout invalidation is needed.
 class AnchorScrollData : public GarbageCollected<AnchorScrollData>,
-                         public ScrollSnapshotClient {
+                         public ScrollSnapshotClient,
+                         public ElementRareDataField {
  public:
   explicit AnchorScrollData(Element*);
   virtual ~AnchorScrollData();
diff --git a/third_party/blink/renderer/core/layout/layout_box_hot.cc b/third_party/blink/renderer/core/layout/layout_box_hot.cc
index 64d34e20..bdf8f58f 100644
--- a/third_party/blink/renderer/core/layout/layout_box_hot.cc
+++ b/third_party/blink/renderer/core/layout/layout_box_hot.cc
@@ -312,6 +312,19 @@
       if (column_spanner_path || cached_layout_result->ColumnSpannerPath())
         return nullptr;
 
+      // Break appeal may have been reduced because the fragment crosses the
+      // fragmentation line, to send a strong signal to break before it
+      // instead. If we actually ended up breaking before it, this break appeal
+      // may no longer be valid, since there could be more room in the next
+      // fragmentainer. Miss the cache.
+      //
+      // TODO(mstensho): Maybe this shouldn't be necessary. Look into how
+      // FinishFragmentation() clamps break appeal down to
+      // kBreakAppealLastResort. Maybe there are better ways.
+      if (break_token && break_token->IsBreakBefore() &&
+          cached_layout_result->BreakAppeal() < kBreakAppealPerfect)
+        return nullptr;
+
       // If the node didn't break into multiple fragments, we might be able to
       // re-use the result. If the fragmentainer block-size has changed, or if
       // the fragment's block-offset within the fragmentainer has changed, we
diff --git a/third_party/blink/renderer/core/layout/ng/ng_absolute_utils_test.cc b/third_party/blink/renderer/core/layout/ng/ng_absolute_utils_test.cc
index 8b206718..b3de0db3 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_absolute_utils_test.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_absolute_utils_test.cc
@@ -110,7 +110,8 @@
         /* self_writing_direction */
         {WritingMode::kHorizontalTb, TextDirection::kLtr},
         /* offset_to_padding_box */
-        PhysicalOffset());
+        PhysicalOffset(),
+        /* is_in_top_layer */ false);
     const NGLogicalOutOfFlowInsets insets = ComputeOutOfFlowInsets(
         node.Style(), space.AvailableSize(), &anchor_evaluator);
     LogicalSize computed_available_size =
@@ -140,7 +141,8 @@
         /* self_writing_direction */
         {WritingMode::kHorizontalTb, TextDirection::kLtr},
         /* offset_to_padding_box */
-        PhysicalOffset());
+        PhysicalOffset(),
+        /* is_in_top_layer */ false);
     const NGLogicalOutOfFlowInsets insets = ComputeOutOfFlowInsets(
         node.Style(), space.AvailableSize(), &anchor_evaluator);
     LogicalSize computed_available_size =
diff --git a/third_party/blink/renderer/core/layout/ng/ng_anchor_query.cc b/third_party/blink/renderer/core/layout/ng/ng_anchor_query.cc
index 78cb526..34894d3 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_anchor_query.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_anchor_query.cc
@@ -94,49 +94,35 @@
 }
 
 const NGPhysicalAnchorReference* NGPhysicalAnchorQuery::AnchorReference(
-    const NGAnchorKey& key) const {
+    const NGAnchorKey& key,
+    bool can_use_invalid_anchors) const {
   if (const NGPhysicalAnchorReference* reference = Base::AnchorReference(key)) {
-    if (!reference->is_invalid)
+    if (can_use_invalid_anchors || !reference->is_invalid)
       return reference;
   }
   return nullptr;
 }
 
-const PhysicalRect* NGPhysicalAnchorQuery::Rect(const NGAnchorKey& key) const {
-  if (const NGPhysicalAnchorReference* reference = AnchorReference(key))
-    return &reference->rect;
-  return nullptr;
-}
-
 const NGPhysicalFragment* NGPhysicalAnchorQuery::Fragment(
-    const NGAnchorKey& key) const {
-  if (const NGPhysicalAnchorReference* reference = AnchorReference(key))
+    const NGAnchorKey& key,
+    bool can_use_invalid_anchors) const {
+  if (const NGPhysicalAnchorReference* reference =
+          AnchorReference(key, can_use_invalid_anchors)) {
     return reference->fragment.Get();
-  return nullptr;
-}
-
-const NGLogicalAnchorReference* NGLogicalAnchorQuery::AnchorReference(
-    const NGAnchorKey& key) const {
-  if (const NGLogicalAnchorReference* reference = Base::AnchorReference(key)) {
-    for (const NGLogicalAnchorReference* result = reference; result;
-         result = result->next) {
-      if (!result->is_invalid)
-        return result;
-    }
   }
   return nullptr;
 }
 
-const LogicalRect* NGLogicalAnchorQuery::Rect(const NGAnchorKey& key) const {
-  if (const NGLogicalAnchorReference* reference = AnchorReference(key))
-    return &reference->rect;
-  return nullptr;
-}
-
-const NGPhysicalFragment* NGLogicalAnchorQuery::Fragment(
-    const NGAnchorKey& key) const {
-  if (const NGLogicalAnchorReference* reference = AnchorReference(key))
-    return reference->fragment;
+const NGLogicalAnchorReference* NGLogicalAnchorQuery::AnchorReference(
+    const NGAnchorKey& key,
+    bool can_use_invalid_anchor) const {
+  if (const NGLogicalAnchorReference* reference = Base::AnchorReference(key)) {
+    for (const NGLogicalAnchorReference* result = reference; result;
+         result = result->next) {
+      if (can_use_invalid_anchor || !result->is_invalid)
+        return result;
+    }
+  }
   return nullptr;
 }
 
@@ -386,8 +372,9 @@
   if (!anchor_query)
     return absl::nullopt;
   const NGLogicalAnchorReference* anchor_reference =
-      anchor_name ? anchor_query->AnchorReference(anchor_name)
-                  : anchor_query->AnchorReference(implicit_anchor_);
+      anchor_name
+          ? anchor_query->AnchorReference(anchor_name, is_in_top_layer_)
+          : anchor_query->AnchorReference(implicit_anchor_, is_in_top_layer_);
   if (!anchor_reference)
     return absl::nullopt;
 
@@ -407,8 +394,9 @@
   if (!anchor_query)
     return absl::nullopt;
   const NGLogicalAnchorReference* anchor_reference =
-      anchor_name ? anchor_query->AnchorReference(anchor_name)
-                  : anchor_query->AnchorReference(implicit_anchor_);
+      anchor_name
+          ? anchor_query->AnchorReference(anchor_name, is_in_top_layer_)
+          : anchor_query->AnchorReference(implicit_anchor_, is_in_top_layer_);
   if (!anchor_reference)
     return absl::nullopt;
 
diff --git a/third_party/blink/renderer/core/layout/ng/ng_anchor_query.h b/third_party/blink/renderer/core/layout/ng/ng_anchor_query.h
index e2957abe..a733aed 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_anchor_query.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_anchor_query.h
@@ -166,9 +166,11 @@
  public:
   using Base = NGAnchorQueryBase<NGPhysicalAnchorReference>;
 
-  const NGPhysicalAnchorReference* AnchorReference(const NGAnchorKey&) const;
-  const PhysicalRect* Rect(const NGAnchorKey&) const;
-  const NGPhysicalFragment* Fragment(const NGAnchorKey&) const;
+  const NGPhysicalAnchorReference* AnchorReference(
+      const NGAnchorKey&,
+      bool can_use_invalid_anchors) const;
+  const NGPhysicalFragment* Fragment(const NGAnchorKey&,
+                                     bool can_use_invalid_anchors) const;
 
   void SetFromLogical(const NGLogicalAnchorQuery& logical_query,
                       const WritingModeConverter& converter);
@@ -202,9 +204,9 @@
   // Returns an empty instance.
   static const NGLogicalAnchorQuery& Empty();
 
-  const NGLogicalAnchorReference* AnchorReference(const NGAnchorKey&) const;
-  const LogicalRect* Rect(const NGAnchorKey&) const;
-  const NGPhysicalFragment* Fragment(const NGAnchorKey&) const;
+  const NGLogicalAnchorReference* AnchorReference(
+      const NGAnchorKey&,
+      bool can_use_invalid_anchor) const;
 
   enum class SetOptions {
     // A valid entry. The call order is in the tree order.
@@ -256,12 +258,14 @@
                         const LayoutObject* implicit_anchor,
                         const WritingModeConverter& container_converter,
                         WritingDirectionMode self_writing_direction,
-                        const PhysicalOffset& offset_to_padding_box)
+                        const PhysicalOffset& offset_to_padding_box,
+                        bool is_in_top_layer)
       : anchor_query_(&anchor_query),
         implicit_anchor_(implicit_anchor),
         container_converter_(container_converter),
         self_writing_direction_(self_writing_direction),
-        offset_to_padding_box_(offset_to_padding_box) {
+        offset_to_padding_box_(offset_to_padding_box),
+        is_in_top_layer_(is_in_top_layer) {
     DCHECK(anchor_query_);
   }
 
@@ -272,13 +276,15 @@
                         const LayoutObject& containing_block,
                         const WritingModeConverter& container_converter,
                         WritingDirectionMode self_writing_direction,
-                        const PhysicalOffset& offset_to_padding_box)
+                        const PhysicalOffset& offset_to_padding_box,
+                        bool is_in_top_layer)
       : anchor_queries_(&anchor_queries),
         implicit_anchor_(implicit_anchor),
         containing_block_(&containing_block),
         container_converter_(container_converter),
         self_writing_direction_(self_writing_direction),
-        offset_to_padding_box_(offset_to_padding_box) {
+        offset_to_padding_box_(offset_to_padding_box),
+        is_in_top_layer_(is_in_top_layer) {
     DCHECK(anchor_queries_);
     DCHECK(containing_block_);
   }
@@ -323,6 +329,7 @@
   LayoutUnit available_size_;
   bool is_y_axis_ = false;
   bool is_right_or_bottom_ = false;
+  bool is_in_top_layer_ = false;
   mutable bool has_anchor_functions_ = false;
 };
 
diff --git a/third_party/blink/renderer/core/layout/ng/ng_block_node.cc b/third_party/blink/renderer/core/layout/ng/ng_block_node.cc
index 517f47f7ff..2ab8bbda 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_block_node.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_block_node.cc
@@ -1799,6 +1799,11 @@
   return GetLayoutBox()->IsAtomicInlineLevel() && GetLayoutBox()->IsInline();
 }
 
+bool NGBlockNode::IsInTopLayer() const {
+  auto* element = DynamicTo<Element>(GetLayoutBox()->GetNode());
+  return element && element->IsInTopLayer();
+}
+
 bool NGBlockNode::HasAspectRatio() const {
   if (!Style().AspectRatio().IsAuto()) {
     DCHECK(!GetAspectRatio().IsEmpty());
diff --git a/third_party/blink/renderer/core/layout/ng/ng_block_node.h b/third_party/blink/renderer/core/layout/ng/ng_block_node.h
index 5c8d6cbe..390e2c4 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_block_node.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_block_node.h
@@ -150,6 +150,7 @@
   bool IsInlineLevel() const;
   bool IsAtomicInlineLevel() const;
   bool HasAspectRatio() const;
+  bool IsInTopLayer() const;
 
   // Returns the aspect ratio of a replaced element.
   LogicalSize GetAspectRatio() const;
diff --git a/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc b/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc
index d25c81e..b1e7189 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc
@@ -1688,14 +1688,16 @@
     anchor_evaluator_storage.emplace(
         *anchor_queries, implicit_anchor, *css_containing_block,
         container_converter, candidate_writing_direction,
-        container_converter.ToPhysical(node_info.container_info.rect).offset);
+        container_converter.ToPhysical(node_info.container_info.rect).offset,
+        node_info.node.IsInTopLayer());
   } else if (const NGLogicalAnchorQuery* anchor_query =
                  container_builder_->AnchorQuery()) {
     // Otherwise the |container_builder_| is the containing block.
     anchor_evaluator_storage.emplace(
         *anchor_query, implicit_anchor, container_converter,
         candidate_writing_direction,
-        container_converter.ToPhysical(node_info.container_info.rect).offset);
+        container_converter.ToPhysical(node_info.container_info.rect).offset,
+        node_info.node.IsInTopLayer());
   } else {
     anchor_evaluator_storage.emplace();
   }
diff --git a/third_party/blink/renderer/core/layout/svg/layout_svg_resource_container.cc b/third_party/blink/renderer/core/layout/svg/layout_svg_resource_container.cc
index f1e763b..6dbee76 100644
--- a/third_party/blink/renderer/core/layout/svg/layout_svg_resource_container.cc
+++ b/third_party/blink/renderer/core/layout/svg/layout_svg_resource_container.cc
@@ -218,15 +218,21 @@
 static inline void RemoveFromCacheAndInvalidateDependencies(
     LayoutObject& object,
     bool needs_layout) {
-  auto* element = DynamicTo<SVGElement>(object.GetNode());
-  if (!element)
-    return;
-
   // TODO(fs): Do we still need this? (If bounds are invalidated on a leaf
   // LayoutObject, we will propagate that during the required layout and
   // invalidate effects of self and any ancestors at that time.)
-  SVGResourceInvalidator(object).InvalidateEffects();
+  if (object.IsSVG())
+    SVGResourceInvalidator(object).InvalidateEffects();
 
+  LayoutSVGResourceContainer::InvalidateDependentElements(object, needs_layout);
+}
+
+void LayoutSVGResourceContainer::InvalidateDependentElements(
+    LayoutObject& object,
+    bool needs_layout) {
+  auto* element = DynamicTo<SVGElement>(object.GetNode());
+  if (!element)
+    return;
   element->NotifyIncomingReferences([needs_layout](SVGElement& element) {
     DCHECK(element.GetLayoutObject());
     LayoutSVGResourceContainer::MarkForLayoutAndParentResourceInvalidation(
@@ -234,6 +240,23 @@
   });
 }
 
+void LayoutSVGResourceContainer::InvalidateAncestorChainResources(
+    LayoutObject& object,
+    bool needs_layout) {
+  LayoutObject* current = object.Parent();
+  while (current) {
+    RemoveFromCacheAndInvalidateDependencies(*current, needs_layout);
+
+    if (current->IsSVGResourceContainer()) {
+      // This will process the rest of the ancestors.
+      To<LayoutSVGResourceContainer>(current)->RemoveAllClientsFromCache();
+      break;
+    }
+
+    current = current->Parent();
+  }
+}
+
 void LayoutSVGResourceContainer::MarkForLayoutAndParentResourceInvalidation(
     LayoutObject& object,
     bool needs_layout) {
@@ -245,20 +268,7 @@
   }
 
   RemoveFromCacheAndInvalidateDependencies(object, needs_layout);
-
-  // Invalidate resources in ancestor chain, if needed.
-  LayoutObject* current = object.Parent();
-  while (current) {
-    RemoveFromCacheAndInvalidateDependencies(*current, needs_layout);
-
-    if (current->IsSVGResourceContainer()) {
-      // This will process the rest of the ancestors.
-      To<LayoutSVGResourceContainer>(current)->RemoveAllClientsFromCache();
-      break;
-    }
-
-    current = current->Parent();
-  }
+  InvalidateAncestorChainResources(object, needs_layout);
 }
 
 static inline bool IsLayoutObjectOfResourceContainer(
diff --git a/third_party/blink/renderer/core/layout/svg/layout_svg_resource_container.h b/third_party/blink/renderer/core/layout/svg/layout_svg_resource_container.h
index 25e5c649..a069f01 100644
--- a/third_party/blink/renderer/core/layout/svg/layout_svg_resource_container.h
+++ b/third_party/blink/renderer/core/layout/svg/layout_svg_resource_container.h
@@ -73,6 +73,9 @@
 
   bool FindCycle() const;
 
+  static void InvalidateDependentElements(LayoutObject&, bool needs_layout);
+  static void InvalidateAncestorChainResources(LayoutObject&,
+                                               bool needs_layout);
   static void MarkForLayoutAndParentResourceInvalidation(
       LayoutObject&,
       bool needs_layout = true);
diff --git a/third_party/blink/renderer/core/layout/svg/svg_resources.cc b/third_party/blink/renderer/core/layout/svg/svg_resources.cc
index 34cf0f0..be6c680 100644
--- a/third_party/blink/renderer/core/layout/svg/svg_resources.cc
+++ b/third_party/blink/renderer/core/layout/svg/svg_resources.cc
@@ -299,6 +299,10 @@
     return;
   layout_object->SetNeedsPaintPropertyUpdate();
   MarkFilterDataDirty();
+  LayoutSVGResourceContainer::InvalidateDependentElements(*layout_object,
+                                                          false);
+  LayoutSVGResourceContainer::InvalidateAncestorChainResources(*layout_object,
+                                                               false);
 }
 
 SVGElementResourceClient::FilterData*
diff --git a/third_party/blink/renderer/core/loader/document_loader.cc b/third_party/blink/renderer/core/loader/document_loader.cc
index ecaf30e..2e3e4ed 100644
--- a/third_party/blink/renderer/core/loader/document_loader.cc
+++ b/third_party/blink/renderer/core/loader/document_loader.cc
@@ -114,6 +114,7 @@
 #include "third_party/blink/renderer/core/page/page.h"
 #include "third_party/blink/renderer/core/permissions_policy/document_policy_parser.h"
 #include "third_party/blink/renderer/core/probe/core_probes.h"
+#include "third_party/blink/renderer/core/speculation_rules/speculation_rules_header.h"
 #include "third_party/blink/renderer/core/timing/dom_window_performance.h"
 #include "third_party/blink/renderer/core/timing/profiler_group.h"
 #include "third_party/blink/renderer/core/timing/soft_navigation_heuristics.h"
@@ -2679,13 +2680,8 @@
 }
 
 void DocumentLoader::CreateParserPostCommit() {
-  if (RuntimeEnabledFeatures::SpeculationRulesFetchFromHeaderEnabled()) {
-    CountUse(WebFeature::kSpeculationRulesHeader);
-    auto& speculation_rules_header =
-        response_.HttpHeaderField(http_names::kSpeculationRules);
-    PreloadHelper::LoadSpeculationRuleLinkFromHeader(
-        speculation_rules_header, GetFrame()->GetDocument(), *GetFrame());
-  }
+  SpeculationRulesHeader::ProcessHeadersForDocumentResponse(
+      response_, *frame_->DomWindow());
 
   if (navigation_delivery_type_ ==
       network::mojom::NavigationDeliveryType::kNavigationalPrefetch) {
diff --git a/third_party/blink/renderer/core/loader/preload_helper.cc b/third_party/blink/renderer/core/loader/preload_helper.cc
index ebaabac..dc4074d5 100644
--- a/third_party/blink/renderer/core/loader/preload_helper.cc
+++ b/third_party/blink/renderer/core/loader/preload_helper.cc
@@ -4,7 +4,6 @@
 
 #include "third_party/blink/renderer/core/loader/preload_helper.h"
 
-#include "net/http/structured_headers.h"
 #include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink.h"
 #include "third_party/blink/public/platform/platform.h"
@@ -38,18 +37,12 @@
 #include "third_party/blink/renderer/core/loader/resource/image_resource.h"
 #include "third_party/blink/renderer/core/loader/resource/link_prefetch_resource.h"
 #include "third_party/blink/renderer/core/loader/resource/script_resource.h"
-#include "third_party/blink/renderer/core/loader/resource/speculation_rules_resource.h"
-#include "third_party/blink/renderer/core/loader/speculation_rule_loader.h"
 #include "third_party/blink/renderer/core/loader/subresource_integrity_helper.h"
 #include "third_party/blink/renderer/core/page/viewport_description.h"
 #include "third_party/blink/renderer/core/script/modulator.h"
 #include "third_party/blink/renderer/core/script/script_loader.h"
-#include "third_party/blink/renderer/core/speculation_rules/document_speculation_rules.h"
-#include "third_party/blink/renderer/core/speculation_rules/speculation_rules_metrics.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
-#include "third_party/blink/renderer/platform/json/json_parser.h"
-#include "third_party/blink/renderer/platform/json/json_values.h"
 #include "third_party/blink/renderer/platform/loader/fetch/fetch_initiator_type_names.h"
 #include "third_party/blink/renderer/platform/loader/fetch/raw_resource.h"
 #include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h"
@@ -635,96 +628,6 @@
     pending_preload->AddResource(resource);
 }
 
-void PreloadHelper::LoadSpeculationRuleLinkFromHeader(
-    const String& header_value,
-    Document* document,
-    LocalFrame& frame) {
-  DCHECK(document);
-  if (header_value.empty())
-    return;
-
-  auto parsed_header = net::structured_headers::ParseList(header_value.Utf8());
-  if (!parsed_header.has_value()) {
-    CountSpeculationRulesLoadOutcome(
-        SpeculationRulesLoadOutcome::kUnparseableSpeculationRulesHeader);
-    SendMessageToConsoleForPossiblyNullDocument(
-        MakeGarbageCollected<ConsoleMessage>(
-            mojom::blink::ConsoleMessageSource::kOther,
-            mojom::blink::ConsoleMessageLevel::kWarning,
-            String("Cannot parse Speculation-Rules header value.")),
-        document, &frame);
-    return;
-  }
-
-  if (parsed_header->empty()) {
-    // This is valid, but unlikely to be intentional. Let's make a note of it.
-    CountSpeculationRulesLoadOutcome(
-        SpeculationRulesLoadOutcome::kEmptySpeculationRulesHeader);
-    return;
-  }
-
-  for (auto const& parsed_item : parsed_header.value()) {
-    // Only strings are valid list members.
-    if (parsed_item.member.size() != 1u ||
-        !parsed_item.member[0].item.is_string()) {
-      CountSpeculationRulesLoadOutcome(
-          SpeculationRulesLoadOutcome::kInvalidSpeculationRulesHeaderItem);
-      SendMessageToConsoleForPossiblyNullDocument(
-          MakeGarbageCollected<ConsoleMessage>(
-              mojom::blink::ConsoleMessageSource::kOther,
-              mojom::blink::ConsoleMessageLevel::kWarning,
-              String("Only strings are valid in Speculation-Rules header value "
-                     "and inner lists are ignored.")),
-          document, &frame);
-      continue;
-    }
-    const auto& url_str = String(parsed_item.member[0].item.GetString());
-    KURL speculation_rule_url(document->BaseURL(), url_str);
-    if (url_str.empty() || !speculation_rule_url.IsValid()) {
-      CountSpeculationRulesLoadOutcome(
-          SpeculationRulesLoadOutcome::kInvalidSpeculationRulesHeaderItem);
-      SendMessageToConsoleForPossiblyNullDocument(
-          MakeGarbageCollected<ConsoleMessage>(
-              mojom::blink::ConsoleMessageSource::kOther,
-              mojom::blink::ConsoleMessageLevel::kWarning,
-              String("URL \"" + url_str +
-                     "\" found in Speculation-Rules header is invalid.")),
-          document, &frame);
-      continue;
-    }
-
-    ResourceRequest resource_request(speculation_rule_url);
-
-    resource_request.SetPrefetchMaybeForTopLevelNavigation(false);
-    resource_request.SetFetchPriorityHint(
-        mojom::blink::FetchPriorityHint::kLow);
-
-    // Always use CORS. Adopt new best practices for subresources: CORS requests
-    // with same-origin credentials only.
-    auto* origin = document->GetExecutionContext()->GetSecurityOrigin();
-    resource_request.SetMode(network::mojom::RequestMode::kCors);
-    resource_request.SetCredentialsMode(
-        network::mojom::CredentialsMode::kSameOrigin);
-    resource_request.RemoveUserAndPassFromURL();
-    resource_request.SetRequestorOrigin(origin);
-    resource_request.SetHTTPOrigin(origin);
-
-    ResourceLoaderOptions options(
-        document->GetExecutionContext()->GetCurrentWorld());
-    options.initiator_info.name = fetch_initiator_type_names::kOther;
-
-    FetchParameters speculation_rule_params(std::move(resource_request),
-                                            options);
-
-    SpeculationRulesResource* resource = SpeculationRulesResource::Fetch(
-        speculation_rule_params, document->Fetcher());
-
-    SpeculationRuleLoader* speculation_rule_loader =
-        MakeGarbageCollected<SpeculationRuleLoader>(*document);
-    speculation_rule_loader->LoadResource(resource, speculation_rule_url);
-  }
-}
-
 void PreloadHelper::LoadLinksFromHeader(
     const String& header_value,
     const KURL& base_url,
diff --git a/third_party/blink/renderer/core/loader/preload_helper.h b/third_party/blink/renderer/core/loader/preload_helper.h
index 2908470..b4be18f3 100644
--- a/third_party/blink/renderer/core/loader/preload_helper.h
+++ b/third_party/blink/renderer/core/loader/preload_helper.h
@@ -34,10 +34,6 @@
   // can be preloaded at commit time.
   enum MediaPreloadPolicy { kLoadAll, kOnlyLoadNonMedia, kOnlyLoadMedia };
 
-  static void LoadSpeculationRuleLinkFromHeader(const String& header_value,
-                                                Document* document,
-                                                LocalFrame& frame);
-
   static void LoadLinksFromHeader(
       const String& header_value,
       const KURL& base_url,
diff --git a/third_party/blink/renderer/core/loader/resource/font_resource_test.cc b/third_party/blink/renderer/core/loader/resource/font_resource_test.cc
index 844cb8a9..8654a8a 100644
--- a/third_party/blink/renderer/core/loader/resource/font_resource_test.cc
+++ b/third_party/blink/renderer/core/loader/resource/font_resource_test.cc
@@ -25,6 +25,7 @@
 #include "third_party/blink/renderer/platform/loader/testing/mock_resource_client.h"
 #include "third_party/blink/renderer/platform/loader/testing/test_loader_factory.h"
 #include "third_party/blink/renderer/platform/loader/testing/test_resource_fetcher_properties.h"
+#include "third_party/blink/renderer/platform/testing/histogram_tester.h"
 #include "third_party/blink/renderer/platform/testing/mock_context_lifecycle_notifier.h"
 #include "third_party/blink/renderer/platform/testing/url_test_helpers.h"
 #include "third_party/blink/renderer/platform/weborigin/kurl.h"
@@ -121,6 +122,90 @@
   MemoryCache::Get()->Remove(resource1);
 }
 
+// Tests if the RevalidationPolicy UMA works properly for fonts.
+TEST_F(FontResourceTest, RevalidationPolicyMetrics) {
+  blink::HistogramTester histogram_tester;
+  auto* properties = MakeGarbageCollected<TestResourceFetcherProperties>();
+  MockFetchContext* context = MakeGarbageCollected<MockFetchContext>();
+  auto* fetcher = MakeGarbageCollected<ResourceFetcher>(
+      ResourceFetcherInit(properties->MakeDetachable(), context,
+                          base::MakeRefCounted<scheduler::FakeTaskRunner>(),
+                          base::MakeRefCounted<scheduler::FakeTaskRunner>(),
+                          MakeGarbageCollected<TestLoaderFactory>(),
+                          MakeGarbageCollected<MockContextLifecycleNotifier>(),
+                          nullptr /* back_forward_cache_loader_helper */));
+
+  KURL url_preload_font("http://127.0.0.1:8000/font_preload.ttf");
+  ResourceResponse response_preload_font(url_preload_font);
+  response_preload_font.SetHttpStatusCode(200);
+  response_preload_font.SetHttpHeaderField(http_names::kCacheControl,
+                                           "max-age=3600");
+  url_test_helpers::RegisterMockedURLLoadWithCustomResponse(
+      url_preload_font, "", WrappedResourceResponse(response_preload_font));
+
+  // Test font preloads are immediately loaded.
+  FetchParameters fetch_params_preload =
+      FetchParameters::CreateForTest(ResourceRequest(url_preload_font));
+  fetch_params_preload.SetLinkPreload(true);
+
+  Resource* resource =
+      FontResource::Fetch(fetch_params_preload, fetcher, nullptr);
+  url_test_helpers::ServeAsynchronousRequests();
+  ASSERT_TRUE(resource);
+  EXPECT_TRUE(MemoryCache::Get()->Contains(resource));
+
+  Resource* new_resource =
+      FontResource::Fetch(fetch_params_preload, fetcher, nullptr);
+  EXPECT_EQ(resource, new_resource);
+
+  // Test histograms.
+  histogram_tester.ExpectTotalCount(
+      "Blink.MemoryCache.RevalidationPolicy.Preload.Font", 2);
+  histogram_tester.ExpectBucketCount(
+      "Blink.MemoryCache.RevalidationPolicy.Preload.Font",
+      static_cast<int>(ResourceFetcher::RevalidationPolicyForMetrics::kLoad),
+      1);
+  histogram_tester.ExpectBucketCount(
+      "Blink.MemoryCache.RevalidationPolicy.Preload.Font",
+      static_cast<int>(ResourceFetcher::RevalidationPolicyForMetrics::kUse), 1);
+
+  KURL url_font("http://127.0.0.1:8000/font.ttf");
+  ResourceResponse response_font(url_preload_font);
+  response_font.SetHttpStatusCode(200);
+  response_font.SetHttpHeaderField(http_names::kCacheControl, "max-age=3600");
+  url_test_helpers::RegisterMockedURLLoadWithCustomResponse(
+      url_font, "", WrappedResourceResponse(response_font));
+
+  // Test deferred and ordinal font loads are correctly counted as deferred.
+  FetchParameters fetch_params =
+      FetchParameters::CreateForTest(ResourceRequest(url_font));
+  resource = FontResource::Fetch(fetch_params, fetcher, nullptr);
+  ASSERT_TRUE(resource);
+  histogram_tester.ExpectTotalCount("Blink.MemoryCache.RevalidationPolicy.Font",
+                                    1);
+  histogram_tester.ExpectBucketCount(
+      "Blink.MemoryCache.RevalidationPolicy.Font",
+      static_cast<int>(ResourceFetcher::RevalidationPolicyForMetrics::kDefer),
+      1);
+  fetcher->StartLoad(resource);
+  url_test_helpers::ServeAsynchronousRequests();
+  histogram_tester.ExpectTotalCount("Blink.MemoryCache.RevalidationPolicy.Font",
+                                    2);
+  histogram_tester.ExpectBucketCount(
+      "Blink.MemoryCache.RevalidationPolicy.Font",
+      static_cast<int>(ResourceFetcher::RevalidationPolicyForMetrics::
+                           kPreviouslyDeferredLoad),
+      1);
+  // Load the resource again, deferred resource already loaded shall be counted
+  // as kUse.
+  resource = FontResource::Fetch(fetch_params, fetcher, nullptr);
+  histogram_tester.ExpectTotalCount("Blink.MemoryCache.RevalidationPolicy.Font",
+                                    3);
+  histogram_tester.ExpectBucketCount(
+      "Blink.MemoryCache.RevalidationPolicy.Font",
+      static_cast<int>(ResourceFetcher::RevalidationPolicyForMetrics::kUse), 1);
+}
+
 // Tests if cache-aware font loading works correctly.
 TEST_F(CacheAwareFontResourceTest, CacheAwareFontLoading) {
   KURL url("http://127.0.0.1:8000/font.woff");
diff --git a/third_party/blink/renderer/core/loader/resource/image_resource_test.cc b/third_party/blink/renderer/core/loader/resource/image_resource_test.cc
index 135bc66..b92a65e 100644
--- a/third_party/blink/renderer/core/loader/resource/image_resource_test.cc
+++ b/third_party/blink/renderer/core/loader/resource/image_resource_test.cc
@@ -68,6 +68,7 @@
 #include "third_party/blink/renderer/platform/network/http_names.h"
 #include "third_party/blink/renderer/platform/scheduler/test/fake_frame_scheduler.h"
 #include "third_party/blink/renderer/platform/scheduler/test/fake_task_runner.h"
+#include "third_party/blink/renderer/platform/testing/histogram_tester.h"
 #include "third_party/blink/renderer/platform/testing/mock_context_lifecycle_notifier.h"
 #include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
 #include "third_party/blink/renderer/platform/testing/scoped_mocked_url.h"
@@ -1178,4 +1179,67 @@
   EXPECT_EQ(++current_ua_count, GetUACSSResourceCount());
 }
 
+TEST_F(ImageResourceCounterTest, RevalidationPolicyMetrics) {
+  blink::HistogramTester histogram_tester;
+  auto* fetcher = CreateFetcher();
+
+  KURL test_url("http://127.0.0.1:8000/img.png");
+  ScopedMockedURLLoad url_load(test_url, GetTestFilePath());
+
+  // Test image preloads are immediately loaded.
+  FetchParameters fetch_params =
+      FetchParameters::CreateForTest(ResourceRequest(test_url));
+  fetch_params.SetLinkPreload(true);
+
+  Resource* resource = ImageResource::Fetch(fetch_params, fetcher);
+  ASSERT_TRUE(resource);
+  EXPECT_TRUE(MemoryCache::Get()->Contains(resource));
+
+  Resource* new_resource = ImageResource::Fetch(fetch_params, fetcher);
+  EXPECT_EQ(resource, new_resource);
+
+  // Test histograms.
+  histogram_tester.ExpectTotalCount(
+      "Blink.MemoryCache.RevalidationPolicy.Preload.Image", 2);
+  histogram_tester.ExpectBucketCount(
+      "Blink.MemoryCache.RevalidationPolicy.Preload.Image",
+      static_cast<int>(ResourceFetcher::RevalidationPolicyForMetrics::kLoad),
+      1);
+  histogram_tester.ExpectBucketCount(
+      "Blink.MemoryCache.RevalidationPolicy.Preload.Image",
+      static_cast<int>(ResourceFetcher::RevalidationPolicyForMetrics::kUse), 1);
+
+  KURL test_url_deferred("http://127.0.0.1:8000/img_deferred.ttf");
+  ScopedMockedURLLoad url_load_deferred(test_url_deferred, GetTestFilePath());
+
+  // Test deferred image loads are correctly counted.
+  FetchParameters fetch_params_deferred =
+      FetchParameters::CreateForTest(ResourceRequest(test_url_deferred));
+  fetch_params_deferred.SetLazyImageDeferred();
+  resource = ImageResource::Fetch(fetch_params_deferred, fetcher);
+  ASSERT_TRUE(resource);
+  histogram_tester.ExpectTotalCount(
+      "Blink.MemoryCache.RevalidationPolicy.Image", 1);
+  histogram_tester.ExpectBucketCount(
+      "Blink.MemoryCache.RevalidationPolicy.Image",
+      static_cast<int>(ResourceFetcher::RevalidationPolicyForMetrics::kDefer),
+      1);
+  fetcher->StartLoad(resource);
+  histogram_tester.ExpectTotalCount(
+      "Blink.MemoryCache.RevalidationPolicy.Image", 2);
+  histogram_tester.ExpectBucketCount(
+      "Blink.MemoryCache.RevalidationPolicy.Image",
+      static_cast<int>(ResourceFetcher::RevalidationPolicyForMetrics::
+                           kPreviouslyDeferredLoad),
+      1);
+  // Load the same deferred image again. Already-loaded resources shall be
+  // counted as kUse.
+  resource = ImageResource::Fetch(fetch_params_deferred, fetcher);
+  histogram_tester.ExpectTotalCount(
+      "Blink.MemoryCache.RevalidationPolicy.Image", 3);
+  histogram_tester.ExpectBucketCount(
+      "Blink.MemoryCache.RevalidationPolicy.Image",
+      static_cast<int>(ResourceFetcher::RevalidationPolicyForMetrics::kUse), 1);
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/messaging/blink_transferable_message_mojom_traits_test.cc b/third_party/blink/renderer/core/messaging/blink_transferable_message_mojom_traits_test.cc
index 2aa5afb..cc76344 100644
--- a/third_party/blink/renderer/core/messaging/blink_transferable_message_mojom_traits_test.cc
+++ b/third_party/blink/renderer/core/messaging/blink_transferable_message_mojom_traits_test.cc
@@ -23,6 +23,7 @@
 #include "third_party/blink/renderer/platform/graphics/unaccelerated_static_bitmap_image.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/skia/include/core/SkBitmap.h"
+#include "third_party/skia/include/core/SkCanvas.h"
 #include "third_party/skia/include/core/SkSurface.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/core/origin_trials/origin_trial_context.cc b/third_party/blink/renderer/core/origin_trials/origin_trial_context.cc
index 5cebbe4..df3222ee 100644
--- a/third_party/blink/renderer/core/origin_trials/origin_trial_context.cc
+++ b/third_party/blink/renderer/core/origin_trials/origin_trial_context.cc
@@ -504,6 +504,11 @@
         features::kSpeculationRulesPrefetchProxy);
   }
 
+  if (trial_name == "SpeculationRulesPrefetchFuture") {
+    return base::FeatureList::IsEnabled(
+        features::kSpeculationRulesPrefetchFuture);
+  }
+
   if (trial_name == "PendingBeaconAPI") {
     return base::FeatureList::IsEnabled(features::kPendingBeaconAPI);
   }
diff --git a/third_party/blink/renderer/core/paint/paint_controller_paint_test.h b/third_party/blink/renderer/core/paint/paint_controller_paint_test.h
index 6166de1d..5ae0f19 100644
--- a/third_party/blink/renderer/core/paint/paint_controller_paint_test.h
+++ b/third_party/blink/renderer/core/paint/paint_controller_paint_test.h
@@ -6,6 +6,8 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_PAINT_CONTROLLER_PAINT_TEST_H_
 
 #include "base/check_op.h"
+#include "cc/paint/paint_op.h"
+#include "cc/paint/paint_op_buffer_iterator.h"
 #include "third_party/blink/renderer/core/editing/frame_selection.h"
 #include "third_party/blink/renderer/core/frame/local_frame_view.h"
 #include "third_party/blink/renderer/core/layout/layout_view.h"
diff --git a/third_party/blink/renderer/core/paint/text_decoration_info.h b/third_party/blink/renderer/core/paint/text_decoration_info.h
index cc2d3ed..b6b964cf 100644
--- a/third_party/blink/renderer/core/paint/text_decoration_info.h
+++ b/third_party/blink/renderer/core/paint/text_decoration_info.h
@@ -6,7 +6,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_TEXT_DECORATION_INFO_H_
 
 #include "base/types/strong_alias.h"
-#include "cc/paint/paint_op_buffer.h"
+#include "cc/paint/paint_record.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/layout/geometry/physical_offset.h"
diff --git a/third_party/blink/renderer/core/permissions_policy/permissions_policy_features.json5 b/third_party/blink/renderer/core/permissions_policy/permissions_policy_features.json5
index afa13c2..ecc3707 100644
--- a/third_party/blink/renderer/core/permissions_policy/permissions_policy_features.json5
+++ b/third_party/blink/renderer/core/permissions_policy/permissions_policy_features.json5
@@ -358,6 +358,11 @@
       depends_on: ["SharedStorageAPI"],
     },
     {
+      name: "SmartCard",
+      permissions_policy_name: "smart-card",
+      depends_on: ["SmartCard"],
+    },
+    {
       name: "StorageAccessAPI",
       feature_default: "EnableForAll",
       permissions_policy_name: "storage-access",
diff --git a/third_party/blink/renderer/core/resize_observer/resize_observer_size.cc b/third_party/blink/renderer/core/resize_observer/resize_observer_size.cc
index 15f3c3b..ad2e363 100644
--- a/third_party/blink/renderer/core/resize_observer/resize_observer_size.cc
+++ b/third_party/blink/renderer/core/resize_observer/resize_observer_size.cc
@@ -19,4 +19,9 @@
 
 ResizeObserverSize::ResizeObserverSize() = default;
 
+void ResizeObserverSize::Trace(Visitor* visitor) const {
+  ScriptWrappable::Trace(visitor);
+  ElementRareDataField::Trace(visitor);
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/resize_observer/resize_observer_size.h b/third_party/blink/renderer/core/resize_observer/resize_observer_size.h
index f4e1a05..10cd06ad 100644
--- a/third_party/blink/renderer/core/resize_observer/resize_observer_size.h
+++ b/third_party/blink/renderer/core/resize_observer/resize_observer_size.h
@@ -6,12 +6,14 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_RESIZE_OBSERVER_RESIZE_OBSERVER_SIZE_H_
 
 #include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/core/dom/element_rare_data_field.h"
 #include "third_party/blink/renderer/core/layout/geometry/logical_size.h"
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
 
 namespace blink {
 
-class CORE_EXPORT ResizeObserverSize final : public ScriptWrappable {
+class CORE_EXPORT ResizeObserverSize final : public ScriptWrappable,
+                                             public ElementRareDataField {
   DEFINE_WRAPPERTYPEINFO();
 
  public:
@@ -27,6 +29,8 @@
     return {LayoutUnit(inline_size_), LayoutUnit(block_size_)};
   }
 
+  void Trace(Visitor*) const override;
+
  private:
   double inline_size_;
   double block_size_;
diff --git a/third_party/blink/renderer/core/speculation_rules/build.gni b/third_party/blink/renderer/core/speculation_rules/build.gni
index b670e85..784aa8c 100644
--- a/third_party/blink/renderer/core/speculation_rules/build.gni
+++ b/third_party/blink/renderer/core/speculation_rules/build.gni
@@ -11,12 +11,15 @@
   "speculation_rule.h",
   "speculation_rule_set.cc",
   "speculation_rule_set.h",
+  "speculation_rules_header.cc",
+  "speculation_rules_header.h",
   "speculation_rules_metrics.cc",
   "speculation_rules_metrics.h",
 ]
 
 blink_core_tests_speculation_rules = [
   "speculation_rule_set_test.cc",
+  "speculation_rules_header_test.cc",
   "speculation_rules_origin_trial_test.cc",
   "stub_speculation_host.cc",
   "stub_speculation_host.h",
diff --git a/third_party/blink/renderer/core/speculation_rules/document_speculation_rules.cc b/third_party/blink/renderer/core/speculation_rules/document_speculation_rules.cc
index 687030e..53343d46 100644
--- a/third_party/blink/renderer/core/speculation_rules/document_speculation_rules.cc
+++ b/third_party/blink/renderer/core/speculation_rules/document_speculation_rules.cc
@@ -117,7 +117,8 @@
   // a means of specifying a suitable policy. Once
   // SpeculationRulesReferrerPolicyKey ships, this workaround should be removed.
   // See https://crbug.com/1398772.
-  if (!RuntimeEnabledFeatures::SpeculationRulesReferrerPolicyKeyEnabled() &&
+  if (!RuntimeEnabledFeatures::SpeculationRulesReferrerPolicyKeyEnabled(
+          execution_context) &&
       !AcceptableReferrerPolicy(referrer, is_initially_same_site)) {
     referrer = SecurityPolicy::GenerateReferrer(
         network::mojom::ReferrerPolicy::kNever, url, outgoing_referrer);
diff --git a/third_party/blink/renderer/core/speculation_rules/speculation_rule_set.cc b/third_party/blink/renderer/core/speculation_rules/speculation_rule_set.cc
index e685bc5..47463438 100644
--- a/third_party/blink/renderer/core/speculation_rules/speculation_rule_set.cc
+++ b/third_party/blink/renderer/core/speculation_rules/speculation_rule_set.cc
@@ -61,8 +61,9 @@
   for (wtf_size_t i = 0; i < input->size(); ++i) {
     const String& input_key = input->at(i).first;
     const bool conditionally_known_key =
-        RuntimeEnabledFeatures::SpeculationRulesReferrerPolicyKeyEnabled() &&
-        (input_key == "referrer_policy");
+        RuntimeEnabledFeatures::SpeculationRulesReferrerPolicyKeyEnabled(
+            context) &&
+        input_key == "referrer_policy";
     if (!base::Contains(kKnownKeys, input_key) && !conditionally_known_key)
       return nullptr;
   }
@@ -206,7 +207,8 @@
   JSONValue* referrer_policy_value = input->Get("referrer_policy");
   if (referrer_policy_value) {
     // Feature gated due to known keys check above.
-    DCHECK(RuntimeEnabledFeatures::SpeculationRulesReferrerPolicyKeyEnabled());
+    DCHECK(RuntimeEnabledFeatures::SpeculationRulesReferrerPolicyKeyEnabled(
+        context));
 
     String referrer_policy_str;
     if (!referrer_policy_value->AsString(&referrer_policy_str))
diff --git a/third_party/blink/renderer/core/speculation_rules/speculation_rules_header.cc b/third_party/blink/renderer/core/speculation_rules/speculation_rules_header.cc
new file mode 100644
index 0000000..041f3a2
--- /dev/null
+++ b/third_party/blink/renderer/core/speculation_rules/speculation_rules_header.cc
@@ -0,0 +1,258 @@
+// 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 "third_party/blink/renderer/core/speculation_rules/speculation_rules_header.h"
+
+#include "base/containers/contains.h"
+#include "base/feature_list.h"
+#include "net/http/structured_headers.h"
+#include "third_party/blink/public/common/features.h"
+#include "third_party/blink/public/common/origin_trials/trial_token.h"
+#include "third_party/blink/public/common/origin_trials/trial_token_result.h"
+#include "third_party/blink/public/mojom/devtools/console_message.mojom-shared.h"
+#include "third_party/blink/renderer/core/execution_context/execution_context.h"
+#include "third_party/blink/renderer/core/execution_context/security_context.h"
+#include "third_party/blink/renderer/core/frame/local_dom_window.h"
+#include "third_party/blink/renderer/core/frame/web_feature.h"
+#include "third_party/blink/renderer/core/loader/resource/speculation_rules_resource.h"
+#include "third_party/blink/renderer/core/loader/speculation_rule_loader.h"
+#include "third_party/blink/renderer/core/origin_trials/origin_trial_context.h"
+#include "third_party/blink/renderer/core/speculation_rules/speculation_rules_metrics.h"
+#include "third_party/blink/renderer/platform/loader/fetch/fetch_initiator_type_names.h"
+#include "third_party/blink/renderer/platform/loader/fetch/fetch_parameters.h"
+#include "third_party/blink/renderer/platform/loader/fetch/resource_loader_options.h"
+#include "third_party/blink/renderer/platform/loader/fetch/resource_request.h"
+#include "third_party/blink/renderer/platform/loader/fetch/resource_response.h"
+#include "third_party/blink/renderer/platform/network/http_names.h"
+#include "third_party/blink/renderer/platform/weborigin/security_origin.h"
+
+namespace blink {
+
+namespace {
+
+// These are the only trials which can be enabled via this approach.
+// This is not a general-purpose way of enabling trials.
+constexpr base::StringPiece kSpeculationRulesHeaderTrials[] = {
+    "SpeculationRulesPrefetchFuture",
+};
+
+}  // namespace
+
+SpeculationRulesHeader::SpeculationRulesHeader() = default;
+SpeculationRulesHeader::~SpeculationRulesHeader() = default;
+
+// static
+void SpeculationRulesHeader::ProcessHeadersForDocumentResponse(
+    const ResourceResponse& response,
+    LocalDOMWindow& window) {
+  // If speculation rules fetch from header isn't enabled, nor is the ability to
+  // turn it on given an Origin-Trial token found in the same response, then we
+  // should not process the Speculation-Rules header at all.
+  const bool can_enable_origin_trial = base::FeatureList::IsEnabled(
+      features::kSpeculationRulesHeaderEnableThirdPartyOriginTrial);
+  if (!RuntimeEnabledFeatures::SpeculationRulesFetchFromHeaderEnabled(
+          &window) &&
+      !can_enable_origin_trial) {
+    return;
+  }
+
+  // If the Speculation-Rules header isn't present at all, then there's nothing
+  // to do.
+  const AtomicString& header_value =
+      response.HttpHeaderField(http_names::kSpeculationRules);
+  if (!header_value)
+    return;
+
+  window.CountUse(WebFeature::kSpeculationRulesHeader);
+
+  SpeculationRulesHeader self;
+  self.ParseSpeculationRulesHeader(header_value, window.BaseURL());
+
+  // If we might be able to enable an origin trial, parse the Origin-Trial
+  // header to find the relevant tokens and load them in.
+  if (can_enable_origin_trial) {
+    self.ParseOriginTrialHeader(
+        response.HttpHeaderField(http_names::kOriginTrial),
+        window.GetSecurityContext());
+    self.MaybeEnableFeatureFromOriginTrial(window);
+  }
+
+  // After doing so, if fetching Speculation-Rules from a header is enabled, we
+  // can proceed.
+  if (RuntimeEnabledFeatures::SpeculationRulesFetchFromHeaderEnabled(&window)) {
+    self.ReportErrors(window);
+    self.StartFetches(*window.document());
+  }
+}
+
+void SpeculationRulesHeader::ParseSpeculationRulesHeader(
+    const String& header_value,
+    const KURL& base_url) {
+  auto parsed_header = net::structured_headers::ParseList(header_value.Utf8());
+  if (!parsed_header.has_value()) {
+    errors_.push_back(std::pair(
+        SpeculationRulesLoadOutcome::kUnparseableSpeculationRulesHeader,
+        "Cannot parse Speculation-Rules header value."));
+    return;
+  }
+
+  if (parsed_header->empty()) {
+    // This is valid, but unlikely to be intentional. Let's make a note of it.
+    CountSpeculationRulesLoadOutcome(
+        SpeculationRulesLoadOutcome::kEmptySpeculationRulesHeader);
+    return;
+  }
+
+  for (auto const& parsed_item : parsed_header.value()) {
+    // Only strings are valid list members.
+    if (parsed_item.member.size() != 1u ||
+        !parsed_item.member[0].item.is_string()) {
+      errors_.push_back(std::pair(
+          SpeculationRulesLoadOutcome::kInvalidSpeculationRulesHeaderItem,
+          "Only strings are valid in Speculation-Rules header value "
+          "and inner lists are ignored."));
+      continue;
+    }
+    const auto& url_str = String(parsed_item.member[0].item.GetString());
+    KURL speculation_rule_url(base_url, url_str);
+    if (url_str.empty() || !speculation_rule_url.IsValid()) {
+      errors_.push_back(std::pair(
+          SpeculationRulesLoadOutcome::kInvalidSpeculationRulesHeaderItem,
+          String("URL \"" + url_str +
+                 "\" found in Speculation-Rules header is invalid.")));
+      continue;
+    }
+    urls_.push_back(std::move(speculation_rule_url));
+  }
+}
+
+// This extracts tokens from the Origin-Trial header and validates them.
+//
+// In particular, while OriginTrialContext takes care of the standard
+// validation, we want to expressly check that only certain speculation rules
+// related origin trials can be enabled in connection with the Speculation-Rules
+// header.
+//
+// This is not intended be a general way of enabling origin trials; this applies
+// only to certain trials, and then only to tokens which can be enabled via a
+// third-party origin.
+//
+// To confirm this we need to invoke some of the validation code to obtain a
+// parsed and validated token, and examine the contained feature name, before
+// storing in the set of tokens which will be injected into OriginTrialContext.
+void SpeculationRulesHeader::ParseOriginTrialHeader(
+    const String& header_value,
+    SecurityContext& security_context) {
+  std::unique_ptr<Vector<String>> tokens =
+      OriginTrialContext::ParseHeaderValue(header_value);
+  if (!tokens || tokens->empty())
+    return;
+
+  // Use an opaque origin as the first-party origin, so that we won't end up
+  // accepting any tokens which are valid as first-party. The normal code path
+  // should handle those.
+  TrialTokenValidator::OriginInfo first_party{
+      SecurityOrigin::CreateUniqueOpaque()->ToUrlOrigin(),
+      security_context.GetSecureContextMode() ==
+          SecureContextMode::kSecureContext,
+  };
+  Vector<TrialTokenValidator::OriginInfo> third_parties;
+  for (const KURL& url : urls_) {
+    auto origin = SecurityOrigin::Create(url);
+    if (origin->IsOpaque() ||
+        origin->IsSameOriginWith(security_context.GetSecurityOrigin())) {
+      continue;
+    }
+    third_parties.emplace_back(origin->ToUrlOrigin());
+  }
+
+  TrialTokenValidator validator;
+  for (String& token : *tokens) {
+    TrialTokenResult result = validator.ValidateTokenAndTrialWithOriginInfo(
+        StringUTF8Adaptor(token).AsStringPiece(), first_party, third_parties,
+        base::Time::Now());
+
+    // Don't store tokens which don't validate.
+    if (result.Status() != OriginTrialTokenStatus::kSuccess)
+      continue;
+
+    const auto& parsed_token = *result.ParsedToken();
+    DCHECK(parsed_token.is_third_party());
+
+    // Don't store tokens which correspond to a trial that is not in the allow
+    // list.
+    if (!base::Contains(kSpeculationRulesHeaderTrials,
+                        parsed_token.feature_name())) {
+      continue;
+    }
+
+    origin_trial_tokens_.push_back(std::move(token));
+  }
+}
+
+void SpeculationRulesHeader::MaybeEnableFeatureFromOriginTrial(
+    ExecutionContext& execution_context) {
+  Vector<scoped_refptr<SecurityOrigin>> external_origins;
+  for (const KURL& url : urls_) {
+    auto origin = SecurityOrigin::Create(url);
+    if (origin->IsOpaque() ||
+        execution_context.GetSecurityOrigin()->IsSameOriginWith(origin.get())) {
+      continue;
+    }
+    external_origins.push_back(std::move(origin));
+  }
+  if (external_origins.empty() || origin_trial_tokens_.empty())
+    return;
+  for (const String& token : origin_trial_tokens_) {
+    execution_context.GetOriginTrialContext()->AddTokenFromExternalScript(
+        token, external_origins);
+  }
+}
+
+void SpeculationRulesHeader::ReportErrors(LocalDOMWindow& window) {
+  for (const auto& [outcome, error] : errors_) {
+    CountSpeculationRulesLoadOutcome(outcome);
+
+    if (error) {
+      window.AddConsoleMessage(mojom::blink::ConsoleMessageSource::kOther,
+                               mojom::blink::ConsoleMessageLevel::kWarning,
+                               error);
+    }
+  }
+}
+
+void SpeculationRulesHeader::StartFetches(Document& document) {
+  for (const KURL& speculation_rule_url : urls_) {
+    ResourceRequest resource_request(speculation_rule_url);
+    resource_request.SetPrefetchMaybeForTopLevelNavigation(false);
+    resource_request.SetFetchPriorityHint(
+        mojom::blink::FetchPriorityHint::kLow);
+
+    // Always use CORS. Adopt new best practices for subresources: CORS requests
+    // with same-origin credentials only.
+    auto* origin = document.GetExecutionContext()->GetSecurityOrigin();
+    resource_request.SetMode(network::mojom::RequestMode::kCors);
+    resource_request.SetCredentialsMode(
+        network::mojom::CredentialsMode::kSameOrigin);
+    resource_request.RemoveUserAndPassFromURL();
+    resource_request.SetRequestorOrigin(origin);
+    resource_request.SetHTTPOrigin(origin);
+
+    ResourceLoaderOptions options(
+        document.GetExecutionContext()->GetCurrentWorld());
+    options.initiator_info.name = fetch_initiator_type_names::kOther;
+
+    FetchParameters speculation_rule_params(std::move(resource_request),
+                                            options);
+
+    SpeculationRulesResource* resource = SpeculationRulesResource::Fetch(
+        speculation_rule_params, document.Fetcher());
+
+    SpeculationRuleLoader* speculation_rule_loader =
+        MakeGarbageCollected<SpeculationRuleLoader>(document);
+    speculation_rule_loader->LoadResource(resource, speculation_rule_url);
+  }
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/core/speculation_rules/speculation_rules_header.h b/third_party/blink/renderer/core/speculation_rules/speculation_rules_header.h
new file mode 100644
index 0000000..eca4ab5
--- /dev/null
+++ b/third_party/blink/renderer/core/speculation_rules/speculation_rules_header.h
@@ -0,0 +1,76 @@
+// 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 THIRD_PARTY_BLINK_RENDERER_CORE_SPECULATION_RULES_SPECULATION_RULES_HEADER_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_SPECULATION_RULES_SPECULATION_RULES_HEADER_H_
+
+#include <utility>
+
+#include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/platform/weborigin/kurl.h"
+#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
+#include "third_party/blink/renderer/platform/wtf/vector.h"
+
+namespace blink {
+
+class Document;
+class ExecutionContext;
+class LocalDOMWindow;
+class ResourceResponse;
+class SecurityContext;
+enum class SpeculationRulesLoadOutcome;
+
+// Responsible for parsing the Speculation-Rules header.
+//
+// This includes additional logic to deal with its interaction with the origin
+// trial system, while this feature is experimental.
+class SpeculationRulesHeader {
+ public:
+  // This does all of the below -- it determines whether fetching from the
+  // Speculation-Rules header is possible, tries to enable it if it's not on but
+  // could be turned on, and then initiates any speculation rules fetches that
+  // are required.
+  CORE_EXPORT static void ProcessHeadersForDocumentResponse(
+      const ResourceResponse&,
+      LocalDOMWindow&);
+
+ private:
+  SpeculationRulesHeader();
+  ~SpeculationRulesHeader();
+
+  // Parse the respective headers. Speculation-Rules must be parsed first, since
+  // it affects which origin trial tokens are considered potentially
+  // significant.
+  void ParseSpeculationRulesHeader(const String& header_value,
+                                   const KURL& base_url);
+  void ParseOriginTrialHeader(const String& header_value, SecurityContext&);
+
+  // Possibly enables features given the found origin trial tokens.
+  void MaybeEnableFeatureFromOriginTrial(ExecutionContext&);
+
+  // If errors were encountered, report the unsuccessful outcome for metrics
+  // purposes and also inform the developer.
+  void ReportErrors(LocalDOMWindow&);
+
+  // Start fetching the rule sets found in the Speculation-Rules header.
+  void StartFetches(Document&);
+
+  // Successfully parsed speculation rules fetches to make.
+  Vector<KURL> urls_;
+
+  // Error information to be reported if the feature is found to be enabled.
+  Vector<std::pair<SpeculationRulesLoadOutcome, String>> errors_;
+
+  // Potentially valid origin trial tokens.
+  // These are the tokens which:
+  // - enable a trial which can be enabled when paired with Speculation-Rules
+  // - do not match the first-party origin
+  // - allow third-party use
+  // - have a third-party origin which matches an origin in `urls_`
+  Vector<String> origin_trial_tokens_;
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_SPECULATION_RULES_SPECULATION_RULES_HEADER_H_
diff --git a/third_party/blink/renderer/core/speculation_rules/speculation_rules_header_test.cc b/third_party/blink/renderer/core/speculation_rules/speculation_rules_header_test.cc
new file mode 100644
index 0000000..887068a3
--- /dev/null
+++ b/third_party/blink/renderer/core/speculation_rules/speculation_rules_header_test.cc
@@ -0,0 +1,180 @@
+// 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 "third_party/blink/renderer/core/speculation_rules/speculation_rules_header.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/renderer/core/frame/local_frame.h"
+#include "third_party/blink/renderer/core/frame/web_feature.h"
+#include "third_party/blink/renderer/core/loader/empty_clients.h"
+#include "third_party/blink/renderer/core/speculation_rules/speculation_rules_metrics.h"
+#include "third_party/blink/renderer/core/testing/dummy_page_holder.h"
+#include "third_party/blink/renderer/platform/network/http_names.h"
+#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
+#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
+#include "third_party/blink/renderer/platform/testing/url_test_helpers.h"
+
+namespace blink {
+namespace {
+
+using ::testing::Contains;
+using ::testing::HasSubstr;
+using ::testing::Not;
+using ::testing::ResultOf;
+
+class ScopedRegisterMockedURLLoads {
+ public:
+  ScopedRegisterMockedURLLoads() {
+    url_test_helpers::RegisterMockedURLLoad(
+        KURL("https://thirdparty-speculationrules.test/"
+             "single_url_prefetch.json"),
+        test::CoreTestDataPath("speculation_rules/single_url_prefetch.json"),
+        "application/speculationrules+json");
+  }
+
+  ~ScopedRegisterMockedURLLoads() {
+    url_test_helpers::UnregisterAllURLsAndClearMemoryCache();
+  }
+};
+
+class ConsoleCapturingChromeClient : public EmptyChromeClient {
+ public:
+  void AddMessageToConsole(LocalFrame*,
+                           mojom::ConsoleMessageSource,
+                           mojom::ConsoleMessageLevel,
+                           const String& message,
+                           unsigned line_number,
+                           const String& source_id,
+                           const String& stack_trace) override {
+    messages_.push_back(message);
+  }
+
+  const Vector<String>& ConsoleMessages() const { return messages_; }
+
+ private:
+  Vector<String> messages_;
+};
+
+TEST(SpeculationRulesHeaderTest, NoMetricsWithoutHeader) {
+  ScopedSpeculationRulesFetchFromHeaderForTest enable_fetch_from_header(true);
+  base::HistogramTester histogram_tester;
+  auto* chrome_client = MakeGarbageCollected<ConsoleCapturingChromeClient>();
+  DummyPageHolder page_holder(/*initial_view_size=*/{}, chrome_client);
+
+  ResourceResponse document_response(KURL("https://speculation-rules.test/"));
+  document_response.SetHttpStatusCode(200);
+  document_response.SetMimeType("text/html");
+  document_response.SetTextEncodingName("UTF-8");
+  SpeculationRulesHeader::ProcessHeadersForDocumentResponse(
+      document_response, *page_holder.GetFrame().DomWindow());
+
+  EXPECT_FALSE(page_holder.GetDocument().IsUseCounted(
+      WebFeature::kSpeculationRulesHeader));
+  histogram_tester.ExpectTotalCount("Blink.SpeculationRules.LoadOutcome", 0);
+  EXPECT_THAT(chrome_client->ConsoleMessages(),
+              Not(Contains(ResultOf([](const auto& m) { return m.Utf8(); },
+                                    HasSubstr("Speculation-Rules")))));
+}
+
+TEST(SpeculationRulesHeaderTest, UnparseableHeader) {
+  ScopedSpeculationRulesFetchFromHeaderForTest enable_fetch_from_header(true);
+  base::HistogramTester histogram_tester;
+  auto* chrome_client = MakeGarbageCollected<ConsoleCapturingChromeClient>();
+  DummyPageHolder page_holder(/*initial_view_size=*/{}, chrome_client);
+
+  ResourceResponse document_response(KURL("https://speculation-rules.test/"));
+  document_response.SetHttpStatusCode(200);
+  document_response.SetMimeType("text/html");
+  document_response.SetTextEncodingName("UTF-8");
+  document_response.AddHttpHeaderField(http_names::kSpeculationRules, "_:");
+  SpeculationRulesHeader::ProcessHeadersForDocumentResponse(
+      document_response, *page_holder.GetFrame().DomWindow());
+
+  EXPECT_TRUE(page_holder.GetDocument().IsUseCounted(
+      WebFeature::kSpeculationRulesHeader));
+  histogram_tester.ExpectUniqueSample(
+      "Blink.SpeculationRules.LoadOutcome",
+      SpeculationRulesLoadOutcome::kUnparseableSpeculationRulesHeader, 1);
+  EXPECT_THAT(chrome_client->ConsoleMessages(),
+              Contains(ResultOf([](const auto& m) { return m.Utf8(); },
+                                HasSubstr("Speculation-Rules"))));
+}
+
+TEST(SpeculationRulesHeaderTest, EmptyHeader) {
+  ScopedSpeculationRulesFetchFromHeaderForTest enable_fetch_from_header(true);
+  base::HistogramTester histogram_tester;
+  DummyPageHolder page_holder;
+
+  ResourceResponse document_response(KURL("https://speculation-rules.test/"));
+  document_response.SetHttpStatusCode(200);
+  document_response.SetMimeType("text/html");
+  document_response.SetTextEncodingName("UTF-8");
+  document_response.AddHttpHeaderField(http_names::kSpeculationRules, "");
+  SpeculationRulesHeader::ProcessHeadersForDocumentResponse(
+      document_response, *page_holder.GetFrame().DomWindow());
+
+  EXPECT_TRUE(page_holder.GetDocument().IsUseCounted(
+      WebFeature::kSpeculationRulesHeader));
+  histogram_tester.ExpectUniqueSample(
+      "Blink.SpeculationRules.LoadOutcome",
+      SpeculationRulesLoadOutcome::kEmptySpeculationRulesHeader, 1);
+}
+
+TEST(SpeculationRulesHeaderTest, InvalidItem) {
+  ScopedSpeculationRulesFetchFromHeaderForTest enable_fetch_from_header(true);
+  base::HistogramTester histogram_tester;
+  auto* chrome_client = MakeGarbageCollected<ConsoleCapturingChromeClient>();
+  DummyPageHolder page_holder(/*initial_view_size=*/{}, chrome_client);
+
+  ResourceResponse document_response(KURL("https://speculation-rules.test/"));
+  document_response.SetHttpStatusCode(200);
+  document_response.SetMimeType("text/html");
+  document_response.SetTextEncodingName("UTF-8");
+  document_response.AddHttpHeaderField(http_names::kSpeculationRules,
+                                       "42, :aGVsbG8=:, ?1, \"://\"");
+  SpeculationRulesHeader::ProcessHeadersForDocumentResponse(
+      document_response, *page_holder.GetFrame().DomWindow());
+
+  EXPECT_TRUE(page_holder.GetDocument().IsUseCounted(
+      WebFeature::kSpeculationRulesHeader));
+  histogram_tester.ExpectUniqueSample(
+      "Blink.SpeculationRules.LoadOutcome",
+      SpeculationRulesLoadOutcome::kInvalidSpeculationRulesHeaderItem, 4);
+  EXPECT_THAT(chrome_client->ConsoleMessages(),
+              Contains(ResultOf([](const auto& m) { return m.Utf8(); },
+                                HasSubstr("Speculation-Rules")))
+                  .Times(4));
+}
+
+TEST(SpeculationRulesHeaderTest, ValidURL) {
+  ScopedSpeculationRulesFetchFromHeaderForTest enable_fetch_from_header(true);
+  base::HistogramTester histogram_tester;
+  auto* chrome_client = MakeGarbageCollected<ConsoleCapturingChromeClient>();
+  DummyPageHolder page_holder(/*initial_view_size=*/{}, chrome_client);
+  ScopedRegisterMockedURLLoads mock_url_loads;
+
+  ResourceResponse document_response(KURL("https://speculation-rules.test/"));
+  document_response.SetHttpStatusCode(200);
+  document_response.SetMimeType("text/html");
+  document_response.SetTextEncodingName("UTF-8");
+  document_response.AddHttpHeaderField(
+      http_names::kSpeculationRules,
+      "\"https://thirdparty-speculationrules.test/single_url_prefetch.json\"");
+  SpeculationRulesHeader::ProcessHeadersForDocumentResponse(
+      document_response, *page_holder.GetFrame().DomWindow());
+  url_test_helpers::ServeAsynchronousRequests();
+
+  EXPECT_TRUE(page_holder.GetDocument().IsUseCounted(
+      WebFeature::kSpeculationRulesHeader));
+  histogram_tester.ExpectUniqueSample("Blink.SpeculationRules.LoadOutcome",
+                                      SpeculationRulesLoadOutcome::kSuccess, 1);
+  histogram_tester.ExpectTotalCount("Blink.SpeculationRules.FetchTime", 1);
+  EXPECT_THAT(chrome_client->ConsoleMessages(),
+              Not(Contains(ResultOf([](const auto& m) { return m.Utf8(); },
+                                    HasSubstr("Speculation-Rules")))));
+}
+
+}  // namespace
+}  // namespace blink
diff --git a/third_party/blink/renderer/core/speculation_rules/speculation_rules_origin_trial_test.cc b/third_party/blink/renderer/core/speculation_rules/speculation_rules_origin_trial_test.cc
index c1a5c2e..ec28b208 100644
--- a/third_party/blink/renderer/core/speculation_rules/speculation_rules_origin_trial_test.cc
+++ b/third_party/blink/renderer/core/speculation_rules/speculation_rules_origin_trial_test.cc
@@ -11,7 +11,10 @@
 #include "build/build_config.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/common/features.h"
+#include "third_party/blink/public/common/features_generated.h"
 #include "third_party/blink/public/common/origin_trials/scoped_test_origin_trial_policy.h"
+#include "third_party/blink/public/platform/web_url_response.h"
+#include "third_party/blink/public/web/web_navigation_params.h"
 #include "third_party/blink/renderer/core/execution_context/security_context.h"
 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
 #include "third_party/blink/renderer/core/frame/local_frame.h"
@@ -20,9 +23,10 @@
 #include "third_party/blink/renderer/core/html/html_meta_element.h"
 #include "third_party/blink/renderer/core/html/html_script_element.h"
 #include "third_party/blink/renderer/core/html_names.h"
-#include "third_party/blink/renderer/core/speculation_rules/document_speculation_rules.h"
 #include "third_party/blink/renderer/core/speculation_rules/stub_speculation_host.h"
 #include "third_party/blink/renderer/core/testing/dummy_page_holder.h"
+#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
+#include "third_party/blink/renderer/platform/testing/url_test_helpers.h"
 #include "third_party/blink/renderer/platform/wtf/functional.h"
 
 namespace blink {
@@ -157,5 +161,255 @@
 
 #endif  // !BUILDFLAG(IS_ANDROID)
 
+class ScopedRegisterMockedURLLoads {
+ public:
+  ScopedRegisterMockedURLLoads() {
+    url_test_helpers::RegisterMockedURLLoad(
+        KURL("https://thirdparty-speculationrules.test/"
+             "single_url_prefetch.json"),
+        test::CoreTestDataPath("speculation_rules/single_url_prefetch.json"),
+        "application/speculationrules+json");
+  }
+
+  ~ScopedRegisterMockedURLLoads() {
+    url_test_helpers::UnregisterAllURLsAndClearMemoryCache();
+  }
+};
+
+void CommitTestNavigation(
+    LocalFrame& frame,
+    const KURL& url,
+    const Vector<std::pair<String, String>>& response_headers) {
+  auto navigation_params = std::make_unique<WebNavigationParams>();
+  navigation_params->url = url;
+  WebNavigationParams::FillStaticResponse(navigation_params.get(), "text/html",
+                                          "UTF-8", "<!DOCTYPE html>");
+  for (const auto& [header, value] : response_headers)
+    navigation_params->response.AddHttpHeaderField(header, value);
+  frame.Loader().CommitNavigation(std::move(navigation_params), nullptr);
+}
+
+// Generated by:
+//  tools/origin_trials/generate_token.py --version 3 --expire-days 3650 \
+//      https://speculationrules.test SpeculationRulesPrefetchFuture
+// Token details:
+//  Version: 3
+//  Origin: https://speculationrules.test:443
+//  Is Subdomain: None
+//  Is Third Party: None
+//  Usage Restriction: None
+//  Feature: SpeculationRulesPrefetchFuture
+//  Expiry: 1984756547 (2032-11-22 17:15:47 UTC)
+//  Signature (Base64):
+//  rnDep07eDfunGZCJ7Czq4/VuMhHmpvhRfRHDHtIfdVhsXetfeGLgRSqpDujMb+R8TlYw6sGWBgeOws+YeNa7Ag==
+[[maybe_unused]] constexpr char kSpeculationRulesPrefetchFutureToken[] =
+    "A65w3qdO3g37pxmQiews6uP1bjIR5qb4UX0Rwx7SH3VYbF3rX3hi4EUqqQ7ozG/kfE"
+    "5WMOrBlgYHjsLPmHjWuwIAAAByeyJvcmlnaW4iOiAiaHR0cHM6Ly9zcGVjdWxhdGlv"
+    "bnJ1bGVzLnRlc3Q6NDQzIiwgImZlYXR1cmUiOiAiU3BlY3VsYXRpb25SdWxlc1ByZW"
+    "ZldGNoRnV0dXJlIiwgImV4cGlyeSI6IDE5ODQ3NTY1NDd9";
+
+// Generated by:
+//  tools/origin_trials/generate_token.py --version 3 --expire-days 3650 \
+//      --is-third-party https://thirdparty-speculationrules.test \
+//      SpeculationRulesPrefetchFuture
+// Token details:
+//  Version: 3
+//  Origin: https://thirdparty-speculationrules.test:443
+//  Is Subdomain: None
+//  Is Third Party: True
+//  Usage Restriction: None
+//  Feature: SpeculationRulesPrefetchFuture
+//  Expiry: 1984756868 (2032-11-22 17:21:08 UTC)
+//  Signature (Base64):
+//  BaklAC9xb6XDHDwozGbcO3mIMmJSu/lFTud/Kn3bvJsGjX7zHFgzCEQolDUTlrzn1V0zI4cxdQvr+Qb24vupBw==
+[[maybe_unused]] constexpr char
+    kThirdPartySpeculationRulesPrefetchFutureToken[] =
+        "AwWpJQAvcW+lwxw8KMxm3Dt5iDJiUrv5RU7nfyp927ybBo1+8xxYMwhEKJQ1E5a859"
+        "VdMyOHMXUL6/kG9uL7qQcAAACTeyJvcmlnaW4iOiAiaHR0cHM6Ly90aGlyZHBhcnR5"
+        "LXNwZWN1bGF0aW9ucnVsZXMudGVzdDo0NDMiLCAiZmVhdHVyZSI6ICJTcGVjdWxhdG"
+        "lvblJ1bGVzUHJlZmV0Y2hGdXR1cmUiLCAiZXhwaXJ5IjogMTk4NDc1Njg2OCwgImlz"
+        "VGhpcmRQYXJ0eSI6IHRydWV9";
+
+// Generated by:
+//  tools/origin_trials/generate_token.py --version 3 --expire-days 3650 \
+//      --is-third-party https://thirdparty-speculationrules.test \
+//      FrobulateThirdParty
+// Token details:
+//  Version: 3
+//  Origin: https://thirdparty-speculationrules.test:443
+//  Is Subdomain: None
+//  Is Third Party: True
+//  Usage Restriction: None
+//  Feature: FrobulateThirdParty
+//  Expiry: 1984783548 (2032-11-23 00:45:48 UTC)
+//  Signature (Base64):
+//  aL+ckkDgVSSRsRVtaPTGmEzxl8a4CmXJbUJRDILfDFgWlZfUIL5nQhNPkWflgRn2EWHMqPp+xKoh3ZhP2IbhDA==
+[[maybe_unused]] constexpr char kFrobulateThirdPartyToken[] =
+    "A2i/nJJA4FUkkbEVbWj0xphM8ZfGuAplyW1CUQyC3wxYFpWX1CC+Z0ITT5Fn5YEZ9hF"
+    "hzKj6fsSqId2YT9iG4QwAAACIeyJvcmlnaW4iOiAiaHR0cHM6Ly90aGlyZHBhcnR5LX"
+    "NwZWN1bGF0aW9ucnVsZXMudGVzdDo0NDMiLCAiZmVhdHVyZSI6ICJGcm9idWxhdGVUa"
+    "GlyZFBhcnR5IiwgImV4cGlyeSI6IDE5ODQ3ODM1NDgsICJpc1RoaXJkUGFydHkiOiB0"
+    "cnVlfQ==";
+
+TEST(SpeculationRulesPrefetchFutureOriginTrialTest,
+     CanEnableFromThirdPartyToken) {
+  base::test::ScopedFeatureList scoped_features;
+  scoped_features.InitWithFeatures(
+      {// Allow a third-party origin trial to be enabled if it's linked to the
+       // Speculation-Rules header.
+       features::kSpeculationRulesHeaderEnableThirdPartyOriginTrial,
+
+       // Allow the SpeculationRulesPrefetchFuture trial itself to be enabled.
+       features::kSpeculationRulesPrefetchFuture},
+      {});
+  ScopedTestOriginTrialPolicy using_test_keys;
+  ScopedRegisterMockedURLLoads mock_url_loads;
+  DummyPageHolder page_holder;
+  LocalFrame& frame = page_holder.GetFrame();
+
+  CommitTestNavigation(
+      frame, KURL("https://speculationrules.test/"),
+      {{"Origin-Trial", kThirdPartySpeculationRulesPrefetchFutureToken},
+       {"Speculation-Rules",
+        "\"//thirdparty-speculationrules.test/single_url_prefetch.json\""}});
+
+  // This should have enabled the origin trial and all its dependent features.
+  EXPECT_TRUE(RuntimeEnabledFeatures::SpeculationRulesPrefetchFutureEnabled(
+      frame.DomWindow()));
+  EXPECT_TRUE(RuntimeEnabledFeatures::DeliveryTypeEnabled(frame.DomWindow()));
+  EXPECT_TRUE(RuntimeEnabledFeatures::SpeculationRulesDocumentRulesEnabled(
+      frame.DomWindow()));
+  EXPECT_TRUE(RuntimeEnabledFeatures::SpeculationRulesFetchFromHeaderEnabled(
+      frame.DomWindow()));
+  EXPECT_TRUE(RuntimeEnabledFeatures::SpeculationRulesPrefetchProxyEnabled(
+      frame.DomWindow()));
+}
+
+TEST(SpeculationRulesPrefetchFutureOriginTrialTest,
+     CannotEnableTrialNotInAllowList) {
+  base::test::ScopedFeatureList scoped_features;
+  scoped_features.InitWithFeatures(
+      {// Allow a third-party origin trial to be enabled if it's linked to the
+       // Speculation-Rules header.
+       features::kSpeculationRulesHeaderEnableThirdPartyOriginTrial},
+      {});
+  ScopedTestOriginTrialPolicy using_test_keys;
+  ScopedRegisterMockedURLLoads mock_url_loads;
+  DummyPageHolder page_holder;
+  LocalFrame& frame = page_holder.GetFrame();
+
+  CommitTestNavigation(
+      frame, KURL("https://speculationrules.test/"),
+      {{"Origin-Trial", kFrobulateThirdPartyToken},
+       {"Speculation-Rules",
+        "\"//thirdparty-speculationrules.test/single_url_prefetch.json\""}});
+
+  // This should not have enabled the feature.
+  EXPECT_FALSE(RuntimeEnabledFeatures::OriginTrialsSampleAPIThirdPartyEnabled(
+      frame.DomWindow()));
+}
+
+TEST(SpeculationRulesPrefetchFutureOriginTrialTest,
+     CannotEnableOriginTrialWhenFeatureIsDisabled) {
+  base::test::ScopedFeatureList scoped_features;
+  scoped_features.InitWithFeatures(
+      {
+          // Allow the SpeculationRulesPrefetchFuture trial itself to be
+          // enabled.
+          features::kSpeculationRulesPrefetchFuture,
+      },
+      {
+          // With this disabled, we shouldn't enable the feature. This makes
+          // sure we can revoke this capability if it turns out to be an issue.
+          features::kSpeculationRulesHeaderEnableThirdPartyOriginTrial,
+      });
+  ScopedTestOriginTrialPolicy using_test_keys;
+  DummyPageHolder page_holder;
+  LocalFrame& frame = page_holder.GetFrame();
+
+  CommitTestNavigation(
+      frame, KURL("https://speculationrules.test/"),
+      {{"Origin-Trial", kThirdPartySpeculationRulesPrefetchFutureToken},
+       {"Speculation-Rules",
+        "\"//thirdparty-speculationrules.test/single_url_prefetch.json\""}});
+
+  // This should not have enabled SpeculationRulesPrefetchFuture.
+  EXPECT_FALSE(RuntimeEnabledFeatures::SpeculationRulesPrefetchFutureEnabled(
+      frame.DomWindow()));
+}
+
+TEST(SpeculationRulesPrefetchFutureOriginTrialTest,
+     FirstPartyTrialTokenStillWorks) {
+  base::test::ScopedFeatureList scoped_features;
+  scoped_features.InitWithFeatures(
+      {
+          // Allow the SpeculationRulesPrefetchFuture trial itself to be
+          // enabled.
+          features::kSpeculationRulesPrefetchFuture,
+          // Enable the third-party trial code path, to check that it doesn't
+          // break the normal code path.
+          features::kSpeculationRulesHeaderEnableThirdPartyOriginTrial,
+      },
+      {});
+  ScopedTestOriginTrialPolicy using_test_keys;
+  ScopedRegisterMockedURLLoads mock_url_loads;
+  DummyPageHolder page_holder;
+  LocalFrame& frame = page_holder.GetFrame();
+
+  CommitTestNavigation(
+      frame, KURL("https://speculationrules.test/"),
+      {{"Origin-Trial", kSpeculationRulesPrefetchFutureToken},
+       {"Speculation-Rules",
+        "\"//thirdparty-speculationrules.test/single_url_prefetch.json\""}});
+
+  // This should have enabled the origin trial and all its dependent features.
+  EXPECT_TRUE(RuntimeEnabledFeatures::SpeculationRulesPrefetchFutureEnabled(
+      frame.DomWindow()));
+  EXPECT_TRUE(RuntimeEnabledFeatures::DeliveryTypeEnabled(frame.DomWindow()));
+  EXPECT_TRUE(RuntimeEnabledFeatures::SpeculationRulesDocumentRulesEnabled(
+      frame.DomWindow()));
+  EXPECT_TRUE(RuntimeEnabledFeatures::SpeculationRulesFetchFromHeaderEnabled(
+      frame.DomWindow()));
+  EXPECT_TRUE(RuntimeEnabledFeatures::SpeculationRulesPrefetchProxyEnabled(
+      frame.DomWindow()));
+}
+
+TEST(SpeculationRulesPrefetchFutureOriginTrialTest,
+     FirstPartyTrialTokenDoesNotRequireSpecialSupport) {
+  base::test::ScopedFeatureList scoped_features;
+  scoped_features.InitWithFeatures(
+      {
+          // Allow the SpeculationRulesPrefetchFuture trial itself to be
+          // enabled.
+          features::kSpeculationRulesPrefetchFuture,
+      },
+      {
+          // Even though this is off, a first-party token should still work via
+          // the general-purpose code path.
+          features::kSpeculationRulesHeaderEnableThirdPartyOriginTrial,
+      });
+  ScopedTestOriginTrialPolicy using_test_keys;
+  ScopedRegisterMockedURLLoads mock_url_loads;
+  DummyPageHolder page_holder;
+  LocalFrame& frame = page_holder.GetFrame();
+
+  CommitTestNavigation(
+      frame, KURL("https://speculationrules.test/"),
+      {{"Origin-Trial", kSpeculationRulesPrefetchFutureToken},
+       {"Speculation-Rules",
+        "\"//thirdparty-speculationrules.test/single_url_prefetch.json\""}});
+
+  // This should have enabled the origin trial and all its dependent features.
+  EXPECT_TRUE(RuntimeEnabledFeatures::SpeculationRulesPrefetchFutureEnabled(
+      frame.DomWindow()));
+  EXPECT_TRUE(RuntimeEnabledFeatures::DeliveryTypeEnabled(frame.DomWindow()));
+  EXPECT_TRUE(RuntimeEnabledFeatures::SpeculationRulesDocumentRulesEnabled(
+      frame.DomWindow()));
+  EXPECT_TRUE(RuntimeEnabledFeatures::SpeculationRulesFetchFromHeaderEnabled(
+      frame.DomWindow()));
+  EXPECT_TRUE(RuntimeEnabledFeatures::SpeculationRulesPrefetchProxyEnabled(
+      frame.DomWindow()));
+}
+
 }  // namespace
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/svg/svg_animation_element.cc b/third_party/blink/renderer/core/svg/svg_animation_element.cc
index 29b9fdd..74dddce5 100644
--- a/third_party/blink/renderer/core/svg/svg_animation_element.cc
+++ b/third_party/blink/renderer/core/svg/svg_animation_element.cc
@@ -436,7 +436,8 @@
 }
 
 float SVGAnimationElement::CalculatePercentFromKeyPoints(float percent) const {
-  DCHECK_NE(GetCalcMode(), kCalcModePaced);
+  DCHECK(GetCalcMode() != kCalcModePaced ||
+         GetAnimationMode() == kPathAnimation);
   DCHECK_GT(KeyTimes().size(), 1u);
   DCHECK(!key_points_.empty());
   DCHECK_EQ(key_points_.size(), KeyTimes().size());
@@ -640,8 +641,6 @@
   if (animation_mode == kValuesAnimation)
     return CalculateValuesAnimation();
   if (animation_mode == kPathAnimation) {
-    if (calc_mode == kCalcModePaced)
-      return true;
     // If 'keyPoints' is specified it should have the same amount of points as
     // 'keyTimes', and at least two points.
     if (FastHasAttribute(svg_names::kKeyPointsAttr) &&
@@ -702,7 +701,8 @@
       last_values_animation_from_ = from;
       last_values_animation_to_ = to;
     }
-  } else if (calc_mode != kCalcModePaced && !key_points_.empty()) {
+  } else if (!key_points_.empty() && (animation_mode == kPathAnimation ||
+                                      calc_mode != kCalcModePaced)) {
     effective_percent = CalculatePercentFromKeyPoints(percent);
   } else if (calc_mode == kCalcModeSpline && key_points_.empty() &&
              KeyTimes().size() > 1) {
diff --git a/third_party/blink/renderer/core/svg/svg_element.cc b/third_party/blink/renderer/core/svg/svg_element.cc
index 400388e..60e6e3b 100644
--- a/third_party/blink/renderer/core/svg/svg_element.cc
+++ b/third_party/blink/renderer/core/svg/svg_element.cc
@@ -1029,7 +1029,7 @@
 }
 
 void SVGElement::SynchronizeSVGAttribute(const QualifiedName& name) const {
-  DCHECK(GetElementData());
+  DCHECK(HasElementData());
   DCHECK(GetElementData()->svg_attributes_are_dirty());
   if (name == AnyQName()) {
     for (SVGAnimatedPropertyBase* property :
diff --git a/third_party/blink/renderer/core/svg/svg_svg_element.cc b/third_party/blink/renderer/core/svg/svg_svg_element.cc
index 93bfb80..260cea0 100644
--- a/third_party/blink/renderer/core/svg/svg_svg_element.cc
+++ b/third_party/blink/renderer/core/svg/svg_svg_element.cc
@@ -502,7 +502,7 @@
   // but many things in LocalFrameView and SVGImage depend on the LayoutSVGRoot
   // when they should instead depend on the LayoutView.
   // https://bugs.webkit.org/show_bug.cgi?id=103493
-  if (GetDocument().documentElement() == this)
+  if (IsDocumentElement())
     return true;
 
   // <svg> elements don't need an SVG parent to render, so we bypass
@@ -589,8 +589,10 @@
 }
 
 bool SVGSVGElement::ShouldSynthesizeViewBox() const {
-  return GetLayoutObject() && GetLayoutObject()->IsSVGRoot() &&
-         To<LayoutSVGRoot>(GetLayoutObject())->IsEmbeddedThroughSVGImage();
+  if (!IsDocumentElement())
+    return false;
+  const auto* svg_root = DynamicTo<LayoutSVGRoot>(GetLayoutObject());
+  return svg_root && svg_root->IsEmbeddedThroughSVGImage();
 }
 
 gfx::RectF SVGSVGElement::CurrentViewBoxRect() const {
diff --git a/third_party/blink/renderer/core/testing/data/speculation_rules/single_url_prefetch.json b/third_party/blink/renderer/core/testing/data/speculation_rules/single_url_prefetch.json
new file mode 100644
index 0000000..cf5f9e8
--- /dev/null
+++ b/third_party/blink/renderer/core/testing/data/speculation_rules/single_url_prefetch.json
@@ -0,0 +1 @@
+{"prefetch": [{"source": "list", "urls": ["https://speculationrules.test/next.html"]}]}
diff --git a/third_party/blink/renderer/core/timing/performance.cc b/third_party/blink/renderer/core/timing/performance.cc
index 4470ecb..f7744a47 100644
--- a/third_party/blink/renderer/core/timing/performance.cc
+++ b/third_party/blink/renderer/core/timing/performance.cc
@@ -63,6 +63,7 @@
 #include "third_party/blink/renderer/core/probe/core_probes.h"
 #include "third_party/blink/renderer/core/timing/back_forward_cache_restoration.h"
 #include "third_party/blink/renderer/core/timing/background_tracing_helper.h"
+#include "third_party/blink/renderer/core/timing/dom_window_performance.h"
 #include "third_party/blink/renderer/core/timing/largest_contentful_paint.h"
 #include "third_party/blink/renderer/core/timing/layout_shift.h"
 #include "third_party/blink/renderer/core/timing/measure_memory/measure_memory_controller.h"
@@ -161,29 +162,21 @@
   entries[rightIndex] = tmp;
 }
 
-void InsertEntryIntoSortedList(std::list<Member<PerformanceEntry>>& all_entries,
-                               const Member<PerformanceEntry>& entry) {
-  for (auto it = all_entries.begin(); it != all_entries.end(); ++it) {
-    if (entry->startTime() <= it->Get()->startTime()) {
-      all_entries.insert(it, entry);
-      return;
-    }
-  }
-
-  all_entries.push_back(entry);
-}
-
 }  // namespace
 
-// TODO (jaspreetsandhu): If benchmarks suggest that the below algorithm is
-// inefficient we will look towards optimization.
-void MergePerformanceEntryVectorIntoList(
-    std::list<Member<PerformanceEntry>>& all_entries,
-    const PerformanceEntryVector& new_entries) {
-  std::list<Member<PerformanceEntry>> new_entries_list(new_entries.begin(),
-                                                       new_entries.end());
-  all_entries.merge(new_entries_list,
-                    PerformanceEntry::StartTimeCompareLessThan);
+PerformanceEntryVector MergePerformanceEntryVectors(
+    const PerformanceEntryVector& first_entry_vector,
+    const PerformanceEntryVector& second_entry_vector) {
+  PerformanceEntryVector merged_entries;
+  merged_entries.reserve(first_entry_vector.size() +
+                         second_entry_vector.size());
+
+  std::merge(first_entry_vector.begin(), first_entry_vector.end(),
+             second_entry_vector.begin(), second_entry_vector.end(),
+             std::back_inserter(merged_entries),
+             PerformanceEntry::StartTimeCompareLessThan);
+
+  return merged_entries;
 }
 
 using PerformanceObserverVector = HeapVector<Member<PerformanceObserver>>;
@@ -277,32 +270,45 @@
       cross_origin_isolated_capability_);
 }
 
-PerformanceEntryVector Performance::getEntries() {
-  std::list<Member<PerformanceEntry>> entries_list;
+PerformanceEntryVector Performance::getEntries(ScriptState* script_state,
+                                               bool include_frames) {
+  if (include_frames)
+    return GetEntriesWithChildFrames(script_state);
+  else
+    return GetEntriesForCurrentFrame();
+}
 
-  MergePerformanceEntryVectorIntoList(entries_list, resource_timing_buffer_);
-  if (first_input_timing_)
-    InsertEntryIntoSortedList(entries_list, *(first_input_timing_.Get()));
-  if (!navigation_timing_)
+PerformanceEntryVector Performance::GetEntriesForCurrentFrame() {
+  PerformanceEntryVector entries;
+
+  entries = MergePerformanceEntryVectors(entries, resource_timing_buffer_);
+  if (first_input_timing_) {
+    InsertEntryIntoSortedBuffer(entries, *first_input_timing_,
+                                kDoNotRecordSwaps);
+  }
+  if (!navigation_timing_) {
     navigation_timing_ = CreateNavigationTimingInstance();
+  }
   // This extra checking is needed when WorkerPerformance
   // calls this method.
-  if (navigation_timing_)
-    InsertEntryIntoSortedList(entries_list, *navigation_timing_);
+  if (navigation_timing_) {
+    InsertEntryIntoSortedBuffer(entries, *navigation_timing_,
+                                kDoNotRecordSwaps);
+  }
 
   if (user_timing_) {
-    MergePerformanceEntryVectorIntoList(entries_list, user_timing_->GetMarks());
-    MergePerformanceEntryVectorIntoList(entries_list,
-                                        user_timing_->GetMeasures());
+    entries = MergePerformanceEntryVectors(entries, user_timing_->GetMarks());
+    entries =
+        MergePerformanceEntryVectors(entries, user_timing_->GetMeasures());
   }
 
   if (paint_entries_timing_.size()) {
-    MergePerformanceEntryVectorIntoList(entries_list, paint_entries_timing_);
+    entries = MergePerformanceEntryVectors(entries, paint_entries_timing_);
   }
 
   if (RuntimeEnabledFeatures::NavigationIdEnabled(GetExecutionContext())) {
-    MergePerformanceEntryVectorIntoList(entries_list,
-                                        back_forward_cache_restoration_buffer_);
+    entries = MergePerformanceEntryVectors(
+        entries, back_forward_cache_restoration_buffer_);
   }
 
   if (RuntimeEnabledFeatures::SoftNavigationHeuristicsEnabled(
@@ -310,13 +316,7 @@
       soft_navigation_buffer_.size()) {
     UseCounter::Count(GetExecutionContext(),
                       WebFeature::kSoftNavigationHeuristics);
-    MergePerformanceEntryVectorIntoList(entries_list, soft_navigation_buffer_);
-  }
-
-  // Convert entries_list into a PerformanceEntryVector.
-  PerformanceEntryVector entries;
-  for (auto& entry : entries_list) {
-    entries.push_back(entry);
+    entries = MergePerformanceEntryVectors(entries, soft_navigation_buffer_);
   }
 
   return entries;
@@ -330,6 +330,16 @@
 }
 
 PerformanceEntryVector Performance::getEntriesByType(
+    ScriptState* script_state,
+    const AtomicString& entry_type,
+    bool include_frames) {
+  if (include_frames)
+    return GetEntriesWithChildFrames(script_state, entry_type);
+  else
+    return GetEntriesByTypeForCurrentFrame(entry_type);
+}
+
+PerformanceEntryVector Performance::GetEntriesByTypeForCurrentFrame(
     const AtomicString& entry_type) {
   PerformanceEntry::EntryType type =
       PerformanceEntry::ToEntryTypeEnum(entry_type);
@@ -432,18 +442,25 @@
 }
 
 PerformanceEntryVector Performance::getEntriesByName(
+    ScriptState* script_state,
     const AtomicString& name,
-    const AtomicString& entry_type) {
+    const AtomicString& entry_type,
+    bool include_frames) {
   PerformanceEntryVector entries;
   PerformanceEntryVector all_entries;
 
   // Get sorted entry list based on provided input.
-  if (entry_type == g_null_atom) {
-    all_entries = getEntries();
+  if (include_frames) {
+    all_entries = GetEntriesWithChildFrames(script_state, entry_type);
   } else {
-    all_entries = getEntriesByType(entry_type);
+    if (entry_type.IsNull()) {
+      all_entries = GetEntriesForCurrentFrame();
+    } else {
+      all_entries = GetEntriesByTypeForCurrentFrame(entry_type);
+    }
   }
 
+  // Filter all entries by name.
   for (const auto& entry : all_entries) {
     if (entry->name() == name)
       entries.push_back(entry);
@@ -452,6 +469,52 @@
   return entries;
 }
 
+PerformanceEntryVector Performance::GetEntriesWithChildFrames(
+    ScriptState* script_state,
+    const AtomicString& entry_type) {
+  PerformanceEntryVector entries;
+
+  LocalDOMWindow* window = LocalDOMWindow::From(script_state);
+  LocalFrame* parent_frame = window->GetFrame();
+
+  HeapDeque<Member<Frame>> queue;
+  queue.push_back(parent_frame);
+
+  while (!queue.empty()) {
+    Frame* current_frame = queue.TakeFirst();
+
+    if (LocalFrame* local_frame = DynamicTo<LocalFrame>(current_frame);
+        local_frame && !local_frame->IsCrossOriginToNearestMainFrame()) {
+      // Get the Performance object from the current frame.
+      LocalDOMWindow* current_window = local_frame->GetDocument()->domWindow();
+      WindowPerformance* window_performance =
+          DOMWindowPerformance::performance(*current_window);
+
+      // Get the performance entries based on entry_type input.
+      PerformanceEntryVector current_entries;
+      if (entry_type.IsNull()) {
+        current_entries = window_performance->GetEntriesForCurrentFrame();
+      } else {
+        current_entries =
+            window_performance->GetEntriesByTypeForCurrentFrame(entry_type);
+      }
+
+      entries.AppendVector(current_entries);
+    }
+
+    // Add both Local and Remote Frames to the queue.
+    for (Frame* child = current_frame->FirstChild(); child;
+         child = child->NextSibling()) {
+      queue.push_back(child);
+    }
+  }
+
+  std::sort(entries.begin(), entries.end(),
+            PerformanceEntry::StartTimeCompareLessThan);
+
+  return entries;
+}
+
 void Performance::clearResourceTimings() {
   resource_timing_buffer_.clear();
 }
@@ -594,7 +657,7 @@
   // https://w3c.github.io/resource-timing/#dfn-add-a-performanceresourcetiming-entry
   if (CanAddResourceTimingEntry() &&
       !resource_timing_buffer_full_event_pending_) {
-    InsertEntryIntoSortedBuffer(resource_timing_buffer_, *entry);
+    InsertEntryIntoSortedBuffer(resource_timing_buffer_, *entry, kRecordSwaps);
     return;
   }
   // The Resource Timing entries have a special processing model in which there
@@ -671,7 +734,7 @@
 
 void Performance::AddElementTimingBuffer(PerformanceElementTiming& entry) {
   if (!IsElementTimingBufferFull()) {
-    InsertEntryIntoSortedBuffer(element_timing_buffer_, entry);
+    InsertEntryIntoSortedBuffer(element_timing_buffer_, entry, kRecordSwaps);
   } else {
     ++(dropped_entries_count_map_.find(PerformanceEntry::kElement)->value);
   }
@@ -679,7 +742,7 @@
 
 void Performance::AddEventTimingBuffer(PerformanceEventTiming& entry) {
   if (!IsEventTimingBufferFull()) {
-    InsertEntryIntoSortedBuffer(event_timing_buffer_, entry);
+    InsertEntryIntoSortedBuffer(event_timing_buffer_, entry, kRecordSwaps);
   } else {
     ++(dropped_entries_count_map_.find(PerformanceEntry::kEvent)->value);
   }
@@ -688,7 +751,7 @@
 void Performance::AddLayoutShiftBuffer(LayoutShift& entry) {
   probe::PerformanceEntryAdded(GetExecutionContext(), &entry);
   if (layout_shift_buffer_.size() < kDefaultLayoutShiftBufferSize) {
-    InsertEntryIntoSortedBuffer(layout_shift_buffer_, entry);
+    InsertEntryIntoSortedBuffer(layout_shift_buffer_, entry, kRecordSwaps);
   } else {
     ++(dropped_entries_count_map_.find(PerformanceEntry::kLayoutShift)->value);
   }
@@ -698,7 +761,8 @@
   probe::PerformanceEntryAdded(GetExecutionContext(), entry);
   if (largest_contentful_paint_buffer_.size() <
       kDefaultLargestContenfulPaintSize) {
-    InsertEntryIntoSortedBuffer(largest_contentful_paint_buffer_, *entry);
+    InsertEntryIntoSortedBuffer(largest_contentful_paint_buffer_, *entry,
+                                kRecordSwaps);
   } else {
     ++(dropped_entries_count_map_
            .find(PerformanceEntry::kLargestContentfulPaint)
@@ -710,7 +774,7 @@
     SoftNavigationEntry* entry) {
   probe::PerformanceEntryAdded(GetExecutionContext(), entry);
   if (soft_navigation_buffer_.size() < kDefaultSoftNavigationBufferSize) {
-    InsertEntryIntoSortedBuffer(soft_navigation_buffer_, *entry);
+    InsertEntryIntoSortedBuffer(soft_navigation_buffer_, *entry, kRecordSwaps);
   } else {
     ++(dropped_entries_count_map_.find(PerformanceEntry::kSoftNavigation)
            ->value);
@@ -734,7 +798,7 @@
   DCHECK((type == PerformancePaintTiming::PaintType::kFirstPaint) ||
          (type == PerformancePaintTiming::PaintType::kFirstContentfulPaint));
   if (paint_entries_timing_.size() < kDefaultPaintEntriesBufferSize) {
-    InsertEntryIntoSortedBuffer(paint_entries_timing_, *entry);
+    InsertEntryIntoSortedBuffer(paint_entries_timing_, *entry, kRecordSwaps);
   } else {
     ++(dropped_entries_count_map_.find(PerformanceEntry::kPaint)->value);
   }
@@ -766,7 +830,7 @@
       name, container_type, container_src, container_id, container_name,
       PerformanceEntry::GetNavigationId(execution_context));
   if (longtask_buffer_.size() < kDefaultLongTaskBufferSize) {
-    InsertEntryIntoSortedBuffer(longtask_buffer_, *entry);
+    InsertEntryIntoSortedBuffer(longtask_buffer_, *entry, kRecordSwaps);
   } else {
     ++(dropped_entries_count_map_.find(PerformanceEntry::kLongTask)->value);
     UseCounter::Count(execution_context, WebFeature::kLongTaskBufferFull);
@@ -790,7 +854,8 @@
       PerformanceEntry::GetNavigationId(GetExecutionContext()));
   if (back_forward_cache_restoration_buffer_.size() <
       back_forward_cache_restoration_buffer_size_limit_) {
-    InsertEntryIntoSortedBuffer(back_forward_cache_restoration_buffer_, *entry);
+    InsertEntryIntoSortedBuffer(back_forward_cache_restoration_buffer_, *entry,
+                                kRecordSwaps);
   } else {
     ++(dropped_entries_count_map_
            .find(PerformanceEntry::kBackForwardCacheRestoration)
@@ -1147,11 +1212,12 @@
   // |memory| is not part of the spec, omitted.
 }
 
-// Insert entry in PerformanceEntryVector while maintaining sorted order.
-// We assume that the order of insertion roughly corresponds to the order of
-// start time, resulting in relatively few swaps.
+// Insert entry in PerformanceEntryVector while maintaining sorted order (via
+// Bubble Sort). We assume that the order of insertion roughly corresponds to
+// the order of the StartTime, hence the sort beginning from the tail-end.
 void Performance::InsertEntryIntoSortedBuffer(PerformanceEntryVector& entries,
-                                              PerformanceEntry& entry) {
+                                              PerformanceEntry& entry,
+                                              Metrics record) {
   entries.push_back(&entry);
 
   int number_of_swaps = 0;
@@ -1161,8 +1227,10 @@
     int left = entries.size() - 2;
     while (left >= 0 &&
            entries[left]->startTime() > entries[left + 1]->startTime()) {
-      UseCounter::Count(GetExecutionContext(),
-                        WebFeature::kPerformanceEntryBufferSwaps);
+      if (record == kRecordSwaps) {
+        UseCounter::Count(GetExecutionContext(),
+                          WebFeature::kPerformanceEntryBufferSwaps);
+      }
       number_of_swaps++;
       SwapEntries(entries, left, left + 1);
       left--;
diff --git a/third_party/blink/renderer/core/timing/performance.h b/third_party/blink/renderer/core/timing/performance.h
index 7f7f2f2..d1c263b 100644
--- a/third_party/blink/renderer/core/timing/performance.h
+++ b/third_party/blink/renderer/core/timing/performance.h
@@ -90,10 +90,10 @@
 using PerformanceEntryVector = HeapVector<Member<PerformanceEntry>>;
 using PerformanceEntryDeque = HeapDeque<Member<PerformanceEntry>>;
 
-// Merge a PerformanceEntryVector into a list, sorted by StartTime.
-void CORE_EXPORT MergePerformanceEntryVectorIntoList(
-    std::list<Member<PerformanceEntry>>& all_entries,
-    const PerformanceEntryVector& new_entries);
+// Merge two sorted PerformanceEntryVectors in linear time.
+CORE_EXPORT PerformanceEntryVector
+MergePerformanceEntryVectors(const PerformanceEntryVector& first_entry_vector,
+                             const PerformanceEntryVector& second_entry_vector);
 
 class CORE_EXPORT Performance : public EventTargetWithInlineData {
   DEFINE_WRAPPERTYPEINFO();
@@ -153,17 +153,30 @@
   // Internal getter method for the time origin value.
   base::TimeTicks GetTimeOriginInternal() const { return time_origin_; }
 
-  PerformanceEntryVector getEntries();
+  // Get performance entries of the current frame, and optionally, nested
+  // same-origin iframes.
+  PerformanceEntryVector getEntries(ScriptState* script_state,
+                                    bool include_frames = false);
   // Get BufferedEntriesByType will return all entries in the buffer regardless
   // of whether they are exposed in the Performance Timeline. getEntriesByType
   // will only return all entries for existing types in
   // PerformanceEntry.IsValidTimelineEntryType.
   PerformanceEntryVector getBufferedEntriesByType(
       const AtomicString& entry_type);
-  PerformanceEntryVector getEntriesByType(const AtomicString& entry_type);
+
+  // Get performance entries of the current frame by type, and optionally,
+  // nested same-origin iframes.
+  PerformanceEntryVector getEntriesByType(ScriptState* script_state,
+                                          const AtomicString& entry_type,
+                                          bool includeFrames = false);
+
+  // Get performance entries of the current frame by name and/or type, and
+  // optionally, nested same-origin iframes.
   PerformanceEntryVector getEntriesByName(
+      ScriptState* script_state,
       const AtomicString& name,
-      const AtomicString& entry_type = g_null_atom);
+      const AtomicString& entry_type = g_null_atom,
+      bool includeFrames = false);
 
   void clearResourceTimings();
   void setResourceTimingBufferSize(unsigned);
@@ -313,8 +326,13 @@
 
   ScriptValue toJSONForBinding(ScriptState*) const;
 
+  enum Metrics { kRecordSwaps = 0, kDoNotRecordSwaps = 1 };
+
+  // Insert a PerformanceEntry into a Vector sorted by StartTime. By Default,
+  // record the number of 'swaps' per function call in a histogram.
   void InsertEntryIntoSortedBuffer(PerformanceEntryVector& vector,
-                                   PerformanceEntry& entry);
+                                   PerformanceEntry& entry,
+                                   Metrics record);
 
   void Trace(Visitor*) const override;
 
@@ -349,6 +367,19 @@
 
   void MeasureMemoryExperimentTimerFired(TimerBase*);
 
+  // Get performance entries of the current frame.
+  PerformanceEntryVector GetEntriesForCurrentFrame();
+
+  // Get performance entries of the current frame by type.
+  PerformanceEntryVector GetEntriesByTypeForCurrentFrame(
+      const AtomicString& entry_type);
+
+  // Get performance entries of nested same-origin iframes, with an optional
+  // type.
+  PerformanceEntryVector GetEntriesWithChildFrames(
+      ScriptState* script_state,
+      const AtomicString& entry_type = g_null_atom);
+
  protected:
   Performance(base::TimeTicks time_origin,
               bool cross_origin_isolated_capability,
diff --git a/third_party/blink/renderer/core/timing/performance.idl b/third_party/blink/renderer/core/timing/performance.idl
index c5d098a..8fe3d46 100644
--- a/third_party/blink/renderer/core/timing/performance.idl
+++ b/third_party/blink/renderer/core/timing/performance.idl
@@ -41,9 +41,9 @@
     // Performance Timeline
     // https://w3c.github.io/performance-timeline/#the-performance-interface
     // TODO(foolip): getEntries() should take an optional FilterOptions argument.
-    [MeasureAs=PerformanceTimeline] PerformanceEntryList getEntries();
-    [MeasureAs=PerformanceTimeline] PerformanceEntryList getEntriesByType(DOMString entryType);
-    [MeasureAs=PerformanceTimeline] PerformanceEntryList getEntriesByName(DOMString name, optional DOMString entryType);
+    [MeasureAs=PerformanceTimeline, CallWith=ScriptState] PerformanceEntryList getEntries(optional boolean includeFrames);
+    [MeasureAs=PerformanceTimeline, CallWith=ScriptState] PerformanceEntryList getEntriesByType(DOMString entryType, optional boolean includeFrames);
+    [MeasureAs=PerformanceTimeline, CallWith=ScriptState] PerformanceEntryList getEntriesByName(DOMString name, optional DOMString entryType, optional boolean includeFrames);
 
     // Resource Timing
     // https://w3c.github.io/resource-timing/#extensions-performance-interface
diff --git a/third_party/blink/renderer/core/timing/performance_resource_timing.cc b/third_party/blink/renderer/core/timing/performance_resource_timing.cc
index 082cb8d15..779aefc 100644
--- a/third_party/blink/renderer/core/timing/performance_resource_timing.cc
+++ b/third_party/blink/renderer/core/timing/performance_resource_timing.cc
@@ -203,7 +203,6 @@
 }
 
 AtomicString PerformanceResourceTiming::deliveryType() const {
-  DCHECK(RuntimeEnabledFeatures::DeliveryTypeEnabled());
   if (!AllowTimingDetails())
     return g_empty_atom;
   return delivery_type_;
@@ -498,8 +497,10 @@
 
 void PerformanceResourceTiming::BuildJSONValue(V8ObjectBuilder& builder) const {
   PerformanceEntry::BuildJSONValue(builder);
+  ExecutionContext* execution_context =
+      ExecutionContext::From(builder.GetScriptState());
   builder.AddString("initiatorType", initiatorType());
-  if (RuntimeEnabledFeatures::DeliveryTypeEnabled()) {
+  if (RuntimeEnabledFeatures::DeliveryTypeEnabled(execution_context)) {
     builder.AddString("deliveryType", deliveryType());
   }
   builder.AddString("nextHopProtocol", nextHopProtocol());
diff --git a/third_party/blink/renderer/core/timing/performance_test.cc b/third_party/blink/renderer/core/timing/performance_test.cc
index ea215fd..b3292ef 100644
--- a/third_party/blink/renderer/core/timing/performance_test.cc
+++ b/third_party/blink/renderer/core/timing/performance_test.cc
@@ -216,10 +216,11 @@
   auto entries = PerformanceEntriesInObserver();
   CheckBackForwardCacheRestoration(entries);
 
-  entries = base_->getEntries();
+  entries = base_->getEntries(scope.GetScriptState());
   CheckBackForwardCacheRestoration(entries);
 
-  entries = base_->getEntriesByType("back-forward-cache-restoration");
+  entries = base_->getEntriesByType(scope.GetScriptState(),
+                                    "back-forward-cache-restoration");
   CheckBackForwardCacheRestoration(entries);
 }
 
@@ -233,7 +234,8 @@
   PerformanceEventTiming* test_entry =
       PerformanceEventTiming::Create("event", 0.0, 0.0, 0.0, false, nullptr, 0);
 
-  base_->InsertEntryIntoSortedBuffer(test_buffer_, *test_entry);
+  base_->InsertEntryIntoSortedBuffer(test_buffer_, *test_entry,
+                                     Performance::kDoNotRecordSwaps);
 
   PerformanceEntryVector sorted_buffer_;
   sorted_buffer_.push_back(*test_entry);
@@ -262,7 +264,8 @@
   // Create copy of the test_buffer_.
   PerformanceEntryVector sorted_buffer_ = test_buffer_;
 
-  base_->InsertEntryIntoSortedBuffer(test_buffer_, *test_entry);
+  base_->InsertEntryIntoSortedBuffer(test_buffer_, *test_entry,
+                                     Performance::kDoNotRecordSwaps);
 
   sorted_buffer_.push_back(*test_entry);
   std::sort(sorted_buffer_.begin(), sorted_buffer_.end(),
@@ -292,7 +295,8 @@
   // Create copy of the test_buffer_.
   PerformanceEntryVector sorted_buffer_ = test_buffer_;
 
-  base_->InsertEntryIntoSortedBuffer(test_buffer_, *test_entry);
+  base_->InsertEntryIntoSortedBuffer(test_buffer_, *test_entry,
+                                     Performance::kDoNotRecordSwaps);
 
   sorted_buffer_.push_back(*test_entry);
   std::sort(sorted_buffer_.begin(), sorted_buffer_.end(),
@@ -301,12 +305,10 @@
   EXPECT_EQ(test_buffer_, sorted_buffer_);
 }
 
-TEST_F(PerformanceTest, MergePerformanceEntryVectorIntoListTest) {
+TEST_F(PerformanceTest, MergePerformanceEntryVectorsTest) {
   PerformanceEntryVector first_vector;
   PerformanceEntryVector second_vector;
 
-  std::list<Member<PerformanceEntry>> all_entries;
-
   PerformanceEntryVector test_vector;
 
   for (int i = 0; i < 6; i += 2) {
@@ -317,8 +319,6 @@
     test_vector.push_back(*entry);
   }
 
-  MergePerformanceEntryVectorIntoList(all_entries, first_vector);
-
   for (int i = 1; i < 6; i += 2) {
     double tmp = 1.0;
     PerformanceEventTiming* entry = PerformanceEventTiming::Create(
@@ -327,17 +327,14 @@
     test_vector.push_back(*entry);
   }
 
-  MergePerformanceEntryVectorIntoList(all_entries, second_vector);
-
-  PerformanceEntryVector entries;
-  for (auto& entry : all_entries) {
-    entries.push_back(entry);
-  }
+  PerformanceEntryVector all_entries;
+  all_entries = MergePerformanceEntryVectors(all_entries, first_vector);
+  all_entries = MergePerformanceEntryVectors(all_entries, second_vector);
 
   std::sort(test_vector.begin(), test_vector.end(),
             PerformanceEntry::StartTimeCompareLessThan);
 
-  EXPECT_EQ(entries, test_vector);
+  EXPECT_EQ(all_entries, test_vector);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/timing/performance_user_timing.cc b/third_party/blink/renderer/core/timing/performance_user_timing.cc
index c4e2ff2..de67bfbb 100644
--- a/third_party/blink/renderer/core/timing/performance_user_timing.cc
+++ b/third_party/blink/renderer/core/timing/performance_user_timing.cc
@@ -258,7 +258,8 @@
     PerformanceEntryMap& performance_entry_map,
     PerformanceEntryVector& performance_entry_buffer,
     PerformanceEntry& entry) {
-  performance_->InsertEntryIntoSortedBuffer(performance_entry_buffer, entry);
+  performance_->InsertEntryIntoSortedBuffer(performance_entry_buffer, entry,
+                                            Performance::kDoNotRecordSwaps);
 
   auto it = performance_entry_map.find(entry.name());
   if (it == performance_entry_map.end()) {
@@ -270,7 +271,8 @@
   }
 
   DCHECK(it->value);
-  performance_->InsertEntryIntoSortedBuffer(*it->value.Get(), entry);
+  performance_->InsertEntryIntoSortedBuffer(*it->value.Get(), entry,
+                                            Performance::kDoNotRecordSwaps);
 }
 
 void UserTiming::ClearPerformanceEntries(
diff --git a/third_party/blink/renderer/core/timing/window_performance_test.cc b/third_party/blink/renderer/core/timing/window_performance_test.cc
index 0a28be5..e8dd5813 100644
--- a/third_party/blink/renderer/core/timing/window_performance_test.cc
+++ b/third_party/blink/renderer/core/timing/window_performance_test.cc
@@ -309,7 +309,7 @@
     performance_->mark(GetScriptState(), AtomicString::Number(i), nullptr,
                        exception_state);
   }
-  PerformanceEntryVector entries = performance_->getEntries();
+  PerformanceEntryVector entries = performance_->getEntries(GetScriptState());
   EXPECT_EQ(17U, entries.size());
   for (int i = 0; i < 8; i++) {
     EXPECT_EQ(AtomicString::Number(i), entries[i]->name());
@@ -436,7 +436,7 @@
     }
     SimulateSwapPromise(GetTimeOrigin() + base::Milliseconds(3));
     PerformanceEntryVector firstInputs =
-        performance_->getEntriesByType("first-input");
+        performance_->getEntriesByType(GetScriptState(), "first-input");
     EXPECT_GE(1u, firstInputs.size());
     EXPECT_EQ(input.should_report, firstInputs.size() == 1u);
     ResetPerformance();
@@ -453,9 +453,12 @@
                          GetTimeOrigin() + base::Milliseconds(2), 4);
     SimulateSwapPromise(GetTimeOrigin() + base::Milliseconds(3));
   }
-  ASSERT_EQ(1u, performance_->getEntriesByType("first-input").size());
+  ASSERT_EQ(
+      1u,
+      performance_->getEntriesByType(GetScriptState(), "first-input").size());
   EXPECT_EQ("mousedown",
-            performance_->getEntriesByType("first-input")[0]->name());
+            performance_->getEntriesByType(GetScriptState(), "first-input")[0]
+                ->name());
 }
 
 // Test that pointerdown followed by pointerup works as a 'firstInput'.
@@ -467,14 +470,20 @@
   RegisterPointerEvent("pointerdown", start_time, processing_start,
                        processing_end, 4);
   SimulateSwapPromise(swap_time);
-  EXPECT_EQ(0u, performance_->getEntriesByType("first-input").size());
+  EXPECT_EQ(
+      0u,
+      performance_->getEntriesByType(GetScriptState(), "first-input").size());
   RegisterPointerEvent("pointerup", start_time, processing_start,
                        processing_end, 4);
   SimulateSwapPromise(swap_time);
-  EXPECT_EQ(1u, performance_->getEntriesByType("first-input").size());
+  EXPECT_EQ(
+      1u,
+      performance_->getEntriesByType(GetScriptState(), "first-input").size());
   // The name of the entry should be "pointerdown".
   EXPECT_EQ(
-      1u, performance_->getEntriesByName("pointerdown", "first-input").size());
+      1u, performance_
+              ->getEntriesByName(GetScriptState(), "pointerdown", "first-input")
+              .size());
 }
 
 // When the pointerdown is optimized out, the mousedown works as a
@@ -487,10 +496,14 @@
   RegisterPointerEvent("mousedown", start_time, processing_start,
                        processing_end, 4);
   SimulateSwapPromise(swap_time);
-  EXPECT_EQ(1u, performance_->getEntriesByType("first-input").size());
+  EXPECT_EQ(
+      1u,
+      performance_->getEntriesByType(GetScriptState(), "first-input").size());
   // The name of the entry should be "pointerdown".
   EXPECT_EQ(1u,
-            performance_->getEntriesByName("mousedown", "first-input").size());
+            performance_
+                ->getEntriesByName(GetScriptState(), "mousedown", "first-input")
+                .size());
 }
 
 // Test that pointerdown followed by mousedown, pointerup works as a
@@ -503,18 +516,26 @@
   RegisterPointerEvent("pointerdown", start_time, processing_start,
                        processing_end, 4);
   SimulateSwapPromise(swap_time);
-  EXPECT_EQ(0u, performance_->getEntriesByType("first-input").size());
+  EXPECT_EQ(
+      0u,
+      performance_->getEntriesByType(GetScriptState(), "first-input").size());
   RegisterPointerEvent("mousedown", start_time, processing_start,
                        processing_end, 4);
   SimulateSwapPromise(swap_time);
-  EXPECT_EQ(0u, performance_->getEntriesByType("first-input").size());
+  EXPECT_EQ(
+      0u,
+      performance_->getEntriesByType(GetScriptState(), "first-input").size());
   RegisterPointerEvent("pointerup", start_time, processing_start,
                        processing_end, 4);
   SimulateSwapPromise(swap_time);
-  EXPECT_EQ(1u, performance_->getEntriesByType("first-input").size());
+  EXPECT_EQ(
+      1u,
+      performance_->getEntriesByType(GetScriptState(), "first-input").size());
   // The name of the entry should be "pointerdown".
   EXPECT_EQ(
-      1u, performance_->getEntriesByName("pointerdown", "first-input").size());
+      1u, performance_
+              ->getEntriesByName(GetScriptState(), "pointerdown", "first-input")
+              .size());
 }
 
 TEST_F(WindowPerformanceTest, OneKeyboardInteraction) {
diff --git a/third_party/blink/renderer/core/workers/cross_thread_global_scope_creation_params_copier.cc b/third_party/blink/renderer/core/workers/cross_thread_global_scope_creation_params_copier.cc
index 7e2f6cd..56b5f1a 100644
--- a/third_party/blink/renderer/core/workers/cross_thread_global_scope_creation_params_copier.cc
+++ b/third_party/blink/renderer/core/workers/cross_thread_global_scope_creation_params_copier.cc
@@ -54,9 +54,10 @@
   return network::mojom::blink::CSPSourceList::New(
       std::move(sources), CrossThreadCopier<Vector<String>>::Copy(in->nonces),
       std::move(hashes), in->allow_self, in->allow_star,
-      in->allow_response_redirects, in->allow_inline, in->allow_eval,
-      in->allow_wasm_eval, in->allow_wasm_unsafe_eval, in->allow_dynamic,
-      in->allow_unsafe_hashes, in->report_sample);
+      in->allow_response_redirects, in->allow_inline,
+      in->allow_inline_speculation_rules, in->allow_eval, in->allow_wasm_eval,
+      in->allow_wasm_unsafe_eval, in->allow_dynamic, in->allow_unsafe_hashes,
+      in->report_sample);
 }
 
 HashMap<network::mojom::blink::CSPDirectiveName,
diff --git a/third_party/blink/renderer/modules/hid/OWNERS b/third_party/blink/renderer/modules/hid/OWNERS
index c971084..6f531f3 100644
--- a/third_party/blink/renderer/modules/hid/OWNERS
+++ b/third_party/blink/renderer/modules/hid/OWNERS
@@ -1,2 +1,3 @@
 mattreynolds@chromium.org
 reillyg@chromium.org
+chengweih@chromium.org
diff --git a/third_party/blink/renderer/modules/media/audio/audio_device_factory.cc b/third_party/blink/renderer/modules/media/audio/audio_device_factory.cc
index 44ddcd6..59931d7 100644
--- a/third_party/blink/renderer/modules/media/audio/audio_device_factory.cc
+++ b/third_party/blink/renderer/modules/media/audio/audio_device_factory.cc
@@ -164,6 +164,7 @@
   constexpr base::TimeDelta kDeleteTimeout = base::Milliseconds(5000);
 
   if (!sink_cache_) {
+    // Do we actually need a separate thread pool just for deleting audio sinks?
     sink_cache_ = std::make_unique<AudioRendererSinkCache>(
         base::ThreadPool::CreateSequencedTaskRunner(
             {base::TaskPriority::BEST_EFFORT,
diff --git a/third_party/blink/renderer/modules/media/audio/audio_renderer_sink_cache.cc b/third_party/blink/renderer/modules/media/audio/audio_renderer_sink_cache.cc
index cb19037..b56ec6d4a 100644
--- a/third_party/blink/renderer/modules/media/audio/audio_renderer_sink_cache.cc
+++ b/third_party/blink/renderer/modules/media/audio/audio_renderer_sink_cache.cc
@@ -89,7 +89,6 @@
   LocalFrameToken source_frame_token;
   std::string device_id;
   scoped_refptr<media::AudioRendererSink> sink;  // Sink instance
-  bool used;                                     // True if in use by a client.
 };
 
 // static
@@ -112,14 +111,15 @@
 }
 
 AudioRendererSinkCache::~AudioRendererSinkCache() {
-  // We just release all the cached sinks here. Stop them first.
-  // We can stop all the sinks, no matter they are used or not, since
-  // everything is being destroyed anyways.
-  for (auto& entry : cache_)
-    entry.sink->Stop();
+  {
+    // Stop all of the sinks before destruction.
+    base::AutoLock auto_lock(cache_lock_);
+    for (auto& entry : cache_)
+      entry.sink->Stop();
+  }
 
-  if (instance_ == this)
-    instance_ = nullptr;
+  DCHECK(instance_ == this);
+  instance_ = nullptr;
 }
 
 media::OutputDeviceInfo AudioRendererSinkCache::GetSinkInfo(
@@ -137,8 +137,8 @@
     scoped_refptr<media::AudioRendererSink> sink =
         create_sink_cb_.Run(source_frame_token, {session_id, device_id});
 
-    CacheOrStopUnusedSink(source_frame_token,
-                          sink->GetOutputDeviceInfo().device_id(), sink);
+    MaybeCacheSink(source_frame_token, sink->GetOutputDeviceInfo().device_id(),
+                   sink);
 
     UMA_HISTOGRAM_ENUMERATION(
         "Media.Audio.Render.SinkCache.GetOutputDeviceInfoCacheUtilization",
@@ -151,8 +151,7 @@
   // Ignore session id.
   {
     base::AutoLock auto_lock(cache_lock_);
-    auto cache_iter = FindCacheEntry_Locked(source_frame_token, device_id,
-                                            false /* unused_only */);
+    auto cache_iter = FindCacheEntry_Locked(source_frame_token, device_id);
     if (cache_iter != cache_.end()) {
       // A matching cached sink is found.
       UMA_HISTOGRAM_ENUMERATION(
@@ -169,7 +168,7 @@
       source_frame_token,
       media::AudioSinkParameters(base::UnguessableToken(), device_id));
 
-  CacheOrStopUnusedSink(source_frame_token, device_id, sink);
+  MaybeCacheSink(source_frame_token, device_id, sink);
 
   UMA_HISTOGRAM_ENUMERATION(
       "Media.Audio.Render.SinkCache.GetOutputDeviceInfoCacheUtilization",
@@ -182,54 +181,7 @@
   return sink->GetOutputDeviceInfo();
 }
 
-scoped_refptr<media::AudioRendererSink> AudioRendererSinkCache::GetSink(
-    const LocalFrameToken& source_frame_token,
-    const std::string& device_id) {
-  UMA_HISTOGRAM_BOOLEAN("Media.Audio.Render.SinkCache.UsedForSinkCreation",
-                        true);
-  TRACE_EVENT_BEGIN2("audio", "AudioRendererSinkCache::GetSink", "frame_token",
-                     source_frame_token.ToString(), "device id", device_id);
-
-  base::AutoLock auto_lock(cache_lock_);
-
-  auto cache_iter = FindCacheEntry_Locked(source_frame_token, device_id,
-                                          true /* unused sink only */);
-
-  if (cache_iter != cache_.end()) {
-    // Found unused sink; mark it as used and return.
-    cache_iter->used = true;
-    TRACE_EVENT_END1("audio", "AudioRendererSinkCache::GetSink", "result",
-                     "Cache hit");
-    return cache_iter->sink;
-  }
-
-  // No unused sink is found, create one, mark it used, cache it and return.
-  CacheEntry cache_entry = {
-      source_frame_token, device_id,
-      create_sink_cb_.Run(
-          source_frame_token,
-          media::AudioSinkParameters(base::UnguessableToken(), device_id)),
-      true /* used */};
-
-  if (SinkIsHealthy(cache_entry.sink.get())) {
-    TRACE_EVENT_INSTANT0("audio",
-                         "AudioRendererSinkCache::GetSink: caching new sink",
-                         TRACE_EVENT_SCOPE_THREAD);
-    cache_.push_back(cache_entry);
-  }
-
-  TRACE_EVENT_END1("audio", "AudioRendererSinkCache::GetSink", "result",
-                   "Cache miss");
-  return cache_entry.sink;
-}
-
-void AudioRendererSinkCache::ReleaseSink(
-    const media::AudioRendererSink* sink_ptr) {
-  // We don't know the sink state, so won't reuse it. Delete it immediately.
-  DeleteSink(sink_ptr, true);
-}
-
-void AudioRendererSinkCache::DeleteLaterIfUnused(
+void AudioRendererSinkCache::DeleteLater(
     scoped_refptr<media::AudioRendererSink> sink) {
   PostDelayedCrossThreadTask(
       *cleanup_task_runner_, FROM_HERE,
@@ -237,14 +189,12 @@
           &AudioRendererSinkCache::DeleteSink,
           // Unretained is safe here since this is a process-wide
           // singleton and tests will ensure lifetime.
-          CrossThreadUnretained(this), WTF::RetainedRef(std::move(sink)),
-          false /*do not delete if used*/),
+          CrossThreadUnretained(this), WTF::RetainedRef(std::move(sink))),
       delete_timeout_);
 }
 
 void AudioRendererSinkCache::DeleteSink(
-    const media::AudioRendererSink* sink_ptr,
-    bool force_delete_used) {
+    const media::AudioRendererSink* sink_ptr) {
   DCHECK(sink_ptr);
 
   scoped_refptr<media::AudioRendererSink> sink_to_stop;
@@ -259,24 +209,12 @@
     if (cache_iter == cache_.end())
       return;
 
-    // When |force_delete_used| is set, it's expected that we are deleting a
-    // used sink.
-    DCHECK((!force_delete_used) || (force_delete_used && cache_iter->used))
-        << "Attempt to delete a non-acquired sink.";
-
-    if (!force_delete_used && cache_iter->used)
-      return;
-
-    // To stop the sink before deletion if it's not used, we need to hold
-    // a ref to it.
-    if (!cache_iter->used)
-      sink_to_stop = cache_iter->sink;
-
+    sink_to_stop = cache_iter->sink;
     cache_.erase(cache_iter);
   }  // Lock scope;
 
   // Stop the sink out of the lock scope.
-  if (sink_to_stop.get()) {
+  if (sink_to_stop) {
     DCHECK_EQ(sink_ptr, sink_to_stop.get());
     sink_to_stop->Stop();
   }
@@ -285,13 +223,10 @@
 AudioRendererSinkCache::CacheContainer::iterator
 AudioRendererSinkCache::FindCacheEntry_Locked(
     const LocalFrameToken& source_frame_token,
-    const std::string& device_id,
-    bool unused_only) {
+    const std::string& device_id) {
+  cache_lock_.AssertAcquired();
   return base::ranges::find_if(
-      cache_,
-      [source_frame_token, &device_id, unused_only](const CacheEntry& val) {
-        if (val.used && unused_only)
-          return false;
+      cache_, [source_frame_token, &device_id](const CacheEntry& val) {
         if (val.source_frame_token != source_frame_token)
           return false;
         if (media::AudioDeviceDescription::IsDefaultDevice(device_id) &&
@@ -304,27 +239,26 @@
       });
 }
 
-void AudioRendererSinkCache::CacheOrStopUnusedSink(
+void AudioRendererSinkCache::MaybeCacheSink(
     const LocalFrameToken& source_frame_token,
     const std::string& device_id,
     scoped_refptr<media::AudioRendererSink> sink) {
   if (!SinkIsHealthy(sink.get())) {
-    TRACE_EVENT_INSTANT0("audio", "CacheOrStopUnusedSink: Unhealthy sink",
+    TRACE_EVENT_INSTANT0("audio", "MaybeCacheSink: Unhealthy sink",
                          TRACE_EVENT_SCOPE_THREAD);
     // Since |sink| is not cached, we must make sure to Stop it now.
     sink->Stop();
     return;
   }
 
-  CacheEntry cache_entry = {source_frame_token, device_id, std::move(sink),
-                            false /* not used */};
+  CacheEntry cache_entry = {source_frame_token, device_id, std::move(sink)};
 
   {
     base::AutoLock auto_lock(cache_lock_);
     cache_.push_back(cache_entry);
   }
 
-  DeleteLaterIfUnused(cache_entry.sink);
+  DeleteLater(cache_entry.sink);
 }
 
 void AudioRendererSinkCache::DropSinksForFrame(
@@ -340,6 +274,7 @@
 }
 
 size_t AudioRendererSinkCache::GetCacheSizeForTesting() {
+  base::AutoLock auto_lock(cache_lock_);
   return cache_.size();
 }
 
diff --git a/third_party/blink/renderer/modules/media/audio/audio_renderer_sink_cache.h b/third_party/blink/renderer/modules/media/audio/audio_renderer_sink_cache.h
index 78332b6..d544bf3 100644
--- a/third_party/blink/renderer/modules/media/audio/audio_renderer_sink_cache.h
+++ b/third_party/blink/renderer/modules/media/audio/audio_renderer_sink_cache.h
@@ -13,6 +13,7 @@
 #include "base/memory/scoped_refptr.h"
 #include "base/synchronization/lock.h"
 #include "base/task/sequenced_task_runner.h"
+#include "base/thread_annotations.h"
 #include "base/time/time.h"
 #include "base/unguessable_token.h"
 #include "media/audio/audio_sink_parameters.h"
@@ -28,9 +29,9 @@
 
 class LocalDOMWindow;
 
-// Caches AudioRendererSink instances, provides them to the clients for usage,
-// tracks their used/unused state, reuses them to obtain output device
-// information, garbage-collects unused sinks.
+// Creates temporary audio sinks in order to acquire OutputDeviceInfo from them.
+// These sinks live for a total time of |delete_timeout| to allow for multiple
+// queries without reconstructing the temporary sink, and are then deleted.
 // Must live on the main render thread. Thread safe.
 class MODULES_EXPORT AudioRendererSinkCache {
  public:
@@ -46,7 +47,7 @@
   // navigation.
   static void InstallWindowObserver(LocalDOMWindow&);
 
-  // |cleanup_task_runner| will be used to delete sinks when they are unused,
+  // |cleanup_task_runner| will be used to delete sinks.
   // AudioRendererSinkCache must outlive any tasks posted to it. Since
   // the sink cache is normally a process-wide singleton, this isn't a problem.
   AudioRendererSinkCache(
@@ -59,14 +60,9 @@
 
   ~AudioRendererSinkCache();
 
-  // AudioRendererSinkCache implementation:
   media::OutputDeviceInfo GetSinkInfo(const LocalFrameToken& source_frame_token,
                                       const base::UnguessableToken& session_id,
                                       const std::string& device_id);
-  scoped_refptr<media::AudioRendererSink> GetSink(
-      const LocalFrameToken& source_frame_token,
-      const std::string& device_id);
-  void ReleaseSink(const media::AudioRendererSink* sink_ptr);
 
  private:
   friend class AudioRendererSinkCacheTest;
@@ -78,22 +74,18 @@
 
   // Schedules a sink for deletion. Deletion will be performed on the same
   // thread the cache is created on.
-  void DeleteLaterIfUnused(scoped_refptr<media::AudioRendererSink> sink);
+  void DeleteLater(scoped_refptr<media::AudioRendererSink> sink);
 
-  // Deletes a sink from the cache. If |force_delete_used| is set, a sink being
-  // deleted can (and should) be in use at the moment of deletion; otherwise the
-  // sink is deleted only if unused.
-  void DeleteSink(const media::AudioRendererSink* sink_ptr,
-                  bool force_delete_used);
+  // Deletes a sink from the cache.
+  void DeleteSink(const media::AudioRendererSink* sink_ptr);
 
   CacheContainer::iterator FindCacheEntry_Locked(
       const LocalFrameToken& source_frame_token,
-      const std::string& device_id,
-      bool unused_only);
+      const std::string& device_id);
 
-  void CacheOrStopUnusedSink(const LocalFrameToken& source_frame_token,
-                             const std::string& device_id,
-                             scoped_refptr<media::AudioRendererSink> sink);
+  void MaybeCacheSink(const LocalFrameToken& source_frame_token,
+                      const std::string& device_id,
+                      scoped_refptr<media::AudioRendererSink> sink);
 
   void DropSinksForFrame(const LocalFrameToken& source_frame_token);
 
@@ -110,16 +102,11 @@
   const CreateSinkCallback create_sink_cb_;
 
   // Cached sink deletion timeout.
-  // For example: (1) sink was created and cached in GetSinkInfo(), and then (2)
-  // the same sink is requested in GetSink(), if time interval between (1) and
-  // (2) is less than |kDeleteTimeoutMs|, then sink cached in (1) is reused in
-  // (2). On the other hand, if after (1) nobody is interested in the sink
-  // within |kDeleteTimeoutMs|, it is garbage-collected.
   const base::TimeDelta delete_timeout_;
 
   // Cached sinks, protected by lock.
   base::Lock cache_lock_;
-  CacheContainer cache_;
+  CacheContainer cache_ GUARDED_BY(cache_lock_);
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/media/audio/audio_renderer_sink_cache_test.cc b/third_party/blink/renderer/modules/media/audio/audio_renderer_sink_cache_test.cc
index 1920437..f448bf4e 100644
--- a/third_party/blink/renderer/modules/media/audio/audio_renderer_sink_cache_test.cc
+++ b/third_party/blink/renderer/modules/media/audio/audio_renderer_sink_cache_test.cc
@@ -51,12 +51,6 @@
     task_runner_->FastForwardUntilNoTasksRemain();
   }
 
-  void GetSink(const LocalFrameToken& frame_token,
-               const std::string& device_id,
-               media::AudioRendererSink** sink) {
-    *sink = cache_->GetSink(frame_token, device_id).get();
-  }
-
  protected:
   size_t sink_count() {
     DCHECK(task_runner_->BelongsToCurrentThread());
@@ -106,55 +100,6 @@
   std::unique_ptr<AudioRendererSinkCache> cache_;
 };
 
-// Verify that normal get/release sink sequence works.
-TEST_F(AudioRendererSinkCacheTest, GetReleaseSink) {
-  // Verify that a new sink is successfully created.
-  EXPECT_EQ(0u, sink_count());
-  scoped_refptr<media::AudioRendererSink> sink =
-      cache_->GetSink(kFrameToken, kDefaultDeviceId).get();
-  ExpectNotToStop(sink.get());  // Cache should not stop sinks marked as used.
-  EXPECT_EQ(kDefaultDeviceId, sink->GetOutputDeviceInfo().device_id());
-  EXPECT_EQ(1u, sink_count());
-
-  // Verify that another sink with the same key is successfully created
-  scoped_refptr<media::AudioRendererSink> another_sink =
-      cache_->GetSink(kFrameToken, kDefaultDeviceId).get();
-  ExpectNotToStop(another_sink.get());
-  EXPECT_EQ(kDefaultDeviceId, another_sink->GetOutputDeviceInfo().device_id());
-  EXPECT_EQ(2u, sink_count());
-  EXPECT_NE(sink, another_sink);
-
-  // Verify that another sink with a different kay is successfully created.
-  scoped_refptr<media::AudioRendererSink> yet_another_sink =
-      cache_->GetSink(kFrameToken, kAnotherDeviceId).get();
-  ExpectNotToStop(yet_another_sink.get());
-  EXPECT_EQ(kAnotherDeviceId,
-            yet_another_sink->GetOutputDeviceInfo().device_id());
-  EXPECT_EQ(3u, sink_count());
-  EXPECT_NE(sink, yet_another_sink);
-  EXPECT_NE(another_sink, yet_another_sink);
-
-  // Verify that the first sink is successfully deleted.
-  cache_->ReleaseSink(sink.get());
-  EXPECT_EQ(2u, sink_count());
-  sink = nullptr;
-
-  // Make sure we deleted the right sink, and the memory for the rest is not
-  // corrupted.
-  EXPECT_EQ(kDefaultDeviceId, another_sink->GetOutputDeviceInfo().device_id());
-  EXPECT_EQ(kAnotherDeviceId,
-            yet_another_sink->GetOutputDeviceInfo().device_id());
-
-  // Verify that the second sink is successfully deleted.
-  cache_->ReleaseSink(another_sink.get());
-  EXPECT_EQ(1u, sink_count());
-  EXPECT_EQ(kAnotherDeviceId,
-            yet_another_sink->GetOutputDeviceInfo().device_id());
-
-  cache_->ReleaseSink(yet_another_sink.get());
-  EXPECT_EQ(0u, sink_count());
-}
-
 // Verify that the sink created with GetSinkInfo() is reused when possible.
 TEST_F(AudioRendererSinkCacheTest, GetDeviceInfo) {
   EXPECT_EQ(0u, sink_count());
@@ -167,30 +112,9 @@
       kFrameToken, base::UnguessableToken(), kDefaultDeviceId);
   EXPECT_EQ(1u, sink_count());
   EXPECT_EQ(device_info.device_id(), one_more_device_info.device_id());
-
-  // Aquire the sink that was created on GetSinkInfo().
-  scoped_refptr<media::AudioRendererSink> sink =
-      cache_->GetSink(kFrameToken, kDefaultDeviceId).get();
-  EXPECT_EQ(1u, sink_count());
-  EXPECT_EQ(device_info.device_id(), sink->GetOutputDeviceInfo().device_id());
-
-  // Now the sink is in used, but we can still get the device info out of it, no
-  // new sink is created.
-  one_more_device_info = cache_->GetSinkInfo(
-      kFrameToken, base::UnguessableToken(), kDefaultDeviceId);
-  EXPECT_EQ(1u, sink_count());
-  EXPECT_EQ(device_info.device_id(), one_more_device_info.device_id());
-
-  // Request sink for the same device. The first sink is in use, so a new one
-  // should be created.
-  scoped_refptr<media::AudioRendererSink> another_sink =
-      cache_->GetSink(kFrameToken, kDefaultDeviceId).get();
-  EXPECT_EQ(2u, sink_count());
-  EXPECT_EQ(device_info.device_id(),
-            another_sink->GetOutputDeviceInfo().device_id());
 }
 
-// Verify that the sink created with GetSinkInfo() is deleted if unused.
+// Verify that the sink created with GetSinkInfo() is deleted.
 TEST_F(AudioRendererSinkCacheTest, GarbageCollection) {
   EXPECT_EQ(0u, sink_count());
 
@@ -210,35 +134,6 @@
   EXPECT_EQ(0u, sink_count());
 }
 
-// Verify that the sink created with GetSinkInfo() is not deleted if used within
-// the timeout.
-TEST_F(AudioRendererSinkCacheTest, NoGarbageCollectionForUsedSink) {
-  EXPECT_EQ(0u, sink_count());
-
-  media::OutputDeviceInfo device_info = cache_->GetSinkInfo(
-      kFrameToken, base::UnguessableToken(), kDefaultDeviceId);
-  EXPECT_EQ(1u, sink_count());
-
-  // Wait less than garbage collection timeout.
-  base::TimeDelta wait_a_bit = kDeleteTimeout - base::Milliseconds(1);
-  task_runner_->FastForwardBy(wait_a_bit);
-
-  // Sink is not deleted yet.
-  EXPECT_EQ(1u, sink_count());
-
-  // Request it:
-  scoped_refptr<media::AudioRendererSink> sink =
-      cache_->GetSink(kFrameToken, kDefaultDeviceId).get();
-  EXPECT_EQ(kDefaultDeviceId, sink->GetOutputDeviceInfo().device_id());
-  EXPECT_EQ(1u, sink_count());
-
-  // Wait more to hit garbage collection timeout.
-  task_runner_->FastForwardBy(kDeleteTimeout);
-
-  // The sink is still in place.
-  EXPECT_EQ(1u, sink_count());
-}
-
 // Verify that the sink created with GetSinkInfo() is not cached if it is
 // unhealthy.
 TEST_F(AudioRendererSinkCacheTest, UnhealthySinkIsNotCached) {
@@ -246,9 +141,6 @@
   media::OutputDeviceInfo device_info = cache_->GetSinkInfo(
       kFrameToken, base::UnguessableToken(), kUnhealthyDeviceId);
   EXPECT_EQ(0u, sink_count());
-  scoped_refptr<media::AudioRendererSink> sink =
-      cache_->GetSink(kFrameToken, kUnhealthyDeviceId).get();
-  EXPECT_EQ(0u, sink_count());
 }
 
 // Verify that a sink created with GetSinkInfo() is stopped even if it's
@@ -307,38 +199,6 @@
       kFrameToken, base::UnguessableToken::Create(), std::string());
 }
 
-// Verify that cache works fine if a sink scheduled for deletion is acquired and
-// released before deletion timeout elapses.
-TEST_F(AudioRendererSinkCacheTest, ReleaseSinkBeforeScheduledDeletion) {
-  EXPECT_EQ(0u, sink_count());
-
-  base::Thread thread("timeout_thread");
-  thread.Start();
-
-  media::OutputDeviceInfo device_info = cache_->GetSinkInfo(
-      kFrameToken, base::UnguessableToken(), kDefaultDeviceId);
-  EXPECT_EQ(1u, sink_count());  // This sink is scheduled for deletion now.
-
-  // Request it:
-  scoped_refptr<media::AudioRendererSink> sink =
-      cache_->GetSink(kFrameToken, kDefaultDeviceId).get();
-  ExpectNotToStop(sink.get());
-  EXPECT_EQ(1u, sink_count());
-
-  // Release it:
-  cache_->ReleaseSink(sink.get());
-  EXPECT_EQ(0u, sink_count());
-
-  media::OutputDeviceInfo another_device_info = cache_->GetSinkInfo(
-      kFrameToken, base::UnguessableToken(), kAnotherDeviceId);
-  EXPECT_EQ(1u, sink_count());  // This sink is scheduled for deletion now.
-
-  task_runner_->FastForwardBy(kDeleteTimeout);
-
-  // Nothing crashed and the second sink deleted on schedule.
-  EXPECT_EQ(0u, sink_count());
-}
-
 // Check that a sink created on one thread in response to GetSinkInfo can be
 // used on another thread.
 TEST_F(AudioRendererSinkCacheTest, MultithreadedAccess) {
@@ -359,64 +219,12 @@
 
   EXPECT_EQ(1u, sink_count());
 
-  // Request the sink on the second thread.
-  media::AudioRendererSink* sink;
-
-  PostAndWaitUntilDone(thread2,
-                       base::BindOnce(&AudioRendererSinkCacheTest::GetSink,
-                                      base::Unretained(this), kFrameToken,
-                                      kDefaultDeviceId, &sink));
-
-  EXPECT_EQ(kDefaultDeviceId, sink->GetOutputDeviceInfo().device_id());
-  EXPECT_EQ(1u, sink_count());
-
-  // Request device information on the first thread again.
+  // Request the device information again on the second thread.
   PostAndWaitUntilDone(
-      thread1,
+      thread2,
       base::BindOnce(base::IgnoreResult(&AudioRendererSinkCache::GetSinkInfo),
                      base::Unretained(cache_.get()), kFrameToken,
                      base::UnguessableToken(), kDefaultDeviceId));
-  EXPECT_EQ(1u, sink_count());
-
-  // Release the sink on the second thread.
-  PostAndWaitUntilDone(
-      thread2,
-      base::BindOnce(&AudioRendererSinkCache::ReleaseSink,
-                     base::Unretained(cache_.get()), base::RetainedRef(sink)));
-
-  EXPECT_EQ(0u, sink_count());
-}
-
-TEST_F(AudioRendererSinkCacheTest, StopsAndDropsSinks) {
-  EXPECT_EQ(0u, sink_count());
-  scoped_refptr<media::AudioRendererSink> sink1 =
-      cache_->GetSink(kFrameToken, "device1").get();
-  scoped_refptr<media::AudioRendererSink> sink2 =
-      cache_->GetSink(kFrameToken, "device2").get();
-  EXPECT_EQ(2u, sink_count());
-
-  EXPECT_CALL(*static_cast<media::MockAudioRendererSink*>(sink1.get()), Stop());
-  EXPECT_CALL(*static_cast<media::MockAudioRendererSink*>(sink2.get()), Stop());
-  DropSinksForFrame(kFrameToken);
-  EXPECT_EQ(0u, sink_count());
-}
-
-TEST_F(AudioRendererSinkCacheTest, StopsAndDropsCorrectSinks) {
-  EXPECT_EQ(0u, sink_count());
-  scoped_refptr<media::AudioRendererSink> sink1 =
-      cache_->GetSink(kFrameToken, "device1").get();
-  scoped_refptr<media::AudioRendererSink> another_sink =
-      cache_->GetSink(LocalFrameToken(), "device1").get();
-  scoped_refptr<media::AudioRendererSink> sink2 =
-      cache_->GetSink(kFrameToken, "device2").get();
-  EXPECT_EQ(3u, sink_count());
-
-  EXPECT_CALL(*static_cast<media::MockAudioRendererSink*>(sink1.get()), Stop());
-  EXPECT_CALL(*static_cast<media::MockAudioRendererSink*>(sink2.get()), Stop());
-  DropSinksForFrame(kFrameToken);
-  EXPECT_EQ(1u, sink_count());
-  EXPECT_CALL(*static_cast<media::MockAudioRendererSink*>(another_sink.get()),
-              Stop());
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/mediastream/media_devices.cc b/third_party/blink/renderer/modules/mediastream/media_devices.cc
index 14df3ece..7270dc9 100644
--- a/third_party/blink/renderer/modules/mediastream/media_devices.cc
+++ b/third_party/blink/renderer/modules/mediastream/media_devices.cc
@@ -787,43 +787,18 @@
 
 void MediaDevices::DevicesEnumerated(
     ScriptPromiseResolver* resolver,
-    mojom::blink::EnumerationResponsePtr response) {
+    const Vector<Vector<WebMediaDeviceInfo>>& enumeration,
+    Vector<mojom::blink::VideoInputDeviceCapabilitiesPtr>
+        video_input_capabilities,
+    Vector<mojom::blink::AudioInputDeviceCapabilitiesPtr>
+        audio_input_capabilities) {
   if (!enumerate_device_requests_.Contains(resolver))
     return;
 
   RequestMetadata request_metadata = enumerate_device_requests_.at(resolver);
+
   enumerate_device_requests_.erase(resolver);
 
-  if (response->result_code !=
-      media::mojom::blink::DeviceEnumerationResult::kSuccess) {
-    switch (response->result_code) {
-      case media::mojom::blink::DeviceEnumerationResult::
-          kErrorCaptureServiceCrash:
-        resolver->Reject(MakeGarbageCollected<DOMException>(
-            DOMExceptionCode::kOperationError, "Could not enumerate cameras"));
-        break;
-      case media::mojom::blink::DeviceEnumerationResult::kUnknownError:
-        resolver->Reject(MakeGarbageCollected<DOMException>(
-            DOMExceptionCode::kOperationError, "Enumeration failed"));
-        break;
-      case media::mojom::blink::DeviceEnumerationResult::kSuccess:
-        NOTREACHED();
-        break;
-    }
-
-    // TODO(crbug.com/1373398): Add a UMA histogram tracking failures.
-    RecordEnumerateDevicesLatency(request_metadata.start_time);
-    return;
-  }
-
-  const Vector<Vector<WebMediaDeviceInfo>>& enumeration = response->enumeration;
-  Vector<mojom::blink::VideoInputDeviceCapabilitiesPtr>
-      video_input_capabilities =
-          std::move(response->video_input_device_capabilities);
-  Vector<mojom::blink::AudioInputDeviceCapabilitiesPtr>
-      audio_input_capabilities =
-          std::move(response->audio_input_device_capabilities);
-
   if (!resolver->GetExecutionContext() ||
       resolver->GetExecutionContext()->IsContextDestroyed()) {
     return;
diff --git a/third_party/blink/renderer/modules/mediastream/media_devices.h b/third_party/blink/renderer/modules/mediastream/media_devices.h
index fdeb9fae..673f046 100644
--- a/third_party/blink/renderer/modules/mediastream/media_devices.h
+++ b/third_party/blink/renderer/modules/mediastream/media_devices.h
@@ -133,8 +133,10 @@
   void DispatchScheduledEvents();
   void StartObserving();
   void StopObserving();
-  void DevicesEnumerated(ScriptPromiseResolver* resolver,
-                         mojom::blink::EnumerationResponsePtr response);
+  void DevicesEnumerated(ScriptPromiseResolver*,
+                         const Vector<Vector<WebMediaDeviceInfo>>&,
+                         Vector<mojom::blink::VideoInputDeviceCapabilitiesPtr>,
+                         Vector<mojom::blink::AudioInputDeviceCapabilitiesPtr>);
   void OnDispatcherHostConnectionError();
   mojom::blink::MediaDevicesDispatcherHost& GetDispatcherHost(LocalFrame*);
 
diff --git a/third_party/blink/renderer/modules/mediastream/media_devices_test.cc b/third_party/blink/renderer/modules/mediastream/media_devices_test.cc
index 7ee8936..55b9c05 100644
--- a/third_party/blink/renderer/modules/mediastream/media_devices_test.cc
+++ b/third_party/blink/renderer/modules/mediastream/media_devices_test.cc
@@ -35,7 +35,6 @@
 
 using base::HistogramTester;
 using blink::mojom::blink::MediaDeviceInfoPtr;
-using media::mojom::blink::DeviceEnumerationResult;
 using ::testing::_;
 
 namespace blink {
@@ -155,15 +154,9 @@
                       blink::mojom::blink::MediaDeviceType::MEDIA_AUDIO_OUTPUT)]
           .push_back(device_info);
     }
-    mojom::blink::EnumerationResponsePtr response =
-        mojom::blink::EnumerationResponse::New();
-    response->result_code = enumerate_devices_result_code_;
-    response->enumeration = std::move(enumeration);
-    response->video_input_device_capabilities =
-        std::move(video_input_capabilities);
-    response->audio_input_device_capabilities =
-        std::move(audio_input_capabilities);
-    std::move(callback).Run(std::move(response));
+    std::move(callback).Run(std::move(enumeration),
+                            std::move(video_input_capabilities),
+                            std::move(audio_input_capabilities));
   }
 
   void GetVideoInputCapabilities(GetVideoInputCapabilitiesCallback) override {
@@ -253,11 +246,6 @@
 
   void CloseBinding() { receiver_.reset(); }
 
-  void SetEnumerateDevicesResultCode(
-      DeviceEnumerationResult enumerate_devices_result_code) {
-    enumerate_devices_result_code_ = enumerate_devices_result_code;
-  }
-
   mojo::Remote<mojom::blink::MediaDevicesListener>& listener() {
     return listener_;
   }
@@ -269,8 +257,6 @@
 #if !BUILDFLAG(IS_ANDROID)
   String next_crop_id_ = "";  // Empty, not null.
 #endif
-  DeviceEnumerationResult enumerate_devices_result_code_ =
-      DeviceEnumerationResult::kSuccess;
 };
 
 class MediaDevicesTest : public PageTestBase {
@@ -937,26 +923,4 @@
 }
 #endif
 
-TEST_F(MediaDevicesTest, EnumerateDevicesFailedResultCode) {
-  V8TestingScope scope;
-  HistogramTester histogram_tester;
-  auto* media_devices = GetMediaDevices(*GetDocument().domWindow());
-  media_devices->SetEnumerateDevicesCallbackForTesting(WTF::BindOnce(
-      &MediaDevicesTest::DevicesEnumerated, WTF::Unretained(this)));
-
-  dispatcher_host().SetEnumerateDevicesResultCode(
-      DeviceEnumerationResult::kUnknownError);
-
-  ScriptPromise promise = media_devices->enumerateDevices(
-      scope.GetScriptState(), scope.GetExceptionState());
-  ScriptPromiseTester tester(scope.GetScriptState(), promise);
-  tester.WaitUntilSettled();
-
-  EXPECT_TRUE(tester.IsRejected());
-  EXPECT_FALSE(dispatcher_host_connection_error());
-  EXPECT_FALSE(devices_enumerated());
-
-  histogram_tester.ExpectTotalCount(kEnumerateDevicesLatencyHistogram, 1);
-}
-
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/mediastream/track_audio_renderer.cc b/third_party/blink/renderer/modules/mediastream/track_audio_renderer.cc
index 2176dbd..f9c3c22 100644
--- a/third_party/blink/renderer/modules/mediastream/track_audio_renderer.cc
+++ b/third_party/blink/renderer/modules/mediastream/track_audio_renderer.cc
@@ -99,7 +99,7 @@
 // media::AudioRendererSink::RenderCallback implementation
 int TrackAudioRenderer::Render(base::TimeDelta delay,
                                base::TimeTicks delay_timestamp,
-                               int prior_frames_skipped,
+                               const media::AudioGlitchInfo& glitch_info,
                                media::AudioBus* audio_bus) {
   TRACE_EVENT2("audio", "TrackAudioRenderer::Render", "delay (ms)",
                delay.InMillisecondsF(), "delay_timestamp (ms)",
diff --git a/third_party/blink/renderer/modules/mediastream/track_audio_renderer.h b/third_party/blink/renderer/modules/mediastream/track_audio_renderer.h
index 5e56c4e..434d8e5 100644
--- a/third_party/blink/renderer/modules/mediastream/track_audio_renderer.h
+++ b/third_party/blink/renderer/modules/mediastream/track_audio_renderer.h
@@ -131,7 +131,7 @@
   // on the IO thread.
   int Render(base::TimeDelta delay,
              base::TimeTicks delay_timestamp,
-             int prior_frames_skipped,
+             const media::AudioGlitchInfo& glitch_info,
              media::AudioBus* audio_bus) override;
   void OnRenderError() override;
 
diff --git a/third_party/blink/renderer/modules/webaudio/audio_context.cc b/third_party/blink/renderer/modules/webaudio/audio_context.cc
index 3bcd004..e3c7ef71 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_context.cc
+++ b/third_party/blink/renderer/modules/webaudio/audio_context.cc
@@ -1011,15 +1011,14 @@
 }
 
 void AudioContext::DevicesEnumerated(
-    mojom::blink::EnumerationResponsePtr response) {
-  // TODO(crbug.com/1313822): Propagate an error when response->result_code !=
-  // kSuccess rather than acting as if there are no devices.
-  Vector<WebMediaDeviceInfo> output_devices;
-  if (response->result_code ==
-      media::mojom::DeviceEnumerationResult::kSuccess) {
-    output_devices = response->enumeration[static_cast<wtf_size_t>(
-        mojom::blink::MediaDeviceType::MEDIA_AUDIO_OUTPUT)];
-  }
+    const Vector<Vector<WebMediaDeviceInfo>>& enumeration,
+    Vector<mojom::blink::VideoInputDeviceCapabilitiesPtr>
+        video_input_capabilities,
+    Vector<mojom::blink::AudioInputDeviceCapabilitiesPtr>
+        audio_input_capabilities) {
+  Vector<WebMediaDeviceInfo> output_devices =
+      enumeration[static_cast<wtf_size_t>(
+          mojom::blink::MediaDeviceType::MEDIA_AUDIO_OUTPUT)];
 
   OnDevicesChanged(mojom::blink::MediaDeviceType::MEDIA_AUDIO_OUTPUT,
                    output_devices);
diff --git a/third_party/blink/renderer/modules/webaudio/audio_context.h b/third_party/blink/renderer/modules/webaudio/audio_context.h
index 0ae2484..1a8df37 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_context.h
+++ b/third_party/blink/renderer/modules/webaudio/audio_context.h
@@ -215,7 +215,11 @@
   void UninitializeMediaDeviceService();
 
   // Callback from blink::mojom::MediaDevicesDispatcherHost::EnumerateDevices().
-  void DevicesEnumerated(mojom::blink::EnumerationResponsePtr response);
+  void DevicesEnumerated(const Vector<Vector<WebMediaDeviceInfo>>& enumeration,
+                         Vector<mojom::blink::VideoInputDeviceCapabilitiesPtr>
+                             video_input_capabilities,
+                         Vector<mojom::blink::AudioInputDeviceCapabilitiesPtr>
+                             audio_input_capabilities);
 
   // A helper function used to update `v8_sink_id_` whenever `sink_id_` is
   // updated.
diff --git a/third_party/blink/renderer/modules/webgpu/gpu.cc b/third_party/blink/renderer/modules/webgpu/gpu.cc
index a88a1a5..474fe0e 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu.cc
+++ b/third_party/blink/renderer/modules/webgpu/gpu.cc
@@ -267,7 +267,9 @@
             // adapter
             // TODO(crbug.com/973017): Collect GPU info and surface context
             // creation error.
-            resolver->Resolve(v8::Null(script_state->GetIsolate()));
+            gpu->OnRequestAdapterCallback(
+                script_state, options, resolver, WGPURequestAdapterStatus_Error,
+                0, "Failed to create WebGPU Context Provider");
           }
         },
         WrapPersistent(this), WrapPersistent(script_state),
diff --git a/third_party/blink/renderer/modules/webrtc/webrtc_audio_renderer.cc b/third_party/blink/renderer/modules/webrtc/webrtc_audio_renderer.cc
index c7caf62..612351a 100644
--- a/third_party/blink/renderer/modules/webrtc/webrtc_audio_renderer.cc
+++ b/third_party/blink/renderer/modules/webrtc/webrtc_audio_renderer.cc
@@ -608,7 +608,7 @@
 
 int WebRtcAudioRenderer::Render(base::TimeDelta delay,
                                 base::TimeTicks delay_timestamp,
-                                int prior_frames_skipped,
+                                const media::AudioGlitchInfo& glitch_info,
                                 media::AudioBus* audio_bus) {
   DCHECK(sink_->CurrentThreadIsRenderingThread());
   DCHECK_LE(sink_params_.channels(), 8);
diff --git a/third_party/blink/renderer/modules/webrtc/webrtc_audio_renderer.h b/third_party/blink/renderer/modules/webrtc/webrtc_audio_renderer.h
index 5543e72..6fe4e68 100644
--- a/third_party/blink/renderer/modules/webrtc/webrtc_audio_renderer.h
+++ b/third_party/blink/renderer/modules/webrtc/webrtc_audio_renderer.h
@@ -267,7 +267,7 @@
   // These two methods are called on the AudioOutputDevice worker thread.
   int Render(base::TimeDelta delay,
              base::TimeTicks delay_timestamp,
-             int prior_frames_skipped,
+             const media::AudioGlitchInfo& glitch_info,
              media::AudioBus* audio_bus) override;
   void OnRenderError() override;
 
diff --git a/third_party/blink/renderer/platform/bindings/trace_wrapper_v8_reference.h b/third_party/blink/renderer/platform/bindings/trace_wrapper_v8_reference.h
index 0638bb8..10cbe6b 100644
--- a/third_party/blink/renderer/platform/bindings/trace_wrapper_v8_reference.h
+++ b/third_party/blink/renderer/platform/bindings/trace_wrapper_v8_reference.h
@@ -47,7 +47,9 @@
   // v8::TracedReference assumes that references uniquely point to an internal
   // node.
   static constexpr bool kCanCopyWithMemcpy = false;
-  static constexpr bool kCanMoveWithMemcpy = true;
+  // TODO(chromium:1322114): Temporarily disable move with memcpy to evaluate
+  // impact on crashers. Move should always be followed by a clear (non-dtor).
+  static constexpr bool kCanMoveWithMemcpy = false;
 
   // TraceWrapperV8Reference supports concurrent tracing.
   static constexpr bool kCanTraceConcurrently = true;
diff --git a/third_party/blink/renderer/platform/graphics/compositing/paint_chunks_to_cc_layer_test.cc b/third_party/blink/renderer/platform/graphics/compositing/paint_chunks_to_cc_layer_test.cc
index 895f0554..925f9fa 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/paint_chunks_to_cc_layer_test.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/paint_chunks_to_cc_layer_test.cc
@@ -10,7 +10,7 @@
 #include "cc/paint/display_item_list.h"
 #include "cc/paint/paint_filter.h"
 #include "cc/paint/paint_flags.h"
-#include "cc/paint/paint_op_buffer.h"
+#include "cc/paint/paint_op_buffer_iterator.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/renderer/platform/graphics/paint/clip_paint_property_node.h"
diff --git a/third_party/blink/renderer/platform/graphics/dark_mode_image_classifier_test.cc b/third_party/blink/renderer/platform/graphics/dark_mode_image_classifier_test.cc
index 56f7da7f..d06599fc 100644
--- a/third_party/blink/renderer/platform/graphics/dark_mode_image_classifier_test.cc
+++ b/third_party/blink/renderer/platform/graphics/dark_mode_image_classifier_test.cc
@@ -12,6 +12,7 @@
 #include "third_party/blink/renderer/platform/testing/testing_platform_support_with_mock_scheduler.h"
 #include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
 #include "third_party/blink/renderer/platform/wtf/shared_buffer.h"
+#include "third_party/skia/include/core/SkCanvas.h"
 
 namespace blink {
 namespace {
diff --git a/third_party/blink/renderer/platform/graphics/paint/drawing_display_item.cc b/third_party/blink/renderer/platform/graphics/paint/drawing_display_item.cc
index ffcf7a93..62197ef 100644
--- a/third_party/blink/renderer/platform/graphics/paint/drawing_display_item.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/drawing_display_item.cc
@@ -6,6 +6,7 @@
 
 #include "base/logging.h"
 #include "cc/paint/display_item_list.h"
+#include "cc/paint/paint_op_buffer_iterator.h"
 #include "third_party/blink/renderer/platform/graphics/graphics_context.h"
 #include "third_party/blink/renderer/platform/graphics/logging_canvas.h"
 #include "third_party/blink/renderer/platform/graphics/paint/paint_canvas.h"
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc b/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc
index 1760c66..7fd36c5c 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc
@@ -576,29 +576,39 @@
   return properties_->GetControllerServiceWorkerMode();
 }
 
-bool ResourceFetcher::ResourceNeedsLoad(Resource* resource,
-                                        const FetchParameters& params,
-                                        RevalidationPolicy policy) {
-  // MHTML documents should not trigger actual loads (i.e. all resource requests
-  // should be fulfilled by the MHTML archive).
-  if (archive_)
-    return false;
-
+bool ResourceFetcher::ShouldDeferResource(ResourceType type,
+                                          const FetchParameters& params) const {
   // Defer a font load until it is actually needed unless this is a link
   // preload.
-  if (resource->GetType() == ResourceType::kFont && !params.IsLinkPreload())
-    return false;
+  if (type == ResourceType::kFont && !params.IsLinkPreload())
+    return true;
 
   // Defer loading images either when:
   // - images are disabled
   // - instructed to defer loading images from network
-  if (resource->GetType() == ResourceType::kImage &&
-      (ShouldDeferImageLoad(resource->Url()) ||
+  if (type == ResourceType::kImage &&
+      (ShouldDeferImageLoad(params.Url()) ||
        params.GetImageRequestBehavior() ==
            FetchParameters::ImageRequestBehavior::kDeferImageLoad)) {
-    return false;
+    return true;
   }
-  return policy != RevalidationPolicy::kUse || resource->StillNeedsLoad();
+
+  return false;
+}
+
+bool ResourceFetcher::ResourceAlreadyLoadStarted(Resource* resource,
+                                                 RevalidationPolicy policy) {
+  return policy == RevalidationPolicy::kUse && resource &&
+         !resource->StillNeedsLoad();
+}
+
+bool ResourceFetcher::ResourceNeedsLoad(Resource* resource,
+                                        RevalidationPolicy policy,
+                                        bool should_defer) const {
+  // MHTML documents should not trigger actual loads (i.e. all resource requests
+  // should be fulfilled by the MHTML archive).
+  return !archive_ && !should_defer &&
+         !ResourceAlreadyLoadStarted(resource, policy);
 }
 
 void ResourceFetcher::DidLoadResourceFromMemoryCache(
@@ -750,9 +760,33 @@
   }
 }
 
+ResourceFetcher::RevalidationPolicyForMetrics
+ResourceFetcher::MapToPolicyForMetrics(RevalidationPolicy policy,
+                                       Resource* resource,
+                                       bool should_defer) {
+  if (should_defer && !ResourceAlreadyLoadStarted(resource, policy)) {
+    return RevalidationPolicyForMetrics::kDefer;
+  }
+  // A resource in memory cache but not yet loaded is a deferred resource
+  // created in previous loads.
+  if (policy == RevalidationPolicy::kUse && resource->StillNeedsLoad()) {
+    return RevalidationPolicyForMetrics::kPreviouslyDeferredLoad;
+  }
+  switch (policy) {
+    case RevalidationPolicy::kUse:
+      return RevalidationPolicyForMetrics::kUse;
+    case RevalidationPolicy::kRevalidate:
+      return RevalidationPolicyForMetrics::kRevalidate;
+    case RevalidationPolicy::kReload:
+      return RevalidationPolicyForMetrics::kReload;
+    case RevalidationPolicy::kLoad:
+      return RevalidationPolicyForMetrics::kLoad;
+  }
+}
+
 void ResourceFetcher::UpdateMemoryCacheStats(
     Resource* resource,
-    RevalidationPolicy policy,
+    RevalidationPolicyForMetrics policy,
     const FetchParameters& params,
     const ResourceFactory& factory,
     bool is_static_data,
@@ -1135,8 +1169,12 @@
     }
   }
 
-  UpdateMemoryCacheStats(resource, policy, params, factory, is_static_data,
-                         same_top_frame_site_resource_cached);
+  // Use factory.GetType() since resource can be nullptr here.
+  bool should_defer = ShouldDeferResource(factory.GetType(), params);
+
+  UpdateMemoryCacheStats(
+      resource, MapToPolicyForMetrics(policy, resource, should_defer), params,
+      factory, is_static_data, same_top_frame_site_resource_cached);
 
   switch (policy) {
     case RevalidationPolicy::kReload:
@@ -1153,7 +1191,6 @@
           resource->ShouldRevalidateStaleResponse()) {
         ScheduleStaleRevalidate(resource);
       }
-
       break;
   }
   DCHECK(resource);
@@ -1229,7 +1266,7 @@
   // loading immediately. If revalidation policy was determined as |Revalidate|,
   // the resource was already initialized for the revalidation here, but won't
   // start loading.
-  if (ResourceNeedsLoad(resource, params, policy)) {
+  if (ResourceNeedsLoad(resource, policy, should_defer)) {
     if (!StartLoad(resource,
                    std::move(params.MutableResourceRequest().MutableBody()),
                    load_blocking_policy, params.GetRenderBlockingBehavior())) {
@@ -2097,6 +2134,19 @@
 bool ResourceFetcher::StartLoad(Resource* resource) {
   DCHECK(resource->GetType() == ResourceType::kFont ||
          resource->GetType() == ResourceType::kImage);
+  // Currently the metrics collection codes are duplicated here and in
+  // UpdateMemoryCacheStats() because we have two calling paths for triggering a
+  // load here and RequestResource().
+  // TODO(https://crbug.com/1376866): Consider merging the duplicated code.
+  if (resource->GetType() == ResourceType::kFont) {
+    base::UmaHistogramEnumeration(
+        RESOURCE_HISTOGRAM_PREFIX "Font",
+        RevalidationPolicyForMetrics::kPreviouslyDeferredLoad);
+  } else {
+    base::UmaHistogramEnumeration(
+        RESOURCE_HISTOGRAM_PREFIX "Image",
+        RevalidationPolicyForMetrics::kPreviouslyDeferredLoad);
+  }
   return StartLoad(resource, ResourceRequestBody(),
                    ImageLoadBlockingPolicy::kDefault,
                    RenderBlockingBehavior::kNonBlocking);
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h b/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h
index 6d85928..363cdba 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h
+++ b/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h
@@ -196,10 +196,6 @@
   // call this method explicitly on cases such as ResourceNeedsLoad() returning
   // false.
   bool StartLoad(Resource*);
-  bool StartLoad(Resource*,
-                 ResourceRequestBody,
-                 ImageLoadBlockingPolicy,
-                 RenderBlockingBehavior);
 
   void SetAutoLoadImages(bool);
   void SetImagesEnabled(bool);
@@ -342,6 +338,11 @@
     kIncludingKeepaliveLoaders,
   };
 
+  bool StartLoad(Resource*,
+                 ResourceRequestBody,
+                 ImageLoadBlockingPolicy,
+                 RenderBlockingBehavior);
+
   void InitializeRevalidation(ResourceRequest&, Resource*);
   // When |security_origin| of the ResourceLoaderOptions is not a nullptr, it'll
   // be used instead of the associated FetchContext's SecurityOrigin.
@@ -388,14 +389,31 @@
   void StopFetchingInternal(StopFetchingTarget);
   void StopFetchingIncludingKeepaliveLoaders();
 
-  // RevalidationPolicy enum values are used in UMAs https://crbug.com/579496.
   enum class RevalidationPolicy {
     kUse,
     kRevalidate,
     kReload,
     kLoad,
-    kMaxValue = kLoad
+    kMaxValue = kLoad,
   };
+  // The Blink.MemoryCache.RevalationPolicy UMA uses the following enum
+  // rather than RevalidationPolicy to record the deferred resources in
+  // the resource fetcher.
+  // These values are persisted to logs. Entries should not be renumbered and
+  // numeric values should never be reused.
+  enum class RevalidationPolicyForMetrics {
+    kUse,
+    kRevalidate,
+    kReload,
+    kLoad,
+    kDefer,
+    kPreviouslyDeferredLoad,
+    kMaxValue = kPreviouslyDeferredLoad,
+  };
+
+  // Friends required for accessing RevalidationPolicyForMetrics
+  FRIEND_TEST(ImageResourceCounterTest, RevalidationPolicyMetrics);
+  FRIEND_TEST(FontResourceTest, RevalidationPolicyMetrics);
 
   // A wrapper just for placing a trace_event macro.
   RevalidationPolicy DetermineRevalidationPolicy(
@@ -426,14 +444,24 @@
                                       bool is_static_data,
                                       RenderBlockingBehavior);
 
-  bool ResourceNeedsLoad(Resource*, const FetchParameters&, RevalidationPolicy);
+  bool ShouldDeferResource(ResourceType, const FetchParameters& params) const;
+
+  bool ResourceNeedsLoad(Resource*,
+                         RevalidationPolicy,
+                         bool should_defer) const;
+
+  static bool ResourceAlreadyLoadStarted(Resource*, RevalidationPolicy);
 
   void ResourceTimingReportTimerFired(TimerBase*);
 
   void ReloadImagesIfNotDeferred();
 
+  static RevalidationPolicyForMetrics MapToPolicyForMetrics(RevalidationPolicy,
+                                                            Resource*,
+                                                            bool should_defer);
+
   void UpdateMemoryCacheStats(Resource*,
-                              RevalidationPolicy,
+                              RevalidationPolicyForMetrics,
                               const FetchParameters&,
                               const ResourceFactory&,
                               bool is_static_data,
diff --git a/third_party/blink/renderer/platform/media/webaudiosourceprovider_impl.cc b/third_party/blink/renderer/platform/media/webaudiosourceprovider_impl.cc
index c059f070..0f7726c1 100644
--- a/third_party/blink/renderer/platform/media/webaudiosourceprovider_impl.cc
+++ b/third_party/blink/renderer/platform/media/webaudiosourceprovider_impl.cc
@@ -14,6 +14,7 @@
 #include "base/metrics/histogram_macros.h"
 #include "base/notreached.h"
 #include "base/thread_annotations.h"
+#include "media/base/audio_glitch_info.h"
 #include "media/base/audio_timestamp_helper.h"
 #include "media/base/bind_to_current_loop.h"
 #include "media/base/media_log.h"
@@ -47,13 +48,13 @@
   // get a copy of the rendered audio by SetCopyAudioCallback().
   int Render(base::TimeDelta delay,
              base::TimeTicks delay_timestamp,
-             int prior_frames_skipped,
+             const media::AudioGlitchInfo& glitch_info,
              media::AudioBus* audio_bus) override {
     DCHECK(initialized());
     DCHECK_EQ(audio_bus->channels(), channels_);
 
-    const int num_rendered_frames = renderer_->Render(
-        delay, delay_timestamp, prior_frames_skipped, audio_bus);
+    const int num_rendered_frames =
+        renderer_->Render(delay, delay_timestamp, glitch_info, audio_bus);
 
     // Avoid taking the copy lock for the vast majority of cases.
     if (copy_required_) {
@@ -68,6 +69,9 @@
           bus_copy->Zero();
         else
           audio_bus->CopyTo(bus_copy.get());
+
+        // TODO(fhernqvist): Propagate glitch info through here if the callback
+        // needs it.
         copy_audio_bus_callback_.Run(std::move(bus_copy),
                                      static_cast<uint32_t>(frames_delayed),
                                      sample_rate_);
@@ -202,8 +206,10 @@
     return;
   }
 
+  // TODO(fhernqvist): If we need glitches propagated through WebAudio, plumb
+  // them through here.
   const int frames = tee_filter_->Render(
-      base::TimeDelta(), base::TimeTicks::Now(), 0, bus_wrapper_.get());
+      base::TimeDelta(), base::TimeTicks::Now(), {}, bus_wrapper_.get());
 
   // Zero out frames after rendering for tainted origins.
   if (tee_filter_->is_tainted()) {
@@ -336,7 +342,7 @@
 }
 
 int WebAudioSourceProviderImpl::RenderForTesting(media::AudioBus* audio_bus) {
-  return tee_filter_->Render(base::TimeDelta(), base::TimeTicks::Now(), 0,
+  return tee_filter_->Render(base::TimeDelta(), base::TimeTicks::Now(), {},
                              audio_bus);
 }
 
diff --git a/third_party/blink/renderer/platform/media/webaudiosourceprovider_impl_test.cc b/third_party/blink/renderer/platform/media/webaudiosourceprovider_impl_test.cc
index 228fb8d6..11dcc8f 100644
--- a/third_party/blink/renderer/platform/media/webaudiosourceprovider_impl_test.cc
+++ b/third_party/blink/renderer/platform/media/webaudiosourceprovider_impl_test.cc
@@ -7,6 +7,7 @@
 #include "base/bind.h"
 #include "base/run_loop.h"
 #include "base/test/task_environment.h"
+#include "media/base/audio_glitch_info.h"
 #include "media/base/audio_parameters.h"
 #include "media/base/fake_audio_render_callback.h"
 #include "media/base/media_util.h"
@@ -239,7 +240,7 @@
 
   // Ensure volume adjustment is working.
   fake_callback_.reset();
-  fake_callback_.Render(base::TimeDelta(), base::TimeTicks::Now(), 0,
+  fake_callback_.Render(base::TimeDelta(), base::TimeTicks::Now(), {},
                         bus2.get());
   bus2->Scale(kTestVolume);
 
@@ -259,10 +260,10 @@
   // configuring the fake callback to return half the data.  After these calls
   // bus1 is full of junk data, and bus2 is partially filled.
   wasp_impl_->SetVolume(1);
-  fake_callback_.Render(base::TimeDelta(), base::TimeTicks::Now(), 0,
+  fake_callback_.Render(base::TimeDelta(), base::TimeTicks::Now(), {},
                         bus1.get());
   fake_callback_.reset();
-  fake_callback_.Render(base::TimeDelta(), base::TimeTicks::Now(), 0,
+  fake_callback_.Render(base::TimeDelta(), base::TimeTicks::Now(), {},
                         bus2.get());
   bus2->ZeroFramesPartial(bus2->frames() / 2,
                           bus2->frames() - bus2->frames() / 2);
diff --git a/third_party/blink/renderer/platform/network/http_parsers.cc b/third_party/blink/renderer/platform/network/http_parsers.cc
index cf9f5c9..e220e26f 100644
--- a/third_party/blink/renderer/platform/network/http_parsers.cc
+++ b/third_party/blink/renderer/platform/network/http_parsers.cc
@@ -171,9 +171,10 @@
       std::move(sources), std::move(nonces), std::move(hashes),
       source_list->allow_self, source_list->allow_star,
       source_list->allow_response_redirects, source_list->allow_inline,
-      source_list->allow_eval, source_list->allow_wasm_eval,
-      source_list->allow_wasm_unsafe_eval, source_list->allow_dynamic,
-      source_list->allow_unsafe_hashes, source_list->report_sample);
+      source_list->allow_inline_speculation_rules, source_list->allow_eval,
+      source_list->allow_wasm_eval, source_list->allow_wasm_unsafe_eval,
+      source_list->allow_dynamic, source_list->allow_unsafe_hashes,
+      source_list->report_sample);
 }
 
 blink::ContentSecurityPolicyHeaderPtr ConvertToBlink(
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index ff1fda2..ab1e0e6ea 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -601,6 +601,7 @@
       // Allows positioning a positioned element relative to another one.
       // https://drafts.csswg.org/css-anchor-1/
       name: "CSSAnchorPositioning",
+      implied_by: ["HTMLSelectMenuElement"],
       status: "experimental",
     },
     {
@@ -875,7 +876,7 @@
     // Support for CSS Toggles, https://tabatkins.github.io/css-toggle/
     {
       name: "CSSToggles",
-      status: "test",
+      status: "experimental",
     },
     {
       // Explainer: https://github.com/DevSDK/css-trigonometric-functions
@@ -909,7 +910,7 @@
     // https://drafts.csswg.org/scroll-animations-1/#view-timeline-shorthand
     {
       name: "CSSViewTimeline",
-      status: "test",
+      status: "experimental",
     },
     {
       name: "Database",
@@ -945,6 +946,8 @@
     {
       name: "DeliveryType",
       status: "experimental",
+      implied_by: ["SpeculationRulesPrefetchFuture"],
+      origin_trial_feature_name: "DeliveryType",
     },
     {
       name: "DesktopCaptureDisableLocalEchoControl",
@@ -2575,18 +2578,34 @@
     },
     {
       name: "SpeculationRulesDocumentRules",
+      origin_trial_feature_name: "SpeculationRulesPrefetchFuture",
+      origin_trial_allows_third_party: true,
     },
     {
-      name: "SpeculationRulesFetchFromHeader"
+      name: "SpeculationRulesFetchFromHeader",
+      origin_trial_feature_name: "SpeculationRulesPrefetchFuture",
+      origin_trial_allows_third_party: true,
     },
     {
       name: "SpeculationRulesPointerDownHeuristics",
-      base_feature: "SpeculationRulesPointerDownHeuristics"
+      base_feature: "SpeculationRulesPointerDownHeuristics",
+      base_feature_status: "enabled",
     },
     {
       name: "SpeculationRulesPointerHoverHeuristics",
       base_feature: "SpeculationRulesPointerHoverHeuristics"
     },
+    // This feature exists solely to be the target of implied_by for features
+    // that are part of this trial but which may also be enabled another way.
+    // Otherwise, runtime-enabled features can be added to the origin trial
+    // with the following:
+    //   origin_trial_feature_name: "SpeculationRulesPrefetchFuture",
+    //   origin_trial_allows_third_party: true,
+    {
+      name: "SpeculationRulesPrefetchFuture",
+      origin_trial_feature_name: "SpeculationRulesPrefetchFuture",
+      origin_trial_allows_third_party: true,
+    },
     // Origin trial to enable Speculation Rules for access to the prefetch proxy.
     // https://crbug.com/1190167
     {
@@ -2595,6 +2614,7 @@
       status: {"Android": "stable"},
       base_feature: "SpeculationRulesPrefetchProxy",
       copied_from_base_feature_if: "overridden",
+      implied_by: ["SpeculationRulesPrefetchFuture"],
     },
     {
       "name": "SpeculationRulesPrefetchWithSubresources",
@@ -2603,9 +2623,13 @@
     {
       name: "SpeculationRulesReferrerPolicyKey",
       base_feature: "SpeculationRulesReferrerPolicyKey",
+      origin_trial_feature_name: "SpeculationRulesReferrerPolicyKey",
+      implied_by: ["SpeculationRulesPrefetchFuture"],
     },
     {
       name: "SpeculationRulesRelativeToDocument",
+      origin_trial_feature_name: "SpeculationRulesPrefetchFuture",
+      origin_trial_allows_third_party: true,
     },
     {
       name: "SrcsetMaxDensity",
diff --git a/third_party/blink/tools/blinkpy/web_tests/port/fuchsia.py b/third_party/blink/tools/blinkpy/web_tests/port/fuchsia.py
index 0149821..1890ca27 100644
--- a/third_party/blink/tools/blinkpy/web_tests/port/fuchsia.py
+++ b/third_party/blink/tools/blinkpy/web_tests/port/fuchsia.py
@@ -44,35 +44,22 @@
 from blinkpy.web_tests.port import linux
 from blinkpy.web_tests.port import server_process
 
-# Modules loaded dynamically in _import_fuchsia_runner().
-# pylint: disable=invalid-name
-fuchsia_target = None
-qemu_target = None
-symbolizer = None
-
-# pylint: enable=invalid-name
-
 
 # Imports Fuchsia runner modules. This is done dynamically only when FuchsiaPort
 # is instantiated to avoid dependency on Fuchsia runner on other platforms.
 def _import_fuchsia_runner():
-    sys.path.insert(0, os.path.join(get_chromium_src_dir(), 'build/fuchsia'))
+    sys.path.insert(0,
+                    os.path.join(get_chromium_src_dir(), 'build/fuchsia/test'))
 
     # pylint: disable=import-error
     # pylint: disable=invalid-name
     # pylint: disable=redefined-outer-name
-    global ConnectPortForwardingTask
-    from common import ConnectPortForwardingTask
-    global _GetPathToBuiltinTarget, _LoadTargetClass, InitializeTargetArgs
-    from common_args import _GetPathToBuiltinTarget, _LoadTargetClass, InitializeTargetArgs
-    global fuchsia_target
-    import target as fuchsia_target
-    global qemu_target
-    import qemu_target
-    global symbolizer
-    import symbolizer
-    global ffx_session
-    import ffx_session
+    global SDK_ROOT, SDK_TOOLS_DIR, run_continuous_ffx_command, run_ffx_command
+    from common import SDK_ROOT, SDK_TOOLS_DIR, run_continuous_ffx_command, run_ffx_command
+    global get_ssh_prefix
+    from compatible_utils import get_ssh_prefix
+    global port_forward
+    from test_server import port_forward
     # pylint: enable=import-error
     # pylint: enable=invalid-name
     # pylint: disable=redefined-outer-name
@@ -123,73 +110,55 @@
 
 
 class _TargetHost(object):
-    def __init__(self, build_path, ports_to_forward, target):
-        try:
-            self._pkg_repo = None
-            self._target = target
-            self._ffx_session_context = ffx_session.FfxSession(
-                self._target._log_manager)
-            self._ffx_session = self._ffx_session_context.__enter__()
-            self._target.Start()
-            self._setup_target(build_path, ports_to_forward)
-        except:
-            self.cleanup()
-            raise
+    def __init__(self, ports_to_forward, target_id):
+        self._target_id = target_id
+        self._setup_target(ports_to_forward)
 
-    def _setup_target(self, build_path, ports_to_forward):
+    def _setup_target(self, ports_to_forward):
         # Tell SSH to forward all server ports from the Fuchsia device to
         # the host.
+
+        self._host_port_pair = run_ffx_command(
+            ('target', 'get-ssh-address'),
+            self._target_id,
+            capture_output=True).stdout.strip()
+        self._proxy = self._port_forward_list(ports_to_forward)
+
+    def _port_forward_list(self, ports):
+        """Reverse forward all ports listed in |ports| to the device."""
+
+        ssh_prefix = get_ssh_prefix(self._host_port_pair)
         forwarding_flags = [
             '-O',
             'forward',  # Send SSH mux control signal.
             '-N',  # Don't execute command
             '-T'  # Don't allocate terminal.
         ]
-        for port in ports_to_forward:
-            forwarding_flags += ['-R', '%d:localhost:%d' % (port, port)]
-        self._proxy = self._target.RunCommandPiped([],
-                                                   ssh_args=forwarding_flags,
-                                                   stdout=subprocess.PIPE,
-                                                   stderr=subprocess.STDOUT)
-
-        package_path = os.path.join(build_path, CONTENT_SHELL_PACKAGE_PATH)
-        self._target.StartSystemLog([package_path])
-
-        self._pkg_repo = self._target.GetPkgRepo()
-        self._pkg_repo.__enter__()
-
-        self._target.InstallPackage([package_path])
-
-        # Process will be forked for each worker, which may make QemuTarget
-        # unusable (e.g. waitpid() for qemu process returns ECHILD after
-        # fork() ). Save command runner before fork()ing, to use it later to
-        # connect to the target.
-        self.target_command_runner = self._target.GetCommandRunner()
+        for port in ports:
+            forwarding_flags += ['-R', f'{port}:localhost:{port}']
+        return subprocess.Popen(ssh_prefix + forwarding_flags,
+                                stdout=subprocess.PIPE,
+                                stderr=open('/dev/null'))
 
     def run_command(self, command):
-        return self.target_command_runner.RunCommandPiped(
-            command,
-            stdin=subprocess.PIPE,
-            stdout=subprocess.PIPE,
-            stderr=subprocess.PIPE)
+        ssh_prefix = get_ssh_prefix(self._host_port_pair)
+        return subprocess.Popen(ssh_prefix + command,
+                                stdin=subprocess.PIPE,
+                                stdout=subprocess.PIPE,
+                                stderr=subprocess.PIPE)
 
     def run_test_component(self, url, cmd_line):
         # TODO(crbug.com/1381116): migrate off of `ffx test run` when possible
-        return self._ffx_session.test_run(self._target.GetFfxTarget(), url,
-                                          cmd_line)
-
-    def cleanup(self):
-        self._ffx_session = None
-        self._ffx_session_context.__exit__(None, None, None)
-        try:
-            if self._pkg_repo:
-                self._pkg_repo.__exit__(None, None, None)
-        finally:
-            if self._target:
-                self._target.Stop()
+        command = ['test', 'run', url, '--']
+        command.extend(cmd_line)
+        return run_continuous_ffx_command(command,
+                                          self._target_id,
+                                          encoding=None,
+                                          stdout=subprocess.PIPE,
+                                          stderr=subprocess.STDOUT)
 
     def setup_forwarded_port(self, port):
-        return ConnectPortForwardingTask(self._target, port)
+        return port_forward(self._host_port_pair, port)
 
 
 class FuchsiaPort(base.Port):
@@ -220,6 +189,31 @@
         self._target_host = target_host
         self._zircon_logger = None
         _import_fuchsia_runner()
+        self._symbolizer = os.path.join(SDK_TOOLS_DIR, 'symbolizer')
+        self._build_id_dir = os.path.join(SDK_ROOT, '.build-id')
+
+    def run_symbolizer(self, input_fd, output_fd, ids_txt_paths):
+        """Starts a symbolizer process.
+
+        input_fd: Input file to be symbolized.
+        output_fd: Output file for symbolizer stdout and stderr.
+        ids_txt_paths: Path to the ids.txt files which map build IDs to
+                        unstripped binaries on the filesystem.
+        Returns a Popen object for the started process."""
+
+        symbolizer_cmd = [
+            self._symbolizer, '--omit-module-lines', '--build-id-dir',
+            self._build_id_dir
+        ]
+        for ids_txt in ids_txt_paths:
+            symbolizer_cmd.extend(['--ids-txt', ids_txt])
+
+        logging.debug('Running "%s".' % ' '.join(symbolizer_cmd))
+        return subprocess.Popen(symbolizer_cmd,
+                                stdin=input_fd,
+                                stdout=output_fd,
+                                stderr=subprocess.STDOUT,
+                                close_fds=True)
 
     def _driver_class(self):
         return ChromiumFuchsiaDriver
@@ -243,37 +237,21 @@
     def setup_test_run(self):
         super(FuchsiaPort, self).setup_test_run()
         try:
-            target_args = InitializeTargetArgs()
-            target_args.out_dir = self._build_path()
-            target_args.target_cpu = self._target_cpu()
-            target_args.fuchsia_out_dir = self.get_option('fuchsia_out_dir')
-            target_args.custom_image = self.get_option('custom_image')
-            target_args.ssh_config = self.get_option('fuchsia_ssh_config')
-            target_args.host = self.get_option('fuchsia_host')
-            target_args.port = self.get_option('fuchsia_port')
-            target_args.node_name = self.get_option('fuchsia_node_name')
-            target_args.cpu_cores = self._cpu_cores()
-            target_args.logs_dir = self.get_option('logs_dir')
-            target = _LoadTargetClass(
-                _GetPathToBuiltinTarget(
-                    self._target_device)).CreateFromArgs(target_args)
-            self._target_host = _TargetHost(self._build_path(),
-                                            self.SERVER_PORTS, target)
+            target_id = self.get_option('fuchsia_target_id')
+            self._target_host = _TargetHost(self.SERVER_PORTS, target_id)
 
             if self.get_option('zircon_logging'):
                 klog_proc = self._target_host.run_command(['dlog', '-f'])
-                symbolized_klog_proc = symbolizer.RunSymbolizer(klog_proc.stdout,
-                    subprocess.PIPE, [self.get_build_ids_path()])
+                symbolized_klog_proc = self.run_symbolizer(
+                    klog_proc.stdout, subprocess.PIPE,
+                    [self.get_build_ids_path()])
                 self._zircon_logger = SubprocessOutputLogger(symbolized_klog_proc,
                     'Zircon')
-
-        except fuchsia_target.FuchsiaTargetException as e:
-            _log.error('Failed to start qemu: %s.', str(e))
+        except:
             return exit_codes.NO_DEVICES_EXIT_STATUS
 
     def clean_up_test_run(self):
         if self._target_host:
-            self._target_host.cleanup()
             self._target_host = None
 
     def child_kwargs(self):
@@ -430,7 +408,7 @@
         proc.stdout = stdout_pipe
 
         # Run symbolizer to filter the stderr stream.
-        self._symbolizer_proc = symbolizer.RunSymbolizer(
+        self._symbolizer_proc = self._port.run_symbolizer(
             merged_stdout_stderr, subprocess.PIPE,
             [self._port.get_build_ids_path()])
         proc.stderr = self._symbolizer_proc.stdout
diff --git a/third_party/blink/tools/blinkpy/web_tests/run_web_tests.py b/third_party/blink/tools/blinkpy/web_tests/run_web_tests.py
index 74ebaffe..03b42738 100644
--- a/third_party/blink/tools/blinkpy/web_tests/run_web_tests.py
+++ b/third_party/blink/tools/blinkpy/web_tests/run_web_tests.py
@@ -167,17 +167,9 @@
             '--fuchsia-ssh-config',
             help=('The path to the SSH configuration used for '
                   'connecting to the target device.')),
-        optparse.make_option('--fuchsia-host',
-                             help=('The IP of the target device. Optional.')),
-        optparse.make_option(
-            '--fuchsia-port',
-            type=int,
-            default=None,
-            help=('The port of the SSH service running on the '
-                  'device. Optional.')),
-        optparse.make_option('--fuchsia-node-name',
+        optparse.make_option('--fuchsia-target-id',
                              help=('The node-name of the device to boot or '
-                                   'deploy to. Optional')),
+                                   'deploy to.')),
         optparse.make_option(
             '--fuchsia-host-ip',
             help=('The IP address of the test host observed by the Fuchsia '
diff --git a/third_party/blink/tools/diff_wpt_results.py b/third_party/blink/tools/diff_wpt_results.py
index 8f18ffa..9ef31de 100755
--- a/third_party/blink/tools/diff_wpt_results.py
+++ b/third_party/blink/tools/diff_wpt_results.py
@@ -48,12 +48,14 @@
 # and rwt/content_shell on Linux
 PRODUCTS = PRODUCTS + [
     'chrome_linux', 'content_shell', 'wpt_content_shell_linux',
-    'wpt_content_shell_win10', 'wpt_content_shell_mac12'
+    'wpt_content_shell_win10', 'wpt_content_shell_win11',
+    'wpt_content_shell_mac12'
 ]
 PRODUCTS_TO_STEPNAMES.update({
     'chrome_linux': 'wpt_tests_suite',
     'wpt_content_shell_linux': 'wpt_tests_suite_linux',
     'wpt_content_shell_win10': 'wpt_tests_suite_win10',
+    'wpt_content_shell_win11': 'wpt_tests_suite_win11',
     'wpt_content_shell_mac12': 'wpt_tests_suite_mac12',
     'content_shell': 'blink_wpt_tests'
 })
@@ -63,10 +65,10 @@
     'chrome_linux': 'linux-wpt-fyi-rel',
     'wpt_content_shell_linux': 'linux-wpt-content-shell-fyi-rel',
     'wpt_content_shell_win10': 'win10-wpt-content-shell-fyi-rel',
+    'wpt_content_shell_win11': 'win11-wpt-content-shell-fyi-rel',
     'wpt_content_shell_mac12': 'mac-12-wpt-fyi-rel',
     'content_shell': "Linux Tests"
 }
-
 STEP_NAME_VARIANTS = {
     'chrome_public_wpt': ['chrome_public_wpt on Ubuntu-16.04 or Ubuntu-18.04'],
     'system_webview_wpt':
@@ -75,6 +77,8 @@
     ['wpt_tests_suite (experimental) on Ubuntu-18.04'],
     'wpt_tests_suite_win10':
     ['wpt_tests_suite (experimental) on Windows-10-19042'],
+    'wpt_tests_suite_win11':
+    ['wpt_tests_suite (experimental) on Windows-11-22000'],
     'wpt_tests_suite_mac12': ['wpt_tests_suite (experimental) on Mac-12'],
     'blink_wpt_tests': ['blink_wpt_tests on Ubuntu-18.04']
 }
diff --git a/third_party/blink/web_tests/FlagExpectations/disable-layout-ng b/third_party/blink/web_tests/FlagExpectations/disable-layout-ng
index 96212367..94959d0 100644
--- a/third_party/blink/web_tests/FlagExpectations/disable-layout-ng
+++ b/third_party/blink/web_tests/FlagExpectations/disable-layout-ng
@@ -11,6 +11,7 @@
 # Tests that fail in legacy but pass in NG
 
 # ====== New tests from wpt-importer added here ======
+crbug.com/626703 external/wpt/html/semantics/forms/the-selectmenu-element/selectmenu-option-arbitrary-content-displayed.tentative.html [ Failure ]
 crbug.com/626703 external/wpt/fullscreen/api/document-exit-fullscreen-nested-in-iframe.html [ Timeout ]
 crbug.com/626703 external/wpt/css/css-sizing/aspect-ratio/replaced-element-039.html [ Failure ]
 crbug.com/626703 external/wpt/css/css-sizing/aspect-ratio/replaced-element-040.html [ Failure ]
@@ -3399,6 +3400,7 @@
 external/wpt/html/semantics/popovers/popover-anchor-display.tentative.html [ Skip ]
 external/wpt/html/semantics/popovers/popover-anchor-multicol-display.tentative.html [ Skip ]
 external/wpt/html/semantics/popovers/popover-anchor-scroll-display.tentative.html [ Skip ]
+external/wpt/html/semantics/popovers/popover-anchor-nested-display.tentative.html [ Skip ]
 
 crbug.com/1303102 external/wpt/css/css-images/object-view-box* [ Skip ]
 
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index a0347a4..5be8555 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -3024,219 +3024,6 @@
 crbug.com/626703 external/wpt/geolocation-API/enabled-by-permission-policy-attribute.https.sub.html [ Timeout ]
 crbug.com/626703 external/wpt/geolocation-API/enabled-by-permissions-policy.https.sub.html [ Timeout ]
 crbug.com/626703 external/wpt/geolocation-API/enabled-on-self-origin-by-permissions-policy.https.sub.html [ Timeout ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/adapter/adapter-absent-getAvailability.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/adapter/adapter-added-getAvailability.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/adapter/adapter-powered-off-getAvailability.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/adapter/adapter-powered-on-getAvailability.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/adapter/adapter-powered-on-off-on-getAvailability.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/adapter/adapter-removed-getAvailability.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/adapter/cross-origin-iframe-getAvailability.sub.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/characteristic/characteristicProperties.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/characteristic/getDescriptor/detachedIframe.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/characteristic/getDescriptor/gen-characteristic-is-removed.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/characteristic/getDescriptor/gen-descriptor-get-same-object.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/characteristic/getDescriptor/gen-service-is-removed.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/characteristic/getDescriptors/detachedIframe.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/characteristic/getDescriptors/gen-characteristic-is-removed-with-uuid.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/characteristic/getDescriptors/gen-characteristic-is-removed.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/characteristic/getDescriptors/gen-descriptor-get-same-object.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/characteristic/getDescriptors/gen-service-is-removed-with-uuid.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/characteristic/getDescriptors/gen-service-is-removed.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/characteristic/notifications/characteristic-is-removed.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/characteristic/notifications/service-is-removed.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/characteristic/readValue/add-multiple-event-listeners.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/characteristic/readValue/characteristic-is-removed.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/characteristic/readValue/detachedIframe.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/characteristic/readValue/event-is-fired.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/characteristic/readValue/gen-characteristic-is-removed.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/characteristic/readValue/read-succeeds.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/characteristic/readValue/read-updates-value.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/characteristic/readValue/service-is-removed.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/characteristic/service-same-from-2-characteristics.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/characteristic/service-same-object.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/characteristic/startNotifications/detachedIframe.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/characteristic/startNotifications/gen-characteristic-is-removed.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/characteristic/stopNotifications/detachedIframe.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/characteristic/writeValue/buffer-is-detached.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/characteristic/writeValue/characteristic-is-removed.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/characteristic/writeValue/detachedIframe.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/characteristic/writeValue/gen-characteristic-is-removed.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/characteristic/writeValue/service-is-removed.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/characteristic/writeValue/write-succeeds.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/characteristic/writeValueWithResponse/buffer-is-detached.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/characteristic/writeValueWithResponse/characteristic-is-removed.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/characteristic/writeValueWithResponse/gen-characteristic-is-removed.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/characteristic/writeValueWithResponse/service-is-removed.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/characteristic/writeValueWithResponse/write-succeeds.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/characteristic/writeValueWithoutResponse/buffer-is-detached.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/characteristic/writeValueWithoutResponse/characteristic-is-removed.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/characteristic/writeValueWithoutResponse/gen-characteristic-is-removed.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/characteristic/writeValueWithoutResponse/service-is-removed.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/characteristic/writeValueWithoutResponse/write-succeeds.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/descriptor/readValue/detachedIframe.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/descriptor/readValue/gen-service-is-removed.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/descriptor/readValue/read-succeeds.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/descriptor/writeValue/buffer-is-detached.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/descriptor/writeValue/detachedIframe.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/descriptor/writeValue/gen-service-is-removed.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/device/forget/connect-after-forget.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/device/forget/detachedIframe.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/device/forget/getDevices.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/device/gattserverdisconnected-event/disconnected.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/device/gattserverdisconnected-event/disconnected_gc.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/device/gattserverdisconnected-event/one-event-per-disconnection.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/device/gattserverdisconnected-event/reconnect-during-disconnected-event.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/device/watchAdvertisements/abort-before-watchAdvertisements.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/device/watchAdvertisements/abort-pending-operation.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/device/watchAdvertisements/abort-signal-stops-events.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/device/watchAdvertisements/abort-subsequent-watchAdvertisements-call-stops-events.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/device/watchAdvertisements/advertisementreceived-event-fired.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/device/watchAdvertisements/blocklisted-manufacturer-data-filtered-from-event.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/device/watchAdvertisements/concurrent-watchAdvertisements-calls.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/device/watchAdvertisements/detachedIframe.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/device/watchAdvertisements/service-and-manufacturer-data-filtered-from-event.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/device/watchAdvertisements/subsequent-watchAdvertisements-call.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/device/watchAdvertisements/watching-two-devices-abort-one-watchAdvertisements.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/device/watchAdvertisements/watching-two-devices.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/getDevices/granted-devices-with-services.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/getDevices/no-granted-devices.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/getDevices/returns-same-bluetooth-device-object.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/idl/idl-BluetoothDevice.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/requestDevice/acceptAllDevices/device-with-empty-name.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/requestDevice/acceptAllDevices/device-with-name.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/requestDevice/acceptAllDevices/optional-services-missing.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/requestDevice/acceptAllDevices/optional-services-present.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/requestDevice/blocklisted-manufacturer-data-in-filter.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/requestDevice/blocklisted-service-in-filter.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/requestDevice/blocklisted-service-in-optionalServices.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/requestDevice/canonicalizeFilter/data-prefix-and-mask-size.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/requestDevice/canonicalizeFilter/dataPrefix-buffer-is-detached.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/requestDevice/canonicalizeFilter/device-name-longer-than-29-bytes.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/requestDevice/canonicalizeFilter/empty-dataPrefix.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/requestDevice/canonicalizeFilter/empty-filter.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/requestDevice/canonicalizeFilter/empty-filters-member.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/requestDevice/canonicalizeFilter/empty-manufacturerData-member.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/requestDevice/canonicalizeFilter/empty-namePrefix.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/requestDevice/canonicalizeFilter/empty-services-member.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/requestDevice/canonicalizeFilter/filters-xor-acceptAllDevices.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/requestDevice/canonicalizeFilter/invalid-companyIdentifier.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/requestDevice/canonicalizeFilter/mask-buffer-is-detached.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/requestDevice/canonicalizeFilter/max-length-exceeded-name-unicode.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/requestDevice/canonicalizeFilter/max-length-exceeded-name.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/requestDevice/canonicalizeFilter/max-length-exceeded-namePrefix-unicode.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/requestDevice/canonicalizeFilter/max-length-exceeded-namePrefix.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/requestDevice/canonicalizeFilter/max-length-name-unicode.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/requestDevice/canonicalizeFilter/max-length-name.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/requestDevice/canonicalizeFilter/max-length-namePrefix-unicode.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/requestDevice/canonicalizeFilter/max-length-namePrefix.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/requestDevice/canonicalizeFilter/same-company-identifier.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/requestDevice/canonicalizeFilter/unicode-valid-length-name-name.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/requestDevice/canonicalizeFilter/unicode-valid-length-name-namePrefix.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/requestDevice/canonicalizeFilter/wrong-service-in-optionalServices-member.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/requestDevice/canonicalizeFilter/wrong-service-in-services-member.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/requestDevice/cross-origin-iframe.sub.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/requestDevice/discovery-succeeds.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/requestDevice/doesnt-consume-user-gesture.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/requestDevice/filter-matches.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/requestDevice/le-not-supported.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/requestDevice/manufacturer-data-filter-matches.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/requestDevice/name-empty-device-from-name-empty-filter.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/requestDevice/not-processing-user-gesture.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/requestDevice/radio-not-present.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/requestDevice/request-from-iframe.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/requestDevice/request-from-sandboxed-iframe.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/requestDevice/same-device.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/requestDevice/single-filter-single-service.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/connect/connection-succeeds.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/connect/detachedIframe.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/connect/garbage-collection-ran-during-success.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/connect/get-same-gatt-server.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/device-same-object.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/disconnect/connect-disconnect-twice.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/disconnect/detach-gc.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/disconnect/detachedIframe.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/disconnect/disconnect-twice-in-a-row.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/disconnect/gc-detach.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/getPrimaryService/gen-disconnect-called-before.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/getPrimaryService/gen-disconnect-called-during-error.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/getPrimaryService/gen-disconnect-called-during-success.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/getPrimaryService/gen-disconnect-discovery-timeout.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/getPrimaryService/gen-disconnect-invalidates-objects.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/getPrimaryService/gen-disconnected-device.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/getPrimaryService/gen-discovery-complete-no-permission-absent-service.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/getPrimaryService/gen-discovery-complete-service-not-found.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/getPrimaryService/gen-garbage-collection-ran-during-error.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/getPrimaryService/gen-garbage-collection-ran-during-success.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/getPrimaryService/gen-get-different-service-after-reconnection.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/getPrimaryService/gen-get-same-object.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/getPrimaryService/gen-invalid-service-name.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/getPrimaryService/gen-no-permission-absent-service.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/getPrimaryService/gen-no-permission-for-any-service.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/getPrimaryService/gen-no-permission-present-service.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/getPrimaryService/gen-service-not-found.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/getPrimaryService/service-found.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/getPrimaryService/two-iframes-from-same-origin.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/getPrimaryServices/blocklisted-services-with-uuid.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/getPrimaryServices/blocklisted-services.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/getPrimaryServices/correct-services.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/getPrimaryServices/gen-disconnect-called-before-with-uuid.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/getPrimaryServices/gen-disconnect-called-before.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/getPrimaryServices/gen-disconnect-called-during-error-with-uuid.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/getPrimaryServices/gen-disconnect-called-during-error.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/getPrimaryServices/gen-disconnect-called-during-success-with-uuid.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/getPrimaryServices/gen-disconnect-called-during-success.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/getPrimaryServices/gen-disconnect-discovery-timeout-with-uuid.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/getPrimaryServices/gen-disconnect-discovery-timeout.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/getPrimaryServices/gen-disconnect-invalidates-objects-with-uuid.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/getPrimaryServices/gen-disconnect-invalidates-objects.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/getPrimaryServices/gen-disconnected-device-with-uuid.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/getPrimaryServices/gen-disconnected-device.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/getPrimaryServices/gen-discovery-complete-no-permission-absent-service-with-uuid.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/getPrimaryServices/gen-discovery-complete-service-not-found-with-uuid.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/getPrimaryServices/gen-garbage-collection-ran-during-error-with-uuid.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/getPrimaryServices/gen-garbage-collection-ran-during-error.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/getPrimaryServices/gen-garbage-collection-ran-during-success-with-uuid.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/getPrimaryServices/gen-garbage-collection-ran-during-success.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/getPrimaryServices/gen-get-different-service-after-reconnection-with-uuid.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/getPrimaryServices/gen-get-different-service-after-reconnection.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/getPrimaryServices/gen-get-same-object-with-uuid.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/getPrimaryServices/gen-get-same-object.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/getPrimaryServices/gen-invalid-service-name.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/getPrimaryServices/gen-no-permission-absent-service-with-uuid.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/getPrimaryServices/gen-no-permission-for-any-service-with-uuid.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/getPrimaryServices/gen-no-permission-for-any-service.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/getPrimaryServices/gen-no-permission-present-service-with-uuid.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/getPrimaryServices/gen-service-not-found-with-uuid.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/getPrimaryServices/services-found-with-uuid.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/getPrimaryServices/services-found.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/server/getPrimaryServices/services-not-found.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/service/detachedIframe.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/service/device-same-from-2-services.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/service/device-same-object.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/service/getCharacteristic/characteristic-found.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/service/getCharacteristic/detachedIframe.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/service/getCharacteristic/gen-blocklisted-characteristic.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/service/getCharacteristic/gen-characteristic-not-found.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/service/getCharacteristic/gen-garbage-collection-ran-during-error.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/service/getCharacteristic/gen-get-same-object.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/service/getCharacteristic/gen-invalid-characteristic-name.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/service/getCharacteristic/gen-reconnect-during.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/service/getCharacteristic/gen-service-is-removed.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/service/getCharacteristics/blocklisted-characteristics.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/service/getCharacteristics/characteristics-found-with-uuid.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/service/getCharacteristics/characteristics-found.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/service/getCharacteristics/characteristics-not-found.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/service/getCharacteristics/gen-blocklisted-characteristic-with-uuid.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/service/getCharacteristics/gen-characteristic-not-found-with-uuid.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/service/getCharacteristics/gen-garbage-collection-ran-during-error-with-uuid.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/service/getCharacteristics/gen-garbage-collection-ran-during-error.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/service/getCharacteristics/gen-get-same-object-with-uuid.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/service/getCharacteristics/gen-get-same-object.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/service/getCharacteristics/gen-invalid-characteristic-name.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/service/getCharacteristics/gen-reconnect-during-with-uuid.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/service/getCharacteristics/gen-reconnect-during.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/service/getCharacteristics/gen-service-is-removed-with-uuid.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] external/wpt/bluetooth/service/getCharacteristics/gen-service-is-removed.https.window.html [ Crash ]
 crbug.com/626703 [ Linux ] external/wpt/serial/serialPort_close.https.any.html [ Timeout ]
 crbug.com/626703 [ Linux ] external/wpt/serial/serialPort_close.https.any.worker.html [ Timeout ]
 crbug.com/626703 [ Linux ] external/wpt/serial/serialPort_getSignals.https.any.html [ Timeout ]
@@ -3403,507 +3190,6 @@
 crbug.com/626703 [ Linux ] virtual/abort-signal-remove/external/wpt/web-nfc/NDEFReader_scan_iframe.https.html [ Timeout ]
 crbug.com/626703 [ Linux ] virtual/abort-signal-remove/external/wpt/web-nfc/NDEFReader_write.https.html [ Timeout ]
 crbug.com/626703 [ Linux ] virtual/feature-policy-permissions/external/wpt/mediacapture-streams/MediaDevices-enumerateDevices-per-origin-ids.sub.https.html [ Timeout ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/adapter/adapter-absent-getAvailability.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/adapter/adapter-added-getAvailability.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/adapter/adapter-powered-off-getAvailability.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/adapter/adapter-powered-on-getAvailability.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/adapter/adapter-powered-on-off-on-getAvailability.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/adapter/adapter-removed-getAvailability.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/adapter/cross-origin-iframe-getAvailability.sub.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/characteristic/characteristicProperties.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/characteristic/getDescriptor/detachedIframe.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/characteristic/getDescriptor/gen-characteristic-is-removed.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/characteristic/getDescriptor/gen-descriptor-get-same-object.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/characteristic/getDescriptor/gen-service-is-removed.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/characteristic/getDescriptors/detachedIframe.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/characteristic/getDescriptors/gen-characteristic-is-removed-with-uuid.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/characteristic/getDescriptors/gen-characteristic-is-removed.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/characteristic/getDescriptors/gen-descriptor-get-same-object.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/characteristic/getDescriptors/gen-service-is-removed-with-uuid.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/characteristic/getDescriptors/gen-service-is-removed.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/characteristic/notifications/characteristic-is-removed.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/characteristic/notifications/service-is-removed.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/characteristic/readValue/add-multiple-event-listeners.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/characteristic/readValue/characteristic-is-removed.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/characteristic/readValue/detachedIframe.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/characteristic/readValue/event-is-fired.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/characteristic/readValue/gen-characteristic-is-removed.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/characteristic/readValue/read-succeeds.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/characteristic/readValue/read-updates-value.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/characteristic/readValue/service-is-removed.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/characteristic/service-same-from-2-characteristics.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/characteristic/service-same-object.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/characteristic/startNotifications/detachedIframe.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/characteristic/startNotifications/gen-characteristic-is-removed.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/characteristic/stopNotifications/detachedIframe.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/characteristic/writeValue/buffer-is-detached.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/characteristic/writeValue/characteristic-is-removed.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/characteristic/writeValue/detachedIframe.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/characteristic/writeValue/gen-characteristic-is-removed.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/characteristic/writeValue/service-is-removed.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/characteristic/writeValue/write-succeeds.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/characteristic/writeValueWithResponse/buffer-is-detached.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/characteristic/writeValueWithResponse/characteristic-is-removed.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/characteristic/writeValueWithResponse/gen-characteristic-is-removed.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/characteristic/writeValueWithResponse/service-is-removed.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/characteristic/writeValueWithResponse/write-succeeds.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/characteristic/writeValueWithoutResponse/buffer-is-detached.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/characteristic/writeValueWithoutResponse/characteristic-is-removed.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/characteristic/writeValueWithoutResponse/gen-characteristic-is-removed.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/characteristic/writeValueWithoutResponse/service-is-removed.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/characteristic/writeValueWithoutResponse/write-succeeds.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/descriptor/readValue/detachedIframe.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/descriptor/readValue/gen-service-is-removed.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/descriptor/readValue/read-succeeds.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/descriptor/writeValue/buffer-is-detached.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/descriptor/writeValue/detachedIframe.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/descriptor/writeValue/gen-service-is-removed.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/device/forget/connect-after-forget.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/device/forget/detachedIframe.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/device/forget/getDevices.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/device/gattserverdisconnected-event/disconnected.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/device/gattserverdisconnected-event/disconnected_gc.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/device/gattserverdisconnected-event/one-event-per-disconnection.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/device/gattserverdisconnected-event/reconnect-during-disconnected-event.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/device/watchAdvertisements/abort-before-watchAdvertisements.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/device/watchAdvertisements/abort-pending-operation.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/device/watchAdvertisements/abort-signal-stops-events.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/device/watchAdvertisements/abort-subsequent-watchAdvertisements-call-stops-events.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/device/watchAdvertisements/advertisementreceived-event-fired.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/device/watchAdvertisements/blocklisted-manufacturer-data-filtered-from-event.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/device/watchAdvertisements/concurrent-watchAdvertisements-calls.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/device/watchAdvertisements/detachedIframe.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/device/watchAdvertisements/service-and-manufacturer-data-filtered-from-event.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/device/watchAdvertisements/subsequent-watchAdvertisements-call.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/device/watchAdvertisements/watching-two-devices-abort-one-watchAdvertisements.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/device/watchAdvertisements/watching-two-devices.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/getDevices/granted-devices-with-services.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/getDevices/no-granted-devices.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/getDevices/returns-same-bluetooth-device-object.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/idl/idl-BluetoothDevice.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/requestDevice/acceptAllDevices/device-with-empty-name.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/requestDevice/acceptAllDevices/device-with-name.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/requestDevice/acceptAllDevices/optional-services-missing.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/requestDevice/acceptAllDevices/optional-services-present.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/requestDevice/blocklisted-manufacturer-data-in-filter.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/requestDevice/blocklisted-service-in-filter.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/requestDevice/blocklisted-service-in-optionalServices.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/requestDevice/canonicalizeFilter/data-prefix-and-mask-size.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/requestDevice/canonicalizeFilter/dataPrefix-buffer-is-detached.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/requestDevice/canonicalizeFilter/device-name-longer-than-29-bytes.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/requestDevice/canonicalizeFilter/empty-dataPrefix.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/requestDevice/canonicalizeFilter/empty-filter.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/requestDevice/canonicalizeFilter/empty-filters-member.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/requestDevice/canonicalizeFilter/empty-manufacturerData-member.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/requestDevice/canonicalizeFilter/empty-namePrefix.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/requestDevice/canonicalizeFilter/empty-services-member.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/requestDevice/canonicalizeFilter/filters-xor-acceptAllDevices.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/requestDevice/canonicalizeFilter/invalid-companyIdentifier.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/requestDevice/canonicalizeFilter/mask-buffer-is-detached.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/requestDevice/canonicalizeFilter/max-length-exceeded-name-unicode.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/requestDevice/canonicalizeFilter/max-length-exceeded-name.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/requestDevice/canonicalizeFilter/max-length-exceeded-namePrefix-unicode.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/requestDevice/canonicalizeFilter/max-length-exceeded-namePrefix.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/requestDevice/canonicalizeFilter/max-length-name-unicode.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/requestDevice/canonicalizeFilter/max-length-name.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/requestDevice/canonicalizeFilter/max-length-namePrefix-unicode.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/requestDevice/canonicalizeFilter/max-length-namePrefix.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/requestDevice/canonicalizeFilter/same-company-identifier.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/requestDevice/canonicalizeFilter/unicode-valid-length-name-name.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/requestDevice/canonicalizeFilter/unicode-valid-length-name-namePrefix.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/requestDevice/canonicalizeFilter/wrong-service-in-optionalServices-member.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/requestDevice/canonicalizeFilter/wrong-service-in-services-member.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/requestDevice/cross-origin-iframe.sub.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/requestDevice/discovery-succeeds.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/requestDevice/doesnt-consume-user-gesture.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/requestDevice/filter-matches.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/requestDevice/le-not-supported.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/requestDevice/manufacturer-data-filter-matches.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/requestDevice/name-empty-device-from-name-empty-filter.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/requestDevice/not-processing-user-gesture.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/requestDevice/radio-not-present.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/requestDevice/request-from-iframe.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/requestDevice/request-from-sandboxed-iframe.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/requestDevice/same-device.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/requestDevice/single-filter-single-service.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/connect/connection-succeeds.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/connect/detachedIframe.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/connect/garbage-collection-ran-during-success.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/connect/get-same-gatt-server.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/device-same-object.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/disconnect/connect-disconnect-twice.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/disconnect/detach-gc.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/disconnect/detachedIframe.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/disconnect/disconnect-twice-in-a-row.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/disconnect/gc-detach.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/getPrimaryService/gen-disconnect-called-before.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/getPrimaryService/gen-disconnect-called-during-error.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/getPrimaryService/gen-disconnect-called-during-success.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/getPrimaryService/gen-disconnect-discovery-timeout.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/getPrimaryService/gen-disconnect-invalidates-objects.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/getPrimaryService/gen-disconnected-device.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/getPrimaryService/gen-discovery-complete-no-permission-absent-service.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/getPrimaryService/gen-discovery-complete-service-not-found.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/getPrimaryService/gen-garbage-collection-ran-during-error.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/getPrimaryService/gen-garbage-collection-ran-during-success.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/getPrimaryService/gen-get-different-service-after-reconnection.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/getPrimaryService/gen-get-same-object.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/getPrimaryService/gen-invalid-service-name.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/getPrimaryService/gen-no-permission-absent-service.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/getPrimaryService/gen-no-permission-for-any-service.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/getPrimaryService/gen-no-permission-present-service.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/getPrimaryService/gen-service-not-found.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/getPrimaryService/service-found.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/getPrimaryService/two-iframes-from-same-origin.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/getPrimaryServices/blocklisted-services-with-uuid.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/getPrimaryServices/blocklisted-services.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/getPrimaryServices/correct-services.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/getPrimaryServices/gen-disconnect-called-before-with-uuid.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/getPrimaryServices/gen-disconnect-called-before.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/getPrimaryServices/gen-disconnect-called-during-error-with-uuid.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/getPrimaryServices/gen-disconnect-called-during-error.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/getPrimaryServices/gen-disconnect-called-during-success-with-uuid.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/getPrimaryServices/gen-disconnect-called-during-success.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/getPrimaryServices/gen-disconnect-discovery-timeout-with-uuid.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/getPrimaryServices/gen-disconnect-discovery-timeout.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/getPrimaryServices/gen-disconnect-invalidates-objects-with-uuid.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/getPrimaryServices/gen-disconnect-invalidates-objects.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/getPrimaryServices/gen-disconnected-device-with-uuid.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/getPrimaryServices/gen-disconnected-device.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/getPrimaryServices/gen-discovery-complete-no-permission-absent-service-with-uuid.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/getPrimaryServices/gen-discovery-complete-service-not-found-with-uuid.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/getPrimaryServices/gen-garbage-collection-ran-during-error-with-uuid.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/getPrimaryServices/gen-garbage-collection-ran-during-error.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/getPrimaryServices/gen-garbage-collection-ran-during-success-with-uuid.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/getPrimaryServices/gen-garbage-collection-ran-during-success.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/getPrimaryServices/gen-get-different-service-after-reconnection-with-uuid.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/getPrimaryServices/gen-get-different-service-after-reconnection.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/getPrimaryServices/gen-get-same-object-with-uuid.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/getPrimaryServices/gen-get-same-object.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/getPrimaryServices/gen-invalid-service-name.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/getPrimaryServices/gen-no-permission-absent-service-with-uuid.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/getPrimaryServices/gen-no-permission-for-any-service-with-uuid.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/getPrimaryServices/gen-no-permission-for-any-service.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/getPrimaryServices/gen-no-permission-present-service-with-uuid.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/getPrimaryServices/gen-service-not-found-with-uuid.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/getPrimaryServices/services-found-with-uuid.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/getPrimaryServices/services-found.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/server/getPrimaryServices/services-not-found.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/service/detachedIframe.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/service/device-same-from-2-services.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/service/device-same-object.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/service/getCharacteristic/characteristic-found.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/service/getCharacteristic/detachedIframe.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/service/getCharacteristic/gen-blocklisted-characteristic.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/service/getCharacteristic/gen-characteristic-not-found.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/service/getCharacteristic/gen-garbage-collection-ran-during-error.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/service/getCharacteristic/gen-get-same-object.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/service/getCharacteristic/gen-invalid-characteristic-name.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/service/getCharacteristic/gen-reconnect-during.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/service/getCharacteristic/gen-service-is-removed.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/service/getCharacteristics/blocklisted-characteristics.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/service/getCharacteristics/characteristics-found-with-uuid.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/service/getCharacteristics/characteristics-found.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/service/getCharacteristics/characteristics-not-found.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/service/getCharacteristics/gen-blocklisted-characteristic-with-uuid.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/service/getCharacteristics/gen-characteristic-not-found-with-uuid.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/service/getCharacteristics/gen-garbage-collection-ran-during-error-with-uuid.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/service/getCharacteristics/gen-garbage-collection-ran-during-error.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/service/getCharacteristics/gen-get-same-object-with-uuid.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/service/getCharacteristics/gen-get-same-object.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/service/getCharacteristics/gen-invalid-characteristic-name.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/service/getCharacteristics/gen-reconnect-during-with-uuid.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/service/getCharacteristics/gen-reconnect-during.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/service/getCharacteristics/gen-service-is-removed-with-uuid.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/service/getCharacteristics/gen-service-is-removed.https.window.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/getDescriptor/descriptor-not-found.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/getDescriptor/gen-descriptor-blocklisted.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/getDescriptor/gen-descriptor-device-disconnects-before.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/getDescriptor/gen-descriptor-device-disconnects-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/getDescriptor/gen-descriptor-device-disconnects-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/getDescriptor/gen-descriptor-disconnect-called-before.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/getDescriptor/gen-descriptor-disconnect-called-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/getDescriptor/gen-descriptor-disconnect-called-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/getDescriptor/gen-descriptor-garbage-collection-ran-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/getDescriptor/gen-descriptor-garbage-collection-ran-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/getDescriptor/gen-descriptor-invalid-name.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/getDescriptor/gen-device-out-of-range.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/getDescriptors/blocklisted-descriptors-not-present.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/getDescriptors/gen-descriptor-blocklisted.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/getDescriptors/gen-descriptor-device-disconnects-before-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/getDescriptors/gen-descriptor-device-disconnects-before.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/getDescriptors/gen-descriptor-device-disconnects-during-error-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/getDescriptors/gen-descriptor-device-disconnects-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/getDescriptors/gen-descriptor-device-disconnects-during-success-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/getDescriptors/gen-descriptor-device-disconnects-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/getDescriptors/gen-descriptor-disconnect-called-before-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/getDescriptors/gen-descriptor-disconnect-called-before.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/getDescriptors/gen-descriptor-disconnect-called-during-error-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/getDescriptors/gen-descriptor-disconnect-called-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/getDescriptors/gen-descriptor-disconnect-called-during-success-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/getDescriptors/gen-descriptor-disconnect-called-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/getDescriptors/gen-descriptor-garbage-collection-ran-during-error-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/getDescriptors/gen-descriptor-garbage-collection-ran-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/getDescriptors/gen-descriptor-garbage-collection-ran-during-success-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/getDescriptors/gen-descriptor-garbage-collection-ran-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/getDescriptors/gen-descriptor-invalid-name.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/getDescriptors/gen-device-out-of-range-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/getDescriptors/gen-device-out-of-range.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/notifications/add-listener-after-promise.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/notifications/add-multiple-event-listeners.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/notifications/characteristic-does-not-support-notifications.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/notifications/concurrent-starts-and-receive-notification.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/notifications/concurrent-starts.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/notifications/concurrent-stops.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/notifications/device-goes-out-of-range.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/notifications/event-after-starting.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/notifications/gc-with-event-listener.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/notifications/gc-with-pending-start.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/notifications/gc-with-pending-stop.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/notifications/notification-after-disconnection.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/notifications/parallel-start-stop.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/notifications/start-before-stop-resolves.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/notifications/start-fails.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/notifications/start-stop-start-stop.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/notifications/start-succeeds.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/notifications/start-twice-in-a-row.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/notifications/stop-after-start-succeeds.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/notifications/stop-twice.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/notifications/stop-without-starting.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/readValue/blocklisted-characteristic.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/readValue/device-goes-out-of-range.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/readValue/gen-gatt-op-device-disconnects-before.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/readValue/gen-gatt-op-device-disconnects-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/readValue/gen-gatt-op-device-disconnects-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/readValue/gen-gatt-op-device-reconnects-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/readValue/gen-gatt-op-device-reconnects-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/readValue/gen-gatt-op-disconnect-called-before.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/readValue/gen-gatt-op-disconnect-called-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/readValue/gen-gatt-op-disconnect-called-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/readValue/gen-gatt-op-garbage-collection-ran-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/readValue/gen-gatt-op-garbage-collection-ran-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/readValue/gen-gatt-op-reconnect-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/readValue/gen-gatt-op-reconnect-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/readValue/read-fails.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/startNotifications/gen-gatt-op-device-disconnects-before.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/startNotifications/gen-gatt-op-device-disconnects-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/startNotifications/gen-gatt-op-device-disconnects-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/startNotifications/gen-gatt-op-device-reconnects-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/startNotifications/gen-gatt-op-device-reconnects-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/startNotifications/gen-gatt-op-disconnect-called-before.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/startNotifications/gen-gatt-op-disconnect-called-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/startNotifications/gen-gatt-op-disconnect-called-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/startNotifications/gen-gatt-op-garbage-collection-ran-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/startNotifications/gen-gatt-op-garbage-collection-ran-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/startNotifications/gen-gatt-op-reconnect-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/startNotifications/gen-gatt-op-reconnect-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/stopNotifications/device-reconnects-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/stopNotifications/gen-gatt-op-device-disconnects-before.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/stopNotifications/gen-gatt-op-device-disconnects-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/stopNotifications/gen-gatt-op-disconnect-called-before.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/stopNotifications/gen-gatt-op-disconnect-called-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/stopNotifications/gen-gatt-op-garbage-collection-ran-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/stopNotifications/reconnect-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/writeValue/blocklisted-characteristic.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/writeValue/device-goes-out-of-range.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/writeValue/gen-gatt-op-device-disconnects-before.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/writeValue/gen-gatt-op-device-disconnects-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/writeValue/gen-gatt-op-device-disconnects-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/writeValue/gen-gatt-op-device-reconnects-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/writeValue/gen-gatt-op-device-reconnects-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/writeValue/gen-gatt-op-disconnect-called-before.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/writeValue/gen-gatt-op-disconnect-called-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/writeValue/gen-gatt-op-disconnect-called-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/writeValue/gen-gatt-op-garbage-collection-ran-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/writeValue/gen-gatt-op-garbage-collection-ran-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/writeValue/gen-gatt-op-reconnect-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/writeValue/gen-gatt-op-reconnect-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/writeValue/value-too-long.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/writeValue/write-fails.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/writeValue/write-updates-value.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/writeValueWithResponse/blocklisted-characteristic.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/writeValueWithResponse/device-goes-out-of-range.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/writeValueWithResponse/gen-gatt-op-device-disconnects-before.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/writeValueWithResponse/gen-gatt-op-device-disconnects-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/writeValueWithResponse/gen-gatt-op-device-disconnects-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/writeValueWithResponse/gen-gatt-op-device-reconnects-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/writeValueWithResponse/gen-gatt-op-device-reconnects-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/writeValueWithResponse/gen-gatt-op-disconnect-called-before.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/writeValueWithResponse/gen-gatt-op-disconnect-called-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/writeValueWithResponse/gen-gatt-op-disconnect-called-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/writeValueWithResponse/gen-gatt-op-garbage-collection-ran-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/writeValueWithResponse/gen-gatt-op-garbage-collection-ran-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/writeValueWithResponse/gen-gatt-op-reconnect-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/writeValueWithResponse/gen-gatt-op-reconnect-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/writeValueWithResponse/value-too-long.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/writeValueWithResponse/write-fails.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/writeValueWithResponse/write-updates-value.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/writeValueWithoutResponse/blocklisted-characteristic.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/writeValueWithoutResponse/device-goes-out-of-range.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/writeValueWithoutResponse/gen-gatt-op-device-disconnects-before.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/writeValueWithoutResponse/gen-gatt-op-device-disconnects-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/writeValueWithoutResponse/gen-gatt-op-device-disconnects-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/writeValueWithoutResponse/gen-gatt-op-device-reconnects-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/writeValueWithoutResponse/gen-gatt-op-device-reconnects-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/writeValueWithoutResponse/gen-gatt-op-disconnect-called-before.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/writeValueWithoutResponse/gen-gatt-op-disconnect-called-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/writeValueWithoutResponse/gen-gatt-op-disconnect-called-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/writeValueWithoutResponse/gen-gatt-op-garbage-collection-ran-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/writeValueWithoutResponse/gen-gatt-op-garbage-collection-ran-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/writeValueWithoutResponse/gen-gatt-op-reconnect-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/writeValueWithoutResponse/gen-gatt-op-reconnect-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/writeValueWithoutResponse/value-too-long.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/writeValueWithoutResponse/write-fails.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/characteristic/writeValueWithoutResponse/write-updates-value.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/descriptor/characteristic-same-from-2-descriptors.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/descriptor/characteristic-same-object.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/descriptor/readValue/descriptor-is-blocklisted.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/descriptor/readValue/gen-descriptor-is-removed.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/descriptor/readValue/gen-device-goes-out-of-range.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/descriptor/readValue/gen-io-op-device-disconnects-before.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/descriptor/readValue/gen-io-op-device-disconnects-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/descriptor/readValue/gen-io-op-device-disconnects-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/descriptor/readValue/gen-io-op-device-reconnects-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/descriptor/readValue/gen-io-op-device-reconnects-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/descriptor/readValue/gen-io-op-disconnect-called-before.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/descriptor/readValue/gen-io-op-disconnect-called-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/descriptor/readValue/gen-io-op-disconnect-called-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/descriptor/readValue/gen-io-op-fails.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/descriptor/readValue/gen-io-op-garbage-collection-ran-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/descriptor/readValue/gen-io-op-garbage-collection-ran-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/descriptor/readValue/gen-io-op-reconnect-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/descriptor/readValue/gen-io-op-reconnect-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/descriptor/readValue/read-updates-value.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/descriptor/writeValue/descriptor-is-blocklisted.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/descriptor/writeValue/gen-descriptor-is-removed.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/descriptor/writeValue/gen-io-op-device-disconnects-before.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/descriptor/writeValue/gen-io-op-device-disconnects-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/descriptor/writeValue/gen-io-op-device-disconnects-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/descriptor/writeValue/gen-io-op-device-reconnects-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/descriptor/writeValue/gen-io-op-device-reconnects-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/descriptor/writeValue/gen-io-op-disconnect-called-before.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/descriptor/writeValue/gen-io-op-disconnect-called-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/descriptor/writeValue/gen-io-op-disconnect-called-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/descriptor/writeValue/gen-io-op-fails.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/descriptor/writeValue/gen-io-op-garbage-collection-ran-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/descriptor/writeValue/gen-io-op-garbage-collection-ran-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/descriptor/writeValue/gen-io-op-reconnect-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/descriptor/writeValue/gen-io-op-reconnect-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/descriptor/writeValue/value-too-long.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/descriptor/writeValue/write-succeeds.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/descriptor/writeValue/write-updates-value.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/requestDevice/acceptAllDevices/device-with-no-name.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/requestDevice/acceptAllDevices/device-with-uuids.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/requestDevice/chooser/consecutive-calls.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/requestDevice/chooser/device-removed.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/requestDevice/chooser/multiple-matching-devices.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/requestDevice/chooser/new-scan-all-types.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/requestDevice/chooser/new-scan-connected-devices.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/requestDevice/chooser/new-scan-device-added.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/requestDevice/chooser/new-scan-device-changed.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/requestDevice/chooser/new-scan-services-discovered.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/requestDevice/chooser/restart-scan-finds-new-device.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/requestDevice/chooser/restart-scan-includes-previous-device.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/requestDevice/correct-filters.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/requestDevice/discovery-fails-to-start.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/requestDevice/filter-does-not-match.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/requestDevice/name-empty-device-from-name-prefix-filter.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/requestDevice/name-empty-device-from-name-wrong-filter.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/requestDevice/name-empty-device-from-service-filter.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/requestDevice/name-empty-filter.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/requestDevice/name-missing-device-from-name-empty-filter.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/requestDevice/name-missing-device-from-name-prefix-filter.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/requestDevice/name-missing-device-from-name-wrong-filter.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/requestDevice/name-missing-device-from-service-filter.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/requestDevice/no-devices.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/requestDevice/radio-off.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/requestDevice/single-filter-two-services-fails.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/requestDevice/single-filter-two-services-succeeds.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/requestDevice/two-filters.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/requestLEScan/accept-all-with-filter-throws.https.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/requestLEScan/attempt-to-connect-after-scan.https.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/requestLEScan/basic-scan.https.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/requestLEScan/device-ids-match.https.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/requestLEScan/doesnt-consume-user-gesture.https.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/requestLEScan/le-not-supported.https.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/requestLEScan/multiple-scan.https.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/requestLEScan/page-visibility.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/requestLEScan/radio-not-present.https.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/requestLEScan/scan-options.https.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/requestLEScan/scan-with-multiple-filters.https.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/requestLEScan/scan-with-name-and-uuid-filter.https.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/requestLEScan/scan-with-name-filter.https.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/requestLEScan/scan-with-name-prefix-filter.https.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/requestLEScan/scan-with-service-uuid-filter.https.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/server/connect/connect-disconnected-connect.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/server/connect/connect-twice.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/server/connect/connection-fails.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/server/connect/device-goes-out-of-range.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/server/connect/garbage-collection-ran-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/server/connect/same-gatt-server-both-receive-disconnect-event.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/server/disconnect/disconnect-after-request-disconnection.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/server/disconnect/disconnect-fires-event.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/server/disconnect/disconnect-once.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/server/getPrimaryService/gen-delayed-discovery-no-permission-present-service.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/server/getPrimaryService/gen-delayed-discovery-service-found.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/server/getPrimaryService/gen-device-disconnects-before.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/server/getPrimaryService/gen-device-disconnects-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/server/getPrimaryService/gen-device-disconnects-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/server/getPrimaryService/gen-device-disconnects-during.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/server/getPrimaryService/gen-device-disconnects-invalidates-objects.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/server/getPrimaryService/gen-device-goes-out-of-range.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/server/getPrimaryService/gen-device-reconnects-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/server/getPrimaryService/gen-device-reconnects-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/server/getPrimaryService/gen-reconnect-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/server/getPrimaryService/gen-reconnect-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/server/getPrimaryServices/delayed-discovery-no-permission-present-service.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/server/getPrimaryServices/delayed-discovery-service-not-found.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/server/getPrimaryServices/gen-delayed-discovery-no-permission-present-service-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/server/getPrimaryServices/gen-delayed-discovery-service-found-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/server/getPrimaryServices/gen-delayed-discovery-service-found.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/server/getPrimaryServices/gen-device-disconnects-before-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/server/getPrimaryServices/gen-device-disconnects-before.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/server/getPrimaryServices/gen-device-disconnects-during-error-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/server/getPrimaryServices/gen-device-disconnects-during-success-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/server/getPrimaryServices/gen-device-disconnects-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/server/getPrimaryServices/gen-device-disconnects-during-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/server/getPrimaryServices/gen-device-disconnects-during.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/server/getPrimaryServices/gen-device-disconnects-invalidates-objects-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/server/getPrimaryServices/gen-device-disconnects-invalidates-objects.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/server/getPrimaryServices/gen-device-goes-out-of-range-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/server/getPrimaryServices/gen-device-goes-out-of-range.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/server/getPrimaryServices/gen-device-reconnects-during-error-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/server/getPrimaryServices/gen-device-reconnects-during-success-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/server/getPrimaryServices/gen-device-reconnects-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/server/getPrimaryServices/gen-reconnect-during-error-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/server/getPrimaryServices/gen-reconnect-during-success-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/server/getPrimaryServices/gen-reconnect-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/server/getPrimaryServices/no-permission-present-service.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/service/getCharacteristic/gen-device-disconnects-before.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/service/getCharacteristic/gen-device-disconnects-during.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/service/getCharacteristic/gen-device-disconnects-invalidates-objects.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/service/getCharacteristic/gen-device-goes-out-of-range.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/service/getCharacteristic/gen-disconnect-called-before.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/service/getCharacteristic/gen-disconnect-called-during.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/service/getCharacteristic/gen-disconnect-invalidates-objects.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/service/getCharacteristic/gen-garbage-collection-ran-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/service/getCharacteristics/correct-characteristics.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/service/getCharacteristics/gen-device-disconnects-before-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/service/getCharacteristics/gen-device-disconnects-before.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/service/getCharacteristics/gen-device-disconnects-during-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/service/getCharacteristics/gen-device-disconnects-during.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/service/getCharacteristics/gen-device-disconnects-invalidates-objects-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/service/getCharacteristics/gen-device-disconnects-invalidates-objects.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/service/getCharacteristics/gen-device-goes-out-of-range-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/service/getCharacteristics/gen-device-goes-out-of-range.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/service/getCharacteristics/gen-disconnect-called-before-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/service/getCharacteristics/gen-disconnect-called-before.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/service/getCharacteristics/gen-disconnect-called-during-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/service/getCharacteristics/gen-disconnect-called-during.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/service/getCharacteristics/gen-disconnect-invalidates-objects-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/service/getCharacteristics/gen-disconnect-invalidates-objects.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/service/getCharacteristics/gen-garbage-collection-ran-during-success-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/service/getCharacteristics/gen-garbage-collection-ran-during-success.https.html [ Crash ]
 crbug.com/626703 [ Linux ] wpt_internal/battery-status/api-defined.https.html [ Timeout ]
 crbug.com/626703 [ Linux ] wpt_internal/battery-status/multiple-promises-after-resolve.https.html [ Timeout ]
 crbug.com/626703 [ Linux ] wpt_internal/battery-status/multiple-promises.https.html [ Timeout ]
@@ -3911,294 +3197,6 @@
 crbug.com/626703 [ Linux ] wpt_internal/battery-status/page-visibility.https.html [ Timeout ]
 crbug.com/626703 [ Linux ] wpt_internal/battery-status/promise-with-eventlisteners.https.html [ Timeout ]
 crbug.com/626703 [ Linux ] wpt_internal/battery-status/restricted-level-precision.https.html [ Timeout ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/getDescriptor/descriptor-not-found.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/getDescriptor/gen-descriptor-blocklisted.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/getDescriptor/gen-descriptor-device-disconnects-before.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/getDescriptor/gen-descriptor-device-disconnects-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/getDescriptor/gen-descriptor-device-disconnects-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/getDescriptor/gen-descriptor-disconnect-called-before.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/getDescriptor/gen-descriptor-disconnect-called-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/getDescriptor/gen-descriptor-disconnect-called-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/getDescriptor/gen-descriptor-garbage-collection-ran-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/getDescriptor/gen-descriptor-garbage-collection-ran-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/getDescriptor/gen-descriptor-invalid-name.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/getDescriptor/gen-device-out-of-range.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/getDescriptors/blocklisted-descriptors-not-present.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/getDescriptors/gen-descriptor-blocklisted.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/getDescriptors/gen-descriptor-device-disconnects-before-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/getDescriptors/gen-descriptor-device-disconnects-before.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/getDescriptors/gen-descriptor-device-disconnects-during-error-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/getDescriptors/gen-descriptor-device-disconnects-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/getDescriptors/gen-descriptor-device-disconnects-during-success-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/getDescriptors/gen-descriptor-device-disconnects-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/getDescriptors/gen-descriptor-disconnect-called-before-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/getDescriptors/gen-descriptor-disconnect-called-before.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/getDescriptors/gen-descriptor-disconnect-called-during-error-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/getDescriptors/gen-descriptor-disconnect-called-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/getDescriptors/gen-descriptor-disconnect-called-during-success-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/getDescriptors/gen-descriptor-disconnect-called-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/getDescriptors/gen-descriptor-garbage-collection-ran-during-error-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/getDescriptors/gen-descriptor-garbage-collection-ran-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/getDescriptors/gen-descriptor-garbage-collection-ran-during-success-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/getDescriptors/gen-descriptor-garbage-collection-ran-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/getDescriptors/gen-descriptor-invalid-name.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/getDescriptors/gen-device-out-of-range-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/getDescriptors/gen-device-out-of-range.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/notifications/add-listener-after-promise.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/notifications/add-multiple-event-listeners.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/notifications/characteristic-does-not-support-notifications.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/notifications/concurrent-starts-and-receive-notification.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/notifications/concurrent-starts.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/notifications/concurrent-stops.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/notifications/device-goes-out-of-range.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/notifications/event-after-starting.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/notifications/gc-with-event-listener.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/notifications/gc-with-pending-start.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/notifications/gc-with-pending-stop.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/notifications/notification-after-disconnection.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/notifications/parallel-start-stop.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/notifications/start-before-stop-resolves.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/notifications/start-fails.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/notifications/start-stop-start-stop.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/notifications/start-succeeds.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/notifications/start-twice-in-a-row.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/notifications/stop-after-start-succeeds.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/notifications/stop-twice.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/notifications/stop-without-starting.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/readValue/blocklisted-characteristic.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/readValue/device-goes-out-of-range.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/readValue/gen-gatt-op-device-disconnects-before.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/readValue/gen-gatt-op-device-disconnects-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/readValue/gen-gatt-op-device-disconnects-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/readValue/gen-gatt-op-device-reconnects-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/readValue/gen-gatt-op-device-reconnects-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/readValue/gen-gatt-op-disconnect-called-before.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/readValue/gen-gatt-op-disconnect-called-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/readValue/gen-gatt-op-disconnect-called-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/readValue/gen-gatt-op-garbage-collection-ran-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/readValue/gen-gatt-op-garbage-collection-ran-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/readValue/gen-gatt-op-reconnect-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/readValue/gen-gatt-op-reconnect-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/readValue/read-fails.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/startNotifications/gen-gatt-op-device-disconnects-before.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/startNotifications/gen-gatt-op-device-disconnects-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/startNotifications/gen-gatt-op-device-disconnects-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/startNotifications/gen-gatt-op-device-reconnects-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/startNotifications/gen-gatt-op-device-reconnects-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/startNotifications/gen-gatt-op-disconnect-called-before.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/startNotifications/gen-gatt-op-disconnect-called-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/startNotifications/gen-gatt-op-disconnect-called-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/startNotifications/gen-gatt-op-garbage-collection-ran-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/startNotifications/gen-gatt-op-garbage-collection-ran-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/startNotifications/gen-gatt-op-reconnect-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/startNotifications/gen-gatt-op-reconnect-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/stopNotifications/device-reconnects-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/stopNotifications/gen-gatt-op-device-disconnects-before.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/stopNotifications/gen-gatt-op-device-disconnects-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/stopNotifications/gen-gatt-op-disconnect-called-before.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/stopNotifications/gen-gatt-op-disconnect-called-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/stopNotifications/gen-gatt-op-garbage-collection-ran-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/stopNotifications/reconnect-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/writeValue/blocklisted-characteristic.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/writeValue/device-goes-out-of-range.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/writeValue/gen-gatt-op-device-disconnects-before.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/writeValue/gen-gatt-op-device-disconnects-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/writeValue/gen-gatt-op-device-disconnects-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/writeValue/gen-gatt-op-device-reconnects-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/writeValue/gen-gatt-op-device-reconnects-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/writeValue/gen-gatt-op-disconnect-called-before.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/writeValue/gen-gatt-op-disconnect-called-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/writeValue/gen-gatt-op-disconnect-called-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/writeValue/gen-gatt-op-garbage-collection-ran-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/writeValue/gen-gatt-op-garbage-collection-ran-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/writeValue/gen-gatt-op-reconnect-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/writeValue/gen-gatt-op-reconnect-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/writeValue/value-too-long.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/writeValue/write-fails.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/writeValue/write-updates-value.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/writeValueWithResponse/blocklisted-characteristic.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/writeValueWithResponse/device-goes-out-of-range.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/writeValueWithResponse/gen-gatt-op-device-disconnects-before.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/writeValueWithResponse/gen-gatt-op-device-disconnects-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/writeValueWithResponse/gen-gatt-op-device-disconnects-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/writeValueWithResponse/gen-gatt-op-device-reconnects-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/writeValueWithResponse/gen-gatt-op-device-reconnects-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/writeValueWithResponse/gen-gatt-op-disconnect-called-before.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/writeValueWithResponse/gen-gatt-op-disconnect-called-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/writeValueWithResponse/gen-gatt-op-disconnect-called-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/writeValueWithResponse/gen-gatt-op-garbage-collection-ran-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/writeValueWithResponse/gen-gatt-op-garbage-collection-ran-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/writeValueWithResponse/gen-gatt-op-reconnect-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/writeValueWithResponse/gen-gatt-op-reconnect-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/writeValueWithResponse/value-too-long.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/writeValueWithResponse/write-fails.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/writeValueWithResponse/write-updates-value.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/writeValueWithoutResponse/blocklisted-characteristic.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/writeValueWithoutResponse/device-goes-out-of-range.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/writeValueWithoutResponse/gen-gatt-op-device-disconnects-before.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/writeValueWithoutResponse/gen-gatt-op-device-disconnects-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/writeValueWithoutResponse/gen-gatt-op-device-disconnects-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/writeValueWithoutResponse/gen-gatt-op-device-reconnects-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/writeValueWithoutResponse/gen-gatt-op-device-reconnects-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/writeValueWithoutResponse/gen-gatt-op-disconnect-called-before.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/writeValueWithoutResponse/gen-gatt-op-disconnect-called-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/writeValueWithoutResponse/gen-gatt-op-disconnect-called-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/writeValueWithoutResponse/gen-gatt-op-garbage-collection-ran-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/writeValueWithoutResponse/gen-gatt-op-garbage-collection-ran-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/writeValueWithoutResponse/gen-gatt-op-reconnect-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/writeValueWithoutResponse/gen-gatt-op-reconnect-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/writeValueWithoutResponse/value-too-long.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/writeValueWithoutResponse/write-fails.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/characteristic/writeValueWithoutResponse/write-updates-value.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/descriptor/characteristic-same-from-2-descriptors.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/descriptor/characteristic-same-object.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/descriptor/readValue/descriptor-is-blocklisted.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/descriptor/readValue/gen-descriptor-is-removed.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/descriptor/readValue/gen-device-goes-out-of-range.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/descriptor/readValue/gen-io-op-device-disconnects-before.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/descriptor/readValue/gen-io-op-device-disconnects-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/descriptor/readValue/gen-io-op-device-disconnects-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/descriptor/readValue/gen-io-op-device-reconnects-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/descriptor/readValue/gen-io-op-device-reconnects-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/descriptor/readValue/gen-io-op-disconnect-called-before.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/descriptor/readValue/gen-io-op-disconnect-called-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/descriptor/readValue/gen-io-op-disconnect-called-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/descriptor/readValue/gen-io-op-fails.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/descriptor/readValue/gen-io-op-garbage-collection-ran-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/descriptor/readValue/gen-io-op-garbage-collection-ran-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/descriptor/readValue/gen-io-op-reconnect-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/descriptor/readValue/gen-io-op-reconnect-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/descriptor/readValue/read-updates-value.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/descriptor/writeValue/descriptor-is-blocklisted.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/descriptor/writeValue/gen-descriptor-is-removed.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/descriptor/writeValue/gen-io-op-device-disconnects-before.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/descriptor/writeValue/gen-io-op-device-disconnects-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/descriptor/writeValue/gen-io-op-device-disconnects-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/descriptor/writeValue/gen-io-op-device-reconnects-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/descriptor/writeValue/gen-io-op-device-reconnects-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/descriptor/writeValue/gen-io-op-disconnect-called-before.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/descriptor/writeValue/gen-io-op-disconnect-called-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/descriptor/writeValue/gen-io-op-disconnect-called-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/descriptor/writeValue/gen-io-op-fails.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/descriptor/writeValue/gen-io-op-garbage-collection-ran-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/descriptor/writeValue/gen-io-op-garbage-collection-ran-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/descriptor/writeValue/gen-io-op-reconnect-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/descriptor/writeValue/gen-io-op-reconnect-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/descriptor/writeValue/value-too-long.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/descriptor/writeValue/write-succeeds.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/descriptor/writeValue/write-updates-value.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/requestDevice/acceptAllDevices/device-with-no-name.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/requestDevice/acceptAllDevices/device-with-uuids.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/requestDevice/chooser/consecutive-calls.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/requestDevice/chooser/device-removed.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/requestDevice/chooser/multiple-matching-devices.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/requestDevice/chooser/new-scan-all-types.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/requestDevice/chooser/new-scan-connected-devices.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/requestDevice/chooser/new-scan-device-added.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/requestDevice/chooser/new-scan-device-changed.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/requestDevice/chooser/new-scan-services-discovered.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/requestDevice/chooser/restart-scan-finds-new-device.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/requestDevice/chooser/restart-scan-includes-previous-device.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/requestDevice/correct-filters.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/requestDevice/discovery-fails-to-start.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/requestDevice/filter-does-not-match.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/requestDevice/name-empty-device-from-name-prefix-filter.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/requestDevice/name-empty-device-from-name-wrong-filter.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/requestDevice/name-empty-device-from-service-filter.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/requestDevice/name-empty-filter.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/requestDevice/name-missing-device-from-name-empty-filter.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/requestDevice/name-missing-device-from-name-prefix-filter.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/requestDevice/name-missing-device-from-name-wrong-filter.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/requestDevice/name-missing-device-from-service-filter.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/requestDevice/no-devices.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/requestDevice/radio-off.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/requestDevice/single-filter-two-services-fails.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/requestDevice/single-filter-two-services-succeeds.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/requestDevice/two-filters.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/requestLEScan/accept-all-with-filter-throws.https.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/requestLEScan/attempt-to-connect-after-scan.https.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/requestLEScan/basic-scan.https.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/requestLEScan/device-ids-match.https.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/requestLEScan/doesnt-consume-user-gesture.https.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/requestLEScan/le-not-supported.https.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/requestLEScan/multiple-scan.https.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/requestLEScan/page-visibility.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/requestLEScan/radio-not-present.https.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/requestLEScan/scan-options.https.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/requestLEScan/scan-with-multiple-filters.https.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/requestLEScan/scan-with-name-and-uuid-filter.https.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/requestLEScan/scan-with-name-filter.https.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/requestLEScan/scan-with-name-prefix-filter.https.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/requestLEScan/scan-with-service-uuid-filter.https.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/server/connect/connect-disconnected-connect.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/server/connect/connect-twice.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/server/connect/connection-fails.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/server/connect/device-goes-out-of-range.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/server/connect/garbage-collection-ran-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/server/connect/same-gatt-server-both-receive-disconnect-event.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/server/disconnect/disconnect-after-request-disconnection.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/server/disconnect/disconnect-fires-event.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/server/disconnect/disconnect-once.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/server/getPrimaryService/gen-delayed-discovery-no-permission-present-service.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/server/getPrimaryService/gen-delayed-discovery-service-found.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/server/getPrimaryService/gen-device-disconnects-before.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/server/getPrimaryService/gen-device-disconnects-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/server/getPrimaryService/gen-device-disconnects-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/server/getPrimaryService/gen-device-disconnects-during.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/server/getPrimaryService/gen-device-disconnects-invalidates-objects.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/server/getPrimaryService/gen-device-goes-out-of-range.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/server/getPrimaryService/gen-device-reconnects-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/server/getPrimaryService/gen-device-reconnects-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/server/getPrimaryService/gen-reconnect-during-error.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/server/getPrimaryService/gen-reconnect-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/server/getPrimaryServices/delayed-discovery-no-permission-present-service.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/server/getPrimaryServices/delayed-discovery-service-not-found.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/server/getPrimaryServices/gen-delayed-discovery-no-permission-present-service-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/server/getPrimaryServices/gen-delayed-discovery-service-found-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/server/getPrimaryServices/gen-delayed-discovery-service-found.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/server/getPrimaryServices/gen-device-disconnects-before-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/server/getPrimaryServices/gen-device-disconnects-before.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/server/getPrimaryServices/gen-device-disconnects-during-error-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/server/getPrimaryServices/gen-device-disconnects-during-success-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/server/getPrimaryServices/gen-device-disconnects-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/server/getPrimaryServices/gen-device-disconnects-during-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/server/getPrimaryServices/gen-device-disconnects-during.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/server/getPrimaryServices/gen-device-disconnects-invalidates-objects-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/server/getPrimaryServices/gen-device-disconnects-invalidates-objects.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/server/getPrimaryServices/gen-device-goes-out-of-range-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/server/getPrimaryServices/gen-device-goes-out-of-range.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/server/getPrimaryServices/gen-device-reconnects-during-error-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/server/getPrimaryServices/gen-device-reconnects-during-success-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/server/getPrimaryServices/gen-device-reconnects-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/server/getPrimaryServices/gen-reconnect-during-error-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/server/getPrimaryServices/gen-reconnect-during-success-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/server/getPrimaryServices/gen-reconnect-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/server/getPrimaryServices/no-permission-present-service.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/service/getCharacteristic/gen-device-disconnects-before.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/service/getCharacteristic/gen-device-disconnects-during.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/service/getCharacteristic/gen-device-disconnects-invalidates-objects.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/service/getCharacteristic/gen-device-goes-out-of-range.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/service/getCharacteristic/gen-disconnect-called-before.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/service/getCharacteristic/gen-disconnect-called-during.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/service/getCharacteristic/gen-disconnect-invalidates-objects.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/service/getCharacteristic/gen-garbage-collection-ran-during-success.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/service/getCharacteristics/correct-characteristics.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/service/getCharacteristics/gen-device-disconnects-before-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/service/getCharacteristics/gen-device-disconnects-before.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/service/getCharacteristics/gen-device-disconnects-during-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/service/getCharacteristics/gen-device-disconnects-during.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/service/getCharacteristics/gen-device-disconnects-invalidates-objects-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/service/getCharacteristics/gen-device-disconnects-invalidates-objects.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/service/getCharacteristics/gen-device-goes-out-of-range-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/service/getCharacteristics/gen-device-goes-out-of-range.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/service/getCharacteristics/gen-disconnect-called-before-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/service/getCharacteristics/gen-disconnect-called-before.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/service/getCharacteristics/gen-disconnect-called-during-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/service/getCharacteristics/gen-disconnect-called-during.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/service/getCharacteristics/gen-disconnect-invalidates-objects-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/service/getCharacteristics/gen-disconnect-invalidates-objects.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/service/getCharacteristics/gen-garbage-collection-ran-during-success-with-uuid.https.html [ Crash ]
-crbug.com/626703 [ Linux ] wpt_internal/bluetooth/service/getCharacteristics/gen-garbage-collection-ran-during-success.https.html [ Crash ]
 crbug.com/626703 [ Linux ] wpt_internal/forms/file/file-input-webkitdirectory-key-enter-prevent-keypress.html [ Timeout ]
 crbug.com/626703 [ Linux ] wpt_internal/geolocation-api/cached-position-called-once.https.html [ Timeout ]
 crbug.com/626703 [ Linux ] wpt_internal/geolocation-api/callback-exception.https.html [ Timeout ]
@@ -4478,6 +3476,7 @@
 crbug.com/626703 external/wpt/css/css-will-change/will-change-abspos-cb-dynamic-001.html [ Failure ]
 crbug.com/626703 external/wpt/css/css-will-change/will-change-abspos-cb-001.html [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/url/a-element.html [ Failure ]
+crbug.com/892337 external/wpt/resource-timing/content-type-parsing.html [ Failure ]
 
 # Out-of-order pointerevents: https://github.com/w3c/pointerevents/issues/355
 crbug.com/1186788 [ Linux ] external/wpt/pointerevents/pointerevent_pointercapture_in_frame.html?pen [ Failure Pass Timeout ]
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites
index deff4a6f..b335a5f 100644
--- a/third_party/blink/web_tests/VirtualTestSuites
+++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -1073,13 +1073,19 @@
   {
     "prefix": "prerender",
     "platforms": ["Linux", "Mac", "Win"],
-    "bases": [ "external/wpt/speculation-rules/prerender",
-               "wpt_internal/prerender",
-               "http/tests/inspector-protocol/prerender"],
-    "exclusive_tests": ["external/wpt/speculation-rules/prerender/referrer-policy-from-rules.html"],
+    "bases": [
+      "external/wpt/speculation-rules/prerender",
+      "wpt_internal/prerender",
+      "http/tests/inspector-protocol/prerender"
+    ],
+    "exclusive_tests": [
+      "external/wpt/speculation-rules/prerender/referrer-policy-from-rules.html",
+      "external/wpt/speculation-rules/prerender/csp-script-src-inline-speculation-rules.html",
+      "external/wpt/speculation-rules/prerender/csp-script-src-unsafe-inline.html"
+    ],
     "args": [
       "--enable-blink-features=SpeculationRulesReferrerPolicyKey",
-      "--enable-features=SameSiteCrossOriginForSpeculationRulesPrerender"
+      "--enable-features=SameSiteCrossOriginForSpeculationRulesPrerender,Prerender2ContentSecurityPolicyExtensions"
     ],
     "expires": "Jul 1, 2023"
   },
@@ -1432,11 +1438,13 @@
     "expires": "Jul 1, 2023"
   },
 
+  "Tests URL processing in IDNA 2008 Transitional Mode. Explicitly disables ",
+  "the Non-Transitional Mode feature flag to do so.",
   {
     "prefix": "idna-2008",
     "platforms": ["Linux"],
     "bases": [ "external/wpt/url", "fast/url"],
-    "args": ["--enable-features=UseIDNA2008NonTransitional"],
+    "args": ["--disable-features=UseIDNA2008NonTransitional"],
     "expires": "Jul 1, 2023"
   },
 
@@ -1544,7 +1552,7 @@
     "exclusive_tests": "ALL",
     "args": [
       "--enable-blink-features=SpeculationRulesPrefetchProxy,SpeculationRulesFetchFromHeader,SpeculationRulesReferrerPolicyKey,SpeculationRulesDocumentRules",
-      "--enable-features=SpeculationRulesPrefetchProxy,PrefetchUseContentRefactor",
+      "--enable-features=SpeculationRulesPrefetchProxy,PrefetchUseContentRefactor,PrefetchNoVarySearch",
       "--bypass-prefetch-proxy-for-host=not-web-platform.test"
     ],
     "expires": "Jul 1, 2023"
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 62517fe..3252992 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
@@ -2009,6 +2009,20 @@
         {}
        ]
       ],
+      "balancing-flex-item-trailing-margin-freeze.html": [
+       "3cbf2f853e257745817de5c804e9743b394950b4",
+       [
+        null,
+        {}
+       ]
+      ],
+      "balancing-tall-borders-freeze.html": [
+       "2672690669e5c53a30e6314a9752d2ab80f0bcfd",
+       [
+        null,
+        {}
+       ]
+      ],
       "body-becomes-spanner-html-becomes-vertical-rl.html": [
        "7cd544b71cb8d214a68ed37da4903ff0b7474a1a",
        [
@@ -16595,7 +16609,7 @@
    },
    "window-placement": {
     "fullscreen-companion-window-manual.tentative.https.html": [
-     "07f768384292faceced3d27ec0b86ae354975f1d",
+     "10f30a1906076c1eef1476fe5686c1d93e4412cc",
      [
       null,
       {}
@@ -73642,6 +73656,84 @@
      }
     },
     "css-anchor-position": {
+     "anchor-position-top-layer-001.html": [
+      "c96f6eef558f3c2aa1e7fb4dd084575ac5a036b4",
+      [
+       null,
+       [
+        [
+         "/css/css-anchor-position/anchor-position-top-layer-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "anchor-position-top-layer-002.html": [
+      "e626e6b93566c730ccf620b4e7724ee6f06b1dd5",
+      [
+       null,
+       [
+        [
+         "/css/css-anchor-position/anchor-position-top-layer-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "anchor-position-top-layer-003.html": [
+      "39f3c362c7ba11616c3afca91407c66b6fba4b09",
+      [
+       null,
+       [
+        [
+         "/css/css-anchor-position/anchor-position-top-layer-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "anchor-position-top-layer-004.html": [
+      "8e189e0e7b709853f3545db35b750ec286de61e3",
+      [
+       null,
+       [
+        [
+         "/css/css-anchor-position/anchor-position-top-layer-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "anchor-position-top-layer-005.html": [
+      "d9e4fa86e10dcc9812366a949c14738962b6c9dc",
+      [
+       null,
+       [
+        [
+         "/css/css-anchor-position/anchor-position-top-layer-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "anchor-position-top-layer-006.html": [
+      "5f5cd67a1d709b7e958cb5d199f42e2011cabe87",
+      [
+       null,
+       [
+        [
+         "/css/css-anchor-position/anchor-position-top-layer-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
      "anchor-scroll-basics.tentative.html": [
       "fa42e33d9266a41e803f427f4bec729a4636a858",
       [
@@ -83609,6 +83701,19 @@
         {}
        ]
       ],
+      "increase-fragmentainer-size-flex-item-trailing-margin.html": [
+       "94845449fbf185b2004cc532ecb401eab4e4221d",
+       [
+        null,
+        [
+         [
+          "/css/reference/ref-filled-green-100px-square.xht",
+          "=="
+         ]
+        ],
+        {}
+       ]
+      ],
       "multi-line-column-flex-fragmentation-001.html": [
        "9f064d4ae40ea9e98f0b1a18c5b8fc143bbb6027",
        [
@@ -87642,6 +87747,19 @@
        ]
       ]
      },
+     "increase-fragmentainer-size-tall-border.html": [
+      "b7e716b23c4e7db17ef667f30c771d2d1df1cb9a",
+      [
+       null,
+       [
+        [
+         "/css/reference/ref-filled-green-100px-square.xht",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
      "ink-overflow-002.html": [
       "8af605efea87b618007641729bd3b579fcb654eb",
       [
@@ -234568,6 +234686,19 @@
        {}
       ]
      ],
+     "svg-mutation-filter-used-by-mask.html": [
+      "59ac6accbcafe5e7ba83a1e62538b10d4c4cef65",
+      [
+       null,
+       [
+        [
+         "/css/filter-effects/reference/green-100x100.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
      "svg-mutation-function-to-url.html": [
       "cd2f61f3488d40c3456bbcd72d1bc6d8867a10e6",
       [
@@ -247718,6 +247849,19 @@
         {}
        ]
       ],
+      "popover-anchor-nested-display.tentative.html": [
+       "426c0fcb85542870e7fcfecdf228e56e73957e2c",
+       [
+        null,
+        [
+         [
+          "/html/semantics/popovers/popover-anchor-nested-display-ref.html",
+          "=="
+         ]
+        ],
+        {}
+       ]
+      ],
       "popover-anchor-scroll-display.tentative.html": [
        "06304778129a7d22932e4a6656db1046bfc717e4",
        [
@@ -252792,6 +252936,19 @@
      ]
     },
     "embedded": {
+     "image-embedding-svg-nested-svg-in-foreignobject.html": [
+      "880305253c59fe4d7af9808894f82bd88a6aa760",
+      [
+       null,
+       [
+        [
+         "/svg/struct/reftests/reference/green-100x100.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
      "image-embedding-svg-viewref-with-viewbox.svg": [
       "6340c19d2a0e7c72faec83e2fb990ed9abe487f8",
       [
@@ -259099,11 +259256,11 @@
   "support": {
    ".cache": {
     "gitignore2.json": [
-     "80fc17d29856db7b8670032d91d60d7a2483bc28",
+     "399f500713d1f993d90c8f0503438678d2212aa1",
      []
     ],
     "mtime.json": [
-     "2d1f7df3fe0cd4641b8cda7cc859e21e25b7914c",
+     "9be110f326ec9215cb6eb4e926a8de474847d84f",
      []
     ]
    },
@@ -259385,7 +259542,7 @@
      []
     ],
     "idbobjectstore_batchGetAll_largeValue.tentative.any.js.ini": [
-     "5b482e4d1876ae2d6dcf5a3d080fbbd00182c60e",
+     "77a61babdeb50a9fa2c96137b7e2c5e91204b82c",
      []
     ],
     "resources": {
@@ -259443,7 +259600,7 @@
      []
     ],
     "structured-clone.any.js.ini": [
-     "0b0a546f78d4aa5f5efe7cdf2b414ebf9ed4404d",
+     "244c9165b4dc8c076f25f93dc3c9bf03913b1525",
      []
     ]
    },
@@ -263355,10 +263512,6 @@
      }
     },
     "form-action": {
-     "form-action-src-allowed-target-blank.sub.html.ini": [
-      "89908d86b532ceccc50e02ca813157bcd849b994",
-      []
-     ],
      "form-action-src-javascript-blocked.sub.html.sub.headers": [
       "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391",
       []
@@ -265645,7 +265798,7 @@
      []
     ],
     "cookieStore_set_arguments.https.any.js.ini": [
-     "d05fc2fedfafcac6990b701981f65d921b4f22a0",
+     "e8f1f64ec3037cb5ca12faab0f0743d162bc338d",
      []
     ],
     "cookieStore_subscribe_arguments.https.any.js.ini": [
@@ -265937,8 +266090,12 @@
      ]
     },
     "samesite": {
+     "about-blank-subresource.https.html.ini": [
+      "2e65780b8b8dc8c13869b298952186b189c6e7aa",
+      []
+     ],
      "form-post-blank-reload.https.html.ini": [
-      "474bdcb939113d28f9ec5d5eeedd78f0f3c471d7",
+      "cff3fc495bac3351066bc2c1c82128c679a3fab6",
       []
      ],
      "form-post-blank.https.html.ini": [
@@ -266121,7 +266278,7 @@
      ]
     },
     "fedcm-network-requests.https.html.ini": [
-     "3dbcf6ad51dfcd197c0f09257bcc0816589d6ef0",
+     "2407bd6c6c4d7f8aa63c109bc1144b5079593cf8",
      []
     ],
     "federatedcredential-framed-get.sub.https-expected.txt": [
@@ -274893,6 +275050,10 @@
       "b2fa31ba5afca514557cac6bfe4b9ffbb94600c8",
       []
      ],
+     "anchor-position-top-layer-ref.html": [
+      "dc7f77f2b3441ebee25f3e40a9480b668ee79ea4",
+      []
+     ],
      "reference": {
       "anchor-scroll-fixedpos-ref.html": [
        "e73354df72dac33f7a94eaef445e80ec61e3976a",
@@ -274964,7 +275125,7 @@
       []
      ],
      "Element-getAnimations-dynamic-changes.tentative.html.ini": [
-      "02694929677056c713869f66cc6b6381ee7f7098",
+      "90372850a2b15bec987edeacd277c571fa2bae64",
       []
      ],
      "KeyframeEffect-getKeyframes.tentative-expected.txt": [
@@ -285674,7 +285835,7 @@
       []
      ],
      "image-as-flexitem-size-007v.html.ini": [
-      "5d58a48ac7dbfdf24f0534672e6412b05bb963b5",
+      "c8aa4b5796a4f1f672b7769e34ac51dbb8048db2",
       []
      ],
      "interactive": {
@@ -287137,20 +287298,12 @@
       "b2c06f72be49fb1e9ecd45b0fa3b209882e13cbd",
       []
      ],
-     "font-palette-vs-shorthand-expected.txt": [
-      "b9913eda83a45350b9a28884869a8fe5dcbe31b7",
-      []
-     ],
-     "font-palette-vs-shorthand.html.ini": [
-      "3a37c7353a9d915573d11104b932f3bacf219dc9",
-      []
-     ],
      "font-shorthand-serialization-prevention-expected.txt": [
-      "77c053b8372b72d8ad1f54048c004ec78cc0e1a9",
+      "cf8ba31b186710060ff1b9e11db2bf68c38fa174",
       []
      ],
      "font-shorthand-serialization-prevention.html.ini": [
-      "0c39f805131c226f22f18dad15551194f9fbe5d7",
+      "7b50e7cf9cb7dac86a30a5c52783c94305d3a319",
       []
      ],
      "font-size-adjust-001-ref.html": [
@@ -298857,7 +299010,7 @@
        []
       ],
       "Initial-letter-breaking-vlr.html.ini": [
-       "a76ddebb09122ea844c4ced99309e7c4105566b3",
+       "0b139d8c707a3a0282d818bd3cead54c3fbf6f53",
        []
       ],
       "Initial-letter-breaking-vrl-ref.html": [
@@ -298865,7 +299018,7 @@
        []
       ],
       "Initial-letter-breaking-vrl.html.ini": [
-       "95bff1082a42475a6be9667358d42516b14d2824",
+       "a3abcef3f2de6b9a92a2186e9a3fbcc24cbc8aea",
        []
       ],
       "Initial-letter-breaking.html.ini": [
@@ -298897,7 +299050,7 @@
        []
       ],
       "initial-letter-block-position-drop-under-ruby-tall.html.ini": [
-       "37fb91ec3efb837e7dc32c148ee54d4ffdfd6c79",
+       "2d222a45d3d8d93120bc0574099b5939c730bc9b",
        []
       ],
       "initial-letter-block-position-drop-under-ruby.html.ini": [
@@ -298913,7 +299066,7 @@
        []
       ],
       "initial-letter-block-position-margins-rtl.html.ini": [
-       "a084bdb3aac01e0c8abc03e4f018725cb3e5765a",
+       "7dd454ccc02d9006a244d8811eb808233484ef66",
        []
       ],
       "initial-letter-block-position-margins-vlr-ref.html": [
@@ -298965,7 +299118,7 @@
        []
       ],
       "initial-letter-block-position-raise-under-ruby.html.ini": [
-       "c4296d60bd16d3c4ea8dc9457b85ce6965627fc0",
+       "7ab9e673107e1736059a44c0c64cfc948ff31d09",
        []
       ],
       "initial-letter-computed.html.ini": [
@@ -298997,11 +299150,11 @@
        []
       ],
       "initial-letter-drop-initial-vrl.html.ini": [
-       "90a6105fa6ab976c667bf33c9665a8656edadd75",
+       "9324bd347f71f55e49cd491d089e1145363f6d95",
        []
       ],
       "initial-letter-drop-initial.html.ini": [
-       "e47b563d731aaec9a1cbb0e90ae516abc881c138",
+       "560a2a205b27693022084b36bd3a25ac84de9cc3",
        []
       ],
       "initial-letter-float-001-ref.html": [
@@ -299013,7 +299166,7 @@
        []
       ],
       "initial-letter-float-001-rtl.html.ini": [
-       "1678bc1725ca1f673a611be899462fef65919faf",
+       "f8d9885ba0375cee5ab92f9e3ea428fb89f11938",
        []
       ],
       "initial-letter-float-001-vlr-ref.html": [
@@ -299021,7 +299174,7 @@
        []
       ],
       "initial-letter-float-001-vlr.html.ini": [
-       "c90a4c2d81fa1d6dea8c956c8a593c503338dec0",
+       "815486cd822115d5d8a0861fb545fb9d6eff1c90",
        []
       ],
       "initial-letter-float-001-vrl-ref.html": [
@@ -299033,7 +299186,7 @@
        []
       ],
       "initial-letter-float-001.html.ini": [
-       "3d20a75e89b3d6b44c4fa4151cd68d2c9c663d39",
+       "263a4211cc99b44532d99c92af3a04bad8ed4b45",
        []
       ],
       "initial-letter-float-002-ref.html": [
@@ -299041,7 +299194,7 @@
        []
       ],
       "initial-letter-float-002.html.ini": [
-       "eeb0e18a7eace82f31abbdd12e385c425c5845fa",
+       "2e8b17aca51ee554d27822eec333954f9cfa77f8",
        []
       ],
       "initial-letter-float-003-ref.html": [
@@ -299077,11 +299230,11 @@
        []
       ],
       "initial-letter-indentation-rtl.html.ini": [
-       "94f83221e57c2b98b4c8e5138e709e8dbe1c889d",
+       "776a6e966f53708bea9c42982fdaa96d630dce1e",
        []
       ],
       "initial-letter-indentation.html.ini": [
-       "2f25a7a3aa6494563e10625c54ef4659a702ece5",
+       "fbc24c225c34e2f35829cc904f469bff56350f9a",
        []
       ],
       "initial-letter-layout-text-decoration-underline-ref.html": [
@@ -299089,7 +299242,7 @@
        []
       ],
       "initial-letter-layout-text-decoration-underline.html.ini": [
-       "87a653d81d272f04db703b27e8ef6e2aae8cf63a",
+       "ad12f91f3f926cadbcfadf6928093e3691b8fb74",
        []
       ],
       "initial-letter-raise-initial-ref.html": [
@@ -299137,7 +299290,7 @@
        []
       ],
       "initial-letter-raised-sunken-caps-sunken.html.ini": [
-       "858a0ee023dd09641c6760806feec30819a37bee",
+       "9280e8ef0b41c09e7a95315599c1a38607ad4721",
        []
       ],
       "initial-letter-short-para-initial-letter-clears-ref.html": [
@@ -299173,7 +299326,7 @@
        []
       ],
       "initial-letter-sunk-initial-vlr.html.ini": [
-       "84fa955e9b48a110d3b028481fc640d8ced287f8",
+       "3d2a32bf7161487da9f8a792fbf3763dd3e182e8",
        []
       ],
       "initial-letter-sunk-initial-vrl-ref.html": [
@@ -300557,11 +300710,11 @@
        []
       ],
       "clip-path-inline-001.html.ini": [
-       "c87eb82357b0de46119bbf273892ce08cf2eac89",
+       "3917a432eee9ec2d3266c4db5941181ee2d5015a",
        []
       ],
       "clip-path-inline-002.html.ini": [
-       "84724ceb17adbb81abbf45520c33b82b13c67f36",
+       "a2eabbf232c203107a27deb8592abf508f3a5adc",
        []
       ],
       "clip-path-inline-003.html.ini": [
@@ -300629,7 +300782,7 @@
        []
       ],
       "clip-path-reference-box-004.html.ini": [
-       "6bc73142f3a3d3e4e4c8e9329a390ac9cc458ce5",
+       "ff4cde4a1479d5c1703a5ca027c20046efe0934b",
        []
       ],
       "clip-path-rotated-will-change-transform-ref.html": [
@@ -303666,7 +303819,7 @@
       []
      ],
      "scrollable-overflow-input-001.html.ini": [
-      "53c5a71e2f1773ce8c40e0f759b4c5fb91952439",
+      "af5b11c450c65eaf1e3432fb428f5b5e8bd9f109",
       []
      ],
      "scrollable-overflow-input-002-ref.html": [
@@ -303674,7 +303827,7 @@
       []
      ],
      "scrollable-overflow-input-002.html.ini": [
-      "eba9b192af82bb1e776a747aec56d515e6bd072c",
+      "6e03ac5f353bb60fc0f81f763375e7aac0cbbbd1",
       []
      ],
      "scrollable-overflow-padding.html.ini": [
@@ -304047,6 +304200,10 @@
       "3e7db50548e0ddd1739ab91db24e29072d267061",
       []
      ],
+     "geometry-background-image-001.https.html.ini": [
+      "28cf8a5ae6dd98c6007c7c8b0ac0aa9468df83c3",
+      []
+     ],
      "geometry-background-image-002-ref.html": [
       "b03fcd02ec348e42fe74c61d8b887ff07e260ff8",
       []
@@ -304206,7 +304363,7 @@
       []
      ],
      "parse-input-arguments-018.https.html.ini": [
-      "e4f798487be77923962fa7daa26382311addde19",
+      "55865af0870884eb74dc88845612f7271798b255",
       []
      ],
      "parse-input-arguments-ref.html": [
@@ -306650,7 +306807,7 @@
       []
      ],
      "ruby-text-combine-upright-001a.html.ini": [
-      "a5cb32a694c8f496fd39b9ab38fe46edaf26140f",
+      "0df837fbb07d756aa4f778765d6ce08a5f296275",
       []
      ],
      "ruby-text-combine-upright-001b.html.ini": [
@@ -306662,7 +306819,7 @@
       []
      ],
      "ruby-text-combine-upright-002a.html.ini": [
-      "894ea23afe9741293f87e2b20eecbc56dacc8921",
+      "3ba2bb3b78b4c4947fab7bc4c2923c1ba9f7873f",
       []
      ],
      "ruby-text-combine-upright-002b.html.ini": [
@@ -329260,6 +329417,10 @@
        "a3cc6d3d8f56fd31c4b19d23af606d7c65487145",
        []
       ],
+      "fullscreen-pseudo-class-in-has.html.ini": [
+       "fe556653aa9497493e471160a41f962d233a77c8",
+       []
+      ],
       "has-complexity.html.ini": [
        "bf873f5f49b34507c02b9f8e646f547b4b17d981",
        []
@@ -329268,6 +329429,10 @@
        "5695721fc8827da2eb1b25998402639ca4b16323",
        []
       ],
+      "modal-pseudo-class-in-has.html.ini": [
+       "153ead5932ba240998e297aaf9c6cba2dbfac03f",
+       []
+      ],
       "sheet-going-away-002-ref.html": [
        "67841617736730e588f5659fd485fb09a159bd33",
        []
@@ -330016,7 +330181,7 @@
       []
      ],
      "ElementInternals-target-element-is-held-strongly.html.ini": [
-      "d1e5bb1161d4f53a4d4430d208ee1cb94e70a154",
+      "5b9453dd9a3d2a8c803f0b5fe89945e2ee94ea3d",
       []
      ],
      "ElementInternals-validation-expected.txt": [
@@ -330127,7 +330292,7 @@
      []
     ],
     "throw-on-dynamic-markup-insertion-counter-construct.html.ini": [
-     "fb0218065ce0c37045e0bcf2f9f3b86a9bfa7ca3",
+     "8d48593553e07fadc45e68ba8300cc28b69208ae",
      []
     ],
     "throw-on-dynamic-markup-insertion-counter-reactions-xml-parser-expected.txt": [
@@ -331600,7 +331765,11 @@
     },
     "ranges": {
      "Range-mutations-dataChange.html.ini": [
-      "c1f1d46ce54df403fe78ec637233281dbb7e2fef",
+      "7158a50a34d3428f41cd3a6672018936c2c3c088",
+      []
+     ],
+     "Range-mutations-deleteData.html.ini": [
+      "8a4d2feb07a10de291aaf74a7996c09fef195942",
       []
      ],
      "Range-mutations.js": [
@@ -333022,7 +333191,7 @@
       []
      ],
      "formatblock.html.ini": [
-      "8247fe6b6b711d75c510c855a63b8d0fd0023edc",
+      "8b0be3db77b7b2f5510aca20be07b6bd65667b4c",
       []
      ],
      "formatblock_1-1000-expected.txt": [
@@ -333425,7 +333594,7 @@
      []
     ],
     "image-carousel.html.ini": [
-     "f0807766f9b028e6c294a92fbf552359b8ba033a",
+     "3b89847db19cc319b93c16a11a963c206c26b5eb",
      []
     ],
     "image-src-change.html.ini": [
@@ -333775,7 +333944,7 @@
        []
       ],
       "sjis-decode-ms_kanji.html.ini": [
-       "521412ae4d0588e0b198c8730c3e5f9dd775e987",
+       "d1ad47ee06fcdf1c0850408e3ce119450b119bc2",
        []
       ],
       "sjis-decoder.js": [
@@ -334176,10 +334345,6 @@
        "49773a44f8de6275f1fa88dca396ec0178e90acf",
        []
       ],
-      "big5-encode-form.html.ini": [
-       "32e602f291b9acedade9014a3684a358d2ff3f52",
-       []
-      ],
       "big5-encode-href-errors-han.html.headers": [
        "49773a44f8de6275f1fa88dca396ec0178e90acf",
        []
@@ -334429,7 +334594,7 @@
      []
     ],
     "unsupported-labels.window.js.ini": [
-     "9a401efcf40cde795371bcd96be5f1a23c0e5871",
+     "ff6ba32c8edce3a6ed6b4c9b29a1b6a48bf8d529",
      []
     ]
    },
@@ -334546,10 +334711,6 @@
      "ff1a710fd9634ecbecb94c533f7aac772b876362",
      []
     ],
-    "ru-ISO-8859-5-late.tentative.html.ini": [
-     "c722a3b0c5aecc404ad5c97499e4ec252823322b",
-     []
-    ],
     "support": {
      "__dir__.headers": [
       "a50d2c8454fd943a3e26a80398ad11e8fa9e9551",
@@ -338302,7 +338463,7 @@
       ]
      },
      "service-worker-background-fetch.https.window.js.ini": [
-      "12df1cad0d6f5aba27db4b460bb25eabc69fb4fd",
+      "82a91238fffb51c04fc7d527fd9b210869c812b1",
       []
      ],
      "service-worker-fetch.https.window.js.ini": [
@@ -338719,7 +338880,7 @@
      []
     ],
     "iframe-focus-with-different-site-intermediate-frame.html.ini": [
-     "3dbc2c45845203ba4bbcc1a31bb3bcb9455c35b9",
+     "a4e9ffefd3d4c43282dea6fd72191db23d6006cb",
      []
     ],
     "iframe-focus-with-same-as-top-intermediate-frame-expected.txt": [
@@ -340159,7 +340320,7 @@
      []
     ],
     "FileSystemFileHandle-create-sync-access-handle.https.tentative.window.js.ini": [
-     "c3348a5c9aef335e28df011c41c92883beabaef3",
+     "8850b9a3bfe6937010a42c7d09a3716193816283",
      []
     ],
     "META.yml": [
@@ -340915,7 +341076,7 @@
      []
     ],
     "clamped-time-origin-isolated.https.html.ini": [
-     "2e88cea7c61dd7984dc9aea9c6347a39c0a2d7e5",
+     "e38372b4a792fc3475754544119a3a8d7ee9ae83",
      []
     ],
     "clamped-time-origin.html.ini": [
@@ -340988,7 +341149,7 @@
       []
      ],
      "embedding.tentative.https.window.js.ini": [
-      "d0866b7dc69ecb3c413fd69e12369ee913577073",
+      "be5f4f575a4b9c75db191e9cda36af69044dbee1",
       []
      ],
      "fenced-frame-bypass.tentative.https.window-expected.txt": [
@@ -341063,7 +341224,7 @@
          []
         ],
         "inflight-fetch-cors.html.ini": [
-         "9541f41527625ebb7c49c1b29ae0c36f3ea9c771",
+         "5e823bd6d7e86a1cc35abf55c0416c41e5c5f0f0",
          []
         ],
         "inflight-fetch-redirects-expected.txt": [
@@ -341854,7 +342015,7 @@
         []
        ],
        "same-document-traversal-cross-document-traversal.html.ini": [
-        "2bb8c179c85e8fd0f08f55f10adb78a20d715210",
+        "dcb750585df15799d66d64f38c40bf5550388f3d",
         []
        ],
        "same-document-traversal-same-document-traversal-hashchange.html.ini": [
@@ -342263,6 +342424,10 @@
         "d150449eb2df241e4ae286cf8ba13fc37f0874c3",
         []
        ],
+       "010.html.ini": [
+        "b94165a50142ef994d3edaf6a7116dfd5809a85d",
+        []
+       ],
        "blank-new.html": [
         "2a545af0ed30d69b1e4bb7d1e608273b89dd3d1e",
         []
@@ -342284,7 +342449,7 @@
         []
        ],
        "combination_history_006.html.ini": [
-        "a339e37fb2dee62a70403a338ee10df1a684f356",
+        "9a7593eab709f3940ee46d0deb382620b4e5e8d4",
         []
        ],
        "history-state-after-bfcache.window.js.ini": [
@@ -342348,7 +342513,7 @@
         []
        ],
        "history_pushstate_url_rewriting.html.ini": [
-        "d28878e35aacebc03efcf47444d4c520fbd60f91",
+        "b50924d9784df3382a094a62b2221139bddb0417",
         []
        ],
        "joint_session_history": {
@@ -342528,7 +342693,7 @@
         []
        ],
        "location-protocol-setter-non-broken.html.ini": [
-        "504d86aee122cf41ba76c332ac4ad6bbe8654b5a",
+        "df601b42edd56e9c3ccc9970785a0511338a3440",
         []
        ],
        "location-prototype-setting-cross-origin-domain.sub-expected.txt": [
@@ -342750,7 +342915,7 @@
         []
        ],
        "cross-origin-objects-function-name.html.ini": [
-        "df54342880d0038f649b9a569f018e176e1b65db",
+        "ab20f7d67ec27b63a5eeba7fc9bd2357fab36d05",
         []
        ],
        "cross-origin-objects-on-new-window.html.ini": [
@@ -342813,7 +342978,7 @@
          []
         ],
         "parent-yes-child-no-port.sub.https.html.ini": [
-         "5383b4d6c44128b9efe8d2a58b3d4660d59f1198",
+         "5504eb18d1640f0d5f622b3cfa215e1f0848689b",
          []
         ],
         "parent-yes-child-no-same.sub.https.html.headers": [
@@ -342821,7 +342986,7 @@
          []
         ],
         "parent-yes-child-no-same.sub.https.html.ini": [
-         "c5662b40305800686cf263cc61750628ee4e991c",
+         "7683c127a6934937d22bf2eaaaba2f8789fd25fa",
          []
         ],
         "parent-yes-child-no-subdomain.sub.https.html.headers": [
@@ -342829,7 +342994,7 @@
          []
         ],
         "parent-yes-child-no-subdomain.sub.https.html.ini": [
-         "88fd82f5ed244ea415ff50b2d0d5dcdee19abb06",
+         "4df55d70d7e6176e97c1361ebd0e1542b2b3d290",
          []
         ],
         "parent-yes-child-yes-port.sub.https.html.headers": [
@@ -342845,7 +343010,7 @@
          []
         ],
         "parent-yes-child-yes-same.sub.https.html.ini": [
-         "e3a89c0f7ce9961f842110f8818e4e27fd505f8b",
+         "2b7fa4f27546ce75c088fb484df17a380bfb685f",
          []
         ],
         "parent-yes-child-yes-subdomain.sub.https.html.headers": [
@@ -342871,11 +343036,7 @@
          []
         ],
         "parent-no-child1-yes-subdomain-child2-no-port.sub.https.html.ini": [
-         "d91cf4a25896887e4178ddc3fc0e70a77fb525dd",
-         []
-        ],
-        "parent-no-child1-yes-subdomain-child2-no-subdomain.sub.https.html.ini": [
-         "2ae4867c1c4fc9e6b1049177e5f6c0476956640b",
+         "5ecea15a6e64297539aaf7c6d981486fae67b0ac",
          []
         ],
         "parent-yes-child1-no-subdomain-child2-no-subdomain.sub.https.html.headers": [
@@ -342883,7 +343044,7 @@
          []
         ],
         "parent-yes-child1-no-subdomain-child2-no-subdomain.sub.https.html.ini": [
-         "2c563ba3a38e38614c1a5b99955a11d7582332cb",
+         "cab93f6bf86938db656efb5719ca85199e00a98d",
          []
         ],
         "parent-yes-child1-no-subdomain-child2-no-subdomain2.sub.https.html.headers": [
@@ -342891,7 +343052,7 @@
          []
         ],
         "parent-yes-child1-no-subdomain-child2-no-subdomain2.sub.https.html.ini": [
-         "aa399ed59ccdefdbb932b99134446613c9aacf5c",
+         "667b95edb5efa7db90a0d3962a71aaed5e8e20bf",
          []
         ],
         "parent-yes-child1-no-subdomain-child2-yes-subdomain.sub.https.html.headers": [
@@ -342899,7 +343060,7 @@
          []
         ],
         "parent-yes-child1-no-subdomain-child2-yes-subdomain.sub.https.html.ini": [
-         "a21f71a8ba412e447a26cf2689af00dcb6719f56",
+         "e77ac91c4531d403826da7b9bb6c47c2ef63b7b1",
          []
         ],
         "parent-yes-child1-no-subdomain-child2-yes-subdomain2.sub.https.html.headers": [
@@ -342907,7 +343068,7 @@
          []
         ],
         "parent-yes-child1-no-subdomain-child2-yes-subdomain2.sub.https.html.ini": [
-         "ca5ad44b3ba02c6f7daad5cd97bb2761f37d492b",
+         "adb5640efc0c4fc7932066a76ccfe17fa424b2b2",
          []
         ],
         "parent-yes-child1-no-subdomain-child2-yes-subdomainport.sub.https.html.headers": [
@@ -342915,7 +343076,7 @@
          []
         ],
         "parent-yes-child1-no-subdomain-child2-yes-subdomainport.sub.https.html.ini": [
-         "86a8d77ff5bc2018ca6899eba2818d57150d63ff",
+         "83497785eecbb458d7175b44068eded801e7149d",
          []
         ],
         "parent-yes-child1-yes-subdomain-child2-no-port.sub.https.html.headers": [
@@ -342923,7 +343084,7 @@
          []
         ],
         "parent-yes-child1-yes-subdomain-child2-no-port.sub.https.html.ini": [
-         "54fc998ef0bc7951df4757f05c19f4bc08f3907a",
+         "a3efc43445f1e3cccf221af3c9c8d998933718d1",
          []
         ],
         "parent-yes-child1-yes-subdomain-child2-no-subdomain.sub.https.html.headers": [
@@ -342939,23 +343100,19 @@
          []
         ],
         "parent-yes-child1-yes-subdomain-child2-yes-subdomain.sub.https.html.ini": [
-         "c0dbbaa6c51fa2e5a13ee00664a92a81e96d0d66",
+         "3735ac72c3449fd8f75119de464ed3f0c7ac7b2d",
          []
         ],
         "parent-yes-child1-yes-subdomain-child2-yes-subdomain2.sub.https.html.headers": [
          "79a20f30fc0f486014c8b93edef7483605101504",
          []
         ],
-        "parent-yes-child1-yes-subdomain-child2-yes-subdomain2.sub.https.html.ini": [
-         "9765225548f716c44032e757035c9ab8f09cb15d",
-         []
-        ],
         "parent-yes-child1-yes-subdomain-child2-yes-subdomainport.sub.https.html.headers": [
          "79a20f30fc0f486014c8b93edef7483605101504",
          []
         ],
         "parent-yes-child1-yes-subdomain-child2-yes-subdomainport.sub.https.html.ini": [
-         "e3b0508f4657609580638d04ce6d4902d3c18ee9",
+         "05729211a40f89914e08a4462d315d98c013b735",
          []
         ]
        },
@@ -342984,7 +343141,7 @@
         []
        ],
        "document-domain.sub.https.html.ini": [
-        "019f39a3242abb15b1dab75ddd97da27cd004cc8",
+        "c32ba34d011f20d15e9f773faf9501e1160b748f",
         []
        ],
        "getter-special-cases": {
@@ -343013,13 +343170,17 @@
          []
         ],
         "javascript-url-yes.https.html.ini": [
-         "189de0412095c179a135e6aa017e6c204b9e0446",
+         "300ae43ef4c3e8800127b329607ad79591d30700",
          []
         ],
         "removed-iframe.sub.https.html.headers": [
          "79a20f30fc0f486014c8b93edef7483605101504",
          []
         ],
+        "removed-iframe.sub.https.html.ini": [
+         "55f11aaf08ab4b06ada0ae665685c483a7805dd2",
+         []
+        ],
         "resources": {
          "data-to-javascript-test.mjs": [
           "3a88253ee3053465472ef7d6ecba74b92fab79ce",
@@ -343051,7 +343212,7 @@
          []
         ],
         "sandboxed-same-origin-iframe-no.https.html.ini": [
-         "27d7b90a39f0b3627a686e2a6c41bd450bbc5872",
+         "e890d00d3b419bdec5486783adba60620b86972b",
          []
         ],
         "sandboxed-same-origin-iframe-yes.https.html.headers": [
@@ -343059,7 +343220,7 @@
          []
         ],
         "sandboxed-same-origin-iframe-yes.https.html.ini": [
-         "7ab81268e359acb01f8d7c7c8c581b2bc5705c47",
+         "09723dad56747c9366c4528f265ae952785b8549",
          []
         ]
        },
@@ -343073,19 +343234,19 @@
        ],
        "iframe-navigation": {
         "parent-no-1-no-same-2-yes-subdomain.sub.https.html.ini": [
-         "ae3cfc9414ddc04e6cbc6d2fa181d2801cc6cbb1",
+         "6d56fe418909f35235fb894e60e77053678e592b",
          []
         ],
         "parent-no-1-no-subdomain-2-yes-subdomain.sub.https.html.ini": [
-         "9db791e53b2e9c4e80986219a960f80f8beb4417",
+         "5f644dc4fed1ff8ef34607dd6120097f2992c2de",
          []
         ],
         "parent-no-1-no-subdomain-2-yes-subdomain2.sub.https.html.ini": [
-         "dc60d1c5e800ada90319c373846dbe045c24383c",
+         "aaedd6bbbefaf0ce5018047ed799225e002435b6",
          []
         ],
         "parent-no-1-subdomain-yes-2-subdomain2-no.sub.https.html.ini": [
-         "bb34977f71513d4cf4fabbe8f23fef03a7bb6635",
+         "d4d3a09262b3ec21019303e909fcba6cbfa87dc8",
          []
         ],
         "parent-no-1-yes-subdomain-2-no-subdomain.sub.https.html.ini": [
@@ -343105,7 +343266,7 @@
          []
         ],
         "parent-yes-1-no-same-2-no-subdomain.sub.https.html.ini": [
-         "9be034bc3ba841b5d75ccd264a45873b7fcd44fc",
+         "4abd6d154a5bdf13de642870bdc393ec552c016c",
          []
         ]
        },
@@ -343118,12 +343279,16 @@
         []
        ],
        "popups": {
+        "opener-no-openee-yes-port.sub.https.html.ini": [
+         "81caef44d49e6c9883d2cc615f1ed58c3835ecc3",
+         []
+        ],
         "opener-no-openee-yes-same.sub.https.html.ini": [
-         "01a9296bad9ca6c03f71cfb2fa250b3317e43f42",
+         "cb4a0654530c97cf40672006d034ee04e68e6291",
          []
         ],
         "opener-no-openee-yes-subdomain.sub.https.html.ini": [
-         "575ec913b83267f45fd77d5c5f8dd9290ad2bfb1",
+         "cd6ff86474171e22c5d8b72318e9204acdf002f9",
          []
         ],
         "opener-yes-openee-no-port.sub.https.html.headers": [
@@ -343139,7 +343304,7 @@
          []
         ],
         "opener-yes-openee-no-same.sub.https.html.ini": [
-         "8b96f964db7e1b9ae9629b3ea5bae6e2dfa8319c",
+         "053a0b74c9f0692fea76477f8f8ad74481baa651",
          []
         ],
         "opener-yes-openee-no-subdomain.sub.https.html.headers": [
@@ -343147,7 +343312,7 @@
          []
         ],
         "opener-yes-openee-no-subdomain.sub.https.html.ini": [
-         "c3ea49453cb248dafb7cbbee1b8e11fd0cfa6ad2",
+         "83fcc1fe1ce693d1b36c9ca3daef9ab38cb52aa6",
          []
         ],
         "opener-yes-openee-yes-port.sub.https.html.headers": [
@@ -343155,7 +343320,7 @@
          []
         ],
         "opener-yes-openee-yes-port.sub.https.html.ini": [
-         "aa471127284f8b9b53d076d6f4f4e3686b45ef89",
+         "e1431348cca8f106345fbafd820354e60e87f00c",
          []
         ],
         "opener-yes-openee-yes-same.sub.https.html.headers": [
@@ -343171,7 +343336,7 @@
          []
         ],
         "opener-yes-openee-yes-subdomain.sub.https.html.ini": [
-         "8edfd8e7ce58578eb26b137da80e71c84f092395",
+         "941c31f33e56b3ae39a0cdac491970bf444e8b35",
          []
         ]
        },
@@ -343180,7 +343345,7 @@
         []
        ],
        "removing-iframes.sub.https.html.ini": [
-        "2a45522547ccdc13161f55079c9ece0bd65a5bd1",
+        "8afcc3f28b12f13ba4353ace45ce001db59537f2",
         []
        ],
        "resources": {
@@ -343236,7 +343401,7 @@
       },
       "relaxing-the-same-origin-restriction": {
        "document_domain_access_details.sub.html.ini": [
-        "29f86924eef02d1e3b0ae455083488ecd296a665",
+        "ece0b853478a0747933888fef1b1e70d20518535",
         []
        ],
        "document_domain_setter.html.ini": [
@@ -343526,7 +343691,7 @@
         ]
        },
        "open-features-is-popup-condition.html.ini": [
-        "bd8e1e949fb4adcb4d9e53f2bf170e9231f1fdfc",
+        "6abbca58ac2cfc9960336bc3385f004f180ceb1f",
         []
        ],
        "open-features-negative-innerwidth-innerheight.html.ini": [
@@ -343618,7 +343783,7 @@
         []
        ],
        "open-features-tokenization-width-height.html.ini": [
-        "cf35157225c8fbf0b6222bb51792bfff5a3d67e8",
+        "25200b9fcdb4c04d9579be0768e9efcbb236abbc",
         []
        ],
        "resources": {
@@ -343779,10 +343944,6 @@
        }
       },
       "browsing-context-names": {
-       "choose-_blank-002.html.ini": [
-        "9038f75313fb646954d192d94240d99c52dac53c",
-        []
-       ],
        "choose-_blank-003.html.ini": [
         "dc91542cb352a223d074ef2883e40bd40cbe73df",
         []
@@ -345209,7 +345370,7 @@
          []
         ],
         "canvas-display-p3-drawImage-ImageBitmap-video.html.ini": [
-         "ced13008276196974e7f61b89de89d1b63fba05d",
+         "772005c3db0d76a4b153f6ea91282d5d0ff84008",
          []
         ],
         "canvas-display-p3-drawImage-expected.txt": [
@@ -345803,7 +345964,7 @@
          []
         ],
         "offscreencanvas.resize.html.ini": [
-         "838a7c83e819ab6e4b0ef3699b71f944032657c4",
+         "a06b13cb16688f2325fd303ce4be1c9859d05b2c",
          []
         ],
         "offscreencanvas.transferrable-expected.txt": [
@@ -346388,7 +346549,7 @@
       []
      ],
      "cross-origin-isolated-permission-worker.https.window.js.ini": [
-      "1ed2310587c0905534e7e6c4c9f12f0866ffc80f",
+      "1cda697821f262d6ecbc160bfecc3bb8adceeec1",
       []
      ],
      "data.https.html.headers": [
@@ -346565,6 +346726,10 @@
       "8df98474b589d070992677cb0134bd47bd0509c4",
       []
      ],
+     "require-corp-sw.https.html.ini": [
+      "b50fc629a57bb6c79a8898abdd97c2d97790b0f2",
+      []
+     ],
      "require-corp.https.html.headers": [
       "6604450991a122e3e241e40b1b9e0516c525389d",
       []
@@ -346794,7 +346959,7 @@
       []
      ],
      "coep-with-cross-origin.https.html.ini": [
-      "d954e94f34207b5d428b2577a83a844aa72e2d4b",
+      "73772d1f3b6da1c5b2508ba4b1f950e7ae1037f8",
       []
      ],
      "coep-with-same-origin.https.html.headers": [
@@ -346834,7 +346999,7 @@
       []
      ],
      "coop-sandbox-redirects-cuts-opener.https.html.ini": [
-      "f4952b86b706b8a5668ceb7173ab9ca9167c667a",
+      "62c1867171ac53eb24c8ab852071dfba59adff51",
       []
      ],
      "coop-sandbox.https.html.headers": [
@@ -346854,7 +347019,7 @@
       []
      ],
      "header-parsing-repeated.https.html.ini": [
-      "6c6d744722cce15a515c5f0f8755f60cde65d9a7",
+      "0ff031216698bcc77afe7110f069d5f7d0fd27e9",
       []
      ],
      "historical": {
@@ -346863,7 +347028,7 @@
        []
       ],
       "popup-same-origin-unsafe-allow-outgoing-with-cross-origin.https.html.ini": [
-       "cdf77335bc55b32c6945120f31179f41d3558e3f",
+       "1c7624e8d0868560649c5878e334ebe69933f19f",
        []
       ],
       "popup-same-origin-unsafe-allow-outgoing-with-same-origin.https.html.headers": [
@@ -346902,10 +347067,6 @@
        "ab7b28948150ff64101ef080b0d9c7cc9a6a34d2",
        []
       ],
-      "popup-same-site-unsafe-allow-outgoing-with-same-site.https.html.ini": [
-       "596920f4928fd12de33820295d77abde57b89138",
-       []
-      ],
       "popup-same-site-with-cross-origin.https.html.headers": [
        "34bd099a302f893f92586241ea38aac812bf28d0",
        []
@@ -346915,7 +347076,7 @@
        []
       ],
       "popup-same-site-with-same-origin.https.html.ini": [
-       "71fe175cf71dcb57025122ae5251bb2abfbcfbaa",
+       "4a8a69c67ce65a40e37fc817e5763a78a2cf951a",
        []
       ],
       "popup-same-site-with-same-site.https.html.headers": [
@@ -346940,7 +347101,7 @@
       []
      ],
      "iframe-popup-same-origin-allow-popups-to-same-origin.https.html.ini": [
-      "fbb8b7ea360ac444a02016d6e916050745585372",
+      "108522d64182da1943e6c4933075380ac3f4e9a2",
       []
      ],
      "iframe-popup-same-origin-allow-popups-to-unsafe-none.https.html.headers": [
@@ -346948,7 +347109,7 @@
       []
      ],
      "iframe-popup-same-origin-allow-popups-to-unsafe-none.https.html.ini": [
-      "a82ac75288fa958187916d7adbd091663311be06",
+      "4af327b4dcff11a7d9c3a5cd3cc1d470c0fcf53a",
       []
      ],
      "iframe-popup-same-origin-to-same-origin.https.html.headers": [
@@ -346964,7 +347125,7 @@
       []
      ],
      "iframe-popup-same-origin-to-unsafe-none.https.html.ini": [
-      "8eec1633c1b737c49a14249cda902fca3a7199de",
+      "2cb63b2f9ffc698216d4f38eaf491e06ddabdab3",
       []
      ],
      "iframe-popup-unsafe-none-to-same-origin.https.html.headers": [
@@ -346984,7 +347145,7 @@
       []
      ],
      "javascript-url.https.html.ini": [
-      "3896beaee97cea889b95f45b64bb76096e9f4e8c",
+      "e78b4a650980824f5a49461a8ef7795209193b4c",
       []
      ],
      "navigate-to-aboutblank.https.html.ini": [
@@ -347003,10 +347164,18 @@
       "46ad58d83bf6e98913ca4c564b7acb8f19fa0093",
       []
      ],
+     "popup-redirect-cache.https.html.ini": [
+      "d4b9aeaafdc2d139ff87c42cf011ee7fd32dabcd",
+      []
+     ],
      "popup-same-origin-allow-popups-with-cross-origin.https.html.headers": [
       "d83ed86fb9b5d159b9f380424887402edc96cb75",
       []
      ],
+     "popup-same-origin-allow-popups-with-cross-origin.https.html.ini": [
+      "ddd028e2ea76a0899bed36a40e2607dc3c213bb7",
+      []
+     ],
      "popup-same-origin-allow-popups-with-same-origin.https.html.headers": [
       "d83ed86fb9b5d159b9f380424887402edc96cb75",
       []
@@ -347032,7 +347201,7 @@
       []
      ],
      "popup-same-origin-with-same-origin.https.html.ini": [
-      "f1d91d7c42c54ce00e774821719b575fe381a891",
+      "817cd70b3ddfb062864c8a571da9f0020743a05b",
       []
      ],
      "popup-same-origin-with-same-site.https.html.headers": [
@@ -347051,14 +347220,14 @@
       "073ce7adfbd81cb7c0b2f91f96c8349b6677f26c",
       []
      ],
-     "popup-unsafe-none-with-same-site.https.html.ini": [
-      "c087e20995b049d95a5478c10ced52f7f2ee2b68",
-      []
-     ],
      "popup-unspecified-with-cross-origin.https.html.ini": [
       "6f1e872520590723fb96d2c67837a02d78f18977",
       []
      ],
+     "popup-unspecified-with-same-site.https.html.ini": [
+      "ccfbd06e94ece9ba5235adb3de7db4a300584ec4",
+      []
+     ],
      "popup-with-structured-header.https.html.headers": [
       "46ad58d83bf6e98913ca4c564b7acb8f19fa0093",
       []
@@ -347078,7 +347247,7 @@
         []
        ],
        "access-from-coop-page-to-openee_coop-ro_cross-origin.https.html.ini": [
-        "c525d52f14f5dc4556b20fd1965377e84ca1469b",
+        "9857e85d4f4a705a19df4c474d8ec72e63cbfd0d",
         []
        ],
        "access-from-coop-page-to-opener_coop-ro.https.html.ini": [
@@ -347090,19 +347259,19 @@
         []
        ],
        "access-from-coop-page-to-other_coop-ro.https.html.ini": [
-        "dd923b49d338c61b1c701f72a0fe7c9d21a51306",
+        "ad1f3046344665f83be193b63ccc46c1a691c671",
         []
        ],
        "access-from-coop-page-to-other_coop-ro_cross-origin.https.html.ini": [
-        "56b8e031cc1caaee836abb34f380ea522c92155c",
+        "dd5c96b2e6d713af4e5402595b2b6e459a64352e",
         []
        ],
        "access-to-coop-page-from-openee_coop-ro.https.html.ini": [
-        "bf827e7921d353b76aa69e62ef999129f23aa7d3",
+        "45761bcac8beb541cddf6110a9432b612089da0b",
         []
        ],
        "access-to-coop-page-from-openee_coop-ro_cross-origin.https.html.ini": [
-        "f90dfadf0031050388abab28128b4c575ee0b842",
+        "a609388561b9a0d87902cecb4a1bef2c20277b53",
         []
        ],
        "access-to-coop-page-from-opener_coop-ro.https.html.ini": [
@@ -347134,11 +347303,11 @@
         []
        ],
        "property-focus.https.html.ini": [
-        "2db32f1edef3c5c1def240fb6fa5fbf980b5e42b",
+        "4f240a0e09a93beb9337afb824914e037707206d",
         []
        ],
        "property-frames.https.html.ini": [
-        "01f4fcd2960d85861b72c469942031529268ec2f",
+        "f6d2d333bad5ce93e388b23469807f3f04336d18",
         []
        ],
        "property-indexed-getter.https.html.headers": [
@@ -347146,11 +347315,11 @@
         []
        ],
        "property-indexed-getter.https.html.ini": [
-        "7e33cb0a449b6bfdc45ef891f7f9a330c085d4c5",
+        "9f7656cb32626312d13c4eee33831b29d2edf760",
         []
        ],
        "property-length.https.html.ini": [
-        "3435757a0b1006729ce145e2a044df8233d8f4da",
+        "20d6b2365f8b4923966dc04baee0b70e9f004ed3",
         []
        ],
        "property-location-get.https.html.ini": [
@@ -347182,7 +347351,7 @@
         []
        ],
        "property-self.https.html.ini": [
-        "419a9ae31b2f55d558166aee54140111ac4bdf1b",
+        "189215bc7f4f6a65585c207e1ea409f9b0320ac5",
         []
        ],
        "property-top.https.html.ini": [
@@ -347216,11 +347385,11 @@
         []
        ],
        "reporting-redirect-with-same-origin-allow-popups.https.html.ini": [
-        "80bec0a81f7a9d7d09671ee611d5694ba88b57c3",
+        "18bb1f2390bffd060cf2e565cb466a01b64357b2",
         []
        ],
        "reporting-redirect-with-unsafe-none.https.html.ini": [
-        "f6002819e54c01078fa964d9f2565d8801071f6b",
+        "7b11492b982a5f967e17720d55c7e546173d1e25",
         []
        ]
       },
@@ -347318,7 +347487,7 @@
         []
        ],
        "reporting-redirect-with-same-origin-allow-popups.https.html.ini": [
-        "c37bd366ad7b8255bd6660e38a88a0286d6833e6",
+        "cf651f591293daa7d620c38c1811627d08a3d558",
         []
        ]
       },
@@ -347337,6 +347506,10 @@
        ]
       }
      },
+     "resource-popup.https.html.ini": [
+      "950ed5a35712e468a2528cbffb9fe22d1c6cce11",
+      []
+     ],
      "resources": {
       "call-functionCalledByOpenee.html": [
        "d0ff0b723e0f31c6ed635db95851769a598ce2a1",
@@ -347418,7 +347591,7 @@
         []
        ],
        "iframe-popup-to-soap.https.html.ini": [
-        "dd70f03b2e8d09c23ac6f0ee6e91bb37edf4b0e9",
+        "519adab1d65e195ad90af94e99fce2cb08447a80",
         []
        ],
        "iframe-popup-to-un.https.html.headers": [
@@ -347522,7 +347695,7 @@
         []
        ],
        "popup-un.https.html.ini": [
-        "3825ee4b58097e7262bc7d1e1cff1fcc0684f5fa",
+        "245d44f04bd04c497a587d0b1f23c067a3808476",
         []
        ],
        "popup-with-cross-origin.https-expected.txt": [
@@ -347622,7 +347795,7 @@
         []
        ],
        "document-cookie.html.ini": [
-        "b9b8e1df1df72eda3ae73786de4a36e371feb0b8",
+        "87ed3754a09930097225b085f7f8528dd5256968",
         []
        ],
        "document-lastModified.html.headers": [
@@ -348115,7 +348288,7 @@
      ],
      "render-blocking": {
       "parser-inserted-async-script.tentative.html.ini": [
-       "632b0b9d9583e1f5040e25fd4ff40981355387d4",
+       "fdd5875612bea98ead2805b8abb4e4b78c658752",
        []
       ],
       "remove-attr-script-keeps-blocking.tentative.html.ini": [
@@ -351976,7 +352149,7 @@
         ]
        },
        "spin-by-blocking-style-sheet.html.ini": [
-        "5d064a0efd34fa600d4a720568e8d1bb5f923168",
+        "a197e599d7ad4625823b89923332040c0c30d315",
         []
        ],
        "update-the-rendering.html.ini": [
@@ -353767,7 +353940,7 @@
          []
         ],
         "resource-selection-invoke-audio-constructor.html.ini": [
-         "832554d322a83d49960e5586a1b22eb166cf8708",
+         "0fb6a8301e84fda502c54a63dd632e2fed3fe47a",
          []
         ],
         "resource-selection-invoke-in-sync-event-expected.txt": [
@@ -353791,7 +353964,7 @@
          []
         ],
         "resource-selection-invoke-insert-source.html.ini": [
-         "84d99c11b909587f5f3ee794506f8dcd9433ff60",
+         "b9e47853f8f672609ae70204e6051675a51d8f81",
          []
         ],
         "resource-selection-invoke-load.html.ini": [
@@ -353799,7 +353972,7 @@
          []
         ],
         "resource-selection-invoke-pause-networkState.html.ini": [
-         "c6704d782d88e8e1483c1f4b3b7197b64b02b94d",
+         "67499a6e3a23b902b3e8a7803cfe2769abf7d560",
          []
         ],
         "resource-selection-invoke-pause.html.ini": [
@@ -353811,7 +353984,11 @@
          []
         ],
         "resource-selection-invoke-remove-from-document-networkState.html.ini": [
-         "a6671089a66cad350adac4fba08574b58f081481",
+         "33e2bbbf2db5f400c6b95c9f46e33d3e663a43c5",
+         []
+        ],
+        "resource-selection-invoke-remove-src.html.ini": [
+         "b11e0fc02e6b89469cdfa808639f29d7b0eac3fe",
          []
         ],
         "resource-selection-invoke-set-src-not-in-document.html.ini": [
@@ -353819,7 +353996,7 @@
          []
         ],
         "resource-selection-invoke-set-src.html.ini": [
-         "c0581652ef344bf42e125cffdd9d6761b9b91d4b",
+         "0c0262e0ddddb49fb414c2e8affdc4669f01c8a0",
          []
         ],
         "resource-selection-pointer-control-expected.txt": [
@@ -354298,7 +354475,7 @@
           []
          ],
          "track-cue-rendering-after-controls-removed.html.ini": [
-          "9da26fdbc57f67f3aced7ccda3d41a27aa355330",
+          "daf8eb0fcf763e85af944ff72456aafca6ed10e1",
           []
          ],
          "track-cue-rendering-line-doesnt-fit-ref.html": [
@@ -354764,19 +354941,19 @@
         []
        ],
        "sandbox-top-navigation-child-special-cases.tentative.sub.window.js.ini": [
-        "6781bb486207854ad6f5d90ac9497947459d5855",
+        "8e7326e5cfd2bd23d749e9ce14bae2dab527dc59",
         []
        ],
        "sandbox-top-navigation-child.tentative.sub.window.js.ini": [
-        "ce6897a13e1dfb8c0b1412cb27eff9d89af02617",
+        "4d916ea98cfcaa51a6b60d1bddcd140e3d53fb5e",
         []
        ],
        "sandbox-top-navigation-escalate-privileges.tentative.sub.window.js.ini": [
-        "ae3370f78198955144375363786e6be34e977508",
+        "8a0440cc94d8893999376b60e056bdea0444d13b",
         []
        ],
        "sandbox-top-navigation-grandchild.tentative.sub.window.js.ini": [
-        "0f880d19a60c4a6828e6646a4ffe8c3be47f1fb8",
+        "ddb32689d41aad47b811d892ac5f263f1d06fd94",
         []
        ],
        "sandbox_026.htm.ini": [
@@ -355272,6 +355449,10 @@
         }
        },
        "srcset": {
+        "avoid-reload-on-resize.html.ini": [
+         "68252033ef1ac29abb24d3e42fe54d7a5c977b7a",
+         []
+        ],
         "common.js": [
          "d4d2c7534c7fadac56a59a09455180f57697a6d9",
          []
@@ -355481,12 +355662,8 @@
          []
         ]
        },
-       "text-plain.window.js.ini": [
-        "1f34e29cda54403b854bc51c9bf187be85837072",
-        []
-       ],
        "urlencoded2.window.js.ini": [
-        "f3533e60e710331c864e6dfde3ce03aef932f62f",
+        "d66b56e19d3af18745f284c6624621b8cffd4a0a",
         []
        ]
       },
@@ -355820,7 +355997,7 @@
         []
        ],
        "selectmenu-form-state-restore.tentative.html.ini": [
-        "839c0e10757f54eaeb1867d04cb31ad635727a03",
+        "7f86f19ec658628f2255177cc9d150b4577416ef",
         []
        ],
        "selectmenu-keyboard.tentative.html.ini": [
@@ -355832,7 +356009,7 @@
         []
        ],
        "selectmenu-option-arbitrary-content-displayed.tentative.html.ini": [
-        "690e048f5adf5685136b1b79a5e9b85597c29a26",
+        "5ef441dbdd5d3ee16a998e578e630041e79e6ebf",
         []
        ],
        "selectmenu-option-arbitrary-content-not-displayed-ref.tentative.html": [
@@ -356228,7 +356405,7 @@
         []
        ],
        "activation-behavior.window.js.ini": [
-        "ecf50d43fb2374c3282b88318c1ccb692e69fcf2",
+        "51f89168d66291ee98e720c1e12744e04dbd0a77",
         []
        ]
       },
@@ -356350,6 +356527,14 @@
        "d0f0055a4b9404ca8390af00d2096d8eaa339943",
        []
       ],
+      "popover-anchor-nested-display-ref.html": [
+       "17311f218b24d49fd72dd8c9e330efb13de43990",
+       []
+      ],
+      "popover-anchor-nested-display.tentative.html.ini": [
+       "f05095854ab717ee92a2c7d5169cea5d117debb3",
+       []
+      ],
       "popover-anchor-scroll-display-ref.tentative.html": [
        "dbaa30b047f7d933cbbaabe769f86976c6098dd1",
        []
@@ -360527,7 +360712,7 @@
      []
     ],
     "idle-detection-allowed-by-permissions-policy.https.sub.html.ini": [
-     "2e5bbacb24c3289077d6bfd1b274b58fc382521f",
+     "f6cc69da3ffc659493cd3096afde6da2c36f5844",
      []
     ],
     "idle-detection-default-permissions-policy.https.sub.html.ini": [
@@ -361606,7 +361791,7 @@
       []
      ],
      "webtransport-h3.https.sub.any.js.ini": [
-      "92b479919ad3fb369c0c278f99f9ca7f7935b8cf",
+      "bd9c4f877884896eb35aa2401c2fdd05a8acebf3",
       []
      ]
     },
@@ -363063,6 +363248,10 @@
      []
     ],
     "v2": {
+     "animated-occlusion.html.ini": [
+      "b8079d212e7666debbaf356e216d761002884ba3",
+      []
+     ],
      "cross-origin-effects.sub.html.ini": [
       "c224c903c36f273e16c9764e5345643cd909372e",
       []
@@ -363273,7 +363462,7 @@
      ]
     },
     "first-paint-equals-lcp-text.html.ini": [
-     "4f9cabd272d3f0602b5d4eddcbca32a89f32df20",
+     "88987019ca6d803177a045f9f23a3ee6e0124dbe",
      []
     ],
     "image-upscaling-expected.txt": [
@@ -363292,6 +363481,18 @@
      "aa1921a72fc90346d33624a48f77e0ec51733ec2",
      []
     ],
+    "non-tao-image-load-before-fcp-render-after.tentative.html.ini": [
+     "5cec90b70b6f916c87569ed721fba09a761fa484",
+     []
+    ],
+    "non-tao-image-load-before-fcp-render-at-fcp.tentative.html.ini": [
+     "0cc5e0766b7b2347d72b0b000f9a597906aca35a",
+     []
+    ],
+    "non-tao-image-subsequent-lcp-candidate.tentative.html.ini": [
+     "fa8ed91399aeb87926badddd5fcf41b4421be8b6",
+     []
+    ],
     "resources": {
      "iframe-stores-entry.html": [
       "cd600254805570deab8447ea843657d7f268b7c5",
@@ -363302,7 +363503,7 @@
       []
      ],
      "largest-contentful-paint-helpers.js": [
-      "190df0383ddae9271ffe63c6ac109af5c9875f09",
+      "043587ca654c44fb97ae41e32cff094852199365",
       []
      ],
      "lcp-sw-from-cache.js": [
@@ -363438,7 +363639,7 @@
       []
      ],
      "coep-early-hints-require-corp-final-none.h2.window.js.ini": [
-      "ade87392f27a301fada50aa301e309d48a95240b",
+      "092a8a73101c5b021aeaa0caa5cac1c8c484baf3",
       []
      ],
      "csp-early-hints-absent-final-absent.h2.window.js.ini": [
@@ -363458,7 +363659,7 @@
       []
      ],
      "csp-early-hints-allowed-final-allowed.h2.window.js.ini": [
-      "9071ef7f0474c8d2bbe0e1e89b29749e23406017",
+      "8774e47e65660d86070ba1ff2678709bf339e585",
       []
      ],
      "csp-early-hints-allowed-final-disallowed.h2.window.js.ini": [
@@ -363470,7 +363671,7 @@
       []
      ],
      "csp-early-hints-disallowed-final-allowed.h2.window.js.ini": [
-      "e935dbe26b09071d110aba686e56187fe3131622",
+      "66e9cf6d20efdc07c642c37028aa843e0c7d9917",
       []
      ],
      "csp-early-hints-disallowed-final-disallowed.h2.window.js.ini": [
@@ -363498,11 +363699,11 @@
       []
      ],
      "preload-finished-while-receiving-final-response-body.h2.window.js.ini": [
-      "4e0b8871c094ca6b78ad2a8a79dc3c3279204189",
+      "77d6e57def5554ba0670c2d4cc78b7a575121f2a",
       []
      ],
      "preload-in-flight-when-consumed.h2.window.js.ini": [
-      "34414adcd731ec4938977b572049059342d666a4",
+      "426ffabfb947d4c1f5760a11da7de52090eecc8d",
       []
      ],
      "preload-initiator-type.h2.window.js.ini": [
@@ -363526,7 +363727,7 @@
       []
      ],
      "redirect-cross-origin-between-early-hints.h2.window.js.ini": [
-      "3a2c386088d8cdcec6a6717bb3058aba1b5d7f03",
+      "46629ffe874fbda3ae4d17598c002faf75e2e3bc",
       []
      ],
      "redirect-cross-origin.h2.window.js.ini": [
@@ -363793,7 +363994,7 @@
      []
     ],
     "longtask-in-sibling-iframe-crossorigin.html.ini": [
-     "29ede04c48735a05b38f3c19d7ef1a44d44088c6",
+     "ed8bf35c2602633171ed718b4e001e124374c364",
      []
     ],
     "resources": {
@@ -364503,7 +364704,7 @@
        ]
       },
       "ignored-properties-001.html.ini": [
-       "7941919561316fab213d4f863cb30e4b6d40d71f",
+       "edd5bed6dd9ca395b4fdd4d7d739c6e38afa5af7",
        []
       ],
       "legacy-scriptminsize-attribute-ref.html": [
@@ -365567,7 +365768,7 @@
      []
     ],
     "mediasource-avtracks.html.ini": [
-     "13dc4e501a32946d1ec7209a6ef24198be6f73d2",
+     "c55b56b1cfa5bd96e4e08d9eeb7c27d103cbebbc",
      []
     ],
     "mediasource-buffered-expected.txt": [
@@ -365639,7 +365840,7 @@
      []
     ],
     "mediasource-duration.html.ini": [
-     "0b2951936e8b8e0ad283aee34daf880838cc29c8",
+     "bbd5a8c183faa146bb07d8e8a87972dac298cd15",
      []
     ],
     "mediasource-endofstream-expected.txt": [
@@ -365647,7 +365848,7 @@
      []
     ],
     "mediasource-endofstream.html.ini": [
-     "da1986cfb03d9426b771aa161d6229506ce30f5b",
+     "3fe53b824f23251fb14d4c2cbe1fbe4b7d85fa19",
      []
     ],
     "mediasource-invalid-codec.html.ini": [
@@ -366930,6 +367131,10 @@
       []
      ]
     },
+    "detach_iframe_during_open.https.window.js.ini": [
+     "1f3251675043c9cf26f7cb81286b9b9ab306725a",
+     []
+    ],
     "resources": {
      "opaque-origin-sandbox.html": [
       "b3490c3341a892052ccab603722059cf8bca0cd5",
@@ -367400,7 +367605,7 @@
     },
     "scroll-behavior": {
      "after-transition-intercept-handler-modifies.html.ini": [
-      "411718d4f69c535ee3832109bafba1326a81bd41",
+      "b7b9b420fb1b7866b53a0543534bbef4dac2abb7",
       []
      ]
     },
@@ -367547,7 +367752,7 @@
      ]
     },
     "test-performance-attributes.sub.html.ini": [
-     "45c94f0b636fb67be47bc2ed2e58f04d5d854ce1",
+     "c0db19d2b44e93b9bc5dfbf81978d9d3be9328dd",
      []
     ]
    },
@@ -367932,7 +368137,7 @@
       []
      ],
      "fcp-invisible-3d-rotate.html.ini": [
-      "7f20bcb538b2e0e1a006291b0dd9d376ab8c41cc",
+      "a2377dfd60854f58b7fcb47617ab05ca9c63db17",
       []
      ],
      "fcp-invisible-scale-transition.html.ini": [
@@ -368323,7 +368528,7 @@
      []
     ],
     "pending_beacon-sendondiscard.tentative.https.window.js.ini": [
-     "fc638a30a8fe437b234a6f8581bed28133f27d8a",
+     "904cd16d63b69304154d6ad618ebd8417352c2f9",
      []
     ],
     "pending_beacon-sendonhidden.tentative.https.window.js.ini": [
@@ -368362,10 +368567,6 @@
      "7e82a7530d51750cc6f896ed11b193c7d2fd2466",
      []
     ],
-    "cross-origin-non-tao-image.sub.html.ini": [
-     "433428ebc0987fd401d888c05bcd7599ecaab6a0",
-     []
-    ],
     "idlharness-shadowrealm.window-expected.txt": [
      "2db45e1a047c174b9c072cddd88d6f749c59eb7c",
      []
@@ -368375,7 +368576,7 @@
      []
     ],
     "navigation-id-element-timing.tentative.html.ini": [
-     "0859b395696db39e7a84a974887edbf337a07fd2",
+     "11ce5f11dc3b16e622a686bf00f6e4ae217ad769",
      []
     ],
     "navigation-id-long-task-task-attribution.tentative.html.ini": [
@@ -368416,7 +368617,7 @@
       []
      ],
      "performance-navigation-timing-same-origin-bfcache.tentative.window.js.ini": [
-      "e10b0da9752da7e24fa38ad88177155624ac8d17",
+      "680f89318173031070f208f826e74056fb57e410",
       []
      ],
      "performance-navigation-timing-same-origin-replace.tentative.window.js.ini": [
@@ -368433,6 +368634,10 @@
      []
     ],
     "resources": {
+     "child-frame.html": [
+      "846979358ebbca7a2af6bdb9c02225d67b162442",
+      []
+     ],
      "empty.html": [
       "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391",
       []
@@ -368449,6 +368654,10 @@
       "02aafbb5c78368c69dbf1cdae81b432d988e17d1",
       []
      ],
+     "parent-frame-with-child.sub.html": [
+      "32dd4cb48bc2cba536b991dcfdff9e9adbd649c9",
+      []
+     ],
      "postmessage-entry.html": [
       "ef5be73395b49d895a2589be547a7f3950f416ba",
       []
@@ -372010,10 +372219,6 @@
      "4fe4a41af350f4987197ffa3727c3c9a0b40100a",
      []
     ],
-    "callback-exception.html.ini": [
-     "bac37b07ca084fb8a6474efda72b47b2c8f0d392",
-     []
-    ],
     "callback-iframe.html.ini": [
      "2c234a543927877ad35b1dc5d40c115f745149a3",
      []
@@ -372047,11 +372252,15 @@
      []
     ],
     "deadline-after-expired-timer.html.ini": [
-     "fc00d2e27e8fe14c0bf9988886f23467019a7da7",
+     "a78693b02db6f2cc33062595c3543ca9629ff8d9",
      []
     ],
     "deadline-max-rAF-dynamic.html.ini": [
-     "a3741db8a2ddde7857506d5dd8e735ac09686821",
+     "3dbee2d405f07199581fab3a2352b0f24261e47b",
+     []
+    ],
+    "deadline-max-rAF.html.ini": [
+     "c4cc2f5d3b2f644da7c7b705736f467e3d367f8b",
      []
     ],
     "deadline-max-timeout-dynamic.html.ini": [
@@ -372198,16 +372407,12 @@
      "33006cf002b9b9c3f4fb4c4757916052ae12fe92",
      []
     ],
-    "nested-context-navigations-iframe.html.ini": [
-     "b48c0ae47629462318187fbbf7c55e8ed09ccedc",
-     []
-    ],
     "no-entries-for-cross-origin-css-fetched-memory-cache.sub-expected.txt": [
      "9c172388cd5a0351593fc8dedb00168579aca645",
      []
     ],
     "no-entries-for-cross-origin-css-fetched-memory-cache.sub.html.ini": [
-     "4e976507ccc48d68ba35f016f13944925707e5bc",
+     "a118839080bf1d7526382eeeacd03b1a67509764",
      []
     ],
     "object-not-found-after-cross-origin-redirect.html.ini": [
@@ -374542,7 +374747,7 @@
       []
      ],
      "cache-add.https.any.js.ini": [
-      "bbd80aa2beaf7a71ac9564feabb7656e5e1effb9",
+      "38b9e327af69432215a0104ade3ac575bff5e0e9",
       []
      ],
      "cache-add.https.any.serviceworker-expected.txt": [
@@ -376724,7 +376929,7 @@
       []
      ],
      "unregister-immediately-during-extendable-events.https.html.ini": [
-      "a10e0a97f8a1fdbfdc77084669d8736e8e191076",
+      "efbf9346870b90ced408f20bada21a5b986ed3bb",
       []
      ],
      "unregister-then-register-new-script.https-expected.txt": [
@@ -377511,6 +377716,12 @@
       "53d3be246cc879df8cd5fbba1dfcd751f27eaf33",
       []
      ],
+     "no-vary-search": {
+      "prefetch-single.https.html.ini": [
+       "68ee4fce4f7110c2f2a387f2ba6dd920f5eff793",
+       []
+      ]
+     },
      "out-of-document-rule-set.https.html.ini": [
       "0ae4bf4b18b59b4d492e43ac972073633a111352",
       []
@@ -377536,7 +377747,7 @@
       []
      ],
      "referrer-policy.https.html.ini": [
-      "05ab2a4f568824ca8040ece23a44c1ebcb42cd25",
+      "2e3eaa49207ede3b7b348ab59d812f0e7427823f",
       []
      ],
      "resources": {
@@ -377565,7 +377776,7 @@
        []
       ],
       "utils.sub.js": [
-       "a306b8cea9817d7bd907f3940acd9718530e97f9",
+       "ea70939aff0853e9a1f4f26375231c329945c266",
        []
       ]
      },
@@ -377636,7 +377847,7 @@
       []
      ],
      "indexeddb.html.ini": [
-      "3dd4ff19b55d1fb59cc6a5c63cbde535ba936cd9",
+      "d3393972fbdada59a2f814402fbe608d71388d0c",
       []
      ],
      "local-storage.html.ini": [
@@ -377648,7 +377859,7 @@
       []
      ],
      "media-autoplay.html.ini": [
-      "12abdfdd57a96f1d46e72d7aab8b6fb66a315608",
+      "dcad0990b062253b60381f1c830be6cd905126e5",
       []
      ],
      "navigation-intercepted-by-service-worker.https.html.ini": [
@@ -378082,7 +378293,7 @@
       ]
      },
      "response-code-non-successful.html.ini": [
-      "df2c0c92e35d0ece3990dcac0581da62397ff6ea",
+      "4b1a60622c9eb09cb47da41d3803c6036bca8116",
       []
      ],
      "response-code-successful.html.ini": [
@@ -378094,7 +378305,7 @@
       []
      ],
      "restriction-audio-setSinkId.https.tentative.html.ini": [
-      "e54044024d5bbf9454e1666659e2f72596a33431",
+      "ef53be2d8960978ae4be75925af9bd64274ad241",
       []
      ],
      "restriction-background-fetch.https.html.ini": [
@@ -378110,7 +378321,7 @@
       []
      ],
      "restriction-bluetooth.tentative.https.html.ini": [
-      "8dacf33334b4b93b78abf35afd65e007ea45c378",
+      "430d214a46243795c50eef1a5dbe7e0ecce35eac",
       []
      ],
      "restriction-broadcast-channel.html.ini": [
@@ -378118,7 +378329,7 @@
       []
      ],
      "restriction-dedicated-worker.https.html.ini": [
-      "4aeadc53fb826a843e3a9ceedf09e3d68e77daa4",
+      "9362fa3dfd23e482e1bc3618d11486040fcb6064",
       []
      ],
      "restriction-encrypted-media-unsupported-config.https.html.ini": [
@@ -378126,7 +378337,7 @@
       []
      ],
      "restriction-encrypted-media.https.html.ini": [
-      "a72b2256c439a5be422d5893a270d1937bab5749",
+      "e09bff4cfff69008182740923a0853b57ab7b591",
       []
      ],
      "restriction-focus.html.ini": [
@@ -378142,7 +378353,7 @@
       []
      ],
      "restriction-media-auto-play-attribute.html.ini": [
-      "8abf5802b335c8d5bbfdb1c5d75db484fa20ce1c",
+      "66ea6cb7d2ce1982cbd5b55389211dd5ff9b5f9e",
       []
      ],
      "restriction-media-camera.https.html.ini": [
@@ -378186,7 +378397,7 @@
       []
      ],
      "restriction-prompt-by-before-unload.html.ini": [
-      "465f205d31b2c64fbf8abc8dfd6932d663df3e38",
+      "cf5f8a1a1e34f51dad6039bd7d981f01ca4128c8",
       []
      ],
      "restriction-push.https.html.ini": [
@@ -378222,7 +378433,7 @@
       []
      ],
      "restriction-service-worker-postmessage.https.html.ini": [
-      "d9875a35a59f9a50af436bfdaa706e7562fd89cd",
+      "3cb7332888cd824bca0b46e9f2aa661e57c49dcc",
       []
      ],
      "restriction-service-worker-unregister.https.html.ini": [
@@ -378234,7 +378445,7 @@
       []
      ],
      "restriction-speech-synthesis.html.ini": [
-      "ad1ad4c2b02022f3bdfe9a9b47eb552ff4a2e77f",
+      "6984300e6cd9030dfa2b49775fba95f141b0a14c",
       []
      ],
      "restriction-storage-persist.https.html.ini": [
@@ -378250,7 +378461,7 @@
       []
      ],
      "restriction-web-locks.https.html.ini": [
-      "fee948408ed9489a5b2ce822478e4fe45c917103",
+      "d55497aebf9840b327c385139090af4fabebdd0b",
       []
      ],
      "restriction-web-nfc.https.html.ini": [
@@ -378278,7 +378489,7 @@
       []
      ],
      "restriction-window-move.html.ini": [
-      "1f07b4c2d4998f2362163af38a7b4ab95c7befaa",
+      "241db3c6ae3ceca2e0028c3a96405802838fa6ab",
       []
      ],
      "restriction-window-open.html.ini": [
@@ -378318,7 +378529,7 @@
       []
      ],
      "session-history-subframe-navigation.https.html.ini": [
-      "d28737886f600ede3affd26bca6d257d156b0583",
+      "8d58141abfcb6bd287645bced51be97cbd735ab0",
       []
      ],
      "session-history-subframe-reload.https.html.ini": [
@@ -378346,7 +378557,7 @@
       []
      ],
      "windowclient-navigate-to-cross-origin-url-on-iframe.https.html.ini": [
-      "f3d325bcdc5cb1e0b4140c4894032d13dbf0dfc4",
+      "4f32bf9f34679b0cace1cfef03e9f098f03d544f",
       []
      ],
      "windowclient-navigate-to-same-origin-url-on-iframe.https.html.ini": [
@@ -378448,6 +378659,10 @@
       []
      ]
     },
+    "estimate-indexeddb.https.any.js.ini": [
+     "54ad9ea4ec490cff82b8d5d9b3ead4a633767337",
+     []
+    ],
     "helpers.js": [
      "b524c1b82cfb7b7f8810d1150ff3df8b98f31017",
      []
@@ -378469,7 +378684,7 @@
      []
     ],
     "partitioned-estimate-usage-details-caches.tentative.https.sub.html.ini": [
-     "6c84eadbb2757e6277bf0e02ab91b4d7700f7ce0",
+     "9e9b6ccff86c034fd02bef37a1a37345f77f0311",
      []
     ],
     "partitioned-estimate-usage-details-indexeddb.tentative.https.sub-expected.txt": [
@@ -382175,23 +382390,23 @@
      []
     ],
     "a-element-origin-xhtml.xhtml.ini": [
-     "f6ff3b77c5a93012263b5c2f747176c9596d8ce8",
+     "587670e6d77ecdd6b1f8260a53a624e8ae9961ab",
      []
     ],
     "a-element-origin.html.ini": [
-     "449e0217fb87c0bfd8843164ab8ce4faf84d4eb0",
+     "89d5b4a141756a343fc41554a386b8f4d779432a",
      []
     ],
     "a-element-xhtml.xhtml.ini": [
-     "cb5909d63ea2aeb695b31965d32c76bcc36b5031",
+     "8d16d543b814b0bc8662b11617f0dce90bf2c081",
      []
     ],
     "a-element.html.ini": [
-     "7736b9184b20228ef90b4f9e4e2f44576377da16",
+     "4056041f1da7158b7456e2f8cbb690279c4f765a",
      []
     ],
     "failure.html.ini": [
-     "b9f0d7c008a65da7927374b0cf1fe75d8ba0117f",
+     "b1cd5fdf4f649769a9523ca67ddb374691b137d0",
      []
     ],
     "idlharness-shadowrealm.window-expected.txt": [
@@ -382233,23 +382448,23 @@
      ]
     },
     "toascii.window-expected.txt": [
-     "9229523f31bdbc3d0cbabffb1356063584e591b6",
+     "2ca7f866fa8f2bc9679cc7759a946e8ed8d14108",
      []
     ],
     "toascii.window.js.ini": [
-     "e4394037c14cf70d6f0b7374e61a03664264a0fd",
+     "5cfe13bf17539c78cb35a60876727bad45c9caca",
      []
     ],
     "url-constructor.any.js.ini": [
-     "3eb2f508d6c76a169f4035157765d7a77c9f9afd",
+     "e8741cdf10e71e07c87f40eb35a15c995e38e369",
      []
     ],
     "url-origin.any.js.ini": [
-     "140126f8614603fd6f76e0050a1c6294d1f16b54",
+     "4c9a931290b826b53af59c2fee249a8c637e0252",
      []
     ],
     "url-setters-a-area.window.js.ini": [
-     "7211294d567b04398419a3568bd58abd662e7afd",
+     "5f5f5b47a8333dda3564758ea804a3ab8e59c9e4",
      []
     ],
     "url-setters-stripping.any-expected.txt": [
@@ -382257,7 +382472,7 @@
      []
     ],
     "url-setters-stripping.any.js.ini": [
-     "d2c3ff181aaf8d67b9845359f03b64478e9a4982",
+     "967c34b4033d632115b2e78fc2422cc1e115ffbe",
      []
     ],
     "url-setters-stripping.any.worker-expected.txt": [
@@ -382265,7 +382480,7 @@
      []
     ],
     "url-setters.any.js.ini": [
-     "9d62ebfc729144c9ebb991895b4799e0cc97295f",
+     "e6bf441461c9671218e795f30fc3cb493a488da6",
      []
     ],
     "urlsearchparams-delete.any.js.ini": [
@@ -382275,10 +382490,6 @@
     "urlsearchparams-get.any.js.ini": [
      "c08cea001726431bf4d10f39e5b22d29dd44ee02",
      []
-    ],
-    "urlsearchparams-stringifier.any.js.ini": [
-     "c61e9b0043c06ea9ebdd0120a63eaa63c7038dad",
-     []
     ]
    },
    "urlpattern": {
@@ -382421,7 +382632,7 @@
      []
     ],
     "viewport-resize-event-on-load-overflowing-page.html.ini": [
-     "70faeff4aac2105f8133b1b750aeceba692f2cd6",
+     "2ac64bb9c8ef21614d1aaec4e2fe7b5d97396bb2",
      []
     ],
     "viewport_support.js": [
@@ -383394,10 +383605,6 @@
       ]
      },
      "timelines": {
-      "document-timelines.html.ini": [
-       "08dcd48709615c9a9b1a969e392a50990c32d801",
-       []
-      ],
       "resources": {
        "target-frame.html": [
         "18ee4fd8a23e6a9b4f95068b418d4da1bdef1679",
@@ -383413,7 +383620,7 @@
        []
       ],
       "sibling-iframe-timeline.html.ini": [
-       "7cbc92602ef03c25009b956b00a88be956f47713",
+       "74063bfea9f83d07df139a91936ada2735a1a1a6",
        []
       ],
       "timelines.html.ini": [
@@ -383838,7 +384045,7 @@
       ]
      },
      "reuse-web-bundle-resource.https.tentative.html.ini": [
-      "f41cb516d16d0aa7cf2e8df409ec1ff020340781",
+      "75e8f9c090fccb5342fe29bd95ab5c0cb6f88258",
       []
      ]
     },
@@ -383909,7 +384116,7 @@
      []
     ],
     "query-ordering.tentative.https.html.ini": [
-     "5b3052833226b3c56a6c5240d69293092888d317",
+     "236f81ad0bebb737c8cf24c41b4b375df775be5a",
      []
     ],
     "query.tentative.https.any.js.ini": [
@@ -384229,19 +384436,13 @@
        []
       ]
      },
-     "the-analysernode-interface": {
-      "test-analyser-output.html.ini": [
-       "26534252e2873afc1426a6960a8e0eaad4d93f11",
-       []
-      ]
-     },
      "the-audiobuffer-interface": {
       "acquire-the-content-expected.txt": [
        "74faed557c78367661d2024d85dc5ec4b5c36c85",
        []
       ],
       "acquire-the-content.html.ini": [
-       "d9caee5c742d56817786072054f37c94cf8b6b36",
+       "9f7727640c39787c3e5323636c312a69de355d36",
        []
       ]
      },
@@ -384755,7 +384956,7 @@
      []
     ],
     "videoFrame-canvasImageSource.html.ini": [
-     "9a09fd3f30537bf2838d4dc0aa8b796c8b362008",
+     "177c5acd2a20786e5c3ce6e0540afae68c4197c8",
      []
     ],
     "videoFrame-construction.crossOriginIsolated.https.any.js.headers": [
@@ -385696,7 +385897,7 @@
       []
      ],
      "broadcastchannel-incumbent.sub.html.ini": [
-      "b7ed439511b5a8337fc4d9c8cf7de759e0ea3891",
+      "6742e10278a81a61ec2e9df0addcb894fba30326",
       []
      ],
      "support": {
@@ -385719,7 +385920,7 @@
      }
     },
     "postMessage_Date.sub.htm.ini": [
-     "2ab22ee829dc70f1e8ed1a10c98286e0f0b6f8e6",
+     "dd186255000a3418f074ec18595d102f3a8b3010",
      []
     ],
     "resources": {
@@ -385758,7 +385959,7 @@
       []
      ],
      "020.html.ini": [
-      "921da39ff76a5d9ec53d7a5487f54afcfb1a5e0a",
+      "b35e66e6b3ce06b2885f4808ec48df56a5e255f0",
       []
      ]
     },
@@ -386631,7 +386832,7 @@
      []
     ],
     "supported-stats.https.html.ini": [
-     "66af6f8385edb9e3188e8d416792e56cef1d7d7a",
+     "2051dec74f12d9c721f5c4c9589e2a3c6c95475d",
      []
     ]
    },
@@ -387215,7 +387416,7 @@
        []
       ],
       "abort.any.js.ini": [
-       "0de3ca02a474e3aa1fda33893c88fa421fa6e2d5",
+       "99ed37cec1183c72396670ac40de1b63f6448caf",
        []
       ],
       "abort.any.sharedworker_wpt_flags=h2-expected.txt": [
@@ -387263,7 +387464,7 @@
        []
       ],
       "close.any.js.ini": [
-       "a1a918feb340be35b488ed6081f1f8a0dbf9305b",
+       "46e8e6ab5ea428ab38dc74896360b6c341f4023b",
        []
       ],
       "constructor.any.serviceworker_wpt_flags=h2-expected.txt": [
@@ -388353,10 +388554,6 @@
         "d4bd2b74c2db0e560114109af960c7f38cb38575",
         []
        ],
-       "audio_has_no_subtitles.html.ini": [
-        "6a131e93824e3d10701a21726ed463f487d76d3d",
-        []
-       ],
        "background.png": [
         "6d16cc84c4dd3b8777bd83d97888eaf8d351f6b5",
         []
@@ -391215,7 +391412,7 @@
      []
     ],
     "xrStationaryReferenceSpace_floorlevel_updates.https.html.ini": [
-     "288ec60eb75a5be9bbdd3a2ba22517f9f278e7de",
+     "5011c0dba50647a08d3ef0786348647351abe8aa",
      []
     ],
     "xrView_eyes.https.html.ini": [
@@ -391605,7 +391802,7 @@
      []
     ],
     "dedicated-worker-from-blob-url.window.js.ini": [
-     "282999e38b34fbb255ff0a9021e8419d9f039d02",
+     "b6f8e24e5a0d7742cdd446ae46283964140925f2",
      []
     ],
     "dedicated-worker-in-data-url-context.window-expected.txt": [
@@ -391900,7 +392097,7 @@
         []
        ],
        "report-error-setTimeout-same-origin.sub.any.js.ini": [
-        "c377da75fb4f059f637e853be80518296101ffe7",
+        "19de124df9c4b17ba4648f5d0d03afe8bd214684",
         []
        ],
        "undefined": [
@@ -392370,6 +392567,10 @@
       ]
      },
      "reporting-errors": {
+      "001.html.ini": [
+       "6dd41a78c5dbadee0bf910831e3d4400c1ed1e8c",
+       []
+      ],
       "001.js": [
        "5736666cc3b623cb47af4fc04161c6c5345198c6",
        []
@@ -392407,7 +392608,7 @@
      },
      "structured-clone": {
       "shared.html.ini": [
-       "a499611fdee719b6d7466e844ecbe8e70a4b1bd0",
+       "de991947deb5b8294a9beb4d4066bf5c2d4e3a6c",
        []
       ]
      },
@@ -392720,10 +392921,6 @@
      "b06ea33cd926fd99d890b0dd7544213170a15e8e",
      []
     ],
-    "animation-worklet-import.https.html.ini": [
-     "0b7048512beac8867185ac44dda506974829672a",
-     []
-    ],
     "audio-worklet-service-worker-interception.https.html.ini": [
      "c76f84db537c8f44b86338431f0a13b210a3ae81",
      []
@@ -392937,7 +393134,7 @@
      []
     ],
     "abort-after-send.any.js.ini": [
-     "a0be80752e1abc36ff12ec082de76186d44a3711",
+     "e3c68023891b3a82acfde20d5fb3dc438a9d4a7a",
      []
     ],
     "abort-after-send.any.worker-expected.txt": [
@@ -392974,7 +393171,7 @@
       []
      ],
      "set-blob.any.js.ini": [
-      "94d35edbda0d31485e1ab8bcd3497a2d287fc2cb",
+      "f188fd27f0a9fea72faf2b387b8a966a845f7598",
       []
      ],
      "set-blob.any.worker-expected.txt": [
@@ -434111,7 +434308,7 @@
       ]
      ],
      "font-palette-vs-shorthand.html": [
-      "6c5cc97da94162edf86374032dfd257eeded61c0",
+      "fa8308ef0093ed83ef390ad18133efcc73049f11",
       [
        null,
        {}
@@ -434132,7 +434329,7 @@
       ]
      ],
      "font-shorthand-serialization-prevention.html": [
-      "223c2fa2cbe88468c86eb93067fcbe840bce3c84",
+      "f6a1ea9e610b05287b8a696a54e4647e21a82bfc",
       [
        null,
        {}
@@ -450652,7 +450849,7 @@
         ]
        ],
        "parse.tentative.html": [
-        "8236b326a8c9253b8ef0f52ad754d443f28391e4",
+        "f8a3412488c348a791120e2ad29c48ba87fc78f7",
         [
          null,
          {}
@@ -458856,6 +459053,15 @@
         {}
        ]
       ],
+      "fullscreen-pseudo-class-in-has.html": [
+       "dbbcea250219c369396e686992ea35595a5cba41",
+       [
+        null,
+        {
+         "testdriver": true
+        }
+       ]
+      ],
       "has-complexity.html": [
        "0bdcdec13b97d8a2b3973f86773512474988519c",
        [
@@ -458997,10 +459203,12 @@
        ]
       ],
       "modal-pseudo-class-in-has.html": [
-       "9c487fba16fdad7d07dfc1b867dfddf7a4a021cb",
+       "1bff896d49e97a289adafcd9580533ca3ede3ead",
        [
         null,
-        {}
+        {
+         "testdriver": true
+        }
        ]
       ],
       "not-001.html": [
@@ -483269,7 +483477,7 @@
        ]
       ],
       "request-forbidden-headers.any.js": [
-       "fa5e277abe2f7d3b295a4f4deaaf4379670c74e1",
+       "511ce601e7c45cf39223ef0bd29bb6437ae078a1",
        [
         "fetch/api/basic/request-forbidden-headers.any.html",
         {
@@ -488226,7 +488434,7 @@
        ]
       ],
       "request-headers.any.js": [
-       "cb34e5a790a6370259b45a00c477466da78ceab2",
+       "b73b398013ab0d9bad4ecf6b1844ed29484cb6ee",
        [
         "fetch/api/request/request-headers.any.html",
         {
@@ -551865,6 +552073,34 @@
       {}
      ]
     ],
+    "non-tao-image-load-after-fcp.tentative.html": [
+     "06b065be3ab76809b02d4fa23ce00e4bff31fb6e",
+     [
+      null,
+      {}
+     ]
+    ],
+    "non-tao-image-load-before-fcp-render-after.tentative.html": [
+     "57f29c353560956b89e507d3056c8a43096dc631",
+     [
+      null,
+      {}
+     ]
+    ],
+    "non-tao-image-load-before-fcp-render-at-fcp.tentative.html": [
+     "b209b50c8b2cf2500f6171f55fbec390b367b0c8",
+     [
+      null,
+      {}
+     ]
+    ],
+    "non-tao-image-subsequent-lcp-candidate.tentative.html": [
+     "50f9a229ea6bd99c38e2a40ab4219c1eb96e3cf5",
+     [
+      null,
+      {}
+     ]
+    ],
     "observe-after-untrusted-scroll.html": [
      "c84f922e5e589eea59290ab22731f1bb5eb4415b",
      [
@@ -565735,13 +565971,6 @@
       {}
      ]
     ],
-    "cross-origin-non-tao-image.sub.html": [
-     "210f4e1fa3e8afdf4ac6a97671829bf52d223df1",
-     [
-      null,
-      {}
-     ]
-    ],
     "droppedentriescount.any.js": [
      "4de816bdc42bd8f73defcb07cb694a5fddfe058b",
      [
@@ -566538,6 +566767,50 @@
       {}
      ]
     ],
+    "tentative": {
+     "include-frames-one-local-child-one-local-grandchild.html": [
+      "ee2dadde8f997b4e1902362ca67de8e51a88025a",
+      [
+       null,
+       {}
+      ]
+     ],
+     "include-frames-one-local-child-one-remote-child.sub.html": [
+      "8e1ca9774fa15bdf4abbafe58276b71f6a29032d",
+      [
+       null,
+       {}
+      ]
+     ],
+     "include-frames-one-local-child.html": [
+      "46d4079fd20fb6d9c66cc9b4d6283ee53a13d09d",
+      [
+       null,
+       {}
+      ]
+     ],
+     "include-frames-one-remote-child-one-local-grandchild.sub.html": [
+      "a9efe4146161fbb0296a2612c8e2a489a620c000",
+      [
+       null,
+       {}
+      ]
+     ],
+     "include-frames-one-remote-child.sub.html": [
+      "822924c18b628e137a8d8518345da2bd0b372576",
+      [
+       null,
+       {}
+      ]
+     ],
+     "include-frames-two-local-children.html": [
+      "d0274050dbd271e785a240957bd6965490a09d66",
+      [
+       null,
+       {}
+      ]
+     ]
+    },
     "timing-removed-iframe.html": [
      "43988b21fbbe022d001c7177da87ae0d62f700ae",
      [
@@ -581699,6 +581972,13 @@
      }
     },
     "generic": {
+     "first-meta-changed-after-second-added.http.html": [
+      "2ee5ade8c08ad68b68ba68ce2c3a18dbe89469a1",
+      [
+       null,
+       {}
+      ]
+     ],
      "iframe-src-change.html": [
       "15202a76a1ea3b7ad8deb35cdb33826414d7a95b",
       [
@@ -581820,6 +582100,20 @@
        {}
       ]
      ],
+     "meta-referrer-removed-1.http.html": [
+      "70277885043537471e0e89584479494f90cc0a9d",
+      [
+       null,
+       {}
+      ]
+     ],
+     "meta-referrer-removed-2.http.html": [
+      "42f73e878155ecbafd3997fb7d1844b954900878",
+      [
+       null,
+       {}
+      ]
+     ],
      "meta-tag-in-svg-image.html": [
       "5bdc2c1abfa82777bac25b03a583898c51585ff6",
       [
@@ -581869,6 +582163,13 @@
        {}
       ]
      ],
+     "second-meta-referrer-added-before-first.http.html": [
+      "38fab901fb749026a7e4974f417cc5d260ccda79",
+      [
+       null,
+       {}
+      ]
+     ],
      "subresource-test": {
       "area-navigate.html": [
        "2a4f29654df1da8e3f18359dacf6ce092c325a67",
@@ -593552,6 +593853,115 @@
        {}
       ]
      ],
+     "no-vary-search": {
+      "prefetch-single.https.html": [
+       "543b46c535ae4e1403c0d8e2dc5d0a2cc82d774f",
+       [
+        "speculation-rules/prefetch/no-vary-search/prefetch-single.https.html?1-1",
+        {}
+       ],
+       [
+        "speculation-rules/prefetch/no-vary-search/prefetch-single.https.html?10-10",
+        {}
+       ],
+       [
+        "speculation-rules/prefetch/no-vary-search/prefetch-single.https.html?11-11",
+        {}
+       ],
+       [
+        "speculation-rules/prefetch/no-vary-search/prefetch-single.https.html?12-12",
+        {}
+       ],
+       [
+        "speculation-rules/prefetch/no-vary-search/prefetch-single.https.html?13-13",
+        {}
+       ],
+       [
+        "speculation-rules/prefetch/no-vary-search/prefetch-single.https.html?14-14",
+        {}
+       ],
+       [
+        "speculation-rules/prefetch/no-vary-search/prefetch-single.https.html?15-15",
+        {}
+       ],
+       [
+        "speculation-rules/prefetch/no-vary-search/prefetch-single.https.html?16-16",
+        {}
+       ],
+       [
+        "speculation-rules/prefetch/no-vary-search/prefetch-single.https.html?17-17",
+        {}
+       ],
+       [
+        "speculation-rules/prefetch/no-vary-search/prefetch-single.https.html?18-18",
+        {}
+       ],
+       [
+        "speculation-rules/prefetch/no-vary-search/prefetch-single.https.html?19-19",
+        {}
+       ],
+       [
+        "speculation-rules/prefetch/no-vary-search/prefetch-single.https.html?2-2",
+        {}
+       ],
+       [
+        "speculation-rules/prefetch/no-vary-search/prefetch-single.https.html?20-20",
+        {}
+       ],
+       [
+        "speculation-rules/prefetch/no-vary-search/prefetch-single.https.html?21-21",
+        {}
+       ],
+       [
+        "speculation-rules/prefetch/no-vary-search/prefetch-single.https.html?22-22",
+        {}
+       ],
+       [
+        "speculation-rules/prefetch/no-vary-search/prefetch-single.https.html?23-23",
+        {}
+       ],
+       [
+        "speculation-rules/prefetch/no-vary-search/prefetch-single.https.html?24-24",
+        {}
+       ],
+       [
+        "speculation-rules/prefetch/no-vary-search/prefetch-single.https.html?25-25",
+        {}
+       ],
+       [
+        "speculation-rules/prefetch/no-vary-search/prefetch-single.https.html?26-last",
+        {}
+       ],
+       [
+        "speculation-rules/prefetch/no-vary-search/prefetch-single.https.html?3-3",
+        {}
+       ],
+       [
+        "speculation-rules/prefetch/no-vary-search/prefetch-single.https.html?4-4",
+        {}
+       ],
+       [
+        "speculation-rules/prefetch/no-vary-search/prefetch-single.https.html?5-5",
+        {}
+       ],
+       [
+        "speculation-rules/prefetch/no-vary-search/prefetch-single.https.html?6-6",
+        {}
+       ],
+       [
+        "speculation-rules/prefetch/no-vary-search/prefetch-single.https.html?7-7",
+        {}
+       ],
+       [
+        "speculation-rules/prefetch/no-vary-search/prefetch-single.https.html?8-8",
+        {}
+       ],
+       [
+        "speculation-rules/prefetch/no-vary-search/prefetch-single.https.html?9-9",
+        {}
+       ]
+      ]
+     },
      "out-of-document-rule-set.https.html": [
       "9f2c311715a8a3d9b5ff129809e57dbfe80dceb6",
       [
@@ -599918,6 +600328,13 @@
        {}
       ]
      ],
+     "animateMotion-keyPoints-001.html": [
+      "1397bd6039037f1c8b55685b967042c863a0a705",
+      [
+       null,
+       {}
+      ]
+     ],
      "animateMotion-line.html": [
       "f304de62dcfa8f6762220a1d4e099af304b1da15",
       [
diff --git a/third_party/blink/web_tests/external/wpt/IndexedDB/idbobjectstore_batchGetAll_largeValue.tentative.any.js.ini b/third_party/blink/web_tests/external/wpt/IndexedDB/idbobjectstore_batchGetAll_largeValue.tentative.any.js.ini
index 5b482e4..77a61ba 100644
--- a/third_party/blink/web_tests/external/wpt/IndexedDB/idbobjectstore_batchGetAll_largeValue.tentative.any.js.ini
+++ b/third_party/blink/web_tests/external/wpt/IndexedDB/idbobjectstore_batchGetAll_largeValue.tentative.any.js.ini
@@ -11,8 +11,8 @@
 
 [idbobjectstore_batchGetAll_largeValue.tentative.any.worker.html]
   expected:
-    if (product == "content_shell") and (flag_specific == "disable-site-isolation-trials"): [OK, TIMEOUT]
-    if product == "chrome": OK
+    if (flag_specific == "") and (product == "chrome"): [OK, TIMEOUT]
+    if flag_specific == "disable-site-isolation-trials": OK
     TIMEOUT
   [Get bound range]
     expected: [PASS, TIMEOUT]
@@ -22,3 +22,7 @@
 
   [zero maxCount]
     expected: [TIMEOUT, PASS]
+
+  [Get upper/lower excluded]
+    expected:
+      if product == "chrome": [PASS, TIMEOUT]
diff --git a/third_party/blink/web_tests/external/wpt/IndexedDB/structured-clone.any.js.ini b/third_party/blink/web_tests/external/wpt/IndexedDB/structured-clone.any.js.ini
index 0b0a546..244c916 100644
--- a/third_party/blink/web_tests/external/wpt/IndexedDB/structured-clone.any.js.ini
+++ b/third_party/blink/web_tests/external/wpt/IndexedDB/structured-clone.any.js.ini
@@ -26,7 +26,7 @@
 
 [structured-clone.any.html?101-last]
   expected:
-    if (os == "linux") and (flag_specific == "") and (product == "content_shell"): [OK, TIMEOUT]
+    if flag_specific == "disable-layout-ng": [OK, TIMEOUT]
 
 [structured-clone.any.html?21-40]
 
diff --git a/third_party/blink/web_tests/external/wpt/content-security-policy/form-action/form-action-src-allowed-target-blank.sub.html.ini b/third_party/blink/web_tests/external/wpt/content-security-policy/form-action/form-action-src-allowed-target-blank.sub.html.ini
deleted file mode 100644
index 89908d8..0000000
--- a/third_party/blink/web_tests/external/wpt/content-security-policy/form-action/form-action-src-allowed-target-blank.sub.html.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-[form-action-src-allowed-target-blank.sub.html]
-  expected:
-    if product == "chrome": ERROR
diff --git a/third_party/blink/web_tests/external/wpt/cookie-store/cookieStore_set_arguments.https.any.js.ini b/third_party/blink/web_tests/external/wpt/cookie-store/cookieStore_set_arguments.https.any.js.ini
index d05fc2f..e8f1f64 100644
--- a/third_party/blink/web_tests/external/wpt/cookie-store/cookieStore_set_arguments.https.any.js.ini
+++ b/third_party/blink/web_tests/external/wpt/cookie-store/cookieStore_set_arguments.https.any.js.ini
@@ -1,18 +1,6 @@
 [cookieStore_set_arguments.https.any.html]
-  [cookieStore.set with expires set to a future Date]
-    expected:
-      if os == "win": FAIL
-
-  [cookieStore.set with expires set to a future timestamp]
-    expected:
-      if os == "win": FAIL
-
 
 [cookieStore_set_arguments.https.any.serviceworker.html]
-  [cookieStore.set with expires set to a future timestamp]
-    expected:
-      if os == "win": FAIL
-
   [cookieStore.set with expires set to a future Date]
     expected:
       if os == "win": [PASS, FAIL]
diff --git a/third_party/blink/web_tests/external/wpt/cookies/samesite/about-blank-subresource.https.html.ini b/third_party/blink/web_tests/external/wpt/cookies/samesite/about-blank-subresource.https.html.ini
new file mode 100644
index 0000000..2e65780
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/cookies/samesite/about-blank-subresource.https.html.ini
@@ -0,0 +1,3 @@
+[about-blank-subresource.https.html]
+  expected:
+    if os == "win": [OK, TIMEOUT]
diff --git a/third_party/blink/web_tests/external/wpt/cookies/samesite/form-post-blank-reload.https.html.ini b/third_party/blink/web_tests/external/wpt/cookies/samesite/form-post-blank-reload.https.html.ini
index 474bdcb9..cff3fc4 100644
--- a/third_party/blink/web_tests/external/wpt/cookies/samesite/form-post-blank-reload.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/cookies/samesite/form-post-blank-reload.https.html.ini
@@ -1,3 +1,5 @@
 [form-post-blank-reload.https.html]
+  expected:
+    if product == "chrome": [OK, ERROR]
   [Reloaded cross-site top-level form POSTs are strictly same-site]
     expected: FAIL
diff --git a/third_party/blink/web_tests/external/wpt/credential-management/fedcm-network-requests.https.html.ini b/third_party/blink/web_tests/external/wpt/credential-management/fedcm-network-requests.https.html.ini
index 3dbcf6ad..2407bd6 100644
--- a/third_party/blink/web_tests/external/wpt/credential-management/fedcm-network-requests.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/credential-management/fedcm-network-requests.https.html.ini
@@ -1,5 +1,5 @@
 [fedcm-network-requests.https.html]
   expected:
-    if (product == "content_shell") and (os == "linux") and (flag_specific == "disable-layout-ng"): [OK, TIMEOUT]
-    if (product == "content_shell") and (os == "win"): TIMEOUT
-    if product == "chrome": CRASH
+    if (flag_specific == "") and (product == "content_shell") and (os == "linux"): [OK, TIMEOUT]
+    if (flag_specific == "") and (product == "content_shell") and (os == "win"): TIMEOUT
+    if (flag_specific == "") and (product == "chrome"): CRASH
diff --git a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/anchor-position-top-layer-001.html b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/anchor-position-top-layer-001.html
new file mode 100644
index 0000000..c96f6ee
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/anchor-position-top-layer-001.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<title>Top-layer element can anchor to non-top-layer absolutely positioned element</title>
+<link rel="help" href="https://drafts.csswg.org/css-anchor-1/#target-anchor-element">
+<link rel="author" href="mailto:xiaochengh@chromium.org">
+<link rel="match" href="anchor-position-top-layer-ref.html">
+
+<style>
+#anchor {
+  position: absolute;
+  top: 300px;
+  left: 200px;
+  width: 100px;
+  height: 100px;
+  background: orange;
+  anchor-name: --a;
+}
+
+#target {
+  top: anchor(--a top);
+  left: anchor(--a right);
+  width: 100px;
+  height: 100px;
+  background: lime;
+  anchor-scroll: --a;
+}
+
+body {
+  margin: 0;
+  height: 300vh;
+}
+
+dialog {
+  margin: 0;
+  border: 0;
+  padding: 0;
+  inset: auto;
+}
+
+dialog::backdrop {
+  background: transparent;
+}
+</style>
+
+<div id="anchor"></div>
+<dialog id="target"></dialog>
+
+<script>
+target.showModal();
+document.scrollingElement.scrollTop = 100;
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/anchor-position-top-layer-002.html b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/anchor-position-top-layer-002.html
new file mode 100644
index 0000000..e626e6b
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/anchor-position-top-layer-002.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<title>Top-layer element can anchor to non-top-layer fixed positioned element</title>
+<link rel="help" href="https://drafts.csswg.org/css-anchor-1/#target-anchor-element">
+<link rel="author" href="mailto:xiaochengh@chromium.org">
+<link rel="match" href="anchor-position-top-layer-ref.html">
+
+<style>
+#anchor {
+  position: fixed;
+  top: 200px;
+  left: 200px;
+  width: 100px;
+  height: 100px;
+  background: orange;
+  anchor-name: --a;
+}
+
+#target {
+  top: anchor(--a top);
+  left: anchor(--a right);
+  width: 100px;
+  height: 100px;
+  background: lime;
+  anchor-scroll: --a;
+}
+
+body {
+  margin: 0;
+  height: 300vh;
+}
+
+dialog {
+  margin: 0;
+  border: 0;
+  padding: 0;
+  inset: auto;
+}
+
+dialog::backdrop {
+  background: transparent;
+}
+</style>
+
+<div id="anchor"></div>
+<dialog id="target"></dialog>
+
+<script>
+target.showModal();
+document.scrollingElement.scrollTop = 100;
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/anchor-position-top-layer-003.html b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/anchor-position-top-layer-003.html
new file mode 100644
index 0000000..39f3c36
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/anchor-position-top-layer-003.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<title>Top-layer element can anchor to preceeding top-layer absolutely positioned element</title>
+<link rel="help" href="https://drafts.csswg.org/css-anchor-1/#target-anchor-element">
+<link rel="author" href="mailto:xiaochengh@chromium.org">
+<link rel="match" href="anchor-position-top-layer-ref.html">
+
+<style>
+#anchor {
+  position: absolute;
+  top: 300px;
+  left: 200px;
+  width: 100px;
+  height: 100px;
+  background: orange;
+  anchor-name: --a;
+}
+
+#target {
+  top: anchor(--a top);
+  left: anchor(--a right);
+  width: 100px;
+  height: 100px;
+  background: lime;
+  anchor-scroll: --a;
+}
+
+body {
+  margin: 0;
+  height: 300vh;
+}
+
+dialog {
+  margin: 0;
+  border: 0;
+  padding: 0;
+  inset: auto;
+}
+
+dialog::backdrop {
+  background: transparent;
+}
+</style>
+
+<dialog id="anchor"></dialog>
+<dialog id="target"></dialog>
+
+<script>
+anchor.showModal();
+target.showModal();
+document.scrollingElement.scrollTop = 100;
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/anchor-position-top-layer-004.html b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/anchor-position-top-layer-004.html
new file mode 100644
index 0000000..8e189e0
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/anchor-position-top-layer-004.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<title>Top-layer element can anchor to preceeding top-layer fixed positioned element</title>
+<link rel="help" href="https://drafts.csswg.org/css-anchor-1/#target-anchor-element">
+<link rel="author" href="mailto:xiaochengh@chromium.org">
+<link rel="match" href="anchor-position-top-layer-ref.html">
+
+<style>
+#anchor {
+  position: fixed;
+  top: 200px;
+  left: 200px;
+  width: 100px;
+  height: 100px;
+  background: orange;
+  anchor-name: --a;
+}
+
+#target {
+  top: anchor(--a top);
+  left: anchor(--a right);
+  width: 100px;
+  height: 100px;
+  background: lime;
+  anchor-scroll: --a;
+}
+
+body {
+  margin: 0;
+  height: 300vh;
+}
+
+dialog {
+  margin: 0;
+  border: 0;
+  padding: 0;
+  inset: auto;
+}
+
+dialog::backdrop {
+  background: transparent;
+}
+</style>
+
+<dialog id="anchor"></dialog>
+<dialog id="target"></dialog>
+
+<script>
+anchor.showModal();
+target.showModal();
+document.scrollingElement.scrollTop = 100;
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/anchor-position-top-layer-005.html b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/anchor-position-top-layer-005.html
new file mode 100644
index 0000000..d9e4fa86
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/anchor-position-top-layer-005.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<title>Non-top-layer element cannot anchor to top-layer element</title>
+<link rel="help" href="https://drafts.csswg.org/css-anchor-1/#target-anchor-element">
+<link rel="author" href="mailto:xiaochengh@chromium.org">
+<link rel="match" href="anchor-position-top-layer-ref.html">
+
+<style>
+#anchor {
+  position: absolute;
+  top: 300px;
+  left: 200px;
+  width: 100px;
+  height: 100px;
+  background: orange;
+  anchor-name: --a;
+}
+
+#target {
+  position: fixed;
+  top: anchor(--a bottom, 200px);
+  left: anchor(--a left, 300px);
+  width: 100px;
+  height: 100px;
+  background: lime;
+  anchor-scroll: --a;
+}
+
+body {
+  margin: 0;
+  height: 300vh;
+}
+
+dialog {
+  margin: 0;
+  border: 0;
+  padding: 0;
+  inset: auto;
+}
+
+dialog::backdrop {
+  background: transparent;
+}
+</style>
+
+<dialog id="anchor"></dialog>
+<div id="target"></div>
+
+<script>
+anchor.showModal();
+document.scrollingElement.scrollTop = 100;
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/anchor-position-top-layer-006.html b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/anchor-position-top-layer-006.html
new file mode 100644
index 0000000..5f5cd67a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/anchor-position-top-layer-006.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<title>Top-layer element cannot anchor to succeeding top-layer element</title>
+<link rel="help" href="https://drafts.csswg.org/css-anchor-1/#target-anchor-element">
+<link rel="author" href="mailto:xiaochengh@chromium.org">
+<link rel="match" href="anchor-position-top-layer-ref.html">
+
+<style>
+#anchor {
+  position: absolute;
+  top: 300px;
+  left: 200px;
+  width: 100px;
+  height: 100px;
+  background: orange;
+  anchor-name: --a;
+}
+
+#target {
+  position: fixed;
+  top: anchor(--a bottom, 200px);
+  left: anchor(--a left, 300px);
+  width: 100px;
+  height: 100px;
+  background: lime;
+  anchor-scroll: --a;
+}
+
+body {
+  margin: 0;
+  height: 300vh;
+}
+
+dialog {
+  margin: 0;
+  border: 0;
+  padding: 0;
+}
+
+dialog::backdrop {
+  background: transparent;
+}
+</style>
+
+<dialog id="anchor"></dialog>
+<dialog id="target"></dialog>
+
+<script>
+target.showModal();
+anchor.showModal();
+document.scrollingElement.scrollTop = 100;
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/anchor-position-top-layer-ref.html b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/anchor-position-top-layer-ref.html
new file mode 100644
index 0000000..dc7f77f
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/anchor-position-top-layer-ref.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<title>Tests anchor positioning with top-layer elements</title>
+
+<style>
+body {
+  margin: 0;
+  height: 300vh;
+}
+
+#anchor {
+  position: fixed;
+  top: 200px;
+  left: 200px;
+  width: 100px;
+  height: 100px;
+  background: orange;
+}
+
+#target {
+  position: fixed;
+  top: 200px;
+  left: 300px;
+  width: 100px;
+  height: 100px;
+  background: lime;
+}
+</style>
+
+<div id="anchor"></div>
+<div id="target"></div>
+
+<script>
+document.scrollingElement.scrollTop = 100;
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-animations/Element-getAnimations-dynamic-changes.tentative.html.ini b/third_party/blink/web_tests/external/wpt/css/css-animations/Element-getAnimations-dynamic-changes.tentative.html.ini
index 0269492..9037285 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-animations/Element-getAnimations-dynamic-changes.tentative.html.ini
+++ b/third_party/blink/web_tests/external/wpt/css/css-animations/Element-getAnimations-dynamic-changes.tentative.html.ini
@@ -10,4 +10,4 @@
 
   [Animations preserve their startTime when changed]
     expected:
-      if product == "chrome": FAIL
+      if product == "chrome": [PASS, FAIL]
diff --git a/third_party/blink/web_tests/external/wpt/css/css-break/flexbox/increase-fragmentainer-size-flex-item-trailing-margin.html b/third_party/blink/web_tests/external/wpt/css/css-break/flexbox/increase-fragmentainer-size-flex-item-trailing-margin.html
new file mode 100644
index 0000000..9484544
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-break/flexbox/increase-fragmentainer-size-flex-item-trailing-margin.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=1395408">
+<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=1399449">
+<link rel="match" href="../../reference/ref-filled-green-100px-square.xht">
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div id="mc" style="columns:1; width:100px; column-fill:auto; height:90px; background:red;">
+  <div style="height:40px; background:green;"></div>
+  <div style="display:flex; background:green;">
+    <div style="margin-bottom:10px; height:50px; width:100px;"></div>
+  </div>
+</div>
+<script>
+  document.body.offsetTop;
+  mc.style.height = "100px";
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-break/increase-fragmentainer-size-tall-border.html b/third_party/blink/web_tests/external/wpt/css/css-break/increase-fragmentainer-size-tall-border.html
new file mode 100644
index 0000000..b7e716b2
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-break/increase-fragmentainer-size-tall-border.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=1395408">
+<link rel="match" href="../reference/ref-filled-green-100px-square.xht">
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div id="mc" style="columns:1; width:100px; column-fill:auto; height:60px; background:red;">
+  <div style="height:50px; background:green;"></div>
+  <div style="display:flow-root; border-top:50px solid green;"></div>
+</div>
+<script>
+  document.body.offsetTop;
+  mc.style.height = "100px";
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-contain/container-queries/container-for-shadow-dom.html b/third_party/blink/web_tests/external/wpt/css/css-contain/container-queries/container-for-shadow-dom.html
index 22fa9dd..9328060 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-contain/container-queries/container-for-shadow-dom.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-contain/container-queries/container-for-shadow-dom.html
@@ -16,7 +16,8 @@
   #inclusive-ancestor-host-before,
   #inclusive-ancestor-part-before,
   #inclusive-ancestor-inner-part,
-  #inclusive-ancestor-slot-fallback {
+  #inclusive-ancestor-slot-fallback,
+  #inner-scope-host-part {
     width: 400px;
     container-type: inline-size;
   }
@@ -250,6 +251,27 @@
   </div>
 </div>
 
+<div id="inner-scope-host-part">
+  <div>
+    <template shadowroot="open">
+      <style>
+        div {
+          width: 200px;
+          container-type: inline-size;
+        }
+        @container (width = 400px) {
+          :host::part(part) { color: green; }
+        }
+      </style>
+      <div>
+        <span id="t12" part="part"></span>
+      </div>
+    </template>
+    <style>
+    </style>
+  </div>
+</div>
+
 <script>
   setup(() => {
     assert_implements_container_queries();
@@ -314,4 +336,10 @@
     const t11 = document.querySelector("#no-container-for-part > div").shadowRoot.querySelector("#t11");
     assert_equals(getComputedStyle(t11).color, green);
   }, "Should not match container inside shadow tree for ::part()");
+
+  test(() => {
+    const t12 = document.querySelector("#inner-scope-host-part > div").shadowRoot.querySelector("#t12");
+    assert_equals(getComputedStyle(t12).color, green);
+  }, "A :host::part rule should match containers in the originating element tree");
+
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-flexbox/image-as-flexitem-size-007v.html.ini b/third_party/blink/web_tests/external/wpt/css/css-flexbox/image-as-flexitem-size-007v.html.ini
index 5d58a48a..c8aa4b5 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-flexbox/image-as-flexitem-size-007v.html.ini
+++ b/third_party/blink/web_tests/external/wpt/css/css-flexbox/image-as-flexitem-size-007v.html.ini
@@ -9,32 +9,32 @@
 
   [.flexbox > img 10]
     expected:
-      if flag_specific == "disable-layout-ng": [PASS, FAIL]
+      if flag_specific == "disable-layout-ng": FAIL
 
   [.flexbox > img 7]
     expected:
-      if flag_specific == "disable-layout-ng": [PASS, FAIL]
+      if flag_specific == "disable-layout-ng": FAIL
 
   [.flexbox > img 1]
     expected:
-      if flag_specific == "disable-layout-ng": [PASS, FAIL]
+      if flag_specific == "disable-layout-ng": FAIL
 
   [.flexbox > img 14]
     expected:
-      if flag_specific == "disable-layout-ng": [PASS, FAIL]
+      if flag_specific == "disable-layout-ng": FAIL
 
   [.flexbox > img 12]
     expected:
-      if flag_specific == "disable-layout-ng": [PASS, FAIL]
+      if flag_specific == "disable-layout-ng": FAIL
 
   [.flexbox > img 6]
     expected:
-      if flag_specific == "disable-layout-ng": [PASS, FAIL]
+      if flag_specific == "disable-layout-ng": FAIL
 
   [.flexbox > img 9]
     expected:
-      if flag_specific == "disable-layout-ng": [PASS, FAIL]
+      if flag_specific == "disable-layout-ng": FAIL
 
   [.flexbox > img 11]
     expected:
-      if flag_specific == "disable-layout-ng": [PASS, FAIL]
+      if flag_specific == "disable-layout-ng": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-fonts/font-palette-vs-shorthand-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-fonts/font-palette-vs-shorthand-expected.txt
deleted file mode 100644
index b9913eda..0000000
--- a/third_party/blink/web_tests/external/wpt/css/css-fonts/font-palette-vs-shorthand-expected.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-This is a testharness.js-based test.
-FAIL CSS Fonts Module Level 4: interaction of font-palette and font shorthand assert_equals: expected "" but got "50px colr"
-FAIL CSS Fonts Module Level 4: interaction of font-palette and font shorthand 1 assert_equals: expected "normal" but got "dark"
-FAIL CSS Fonts Module Level 4: interaction of font-palette and font shorthand 2 assert_equals: expected "" but got "50px colr"
-PASS CSS Fonts Module Level 4: interaction of font-palette and font shorthand 3
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/css/css-fonts/font-palette-vs-shorthand.html b/third_party/blink/web_tests/external/wpt/css/css-fonts/font-palette-vs-shorthand.html
index 6c5cc97..fa8308ef 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-fonts/font-palette-vs-shorthand.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-fonts/font-palette-vs-shorthand.html
@@ -4,7 +4,7 @@
 <meta charset="utf-8">
 <title>CSS Fonts Module Level 4: interaction of font-palette and font shorthand</title>
 <link rel="help" href="https://drafts.csswg.org/css-fonts/#font-prop">
-<meta name="assert" content="font-palette is reset to normal by font shorthand.">
+<meta name="assert" content="font-palette is NOT reset to normal by font shorthand.">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <style id="style">
@@ -17,11 +17,11 @@
 }
 #a {
     font: 50px colr;
-    font-palette: dark;
+    font-palette: dark;  /* should NOT cause the shorthand to be empty */
 }
 #b {
     font-palette: dark;
-    font: 50px colr;  /* should reset font-palette to normal */
+    font: 50px colr;  /* should NOT reset font-palette to normal */
 }
 #c {
     font-palette: dark;
@@ -37,7 +37,7 @@
 </style>
 </head>
 <body>
-<p>The first and third examples should use the 'dark' palette; the second and fourth, 'normal'.</p>
+<p>The first three examples should use the 'dark' palette; the fourth, 'normal'.</p>
 <div id=a>A</div>
 <div id=b>A</div>
 <div id=c>A</div>
@@ -47,7 +47,10 @@
     let testElem = document.getElementById("a");
     let computed = window.getComputedStyle(testElem);
     assert_equals(computed.fontPalette, "dark");
-    assert_equals(computed.font, "");
+    assert_not_equals(computed.font, "");
+    /* The exact form of the font shorthand varies, but should include these pieces: */
+    assert_not_equals(computed.font.indexOf("50px"), -1);
+    assert_not_equals(computed.font.indexOf("colr"), -1);
     assert_equals(computed.fontFamily, "colr");
     assert_equals(computed.fontSize, "50px");
 });
@@ -55,20 +58,22 @@
 test(function() {
     let testElem = document.getElementById("b");
     let computed = window.getComputedStyle(testElem);
-    assert_equals(computed.fontPalette, "normal");
+    assert_equals(computed.fontPalette, "dark");
     assert_not_equals(computed.font, "");
-    /* The exact form of the font shorthand varies, but should include these pieces: */
     assert_not_equals(computed.font.indexOf("50px"), -1);
     assert_not_equals(computed.font.indexOf("colr"), -1);
-    /* And there should be no trace of this: */
-    assert_equals(computed.font.indexOf("dark"), -1);
+    assert_equals(computed.fontFamily, "colr");
+    assert_equals(computed.fontSize, "50px");
 });
 
 test(function() {
     let testElem = document.getElementById("c");
     let computed = window.getComputedStyle(testElem);
     assert_equals(computed.fontPalette, "dark");
-    assert_equals(computed.font, "");
+    assert_not_equals(computed.font.indexOf("50px"), -1);
+    assert_not_equals(computed.font.indexOf("colr"), -1);
+    assert_equals(computed.fontFamily, "colr");
+    assert_equals(computed.fontSize, "50px");
 });
 
 test(function() {
@@ -77,6 +82,8 @@
     assert_equals(computed.fontPalette, "normal");
     assert_not_equals(computed.font.indexOf("50px"), -1);
     assert_not_equals(computed.font.indexOf("colr"), -1);
+    assert_equals(computed.fontFamily, "colr");
+    assert_equals(computed.fontSize, "50px");
 });
 </script>
 </body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-fonts/font-palette-vs-shorthand.html.ini b/third_party/blink/web_tests/external/wpt/css/css-fonts/font-palette-vs-shorthand.html.ini
deleted file mode 100644
index 3a37c73..0000000
--- a/third_party/blink/web_tests/external/wpt/css/css-fonts/font-palette-vs-shorthand.html.ini
+++ /dev/null
@@ -1,9 +0,0 @@
-[font-palette-vs-shorthand.html]
-  [CSS Fonts Module Level 4: interaction of font-palette and font shorthand]
-    expected: FAIL
-
-  [CSS Fonts Module Level 4: interaction of font-palette and font shorthand 1]
-    expected: FAIL
-
-  [CSS Fonts Module Level 4: interaction of font-palette and font shorthand 2]
-    expected: FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-fonts/font-shorthand-serialization-prevention-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-fonts/font-shorthand-serialization-prevention-expected.txt
index 77c053b8..cf8ba31 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-fonts/font-shorthand-serialization-prevention-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/css/css-fonts/font-shorthand-serialization-prevention-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 202 tests; 156 PASS, 46 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 188 tests; 149 PASS, 39 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Setting font-family to sans-serif should not prevent the font shorthand from serializing in specified style
 PASS Setting font-family to sans-serif should not prevent the font shorthand from serializing in computed style
 PASS Setting font-variant to initial should prevent the font shorthand from serializing in specified style
@@ -188,19 +188,5 @@
 PASS Setting font-variation-settings to normal should not prevent the font shorthand from serializing in computed style
 FAIL Setting font-variation-settings to "aaaa" 1 should prevent the font shorthand from serializing in specified style assert_equals: expected "" but got "16px serif"
 FAIL Setting font-variation-settings to "aaaa" 1 should prevent the font shorthand from serializing in computed style assert_equals: expected "" but got "16px serif"
-FAIL Setting font-palette to initial should prevent the font shorthand from serializing in specified style assert_equals: expected "" but got "16px serif"
-PASS Setting font-palette to initial should not prevent the font shorthand from serializing in computed style
-FAIL Setting font-palette to inherit should prevent the font shorthand from serializing in specified style assert_equals: expected "" but got "16px serif"
-PASS Setting font-palette to inherit should not prevent the font shorthand from serializing in computed style
-FAIL Setting font-palette to unset should prevent the font shorthand from serializing in specified style assert_equals: expected "" but got "16px serif"
-PASS Setting font-palette to unset should not prevent the font shorthand from serializing in computed style
-FAIL Setting font-palette to revert should prevent the font shorthand from serializing in specified style assert_equals: expected "" but got "16px serif"
-PASS Setting font-palette to revert should not prevent the font shorthand from serializing in computed style
-FAIL Setting font-palette to revert-layer should prevent the font shorthand from serializing in specified style assert_equals: expected "" but got "16px serif"
-PASS Setting font-palette to revert-layer should not prevent the font shorthand from serializing in computed style
-PASS Setting font-palette to normal should not prevent the font shorthand from serializing in specified style
-PASS Setting font-palette to normal should not prevent the font shorthand from serializing in computed style
-FAIL Setting font-palette to light should prevent the font shorthand from serializing in specified style assert_equals: expected "" but got "16px serif"
-FAIL Setting font-palette to light should prevent the font shorthand from serializing in computed style assert_equals: expected "" but got "16px serif"
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/external/wpt/css/css-fonts/font-shorthand-serialization-prevention.html b/third_party/blink/web_tests/external/wpt/css/css-fonts/font-shorthand-serialization-prevention.html
index 223c2fa..f6a1ea9e 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-fonts/font-shorthand-serialization-prevention.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-fonts/font-shorthand-serialization-prevention.html
@@ -92,6 +92,5 @@
 test_font_shorthand_serialization_after_setting_subproperty('font-language-override', 'normal', '"SRB"')
 test_font_shorthand_serialization_after_setting_subproperty('font-optical-sizing', 'auto', 'none')
 test_font_shorthand_serialization_after_setting_subproperty('font-variation-settings', 'normal', '"aaaa" 1')
-test_font_shorthand_serialization_after_setting_subproperty('font-palette', 'normal', 'light')
 
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-fonts/font-shorthand-serialization-prevention.html.ini b/third_party/blink/web_tests/external/wpt/css/css-fonts/font-shorthand-serialization-prevention.html.ini
index 0c39f805..7b50e7c 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-fonts/font-shorthand-serialization-prevention.html.ini
+++ b/third_party/blink/web_tests/external/wpt/css/css-fonts/font-shorthand-serialization-prevention.html.ini
@@ -115,24 +115,3 @@
 
   [Setting font-variation-settings to "aaaa" 1 should prevent the font shorthand from serializing in computed style]
     expected: FAIL
-
-  [Setting font-palette to initial should prevent the font shorthand from serializing in specified style]
-    expected: FAIL
-
-  [Setting font-palette to inherit should prevent the font shorthand from serializing in specified style]
-    expected: FAIL
-
-  [Setting font-palette to unset should prevent the font shorthand from serializing in specified style]
-    expected: FAIL
-
-  [Setting font-palette to revert should prevent the font shorthand from serializing in specified style]
-    expected: FAIL
-
-  [Setting font-palette to revert-layer should prevent the font shorthand from serializing in specified style]
-    expected: FAIL
-
-  [Setting font-palette to light should prevent the font shorthand from serializing in specified style]
-    expected: FAIL
-
-  [Setting font-palette to light should prevent the font shorthand from serializing in computed style]
-    expected: FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/Initial-letter-breaking-vlr.html.ini b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/Initial-letter-breaking-vlr.html.ini
index a76ddebb..0b139d8c 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/Initial-letter-breaking-vlr.html.ini
+++ b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/Initial-letter-breaking-vlr.html.ini
@@ -1,4 +1,4 @@
 [Initial-letter-breaking-vlr.html]
   expected:
-    if (product == "content_shell") and (flag_specific == "disable-layout-ng"): FAIL
-    if product == "chrome": FAIL
+    if (flag_specific == "") and (product == "chrome"): [PASS, FAIL]
+    if flag_specific == "disable-layout-ng": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/Initial-letter-breaking-vrl.html.ini b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/Initial-letter-breaking-vrl.html.ini
index 95bff10..a3abcef 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/Initial-letter-breaking-vrl.html.ini
+++ b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/Initial-letter-breaking-vrl.html.ini
@@ -1,4 +1,4 @@
 [Initial-letter-breaking-vrl.html]
   expected:
-    if (flag_specific == "") and (product == "chrome"): [PASS, FAIL]
-    if flag_specific == "disable-layout-ng": FAIL
+    if (product == "content_shell") and (flag_specific == "disable-layout-ng"): FAIL
+    if product == "chrome": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-block-position-drop-under-ruby-tall.html.ini b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-block-position-drop-under-ruby-tall.html.ini
index 37fb91ec..2d222a4 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-block-position-drop-under-ruby-tall.html.ini
+++ b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-block-position-drop-under-ruby-tall.html.ini
@@ -1,4 +1,4 @@
 [initial-letter-block-position-drop-under-ruby-tall.html]
   expected:
-    if (product == "content_shell") and (flag_specific == ""): PASS
-    FAIL
+    if (flag_specific == "") and (product == "chrome"): [PASS, FAIL]
+    if flag_specific == "disable-layout-ng": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-block-position-margins-rtl.html.ini b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-block-position-margins-rtl.html.ini
index a084bdb3..7dd454cc 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-block-position-margins-rtl.html.ini
+++ b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-block-position-margins-rtl.html.ini
@@ -1,4 +1,4 @@
 [initial-letter-block-position-margins-rtl.html]
   expected:
-    if (product == "content_shell") and (flag_specific == "disable-layout-ng"): FAIL
-    if product == "chrome": FAIL
+    if (flag_specific == "") and (product == "chrome"): [PASS, FAIL]
+    if flag_specific == "disable-layout-ng": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-block-position-raise-under-ruby.html.ini b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-block-position-raise-under-ruby.html.ini
index c4296d6..7ab9e673 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-block-position-raise-under-ruby.html.ini
+++ b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-block-position-raise-under-ruby.html.ini
@@ -1,4 +1,4 @@
 [initial-letter-block-position-raise-under-ruby.html]
   expected:
-    if (product == "content_shell") and (flag_specific == "disable-layout-ng"): FAIL
-    if product == "chrome": FAIL
+    if (flag_specific == "") and (product == "chrome"): [PASS, FAIL]
+    if flag_specific == "disable-layout-ng": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-drop-initial-vrl.html.ini b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-drop-initial-vrl.html.ini
index 90a6105fa..9324bd34 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-drop-initial-vrl.html.ini
+++ b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-drop-initial-vrl.html.ini
@@ -1,4 +1,4 @@
 [initial-letter-drop-initial-vrl.html]
   expected:
-    if (product == "content_shell") and (flag_specific == "disable-layout-ng"): FAIL
-    if product == "chrome": FAIL
+    if (flag_specific == "") and (product == "chrome"): [PASS, FAIL]
+    if flag_specific == "disable-layout-ng": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-drop-initial.html.ini b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-drop-initial.html.ini
index e47b563d7..560a2a2 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-drop-initial.html.ini
+++ b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-drop-initial.html.ini
@@ -1,4 +1,4 @@
 [initial-letter-drop-initial.html]
   expected:
-    if (flag_specific == "") and (product == "chrome"): [PASS, FAIL]
-    if flag_specific == "disable-layout-ng": FAIL
+    if (product == "content_shell") and (flag_specific == "disable-layout-ng"): FAIL
+    if product == "chrome": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-float-001-rtl.html.ini b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-float-001-rtl.html.ini
index 1678bc1..f8d9885 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-float-001-rtl.html.ini
+++ b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-float-001-rtl.html.ini
@@ -1,4 +1,4 @@
 [initial-letter-float-001-rtl.html]
   expected:
-    if (flag_specific == "") and (product == "chrome"): [PASS, FAIL]
-    if flag_specific == "disable-layout-ng": FAIL
+    if (product == "content_shell") and (flag_specific == "disable-layout-ng"): FAIL
+    if product == "chrome": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-float-001-vlr.html.ini b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-float-001-vlr.html.ini
index c90a4c2..815486cd8 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-float-001-vlr.html.ini
+++ b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-float-001-vlr.html.ini
@@ -1,4 +1,4 @@
 [initial-letter-float-001-vlr.html]
   expected:
-    if (flag_specific == "") and (product == "chrome"): [PASS, FAIL]
-    if flag_specific == "disable-layout-ng": FAIL
+    if (product == "content_shell") and (flag_specific == "disable-layout-ng"): FAIL
+    if product == "chrome": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-float-001.html.ini b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-float-001.html.ini
index 3d20a75e..263a421 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-float-001.html.ini
+++ b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-float-001.html.ini
@@ -1,4 +1,4 @@
 [initial-letter-float-001.html]
   expected:
-    if (product == "content_shell") and (flag_specific == "disable-layout-ng"): FAIL
-    if product == "chrome": FAIL
+    if (flag_specific == "") and (product == "chrome"): [PASS, FAIL]
+    if flag_specific == "disable-layout-ng": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-float-002.html.ini b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-float-002.html.ini
index eeb0e18..2e8b17a 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-float-002.html.ini
+++ b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-float-002.html.ini
@@ -1,4 +1,4 @@
 [initial-letter-float-002.html]
   expected:
-    if (flag_specific == "") and (product == "chrome"): [PASS, FAIL]
-    if flag_specific == "disable-layout-ng": FAIL
+    if (product == "content_shell") and (flag_specific == "disable-layout-ng"): FAIL
+    if product == "chrome": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-indentation-rtl.html.ini b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-indentation-rtl.html.ini
index 94f8322..776a6e96 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-indentation-rtl.html.ini
+++ b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-indentation-rtl.html.ini
@@ -1,4 +1,4 @@
 [initial-letter-indentation-rtl.html]
   expected:
-    if (flag_specific == "") and (product == "chrome"): [PASS, FAIL]
-    if flag_specific == "disable-layout-ng": FAIL
+    if (product == "content_shell") and (flag_specific == "disable-layout-ng"): FAIL
+    if product == "chrome": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-indentation.html.ini b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-indentation.html.ini
index 2f25a7a..fbc24c2 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-indentation.html.ini
+++ b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-indentation.html.ini
@@ -1,4 +1,4 @@
 [initial-letter-indentation.html]
   expected:
-    if (flag_specific == "") and (product == "chrome"): [PASS, FAIL]
-    if flag_specific == "disable-layout-ng": FAIL
+    if (product == "content_shell") and (flag_specific == "disable-layout-ng"): FAIL
+    if product == "chrome": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-layout-text-decoration-underline.html.ini b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-layout-text-decoration-underline.html.ini
index 87a653d8..ad12f91f 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-layout-text-decoration-underline.html.ini
+++ b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-layout-text-decoration-underline.html.ini
@@ -1,4 +1,4 @@
 [initial-letter-layout-text-decoration-underline.html]
   expected:
-    if (flag_specific == "") and (product == "chrome"): [PASS, FAIL]
-    if flag_specific == "disable-layout-ng": FAIL
+    if (product == "content_shell") and (flag_specific == "disable-layout-ng"): FAIL
+    if product == "chrome": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-raised-sunken-caps-sunken.html.ini b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-raised-sunken-caps-sunken.html.ini
index 858a0ee..9280e8e 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-raised-sunken-caps-sunken.html.ini
+++ b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-raised-sunken-caps-sunken.html.ini
@@ -1,4 +1,4 @@
 [initial-letter-raised-sunken-caps-sunken.html]
   expected:
-    if (product == "content_shell") and (flag_specific == "disable-layout-ng"): FAIL
-    if product == "chrome": FAIL
+    if (flag_specific == "") and (product == "chrome"): [PASS, FAIL]
+    if flag_specific == "disable-layout-ng": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-sunk-initial-vlr.html.ini b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-sunk-initial-vlr.html.ini
index 84fa955..3d2a32b 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-sunk-initial-vlr.html.ini
+++ b/third_party/blink/web_tests/external/wpt/css/css-inline/initial-letter/initial-letter-sunk-initial-vlr.html.ini
@@ -1,4 +1,4 @@
 [initial-letter-sunk-initial-vlr.html]
   expected:
-    if (flag_specific == "") and (product == "chrome"): [PASS, FAIL]
-    if flag_specific == "disable-layout-ng": FAIL
+    if (product == "content_shell") and (flag_specific == "disable-layout-ng"): FAIL
+    if product == "chrome": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-inline-001.html.ini b/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-inline-001.html.ini
index c87eb823..3917a43 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-inline-001.html.ini
+++ b/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-inline-001.html.ini
@@ -1,3 +1,3 @@
 [clip-path-inline-001.html]
   expected:
-    if product == "chrome": FAIL
+    if product == "chrome": [PASS, FAIL]
diff --git a/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-inline-002.html.ini b/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-inline-002.html.ini
index 84724ce..a2eabbf2 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-inline-002.html.ini
+++ b/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-inline-002.html.ini
@@ -1,3 +1,3 @@
 [clip-path-inline-002.html]
   expected:
-    if product == "chrome": FAIL
+    if product == "chrome": [PASS, FAIL]
diff --git a/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-reference-box-004.html.ini b/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-reference-box-004.html.ini
index 6bc7314..ff4cde4 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-reference-box-004.html.ini
+++ b/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-reference-box-004.html.ini
@@ -1,3 +1,3 @@
 [clip-path-reference-box-004.html]
   expected:
-    if product == "chrome": [PASS, FAIL]
+    if product == "chrome": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-multicol/crashtests/balancing-flex-item-trailing-margin-freeze.html b/third_party/blink/web_tests/external/wpt/css/css-multicol/crashtests/balancing-flex-item-trailing-margin-freeze.html
new file mode 100644
index 0000000..3cbf2f8
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-multicol/crashtests/balancing-flex-item-trailing-margin-freeze.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=1395408">
+<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=1399449">
+<div style="columns:2;">
+  <div style="contain:size; height:40px;">PASS if no freeze.</div>
+  <div style="display:flex;">
+    <div style="contain:size; margin-bottom:2px; height:30px; width:100px;"></div>
+  </div>
+  <div style="display:flex;">
+    <div style="contain:size; margin-bottom:2px; height:30px; width:100px;"></div>
+  </div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-multicol/crashtests/balancing-tall-borders-freeze.html b/third_party/blink/web_tests/external/wpt/css/css-multicol/crashtests/balancing-tall-borders-freeze.html
new file mode 100644
index 0000000..2672690
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-multicol/crashtests/balancing-tall-borders-freeze.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=1395408">
+<div style="columns:2;">
+  <div style="contain:size; height:40px;">PASS if no freeze.</div>
+  <div style="display:flow-root; border-top:32px solid;"></div>
+  <div style="display:flow-root; border-top:32px solid;"></div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-overflow/scrollable-overflow-input-001.html.ini b/third_party/blink/web_tests/external/wpt/css/css-overflow/scrollable-overflow-input-001.html.ini
index 53c5a71..af5b11c 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-overflow/scrollable-overflow-input-001.html.ini
+++ b/third_party/blink/web_tests/external/wpt/css/css-overflow/scrollable-overflow-input-001.html.ini
@@ -1,4 +1,4 @@
 [scrollable-overflow-input-001.html]
   expected:
-    if (flag_specific == "") and (product == "chrome"): [PASS, FAIL]
-    if flag_specific == "disable-layout-ng": FAIL
+    if (product == "content_shell") and (flag_specific == "disable-layout-ng"): FAIL
+    if product == "chrome": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-overflow/scrollable-overflow-input-002.html.ini b/third_party/blink/web_tests/external/wpt/css/css-overflow/scrollable-overflow-input-002.html.ini
index eba9b19..6e03ac5 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-overflow/scrollable-overflow-input-002.html.ini
+++ b/third_party/blink/web_tests/external/wpt/css/css-overflow/scrollable-overflow-input-002.html.ini
@@ -1,3 +1,3 @@
 [scrollable-overflow-input-002.html]
   expected:
-    if product == "chrome": [PASS, FAIL]
+    if product == "chrome": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-paint-api/geometry-background-image-001.https.html.ini b/third_party/blink/web_tests/external/wpt/css/css-paint-api/geometry-background-image-001.https.html.ini
new file mode 100644
index 0000000..28cf8a5a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-paint-api/geometry-background-image-001.https.html.ini
@@ -0,0 +1,3 @@
+[geometry-background-image-001.https.html]
+  expected:
+    if os == "win": [PASS, TIMEOUT]
diff --git a/third_party/blink/web_tests/external/wpt/css/css-paint-api/parse-input-arguments-018.https.html.ini b/third_party/blink/web_tests/external/wpt/css/css-paint-api/parse-input-arguments-018.https.html.ini
index e4f7984..55865af 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-paint-api/parse-input-arguments-018.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/css/css-paint-api/parse-input-arguments-018.https.html.ini
@@ -1,5 +1,4 @@
 [parse-input-arguments-018.https.html]
   expected:
-    if (os == "linux") and (product == "content_shell") and (flag_specific == ""): [CRASH, PASS]
     if os == "win": TIMEOUT
-    [PASS, CRASH]
+    CRASH
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ruby/ruby-text-combine-upright-001a.html.ini b/third_party/blink/web_tests/external/wpt/css/css-ruby/ruby-text-combine-upright-001a.html.ini
index a5cb32a6..0df837fb 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-ruby/ruby-text-combine-upright-001a.html.ini
+++ b/third_party/blink/web_tests/external/wpt/css/css-ruby/ruby-text-combine-upright-001a.html.ini
@@ -1,3 +1,3 @@
 [ruby-text-combine-upright-001a.html]
   expected:
-    if product == "chrome": [PASS, FAIL]
+    if product == "chrome": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ruby/ruby-text-combine-upright-002a.html.ini b/third_party/blink/web_tests/external/wpt/css/css-ruby/ruby-text-combine-upright-002a.html.ini
index 894ea23..3ba2bb3b7 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-ruby/ruby-text-combine-upright-002a.html.ini
+++ b/third_party/blink/web_tests/external/wpt/css/css-ruby/ruby-text-combine-upright-002a.html.ini
@@ -1,3 +1,3 @@
 [ruby-text-combine-upright-002a.html]
   expected:
-    if product == "chrome": FAIL
+    if product == "chrome": [PASS, FAIL]
diff --git a/third_party/blink/web_tests/external/wpt/css/css-typed-om/stylevalue-subclasses/numeric-objects/parse.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-typed-om/stylevalue-subclasses/numeric-objects/parse.tentative.html
index 8236b32..f8a341248 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-typed-om/stylevalue-subclasses/numeric-objects/parse.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-typed-om/stylevalue-subclasses/numeric-objects/parse.tentative.html
@@ -46,4 +46,13 @@
   assert_style_value_equals(expected, CSSNumericValue.parse('clamp(10px, 10%, 20px)'));
 }, 'Parsing clamp() is successful');
 
+test(() => {
+  const expected = new CSSMathSum(...[1, 2, 3].map(x => new CSSMathMin(CSS.number(x))));
+  assert_style_value_equals(expected, CSSNumericValue.parse('min(1) + min(2) + min(3)'));
+}, 'Parsing sum of multiple min() is successful');
+
+test(() => {
+  const expected = new CSSMathProduct(...[1, 2, 3].map(x => new CSSMathMin(CSS.number(x))));
+  assert_style_value_equals(expected, CSSNumericValue.parse('min(1) * min(2) * min(3)'));
+}, 'Parsing product of multiple min() is successful');
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/css/filter-effects/svg-mutation-filter-used-by-mask.html b/third_party/blink/web_tests/external/wpt/css/filter-effects/svg-mutation-filter-used-by-mask.html
new file mode 100644
index 0000000..59ac6ac
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/filter-effects/svg-mutation-filter-used-by-mask.html
@@ -0,0 +1,32 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Mutating filter primitive in &lt;filter> referenced by &lt;mask></title>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/rendering-utils.js"></script>
+<link rel="help" href="https://drafts.fxtf.org/filter-effects/#FilterElement">
+<link rel="help" href="https://drafts.fxtf.org/filter-effects/#feColorMatrixElement">
+<link rel="help" href="https://drafts.fxtf.org/filter-effects/#feComponentTransferElement">
+<link rel="help" href="https://drafts.fxtf.org/css-masking-1/#svg-masks">
+<link rel="match" href="reference/green-100x100.html">
+<svg>
+  <filter id="filter" color-interpolation-filters="sRGB">
+    <feColorMatrix type="hueRotate" values="0"/>
+    <feComponentTransfer>
+      <feFuncG type="linear" slope="10000"/>
+    </feComponentTransfer>
+    <feColorMatrix type="matrix" values="0 1 0 0 0, 0 1 0 0 0, 0 1 0 0 0, 0 0 0 1 0"/>
+  </filter>
+
+  <mask id="mask">
+    <rect width="100%" height="100%" fill="red" filter="url(#filter)"/>
+  </mask>
+
+  <rect width="100" height="100" fill="red"/>
+  <rect width="100" height="100" fill="green" mask="url(#mask)"/>
+</svg>
+<script>
+  waitForAtLeastOneFrame().then(() => {
+    document.querySelector('svg > filter > feColorMatrix').setAttribute('values', 90);
+    takeScreenshot();
+  });
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/selectors/invalidation/fullscreen-pseudo-class-in-has.html b/third_party/blink/web_tests/external/wpt/css/selectors/invalidation/fullscreen-pseudo-class-in-has.html
new file mode 100644
index 0000000..dbbcea2
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/selectors/invalidation/fullscreen-pseudo-class-in-has.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>CSS Selectors Invalidation: :fullscreen pseudo class in :has()</title>
+<link rel="author" title="Tim Nguyen" href="https://github.com/nt1m">
+<link rel="help" href="https://drafts.csswg.org/selectors/#relational">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<style>
+    #subject:has(#target:fullscreen) { color: green; }
+</style>
+
+<div id="subject">
+    This is some text.
+    <div id="target">This is going to be fullscreened</div>
+</div>
+
+<script>
+let waitForFullscreenChange = () => {
+    return new Promise((resolve) => {
+      document.addEventListener("fullscreenchange", resolve, { once: true });
+    });
+};
+
+promise_test(async function() {
+    assert_equals(getComputedStyle(subject).color, "rgb(0, 0, 0)",
+                  "ancestor should be black");
+    test_driver.bless("fullscreen", () => target.requestFullscreen());
+    await waitForFullscreenChange();
+    assert_equals(getComputedStyle(subject).color, "rgb(0, 128, 0)",
+                  "ancestor should be green since target is fullscreen");
+    document.exitFullscreen();
+    await waitForFullscreenChange();
+    assert_equals(getComputedStyle(subject).color, "rgb(0, 0, 0)",
+                  "ancestor should be black since target is no longer fullscreen");
+}, ":fullscreen pseudo-class invalidation with requestFullscreen + exitFullscreen");
+
+promise_test(async function() {
+    assert_equals(getComputedStyle(subject).color, "rgb(0, 0, 0)",
+                  "ancestor should be black");
+    test_driver.bless("fullscreen", () => target.requestFullscreen());
+    await waitForFullscreenChange();
+    assert_equals(getComputedStyle(subject).color, "rgb(0, 128, 0)",
+                  "ancestor should be green since target is fullscreen");
+    target.remove();
+    await waitForFullscreenChange();
+    assert_equals(getComputedStyle(subject).color, "rgb(0, 0, 0)",
+                  "ancestor should be black since target is removed");
+}, ":fullscreen pseudo-class invalidation with requestFullscreen + remove");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/selectors/invalidation/fullscreen-pseudo-class-in-has.html.ini b/third_party/blink/web_tests/external/wpt/css/selectors/invalidation/fullscreen-pseudo-class-in-has.html.ini
new file mode 100644
index 0000000..fe55665
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/selectors/invalidation/fullscreen-pseudo-class-in-has.html.ini
@@ -0,0 +1,7 @@
+[fullscreen-pseudo-class-in-has.html]
+  expected: TIMEOUT
+  [:fullscreen pseudo-class invalidation with requestFullscreen + exitFullscreen]
+    expected: TIMEOUT
+
+  [:fullscreen pseudo-class invalidation with requestFullscreen + remove]
+    expected: NOTRUN
diff --git a/third_party/blink/web_tests/external/wpt/css/selectors/invalidation/modal-pseudo-class-in-has.html b/third_party/blink/web_tests/external/wpt/css/selectors/invalidation/modal-pseudo-class-in-has.html
index 9c487fb..1bff896 100644
--- a/third_party/blink/web_tests/external/wpt/css/selectors/invalidation/modal-pseudo-class-in-has.html
+++ b/third_party/blink/web_tests/external/wpt/css/selectors/invalidation/modal-pseudo-class-in-has.html
@@ -5,14 +5,19 @@
 <link rel="help" href="https://drafts.csswg.org/selectors/#relational">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
 <style>
-  #subject:has(#dialog:modal) { color: green }
+  #subject:has(#dialog:modal) { color: green; }
+  #subject:has(#fullscreenTarget:modal) { color: blue; }
 </style>
 <div id="subject">
   This is some text.
   <dialog id="dialog">This is a dialog</dialog>
+  <div id="fullscreenTarget">This is going to be fullscreened</div>
 </div>
 <script>
+  // Dialog tests
   test(function() {
     assert_equals(getComputedStyle(subject).color, "rgb(0, 0, 0)",
                   "ancestor should be black since dialog is not modal");
@@ -30,7 +35,7 @@
     dialog.close();
     assert_equals(getComputedStyle(subject).color, "rgb(0, 0, 0)",
                   "ancestor should be black since dialog is closed");
-  }, ":modal pseudo-class invalidation with showModal+close");
+  }, ":modal pseudo-class invalidation with showModal + close");
   test(function() {
     assert_equals(getComputedStyle(subject).color, "rgb(0, 0, 0)",
                   "ancestor should be black");
@@ -39,6 +44,37 @@
                   "ancestor should be green since dialog is shown modally");
     dialog.remove();
     assert_equals(getComputedStyle(subject).color, "rgb(0, 0, 0)",
-                  "ancestor should be black since dialog is closed");
-  }, ":modal pseudo-class invalidation with showModal+remove");
-</script>
\ No newline at end of file
+                  "ancestor should be black since dialog is removed");
+  }, ":modal pseudo-class invalidation with showModal + remove");
+
+  // Fullscreen tests
+  let waitForFullscreenChange = () => {
+    return new Promise((resolve) => {
+      document.addEventListener("fullscreenchange", resolve, { once: true });
+    });
+  };
+  promise_test(async function() {
+    assert_equals(getComputedStyle(subject).color, "rgb(0, 0, 0)",
+                  "ancestor should be black");
+    test_driver.bless("fullscreen", () => fullscreenTarget.requestFullscreen());
+    await waitForFullscreenChange();
+    assert_equals(getComputedStyle(subject).color, "rgb(0, 0, 255)",
+                  "ancestor should be blue since target is fullscreen");
+    document.exitFullscreen();
+    await waitForFullscreenChange();
+    assert_equals(getComputedStyle(subject).color, "rgb(0, 0, 0)",
+                  "ancestor should be black since target is no longer fullscreen");
+  }, ":modal pseudo-class invalidation with requestFullscreen + exitFullscreen");
+  promise_test(async function() {
+    assert_equals(getComputedStyle(subject).color, "rgb(0, 0, 0)",
+                  "ancestor should be black");
+    test_driver.bless("fullscreen", () => fullscreenTarget.requestFullscreen());
+    await waitForFullscreenChange();
+    assert_equals(getComputedStyle(subject).color, "rgb(0, 0, 255)",
+                  "ancestor should be blue since target is fullscreen");
+    fullscreenTarget.remove();
+    await waitForFullscreenChange();
+    assert_equals(getComputedStyle(subject).color, "rgb(0, 0, 0)",
+                  "ancestor should be black since target is removed");
+  }, ":modal pseudo-class invalidation with requestFullscreen + remove");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/selectors/invalidation/modal-pseudo-class-in-has.html.ini b/third_party/blink/web_tests/external/wpt/css/selectors/invalidation/modal-pseudo-class-in-has.html.ini
new file mode 100644
index 0000000..153ead59
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/selectors/invalidation/modal-pseudo-class-in-has.html.ini
@@ -0,0 +1,7 @@
+[modal-pseudo-class-in-has.html]
+  expected: TIMEOUT
+  [:modal pseudo-class invalidation with requestFullscreen + exitFullscreen]
+    expected: TIMEOUT
+
+  [:modal pseudo-class invalidation with requestFullscreen + remove]
+    expected: NOTRUN
diff --git a/third_party/blink/web_tests/external/wpt/custom-elements/form-associated/ElementInternals-target-element-is-held-strongly.html.ini b/third_party/blink/web_tests/external/wpt/custom-elements/form-associated/ElementInternals-target-element-is-held-strongly.html.ini
index d1e5bb116..5b9453d 100644
--- a/third_party/blink/web_tests/external/wpt/custom-elements/form-associated/ElementInternals-target-element-is-held-strongly.html.ini
+++ b/third_party/blink/web_tests/external/wpt/custom-elements/form-associated/ElementInternals-target-element-is-held-strongly.html.ini
@@ -1,6 +1,3 @@
 [ElementInternals-target-element-is-held-strongly.html]
   expected:
-    if product == "chrome": TIMEOUT
-  [Target element of ElementsInternals is held strongly and doesn't get GCed if there are no other references]
-    expected:
-      if product == "chrome": TIMEOUT
+    if os == "win": [OK, TIMEOUT]
diff --git a/third_party/blink/web_tests/external/wpt/custom-elements/throw-on-dynamic-markup-insertion-counter-construct.html.ini b/third_party/blink/web_tests/external/wpt/custom-elements/throw-on-dynamic-markup-insertion-counter-construct.html.ini
index fb02180..8d48593 100644
--- a/third_party/blink/web_tests/external/wpt/custom-elements/throw-on-dynamic-markup-insertion-counter-construct.html.ini
+++ b/third_party/blink/web_tests/external/wpt/custom-elements/throw-on-dynamic-markup-insertion-counter-construct.html.ini
@@ -1,3 +1,3 @@
 [throw-on-dynamic-markup-insertion-counter-construct.html]
   expected:
-    if (os == "linux") and (flag_specific == "disable-layout-ng"): [OK, CRASH]
+    if (flag_specific == "") and (os == "linux") and (product == "content_shell"): [OK, CRASH]
diff --git a/third_party/blink/web_tests/external/wpt/dom/ranges/Range-mutations-dataChange.html.ini b/third_party/blink/web_tests/external/wpt/dom/ranges/Range-mutations-dataChange.html.ini
index c1f1d46..7158a50 100644
--- a/third_party/blink/web_tests/external/wpt/dom/ranges/Range-mutations-dataChange.html.ini
+++ b/third_party/blink/web_tests/external/wpt/dom/ranges/Range-mutations-dataChange.html.ini
@@ -1,3 +1,3 @@
 [Range-mutations-dataChange.html]
   expected:
-    if flag_specific == "disable-site-isolation-trials": [OK, CRASH]
+    if flag_specific == "disable-site-isolation-trials": CRASH
diff --git a/third_party/blink/web_tests/external/wpt/dom/ranges/Range-mutations-deleteData.html.ini b/third_party/blink/web_tests/external/wpt/dom/ranges/Range-mutations-deleteData.html.ini
new file mode 100644
index 0000000..8a4d2fe
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/dom/ranges/Range-mutations-deleteData.html.ini
@@ -0,0 +1,3 @@
+[Range-mutations-deleteData.html]
+  expected:
+    if flag_specific == "disable-site-isolation-trials": [OK, CRASH]
diff --git a/third_party/blink/web_tests/external/wpt/editing/run/formatblock.html.ini b/third_party/blink/web_tests/external/wpt/editing/run/formatblock.html.ini
index 8247fe6b..8b0be3d 100644
--- a/third_party/blink/web_tests/external/wpt/editing/run/formatblock.html.ini
+++ b/third_party/blink/web_tests/external/wpt/editing/run/formatblock.html.ini
@@ -1,4 +1,6 @@
 [formatblock.html?1-1000]
+  expected:
+    if os == "win": [OK, CRASH]
   [[["defaultparagraphseparator","div"\],["formatblock","<div>"\]\] "{<p><p> <p>foo</p>}" compare innerHTML]
     expected: FAIL
 
@@ -685,8 +687,6 @@
 
 
 [formatblock.html?3001-4000]
-  expected:
-    if os == "win": [OK, CRASH]
   [[["defaultparagraphseparator","div"\],["formatblock","<h5>"\]\] "<p>[foobar\]</p>" queryCommandValue("formatblock") after]
     expected: FAIL
 
diff --git a/third_party/blink/web_tests/external/wpt/element-timing/image-carousel.html.ini b/third_party/blink/web_tests/external/wpt/element-timing/image-carousel.html.ini
index f0807766..3b89847 100644
--- a/third_party/blink/web_tests/external/wpt/element-timing/image-carousel.html.ini
+++ b/third_party/blink/web_tests/external/wpt/element-timing/image-carousel.html.ini
@@ -1,4 +1,4 @@
 [image-carousel.html]
   [Entries for elements within an image carousel are dispatched when the elements are redrawn.]
     expected:
-      if product == "chrome": [PASS, FAIL]
+      if (product == "content_shell") and (os == "win"): [PASS, FAIL]
diff --git a/third_party/blink/web_tests/external/wpt/encoding-detection/ru-ISO-8859-5-late.tentative.html.ini b/third_party/blink/web_tests/external/wpt/encoding-detection/ru-ISO-8859-5-late.tentative.html.ini
deleted file mode 100644
index c722a3b..0000000
--- a/third_party/blink/web_tests/external/wpt/encoding-detection/ru-ISO-8859-5-late.tentative.html.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-[ru-ISO-8859-5-late.tentative.html]
-  expected:
-    if product == "chrome": ERROR
diff --git a/third_party/blink/web_tests/external/wpt/encoding/legacy-mb-japanese/shift_jis/sjis-decode-ms_kanji.html.ini b/third_party/blink/web_tests/external/wpt/encoding/legacy-mb-japanese/shift_jis/sjis-decode-ms_kanji.html.ini
index 521412a..d1ad47e 100644
--- a/third_party/blink/web_tests/external/wpt/encoding/legacy-mb-japanese/shift_jis/sjis-decode-ms_kanji.html.ini
+++ b/third_party/blink/web_tests/external/wpt/encoding/legacy-mb-japanese/shift_jis/sjis-decode-ms_kanji.html.ini
@@ -1,10 +1,10 @@
 [sjis-decode-ms_kanji.html?6001-7000]
+  expected:
+    if os == "win": [OK, CRASH]
 
 [sjis-decode-ms_kanji.html?4001-5000]
 
 [sjis-decode-ms_kanji.html?3001-4000]
-  expected:
-    if (flag_specific == "") and (os == "linux") and (product == "content_shell"): [OK, CRASH]
 
 [sjis-decode-ms_kanji.html?5001-6000]
 
@@ -13,5 +13,7 @@
 [sjis-decode-ms_kanji.html?7001-last]
 
 [sjis-decode-ms_kanji.html?1001-2000]
+  expected:
+    if (flag_specific == "") and (os == "linux") and (product == "content_shell"): [OK, CRASH]
 
 [sjis-decode-ms_kanji.html?1-1000]
diff --git a/third_party/blink/web_tests/external/wpt/encoding/legacy-mb-tchinese/big5/big5-encode-form.html.ini b/third_party/blink/web_tests/external/wpt/encoding/legacy-mb-tchinese/big5/big5-encode-form.html.ini
deleted file mode 100644
index 32e602f..0000000
--- a/third_party/blink/web_tests/external/wpt/encoding/legacy-mb-tchinese/big5/big5-encode-form.html.ini
+++ /dev/null
@@ -1,31 +0,0 @@
-[big5-encode-form.html?13001-14000]
-
-[big5-encode-form.html?11001-12000]
-
-[big5-encode-form.html?14001-last]
-
-[big5-encode-form.html?2001-3000]
-
-[big5-encode-form.html?9001-10000]
-
-[big5-encode-form.html?1-1000]
-
-[big5-encode-form.html?12001-13000]
-
-[big5-encode-form.html?7001-8000]
-  expected:
-    if (flag_specific == "") and (product == "content_shell"): CRASH
-
-[big5-encode-form.html?5001-6000]
-
-[big5-encode-form.html?3001-4000]
-
-[big5-encode-form.html?10001-11000]
-
-[big5-encode-form.html?4001-5000]
-
-[big5-encode-form.html?6001-7000]
-
-[big5-encode-form.html?1001-2000]
-
-[big5-encode-form.html?8001-9000]
diff --git a/third_party/blink/web_tests/external/wpt/encoding/unsupported-labels.window.js.ini b/third_party/blink/web_tests/external/wpt/encoding/unsupported-labels.window.js.ini
index 9a401ef..ff6ba32 100644
--- a/third_party/blink/web_tests/external/wpt/encoding/unsupported-labels.window.js.ini
+++ b/third_party/blink/web_tests/external/wpt/encoding/unsupported-labels.window.js.ini
@@ -1,6 +1,6 @@
 [unsupported-labels.window.html]
   expected:
-    if product == "chrome": OK
+    if product == "chrome": [OK, TIMEOUT]
     TIMEOUT
   [x-mac-gurmukhi is not supported by the Encoding Standard]
     expected: [PASS, TIMEOUT]
diff --git a/third_party/blink/web_tests/external/wpt/fetch/private-network-access/service-worker-background-fetch.https.window.js.ini b/third_party/blink/web_tests/external/wpt/fetch/private-network-access/service-worker-background-fetch.https.window.js.ini
index 12df1ca..82a91238 100644
--- a/third_party/blink/web_tests/external/wpt/fetch/private-network-access/service-worker-background-fetch.https.window.js.ini
+++ b/third_party/blink/web_tests/external/wpt/fetch/private-network-access/service-worker-background-fetch.https.window.js.ini
@@ -1,12 +1,13 @@
 [service-worker-background-fetch.https.window.html]
   [local to local: success.]
     expected:
+      if (product == "content_shell") and (flag_specific == "disable-site-isolation-trials"): [PASS, FAIL]
       if product == "chrome": FAIL
 
   [private to local: success.]
     expected:
-      if (flag_specific == "") and (os == "linux") and (product == "chrome"): FAIL
-      if (flag_specific == "") and (os == "linux") and (product == "content_shell"): [PASS, FAIL]
+      if (product == "content_shell") and (flag_specific == "") and (os == "linux"): FAIL
+      if product == "chrome": FAIL
 
   [private to private: success.]
     expected:
@@ -14,7 +15,7 @@
 
   [public to local: success.]
     expected:
-      if (flag_specific == "") and (product == "chrome"): FAIL
+      if product == "chrome": FAIL
 
   [public to private: success.]
     expected:
diff --git a/third_party/blink/web_tests/external/wpt/focus/iframe-focus-with-different-site-intermediate-frame.html.ini b/third_party/blink/web_tests/external/wpt/focus/iframe-focus-with-different-site-intermediate-frame.html.ini
index 3dbc2c45..a4e9ffe 100644
--- a/third_party/blink/web_tests/external/wpt/focus/iframe-focus-with-different-site-intermediate-frame.html.ini
+++ b/third_party/blink/web_tests/external/wpt/focus/iframe-focus-with-different-site-intermediate-frame.html.ini
@@ -1,5 +1,3 @@
 [iframe-focus-with-different-site-intermediate-frame.html]
-  expected:
-    if flag_specific == "disable-site-isolation-trials": CRASH
   [Check result]
     expected: FAIL
diff --git a/third_party/blink/web_tests/external/wpt/fs/FileSystemFileHandle-create-sync-access-handle.https.tentative.window.js.ini b/third_party/blink/web_tests/external/wpt/fs/FileSystemFileHandle-create-sync-access-handle.https.tentative.window.js.ini
index c3348a5c..8850b9a 100644
--- a/third_party/blink/web_tests/external/wpt/fs/FileSystemFileHandle-create-sync-access-handle.https.tentative.window.js.ini
+++ b/third_party/blink/web_tests/external/wpt/fs/FileSystemFileHandle-create-sync-access-handle.https.tentative.window.js.ini
@@ -1,4 +1,4 @@
 [FileSystemFileHandle-create-sync-access-handle.https.tentative.window.html]
   [Attempt to create a sync access handle.]
     expected:
-      if os == "win": [PASS, FAIL]
+      if (os == "linux") and (flag_specific == "disable-layout-ng"): [PASS, FAIL]
diff --git a/third_party/blink/web_tests/external/wpt/hr-time/clamped-time-origin-isolated.https.html.ini b/third_party/blink/web_tests/external/wpt/hr-time/clamped-time-origin-isolated.https.html.ini
index 2e88cea..e38372b4 100644
--- a/third_party/blink/web_tests/external/wpt/hr-time/clamped-time-origin-isolated.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/hr-time/clamped-time-origin-isolated.https.html.ini
@@ -1,6 +1,3 @@
 [clamped-time-origin-isolated.https.html]
   expected:
-    if (flag_specific == "") and (product == "chrome"): [OK, TIMEOUT]
-  [timeOrigins are clamped.]
-    expected:
-      if product == "chrome": [PASS, TIMEOUT]
+    if (product == "content_shell") and (flag_specific == "disable-layout-ng"): [OK, TIMEOUT]
diff --git a/third_party/blink/web_tests/external/wpt/html/anonymous-iframe/embedding.tentative.https.window.js.ini b/third_party/blink/web_tests/external/wpt/html/anonymous-iframe/embedding.tentative.https.window.js.ini
index d0866b7dc..be5f4f5 100644
--- a/third_party/blink/web_tests/external/wpt/html/anonymous-iframe/embedding.tentative.https.window.js.ini
+++ b/third_party/blink/web_tests/external/wpt/html/anonymous-iframe/embedding.tentative.https.window.js.ini
@@ -19,8 +19,12 @@
 [embedding.tentative.https.window.html?11-11]
 
 [embedding.tentative.https.window.html?5-5]
+  expected:
+    if product == "chrome": [OK, ERROR]
 
 [embedding.tentative.https.window.html?1-1]
+  expected:
+    if product == "chrome": [OK, ERROR]
 
 [embedding.tentative.https.window.html?6-6]
 
@@ -36,8 +40,6 @@
 [embedding.tentative.https.window.html?4-4]
 
 [embedding.tentative.https.window.html?8-8]
-  expected:
-    if product == "chrome": [OK, ERROR]
 
 [embedding.tentative.https.window.html?3-3]
 
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/browsing-the-web/back-forward-cache/eligibility/inflight-fetch-cors.html.ini b/third_party/blink/web_tests/external/wpt/html/browsers/browsing-the-web/back-forward-cache/eligibility/inflight-fetch-cors.html.ini
index 9541f415..5e823bd 100644
--- a/third_party/blink/web_tests/external/wpt/html/browsers/browsing-the-web/back-forward-cache/eligibility/inflight-fetch-cors.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/browsing-the-web/back-forward-cache/eligibility/inflight-fetch-cors.html.ini
@@ -5,4 +5,5 @@
 
   [Eligibility (in-flight fetch): CORS failed when in BFCache]
     expected:
+      if (product == "content_shell") and (os == "win"): [PASS, PRECONDITION_FAILED]
       if product == "chrome": PRECONDITION_FAILED
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/same-document-traversal-cross-document-traversal.html.ini b/third_party/blink/web_tests/external/wpt/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/same-document-traversal-cross-document-traversal.html.ini
index 2bb8c179..dcb75058 100644
--- a/third_party/blink/web_tests/external/wpt/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/same-document-traversal-cross-document-traversal.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/same-document-traversal-cross-document-traversal.html.ini
@@ -1,7 +1,4 @@
 [same-document-traversal-cross-document-traversal.html]
-  expected:
-    if (flag_specific == "") and (os == "linux") and (product == "content_shell"): [OK, TIMEOUT]
-    if flag_specific == "disable-site-isolation-trials": TIMEOUT
   [traversals in the same (back) direction: queued up]
     expected: [PASS, TIMEOUT]
 
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/history/the-history-interface/010.html.ini b/third_party/blink/web_tests/external/wpt/html/browsers/history/the-history-interface/010.html.ini
new file mode 100644
index 0000000..b94165a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/history/the-history-interface/010.html.ini
@@ -0,0 +1,3 @@
+[010.html]
+  expected:
+    if (flag_specific == "") and (os == "linux") and (product == "content_shell"): [OK, CRASH]
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/history/the-history-interface/combination_history_006.html.ini b/third_party/blink/web_tests/external/wpt/html/browsers/history/the-history-interface/combination_history_006.html.ini
index a339e37..9a7593e 100644
--- a/third_party/blink/web_tests/external/wpt/html/browsers/history/the-history-interface/combination_history_006.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/history/the-history-interface/combination_history_006.html.ini
@@ -1,3 +1,3 @@
 [combination_history_006.html]
   expected:
-    if os == "win": [OK, TIMEOUT]
+    if os == "win": TIMEOUT
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/history/the-history-interface/history_pushstate_url_rewriting.html.ini b/third_party/blink/web_tests/external/wpt/html/browsers/history/the-history-interface/history_pushstate_url_rewriting.html.ini
index d28878e3..b50924d 100644
--- a/third_party/blink/web_tests/external/wpt/html/browsers/history/the-history-interface/history_pushstate_url_rewriting.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/history/the-history-interface/history_pushstate_url_rewriting.html.ini
@@ -1,6 +1,6 @@
 [history_pushstate_url_rewriting.html]
   expected:
-    if flag_specific == "disable-layout-ng": [OK, TIMEOUT]
+    if flag_specific == "disable-layout-ng": TIMEOUT
   [blob:(a blob URL for this origin) to blob:(a blob URL for this origin)?newsearch should not work]
     expected: FAIL
 
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/history/the-location-interface/location-protocol-setter-non-broken.html.ini b/third_party/blink/web_tests/external/wpt/html/browsers/history/the-location-interface/location-protocol-setter-non-broken.html.ini
index 504d86a..df601b42 100644
--- a/third_party/blink/web_tests/external/wpt/html/browsers/history/the-location-interface/location-protocol-setter-non-broken.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/history/the-location-interface/location-protocol-setter-non-broken.html.ini
@@ -1,9 +1,10 @@
 [location-protocol-setter-non-broken.html]
   [Set HTTP URL frame location.protocol to data]
-    expected: FAIL
+    expected:
+      if flag_specific == "disable-layout-ng": [PASS, FAIL]
+      FAIL
 
   [Set data URL frame location.protocol to data]
     expected:
-      if (os == "linux") and (flag_specific == "") and (product == "content_shell"): [PASS, FAIL]
-      if (os == "linux") and (flag_specific == "disable-layout-ng"): PASS
+      if flag_specific == "disable-layout-ng": PASS
       FAIL
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/origin/cross-origin-objects/cross-origin-objects-function-name.html.ini b/third_party/blink/web_tests/external/wpt/html/browsers/origin/cross-origin-objects/cross-origin-objects-function-name.html.ini
index df543428..ab20f7d 100644
--- a/third_party/blink/web_tests/external/wpt/html/browsers/origin/cross-origin-objects/cross-origin-objects-function-name.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/origin/cross-origin-objects/cross-origin-objects-function-name.html.ini
@@ -1,4 +1,6 @@
 [cross-origin-objects-function-name.html]
+  expected:
+    if flag_specific == "disable-site-isolation-trials": [OK, CRASH]
   [Cross-origin Window methods have correct 'name']
     expected: FAIL
 
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-no-port.sub.https.html.ini b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-no-port.sub.https.html.ini
index 5383b4d..5504eb1 100644
--- a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-no-port.sub.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-no-port.sub.https.html.ini
@@ -1,10 +1,8 @@
 [parent-yes-child-no-port.sub.https.html]
   [setting document.domain must not give sync access]
     expected:
-      if (flag_specific == "") and (product == "chrome"): FAIL
-      if flag_specific == "disable-site-isolation-trials": FAIL
+      if product == "chrome": FAIL
 
   [parent: originAgentCluster must equal true]
     expected:
-      if (flag_specific == "") and (product == "chrome"): FAIL
-      if flag_specific == "disable-site-isolation-trials": FAIL
+      if product == "chrome": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-no-same.sub.https.html.ini b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-no-same.sub.https.html.ini
index c5662b4..7683c12 100644
--- a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-no-same.sub.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-no-same.sub.https.html.ini
@@ -1,10 +1,10 @@
 [parent-yes-child-no-same.sub.https.html]
   [parent: originAgentCluster must equal true]
     expected:
-      if (product == "content_shell") and (flag_specific == "disable-site-isolation-trials"): [PASS, FAIL]
-      if product == "chrome": FAIL
+      if (flag_specific == "") and (product == "chrome"): FAIL
+      if flag_specific == "disable-site-isolation-trials": FAIL
 
   [child: originAgentCluster must equal true]
     expected:
-      if (product == "content_shell") and (flag_specific == "disable-site-isolation-trials"): [PASS, FAIL]
-      if product == "chrome": FAIL
+      if (flag_specific == "") and (product == "chrome"): FAIL
+      if flag_specific == "disable-site-isolation-trials": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-no-subdomain.sub.https.html.ini b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-no-subdomain.sub.https.html.ini
index 88fd82f..4df55d70 100644
--- a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-no-subdomain.sub.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-no-subdomain.sub.https.html.ini
@@ -8,4 +8,3 @@
     expected:
       if (flag_specific == "") and (product == "chrome"): FAIL
       if flag_specific == "disable-site-isolation-trials": FAIL
-      if flag_specific == "disable-layout-ng": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-yes-same.sub.https.html.ini b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-yes-same.sub.https.html.ini
index e3a89c0f..2b7fa4f 100644
--- a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-yes-same.sub.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-yes-same.sub.https.html.ini
@@ -1,8 +1,10 @@
 [parent-yes-child-yes-same.sub.https.html]
   [parent: originAgentCluster must equal true]
     expected:
+      if (product == "content_shell") and (flag_specific == "disable-site-isolation-trials"): [PASS, FAIL]
       if product == "chrome": FAIL
 
   [child: originAgentCluster must equal true]
     expected:
+      if (product == "content_shell") and (flag_specific == "disable-site-isolation-trials"): [PASS, FAIL]
       if product == "chrome": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-no-child1-yes-subdomain-child2-no-port.sub.https.html.ini b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-no-child1-yes-subdomain-child2-no-port.sub.https.html.ini
index d91cf4a..5ecea15 100644
--- a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-no-child1-yes-subdomain-child2-no-port.sub.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-no-child1-yes-subdomain-child2-no-port.sub.https.html.ini
@@ -12,10 +12,6 @@
     expected:
       if flag_specific == "disable-site-isolation-trials": [PASS, FAIL]
 
-  [parent: originAgentCluster must equal false]
-    expected:
-      if flag_specific == "disable-site-isolation-trials": [PASS, FAIL]
-
-  [Parent to child2: setting document.domain must give sync access]
+  [Parent to child1: setting document.domain must not give sync access]
     expected:
       if flag_specific == "disable-site-isolation-trials": [PASS, FAIL]
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-no-child1-yes-subdomain-child2-no-subdomain.sub.https.html.ini b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-no-child1-yes-subdomain-child2-no-subdomain.sub.https.html.ini
deleted file mode 100644
index 2ae4867..0000000
--- a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-no-child1-yes-subdomain-child2-no-subdomain.sub.https.html.ini
+++ /dev/null
@@ -1,16 +0,0 @@
-[parent-no-child1-yes-subdomain-child2-no-subdomain.sub.https.html]
-  [Parent to child1: setting document.domain must not give sync access]
-    expected:
-      if flag_specific == "disable-site-isolation-trials": FAIL
-
-  [Parent to child2: setting document.domain must not give sync access]
-    expected:
-      if flag_specific == "disable-site-isolation-trials": FAIL
-
-  [child1: originAgentCluster must equal true]
-    expected:
-      if flag_specific == "disable-site-isolation-trials": FAIL
-
-  [child2: originAgentCluster must equal true]
-    expected:
-      if flag_specific == "disable-site-isolation-trials": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-no-subdomain.sub.https.html.ini b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-no-subdomain.sub.https.html.ini
index 2c563ba..cab93f6 100644
--- a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-no-subdomain.sub.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-no-subdomain.sub.https.html.ini
@@ -1,17 +1,17 @@
 [parent-yes-child1-no-subdomain-child2-no-subdomain.sub.https.html]
   [Parent to child1: setting document.domain must not give sync access]
     expected:
-      if (flag_specific == "") and (product == "chrome"): [PASS, FAIL]
+      if (flag_specific == "") and (product == "chrome"): FAIL
       if flag_specific == "disable-site-isolation-trials": FAIL
 
   [Parent to child2: setting document.domain must not give sync access]
     expected:
-      if (flag_specific == "") and (product == "chrome"): [PASS, FAIL]
+      if (flag_specific == "") and (product == "chrome"): FAIL
       if flag_specific == "disable-site-isolation-trials": FAIL
 
   [parent: originAgentCluster must equal true]
     expected:
-      if (flag_specific == "") and (product == "chrome"): [PASS, FAIL]
+      if (flag_specific == "") and (product == "chrome"): FAIL
       if flag_specific == "disable-site-isolation-trials": FAIL
 
   [child2: originAgentCluster must equal false]
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-no-subdomain2.sub.https.html.ini b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-no-subdomain2.sub.https.html.ini
index aa399ed5..667b95e 100644
--- a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-no-subdomain2.sub.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-no-subdomain2.sub.https.html.ini
@@ -1,25 +1,24 @@
 [parent-yes-child1-no-subdomain-child2-no-subdomain2.sub.https.html]
   [Parent to child1: setting document.domain must not give sync access]
     expected:
-      if (flag_specific == "") and (product == "chrome"): FAIL
-      if flag_specific == "disable-site-isolation-trials": FAIL
+      if product == "chrome": FAIL
 
   [Parent to child2: setting document.domain must not give sync access]
     expected:
-      if (flag_specific == "") and (product == "chrome"): FAIL
-      if flag_specific == "disable-site-isolation-trials": FAIL
+      if product == "chrome": FAIL
 
   [parent: originAgentCluster must equal true]
     expected:
-      if (flag_specific == "") and (product == "chrome"): FAIL
-      if flag_specific == "disable-site-isolation-trials": FAIL
+      if product == "chrome": FAIL
 
   [child2 to child1: setting document.domain must give sync access]
     expected:
-      if (flag_specific == "") and (product == "content_shell"): FAIL
-      if flag_specific == "disable-layout-ng": FAIL
+      if (flag_specific == "") and (product == "chrome"): PASS
+      if flag_specific == "disable-site-isolation-trials": PASS
+      FAIL
 
   [child1 to child2: setting document.domain must give sync access]
     expected:
-      if (flag_specific == "") and (product == "content_shell"): FAIL
-      if flag_specific == "disable-layout-ng": FAIL
+      if (flag_specific == "") and (product == "chrome"): PASS
+      if flag_specific == "disable-site-isolation-trials": PASS
+      FAIL
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomain.sub.https.html.ini b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomain.sub.https.html.ini
index a21f71a..e77ac91 100644
--- a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomain.sub.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomain.sub.https.html.ini
@@ -13,8 +13,8 @@
 
   [child2: originAgentCluster must equal false]
     expected:
-      if flag_specific == "disable-site-isolation-trials": FAIL
+      if flag_specific == "disable-site-isolation-trials": [PASS, FAIL]
 
   [child1: originAgentCluster must equal false]
     expected:
-      if flag_specific == "disable-site-isolation-trials": FAIL
+      if flag_specific == "disable-site-isolation-trials": [PASS, FAIL]
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomain2.sub.https.html.ini b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomain2.sub.https.html.ini
index ca5ad44..adb5640 100644
--- a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomain2.sub.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomain2.sub.https.html.ini
@@ -1,8 +1,8 @@
 [parent-yes-child1-no-subdomain-child2-yes-subdomain2.sub.https.html]
   [parent: originAgentCluster must equal true]
     expected:
-      if product == "chrome": [PASS, FAIL]
+      if (product == "content_shell") and (flag_specific == "disable-site-isolation-trials"): [PASS, FAIL]
 
   [Parent to child1: setting document.domain must not give sync access]
     expected:
-      if product == "chrome": [PASS, FAIL]
+      if (product == "content_shell") and (flag_specific == "disable-site-isolation-trials"): [PASS, FAIL]
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomainport.sub.https.html.ini b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomainport.sub.https.html.ini
index 86a8d77f..8349778 100644
--- a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomainport.sub.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomainport.sub.https.html.ini
@@ -1,10 +1,12 @@
 [parent-yes-child1-no-subdomain-child2-yes-subdomainport.sub.https.html]
   [Parent to child1: setting document.domain must not give sync access]
     expected:
-      if (product == "content_shell") and (flag_specific == "disable-site-isolation-trials"): [PASS, FAIL]
-      if product == "chrome": FAIL
+      if (flag_specific == "") and (product == "chrome"): FAIL
 
   [parent: originAgentCluster must equal true]
     expected:
-      if (product == "content_shell") and (flag_specific == "disable-site-isolation-trials"): [PASS, FAIL]
-      if product == "chrome": FAIL
+      if (flag_specific == "") and (product == "chrome"): FAIL
+
+  [child1: originAgentCluster must equal false]
+    expected:
+      if flag_specific == "disable-site-isolation-trials": [PASS, FAIL]
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-no-port.sub.https.html.ini b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-no-port.sub.https.html.ini
index 54fc998..a3efc434 100644
--- a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-no-port.sub.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-no-port.sub.https.html.ini
@@ -1,26 +1,8 @@
 [parent-yes-child1-yes-subdomain-child2-no-port.sub.https.html]
   [Parent to child2: setting document.domain must not give sync access]
     expected:
-      if (flag_specific == "") and (product == "chrome"): FAIL
-      if flag_specific == "disable-site-isolation-trials": FAIL
+      if product == "chrome": FAIL
 
   [parent: originAgentCluster must equal true]
     expected:
-      if (flag_specific == "") and (product == "chrome"): FAIL
-      if flag_specific == "disable-site-isolation-trials": FAIL
-
-  [child1: originAgentCluster must equal true]
-    expected:
-      if flag_specific == "disable-site-isolation-trials": FAIL
-
-  [child2 to child1: setting document.domain must not give sync access]
-    expected:
-      if flag_specific == "disable-site-isolation-trials": FAIL
-
-  [child1 to child2: setting document.domain must not give sync access]
-    expected:
-      if flag_specific == "disable-site-isolation-trials": FAIL
-
-  [Parent to child1: setting document.domain must not give sync access]
-    expected:
-      if flag_specific == "disable-site-isolation-trials": FAIL
+      if product == "chrome": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomain.sub.https.html.ini b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomain.sub.https.html.ini
index c0dbbaa..3735ac72c 100644
--- a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomain.sub.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomain.sub.https.html.ini
@@ -1,12 +1,21 @@
 [parent-yes-child1-yes-subdomain-child2-yes-subdomain.sub.https.html]
   [parent: originAgentCluster must equal true]
     expected:
-      if (flag_specific == "") and (product == "chrome"): FAIL
+      if (product == "content_shell") and (flag_specific == "disable-site-isolation-trials"): [PASS, FAIL]
+      if product == "chrome": FAIL
 
   [child1: originAgentCluster must equal true]
     expected:
-      if flag_specific == "disable-site-isolation-trials": FAIL
+      if flag_specific == "disable-site-isolation-trials": [PASS, FAIL]
 
   [child2: originAgentCluster must equal true]
     expected:
-      if flag_specific == "disable-site-isolation-trials": FAIL
+      if flag_specific == "disable-site-isolation-trials": [PASS, FAIL]
+
+  [Parent to child2: setting document.domain must not give sync access]
+    expected:
+      if flag_specific == "disable-site-isolation-trials": [PASS, FAIL]
+
+  [Parent to child1: setting document.domain must not give sync access]
+    expected:
+      if flag_specific == "disable-site-isolation-trials": [PASS, FAIL]
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomain2.sub.https.html.ini b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomain2.sub.https.html.ini
deleted file mode 100644
index 97652255..0000000
--- a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomain2.sub.https.html.ini
+++ /dev/null
@@ -1,28 +0,0 @@
-[parent-yes-child1-yes-subdomain-child2-yes-subdomain2.sub.https.html]
-  [child2: originAgentCluster must equal true]
-    expected:
-      if flag_specific == "disable-site-isolation-trials": FAIL
-
-  [child2 to child1: setting document.domain must not give sync access]
-    expected:
-      if flag_specific == "disable-site-isolation-trials": FAIL
-
-  [child1: originAgentCluster must equal true]
-    expected:
-      if flag_specific == "disable-site-isolation-trials": FAIL
-
-  [child1 to child2: setting document.domain must not give sync access]
-    expected:
-      if flag_specific == "disable-site-isolation-trials": FAIL
-
-  [parent: originAgentCluster must equal true]
-    expected:
-      if flag_specific == "disable-site-isolation-trials": FAIL
-
-  [Parent to child1: setting document.domain must not give sync access]
-    expected:
-      if flag_specific == "disable-site-isolation-trials": FAIL
-
-  [Parent to child2: setting document.domain must not give sync access]
-    expected:
-      if flag_specific == "disable-site-isolation-trials": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomainport.sub.https.html.ini b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomainport.sub.https.html.ini
index e3b0508..0572921 100644
--- a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomainport.sub.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomainport.sub.https.html.ini
@@ -1,6 +1,7 @@
 [parent-yes-child1-yes-subdomain-child2-yes-subdomainport.sub.https.html]
   [parent: originAgentCluster must equal true]
     expected:
+      if (product == "content_shell") and (flag_specific == "disable-site-isolation-trials"): [PASS, FAIL]
       if product == "chrome": FAIL
 
   [child1: originAgentCluster must equal true]
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/document-domain.sub.https.html.ini b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/document-domain.sub.https.html.ini
index 019f39a..c32ba34d 100644
--- a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/document-domain.sub.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/document-domain.sub.https.html.ini
@@ -1,11 +1,4 @@
 [document-domain.sub.https.html]
   [Setting document.domain must not change same-originness]
     expected:
-      if (flag_specific == "") and (product == "content_shell") and (os == "linux"): [PASS, FAIL]
-      if (flag_specific == "") and (product == "chrome"): [PASS, FAIL]
-      if flag_specific == "disable-site-isolation-trials": [PASS, FAIL]
-
-  [Having an origin-keyed subdomain child try to set document.domain must not change the document.domain value it sees]
-    expected:
-      if (flag_specific == "") and (os == "linux") and (product == "content_shell"): [PASS, FAIL]
-      if flag_specific == "disable-site-isolation-trials": [PASS, FAIL]
+      if (flag_specific == "") and (os == "linux"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/javascript-url-yes.https.html.ini b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/javascript-url-yes.https.html.ini
index 189de04..300ae43 100644
--- a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/javascript-url-yes.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/javascript-url-yes.https.html.ini
@@ -1,3 +1,3 @@
 [javascript-url-yes.https.html]
   expected:
-    if flag_specific == "disable-site-isolation-trials": [OK, CRASH]
+    if flag_specific == "disable-site-isolation-trials": CRASH
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/removed-iframe.sub.https.html.ini b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/removed-iframe.sub.https.html.ini
new file mode 100644
index 0000000..55f11aaf
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/removed-iframe.sub.https.html.ini
@@ -0,0 +1,4 @@
+[removed-iframe.sub.https.html]
+  [Removing the iframe does not change originAgentCluster]
+    expected:
+      if flag_specific == "disable-site-isolation-trials": [PASS, FAIL]
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/sandboxed-same-origin-iframe-no.https.html.ini b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/sandboxed-same-origin-iframe-no.https.html.ini
index 27d7b90..e890d00d 100644
--- a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/sandboxed-same-origin-iframe-no.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/sandboxed-same-origin-iframe-no.https.html.ini
@@ -1,5 +1,6 @@
 [sandboxed-same-origin-iframe-no.https.html]
   [originAgentCluster must equal false]
     expected:
-      if (flag_specific == "") and (product == "content_shell"): FAIL
-      if flag_specific == "disable-layout-ng": [PASS, FAIL]
+      if (flag_specific == "") and (product == "chrome"): PASS
+      if flag_specific == "disable-site-isolation-trials": PASS
+      FAIL
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/sandboxed-same-origin-iframe-yes.https.html.ini b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/sandboxed-same-origin-iframe-yes.https.html.ini
index 7ab8126..09723da 100644
--- a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/sandboxed-same-origin-iframe-yes.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/sandboxed-same-origin-iframe-yes.https.html.ini
@@ -1,6 +1,6 @@
 [sandboxed-same-origin-iframe-yes.https.html]
   [originAgentCluster must equal true]
     expected:
-      if (flag_specific == "") and (product == "content_shell") and (os == "win"): [PASS, FAIL]
-      if (flag_specific == "") and (product == "chrome"): FAIL
+      if (flag_specific == "") and (os == "linux") and (product == "chrome"): FAIL
+      if (flag_specific == "") and (os == "win"): FAIL
       if flag_specific == "disable-site-isolation-trials": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-no-same-2-yes-subdomain.sub.https.html.ini b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-no-same-2-yes-subdomain.sub.https.html.ini
index ae3cfc94..6d56fe41 100644
--- a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-no-same-2-yes-subdomain.sub.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-no-same-2-yes-subdomain.sub.https.html.ini
@@ -1,12 +1,15 @@
 [parent-no-1-no-same-2-yes-subdomain.sub.https.html]
   [before parent: originAgentCluster must equal false]
     expected:
+      if (flag_specific == "") and (os == "win"): [PASS, FAIL]
       if flag_specific == "disable-layout-ng": FAIL
 
   [before child: originAgentCluster must equal false]
     expected:
+      if (flag_specific == "") and (os == "win"): [PASS, FAIL]
       if flag_specific == "disable-layout-ng": FAIL
 
   [after parent: originAgentCluster must equal false]
     expected:
+      if (flag_specific == "") and (os == "win"): [PASS, FAIL]
       if flag_specific == "disable-layout-ng": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-no-subdomain-2-yes-subdomain.sub.https.html.ini b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-no-subdomain-2-yes-subdomain.sub.https.html.ini
index 9db791e..5f644dc 100644
--- a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-no-subdomain-2-yes-subdomain.sub.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-no-subdomain-2-yes-subdomain.sub.https.html.ini
@@ -1,22 +1,22 @@
 [parent-no-1-no-subdomain-2-yes-subdomain.sub.https.html]
   [Before: parent to child: setting document.domain must give sync access]
     expected:
-      if (flag_specific == "") and (product == "chrome"): PASS
+      if product == "chrome": PASS
       FAIL
 
   [After: parent to child: setting document.domain must give sync access]
     expected:
-      if (flag_specific == "") and (product == "chrome"): PASS
+      if product == "chrome": PASS
       FAIL
 
   [after parent: originAgentCluster must equal false]
     expected:
-      if (flag_specific == "") and (product == "content_shell") and (os == "linux"): FAIL
-      if (flag_specific == "") and (product == "content_shell") and (os == "win"): [PASS, FAIL]
-      if flag_specific == "disable-site-isolation-trials": FAIL
+      if (os == "linux") and (flag_specific == "") and (product == "chrome"): PASS
+      if (os == "linux") and (flag_specific == "disable-layout-ng"): PASS
+      FAIL
 
   [before parent: originAgentCluster must equal false]
     expected:
-      if (flag_specific == "") and (product == "content_shell") and (os == "linux"): FAIL
-      if (flag_specific == "") and (product == "content_shell") and (os == "win"): [PASS, FAIL]
-      if flag_specific == "disable-site-isolation-trials": FAIL
+      if (os == "linux") and (flag_specific == "") and (product == "chrome"): PASS
+      if (os == "linux") and (flag_specific == "disable-layout-ng"): PASS
+      FAIL
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-no-subdomain-2-yes-subdomain2.sub.https.html.ini b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-no-subdomain-2-yes-subdomain2.sub.https.html.ini
index dc60d1c5..aaedd6b 100644
--- a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-no-subdomain-2-yes-subdomain2.sub.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-no-subdomain-2-yes-subdomain2.sub.https.html.ini
@@ -4,24 +4,3 @@
       if (flag_specific == "") and (product == "chrome"): PASS
       if flag_specific == "disable-site-isolation-trials": PASS
       FAIL
-
-  [After: parent to child: setting document.domain must not give sync access]
-    expected:
-      if flag_specific == "disable-site-isolation-trials": FAIL
-
-  [after child: originAgentCluster must equal true]
-    expected:
-      if flag_specific == "disable-layout-ng": [PASS, FAIL]
-      if flag_specific == "disable-site-isolation-trials": FAIL
-
-  [before parent: originAgentCluster must equal false]
-    expected:
-      if os == "win": [PASS, FAIL]
-
-  [before child: originAgentCluster must equal false]
-    expected:
-      if os == "win": [PASS, FAIL]
-
-  [after parent: originAgentCluster must equal false]
-    expected:
-      if os == "win": [PASS, FAIL]
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-subdomain-yes-2-subdomain2-no.sub.https.html.ini b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-subdomain-yes-2-subdomain2-no.sub.https.html.ini
index bb34977..d4d3a092 100644
--- a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-subdomain-yes-2-subdomain2-no.sub.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-subdomain-yes-2-subdomain2-no.sub.https.html.ini
@@ -7,8 +7,8 @@
 
   [after parent: originAgentCluster must equal false]
     expected:
-      if flag_specific == "disable-layout-ng": [PASS, FAIL]
+      if flag_specific == "disable-layout-ng": FAIL
 
   [before parent: originAgentCluster must equal false]
     expected:
-      if flag_specific == "disable-layout-ng": [PASS, FAIL]
+      if flag_specific == "disable-layout-ng": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-yes-1-no-same-2-no-subdomain.sub.https.html.ini b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-yes-1-no-same-2-no-subdomain.sub.https.html.ini
index 9be034b..4abd6d1 100644
--- a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-yes-1-no-same-2-no-subdomain.sub.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-yes-1-no-same-2-no-subdomain.sub.https.html.ini
@@ -1,24 +1,24 @@
 [parent-yes-1-no-same-2-no-subdomain.sub.https.html]
   [After: parent to child: setting document.domain must not give sync access]
     expected:
-      if product == "chrome": FAIL
+      if product == "chrome": [PASS, FAIL]
 
   [before parent: originAgentCluster must equal true]
     expected:
-      if (os == "linux") and (product == "chrome"): FAIL
+      if (os == "linux") and (product == "chrome"): [PASS, FAIL]
       if os == "win": FAIL
 
   [after parent: originAgentCluster must equal true]
     expected:
-      if (os == "linux") and (product == "chrome"): FAIL
+      if (os == "linux") and (product == "chrome"): [PASS, FAIL]
       if os == "win": FAIL
 
   [before child: originAgentCluster must equal true]
     expected:
-      if (os == "linux") and (product == "chrome"): FAIL
+      if (os == "linux") and (product == "chrome"): [PASS, FAIL]
       if os == "win": FAIL
 
   [after child: originAgentCluster must equal false]
     expected:
-      if (flag_specific == "") and (os == "linux") and (product == "content_shell"): [PASS, FAIL]
-      if flag_specific == "disable-site-isolation-trials": [PASS, FAIL]
+      if (flag_specific == "") and (os == "linux") and (product == "content_shell"): FAIL
+      if flag_specific == "disable-site-isolation-trials": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-no-openee-yes-port.sub.https.html.ini b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-no-openee-yes-port.sub.https.html.ini
new file mode 100644
index 0000000..81caef44d
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-no-openee-yes-port.sub.https.html.ini
@@ -0,0 +1,4 @@
+[opener-no-openee-yes-port.sub.https.html]
+  [opener: originAgentCluster must equal false]
+    expected:
+      if flag_specific == "disable-layout-ng": [PASS, FAIL]
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-no-openee-yes-same.sub.https.html.ini b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-no-openee-yes-same.sub.https.html.ini
index 01a9296b..cb4a065 100644
--- a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-no-openee-yes-same.sub.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-no-openee-yes-same.sub.https.html.ini
@@ -1,12 +1,10 @@
 [opener-no-openee-yes-same.sub.https.html]
   [opener: originAgentCluster must equal false]
     expected:
-      if (os == "linux") and (flag_specific == "") and (product == "content_shell"): [PASS, FAIL]
-      if (os == "linux") and (flag_specific == "disable-site-isolation-trials"): [PASS, FAIL]
-      if os == "win": FAIL
+      if (flag_specific == "") and (os == "linux") and (product == "content_shell"): FAIL
+      if flag_specific == "disable-site-isolation-trials": FAIL
 
   [openee: originAgentCluster must equal false]
     expected:
-      if (os == "linux") and (flag_specific == "") and (product == "content_shell"): [PASS, FAIL]
-      if (os == "linux") and (flag_specific == "disable-site-isolation-trials"): [PASS, FAIL]
-      if os == "win": FAIL
+      if (flag_specific == "") and (os == "linux") and (product == "content_shell"): FAIL
+      if flag_specific == "disable-site-isolation-trials": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-no-openee-yes-subdomain.sub.https.html.ini b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-no-openee-yes-subdomain.sub.https.html.ini
index 575ec913..cd6ff86 100644
--- a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-no-openee-yes-subdomain.sub.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-no-openee-yes-subdomain.sub.https.html.ini
@@ -1,9 +1,9 @@
 [opener-no-openee-yes-subdomain.sub.https.html]
   [opener: originAgentCluster must equal false]
     expected:
-      if (flag_specific == "") and (os == "win"): FAIL
       if flag_specific == "disable-layout-ng": FAIL
+      if flag_specific == "disable-site-isolation-trials": [PASS, FAIL]
 
   [openee: originAgentCluster must equal true]
     expected:
-      if os == "win": FAIL
+      if flag_specific == "disable-site-isolation-trials": [PASS, FAIL]
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-no-same.sub.https.html.ini b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-no-same.sub.https.html.ini
index 8b96f964..053a0b7 100644
--- a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-no-same.sub.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-no-same.sub.https.html.ini
@@ -1,10 +1,10 @@
 [opener-yes-openee-no-same.sub.https.html]
   [opener: originAgentCluster must equal true]
     expected:
-      if (product == "content_shell") and (flag_specific == "disable-site-isolation-trials"): [PASS, FAIL]
-      if product == "chrome": FAIL
+      if (flag_specific == "") and (product == "chrome"): FAIL
+      if flag_specific == "disable-site-isolation-trials": FAIL
 
   [openee: originAgentCluster must equal true]
     expected:
-      if (product == "content_shell") and (flag_specific == "disable-site-isolation-trials"): [PASS, FAIL]
-      if product == "chrome": FAIL
+      if (flag_specific == "") and (product == "chrome"): FAIL
+      if flag_specific == "disable-site-isolation-trials": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-no-subdomain.sub.https.html.ini b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-no-subdomain.sub.https.html.ini
index c3ea49453..83fcc1f 100644
--- a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-no-subdomain.sub.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-no-subdomain.sub.https.html.ini
@@ -9,4 +9,4 @@
 
   [openee: originAgentCluster must equal false]
     expected:
-      if flag_specific == "disable-site-isolation-trials": FAIL
+      if flag_specific == "disable-site-isolation-trials": [PASS, FAIL]
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-yes-port.sub.https.html.ini b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-yes-port.sub.https.html.ini
index aa471127..e143134 100644
--- a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-yes-port.sub.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-yes-port.sub.https.html.ini
@@ -1,5 +1,5 @@
 [opener-yes-openee-yes-port.sub.https.html]
   [opener: originAgentCluster must equal true]
     expected:
-      if (flag_specific == "") and (product == "content_shell"): PASS
-      FAIL
+      if (flag_specific == "") and (product == "chrome"): FAIL
+      if flag_specific == "disable-layout-ng": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-yes-subdomain.sub.https.html.ini b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-yes-subdomain.sub.https.html.ini
index 8edfd8e..941c31f 100644
--- a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-yes-subdomain.sub.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-yes-subdomain.sub.https.html.ini
@@ -1,14 +1,9 @@
 [opener-yes-openee-yes-subdomain.sub.https.html]
   [openee: originAgentCluster must equal true]
     expected:
-      if (flag_specific == "") and (os == "linux") and (product == "content_shell"): [PASS, FAIL]
-      if flag_specific == "disable-site-isolation-trials": [PASS, FAIL]
+      if (flag_specific == "") and (os == "linux") and (product == "content_shell"): FAIL
       if flag_specific == "disable-layout-ng": FAIL
 
-  [setting document.domain must not give sync access]
-    expected:
-      if flag_specific == "disable-site-isolation-trials": [PASS, FAIL]
-
   [opener: originAgentCluster must equal true]
     expected:
-      if flag_specific == "disable-site-isolation-trials": [PASS, FAIL]
+      if (flag_specific == "") and (product == "chrome"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/removing-iframes.sub.https.html.ini b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/removing-iframes.sub.https.html.ini
index 2a455225..8afcc3f 100644
--- a/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/removing-iframes.sub.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/removing-iframes.sub.https.html.ini
@@ -1,19 +1,23 @@
 [removing-iframes.sub.https.html]
   [parent: originAgentCluster must equal true]
     expected:
-      if product == "chrome": FAIL
+      if (flag_specific == "") and (product == "chrome"): [PASS, FAIL]
+      if flag_specific == "disable-site-isolation-trials": [PASS, FAIL]
 
   [Parent to child2: setting document.domain must not give sync access]
     expected:
-      if product == "chrome": FAIL
+      if (flag_specific == "") and (product == "chrome"): [PASS, FAIL]
+      if flag_specific == "disable-site-isolation-trials": [PASS, FAIL]
 
   [Before: setting document.domain must not give sync access]
     expected:
-      if product == "chrome": FAIL
+      if (flag_specific == "") and (product == "chrome"): [PASS, FAIL]
+      if flag_specific == "disable-site-isolation-trials": [PASS, FAIL]
 
   [Parent to child3: setting document.domain must not give sync access]
     expected:
-      if product == "chrome": FAIL
+      if (flag_specific == "") and (product == "chrome"): [PASS, FAIL]
+      if flag_specific == "disable-site-isolation-trials": [PASS, FAIL]
 
   [child2 to child3: setting document.domain must give sync access]
     expected:
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/origin/relaxing-the-same-origin-restriction/document_domain_access_details.sub.html.ini b/third_party/blink/web_tests/external/wpt/html/browsers/origin/relaxing-the-same-origin-restriction/document_domain_access_details.sub.html.ini
index 29f86924..ece0b85 100644
--- a/third_party/blink/web_tests/external/wpt/html/browsers/origin/relaxing-the-same-origin-restriction/document_domain_access_details.sub.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/origin/relaxing-the-same-origin-restriction/document_domain_access_details.sub.html.ini
@@ -1,4 +1,6 @@
 [document_domain_access_details.sub.html]
+  expected:
+    if flag_specific == "disable-layout-ng": [OK, TIMEOUT]
   [Access allowed if different-origin but both set document.domain to parent domain.]
     expected:
       if (flag_specific == "") and (product == "chrome"): PASS
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/the-window-object/open-close/open-features-is-popup-condition.html.ini b/third_party/blink/web_tests/external/wpt/html/browsers/the-window-object/open-close/open-features-is-popup-condition.html.ini
index bd8e1e94..6abbca58 100644
--- a/third_party/blink/web_tests/external/wpt/html/browsers/the-window-object/open-close/open-features-is-popup-condition.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/the-window-object/open-close/open-features-is-popup-condition.html.ini
@@ -1,9 +1,11 @@
 [open-features-is-popup-condition.html?position]
 
 [open-features-is-popup-condition.html?combination]
-  expected:
-    if product == "chrome": [OK, ERROR]
 
 [open-features-is-popup-condition.html?single-1]
+  expected:
+    if product == "chrome": [TIMEOUT, OK, ERROR]
 
 [open-features-is-popup-condition.html?single-2]
+  expected:
+    if product == "chrome": [OK, ERROR, TIMEOUT]
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/the-window-object/open-close/open-features-tokenization-width-height.html.ini b/third_party/blink/web_tests/external/wpt/html/browsers/the-window-object/open-close/open-features-tokenization-width-height.html.ini
index cf35157..25200b9 100644
--- a/third_party/blink/web_tests/external/wpt/html/browsers/the-window-object/open-close/open-features-tokenization-width-height.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/the-window-object/open-close/open-features-tokenization-width-height.html.ini
@@ -1,3 +1,3 @@
 [open-features-tokenization-width-height.html]
   expected:
-    if product == "chrome": [OK, ERROR]
+    if product == "chrome": ERROR
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/windows/browsing-context-names/choose-_blank-002.html.ini b/third_party/blink/web_tests/external/wpt/html/browsers/windows/browsing-context-names/choose-_blank-002.html.ini
deleted file mode 100644
index 9038f753..0000000
--- a/third_party/blink/web_tests/external/wpt/html/browsers/windows/browsing-context-names/choose-_blank-002.html.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-[choose-_blank-002.html]
-  expected:
-    if product == "chrome": ERROR
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/wide-gamut-canvas/canvas-display-p3-drawImage-ImageBitmap-video.html.ini b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/wide-gamut-canvas/canvas-display-p3-drawImage-ImageBitmap-video.html.ini
index ced13008..772005c 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/wide-gamut-canvas/canvas-display-p3-drawImage-ImageBitmap-video.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/wide-gamut-canvas/canvas-display-p3-drawImage-ImageBitmap-video.html.ini
@@ -1,6 +1,6 @@
 [canvas-display-p3-drawImage-ImageBitmap-video.html]
   expected:
-    if flag_specific == "disable-site-isolation-trials": [OK, TIMEOUT]
+    if flag_specific == "disable-layout-ng": [OK, TIMEOUT]
   [Rec2020-3FF000000, Context srgb, ImageData srgb, cropSource=false]
     expected:
       if (flag_specific == "") and (os == "linux") and (product == "chrome"): PASS
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.resize.html.ini b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.resize.html.ini
index 838a7c8..a06b13cb 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.resize.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.resize.html.ini
@@ -1,13 +1,12 @@
 [offscreencanvas.resize.html]
   [Verify that resizing an OffscreenCanvas with a webgl context propagates the new size to its placeholder canvas asynchronously.]
     expected:
-      if (flag_specific == "") and (os == "linux") and (product == "chrome"): FAIL
-      if (flag_specific == "") and (os == "win"): [FAIL, PASS]
+      if (os == "linux") and (product == "content_shell") and (flag_specific == "disable-layout-ng"): [PASS, FAIL]
+      if (os == "linux") and (product == "chrome"): FAIL
 
   [Verify that drawImage uses the size of the frame as the intinsic size of a placeholder canvas.]
     expected:
-      if (flag_specific == "") and (os == "linux") and (product == "content_shell"): PASS
-      if flag_specific == "disable-layout-ng": [PASS, FAIL]
+      if (flag_specific == "") and (os == "linux") and (product == "content_shell"): [PASS, FAIL]
       FAIL
 
   [Verify that resizing an OffscreenCanvas with a 2d context propagates the new size to its placeholder canvas asynchronously.]
diff --git a/third_party/blink/web_tests/external/wpt/html/cross-origin-embedder-policy/cross-origin-isolated-permission-worker.https.window.js.ini b/third_party/blink/web_tests/external/wpt/html/cross-origin-embedder-policy/cross-origin-isolated-permission-worker.https.window.js.ini
index 1ed2310..1cda6978 100644
--- a/third_party/blink/web_tests/external/wpt/html/cross-origin-embedder-policy/cross-origin-isolated-permission-worker.https.window.js.ini
+++ b/third_party/blink/web_tests/external/wpt/html/cross-origin-embedder-policy/cross-origin-isolated-permission-worker.https.window.js.ini
@@ -1,5 +1,5 @@
 [cross-origin-isolated-permission-worker.https.window.html]
   expected:
-    if os == "win": [OK, TIMEOUT]
+    if os == "win": TIMEOUT
   [shared_worker (withCoopCoep: true) cross origin isolated permission test]
     expected: FAIL
diff --git a/third_party/blink/web_tests/external/wpt/html/cross-origin-embedder-policy/require-corp-sw.https.html.ini b/third_party/blink/web_tests/external/wpt/html/cross-origin-embedder-policy/require-corp-sw.https.html.ini
new file mode 100644
index 0000000..b50fc62
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/cross-origin-embedder-policy/require-corp-sw.https.html.ini
@@ -0,0 +1,3 @@
+[require-corp-sw.https.html]
+  expected:
+    if os == "win": [OK, TIMEOUT]
diff --git a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/coep-with-cross-origin.https.html.ini b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/coep-with-cross-origin.https.html.ini
index d954e94..73772d1 100644
--- a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/coep-with-cross-origin.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/coep-with-cross-origin.https.html.ini
@@ -1,2 +1,3 @@
 [coep-with-cross-origin.https.html]
-  expected: [OK, ERROR]
+  expected:
+    if product == "chrome": ERROR
diff --git a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/coop-sandbox-redirects-cuts-opener.https.html.ini b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/coop-sandbox-redirects-cuts-opener.https.html.ini
index f4952b86..62c1867 100644
--- a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/coop-sandbox-redirects-cuts-opener.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/coop-sandbox-redirects-cuts-opener.https.html.ini
@@ -1,8 +1,8 @@
 [coop-sandbox-redirects-cuts-opener.https.html]
   [<iframe sandbox="allow-popups allow-scripts"> Sandboxed Cross-Origin-Opener-Policy popup should cut the opener if necessary including during redirects.]
     expected:
-      if (flag_specific == "") and (os == "win"): [PASS, FAIL]
+      if os == "win": FAIL
 
   [<iframe sandbox="allow-popups allow-scripts allow-same-origin"> Sandboxed Cross-Origin-Opener-Policy popup should cut the opener if necessary including during redirects.]
     expected:
-      if os == "win": [PASS, FAIL]
+      if os == "win": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/header-parsing-repeated.https.html.ini b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/header-parsing-repeated.https.html.ini
index 6c6d744..0ff03121 100644
--- a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/header-parsing-repeated.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/header-parsing-repeated.https.html.ini
@@ -1,3 +1,3 @@
 [header-parsing-repeated.https.html]
   expected:
-    if product == "chrome": [OK, ERROR]
+    if product == "chrome": ERROR
diff --git a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/historical/popup-same-origin-unsafe-allow-outgoing-with-cross-origin.https.html.ini b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/historical/popup-same-origin-unsafe-allow-outgoing-with-cross-origin.https.html.ini
index cdf7733..1c7624e8d 100644
--- a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/historical/popup-same-origin-unsafe-allow-outgoing-with-cross-origin.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/historical/popup-same-origin-unsafe-allow-outgoing-with-cross-origin.https.html.ini
@@ -1,3 +1,3 @@
 [popup-same-origin-unsafe-allow-outgoing-with-cross-origin.https.html]
   expected:
-    if product == "chrome": [OK, ERROR]
+    if product == "chrome": ERROR
diff --git a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/historical/popup-same-site-unsafe-allow-outgoing-with-same-site.https.html.ini b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/historical/popup-same-site-unsafe-allow-outgoing-with-same-site.https.html.ini
deleted file mode 100644
index 596920f..0000000
--- a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/historical/popup-same-site-unsafe-allow-outgoing-with-same-site.https.html.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-[popup-same-site-unsafe-allow-outgoing-with-same-site.https.html]
-  expected:
-    if product == "chrome": ERROR
diff --git a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/historical/popup-same-site-with-same-origin.https.html.ini b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/historical/popup-same-site-with-same-origin.https.html.ini
index 71fe175..4a8a69c6 100644
--- a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/historical/popup-same-site-with-same-origin.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/historical/popup-same-site-with-same-origin.https.html.ini
@@ -1,3 +1,3 @@
 [popup-same-site-with-same-origin.https.html]
   expected:
-    if product == "chrome": [OK, ERROR]
+    if product == "chrome": ERROR
diff --git a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/iframe-popup-same-origin-allow-popups-to-same-origin.https.html.ini b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/iframe-popup-same-origin-allow-popups-to-same-origin.https.html.ini
index fbb8b7e..108522d6 100644
--- a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/iframe-popup-same-origin-allow-popups-to-same-origin.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/iframe-popup-same-origin-allow-popups-to-same-origin.https.html.ini
@@ -4,7 +4,7 @@
 
 [iframe-popup-same-origin-allow-popups-to-same-origin.https.html?9-last]
   expected:
-    if product == "chrome": ERROR
+    if product == "chrome": [OK, ERROR]
 
 [iframe-popup-same-origin-allow-popups-to-same-origin.https.html?3-4]
   expected:
diff --git a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/iframe-popup-same-origin-allow-popups-to-unsafe-none.https.html.ini b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/iframe-popup-same-origin-allow-popups-to-unsafe-none.https.html.ini
index a82ac75..4af327b 100644
--- a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/iframe-popup-same-origin-allow-popups-to-unsafe-none.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/iframe-popup-same-origin-allow-popups-to-unsafe-none.https.html.ini
@@ -1,6 +1,6 @@
 [iframe-popup-same-origin-allow-popups-to-unsafe-none.https.html?9-last]
   expected:
-    if product == "chrome": [OK, ERROR]
+    if product == "chrome": ERROR
 
 [iframe-popup-same-origin-allow-popups-to-unsafe-none.https.html?5-6]
   expected:
diff --git a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/iframe-popup-same-origin-to-unsafe-none.https.html.ini b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/iframe-popup-same-origin-to-unsafe-none.https.html.ini
index 8eec163..2cb63b2 100644
--- a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/iframe-popup-same-origin-to-unsafe-none.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/iframe-popup-same-origin-to-unsafe-none.https.html.ini
@@ -11,6 +11,8 @@
     if product == "chrome": ERROR
 
 [iframe-popup-same-origin-to-unsafe-none.https.html?9-last]
+  expected:
+    if product == "chrome": [OK, ERROR]
 
 [iframe-popup-same-origin-to-unsafe-none.https.html?3-4]
   expected:
diff --git a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/javascript-url.https.html.ini b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/javascript-url.https.html.ini
index 3896bea..e78b4a6 100644
--- a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/javascript-url.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/javascript-url.https.html.ini
@@ -8,11 +8,11 @@
 
 [javascript-url.https.html?11-12]
   expected:
-    if product == "chrome": [ERROR, OK]
+    if product == "chrome": ERROR
 
 [javascript-url.https.html?13-14]
   expected:
-    if product == "chrome": ERROR
+    if product == "chrome": [OK, ERROR]
 
 [javascript-url.https.html?15-16]
   expected:
@@ -23,12 +23,10 @@
     if product == "chrome": ERROR
 
 [javascript-url.https.html?5-6]
-  expected:
-    if product == "chrome": [OK, ERROR]
 
 [javascript-url.https.html?7-8]
   expected:
-    if product == "chrome": [OK, ERROR]
+    if product == "chrome": ERROR
 
 [javascript-url.https.html?9-10]
   expected:
diff --git a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/popup-redirect-cache.https.html.ini b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/popup-redirect-cache.https.html.ini
new file mode 100644
index 0000000..d4b9aeaa
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/popup-redirect-cache.https.html.ini
@@ -0,0 +1,11 @@
+[popup-redirect-cache.https.html?6-7]
+
+[popup-redirect-cache.https.html?8-last]
+
+[popup-redirect-cache.https.html?2-3]
+
+[popup-redirect-cache.https.html?0-1]
+
+[popup-redirect-cache.https.html?4-5]
+  expected:
+    if product == "chrome": [ERROR, OK]
diff --git a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/popup-same-origin-allow-popups-with-cross-origin.https.html.ini b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/popup-same-origin-allow-popups-with-cross-origin.https.html.ini
new file mode 100644
index 0000000..ddd028e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/popup-same-origin-allow-popups-with-cross-origin.https.html.ini
@@ -0,0 +1,3 @@
+[popup-same-origin-allow-popups-with-cross-origin.https.html]
+  expected:
+    if product == "chrome": [OK, ERROR]
diff --git a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/popup-same-origin-with-same-origin.https.html.ini b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/popup-same-origin-with-same-origin.https.html.ini
index f1d91d7..817cd70 100644
--- a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/popup-same-origin-with-same-origin.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/popup-same-origin-with-same-origin.https.html.ini
@@ -1,3 +1,3 @@
 [popup-same-origin-with-same-origin.https.html]
   expected:
-    if product == "chrome": ERROR
+    if product == "chrome": [OK, ERROR]
diff --git a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/popup-unsafe-none-with-same-site.https.html.ini b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/popup-unsafe-none-with-same-site.https.html.ini
deleted file mode 100644
index c087e209..0000000
--- a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/popup-unsafe-none-with-same-site.https.html.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-[popup-unsafe-none-with-same-site.https.html]
-  expected:
-    if product == "chrome": ERROR
diff --git a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/popup-unspecified-with-same-site.https.html.ini b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/popup-unspecified-with-same-site.https.html.ini
new file mode 100644
index 0000000..ccfbd06
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/popup-unspecified-with-same-site.https.html.ini
@@ -0,0 +1,3 @@
+[popup-unspecified-with-same-site.https.html]
+  expected:
+    if product == "chrome": [OK, ERROR]
diff --git a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/reporting/access-reporting/access-from-coop-page-to-openee_coop-ro_cross-origin.https.html.ini b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/reporting/access-reporting/access-from-coop-page-to-openee_coop-ro_cross-origin.https.html.ini
index c525d52..9857e85d4 100644
--- a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/reporting/access-reporting/access-from-coop-page-to-openee_coop-ro_cross-origin.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/reporting/access-reporting/access-from-coop-page-to-openee_coop-ro_cross-origin.https.html.ini
@@ -1,3 +1,10 @@
 [access-from-coop-page-to-openee_coop-ro_cross-origin.https.html]
   expected:
-    if product == "chrome": ERROR
+    if product == "chrome": [OK, ERROR]
+  [access-from-coop-page-to-openee, cross-origin]
+    expected:
+      if product == "chrome": FAIL
+
+  [access-from-coop-page-to-openee, cross-origin + redirect]
+    expected:
+      if product == "chrome": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/reporting/access-reporting/access-from-coop-page-to-other_coop-ro.https.html.ini b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/reporting/access-reporting/access-from-coop-page-to-other_coop-ro.https.html.ini
index dd923b49..ad1f304 100644
--- a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/reporting/access-reporting/access-from-coop-page-to-other_coop-ro.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/reporting/access-reporting/access-from-coop-page-to-other_coop-ro.https.html.ini
@@ -1,3 +1,6 @@
 [access-from-coop-page-to-other_coop-ro.https.html]
   expected:
-    if product == "chrome": ERROR
+    if product == "chrome": [OK, ERROR]
+  [access-from-coop-page-to-other (COOP-RO)]
+    expected:
+      if product == "chrome": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/reporting/access-reporting/access-from-coop-page-to-other_coop-ro_cross-origin.https.html.ini b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/reporting/access-reporting/access-from-coop-page-to-other_coop-ro_cross-origin.https.html.ini
index 56b8e03..dd5c96b2 100644
--- a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/reporting/access-reporting/access-from-coop-page-to-other_coop-ro_cross-origin.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/reporting/access-reporting/access-from-coop-page-to-other_coop-ro_cross-origin.https.html.ini
@@ -1,6 +1,3 @@
 [access-from-coop-page-to-other_coop-ro_cross-origin.https.html]
   expected:
-    if product == "chrome": [OK, ERROR]
-  [access-from-coop-page-to-other (COOP-RO)]
-    expected:
-      if product == "chrome": FAIL
+    if product == "chrome": ERROR
diff --git a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/reporting/access-reporting/access-to-coop-page-from-openee_coop-ro.https.html.ini b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/reporting/access-reporting/access-to-coop-page-from-openee_coop-ro.https.html.ini
index bf827e7..45761bca 100644
--- a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/reporting/access-reporting/access-to-coop-page-from-openee_coop-ro.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/reporting/access-reporting/access-to-coop-page-from-openee_coop-ro.https.html.ini
@@ -1,10 +1,3 @@
 [access-to-coop-page-from-openee_coop-ro.https.html]
   expected:
-    if product == "chrome": [OK, ERROR]
-  [access-to-coop-page-from-openee, same-origin + redirect]
-    expected:
-      if product == "chrome": FAIL
-
-  [access-to-coop-page-from-openee, same-origin]
-    expected:
-      if product == "chrome": FAIL
+    if product == "chrome": ERROR
diff --git a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/reporting/access-reporting/access-to-coop-page-from-openee_coop-ro_cross-origin.https.html.ini b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/reporting/access-reporting/access-to-coop-page-from-openee_coop-ro_cross-origin.https.html.ini
index f90dfad..a6093885 100644
--- a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/reporting/access-reporting/access-to-coop-page-from-openee_coop-ro_cross-origin.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/reporting/access-reporting/access-to-coop-page-from-openee_coop-ro_cross-origin.https.html.ini
@@ -1,3 +1,10 @@
 [access-to-coop-page-from-openee_coop-ro_cross-origin.https.html]
   expected:
-    if product == "chrome": ERROR
+    if product == "chrome": [OK, ERROR]
+  [access-to-coop-page-from-openee, cross-origin]
+    expected:
+      if product == "chrome": FAIL
+
+  [access-to-coop-page-from-openee, cross-origin + redirect)]
+    expected:
+      if product == "chrome": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/reporting/access-reporting/property-focus.https.html.ini b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/reporting/access-reporting/property-focus.https.html.ini
index 2db32f1e..4f240a0e 100644
--- a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/reporting/access-reporting/property-focus.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/reporting/access-reporting/property-focus.https.html.ini
@@ -1,3 +1,10 @@
 [property-focus.https.html]
   expected:
-    if product == "chrome": ERROR
+    if product == "chrome": [OK, ERROR]
+  [cross-origin > w => w.focus()]
+    expected:
+      if product == "chrome": FAIL
+
+  [same-origin > w => w.focus()]
+    expected:
+      if product == "chrome": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/reporting/access-reporting/property-frames.https.html.ini b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/reporting/access-reporting/property-frames.https.html.ini
index 01f4fcd..f6d2d33 100644
--- a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/reporting/access-reporting/property-frames.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/reporting/access-reporting/property-frames.https.html.ini
@@ -1,3 +1,10 @@
 [property-frames.https.html]
   expected:
-    if product == "chrome": ERROR
+    if product == "chrome": [OK, ERROR]
+  [cross-origin > w => w.frames]
+    expected:
+      if product == "chrome": FAIL
+
+  [same-origin > w => w.frames]
+    expected:
+      if product == "chrome": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/reporting/access-reporting/property-indexed-getter.https.html.ini b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/reporting/access-reporting/property-indexed-getter.https.html.ini
index 7e33cb0a..9f7656c 100644
--- a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/reporting/access-reporting/property-indexed-getter.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/reporting/access-reporting/property-indexed-getter.https.html.ini
@@ -1,2 +1,3 @@
 [property-indexed-getter.https.html]
-  expected: [OK, ERROR]
+  expected:
+    if product == "chrome": ERROR
diff --git a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/reporting/access-reporting/property-length.https.html.ini b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/reporting/access-reporting/property-length.https.html.ini
index 3435757a..20d6b23 100644
--- a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/reporting/access-reporting/property-length.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/reporting/access-reporting/property-length.https.html.ini
@@ -1,3 +1,10 @@
 [property-length.https.html]
   expected:
-    if product == "chrome": ERROR
+    if product == "chrome": [OK, ERROR]
+  [same-origin > w => w.length]
+    expected:
+      if product == "chrome": FAIL
+
+  [cross-origin > w => w.length]
+    expected:
+      if product == "chrome": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/reporting/access-reporting/property-self.https.html.ini b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/reporting/access-reporting/property-self.https.html.ini
index 419a9ae..189215bc 100644
--- a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/reporting/access-reporting/property-self.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/reporting/access-reporting/property-self.https.html.ini
@@ -1,10 +1,3 @@
 [property-self.https.html]
   expected:
-    if product == "chrome": [OK, ERROR]
-  [same-origin > w => w.self]
-    expected:
-      if product == "chrome": FAIL
-
-  [cross-origin > w => w.self]
-    expected:
-      if product == "chrome": FAIL
+    if product == "chrome": ERROR
diff --git a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/reporting/document-reporting/reporting-redirect-with-same-origin-allow-popups.https.html.ini b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/reporting/document-reporting/reporting-redirect-with-same-origin-allow-popups.https.html.ini
index 80bec0a..18bb1f2 100644
--- a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/reporting/document-reporting/reporting-redirect-with-same-origin-allow-popups.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/reporting/document-reporting/reporting-redirect-with-same-origin-allow-popups.https.html.ini
@@ -1,10 +1,3 @@
 [reporting-redirect-with-same-origin-allow-popups.https.html]
   expected:
-    if product == "chrome": [OK, ERROR]
-  [Same origin openee redirected to same-origin with same-origin-allow-popups]
-    expected:
-      if product == "chrome": FAIL
-
-  [Cross origin openee redirected to same-origin with same-origin-allow-popups]
-    expected:
-      if product == "chrome": FAIL
+    if product == "chrome": ERROR
diff --git a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/reporting/document-reporting/reporting-redirect-with-unsafe-none.https.html.ini b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/reporting/document-reporting/reporting-redirect-with-unsafe-none.https.html.ini
index f600281..7b11492b 100644
--- a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/reporting/document-reporting/reporting-redirect-with-unsafe-none.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/reporting/document-reporting/reporting-redirect-with-unsafe-none.https.html.ini
@@ -1,3 +1,10 @@
 [reporting-redirect-with-unsafe-none.https.html]
   expected:
-    if product == "chrome": ERROR
+    if product == "chrome": [OK, ERROR]
+  [Cross origin openee redirected to same-origin with unsafe-none]
+    expected:
+      if product == "chrome": FAIL
+
+  [Same origin openee redirected to same-origin with unsafe-none]
+    expected:
+      if product == "chrome": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/reporting/navigation-reporting/reporting-redirect-with-same-origin-allow-popups.https.html.ini b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/reporting/navigation-reporting/reporting-redirect-with-same-origin-allow-popups.https.html.ini
index c37bd366..cf651f59 100644
--- a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/reporting/navigation-reporting/reporting-redirect-with-same-origin-allow-popups.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/reporting/navigation-reporting/reporting-redirect-with-same-origin-allow-popups.https.html.ini
@@ -1,6 +1,4 @@
 [reporting-redirect-with-same-origin-allow-popups.https.html]
-  expected:
-    if product == "chrome": [ERROR, OK]
   [Same origin openee redirected to same-origin with same-origin-allow-popups]
     expected:
       if product == "chrome": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/resource-popup.https.html.ini b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/resource-popup.https.html.ini
new file mode 100644
index 0000000..950ed5a35
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/resource-popup.https.html.ini
@@ -0,0 +1,3 @@
+[resource-popup.https.html]
+  expected:
+    if product == "chrome": [OK, ERROR]
diff --git a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/tentative/restrict-properties/iframe-popup-to-soap.https.html.ini b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/tentative/restrict-properties/iframe-popup-to-soap.https.html.ini
index dd70f03..519adab 100644
--- a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/tentative/restrict-properties/iframe-popup-to-soap.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/tentative/restrict-properties/iframe-popup-to-soap.https.html.ini
@@ -12,8 +12,6 @@
 
 [iframe-popup-to-soap.https.html?5-6]
   expected:
-    if product == "chrome": ERROR
+    if product == "chrome": [OK, ERROR]
 
 [iframe-popup-to-soap.https.html?9-last]
-  expected:
-    if product == "chrome": [ERROR, OK]
diff --git a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/tentative/restrict-properties/popup-un.https.html.ini b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/tentative/restrict-properties/popup-un.https.html.ini
index 3825ee4b5..245d44f 100644
--- a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/tentative/restrict-properties/popup-un.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/tentative/restrict-properties/popup-un.https.html.ini
@@ -1,6 +1,4 @@
 [popup-un.https.html]
-  expected:
-    if product == "chrome": ERROR
   [SAME_ORIGIN popup with coop restrict-properties]
     expected: FAIL
 
diff --git a/third_party/blink/web_tests/external/wpt/html/dom/documents/resource-metadata-management/document-cookie.html.ini b/third_party/blink/web_tests/external/wpt/html/dom/documents/resource-metadata-management/document-cookie.html.ini
index b9b8e1df..87ed375 100644
--- a/third_party/blink/web_tests/external/wpt/html/dom/documents/resource-metadata-management/document-cookie.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/dom/documents/resource-metadata-management/document-cookie.html.ini
@@ -1,15 +1,3 @@
 [document-cookie.html]
   [document.cookie 2]
     expected: FAIL
-
-  [document.cookie 1]
-    expected:
-      if product == "chrome": FAIL
-
-  [document has no cookie]
-    expected:
-      if product == "chrome": FAIL
-
-  [document.cookie]
-    expected:
-      if product == "chrome": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/html/dom/render-blocking/parser-inserted-async-script.tentative.html.ini b/third_party/blink/web_tests/external/wpt/html/dom/render-blocking/parser-inserted-async-script.tentative.html.ini
index 632b0b9..fdd58756 100644
--- a/third_party/blink/web_tests/external/wpt/html/dom/render-blocking/parser-inserted-async-script.tentative.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/dom/render-blocking/parser-inserted-async-script.tentative.html.ini
@@ -1,4 +1,4 @@
 [parser-inserted-async-script.tentative.html]
   [Rendering is blocked before render-blocking resources are loaded]
     expected:
-      if (flag_specific == "") and (os == "linux") and (product == "content_shell"): FAIL
+      if (flag_specific == "") and (os == "linux") and (product == "content_shell"): [PASS, FAIL]
diff --git a/third_party/blink/web_tests/external/wpt/html/interaction/focus/the-autofocus-attribute/spin-by-blocking-style-sheet.html.ini b/third_party/blink/web_tests/external/wpt/html/interaction/focus/the-autofocus-attribute/spin-by-blocking-style-sheet.html.ini
index 5d064a0..a197e599 100644
--- a/third_party/blink/web_tests/external/wpt/html/interaction/focus/the-autofocus-attribute/spin-by-blocking-style-sheet.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/interaction/focus/the-autofocus-attribute/spin-by-blocking-style-sheet.html.ini
@@ -1,6 +1,6 @@
 [spin-by-blocking-style-sheet.html]
   expected:
-    if product == "chrome": [OK, TIMEOUT]
+    if product == "chrome": TIMEOUT
   [Script-blocking style sheet should pause flushing autofocus candidates.]
     expected:
-      if product == "chrome": [PASS, TIMEOUT]
+      if product == "chrome": TIMEOUT
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-audio-constructor.html.ini b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-audio-constructor.html.ini
index 832554d..0fb6a830 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-audio-constructor.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-audio-constructor.html.ini
@@ -1,3 +1,5 @@
 [resource-selection-invoke-audio-constructor.html]
   [invoking resource selection with new Audio(src)]
-    expected: FAIL
+    expected:
+      if flag_specific == "disable-site-isolation-trials": PASS
+      FAIL
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-insert-source.html.ini b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-insert-source.html.ini
index 84d99c1..b9e4785 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-insert-source.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-insert-source.html.ini
@@ -1,6 +1,5 @@
 [resource-selection-invoke-insert-source.html]
   [invoking resource selection by inserting <source>]
     expected:
-      if (flag_specific == "") and (product == "chrome"): [PASS, FAIL]
       if flag_specific == "disable-site-isolation-trials": PASS
       FAIL
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-pause-networkState.html.ini b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-pause-networkState.html.ini
index c6704d78..67499a6 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-pause-networkState.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-pause-networkState.html.ini
@@ -1,5 +1,5 @@
 [resource-selection-invoke-pause-networkState.html]
   [NOT invoking resource selection with pause() when networkState is not NETWORK_EMPTY]
     expected:
-      if (flag_specific == "") and (product == "content_shell") and (os == "linux"): [PASS, FAIL]
+      if flag_specific == "disable-site-isolation-trials": [PASS, FAIL]
       FAIL
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-remove-from-document-networkState.html.ini b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-remove-from-document-networkState.html.ini
index a667108..33e2bbb 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-remove-from-document-networkState.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-remove-from-document-networkState.html.ini
@@ -1,5 +1,3 @@
 [resource-selection-invoke-remove-from-document-networkState.html]
   [NOT invoking resource selection with implicit pause() when networkState is not NETWORK_EMPTY]
-    expected:
-      if flag_specific == "disable-layout-ng": [PASS, FAIL]
-      FAIL
+    expected: FAIL
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-remove-src.html.ini b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-remove-src.html.ini
new file mode 100644
index 0000000..b11e0fc
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-remove-src.html.ini
@@ -0,0 +1,5 @@
+[resource-selection-invoke-remove-src.html]
+  [NOT invoking media load or resource selection when removing the src attribute]
+    expected:
+      if flag_specific == "disable-layout-ng": [PASS, FAIL]
+      if flag_specific == "disable-site-isolation-trials": [PASS, FAIL]
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-set-src.html.ini b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-set-src.html.ini
index c0581652..0c0262e 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-set-src.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-set-src.html.ini
@@ -1,3 +1,5 @@
 [resource-selection-invoke-set-src.html]
   [invoking load by setting src]
-    expected: FAIL
+    expected:
+      if product == "chrome": [PASS, FAIL]
+      FAIL
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-after-controls-removed.html.ini b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-after-controls-removed.html.ini
index 9da26fdb..daf8eb0f 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-after-controls-removed.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-after-controls-removed.html.ini
@@ -1,5 +1,3 @@
 [track-cue-rendering-after-controls-removed.html]
   expected:
-    if (os == "linux") and (flag_specific == "") and (product == "content_shell"): [PASS, FAIL]
-    if (os == "linux") and (flag_specific == "disable-site-isolation-trials"): [PASS, FAIL]
-    if os == "win": FAIL
+    if (flag_specific == "") and (os == "linux") and (product == "content_shell"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-iframe-element/sandbox-top-navigation-child-special-cases.tentative.sub.window.js.ini b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-iframe-element/sandbox-top-navigation-child-special-cases.tentative.sub.window.js.ini
index 6781bb4..8e7326e 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-iframe-element/sandbox-top-navigation-child-special-cases.tentative.sub.window.js.ini
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-iframe-element/sandbox-top-navigation-child-special-cases.tentative.sub.window.js.ini
@@ -1,4 +1,5 @@
 [sandbox-top-navigation-child-special-cases.tentative.sub.window.html]
   expected:
+    if (flag_specific == "") and (product == "content_shell") and (os == "win"): [TIMEOUT, OK]
     if (flag_specific == "") and (product == "chrome"): ERROR
-    if flag_specific == "disable-site-isolation-trials": [TIMEOUT, OK]
+    if flag_specific == "disable-site-isolation-trials": TIMEOUT
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-iframe-element/sandbox-top-navigation-child.tentative.sub.window.js.ini b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-iframe-element/sandbox-top-navigation-child.tentative.sub.window.js.ini
index ce6897a..4d916ea 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-iframe-element/sandbox-top-navigation-child.tentative.sub.window.js.ini
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-iframe-element/sandbox-top-navigation-child.tentative.sub.window.js.ini
@@ -1,4 +1,4 @@
 [sandbox-top-navigation-child.tentative.sub.window.html]
   expected:
-    if (product == "content_shell") and (flag_specific == "disable-site-isolation-trials"): [OK, TIMEOUT]
-    if product == "chrome": ERROR
+    if (flag_specific == "") and (product == "chrome"): ERROR
+    if flag_specific == "disable-site-isolation-trials": TIMEOUT
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-iframe-element/sandbox-top-navigation-escalate-privileges.tentative.sub.window.js.ini b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-iframe-element/sandbox-top-navigation-escalate-privileges.tentative.sub.window.js.ini
index ae3370f..8a0440c 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-iframe-element/sandbox-top-navigation-escalate-privileges.tentative.sub.window.js.ini
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-iframe-element/sandbox-top-navigation-escalate-privileges.tentative.sub.window.js.ini
@@ -1,4 +1,3 @@
 [sandbox-top-navigation-escalate-privileges.tentative.sub.window.html]
   expected:
-    if (flag_specific == "") and (product == "chrome"): ERROR
-    if flag_specific == "disable-site-isolation-trials": TIMEOUT
+    if product == "chrome": ERROR
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-iframe-element/sandbox-top-navigation-grandchild.tentative.sub.window.js.ini b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-iframe-element/sandbox-top-navigation-grandchild.tentative.sub.window.js.ini
index 0f880d1..ddb32689 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-iframe-element/sandbox-top-navigation-grandchild.tentative.sub.window.js.ini
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-iframe-element/sandbox-top-navigation-grandchild.tentative.sub.window.js.ini
@@ -1,3 +1,4 @@
 [sandbox-top-navigation-grandchild.tentative.sub.window.html]
   expected:
+    if (product == "content_shell") and (flag_specific == "disable-layout-ng"): [OK, TIMEOUT]
     if product == "chrome": ERROR
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-img-element/srcset/avoid-reload-on-resize.html.ini b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-img-element/srcset/avoid-reload-on-resize.html.ini
new file mode 100644
index 0000000..6825203
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-img-element/srcset/avoid-reload-on-resize.html.ini
@@ -0,0 +1,4 @@
+[avoid-reload-on-resize.html]
+  [Avoid srcset image reloads when viewport resizes]
+    expected:
+      if flag_specific == "disable-layout-ng": [PASS, FAIL]
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/forms/form-submission-0/text-plain.window.js.ini b/third_party/blink/web_tests/external/wpt/html/semantics/forms/form-submission-0/text-plain.window.js.ini
deleted file mode 100644
index 1f34e29..0000000
--- a/third_party/blink/web_tests/external/wpt/html/semantics/forms/form-submission-0/text-plain.window.js.ini
+++ /dev/null
@@ -1,4 +0,0 @@
-[text-plain.window.html]
-  expected:
-    if flag_specific == "disable-layout-ng": [OK, TIMEOUT]
-    if flag_specific == "disable-site-isolation-trials": TIMEOUT
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/forms/form-submission-0/urlencoded2.window.js.ini b/third_party/blink/web_tests/external/wpt/html/semantics/forms/form-submission-0/urlencoded2.window.js.ini
index f3533e60..d66b56e 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/forms/form-submission-0/urlencoded2.window.js.ini
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/forms/form-submission-0/urlencoded2.window.js.ini
@@ -51,7 +51,7 @@
 
   [application/x-www-form-urlencoded: double quote in name (normal form)]
     expected:
-      if product == "chrome": [PASS, TIMEOUT]
+      if product == "chrome": [PASS, NOTRUN]
 
   [application/x-www-form-urlencoded: backslash in filename (formdata event)]
     expected:
@@ -110,3 +110,11 @@
   [application/x-www-form-urlencoded: backslash in value (formdata event)]
     expected:
       if product == "chrome": [PASS, NOTRUN]
+
+  [application/x-www-form-urlencoded: \\n\\r in filename (formdata event)]
+    expected:
+      if product == "chrome": [PASS, NOTRUN]
+
+  [application/x-www-form-urlencoded: \\n\\r in filename (normal form)]
+    expected:
+      if product == "chrome": [PASS, TIMEOUT]
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-selectmenu-element/selectmenu-form-state-restore.tentative.html.ini b/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-selectmenu-element/selectmenu-form-state-restore.tentative.html.ini
index 839c0e1..7f86f19 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-selectmenu-element/selectmenu-form-state-restore.tentative.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-selectmenu-element/selectmenu-form-state-restore.tentative.html.ini
@@ -1,4 +1,4 @@
 [selectmenu-form-state-restore.tentative.html]
   expected:
+    if (flag_specific == "") and (os == "win"): ERROR
     if flag_specific == "disable-layout-ng": ERROR
-    if flag_specific == "disable-site-isolation-trials": [OK, ERROR]
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-selectmenu-element/selectmenu-option-arbitrary-content-displayed.tentative.html.ini b/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-selectmenu-element/selectmenu-option-arbitrary-content-displayed.tentative.html.ini
index 690e048..5ef441d 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-selectmenu-element/selectmenu-option-arbitrary-content-displayed.tentative.html.ini
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-selectmenu-element/selectmenu-option-arbitrary-content-displayed.tentative.html.ini
@@ -1,3 +1,4 @@
 [selectmenu-option-arbitrary-content-displayed.tentative.html]
   expected:
-    if product == "chrome": TIMEOUT
+    if (flag_specific == "") and (product == "chrome"): TIMEOUT
+    if flag_specific == "disable-layout-ng": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/links/following-hyperlinks/activation-behavior.window.js.ini b/third_party/blink/web_tests/external/wpt/html/semantics/links/following-hyperlinks/activation-behavior.window.js.ini
index ecf50d4..51f8916 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/links/following-hyperlinks/activation-behavior.window.js.ini
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/links/following-hyperlinks/activation-behavior.window.js.ini
@@ -1,9 +1,7 @@
 [activation-behavior.window.html]
   [<area> that is not connected should not be followed]
-    expected:
-      if flag_specific == "disable-layout-ng": [PASS, FAIL]
-      FAIL
+    expected: FAIL
 
   [<a> that is not connected should be followed]
     expected:
-      if flag_specific == "disable-layout-ng": [PASS, FAIL]
+      if (flag_specific == "") and (os == "linux") and (product == "content_shell"): [PASS, FAIL]
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/popovers/popover-anchor-nested-display-ref.html b/third_party/blink/web_tests/external/wpt/html/semantics/popovers/popover-anchor-nested-display-ref.html
new file mode 100644
index 0000000..17311f2
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/popovers/popover-anchor-nested-display-ref.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel=author href="mailto:xiaochengh@chromium.org">
+<link rel=help href="https://open-ui.org/components/popup.research.explainer">
+
+<button id=main-menu-button>Show menu</button>
+
+<div id=main-menu>
+  <div>Foo</div>
+  <button id=nested-menu-button>
+    Show nested menu
+  </button>
+  <div>Bar</div>
+</div>
+
+<div id=nested-menu>
+  Baz
+</div>
+
+<style>
+#main-menu-button {
+  position: absolute;
+  top: 200px;
+  left: 100px;
+  width: 100px;
+}
+
+#main-menu {
+  position: absolute;
+  top: 200px;;
+  left: 200px;
+  width: 150px;
+  line-height: 20px;
+}
+
+#nested-menu-button {
+  width: 100%;
+}
+
+#nested-menu {
+  position: absolute;
+  top: 220px;
+  left: 350px;
+}
+
+[popover] {
+  border: 0;
+  margin: 0;
+  padding: 0;
+}
+</style>
+
+<script>
+document.getElementById('main-menu-button').click();
+document.getElementById('nested-menu-button').click();
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/popovers/popover-anchor-nested-display.tentative.html b/third_party/blink/web_tests/external/wpt/html/semantics/popovers/popover-anchor-nested-display.tentative.html
new file mode 100644
index 0000000..426c0fcb
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/popovers/popover-anchor-nested-display.tentative.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel=author href="mailto:xiaochengh@chromium.org">
+<link rel=help href="https://open-ui.org/components/popup.research.explainer">
+<link rel=match href="popover-anchor-nested-display-ref.html">
+
+<button id=main-menu-button popovertoggletarget=main-menu>Show menu</button>
+
+<div id=main-menu popover anchor=main-menu-button>
+  <div>Foo</div>
+  <button id=nested-menu-button popovertoggletarget=nested-menu>
+    Show nested menu
+  </button>
+  <div>Bar</div>
+</div>
+
+<div id=nested-menu popover anchor=nested-menu-button>
+  Baz
+</div>
+
+<style>
+#main-menu-button {
+  position: absolute;
+  top: 200px;
+  left: 100px;
+  width: 100px;
+}
+
+#main-menu {
+  top: anchor(top);
+  left: anchor(right);
+  width: 150px;
+  line-height: 20px;
+}
+
+#nested-menu-button {
+  width: 100%;
+}
+
+#nested-menu {
+  top: anchor(top);
+  left: anchor(right);
+}
+
+[popover] {
+  border: 0;
+  margin: 0;
+  padding: 0;
+}
+</style>
+
+<script>
+document.getElementById('main-menu-button').click();
+document.getElementById('nested-menu-button').click();
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/popovers/popover-anchor-nested-display.tentative.html.ini b/third_party/blink/web_tests/external/wpt/html/semantics/popovers/popover-anchor-nested-display.tentative.html.ini
new file mode 100644
index 0000000..f050958
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/popovers/popover-anchor-nested-display.tentative.html.ini
@@ -0,0 +1,3 @@
+[popover-anchor-nested-display.tentative.html]
+  expected:
+    if flag_specific == "disable-layout-ng": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/idle-detection/idle-detection-allowed-by-permissions-policy.https.sub.html.ini b/third_party/blink/web_tests/external/wpt/idle-detection/idle-detection-allowed-by-permissions-policy.https.sub.html.ini
index 2e5bbac..f6cc69d 100644
--- a/third_party/blink/web_tests/external/wpt/idle-detection/idle-detection-allowed-by-permissions-policy.https.sub.html.ini
+++ b/third_party/blink/web_tests/external/wpt/idle-detection/idle-detection-allowed-by-permissions-policy.https.sub.html.ini
@@ -16,4 +16,4 @@
     expected: NOTRUN
 
   [Inherited header permissions policy allows dedicated workers.]
-    expected: FAIL
+    expected: TIMEOUT
diff --git a/third_party/blink/web_tests/external/wpt/infrastructure/server/webtransport-h3.https.sub.any.js.ini b/third_party/blink/web_tests/external/wpt/infrastructure/server/webtransport-h3.https.sub.any.js.ini
index 92b4799..bd9c4f8 100644
--- a/third_party/blink/web_tests/external/wpt/infrastructure/server/webtransport-h3.https.sub.any.js.ini
+++ b/third_party/blink/web_tests/external/wpt/infrastructure/server/webtransport-h3.https.sub.any.js.ini
@@ -1,13 +1,13 @@
 [webtransport-h3.https.sub.any.sharedworker.html]
   [WebTransport server should be running and should handle a bidirectional stream]
     expected:
-      if os == "win": FAIL
+      if os == "win": [PASS, FAIL]
 
 
 [webtransport-h3.https.sub.any.html]
   [WebTransport server should be running and should handle a bidirectional stream]
     expected:
-      if os == "win": [PASS, FAIL]
+      if os == "win": FAIL
 
 
 [webtransport-h3.https.sub.any.serviceworker.html]
diff --git a/third_party/blink/web_tests/external/wpt/intersection-observer/v2/animated-occlusion.html.ini b/third_party/blink/web_tests/external/wpt/intersection-observer/v2/animated-occlusion.html.ini
new file mode 100644
index 0000000..b8079d2
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/intersection-observer/v2/animated-occlusion.html.ini
@@ -0,0 +1,3 @@
+[animated-occlusion.html]
+  expected:
+    if flag_specific == "disable-site-isolation-trials": [OK, CRASH]
diff --git a/third_party/blink/web_tests/external/wpt/largest-contentful-paint/first-paint-equals-lcp-text.html.ini b/third_party/blink/web_tests/external/wpt/largest-contentful-paint/first-paint-equals-lcp-text.html.ini
index 4f9cabd..88987019 100644
--- a/third_party/blink/web_tests/external/wpt/largest-contentful-paint/first-paint-equals-lcp-text.html.ini
+++ b/third_party/blink/web_tests/external/wpt/largest-contentful-paint/first-paint-equals-lcp-text.html.ini
@@ -1,5 +1,5 @@
 [first-paint-equals-lcp-text.html]
   [FCP and LCP are the same when there is a single text element in the page.]
     expected:
-      if flag_specific == "disable-layout-ng": [FAIL, PASS]
+      if (flag_specific == "") and (os == "win"): [PASS, FAIL]
       FAIL
diff --git a/third_party/blink/web_tests/external/wpt/largest-contentful-paint/non-tao-image-load-before-fcp-render-after.tentative.html.ini b/third_party/blink/web_tests/external/wpt/largest-contentful-paint/non-tao-image-load-before-fcp-render-after.tentative.html.ini
new file mode 100644
index 0000000..5cec90b
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/largest-contentful-paint/non-tao-image-load-before-fcp-render-after.tentative.html.ini
@@ -0,0 +1,3 @@
+[non-tao-image-load-before-fcp-render-after.tentative.html]
+  [Non-Tao Image Load Before FCP and Render After FCP.]
+    expected: FAIL
diff --git a/third_party/blink/web_tests/external/wpt/largest-contentful-paint/non-tao-image-load-before-fcp-render-at-fcp.tentative.html.ini b/third_party/blink/web_tests/external/wpt/largest-contentful-paint/non-tao-image-load-before-fcp-render-at-fcp.tentative.html.ini
new file mode 100644
index 0000000..0cc5e07
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/largest-contentful-paint/non-tao-image-load-before-fcp-render-at-fcp.tentative.html.ini
@@ -0,0 +1,3 @@
+[non-tao-image-load-before-fcp-render-at-fcp.tentative.html]
+  [Non-Tao Image Load Before LCP and Render at the Same Time of FCP.]
+    expected: FAIL
diff --git a/third_party/blink/web_tests/external/wpt/largest-contentful-paint/non-tao-image-subsequent-lcp-candidate.tentative.html.ini b/third_party/blink/web_tests/external/wpt/largest-contentful-paint/non-tao-image-subsequent-lcp-candidate.tentative.html.ini
new file mode 100644
index 0000000..fa8ed913
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/largest-contentful-paint/non-tao-image-subsequent-lcp-candidate.tentative.html.ini
@@ -0,0 +1,3 @@
+[non-tao-image-subsequent-lcp-candidate.tentative.html]
+  [Non-Tao Image Subsequent LCP candidates.]
+    expected: FAIL
diff --git a/third_party/blink/web_tests/external/wpt/loading/early-hints/coep-early-hints-require-corp-final-none.h2.window.js.ini b/third_party/blink/web_tests/external/wpt/loading/early-hints/coep-early-hints-require-corp-final-none.h2.window.js.ini
index ade87392..092a8a731 100644
--- a/third_party/blink/web_tests/external/wpt/loading/early-hints/coep-early-hints-require-corp-final-none.h2.window.js.ini
+++ b/third_party/blink/web_tests/external/wpt/loading/early-hints/coep-early-hints-require-corp-final-none.h2.window.js.ini
@@ -1,2 +1,4 @@
 [coep-early-hints-require-corp-final-none.h2.window.html]
-  expected: ERROR
+  expected:
+    if product == "chrome": [OK, ERROR]
+    ERROR
diff --git a/third_party/blink/web_tests/external/wpt/loading/early-hints/csp-early-hints-allowed-final-allowed.h2.window.js.ini b/third_party/blink/web_tests/external/wpt/loading/early-hints/csp-early-hints-allowed-final-allowed.h2.window.js.ini
index 9071ef7..8774e47e6 100644
--- a/third_party/blink/web_tests/external/wpt/loading/early-hints/csp-early-hints-allowed-final-allowed.h2.window.js.ini
+++ b/third_party/blink/web_tests/external/wpt/loading/early-hints/csp-early-hints-allowed-final-allowed.h2.window.js.ini
@@ -1,2 +1,4 @@
 [csp-early-hints-allowed-final-allowed.h2.window.html]
-  expected: ERROR
+  expected:
+    if product == "chrome": [OK, ERROR]
+    ERROR
diff --git a/third_party/blink/web_tests/external/wpt/loading/early-hints/csp-early-hints-disallowed-final-allowed.h2.window.js.ini b/third_party/blink/web_tests/external/wpt/loading/early-hints/csp-early-hints-disallowed-final-allowed.h2.window.js.ini
index e935dbe..66e9cf6 100644
--- a/third_party/blink/web_tests/external/wpt/loading/early-hints/csp-early-hints-disallowed-final-allowed.h2.window.js.ini
+++ b/third_party/blink/web_tests/external/wpt/loading/early-hints/csp-early-hints-disallowed-final-allowed.h2.window.js.ini
@@ -1,4 +1,4 @@
 [csp-early-hints-disallowed-final-allowed.h2.window.html]
   expected:
-    if product == "chrome": OK
+    if product == "chrome": [OK, ERROR]
     ERROR
diff --git a/third_party/blink/web_tests/external/wpt/loading/early-hints/preload-finished-while-receiving-final-response-body.h2.window.js.ini b/third_party/blink/web_tests/external/wpt/loading/early-hints/preload-finished-while-receiving-final-response-body.h2.window.js.ini
index 4e0b8871..77d6e57 100644
--- a/third_party/blink/web_tests/external/wpt/loading/early-hints/preload-finished-while-receiving-final-response-body.h2.window.js.ini
+++ b/third_party/blink/web_tests/external/wpt/loading/early-hints/preload-finished-while-receiving-final-response-body.h2.window.js.ini
@@ -1,4 +1,2 @@
 [preload-finished-while-receiving-final-response-body.h2.window.html]
-  expected:
-    if product == "chrome": [OK, ERROR]
-    ERROR
+  expected: ERROR
diff --git a/third_party/blink/web_tests/external/wpt/loading/early-hints/preload-in-flight-when-consumed.h2.window.js.ini b/third_party/blink/web_tests/external/wpt/loading/early-hints/preload-in-flight-when-consumed.h2.window.js.ini
index 34414adc..426ffab 100644
--- a/third_party/blink/web_tests/external/wpt/loading/early-hints/preload-in-flight-when-consumed.h2.window.js.ini
+++ b/third_party/blink/web_tests/external/wpt/loading/early-hints/preload-in-flight-when-consumed.h2.window.js.ini
@@ -1,4 +1,4 @@
 [preload-in-flight-when-consumed.h2.window.html]
   expected:
-    if product == "chrome": OK
+    if product == "chrome": [OK, ERROR]
     ERROR
diff --git a/third_party/blink/web_tests/external/wpt/loading/early-hints/redirect-cross-origin-between-early-hints.h2.window.js.ini b/third_party/blink/web_tests/external/wpt/loading/early-hints/redirect-cross-origin-between-early-hints.h2.window.js.ini
index 3a2c386..46629ffe 100644
--- a/third_party/blink/web_tests/external/wpt/loading/early-hints/redirect-cross-origin-between-early-hints.h2.window.js.ini
+++ b/third_party/blink/web_tests/external/wpt/loading/early-hints/redirect-cross-origin-between-early-hints.h2.window.js.ini
@@ -1,4 +1,4 @@
 [redirect-cross-origin-between-early-hints.h2.window.html]
   expected:
-    if product == "chrome": OK
+    if product == "chrome": [OK, ERROR]
     ERROR
diff --git a/third_party/blink/web_tests/external/wpt/longtask-timing/longtask-in-sibling-iframe-crossorigin.html.ini b/third_party/blink/web_tests/external/wpt/longtask-timing/longtask-in-sibling-iframe-crossorigin.html.ini
index 29ede04c..ed8bf35 100644
--- a/third_party/blink/web_tests/external/wpt/longtask-timing/longtask-in-sibling-iframe-crossorigin.html.ini
+++ b/third_party/blink/web_tests/external/wpt/longtask-timing/longtask-in-sibling-iframe-crossorigin.html.ini
@@ -1,4 +1,5 @@
 [longtask-in-sibling-iframe-crossorigin.html]
   [Performance longtask entries from cross-origin iframe are observable in its sibling.]
     expected:
-      if flag_specific == "disable-layout-ng": [PASS, FAIL]
+      if (flag_specific == "") and (os == "linux") and (product == "content_shell"): [PASS, FAIL]
+      if flag_specific == "disable-site-isolation-trials": [PASS, FAIL]
diff --git a/third_party/blink/web_tests/external/wpt/mathml/relations/css-styling/ignored-properties-001.html.ini b/third_party/blink/web_tests/external/wpt/mathml/relations/css-styling/ignored-properties-001.html.ini
index 79419195..edd5bed6 100644
--- a/third_party/blink/web_tests/external/wpt/mathml/relations/css-styling/ignored-properties-001.html.ini
+++ b/third_party/blink/web_tests/external/wpt/mathml/relations/css-styling/ignored-properties-001.html.ini
@@ -1,4 +1,6 @@
 [ignored-properties-001.html]
+  expected:
+    if (os == "linux") and (product == "content_shell") and (flag_specific == ""): [OK, TIMEOUT]
   [maction layout is not affected by width: 100px !important; height: 200px !important;]
     expected: FAIL
 
diff --git a/third_party/blink/web_tests/external/wpt/media-source/mediasource-avtracks.html.ini b/third_party/blink/web_tests/external/wpt/media-source/mediasource-avtracks.html.ini
index 13dc4e501..c55b56b 100644
--- a/third_party/blink/web_tests/external/wpt/media-source/mediasource-avtracks.html.ini
+++ b/third_party/blink/web_tests/external/wpt/media-source/mediasource-avtracks.html.ini
@@ -4,4 +4,5 @@
 
   [Media tracks must be removed when the SourceBuffer is removed from the MediaSource]
     expected:
-      if flag_specific == "disable-layout-ng": FAIL
+      if (os == "linux") and (flag_specific == "disable-layout-ng"): [PASS, FAIL]
+      if os == "win": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/media-source/mediasource-duration.html.ini b/third_party/blink/web_tests/external/wpt/media-source/mediasource-duration.html.ini
index 0b29519..bbd5a8c 100644
--- a/third_party/blink/web_tests/external/wpt/media-source/mediasource-duration.html.ini
+++ b/third_party/blink/web_tests/external/wpt/media-source/mediasource-duration.html.ini
@@ -1,8 +1,12 @@
 [mediasource-duration.html]
   expected:
-    if (flag_specific == "") and (os == "linux") and (product == "content_shell"): ERROR
-    if flag_specific == "disable-layout-ng": [OK, ERROR]
-    if flag_specific == "disable-site-isolation-trials": ERROR
+    if (flag_specific == "") and (os == "linux") and (product == "chrome"): OK
+    if (flag_specific == "") and (os == "win"): OK
+    ERROR
   [Test endOfStream completes previous seek to truncated duration]
     expected:
       if flag_specific == "disable-layout-ng": [PASS, FAIL]
+
+  [Test appendBuffer completes previous seek to truncated duration]
+    expected:
+      if flag_specific == "disable-layout-ng": [PASS, FAIL]
diff --git a/third_party/blink/web_tests/external/wpt/media-source/mediasource-endofstream.html.ini b/third_party/blink/web_tests/external/wpt/media-source/mediasource-endofstream.html.ini
index da1986cf..3fe53b8 100644
--- a/third_party/blink/web_tests/external/wpt/media-source/mediasource-endofstream.html.ini
+++ b/third_party/blink/web_tests/external/wpt/media-source/mediasource-endofstream.html.ini
@@ -1,9 +1,9 @@
 [mediasource-endofstream.html]
   expected:
-    if product == "chrome": [OK, TIMEOUT]
+    if product == "chrome": TIMEOUT
   [MediaSource.endOfStream(): duration truncated to 0 when there are no buffered coded frames]
     expected: FAIL
 
   [MediaSource.endOfStream(): media element notified that it now has all of the media data]
     expected:
-      if product == "chrome": [PASS, TIMEOUT]
+      if product == "chrome": TIMEOUT
diff --git a/third_party/blink/web_tests/external/wpt/native-io/detach_iframe_during_open.https.window.js.ini b/third_party/blink/web_tests/external/wpt/native-io/detach_iframe_during_open.https.window.js.ini
new file mode 100644
index 0000000..1f325167
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/native-io/detach_iframe_during_open.https.window.js.ini
@@ -0,0 +1,3 @@
+[detach_iframe_during_open.https.window.html]
+  expected:
+    if (flag_specific == "") and (os == "linux") and (product == "content_shell"): [OK, CRASH]
diff --git a/third_party/blink/web_tests/external/wpt/navigation-api/scroll-behavior/after-transition-intercept-handler-modifies.html.ini b/third_party/blink/web_tests/external/wpt/navigation-api/scroll-behavior/after-transition-intercept-handler-modifies.html.ini
index 411718d..b7b9b42 100644
--- a/third_party/blink/web_tests/external/wpt/navigation-api/scroll-behavior/after-transition-intercept-handler-modifies.html.ini
+++ b/third_party/blink/web_tests/external/wpt/navigation-api/scroll-behavior/after-transition-intercept-handler-modifies.html.ini
@@ -1,4 +1,4 @@
 [after-transition-intercept-handler-modifies.html]
   [scroll: state should be saved before intercept handlers run]
     expected:
-      if flag_specific == "disable-site-isolation-trials": [PASS, FAIL]
+      if flag_specific == "disable-layout-ng": [PASS, FAIL]
diff --git a/third_party/blink/web_tests/external/wpt/navigation-timing/test-performance-attributes.sub.html.ini b/third_party/blink/web_tests/external/wpt/navigation-timing/test-performance-attributes.sub.html.ini
index 45c94f0..c0db19d 100644
--- a/third_party/blink/web_tests/external/wpt/navigation-timing/test-performance-attributes.sub.html.ini
+++ b/third_party/blink/web_tests/external/wpt/navigation-timing/test-performance-attributes.sub.html.ini
@@ -1,4 +1,4 @@
 [test-performance-attributes.sub.html]
   [Check that performance.timing has reasonable values for secureConnectionStart and other attributes]
     expected:
-      if os == "win": [PASS, FAIL]
+      if os == "win": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/paint-timing/fcp-only/fcp-invisible-3d-rotate.html.ini b/third_party/blink/web_tests/external/wpt/paint-timing/fcp-only/fcp-invisible-3d-rotate.html.ini
index 7f20bcb5..a2377dfd 100644
--- a/third_party/blink/web_tests/external/wpt/paint-timing/fcp-only/fcp-invisible-3d-rotate.html.ini
+++ b/third_party/blink/web_tests/external/wpt/paint-timing/fcp-only/fcp-invisible-3d-rotate.html.ini
@@ -1,5 +1,3 @@
 [fcp-invisible-3d-rotate.html]
   [First contentful paint fires due to 3d rotation into view.]
-    expected:
-      if product == "chrome": PASS
-      FAIL
+    expected: FAIL
diff --git a/third_party/blink/web_tests/external/wpt/pending-beacon/pending_beacon-sendondiscard.tentative.https.window.js.ini b/third_party/blink/web_tests/external/wpt/pending-beacon/pending_beacon-sendondiscard.tentative.https.window.js.ini
index fc638a3..904cd16 100644
--- a/third_party/blink/web_tests/external/wpt/pending-beacon/pending_beacon-sendondiscard.tentative.https.window.js.ini
+++ b/third_party/blink/web_tests/external/wpt/pending-beacon/pending_beacon-sendondiscard.tentative.https.window.js.ini
@@ -3,18 +3,12 @@
     if product == "chrome": ERROR
   [A discarded document does not send an already sent beacon.]
     expected:
-      if (flag_specific == "") and (os == "linux"): [PASS, FAIL]
-      if flag_specific == "disable-site-isolation-trials": [PASS, FAIL]
+      if flag_specific == "disable-layout-ng": [PASS, FAIL]
 
   [A discarded document sends all its beacons of which timeouts are not\n    default.]
     expected:
-      if flag_specific == "disable-site-isolation-trials": [PASS, FAIL]
-
-  [A discarded document sends all its beacons with default config.]
-    expected:
-      if (flag_specific == "") and (os == "linux"): [PASS, FAIL]
+      if flag_specific == "disable-layout-ng": [PASS, FAIL]
 
   [A discarded document sends all its beacons of which backgroundTimeouts are\n    not default.]
     expected:
-      if (flag_specific == "") and (os == "linux"): [FAIL, PASS]
-      if flag_specific == "disable-site-isolation-trials": [PASS, FAIL]
+      if flag_specific == "disable-layout-ng": [PASS, FAIL]
diff --git a/third_party/blink/web_tests/external/wpt/performance-timeline/cross-origin-non-tao-image.sub.html.ini b/third_party/blink/web_tests/external/wpt/performance-timeline/cross-origin-non-tao-image.sub.html.ini
deleted file mode 100644
index 433428eb..0000000
--- a/third_party/blink/web_tests/external/wpt/performance-timeline/cross-origin-non-tao-image.sub.html.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-[cross-origin-non-tao-image.sub.html]
-  [lcp and fcp]
-    expected: FAIL
diff --git a/third_party/blink/web_tests/external/wpt/performance-timeline/navigation-id-element-timing.tentative.html.ini b/third_party/blink/web_tests/external/wpt/performance-timeline/navigation-id-element-timing.tentative.html.ini
index 0859b39..11ce5f1 100644
--- a/third_party/blink/web_tests/external/wpt/performance-timeline/navigation-id-element-timing.tentative.html.ini
+++ b/third_party/blink/web_tests/external/wpt/performance-timeline/navigation-id-element-timing.tentative.html.ini
@@ -1,4 +1,5 @@
 [navigation-id-element-timing.tentative.html]
   [Element Timing navigation id test]
     expected:
+      if (product == "content_shell") and (os == "win"): [PASS, FAIL]
       if product == "chrome": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/performance-timeline/not-restored-reasons/performance-navigation-timing-same-origin-bfcache.tentative.window.js.ini b/third_party/blink/web_tests/external/wpt/performance-timeline/not-restored-reasons/performance-navigation-timing-same-origin-bfcache.tentative.window.js.ini
index e10b0da..680f893 100644
--- a/third_party/blink/web_tests/external/wpt/performance-timeline/not-restored-reasons/performance-navigation-timing-same-origin-bfcache.tentative.window.js.ini
+++ b/third_party/blink/web_tests/external/wpt/performance-timeline/not-restored-reasons/performance-navigation-timing-same-origin-bfcache.tentative.window.js.ini
@@ -1,5 +1,5 @@
 [performance-navigation-timing-same-origin-bfcache.tentative.window.html]
   [RemoteContextHelper navigation using BFCache]
     expected:
-      if (product == "content_shell") and (flag_specific == "disable-layout-ng"): [PASS, FAIL]
-      if product == "chrome": FAIL
+      if (flag_specific == "") and (product == "chrome"): FAIL
+      if flag_specific == "disable-layout-ng": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/performance-timeline/resources/child-frame.html b/third_party/blink/web_tests/external/wpt/performance-timeline/resources/child-frame.html
new file mode 100644
index 0000000..846979358
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/performance-timeline/resources/child-frame.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<head></head>
+<body></body>
+<script>
+    performance.mark("entry-name")
+</script>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/performance-timeline/resources/parent-frame-with-child.sub.html b/third_party/blink/web_tests/external/wpt/performance-timeline/resources/parent-frame-with-child.sub.html
new file mode 100644
index 0000000..32dd4cb
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/performance-timeline/resources/parent-frame-with-child.sub.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<head></head>
+<body></body>
+<script>
+    const childFrame = document.createElement('iframe')
+    childFrame.src = "http://{{hosts[][]}}:{{ports[http][0]}}/performance-timeline/resources/child-frame.html"
+    document.body.appendChild(childFrame)
+
+    performance.mark("entry-name")
+
+    childFrame.addEventListener('load', () => {
+        window.parent.postMessage("DONE", "*")
+    })
+</script>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/performance-timeline/tentative/include-frames-one-local-child-one-local-grandchild.html b/third_party/blink/web_tests/external/wpt/performance-timeline/tentative/include-frames-one-local-child-one-local-grandchild.html
new file mode 100644
index 0000000..ee2dadd
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/performance-timeline/tentative/include-frames-one-local-child-one-local-grandchild.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+</body>
+<script>
+promise_test(() => {
+    return new Promise(resolve => {
+        performance.clearResourceTimings()
+
+        // Create child iframe.
+        const childFrame = document.createElement('iframe')
+        childFrame.src = "../resources/parent-frame-with-child.sub.html"
+        document.body.appendChild(childFrame)
+
+        // Listen for postMessage() from grandchild frame.
+        window.addEventListener("message", () => {
+            const entries = performance.getEntries(true)
+            const navigationEntries = performance.getEntriesByType("navigation", true)
+            const markedEntries = performance.getEntriesByName("entry-name", undefined, true)
+
+            // 4 entries for parent, 3 for child, 2 for grandchild.
+            assert_equals(entries.length, 9)
+
+            // 1 entry for parent, 1 for child, 1 for grandchild.
+            assert_equals(navigationEntries.length, 3)
+
+            // 1 entry for child, 1 for grandchild.
+            assert_equals(markedEntries.length, 2)
+
+            resolve()
+        })
+    })
+}, "GetEntries of a ParentFrame with one LocalFrame child and one LocalFrame grandchild")
+</script>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/performance-timeline/tentative/include-frames-one-local-child-one-remote-child.sub.html b/third_party/blink/web_tests/external/wpt/performance-timeline/tentative/include-frames-one-local-child-one-remote-child.sub.html
new file mode 100644
index 0000000..8e1ca97
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/performance-timeline/tentative/include-frames-one-local-child-one-remote-child.sub.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+</body>
+<script>
+promise_test(() => {
+    return new Promise(resolve => {
+        performance.clearResourceTimings()
+
+        let localFrameLoaded = false
+        let remoteFrameLoaded = false
+
+        // Create first child iframe.
+        const localChildFrame = document.createElement('iframe')
+        localChildFrame.src = "../resources/child-frame.html"
+        document.body.appendChild(localChildFrame)
+
+        localChildFrame.addEventListener("load", () => {
+            localFrameLoaded = true
+            if (remoteFrameLoaded)
+                verifyPerformanceEntries()
+        })
+
+        // Create second child iframe.
+        const remoteChildFrame = document.createElement('iframe')
+        remoteChildFrame.src = "http://{{hosts[][www]}}:{{ports[http][0]}}/resources/child-frame.html"
+        document.body.appendChild(remoteChildFrame)
+
+        remoteChildFrame.addEventListener("load", () => {
+            remoteFrameLoaded = true
+            if (localFrameLoaded)
+                verifyPerformanceEntries()
+        })
+
+        function verifyPerformanceEntries() {
+            const entries = performance.getEntries(true)
+            const navigationEntries = performance.getEntriesByType("navigation", true)
+            const markedEntries = performance.getEntriesByName("entry-name", undefined, true)
+
+            // 4 entries for parent, 2 for local child, 0 for remote child.
+            assert_equals(entries.length, 6)
+
+            // 1 entry for parent, 1 for local child.
+            assert_equals(navigationEntries.length, 2)
+
+            // 1 entry for local child.
+            assert_equals(markedEntries.length, 1)
+
+            resolve()
+        }
+    })
+}, "GetEntries of a ParentFrame with one RemoteFrame child and one LocalFrame child")
+</script>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/performance-timeline/tentative/include-frames-one-local-child.html b/third_party/blink/web_tests/external/wpt/performance-timeline/tentative/include-frames-one-local-child.html
new file mode 100644
index 0000000..46d4079f
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/performance-timeline/tentative/include-frames-one-local-child.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+</body>
+<script>
+promise_test(() => {
+    return new Promise(resolve => {
+        performance.clearResourceTimings()
+
+        // Create child iframe.
+        const childFrame = document.createElement('iframe')
+        childFrame.src = "../resources/child-frame.html"
+        document.body.appendChild(childFrame)
+
+        // On-load event handler to assert size of entries.
+        childFrame.addEventListener("load", () => {
+            const entries = performance.getEntries(true)
+            const navigationEntries = performance.getEntriesByType("navigation", true)
+            const markedEntries = performance.getEntriesByName("entry-name", undefined, true)
+
+            // 3 entries for parent, 2 for child.
+            assert_equals(entries.length, 5)
+
+            // 1 entry for parent, 1 for child.
+            assert_equals(navigationEntries.length, 2)
+
+            // 1 entry for child.
+            assert_equals(markedEntries.length, 1)
+
+            resolve()
+        })
+    })
+}, "GetEntries of a Parent Frame with one LocalFrame child");
+</script>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/performance-timeline/tentative/include-frames-one-remote-child-one-local-grandchild.sub.html b/third_party/blink/web_tests/external/wpt/performance-timeline/tentative/include-frames-one-remote-child-one-local-grandchild.sub.html
new file mode 100644
index 0000000..a9efe41
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/performance-timeline/tentative/include-frames-one-remote-child-one-local-grandchild.sub.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+</body>
+<script>
+promise_test(() => {
+    return new Promise(resolve => {
+        performance.clearResourceTimings()
+
+        // Create child iframe.
+        const remoteChildFrame = document.createElement('iframe')
+        remoteChildFrame.src = "http://{{hosts[][www]}}:{{ports[http][0]}}/performance-timeline/resources/parent-frame-with-child.sub.html"
+        document.body.appendChild(remoteChildFrame)
+
+        // Listen for postMessage() from grandchild frame.
+        window.addEventListener("message", () => {
+            const entries = performance.getEntries(true)
+            const navigationEntries = performance.getEntriesByType("navigation", true)
+            const markedEntries = performance.getEntriesByName("entry-name", undefined, true)
+
+            // 3 entries for parent, 0 for child, 2 for grandchild.
+            assert_equals(entries.length, 5)
+
+            // 1 entry for parent, 1 for grandchild.
+            assert_equals(navigationEntries.length, 2)
+
+            // 1 entry for grandchild.
+            assert_equals(markedEntries.length, 1)
+
+            resolve()
+        })
+    })
+}, "GetEntries of a ParentFrame with one RemoteFrame child and one LocalFrame grandchild")
+</script>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/performance-timeline/tentative/include-frames-one-remote-child.sub.html b/third_party/blink/web_tests/external/wpt/performance-timeline/tentative/include-frames-one-remote-child.sub.html
new file mode 100644
index 0000000..822924c
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/performance-timeline/tentative/include-frames-one-remote-child.sub.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+</body>
+<script>
+promise_test(() => {
+    return new Promise(resolve => {
+        performance.clearResourceTimings()
+
+        // Create Remote child iframe.
+        const childFrame = document.createElement('iframe')
+        childFrame.src = "http://{{hosts[][www]}}:{{ports[http][0]}}/resources/child-frame.html"
+        document.body.appendChild(childFrame)
+
+        childFrame.addEventListener("load", () => {
+            const entries = performance.getEntries(true)
+            const navigationEntries = performance.getEntriesByType("navigation", true)
+            const markedEntries = performance.getEntriesByName("entry-name", undefined, true)
+
+            // 3 entries for parent, 0 for child.
+            assert_equals(entries.length, 3)
+
+            // 1 entry for parent.
+            assert_equals(navigationEntries.length, 1)
+
+            // 0 entries.
+            assert_equals(markedEntries.length, 0)
+
+            resolve()
+        })
+    })
+}, "GetEntries of a ParentFrame with one RemoteFrame child")
+</script>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/performance-timeline/tentative/include-frames-two-local-children.html b/third_party/blink/web_tests/external/wpt/performance-timeline/tentative/include-frames-two-local-children.html
new file mode 100644
index 0000000..d027405
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/performance-timeline/tentative/include-frames-two-local-children.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+</body>
+<script>
+promise_test(() => {
+    return new Promise(resolve => {
+        performance.clearResourceTimings()
+
+        let childFrameOneLoaded = false
+        let childFrameTwoLoaded = false
+
+        // Create first child iframe.
+        const childFrameOne = document.createElement('iframe')
+        childFrameOne.src = "../resources/child-frame.html"
+        document.body.appendChild(childFrameOne)
+
+        childFrameOne.addEventListener("load", () => {
+            childFrameOneLoaded = true
+            if (childFrameTwoLoaded)
+                verifyPerformanceEntries()
+        })
+
+        // Create second child iframe.
+        const childFrameTwo = document.createElement('iframe')
+        childFrameTwo.src = "../resources/child-frame.html"
+        document.body.appendChild(childFrameTwo)
+
+        childFrameTwo.addEventListener("load", () => {
+            childFrameTwoLoaded = true
+            if (childFrameOneLoaded)
+                verifyPerformanceEntries()
+        })
+
+        function verifyPerformanceEntries() {
+            const entries = performance.getEntries(true)
+            const navigationEntries = performance.getEntriesByType("navigation", true)
+            const markedEntries = performance.getEntriesByName("entry-name", undefined, true)
+
+            // 4 entries for parent, 2 for each child
+            assert_equals(entries.length, 8)
+
+            // 1 entry for parent, 1 for each child.
+            assert_equals(navigationEntries.length, 3)
+
+            // 1 entry for each child.
+            assert_equals(markedEntries.length, 2)
+
+            resolve()
+        }
+    })
+}, "GetEntries of a Parent Frame with two LocalFrame children")
+</script>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/referrer-policy/generic/first-meta-changed-after-second-added.http.html b/third_party/blink/web_tests/external/wpt/referrer-policy/generic/first-meta-changed-after-second-added.http.html
new file mode 100644
index 0000000..2ee5ade
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/referrer-policy/generic/first-meta-changed-after-second-added.http.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<head>
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+
+  <meta name="referrer" content="no-referrer" id="referrermeta">
+</head>
+<body>
+  <script>
+    async function fetchAndGetReferrer() {
+      let response = await fetch('/common/security-features/subresource/xhr.py');
+      let data = await response.json();
+      return data.headers.referer;
+    }
+
+    promise_test(async t => {
+      assert_equals(await fetchAndGetReferrer(), undefined,
+                    'referrer should not be set');
+
+      // Add second meta _before_ first meta.
+      const first_meta = document.getElementById('referrermeta');
+      const second_meta = document.createElement('meta');
+      second_meta.name = 'referrer';
+      second_meta.content = 'strict-origin';
+      document.head.appendChild(second_meta);
+      assert_equals(await fetchAndGetReferrer(), window.location.origin + '/',
+                    'referrer should be origin only');
+
+      // Update content attribute of first meta.
+      first_meta.content = 'unsafe-url';
+      assert_equals(await fetchAndGetReferrer(), window.location.href,
+                    'referrer should be full url');
+      }, 'document referrer policy is the value of the most recently modified <meta name="referrer"');
+
+  </script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/referrer-policy/generic/meta-referrer-removed-1.http.html b/third_party/blink/web_tests/external/wpt/referrer-policy/generic/meta-referrer-removed-1.http.html
new file mode 100644
index 0000000..7027788
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/referrer-policy/generic/meta-referrer-removed-1.http.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<head>
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+
+  <meta name="referrer" content="no-referrer" id="referrermeta">
+</head>
+<body>
+  <script>
+    async function fetchAndGetReferrer() {
+      let response = await fetch('/common/security-features/subresource/xhr.py');
+      let data = await response.json();
+      return data.headers.referer;
+    }
+
+    promise_test(async t => {
+      assert_equals(await fetchAndGetReferrer(), undefined,
+                    'referrer should not be set');
+
+      document.getElementById('referrermeta').remove();
+      assert_equals(await fetchAndGetReferrer(), undefined,
+                    'referrer should not be set');
+    }, 'removing <meta name="referrer"> should not change referrer policy');
+  </script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/referrer-policy/generic/meta-referrer-removed-2.http.html b/third_party/blink/web_tests/external/wpt/referrer-policy/generic/meta-referrer-removed-2.http.html
new file mode 100644
index 0000000..42f73e8
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/referrer-policy/generic/meta-referrer-removed-2.http.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<head>
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+
+  <meta name="referrer" content="no-referrer" id="referrermeta">
+</head>
+<body>
+  <script>
+    async function fetchAndGetReferrer() {
+      let response = await fetch('/common/security-features/subresource/xhr.py');
+      let data = await response.json();
+      return data.headers.referer;
+    }
+
+    promise_test(async t => {
+      assert_equals(await fetchAndGetReferrer(), undefined,
+                    'referrer should not be set');
+
+      // Add second meta tag.
+      const second_meta = document.createElement('meta');
+      second_meta.name = 'referrer';
+      second_meta.content = 'strict-origin';
+      document.head.appendChild(second_meta);
+      // Second meta should override the first.
+      assert_equals(await fetchAndGetReferrer(), location.origin + '/',
+                    'referrer should be origin only');
+
+      second_meta.remove();
+      assert_equals(await fetchAndGetReferrer(), window.location.origin + '/',
+                    'referrer should be origin only');
+    }, 'referrer policy does not change when second <meta name="referrer"> is removed');
+  </script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/referrer-policy/generic/second-meta-referrer-added-before-first.http.html b/third_party/blink/web_tests/external/wpt/referrer-policy/generic/second-meta-referrer-added-before-first.http.html
new file mode 100644
index 0000000..38fab90
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/referrer-policy/generic/second-meta-referrer-added-before-first.http.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<head>
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+
+  <meta name="referrer" content="no-referrer" id="referrermeta">
+</head>
+<body>
+  <script>
+    async function fetchAndGetReferrer() {
+      let response = await fetch('/common/security-features/subresource/xhr.py');
+      let data = await response.json();
+      return data.headers.referer;
+    }
+
+    promise_test(async t => {
+      assert_equals(await fetchAndGetReferrer(), undefined,
+                    'referrer should not be set');
+
+      // Add second meta _before_ first meta.
+      const first_meta = document.getElementById('referrermeta');
+      const second_meta = document.createElement('meta');
+      second_meta.name = 'referrer';
+      second_meta.content = 'strict-origin';
+      document.head.insertBefore(second_meta, first_meta);
+      assert_equals(await fetchAndGetReferrer(), window.location.origin + '/',
+                    'referrer should the origin only');
+    }, 'document referrer policy is the value of the most recently added <meta name="referrer">');
+
+  </script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/requestidlecallback/callback-exception.html.ini b/third_party/blink/web_tests/external/wpt/requestidlecallback/callback-exception.html.ini
deleted file mode 100644
index bac37b0..0000000
--- a/third_party/blink/web_tests/external/wpt/requestidlecallback/callback-exception.html.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-[callback-exception.html]
-  expected:
-    if os == "win": TIMEOUT
diff --git a/third_party/blink/web_tests/external/wpt/requestidlecallback/deadline-after-expired-timer.html.ini b/third_party/blink/web_tests/external/wpt/requestidlecallback/deadline-after-expired-timer.html.ini
index fc00d2e2..a78693b0 100644
--- a/third_party/blink/web_tests/external/wpt/requestidlecallback/deadline-after-expired-timer.html.ini
+++ b/third_party/blink/web_tests/external/wpt/requestidlecallback/deadline-after-expired-timer.html.ini
@@ -1,3 +1,4 @@
 [deadline-after-expired-timer.html]
   [The deadline after an expired timer must not be negative]
-    expected: [PASS, FAIL]
+    expected:
+      if product == "chrome": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/requestidlecallback/deadline-max-rAF-dynamic.html.ini b/third_party/blink/web_tests/external/wpt/requestidlecallback/deadline-max-rAF-dynamic.html.ini
index a3741db8..3dbee2d 100644
--- a/third_party/blink/web_tests/external/wpt/requestidlecallback/deadline-max-rAF-dynamic.html.ini
+++ b/third_party/blink/web_tests/external/wpt/requestidlecallback/deadline-max-rAF-dynamic.html.ini
@@ -1,3 +1,3 @@
 [deadline-max-rAF-dynamic.html]
   expected:
-    if (flag_specific == "") and (product == "content_shell"): [OK, TIMEOUT]
+    if os == "win": TIMEOUT
diff --git a/third_party/blink/web_tests/external/wpt/requestidlecallback/deadline-max-rAF.html.ini b/third_party/blink/web_tests/external/wpt/requestidlecallback/deadline-max-rAF.html.ini
new file mode 100644
index 0000000..c4cc2f5
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/requestidlecallback/deadline-max-rAF.html.ini
@@ -0,0 +1,3 @@
+[deadline-max-rAF.html]
+  expected:
+    if (flag_specific == "") and (product == "content_shell"): [OK, TIMEOUT]
diff --git a/third_party/blink/web_tests/external/wpt/resource-timing/content-type-parsing.html b/third_party/blink/web_tests/external/wpt/resource-timing/content-type-parsing.html
new file mode 100644
index 0000000..c0081eb
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/resource-timing/content-type-parsing.html
@@ -0,0 +1,76 @@
+<!DOCTYPE html>
+<head>
+<meta charset="utf-8" />
+<title>This test validates the parsing of content-type of resources.</title>
+<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#sec-performanceresourcetiming"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script>
+
+// Utility function picked from https://github.com/web-platform-tests/wpt/blob/master/mimesniff/mime-types/charset-parameter.window.js
+function isByteCompatible(str) {
+  // see https://fetch.spec.whatwg.org/#concept-header-value-normalize
+  if(/^[\u0009\u0020\u000A\u000D]+|[\u0009\u0020\u000A\u000D]+$/.test(str)) {
+    return "header-value-incompatible";
+  }
+
+  for(let i = 0; i < str.length; i++) {
+    const charCode = str.charCodeAt(i);
+    // See https://fetch.spec.whatwg.org/#concept-header-value
+    if(charCode > 0xFF) {
+      return "incompatible";
+    } else if(charCode === 0x00 || charCode === 0x0A || charCode === 0x0D) {
+      return "header-value-incompatible";
+    }
+  }
+  return "compatible";
+}
+
+// Test for content-type parsing.
+const run_content_type_parsing_tests = (json_entries) => {
+  json_entries.forEach( (json_entry, i) => {
+    promise_test(async t => {
+        let url = "/fetch/content-type/resources/content-type.py?single_header&";
+        json_entry.contentType.forEach(val => {
+          url += "value=" + encodeURIComponent(val) + "&";
+        });
+        fetch(url);
+        const entry = await new Promise(resolve => new PerformanceObserver((entryList, observer) => {
+            observer.disconnect();
+            resolve(entryList.getEntries()[0]);
+        }).observe({entryTypes: ['resource']}));
+        assert_equals(entry.contentType, json_entry["mimeType"]);
+    }, "content-type " + i + " : " + json_entry.contentType);
+  });
+}
+
+// Test for mime-type parsing.
+const run_mime_type_parsing_tests = (json_entries) => {
+  json_entries.forEach( (val, i) => {
+    if(typeof val === "string" || val.navigable === undefined || isByteCompatible(val.input) !== "compatible") {
+      return;
+    }
+    const output = val.output === null ? "" : val.output
+    promise_test(async t => {
+        let url = `/fetch/content-type/resources/content-type.py?single_header&value=${val.input}`;
+        fetch(url);
+        const entry = await new Promise(resolve => new PerformanceObserver((entryList, observer) => {
+            observer.disconnect();
+            resolve(entryList.getEntries()[0]);
+        }).observe({entryTypes: ['resource']}));
+        assert_equals(entry.contentType, output);
+    }, "mime-type " + i + " : " + val.input);
+  });
+}
+
+Promise.all([
+    fetch("/fetch/content-type/resources/content-types.json"),
+    fetch("/mimesniff/mime-types/resources/mime-types.json")
+  ]).then(([res, res2]) => res.json().then(run_content_type_parsing_tests)
+    .then(() => res2.json().then(run_mime_type_parsing_tests)));
+
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/resource-timing/nested-context-navigations-iframe.html.ini b/third_party/blink/web_tests/external/wpt/resource-timing/nested-context-navigations-iframe.html.ini
deleted file mode 100644
index b48c0ae..0000000
--- a/third_party/blink/web_tests/external/wpt/resource-timing/nested-context-navigations-iframe.html.ini
+++ /dev/null
@@ -1,4 +0,0 @@
-[nested-context-navigations-iframe.html]
-  [Test that cross-site iframe navigations are not observable by the parent, even after history navigations by the parent]
-    expected:
-      if (flag_specific == "") and (os == "linux") and (product == "content_shell"): FAIL
diff --git a/third_party/blink/web_tests/external/wpt/resource-timing/no-entries-for-cross-origin-css-fetched-memory-cache.sub.html.ini b/third_party/blink/web_tests/external/wpt/resource-timing/no-entries-for-cross-origin-css-fetched-memory-cache.sub.html.ini
index 4e97650..a118839 100644
--- a/third_party/blink/web_tests/external/wpt/resource-timing/no-entries-for-cross-origin-css-fetched-memory-cache.sub.html.ini
+++ b/third_party/blink/web_tests/external/wpt/resource-timing/no-entries-for-cross-origin-css-fetched-memory-cache.sub.html.ini
@@ -1,6 +1,5 @@
 [no-entries-for-cross-origin-css-fetched-memory-cache.sub.html]
   [Make sure that resources fetched by cross origin CSS are not in the timeline.]
     expected:
-      if (flag_specific == "") and (os == "win"): [PASS, FAIL]
-      if flag_specific == "disable-layout-ng": PASS
+      if flag_specific == "disable-site-isolation-trials": PASS
       FAIL
diff --git a/third_party/blink/web_tests/external/wpt/service-workers/cache-storage/cache-add.https.any.js.ini b/third_party/blink/web_tests/external/wpt/service-workers/cache-storage/cache-add.https.any.js.ini
index bbd80aa2..38b9e327a 100644
--- a/third_party/blink/web_tests/external/wpt/service-workers/cache-storage/cache-add.https.any.js.ini
+++ b/third_party/blink/web_tests/external/wpt/service-workers/cache-storage/cache-add.https.any.js.ini
@@ -2,6 +2,10 @@
   [Cache.addAll called with the same Request object specified twice]
     expected: FAIL
 
+  [Cache.addAll should succeed when entries differ by vary header]
+    expected:
+      if product == "chrome": [PASS, FAIL]
+
 
 [cache-add.https.any.sharedworker.html]
   [Cache.addAll called with the same Request object specified twice]
@@ -18,13 +22,9 @@
 
   [Cache.addAll should succeed when entries differ by vary header]
     expected:
-      if product == "chrome": FAIL
+      if product == "chrome": [PASS, FAIL]
 
 
 [cache-add.https.any.html]
   [Cache.addAll called with the same Request object specified twice]
     expected: FAIL
-
-  [Cache.addAll should succeed when entries differ by vary header]
-    expected:
-      if product == "chrome": [PASS, FAIL]
diff --git a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/unregister-immediately-during-extendable-events.https.html.ini b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/unregister-immediately-during-extendable-events.https.html.ini
index a10e0a9..efbf9346 100644
--- a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/unregister-immediately-during-extendable-events.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/unregister-immediately-during-extendable-events.https.html.ini
@@ -1,9 +1,8 @@
 [unregister-immediately-during-extendable-events.https.html]
   expected:
-    if (flag_specific == "") and (os == "win"): OK
-    if flag_specific == "disable-layout-ng": OK
+    if os == "win": [OK, TIMEOUT]
     TIMEOUT
   [Clear-Site-Data must fail pending subresource fetch events.]
     expected:
-      if product == "chrome": TIMEOUT
+      if os == "linux": TIMEOUT
       FAIL
diff --git a/third_party/blink/web_tests/external/wpt/speculation-rules/prefetch/no-vary-search/prefetch-single.https.html b/third_party/blink/web_tests/external/wpt/speculation-rules/prefetch/no-vary-search/prefetch-single.https.html
new file mode 100644
index 0000000..543b46c5
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/speculation-rules/prefetch/no-vary-search/prefetch-single.https.html
@@ -0,0 +1,282 @@
+<!DOCTYPE html>
+<title>Prefetched response including No-Vary-Search headers is used during navigation</title>
+<meta charset="utf-8">
+
+<meta name="variant" content="?1-1">
+<meta name="variant" content="?2-2">
+<meta name="variant" content="?3-3">
+<meta name="variant" content="?4-4">
+<meta name="variant" content="?5-5">
+<meta name="variant" content="?6-6">
+<meta name="variant" content="?7-7">
+<meta name="variant" content="?8-8">
+<meta name="variant" content="?9-9">
+<meta name="variant" content="?10-10">
+<meta name="variant" content="?11-11">
+<meta name="variant" content="?12-12">
+<meta name="variant" content="?13-13">
+<meta name="variant" content="?14-14">
+<meta name="variant" content="?15-15">
+<meta name="variant" content="?16-16">
+<meta name="variant" content="?17-17">
+<meta name="variant" content="?18-18">
+<meta name="variant" content="?19-19">
+<meta name="variant" content="?20-20">
+<meta name="variant" content="?21-21">
+<meta name="variant" content="?22-22">
+<meta name="variant" content="?23-23">
+<meta name="variant" content="?24-24">
+<meta name="variant" content="?25-25">
+<meta name="variant" content="?26-last">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="/common/utils.js"></script>
+<script src="../resources/utils.sub.js"></script>
+<script src="/common/subset-tests.js"></script>
+
+<script>
+  function addNoVarySearchHeaderUsingPipe(url, value){
+    // Use server pipes https://web-platform-tests.org/writing-tests/server-pipes.html
+    // to populate No-Vary-Search response header.
+    // The "," and ")" characters need to be escaped by using backslash
+    // (see https://web-platform-tests.org/writing-tests/server-pipes.html).
+    // E.g. params=("a") becomes params=("a"\), params=("a"),key-order becomes
+    // params=("a"\)\,key-order etc.
+    url.searchParams.append("pipe",
+      `header(No-Vary-Search,${value.replaceAll(/[,)]/g, '\\$&')})`);
+  }
+
+  /*
+    remoteAgent: the RemoteContext instance used to communicate between the
+      test and the window where prefetch/navigation is happening
+    noVarySearchHeaderValue: the value of No-Vary-Search header to be populated
+      for the prefetched response
+    prefetchQuery: query params to be added to prefetchExecutor url and prefetched
+    navigateQuery: query params to be added to prefetchExecutor url and navigated to
+  */
+  async function prefetchAndNavigate(remoteAgent, noVarySearchHeaderValue, prefetchQuery, navigateQuery){
+    const nextUrl = remoteAgent.getExecutorURL();
+    const navigateToUrl = new URL(nextUrl);
+    // Add query params to the url to be prefetched.
+    const additionalPrefetchedUrlSearchParams = new URLSearchParams(prefetchQuery);
+    addNoVarySearchHeaderUsingPipe(nextUrl, noVarySearchHeaderValue);
+    additionalPrefetchedUrlSearchParams.forEach((value, key) => {
+      nextUrl.searchParams.append(key, value);
+    });
+
+    await remoteAgent.forceSinglePrefetch(nextUrl);
+
+    // Add new query params to navigateToUrl to match No-Vary-Search test case.
+    const additionalNavigateToUrlSearchParams = new URLSearchParams(navigateQuery);
+    addNoVarySearchHeaderUsingPipe(navigateToUrl, noVarySearchHeaderValue);
+    additionalNavigateToUrlSearchParams.forEach((value, key) => {
+      navigateToUrl.searchParams.append(key, value);
+    });
+    await remoteAgent.navigate(navigateToUrl);
+  }
+
+  function prefetch_no_vary_search_test(description, noVarySearch, prefetchQuery, navigateQuery, shouldUsePrefetch){
+    promise_test(async t => {
+      assert_implements(HTMLScriptElement.supports('speculationrules'), "Speculation Rules not supported");
+      const agent = await spawnWindow(t, {});
+      await prefetchAndNavigate(agent,
+        noVarySearch,
+        prefetchQuery,
+        navigateQuery);
+
+      if(shouldUsePrefetch){
+        assert_prefetched(await agent.getRequestHeaders(),
+          "Navigation didn't use the prefetched response!");
+      }
+      else{
+        assert_not_prefetched(await agent.getRequestHeaders(),
+          "Navigation used the prefetched response!");
+      }
+     }, description);
+  }
+
+  // Test inputs:
+  // - description: a description of the test.
+  // - no-vary-search: No-Vary-Search header value for the response.
+  // - prefetch-query: added to query part of prefetch-executor when prefetching
+  // - navigate-query: added to query part of prefetch-executor when navigating
+  // - shouldUsePrefetch: if the test case expects the prefetched entry to be
+  //   used or not.
+  [{description:"Use prefetched response as query parameter b has the same value.",
+    noVarySearch: 'params=("a")',
+    prefetchQuery: "a=2&b=3",
+    navigateQuery: "b=3",
+    shouldUsePrefetch: true},
+
+   {description:"Don't use prefetched response as query parameter b has different value.",
+    noVarySearch: 'params("a")',
+    prefetchQuery: "a=2&b=3",
+    navigateQuery: "b=2",
+    shouldUsePrefetch: false},
+
+   {description:"Use prefetched response as the URLs do not vary by a and b.",
+    noVarySearch: 'params=("a" "b")',
+    prefetchQuery: "a=2&b=3",
+    navigateQuery: "b=2",
+    shouldUsePrefetch: true},
+
+   {description:"Use prefetched response as the URLs do not vary on any query parameters.",
+    noVarySearch: "params",
+    prefetchQuery: "a=2&b=3",
+    navigateQuery: "b=4&c=5",
+    shouldUsePrefetch: true},
+
+   {description:"Use prefetched response as the URLs do not vary on any query parameters.",
+    noVarySearch: "params",
+    prefetchQuery: "",
+    navigateQuery: "b=4&c=5",
+    shouldUsePrefetch: true},
+
+   {description:"Don't use prefetched response as the URLs have different value for c.",
+    noVarySearch: "key-order",
+    prefetchQuery: "c=4&b=3&a=2",
+    navigateQuery: "a=2&c=5&b=3",
+    shouldUsePrefetch: false},
+
+   {description:"Don't use prefetched response as the URLs have the values in different order for a.",
+    noVarySearch: "key-order",
+    prefetchQuery: "b=5&a=3&a=4&d=6&c=5&b=3",
+    navigateQuery: "d=6&a=4&b=5&b=3&c=5&a=3",
+    shouldUsePrefetch: false},
+
+   {description:"Use prefetched response as the URLs have the same values for a.",
+    noVarySearch: "key-order",
+    prefetchQuery: "b=5&a=3&a=4&d=6&c=5&b=3",
+    navigateQuery: "d=6&a=3&b=5&b=3&c=5&a=4",
+    shouldUsePrefetch: true},
+
+   {description:"Use prefetched response as the URLs have the same values for a.",
+    noVarySearch: "key-order=?1",
+    prefetchQuery: "b=5&a=3&a=4&d=6&c=5&b=3",
+    navigateQuery: "d=6&a=3&b=5&b=3&c=5&a=4",
+    shouldUsePrefetch: true},
+
+   {description:"Don't use prefetched response as key-order is set to false and the URLs are not identical.",
+    noVarySearch: "key-order=?0",
+    prefetchQuery: "b=5&a=3&a=4&d=6&c=5&b=3",
+    navigateQuery: "d=6&a=3&b=5&b=3&c=5&a=4",
+    shouldUsePrefetch: false},
+
+   {description:"Use prefetched response as query parameter c can be ignored.",
+    noVarySearch: 'params=("c")',
+    prefetchQuery: "a=2&b=2&c=5",
+    navigateQuery: "a=2&c=3&b=2",
+    shouldUsePrefetch: true},
+
+   {description:"Use prefetched response as query parameter a can be ignored.",
+    noVarySearch: 'params=("a")',
+    prefetchQuery: "a=2",
+    navigateQuery: "",
+    shouldUsePrefetch: true},
+
+   {description:"Use prefetched response as query parameter a can be ignored.",
+    noVarySearch: 'params=("a")',
+    prefetchQuery: "",
+    navigateQuery: "a=2",
+    shouldUsePrefetch: true},
+
+   {description:"Use prefetched response as all query parameters except c can be ignored.",
+    noVarySearch: 'params, except=("c")',
+    prefetchQuery: "b=5&a=3&d=6&c=3",
+    navigateQuery: "a=1&b=2&c=3",
+    shouldUsePrefetch: true},
+
+   {description:"Use prefetched response as all query parameters except c can be ignored." +
+                " Only the last except matters.",
+    noVarySearch: 'params, except=("b"), except=("c")',
+    prefetchQuery: "b=5&a=3&d=6&c=3",
+    navigateQuery: "a=1&b=2&c=3",
+    shouldUsePrefetch: true},
+
+   {description:"Don't use prefetched response as even though all query parameters" +
+                " except c can be ignored, c has different value.",
+    noVarySearch: 'params, except=("c")',
+    prefetchQuery: "b=5&a=3&d=6&c=3",
+    navigateQuery: "a=1&b=2&c=5",
+    shouldUsePrefetch: false},
+
+   {description:"Use prefetched response as even though all query parameters" +
+                " except c and d can be ignored, c value matches and d value matches.",
+    noVarySearch: 'params, except=("c" "d")',
+    prefetchQuery: "b=5&a=3&d=6&c=5",
+    navigateQuery: "d=6&a=1&b=2&c=5",
+    shouldUsePrefetch: true},
+
+   {description:"Use prefetched response as even though all query parameters except" +
+                " c and d can be ignored, c value matches and d value matches." +
+                " Some query parameters to be ignored appear multiple times in the query.",
+    noVarySearch: 'params, except=("c" "d")',
+    prefetchQuery: "b=5&a=3&a=4&d=6&c=5",
+    navigateQuery: "d=6&a=1&a=2&b=2&b=3&c=5",
+    shouldUsePrefetch: true},
+
+   {description:"Use prefetched response as all query parameters except c can be ignored." +
+                " Allow extension via parameters.",
+    noVarySearch: 'params, except=("c";unknown)',
+    prefetchQuery: "b=5&a=3&d=6&c=3",
+    navigateQuery: "a=1&b=2&c=3",
+    shouldUsePrefetch: true},
+
+   {description:"Use prefetched response as query parameter c can be ignored." +
+                " Allow extension via parameters.",
+    noVarySearch: 'params=("c";unknown)',
+    prefetchQuery: "a=2&b=2&c=5",
+    navigateQuery: "a=2&c=3&b=2",
+    shouldUsePrefetch: true},
+
+   {description:"Use prefetched response as the URLs have the values in different order for a." +
+                " Allow extension via parameters.",
+    noVarySearch: "key-order;unknown",
+    prefetchQuery: "b=5&a=3&a=4&d=6&c=5&b=3",
+    navigateQuery: "d=6&a=3&b=5&b=3&c=5&a=4",
+    shouldUsePrefetch: true},
+
+   {description:"Use prefetched response as the URLs do not vary on any query parameters." +
+                " Allow extension via parameters.",
+    noVarySearch: "params;unknown",
+    prefetchQuery: "",
+    navigateQuery: "b=4&c=5",
+    shouldUsePrefetch: true},
+
+   {description:"Use prefetched response as all query parameters except c can be ignored." +
+                " Allow extension via parameters.",
+    noVarySearch: 'params;unknown, except=("c");unknown',
+    prefetchQuery: "b=5&a=3&d=6&c=3",
+    navigateQuery: "a=1&b=2&c=3",
+    shouldUsePrefetch: true},
+
+   {description:"Don't use the prefetched URL. Empty No-Vary-Search means default URL variance." +
+                " The prefetched and the navigated URLs have to be the same.",
+    noVarySearch: "",
+    prefetchQuery: "b=5&a=3&d=6&c=3",
+    navigateQuery: "a=1&b=2&c=3",
+    shouldUsePrefetch: false},
+
+   {description:"Use the prefetched URL. Empty No-Vary-Search means default URL variance." +
+                " The prefetched and the navigated URLs have to be the same.",
+    noVarySearch: "",
+    prefetchQuery: "b=5&a=3&d=6&c=3",
+    navigateQuery: "b=5&a=3&d=6&c=3",
+    shouldUsePrefetch: true},
+
+   {description:"Use the prefetched URL. Empty No-Vary-Search means default URL variance." +
+                " The prefetched and the navigated URLs have to be the same.",
+    noVarySearch: "",
+    prefetchQuery: "",
+    navigateQuery: "",
+    shouldUsePrefetch: true},
+
+  ].forEach(({description, noVarySearch, prefetchQuery, navigateQuery, shouldUsePrefetch}) => {
+    subsetTest(prefetch_no_vary_search_test,
+      description, noVarySearch, prefetchQuery, navigateQuery,
+      shouldUsePrefetch);
+  });
+
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/speculation-rules/prefetch/no-vary-search/prefetch-single.https.html.ini b/third_party/blink/web_tests/external/wpt/speculation-rules/prefetch/no-vary-search/prefetch-single.https.html.ini
new file mode 100644
index 0000000..68ee4fc
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/speculation-rules/prefetch/no-vary-search/prefetch-single.https.html.ini
@@ -0,0 +1,110 @@
+[prefetch-single.https.html?9-9]
+  [Use prefetched response as the URLs have the same values for a.]
+    expected: FAIL
+
+
+[prefetch-single.https.html?7-7]
+
+[prefetch-single.https.html?6-6]
+
+[prefetch-single.https.html?4-4]
+  [Use prefetched response as the URLs do not vary on any query parameters.]
+    expected: FAIL
+
+
+[prefetch-single.https.html?20-20]
+  [Use prefetched response as query parameter c can be ignored. Allow extension via parameters.]
+    expected: FAIL
+
+
+[prefetch-single.https.html?15-15]
+  [Use prefetched response as all query parameters except c can be ignored. Only the last except matters.]
+    expected: FAIL
+
+
+[prefetch-single.https.html?8-8]
+  [Use prefetched response as the URLs have the same values for a.]
+    expected: FAIL
+
+
+[prefetch-single.https.html?14-14]
+  [Use prefetched response as all query parameters except c can be ignored.]
+    expected: FAIL
+
+
+[prefetch-single.https.html?1-1]
+  [Use prefetched response as query parameter b has the same value.]
+    expected: FAIL
+
+
+[prefetch-single.https.html?23-23]
+  [Use prefetched response as all query parameters except c can be ignored. Allow extension via parameters.]
+    expected: FAIL
+
+
+[prefetch-single.https.html?24-24]
+
+[prefetch-single.https.html?13-13]
+  [Use prefetched response as query parameter a can be ignored.]
+    expected: FAIL
+
+
+[prefetch-single.https.html?19-19]
+  [Use prefetched response as all query parameters except c can be ignored. Allow extension via parameters.]
+    expected: FAIL
+
+
+[prefetch-single.https.html?10-10]
+
+[prefetch-single.https.html?5-5]
+  [Use prefetched response as the URLs do not vary on any query parameters.]
+    expected: FAIL
+
+
+[prefetch-single.https.html?26-last]
+  [Use the prefetched URL. Empty No-Vary-Search means default URL variance. The prefetched and the navigated URLs have to be the same.]
+    expected: FAIL
+
+
+[prefetch-single.https.html?18-18]
+  [Use prefetched response as even though all query parameters except c and d can be ignored, c value matches and d value matches. Some query parameters to be ignored appear multiple times in the query.]
+    expected: FAIL
+
+
+[prefetch-single.https.html?25-25]
+  [Use the prefetched URL. Empty No-Vary-Search means default URL variance. The prefetched and the navigated URLs have to be the same.]
+    expected: FAIL
+
+
+[prefetch-single.https.html?3-3]
+  [Use prefetched response as the URLs do not vary by a and b.]
+    expected: FAIL
+
+
+[prefetch-single.https.html?2-2]
+
+[prefetch-single.https.html?12-12]
+  [Use prefetched response as query parameter a can be ignored.]
+    expected: FAIL
+
+
+[prefetch-single.https.html?16-16]
+
+[prefetch-single.https.html?17-17]
+  [Use prefetched response as even though all query parameters except c and d can be ignored, c value matches and d value matches.]
+    expected: FAIL
+
+
+[prefetch-single.https.html?21-21]
+  [Use prefetched response as the URLs have the values in different order for a. Allow extension via parameters.]
+    expected: FAIL
+
+
+[prefetch-single.https.html?22-22]
+  [Use prefetched response as the URLs do not vary on any query parameters. Allow extension via parameters.]
+    expected: FAIL
+
+
+[prefetch-single.https.html?11-11]
+  [Use prefetched response as query parameter c can be ignored.]
+    expected: FAIL
diff --git a/third_party/blink/web_tests/external/wpt/speculation-rules/prefetch/referrer-policy.https.html.ini b/third_party/blink/web_tests/external/wpt/speculation-rules/prefetch/referrer-policy.https.html.ini
index 05ab2a4..2e3eaa4 100644
--- a/third_party/blink/web_tests/external/wpt/speculation-rules/prefetch/referrer-policy.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/speculation-rules/prefetch/referrer-policy.https.html.ini
@@ -14,8 +14,6 @@
 
 
 [referrer-policy.https.html?3-3]
-  expected:
-    if product == "chrome": ERROR
   [with "no-referrer" referrer policy]
     expected: FAIL
 
diff --git a/third_party/blink/web_tests/external/wpt/speculation-rules/prefetch/resources/utils.sub.js b/third_party/blink/web_tests/external/wpt/speculation-rules/prefetch/resources/utils.sub.js
index a306b8ce..ea70939 100644
--- a/third_party/blink/web_tests/external/wpt/speculation-rules/prefetch/resources/utils.sub.js
+++ b/third_party/blink/web_tests/external/wpt/speculation-rules/prefetch/resources/utils.sub.js
@@ -94,6 +94,12 @@
       document.head.append(meta);
     }, [referrerPolicy]);
   }
+
+  async getDeliveryType(){
+    return this.execute_script(() => {
+      return performance.getEntriesByType("navigation")[0].deliveryType;
+    });
+  }
 }
 
 // Produces a URL with a UUID which will record when it's prefetched.
diff --git a/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/csp-script-src-inline-speculation-rules.html b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/csp-script-src-inline-speculation-rules.html
new file mode 100644
index 0000000..923598b
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/csp-script-src-inline-speculation-rules.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta name="timeout" content="long">
+<script src="/common/utils.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+  <script>
+    setup(() => assertSpeculationRulesIsSupported());
+
+    promise_test(async t => {
+      // The key used for storing a test result in the server.
+      const key = token();
+
+      // Open the test runner in a popup - it will prerender itself, record the
+      // test results, and send them back to this harness.
+      const url =
+        `resources/csp-script-src-inline-speculation-rules.html?key=${key}`;
+      window.open(url, '_blank', 'noopener');
+
+      // Wait until the test sends us the results.
+      const result = await nextValueFromServer(key);
+
+      assert_equals(result, "true", "initial document.prerendering");
+    }, 'Test if CSP script-src inline-speculation-rules permits inline speculationrules.');
+  </script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/csp-script-src-self.html b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/csp-script-src-self.html
new file mode 100644
index 0000000..f0f9784
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/csp-script-src-self.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta name="timeout" content="long">
+<script src="/common/utils.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+  <script>
+    setup(() => assertSpeculationRulesIsSupported());
+
+    promise_test(async t => {
+      // The key used for storing a test result in the server.
+      const key = token();
+
+      // Open the test runner in a popup - it will prerender itself, record the
+      // test results, and send them back to this harness.
+      const url =
+        `resources/csp-script-src-self.html?key=${key}`;
+      window.open(url, '_blank', 'noopener');
+
+      // Wait until the test sends us the results.
+      const result = await nextValueFromServer(key);
+
+      assert_equals(result, "blocked by script-src-elem", "csp block");
+    }, 'Test if CSP script-src self does not permit inline speculationrules.');
+  </script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/csp-script-src-unsafe-inline.html b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/csp-script-src-unsafe-inline.html
new file mode 100644
index 0000000..f6925f59
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/csp-script-src-unsafe-inline.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta name="timeout" content="long">
+<script src="/common/utils.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+  <script>
+    setup(() => assertSpeculationRulesIsSupported());
+
+    promise_test(async t => {
+      // The key used for storing a test result in the server.
+      const key = token();
+
+      // Open the test runner in a popup - it will prerender itself, record the
+      // test results, and send them back to this harness.
+      const url =
+        `resources/csp-script-src-unsafe-inline.html?key=${key}`;
+      window.open(url, '_blank', 'noopener');
+
+      // Wait until the test sends us the results.
+      const result = await nextValueFromServer(key);
+
+      assert_equals(result, "true", "initial document.prerendering");
+    }, 'Test if CSP script-src unsafe-inline permits inline speculationrules.');
+  </script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/indexeddb.html.ini b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/indexeddb.html.ini
index 3dd4ff1..d339397 100644
--- a/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/indexeddb.html.ini
+++ b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/indexeddb.html.ini
@@ -1,7 +1,7 @@
 [indexeddb.html]
   expected:
-    if (product == "content_shell") and (os == "win"): [OK, TIMEOUT]
-    if product == "chrome": TIMEOUT
+    if (os == "linux") and (product == "chrome"): TIMEOUT
+    if os == "win": TIMEOUT
   [prerendering page should be able to access Indexed DataBase]
     expected:
       if product == "chrome": TIMEOUT
diff --git a/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/media-autoplay.html.ini b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/media-autoplay.html.ini
index 12abdfd..dcad099 100644
--- a/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/media-autoplay.html.ini
+++ b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/media-autoplay.html.ini
@@ -3,8 +3,8 @@
   disabled:
     if os == "mac": was skipped in 'TestExpectations'
   expected:
-    if (product == "content_shell") and (os == "win"): [OK, TIMEOUT]
-    if product == "chrome": TIMEOUT
+    if (os == "linux") and (product == "chrome"): TIMEOUT
+    if os == "win": TIMEOUT
   [media autoplay should defer playaback]
     expected:
       if product == "chrome": TIMEOUT
diff --git a/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/resources/csp-script-src-inline-speculation-rules.html b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/resources/csp-script-src-inline-speculation-rules.html
new file mode 100644
index 0000000..febfbd01
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/resources/csp-script-src-inline-speculation-rules.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+
+<head>
+  <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'inline-speculation-rules'">
+</head>
+<script src="/common/utils.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="utils.js"></script>
+<script src="csp-script-src.js"></script>
+<script>
+  const params = new URLSearchParams(location.search);
+  writeValueToServer(params.get('key'), "csp is ignored unexpectedly");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/resources/csp-script-src-self.html b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/resources/csp-script-src-self.html
new file mode 100644
index 0000000..8dc3820
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/resources/csp-script-src-self.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+
+<head>
+  <!-- disallow inline script -->
+  <meta http-equiv="Content-Security-Policy" content="script-src 'self'">
+</head>
+<script src="/common/utils.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="utils.js"></script>
+<script src="csp-script-src.js"></script>
+<script>
+  const params = new URLSearchParams(location.search);
+  writeValueToServer(params.get('key'), "csp is ignored unexpectedly");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/resources/csp-script-src-unsafe-inline.html b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/resources/csp-script-src-unsafe-inline.html
new file mode 100644
index 0000000..d2f010d
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/resources/csp-script-src-unsafe-inline.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+
+<head>
+  <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline'">
+</head>
+<script src="/common/utils.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="utils.js"></script>
+<script src="csp-script-src.js"></script>
diff --git a/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/resources/csp-script-src.js b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/resources/csp-script-src.js
new file mode 100644
index 0000000..52419f9b4
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/resources/csp-script-src.js
@@ -0,0 +1,57 @@
+const params = new URLSearchParams(location.search);
+
+// Take a key used for storing a test result in the server.
+const key = params.get('key');
+
+// Speculation rules injection is not blocked in the csp-script-src 'self' test.
+const block = location.pathname.endsWith('csp-script-src-self.html');
+
+// The main test page (csp-script-src-*.html) in the parent directory) will load
+// this page only with the "key" parameter. This page will then try prerendering
+// itself with the "run-test" parameter. When "run-test" is in the URL we'll
+// actually start the test process and record the results to send back to the
+// main test page. We do this because the main test page cannot navigate itself
+// but it also cannot open a popup to a prerendered browsing context so the
+// prerender triggering and activation must both happen in this popup.
+const run_test = params.has('run-test');
+if (!run_test) {
+  // Generate a new stash key so we can communicate with the prerendered page
+  // about when to close the popup.
+  const done_key = token();
+  const url = new URL(document.URL);
+  url.searchParams.append('run-test', '');
+  url.searchParams.append('done-key', done_key);
+
+  if (block) {
+    // Observe `securitypolicyviolation` event that will be triggered by
+    // startPrerendering().
+    document.addEventListener('securitypolicyviolation', e => {
+      if (e.effectiveDirective != 'script-src' &&
+          e.effectiveDirective != 'script-src-elem') {
+        const message = 'unexpected effective directive: ' + e.effectiveDirective;
+        writeValueToServer(key, message).then(() => { window.close(); });
+      } else {
+        const message = 'blocked by ' + e.effectiveDirective;
+        writeValueToServer(key, message).then(() => { window.close(); });
+      }
+    });
+  }
+
+  startPrerendering(url.toString());
+
+  // Wait until the prerendered page signals us it's ready to close.
+  nextValueFromServer(done_key).then(() => {
+    window.close();
+  });
+} else {
+  if (block) {
+    writeValueToServer(key, 'unexpected prerendering');
+  } else {
+    // Tell the harness the initial document.prerendering value.
+    writeValueToServer(key, document.prerendering);
+
+    // Tell the prerendering initiating page test being finished.
+    const done_key = params.get('done-key');
+    writeValueToServer(done_key, "done");
+  }
+}
diff --git a/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/response-code-non-successful.html.ini b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/response-code-non-successful.html.ini
index df2c0c92..4b1a606 100644
--- a/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/response-code-non-successful.html.ini
+++ b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/response-code-non-successful.html.ini
@@ -9,8 +9,8 @@
 
 [response-code-non-successful.html?code=500]
   expected:
-    if product == "chrome": ERROR
+    if product == "chrome": [OK, ERROR]
 
 [response-code-non-successful.html?code=205]
   expected:
-    if product == "chrome": [ERROR, OK]
+    if product == "chrome": ERROR
diff --git a/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/restriction-audio-setSinkId.https.tentative.html.ini b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/restriction-audio-setSinkId.https.tentative.html.ini
index e5404402..ef53be2d 100644
--- a/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/restriction-audio-setSinkId.https.tentative.html.ini
+++ b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/restriction-audio-setSinkId.https.tentative.html.ini
@@ -1,7 +1,7 @@
 [restriction-audio-setSinkId.https.tentative.html]
   expected:
-    if (product == "content_shell") and (os == "win"): [OK, ERROR]
-    if product == "chrome": TIMEOUT
+    if (os == "linux") and (product == "chrome"): TIMEOUT
+    if os == "win": ERROR
   [the access to the setSinkId of Audio API should be deferred until the\n    prerendered page is activated]
     expected:
       if product == "chrome": TIMEOUT
diff --git a/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/restriction-bluetooth.tentative.https.html.ini b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/restriction-bluetooth.tentative.https.html.ini
index 8dacf33..430d214a 100644
--- a/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/restriction-bluetooth.tentative.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/restriction-bluetooth.tentative.https.html.ini
@@ -1,7 +1,6 @@
 [restriction-bluetooth.tentative.https.html]
   expected:
-    if (os == "linux") and (product == "chrome"): TIMEOUT
-    if os == "win": ERROR
+    if product == "chrome": TIMEOUT
   [the access to the Bluetooth API should be deferred until the\n   prerendered page is activated]
     expected:
       if product == "chrome": TIMEOUT
diff --git a/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/restriction-dedicated-worker.https.html.ini b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/restriction-dedicated-worker.https.html.ini
index 4aeadc5..9362fa3 100644
--- a/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/restriction-dedicated-worker.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/restriction-dedicated-worker.https.html.ini
@@ -1,7 +1,6 @@
 [restriction-dedicated-worker.https.html]
   expected:
-    if (os == "linux") and (product == "chrome"): TIMEOUT
-    if os == "win": ERROR
+    if product == "chrome": TIMEOUT
   [The access to the Dedicated Worker API should be deferred until the\n    prerendered page is activated]
     expected:
       if product == "chrome": TIMEOUT
diff --git a/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/restriction-encrypted-media.https.html.ini b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/restriction-encrypted-media.https.html.ini
index a72b225..e09bff4 100644
--- a/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/restriction-encrypted-media.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/restriction-encrypted-media.https.html.ini
@@ -3,8 +3,8 @@
   disabled:
     if flag_specific == "force-renderer-accessibility": was skipped in 'FlagExpectations/force-renderer-accessibility'
   expected:
-    if (product == "content_shell") and (os == "win"): [OK, ERROR]
-    if product == "chrome": TIMEOUT
+    if (os == "linux") and (product == "chrome"): TIMEOUT
+    if os == "win": ERROR
   [the access to the Encrypted Media API should be deferred until the\n    prerendered page is activated]
     expected:
       if product == "chrome": TIMEOUT
diff --git a/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/restriction-media-auto-play-attribute.html.ini b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/restriction-media-auto-play-attribute.html.ini
index 8abf580..66ea6cb7 100644
--- a/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/restriction-media-auto-play-attribute.html.ini
+++ b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/restriction-media-auto-play-attribute.html.ini
@@ -3,8 +3,8 @@
   disabled:
     if flag_specific == "force-renderer-accessibility": was skipped in 'FlagExpectations/force-renderer-accessibility'
   expected:
-    if (product == "content_shell") and (os == "win"): [OK, ERROR]
-    if product == "chrome": TIMEOUT
+    if (os == "linux") and (product == "chrome"): TIMEOUT
+    if os == "win": ERROR
   [autoplay of the audio media should be deferred until the prerendered page is activated]
     expected:
       if product == "chrome": TIMEOUT
diff --git a/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/restriction-prompt-by-before-unload.html.ini b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/restriction-prompt-by-before-unload.html.ini
index 465f205d3..cf5f8a1 100644
--- a/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/restriction-prompt-by-before-unload.html.ini
+++ b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/restriction-prompt-by-before-unload.html.ini
@@ -3,8 +3,8 @@
   disabled:
     if flag_specific == "force-renderer-accessibility": was skipped in 'FlagExpectations/force-renderer-accessibility'
   expected:
-    if (product == "content_shell") and (os == "win"): [OK, TIMEOUT]
-    if product == "chrome": TIMEOUT
+    if (os == "linux") and (product == "chrome"): TIMEOUT
+    if os == "win": TIMEOUT
   [Prerendering cannot invoke the prompt by the beforeunload event.]
     expected:
       if product == "chrome": TIMEOUT
diff --git a/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/restriction-service-worker-postmessage.https.html.ini b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/restriction-service-worker-postmessage.https.html.ini
index d9875a3..3cb7332 100644
--- a/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/restriction-service-worker-postmessage.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/restriction-service-worker-postmessage.https.html.ini
@@ -1,5 +1,6 @@
 [restriction-service-worker-postmessage.https.html]
   expected:
+    if (product == "content_shell") and (os == "win"): [OK, TIMEOUT]
     if product == "chrome": TIMEOUT
   [ServiceWorker#postMessage() from a prerendered page should be deferred until page activation.]
     expected:
diff --git a/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/restriction-speech-synthesis.html.ini b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/restriction-speech-synthesis.html.ini
index ad1ad4c..6984300 100644
--- a/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/restriction-speech-synthesis.html.ini
+++ b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/restriction-speech-synthesis.html.ini
@@ -1,5 +1,6 @@
 [restriction-speech-synthesis.html]
   expected:
+    if (product == "content_shell") and (os == "win"): [OK, ERROR]
     if product == "chrome": TIMEOUT
   [speechSynthesis.speak(utterance) should be deferred until the prerendered page is activated]
     expected:
diff --git a/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/restriction-web-locks.https.html.ini b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/restriction-web-locks.https.html.ini
index fee9484..d55497ae 100644
--- a/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/restriction-web-locks.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/restriction-web-locks.https.html.ini
@@ -1,7 +1,6 @@
 [restriction-web-locks.https.html]
   expected:
-    if (os == "linux") and (product == "chrome"): TIMEOUT
-    if os == "win": ERROR
+    if product == "chrome": TIMEOUT
   [navigator.locks.request should be deferred until the prerendered page is activated]
     expected:
       if product == "chrome": TIMEOUT
diff --git a/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/restriction-window-move.html.ini b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/restriction-window-move.html.ini
index 1f07b4c..241db3c 100644
--- a/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/restriction-window-move.html.ini
+++ b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/restriction-window-move.html.ini
@@ -1,7 +1,7 @@
 [restriction-window-move.html]
   expected:
-    if (product == "content_shell") and (os == "win"): [OK, ERROR]
-    if product == "chrome": TIMEOUT
+    if (os == "linux") and (product == "chrome"): TIMEOUT
+    if os == "win": ERROR
   [a prerendering page cannot move its window by executing moveTo.]
     expected:
       if product == "chrome": TIMEOUT
diff --git a/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/session-history-subframe-navigation.https.html.ini b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/session-history-subframe-navigation.https.html.ini
index d2873788..8d58141 100644
--- a/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/session-history-subframe-navigation.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/session-history-subframe-navigation.https.html.ini
@@ -1,7 +1,6 @@
 [session-history-subframe-navigation.https.html]
   expected:
-    if (product == "content_shell") and (os == "win"): [TIMEOUT, OK]
-    if product == "chrome": TIMEOUT
+    if (os == "linux") and (product == "chrome"): TIMEOUT
   [Subframe navigation in prerender replaces the session entry]
     expected:
       if product == "chrome": TIMEOUT
diff --git a/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/windowclient-navigate-to-cross-origin-url-on-iframe.https.html.ini b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/windowclient-navigate-to-cross-origin-url-on-iframe.https.html.ini
index f3d325bc..4f32bf9 100644
--- a/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/windowclient-navigate-to-cross-origin-url-on-iframe.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/speculation-rules/prerender/windowclient-navigate-to-cross-origin-url-on-iframe.https.html.ini
@@ -3,8 +3,8 @@
   disabled:
     if flag_specific == "force-renderer-accessibility": was skipped in 'FlagExpectations/force-renderer-accessibility'
   expected:
-    if (product == "content_shell") and (os == "win"): [OK, ERROR]
-    if product == "chrome": TIMEOUT
+    if (os == "linux") and (product == "chrome"): TIMEOUT
+    if os == "win": ERROR
   [WindowClient.navigate() to a cross-origin URL on a prerendered iframe should be deferred]
     expected:
       if product == "chrome": TIMEOUT
diff --git a/third_party/blink/web_tests/external/wpt/storage/estimate-indexeddb.https.any.js.ini b/third_party/blink/web_tests/external/wpt/storage/estimate-indexeddb.https.any.js.ini
new file mode 100644
index 0000000..54ad9ea
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/storage/estimate-indexeddb.https.any.js.ini
@@ -0,0 +1,7 @@
+[estimate-indexeddb.https.any.worker.html]
+  [estimate() shows usage increase after large value is stored]
+    expected:
+      if product == "chrome": [PASS, FAIL]
+
+
+[estimate-indexeddb.https.any.html]
diff --git a/third_party/blink/web_tests/external/wpt/storage/partitioned-estimate-usage-details-caches.tentative.https.sub.html.ini b/third_party/blink/web_tests/external/wpt/storage/partitioned-estimate-usage-details-caches.tentative.https.sub.html.ini
index 6c84ead..9e9b6ccf 100644
--- a/third_party/blink/web_tests/external/wpt/storage/partitioned-estimate-usage-details-caches.tentative.https.sub.html.ini
+++ b/third_party/blink/web_tests/external/wpt/storage/partitioned-estimate-usage-details-caches.tentative.https.sub.html.ini
@@ -1,5 +1,3 @@
 [partitioned-estimate-usage-details-caches.tentative.https.sub.html]
-  expected:
-    if (flag_specific == "") and (os == "linux") and (product == "content_shell"): TIMEOUT
   [Partitioned estimate() usage details for caches test.]
     expected: FAIL
diff --git a/third_party/blink/web_tests/external/wpt/svg/animations/animateMotion-keyPoints-001.html b/third_party/blink/web_tests/external/wpt/svg/animations/animateMotion-keyPoints-001.html
new file mode 100644
index 0000000..1397bd60
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/svg/animations/animateMotion-keyPoints-001.html
@@ -0,0 +1,34 @@
+<!doctype html>
+<title>'calcMode' paced and 'keyPoints' on &lt;animateMotion> with 'path'</title>
+<link rel="help" href="https://svgwg.org/specs/animations/#AnimateMotionElement">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/SVGAnimationTestCase-testharness.js"></script>
+<svg>
+  <rect width="100" height="100" fill="red"/>
+  <rect id="target" transform="translate(100, -50)" width="100" height="100" fill="green">
+    <animateMotion dur="5s" keyPoints="1;0" keyTimes="0;1" path="M-200,50h250"/>
+  </rect>
+</svg>
+<script>
+const rootSVGElement = document.querySelector('svg');
+
+function sample(expectedX) {
+  const target = document.getElementById('target');
+  const targetCTM = target.getCTM();
+  assert_approx_equals(targetCTM.e, expectedX, 1e-3, 'x position');
+  assert_equals(targetCTM.f, 0, 'y position');
+  const restOfCTM = ['a', 'b', 'c', 'd'].map(p => targetCTM[p]);
+  assert_array_equals(restOfCTM, [1, 0, 0, 1], 'rest of CTM');
+}
+
+smil_async_test(t => {
+  runAnimationTest(t, [
+    // [animationId, time, sampleCallback]
+    ['anim', 1, sample.bind(this, 100)],
+    ['anim', 2, sample.bind(this, 50)],
+    ['anim', 3, sample.bind(this, 0)],
+  ]);
+});
+window.animationStartsImmediately = true;
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/svg/embedded/image-embedding-svg-nested-svg-in-foreignobject.html b/third_party/blink/web_tests/external/wpt/svg/embedded/image-embedding-svg-nested-svg-in-foreignobject.html
new file mode 100644
index 0000000..8803052
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/svg/embedded/image-embedding-svg-nested-svg-in-foreignobject.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<title>Only synthesize a 'viewBox' for the outermost SVG root in image contexts</title>
+<link rel="help" href="https://crbug.com/1313530">
+<link rel="match" href="../struct/reftests/reference/green-100x100.html">
+<img width="100" height="100" src="data:image/svg+xml,
+  <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 200' width='100' id='svg'>
+    <rect width='200' height='200' fill='red'/>
+    <foreignObject width='200' height='200'>
+      <div xmlns='http://www.w3.org/1999/xhtml' style='height: 100px; width: 100px'>
+        <svg xmlns='http://www.w3.org/2000/svg' style='width: 100%; height: 100%; overflow: visible' width='200' height='200'>
+          <rect width='200' height='200' fill='green'/>
+        </svg>
+      </div>
+    </foreignObject>
+  </svg>
+">
diff --git a/third_party/blink/web_tests/external/wpt/url/a-element-origin-xhtml.xhtml.ini b/third_party/blink/web_tests/external/wpt/url/a-element-origin-xhtml.xhtml.ini
index f6ff3b77..587670e6d7 100644
--- a/third_party/blink/web_tests/external/wpt/url/a-element-origin-xhtml.xhtml.ini
+++ b/third_party/blink/web_tests/external/wpt/url/a-element-origin-xhtml.xhtml.ini
@@ -2,9 +2,6 @@
   [Parsing origin: <http://example.com/foo%00%51> against <about:blank>]
     expected: FAIL
 
-  [Parsing origin: <https://faß.ExAmPlE/> against <about:blank>]
-    expected: FAIL
-
   [Parsing origin: <http://example.com/U+d800𐟾U+dfff﷐﷏﷯ﷰ￾￿?U+d800𐟾U+dfff﷐﷏﷯ﷰ￾￿> against <about:blank>]
     expected: FAIL
 
diff --git a/third_party/blink/web_tests/external/wpt/url/a-element-origin.html.ini b/third_party/blink/web_tests/external/wpt/url/a-element-origin.html.ini
index 449e021..89d5b4a 100644
--- a/third_party/blink/web_tests/external/wpt/url/a-element-origin.html.ini
+++ b/third_party/blink/web_tests/external/wpt/url/a-element-origin.html.ini
@@ -2,9 +2,6 @@
   [Parsing origin: <http://example.com/foo%00%51> against <about:blank>]
     expected: FAIL
 
-  [Parsing origin: <https://faß.ExAmPlE/> against <about:blank>]
-    expected: FAIL
-
   [Parsing origin: <http://example.com/U+d800𐟾U+dfff﷐﷏﷯ﷰ￾￿?U+d800𐟾U+dfff﷐﷏﷯ﷰ￾￿> against <about:blank>]
     expected: FAIL
 
diff --git a/third_party/blink/web_tests/external/wpt/url/a-element-xhtml.xhtml.ini b/third_party/blink/web_tests/external/wpt/url/a-element-xhtml.xhtml.ini
index cb5909d..8d16d543 100644
--- a/third_party/blink/web_tests/external/wpt/url/a-element-xhtml.xhtml.ini
+++ b/third_party/blink/web_tests/external/wpt/url/a-element-xhtml.xhtml.ini
@@ -188,9 +188,6 @@
   [Parsing: <http://%ef%bc%85%ef%bc%90%ef%bc%90.com> against <http://other.com/>]
     expected: FAIL
 
-  [Parsing: <https://faß.ExAmPlE/> against <about:blank>]
-    expected: FAIL
-
   [Parsing: <sc://faß.ExAmPlE/> against <about:blank>]
     expected: FAIL
 
diff --git a/third_party/blink/web_tests/external/wpt/url/a-element.html.ini b/third_party/blink/web_tests/external/wpt/url/a-element.html.ini
index 7736b91..4056041 100644
--- a/third_party/blink/web_tests/external/wpt/url/a-element.html.ini
+++ b/third_party/blink/web_tests/external/wpt/url/a-element.html.ini
@@ -1,6 +1,4 @@
 [a-element.html]
-  expected:
-    if (flag_specific == "") and (os == "linux") and (product == "content_shell"): [OK, CRASH]
   [Parsing: <non-special://test:@test/x> against <about:blank>]
     expected: FAIL
 
@@ -190,9 +188,6 @@
   [Parsing: <http://%ef%bc%85%ef%bc%90%ef%bc%90.com> against <http://other.com/>]
     expected: FAIL
 
-  [Parsing: <https://faß.ExAmPlE/> against <about:blank>]
-    expected: FAIL
-
   [Parsing: <sc://faß.ExAmPlE/> against <about:blank>]
     expected: FAIL
 
diff --git a/third_party/blink/web_tests/external/wpt/url/failure.html.ini b/third_party/blink/web_tests/external/wpt/url/failure.html.ini
index b9f0d7c..b1cd5fdf 100644
--- a/third_party/blink/web_tests/external/wpt/url/failure.html.ini
+++ b/third_party/blink/web_tests/external/wpt/url/failure.html.ini
@@ -1,7 +1,6 @@
 [failure.html]
   expected:
-    if (flag_specific == "") and (product == "chrome"): ERROR
-    if flag_specific == "disable-layout-ng": CRASH
+    if product == "chrome": [TIMEOUT, OK]
   [Location's href: file://example:1/ should throw]
     expected: FAIL
 
diff --git a/third_party/blink/web_tests/external/wpt/url/toascii.window-expected.txt b/third_party/blink/web_tests/external/wpt/url/toascii.window-expected.txt
index 9229523..2ca7f866 100644
--- a/third_party/blink/web_tests/external/wpt/url/toascii.window-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/url/toascii.window-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 352 tests; 198 PASS, 154 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 352 tests; 279 PASS, 73 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Loading data…
 PASS aa-- (using URL)
 PASS aa-- (using URL.host)
@@ -55,15 +55,15 @@
 PASS -x.xn--zca (using <area>)
 PASS -x.xn--zca (using <area>.host)
 PASS -x.xn--zca (using <area>.hostname)
-FAIL -x.ß (using URL) assert_equals: expected "-x.xn--zca" but got "-x.ss"
-FAIL -x.ß (using URL.host) assert_equals: expected "-x.xn--zca" but got "-x.ss"
-FAIL -x.ß (using URL.hostname) assert_equals: expected "-x.xn--zca" but got "-x.ss"
-FAIL -x.ß (using <a>) assert_equals: expected "-x.xn--zca" but got "-x.ss"
-FAIL -x.ß (using <a>.host) assert_equals: expected "-x.xn--zca" but got "-x.ss"
-FAIL -x.ß (using <a>.hostname) assert_equals: expected "-x.xn--zca" but got "-x.ss"
-FAIL -x.ß (using <area>) assert_equals: expected "-x.xn--zca" but got "-x.ss"
-FAIL -x.ß (using <area>.host) assert_equals: expected "-x.xn--zca" but got "-x.ss"
-FAIL -x.ß (using <area>.hostname) assert_equals: expected "-x.xn--zca" but got "-x.ss"
+PASS -x.ß (using URL)
+PASS -x.ß (using URL.host)
+PASS -x.ß (using URL.hostname)
+PASS -x.ß (using <a>)
+PASS -x.ß (using <a>.host)
+PASS -x.ß (using <a>.hostname)
+PASS -x.ß (using <area>)
+PASS -x.ß (using <area>.host)
+PASS -x.ß (using <area>.hostname)
 PASS x-.xn--zca (using URL)
 PASS x-.xn--zca (using URL.host)
 PASS x-.xn--zca (using URL.hostname)
@@ -73,15 +73,15 @@
 PASS x-.xn--zca (using <area>)
 PASS x-.xn--zca (using <area>.host)
 PASS x-.xn--zca (using <area>.hostname)
-FAIL x-.ß (using URL) assert_equals: expected "x-.xn--zca" but got "x-.ss"
-FAIL x-.ß (using URL.host) assert_equals: expected "x-.xn--zca" but got "x-.ss"
-FAIL x-.ß (using URL.hostname) assert_equals: expected "x-.xn--zca" but got "x-.ss"
-FAIL x-.ß (using <a>) assert_equals: expected "x-.xn--zca" but got "x-.ss"
-FAIL x-.ß (using <a>.host) assert_equals: expected "x-.xn--zca" but got "x-.ss"
-FAIL x-.ß (using <a>.hostname) assert_equals: expected "x-.xn--zca" but got "x-.ss"
-FAIL x-.ß (using <area>) assert_equals: expected "x-.xn--zca" but got "x-.ss"
-FAIL x-.ß (using <area>.host) assert_equals: expected "x-.xn--zca" but got "x-.ss"
-FAIL x-.ß (using <area>.hostname) assert_equals: expected "x-.xn--zca" but got "x-.ss"
+PASS x-.ß (using URL)
+PASS x-.ß (using URL.host)
+PASS x-.ß (using URL.hostname)
+PASS x-.ß (using <a>)
+PASS x-.ß (using <a>.host)
+PASS x-.ß (using <a>.hostname)
+PASS x-.ß (using <area>)
+PASS x-.ß (using <area>.host)
+PASS x-.ß (using <area>.hostname)
 PASS x..xn--zca (using URL)
 PASS x..xn--zca (using URL.host)
 PASS x..xn--zca (using URL.hostname)
@@ -91,15 +91,15 @@
 PASS x..xn--zca (using <area>)
 PASS x..xn--zca (using <area>.host)
 PASS x..xn--zca (using <area>.hostname)
-FAIL x..ß (using URL) assert_equals: expected "x..xn--zca" but got "x..ss"
-FAIL x..ß (using URL.host) assert_equals: expected "x..xn--zca" but got "x..ss"
-FAIL x..ß (using URL.hostname) assert_equals: expected "x..xn--zca" but got "x..ss"
-FAIL x..ß (using <a>) assert_equals: expected "x..xn--zca" but got "x..ss"
-FAIL x..ß (using <a>.host) assert_equals: expected "x..xn--zca" but got "x..ss"
-FAIL x..ß (using <a>.hostname) assert_equals: expected "x..xn--zca" but got "x..ss"
-FAIL x..ß (using <area>) assert_equals: expected "x..xn--zca" but got "x..ss"
-FAIL x..ß (using <area>.host) assert_equals: expected "x..xn--zca" but got "x..ss"
-FAIL x..ß (using <area>.hostname) assert_equals: expected "x..xn--zca" but got "x..ss"
+PASS x..ß (using URL)
+PASS x..ß (using URL.host)
+PASS x..ß (using URL.hostname)
+PASS x..ß (using <a>)
+PASS x..ß (using <a>.host)
+PASS x..ß (using <a>.hostname)
+PASS x..ß (using <area>)
+PASS x..ß (using <area>.host)
+PASS x..ß (using <area>.hostname)
 FAIL xn--a (using URL) assert_throws_js: function "() => makeURL("url", hostTest.input)" did not throw
 FAIL xn--a (using URL.host) assert_equals: expected "x" but got "xn--a"
 FAIL xn--a (using URL.hostname) assert_equals: expected "x" but got "xn--a"
@@ -145,15 +145,15 @@
 PASS xn--zca.xn--zca (using <area>)
 PASS xn--zca.xn--zca (using <area>.host)
 PASS xn--zca.xn--zca (using <area>.hostname)
-FAIL xn--zca.ß (using URL) assert_equals: expected "xn--zca.xn--zca" but got "xn--zca.ss"
-FAIL xn--zca.ß (using URL.host) assert_equals: expected "xn--zca.xn--zca" but got "xn--zca.ss"
-FAIL xn--zca.ß (using URL.hostname) assert_equals: expected "xn--zca.xn--zca" but got "xn--zca.ss"
-FAIL xn--zca.ß (using <a>) assert_equals: expected "xn--zca.xn--zca" but got "xn--zca.ss"
-FAIL xn--zca.ß (using <a>.host) assert_equals: expected "xn--zca.xn--zca" but got "xn--zca.ss"
-FAIL xn--zca.ß (using <a>.hostname) assert_equals: expected "xn--zca.xn--zca" but got "xn--zca.ss"
-FAIL xn--zca.ß (using <area>) assert_equals: expected "xn--zca.xn--zca" but got "xn--zca.ss"
-FAIL xn--zca.ß (using <area>.host) assert_equals: expected "xn--zca.xn--zca" but got "xn--zca.ss"
-FAIL xn--zca.ß (using <area>.hostname) assert_equals: expected "xn--zca.xn--zca" but got "xn--zca.ss"
+PASS xn--zca.ß (using URL)
+PASS xn--zca.ß (using URL.host)
+PASS xn--zca.ß (using URL.hostname)
+PASS xn--zca.ß (using <a>)
+PASS xn--zca.ß (using <a>.host)
+PASS xn--zca.ß (using <a>.hostname)
+PASS xn--zca.ß (using <area>)
+PASS xn--zca.ß (using <area>.host)
+PASS xn--zca.ß (using <area>.hostname)
 PASS ab--c.xn--zca (using URL)
 PASS ab--c.xn--zca (using URL.host)
 PASS ab--c.xn--zca (using URL.hostname)
@@ -163,24 +163,24 @@
 PASS ab--c.xn--zca (using <area>)
 PASS ab--c.xn--zca (using <area>.host)
 PASS ab--c.xn--zca (using <area>.hostname)
-FAIL ab--c.ß (using URL) assert_equals: expected "ab--c.xn--zca" but got "ab--c.ss"
-FAIL ab--c.ß (using URL.host) assert_equals: expected "ab--c.xn--zca" but got "ab--c.ss"
-FAIL ab--c.ß (using URL.hostname) assert_equals: expected "ab--c.xn--zca" but got "ab--c.ss"
-FAIL ab--c.ß (using <a>) assert_equals: expected "ab--c.xn--zca" but got "ab--c.ss"
-FAIL ab--c.ß (using <a>.host) assert_equals: expected "ab--c.xn--zca" but got "ab--c.ss"
-FAIL ab--c.ß (using <a>.hostname) assert_equals: expected "ab--c.xn--zca" but got "ab--c.ss"
-FAIL ab--c.ß (using <area>) assert_equals: expected "ab--c.xn--zca" but got "ab--c.ss"
-FAIL ab--c.ß (using <area>.host) assert_equals: expected "ab--c.xn--zca" but got "ab--c.ss"
-FAIL ab--c.ß (using <area>.hostname) assert_equals: expected "ab--c.xn--zca" but got "ab--c.ss"
+PASS ab--c.ß (using URL)
+PASS ab--c.ß (using URL.host)
+PASS ab--c.ß (using URL.hostname)
+PASS ab--c.ß (using <a>)
+PASS ab--c.ß (using <a>.host)
+PASS ab--c.ß (using <a>.hostname)
+PASS ab--c.ß (using <area>)
+PASS ab--c.ß (using <area>.host)
+PASS ab--c.ß (using <area>.hostname)
 FAIL ‍.example (using URL) assert_throws_js: function "() => makeURL("url", hostTest.input)" did not throw
-FAIL ‍.example (using URL.host) assert_equals: expected "x" but got ".example"
-FAIL ‍.example (using URL.hostname) assert_equals: expected "x" but got ".example"
-FAIL ‍.example (using <a>) assert_equals: expected "" but got ".example"
-FAIL ‍.example (using <a>.host) assert_equals: expected "x" but got ".example"
-FAIL ‍.example (using <a>.hostname) assert_equals: expected "x" but got ".example"
-FAIL ‍.example (using <area>) assert_equals: expected "" but got ".example"
-FAIL ‍.example (using <area>.host) assert_equals: expected "x" but got ".example"
-FAIL ‍.example (using <area>.hostname) assert_equals: expected "x" but got ".example"
+FAIL ‍.example (using URL.host) assert_equals: expected "x" but got "xn--1ug.example"
+FAIL ‍.example (using URL.hostname) assert_equals: expected "x" but got "xn--1ug.example"
+FAIL ‍.example (using <a>) assert_equals: expected "" but got "xn--1ug.example"
+FAIL ‍.example (using <a>.host) assert_equals: expected "x" but got "xn--1ug.example"
+FAIL ‍.example (using <a>.hostname) assert_equals: expected "x" but got "xn--1ug.example"
+FAIL ‍.example (using <area>) assert_equals: expected "" but got "xn--1ug.example"
+FAIL ‍.example (using <area>.host) assert_equals: expected "x" but got "xn--1ug.example"
+FAIL ‍.example (using <area>.hostname) assert_equals: expected "x" but got "xn--1ug.example"
 FAIL xn--1ug.example (using URL) assert_throws_js: function "() => makeURL("url", hostTest.input)" did not throw
 FAIL xn--1ug.example (using URL.host) assert_equals: expected "x" but got "xn--1ug.example"
 FAIL xn--1ug.example (using URL.hostname) assert_equals: expected "x" but got "xn--1ug.example"
@@ -208,24 +208,24 @@
 FAIL xn--a-yoc (using <area>) assert_equals: expected "" but got "xn--a-yoc"
 FAIL xn--a-yoc (using <area>.host) assert_equals: expected "x" but got "xn--a-yoc"
 FAIL xn--a-yoc (using <area>.hostname) assert_equals: expected "x" but got "xn--a-yoc"
-FAIL ශ්‍රී (using URL) assert_equals: expected "xn--10cl1a0b660p" but got "xn--10cl1a0b"
-FAIL ශ්‍රී (using URL.host) assert_equals: expected "xn--10cl1a0b660p" but got "xn--10cl1a0b"
-FAIL ශ්‍රී (using URL.hostname) assert_equals: expected "xn--10cl1a0b660p" but got "xn--10cl1a0b"
-FAIL ශ්‍රී (using <a>) assert_equals: expected "xn--10cl1a0b660p" but got "xn--10cl1a0b"
-FAIL ශ්‍රී (using <a>.host) assert_equals: expected "xn--10cl1a0b660p" but got "xn--10cl1a0b"
-FAIL ශ්‍රී (using <a>.hostname) assert_equals: expected "xn--10cl1a0b660p" but got "xn--10cl1a0b"
-FAIL ශ්‍රී (using <area>) assert_equals: expected "xn--10cl1a0b660p" but got "xn--10cl1a0b"
-FAIL ශ්‍රී (using <area>.host) assert_equals: expected "xn--10cl1a0b660p" but got "xn--10cl1a0b"
-FAIL ශ්‍රී (using <area>.hostname) assert_equals: expected "xn--10cl1a0b660p" but got "xn--10cl1a0b"
-FAIL نامه‌ای (using URL) assert_equals: expected "xn--mgba3gch31f060k" but got "xn--mgba3gch31f"
-FAIL نامه‌ای (using URL.host) assert_equals: expected "xn--mgba3gch31f060k" but got "xn--mgba3gch31f"
-FAIL نامه‌ای (using URL.hostname) assert_equals: expected "xn--mgba3gch31f060k" but got "xn--mgba3gch31f"
-FAIL نامه‌ای (using <a>) assert_equals: expected "xn--mgba3gch31f060k" but got "xn--mgba3gch31f"
-FAIL نامه‌ای (using <a>.host) assert_equals: expected "xn--mgba3gch31f060k" but got "xn--mgba3gch31f"
-FAIL نامه‌ای (using <a>.hostname) assert_equals: expected "xn--mgba3gch31f060k" but got "xn--mgba3gch31f"
-FAIL نامه‌ای (using <area>) assert_equals: expected "xn--mgba3gch31f060k" but got "xn--mgba3gch31f"
-FAIL نامه‌ای (using <area>.host) assert_equals: expected "xn--mgba3gch31f060k" but got "xn--mgba3gch31f"
-FAIL نامه‌ای (using <area>.hostname) assert_equals: expected "xn--mgba3gch31f060k" but got "xn--mgba3gch31f"
+PASS ශ්‍රී (using URL)
+PASS ශ්‍රී (using URL.host)
+PASS ශ්‍රී (using URL.hostname)
+PASS ශ්‍රී (using <a>)
+PASS ශ්‍රී (using <a>.host)
+PASS ශ්‍රී (using <a>.hostname)
+PASS ශ්‍රී (using <area>)
+PASS ශ්‍රී (using <area>.host)
+PASS ශ්‍රී (using <area>.hostname)
+PASS نامه‌ای (using URL)
+PASS نامه‌ای (using URL.host)
+PASS نامه‌ای (using URL.hostname)
+PASS نامه‌ای (using <a>)
+PASS نامه‌ای (using <a>.host)
+PASS نامه‌ای (using <a>.hostname)
+PASS نامه‌ای (using <area>)
+PASS نامه‌ای (using <area>.host)
+PASS نامه‌ای (using <area>.hostname)
 PASS �.com (using URL)
 PASS �.com (using URL.host)
 PASS �.com (using URL.hostname)
@@ -271,15 +271,15 @@
 PASS x01234567890123456789012345678901234567890123456789012345678901x.xn--zca (using <area>)
 PASS x01234567890123456789012345678901234567890123456789012345678901x.xn--zca (using <area>.host)
 PASS x01234567890123456789012345678901234567890123456789012345678901x.xn--zca (using <area>.hostname)
-FAIL x01234567890123456789012345678901234567890123456789012345678901x.ß (using URL) assert_equals: expected "x01234567890123456789012345678901234567890123456789012345678901x.xn--zca" but got "x01234567890123456789012345678901234567890123456789012345678901x.ss"
-FAIL x01234567890123456789012345678901234567890123456789012345678901x.ß (using URL.host) assert_equals: expected "x01234567890123456789012345678901234567890123456789012345678901x.xn--zca" but got "x01234567890123456789012345678901234567890123456789012345678901x.ss"
-FAIL x01234567890123456789012345678901234567890123456789012345678901x.ß (using URL.hostname) assert_equals: expected "x01234567890123456789012345678901234567890123456789012345678901x.xn--zca" but got "x01234567890123456789012345678901234567890123456789012345678901x.ss"
-FAIL x01234567890123456789012345678901234567890123456789012345678901x.ß (using <a>) assert_equals: expected "x01234567890123456789012345678901234567890123456789012345678901x.xn--zca" but got "x01234567890123456789012345678901234567890123456789012345678901x.ss"
-FAIL x01234567890123456789012345678901234567890123456789012345678901x.ß (using <a>.host) assert_equals: expected "x01234567890123456789012345678901234567890123456789012345678901x.xn--zca" but got "x01234567890123456789012345678901234567890123456789012345678901x.ss"
-FAIL x01234567890123456789012345678901234567890123456789012345678901x.ß (using <a>.hostname) assert_equals: expected "x01234567890123456789012345678901234567890123456789012345678901x.xn--zca" but got "x01234567890123456789012345678901234567890123456789012345678901x.ss"
-FAIL x01234567890123456789012345678901234567890123456789012345678901x.ß (using <area>) assert_equals: expected "x01234567890123456789012345678901234567890123456789012345678901x.xn--zca" but got "x01234567890123456789012345678901234567890123456789012345678901x.ss"
-FAIL x01234567890123456789012345678901234567890123456789012345678901x.ß (using <area>.host) assert_equals: expected "x01234567890123456789012345678901234567890123456789012345678901x.xn--zca" but got "x01234567890123456789012345678901234567890123456789012345678901x.ss"
-FAIL x01234567890123456789012345678901234567890123456789012345678901x.ß (using <area>.hostname) assert_equals: expected "x01234567890123456789012345678901234567890123456789012345678901x.xn--zca" but got "x01234567890123456789012345678901234567890123456789012345678901x.ss"
+PASS x01234567890123456789012345678901234567890123456789012345678901x.ß (using URL)
+PASS x01234567890123456789012345678901234567890123456789012345678901x.ß (using URL.host)
+PASS x01234567890123456789012345678901234567890123456789012345678901x.ß (using URL.hostname)
+PASS x01234567890123456789012345678901234567890123456789012345678901x.ß (using <a>)
+PASS x01234567890123456789012345678901234567890123456789012345678901x.ß (using <a>.host)
+PASS x01234567890123456789012345678901234567890123456789012345678901x.ß (using <a>.hostname)
+PASS x01234567890123456789012345678901234567890123456789012345678901x.ß (using <area>)
+PASS x01234567890123456789012345678901234567890123456789012345678901x.ß (using <area>.host)
+PASS x01234567890123456789012345678901234567890123456789012345678901x.ß (using <area>.hostname)
 PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.x (using URL)
 PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.x (using URL.host)
 PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.x (using URL.hostname)
@@ -298,15 +298,15 @@
 PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.xn--zca (using <area>)
 PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.xn--zca (using <area>.host)
 PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.xn--zca (using <area>.hostname)
-FAIL 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ß (using URL) assert_equals: expected "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.xn--zca" but got "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ss"
-FAIL 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ß (using URL.host) assert_equals: expected "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.xn--zca" but got "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ss"
-FAIL 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ß (using URL.hostname) assert_equals: expected "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.xn--zca" but got "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ss"
-FAIL 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ß (using <a>) assert_equals: expected "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.xn--zca" but got "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ss"
-FAIL 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ß (using <a>.host) assert_equals: expected "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.xn--zca" but got "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ss"
-FAIL 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ß (using <a>.hostname) assert_equals: expected "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.xn--zca" but got "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ss"
-FAIL 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ß (using <area>) assert_equals: expected "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.xn--zca" but got "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ss"
-FAIL 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ß (using <area>.host) assert_equals: expected "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.xn--zca" but got "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ss"
-FAIL 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ß (using <area>.hostname) assert_equals: expected "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.xn--zca" but got "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ss"
+PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ß (using URL)
+PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ß (using URL.host)
+PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ß (using URL.hostname)
+PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ß (using <a>)
+PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ß (using <a>.host)
+PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ß (using <a>.hostname)
+PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ß (using <area>)
+PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ß (using <area>.host)
+PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ß (using <area>.hostname)
 PASS a­b (using URL)
 PASS a­b (using URL.host)
 PASS a­b (using URL.hostname)
diff --git a/third_party/blink/web_tests/external/wpt/url/toascii.window.js.ini b/third_party/blink/web_tests/external/wpt/url/toascii.window.js.ini
index e439403..5cfe13bf 100644
--- a/third_party/blink/web_tests/external/wpt/url/toascii.window.js.ini
+++ b/third_party/blink/web_tests/external/wpt/url/toascii.window.js.ini
@@ -1,87 +1,4 @@
 [toascii.window.html]
-  expected:
-    if (flag_specific == "") and (os == "linux") and (product == "content_shell"): [OK, CRASH]
-  [-x.ß (using URL)]
-    expected: FAIL
-
-  [-x.ß (using URL.host)]
-    expected: FAIL
-
-  [-x.ß (using URL.hostname)]
-    expected: FAIL
-
-  [-x.ß (using <a>)]
-    expected: FAIL
-
-  [-x.ß (using <a>.host)]
-    expected: FAIL
-
-  [-x.ß (using <a>.hostname)]
-    expected: FAIL
-
-  [-x.ß (using <area>)]
-    expected: FAIL
-
-  [-x.ß (using <area>.host)]
-    expected: FAIL
-
-  [-x.ß (using <area>.hostname)]
-    expected: FAIL
-
-  [x-.ß (using URL)]
-    expected: FAIL
-
-  [x-.ß (using URL.host)]
-    expected: FAIL
-
-  [x-.ß (using URL.hostname)]
-    expected: FAIL
-
-  [x-.ß (using <a>)]
-    expected: FAIL
-
-  [x-.ß (using <a>.host)]
-    expected: FAIL
-
-  [x-.ß (using <a>.hostname)]
-    expected: FAIL
-
-  [x-.ß (using <area>)]
-    expected: FAIL
-
-  [x-.ß (using <area>.host)]
-    expected: FAIL
-
-  [x-.ß (using <area>.hostname)]
-    expected: FAIL
-
-  [x..ß (using URL)]
-    expected: FAIL
-
-  [x..ß (using URL.host)]
-    expected: FAIL
-
-  [x..ß (using URL.hostname)]
-    expected: FAIL
-
-  [x..ß (using <a>)]
-    expected: FAIL
-
-  [x..ß (using <a>.host)]
-    expected: FAIL
-
-  [x..ß (using <a>.hostname)]
-    expected: FAIL
-
-  [x..ß (using <area>)]
-    expected: FAIL
-
-  [x..ß (using <area>.host)]
-    expected: FAIL
-
-  [x..ß (using <area>.hostname)]
-    expected: FAIL
-
   [xn--a (using URL)]
     expected: FAIL
 
@@ -148,60 +65,6 @@
   [xn--tešla (using <area>)]
     expected: FAIL
 
-  [xn--zca.ß (using URL)]
-    expected: FAIL
-
-  [xn--zca.ß (using URL.host)]
-    expected: FAIL
-
-  [xn--zca.ß (using URL.hostname)]
-    expected: FAIL
-
-  [xn--zca.ß (using <a>)]
-    expected: FAIL
-
-  [xn--zca.ß (using <a>.host)]
-    expected: FAIL
-
-  [xn--zca.ß (using <a>.hostname)]
-    expected: FAIL
-
-  [xn--zca.ß (using <area>)]
-    expected: FAIL
-
-  [xn--zca.ß (using <area>.host)]
-    expected: FAIL
-
-  [xn--zca.ß (using <area>.hostname)]
-    expected: FAIL
-
-  [ab--c.ß (using URL)]
-    expected: FAIL
-
-  [ab--c.ß (using URL.host)]
-    expected: FAIL
-
-  [ab--c.ß (using URL.hostname)]
-    expected: FAIL
-
-  [ab--c.ß (using <a>)]
-    expected: FAIL
-
-  [ab--c.ß (using <a>.host)]
-    expected: FAIL
-
-  [ab--c.ß (using <a>.hostname)]
-    expected: FAIL
-
-  [ab--c.ß (using <area>)]
-    expected: FAIL
-
-  [ab--c.ß (using <area>.host)]
-    expected: FAIL
-
-  [ab--c.ß (using <area>.hostname)]
-    expected: FAIL
-
   [‍.example (using URL)]
     expected: FAIL
 
@@ -289,60 +152,6 @@
   [xn--a-yoc (using <area>.hostname)]
     expected: FAIL
 
-  [ශ්‍රී (using URL)]
-    expected: FAIL
-
-  [ශ්‍රී (using URL.host)]
-    expected: FAIL
-
-  [ශ්‍රී (using URL.hostname)]
-    expected: FAIL
-
-  [ශ්‍රී (using <a>)]
-    expected: FAIL
-
-  [ශ්‍රී (using <a>.host)]
-    expected: FAIL
-
-  [ශ්‍රී (using <a>.hostname)]
-    expected: FAIL
-
-  [ශ්‍රී (using <area>)]
-    expected: FAIL
-
-  [ශ්‍රී (using <area>.host)]
-    expected: FAIL
-
-  [ශ්‍රී (using <area>.hostname)]
-    expected: FAIL
-
-  [نامه‌ای (using URL)]
-    expected: FAIL
-
-  [نامه‌ای (using URL.host)]
-    expected: FAIL
-
-  [نامه‌ای (using URL.hostname)]
-    expected: FAIL
-
-  [نامه‌ای (using <a>)]
-    expected: FAIL
-
-  [نامه‌ای (using <a>.host)]
-    expected: FAIL
-
-  [نامه‌ای (using <a>.hostname)]
-    expected: FAIL
-
-  [نامه‌ای (using <area>)]
-    expected: FAIL
-
-  [نامه‌ای (using <area>.host)]
-    expected: FAIL
-
-  [نامه‌ای (using <area>.hostname)]
-    expected: FAIL
-
   [�.com (using <a>)]
     expected: FAIL
 
@@ -376,60 +185,6 @@
   [xn--zn7c.com (using <area>.hostname)]
     expected: FAIL
 
-  [x01234567890123456789012345678901234567890123456789012345678901x.ß (using URL)]
-    expected: FAIL
-
-  [x01234567890123456789012345678901234567890123456789012345678901x.ß (using URL.host)]
-    expected: FAIL
-
-  [x01234567890123456789012345678901234567890123456789012345678901x.ß (using URL.hostname)]
-    expected: FAIL
-
-  [x01234567890123456789012345678901234567890123456789012345678901x.ß (using <a>)]
-    expected: FAIL
-
-  [x01234567890123456789012345678901234567890123456789012345678901x.ß (using <a>.host)]
-    expected: FAIL
-
-  [x01234567890123456789012345678901234567890123456789012345678901x.ß (using <a>.hostname)]
-    expected: FAIL
-
-  [x01234567890123456789012345678901234567890123456789012345678901x.ß (using <area>)]
-    expected: FAIL
-
-  [x01234567890123456789012345678901234567890123456789012345678901x.ß (using <area>.host)]
-    expected: FAIL
-
-  [x01234567890123456789012345678901234567890123456789012345678901x.ß (using <area>.hostname)]
-    expected: FAIL
-
-  [01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ß (using URL)]
-    expected: FAIL
-
-  [01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ß (using URL.host)]
-    expected: FAIL
-
-  [01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ß (using URL.hostname)]
-    expected: FAIL
-
-  [01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ß (using <a>)]
-    expected: FAIL
-
-  [01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ß (using <a>.host)]
-    expected: FAIL
-
-  [01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ß (using <a>.hostname)]
-    expected: FAIL
-
-  [01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ß (using <area>)]
-    expected: FAIL
-
-  [01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ß (using <area>.host)]
-    expected: FAIL
-
-  [01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ß (using <area>.hostname)]
-    expected: FAIL
-
   [­ (using <a>)]
     expected: FAIL
 
diff --git a/third_party/blink/web_tests/external/wpt/url/url-constructor.any.js.ini b/third_party/blink/web_tests/external/wpt/url/url-constructor.any.js.ini
index 3eb2f508..e8741cd 100644
--- a/third_party/blink/web_tests/external/wpt/url/url-constructor.any.js.ini
+++ b/third_party/blink/web_tests/external/wpt/url/url-constructor.any.js.ini
@@ -110,9 +110,6 @@
   [Parsing: <http://%ef%bc%85%ef%bc%94%ef%bc%91.com> against <http://other.com/>]
     expected: FAIL
 
-  [Parsing: <https://faß.ExAmPlE/> against <about:blank>]
-    expected: FAIL
-
   [Parsing: <sc://faß.ExAmPlE/> against <about:blank>]
     expected: FAIL
 
@@ -801,9 +798,6 @@
   [Parsing: <http://%ef%bc%85%ef%bc%94%ef%bc%91.com> against <http://other.com/>]
     expected: FAIL
 
-  [Parsing: <https://faß.ExAmPlE/> against <about:blank>]
-    expected: FAIL
-
   [Parsing: <sc://faß.ExAmPlE/> against <about:blank>]
     expected: FAIL
 
diff --git a/third_party/blink/web_tests/external/wpt/url/url-origin.any.js.ini b/third_party/blink/web_tests/external/wpt/url/url-origin.any.js.ini
index 140126f..4c9a931 100644
--- a/third_party/blink/web_tests/external/wpt/url/url-origin.any.js.ini
+++ b/third_party/blink/web_tests/external/wpt/url/url-origin.any.js.ini
@@ -2,9 +2,6 @@
   [Origin parsing: <http://example.com/foo%00%51> against <about:blank>]
     expected: FAIL
 
-  [Origin parsing: <https://faß.ExAmPlE/> against <about:blank>]
-    expected: FAIL
-
   [Origin parsing: <x> against <sc://ñ>]
     expected: FAIL
 
@@ -40,9 +37,6 @@
   [Origin parsing: <http://example.com/foo%00%51> against <about:blank>]
     expected: FAIL
 
-  [Origin parsing: <https://faß.ExAmPlE/> against <about:blank>]
-    expected: FAIL
-
   [Origin parsing: <x> against <sc://ñ>]
     expected: FAIL
 
diff --git a/third_party/blink/web_tests/external/wpt/url/url-setters-a-area.window.js.ini b/third_party/blink/web_tests/external/wpt/url/url-setters-a-area.window.js.ini
index 7211294d..5f5f5b47 100644
--- a/third_party/blink/web_tests/external/wpt/url/url-setters-a-area.window.js.ini
+++ b/third_party/blink/web_tests/external/wpt/url/url-setters-a-area.window.js.ini
@@ -149,12 +149,6 @@
   [<area>: Setting <sc://x/>.host = 'ß']
     expected: FAIL
 
-  [<a>: Setting <https://x/>.host = 'ß' IDNA Nontransitional_Processing]
-    expected: FAIL
-
-  [<area>: Setting <https://x/>.host = 'ß' IDNA Nontransitional_Processing]
-    expected: FAIL
-
   [<a>: Setting <view-source+http://example.net/foo>.host = '' The empty host is OK for non-special schemes]
     expected: FAIL
 
diff --git a/third_party/blink/web_tests/external/wpt/url/url-setters-stripping.any.js.ini b/third_party/blink/web_tests/external/wpt/url/url-setters-stripping.any.js.ini
index d2c3ff18..967c34b 100644
--- a/third_party/blink/web_tests/external/wpt/url/url-setters-stripping.any.js.ini
+++ b/third_party/blink/web_tests/external/wpt/url/url-setters-stripping.any.js.ini
@@ -1,6 +1,4 @@
 [url-setters-stripping.any.html]
-  expected:
-    if os == "win": TIMEOUT
   [Setting pathname with leading U+0000 (https:)]
     expected: FAIL
 
diff --git a/third_party/blink/web_tests/external/wpt/url/url-setters.any.js.ini b/third_party/blink/web_tests/external/wpt/url/url-setters.any.js.ini
index 9d62ebfc..e6bf441 100644
--- a/third_party/blink/web_tests/external/wpt/url/url-setters.any.js.ini
+++ b/third_party/blink/web_tests/external/wpt/url/url-setters.any.js.ini
@@ -74,9 +74,6 @@
   [URL: Setting <sc://x/>.host = 'ß']
     expected: FAIL
 
-  [URL: Setting <https://x/>.host = 'ß' IDNA Nontransitional_Processing]
-    expected: FAIL
-
   [URL: Setting <view-source+http://example.net/foo>.host = '' The empty host is OK for non-special schemes]
     expected: FAIL
 
@@ -365,9 +362,6 @@
   [URL: Setting <sc://x/>.host = 'ß']
     expected: FAIL
 
-  [URL: Setting <https://x/>.host = 'ß' IDNA Nontransitional_Processing]
-    expected: FAIL
-
   [URL: Setting <view-source+http://example.net/foo>.host = '' The empty host is OK for non-special schemes]
     expected: FAIL
 
diff --git a/third_party/blink/web_tests/external/wpt/url/urlsearchparams-stringifier.any.js.ini b/third_party/blink/web_tests/external/wpt/url/urlsearchparams-stringifier.any.js.ini
deleted file mode 100644
index c61e9b00..0000000
--- a/third_party/blink/web_tests/external/wpt/url/urlsearchparams-stringifier.any.js.ini
+++ /dev/null
@@ -1,6 +0,0 @@
-[urlsearchparams-stringifier.any.worker.html]
-
-[urlsearchparams-stringifier.any.html]
-  expected:
-    if (os == "linux") and (flag_specific == "") and (product == "content_shell"): CRASH
-    if os == "win": TIMEOUT
diff --git a/third_party/blink/web_tests/external/wpt/visual-viewport/viewport-resize-event-on-load-overflowing-page.html.ini b/third_party/blink/web_tests/external/wpt/visual-viewport/viewport-resize-event-on-load-overflowing-page.html.ini
index 70faeff..2ac64bb9 100644
--- a/third_party/blink/web_tests/external/wpt/visual-viewport/viewport-resize-event-on-load-overflowing-page.html.ini
+++ b/third_party/blink/web_tests/external/wpt/visual-viewport/viewport-resize-event-on-load-overflowing-page.html.ini
@@ -1,3 +1,3 @@
 [viewport-resize-event-on-load-overflowing-page.html]
   expected:
-    if product == "chrome": [OK, ERROR]
+    if product == "chrome": ERROR
diff --git a/third_party/blink/web_tests/external/wpt/web-animations/timing-model/timelines/document-timelines.html.ini b/third_party/blink/web_tests/external/wpt/web-animations/timing-model/timelines/document-timelines.html.ini
deleted file mode 100644
index 08dcd48..0000000
--- a/third_party/blink/web_tests/external/wpt/web-animations/timing-model/timelines/document-timelines.html.ini
+++ /dev/null
@@ -1,4 +0,0 @@
-[document-timelines.html]
-  [Document timelines report current time relative to navigationStart]
-    expected:
-      if product == "chrome": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/web-animations/timing-model/timelines/sibling-iframe-timeline.html.ini b/third_party/blink/web_tests/external/wpt/web-animations/timing-model/timelines/sibling-iframe-timeline.html.ini
index 7cbc926..74063bfe 100644
--- a/third_party/blink/web_tests/external/wpt/web-animations/timing-model/timelines/sibling-iframe-timeline.html.ini
+++ b/third_party/blink/web_tests/external/wpt/web-animations/timing-model/timelines/sibling-iframe-timeline.html.ini
@@ -1,6 +1,6 @@
 [sibling-iframe-timeline.html]
   [animation tied to another frame's timeline runs properly]
     expected:
-      if (flag_specific == "") and (product == "content_shell") and (os == "linux"): FAIL
-      if (flag_specific == "") and (product == "content_shell") and (os == "win"): [PASS, FAIL]
-      if flag_specific == "disable-layout-ng": [PASS, FAIL]
+      if (flag_specific == "") and (os == "linux") and (product == "chrome"): PASS
+      if flag_specific == "disable-site-isolation-trials": PASS
+      FAIL
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
index f41cb51..75e8f9c0 100644
--- 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
@@ -1,16 +1,8 @@
 [reuse-web-bundle-resource.https.tentative.html]
-  [Should not reuse webbundle resources if a credentials mode is different (same-origin vs omit)]
+  [A webbundle should be fetched again when new script element is appended.]
     expected:
-      if (flag_specific == "") and (product == "content_shell"): [PASS, FAIL]
+      if (flag_specific == "") and (os == "linux") and (product == "content_shell"): [PASS, FAIL]
 
-  [Multiple 'remove(), then append()' for the same element should reuse webbundle resources]
+  ['remove(), then append()' should reuse webbundle resources]
     expected:
-      if (flag_specific == "") and (product == "content_shell"): [PASS, FAIL]
-
-  [Should not reuse webbundle resources if a credential mode is different (same-origin vs include]
-    expected:
-      if (flag_specific == "") and (product == "content_shell"): [PASS, FAIL]
-
-  ['remove(), then append()' for the same element should reuse webbundle resources]
-    expected:
-      if (flag_specific == "") and (product == "content_shell"): [PASS, FAIL]
+      if (flag_specific == "") and (os == "linux") and (product == "content_shell"): [PASS, FAIL]
diff --git a/third_party/blink/web_tests/external/wpt/web-locks/query-ordering.tentative.https.html.ini b/third_party/blink/web_tests/external/wpt/web-locks/query-ordering.tentative.https.html.ini
index 5b305283..236f81a 100644
--- a/third_party/blink/web_tests/external/wpt/web-locks/query-ordering.tentative.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/web-locks/query-ordering.tentative.https.html.ini
@@ -1,6 +1,3 @@
 [query-ordering.tentative.https.html]
   expected:
-    if flag_specific == "disable-site-isolation-trials": [OK, CRASH]
-  [Requests appear in state in order made.]
-    expected:
-      if flag_specific == "disable-site-isolation-trials": [PASS, FAIL]
+    if flag_specific == "disable-site-isolation-trials": CRASH
diff --git a/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-analysernode-interface/test-analyser-output.html.ini b/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-analysernode-interface/test-analyser-output.html.ini
deleted file mode 100644
index 2653425..0000000
--- a/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-analysernode-interface/test-analyser-output.html.ini
+++ /dev/null
@@ -1,6 +0,0 @@
-[test-analyser-output.html]
-  [AnalyserNode output]
-    expected:
-      if (flag_specific == "") and (product == "chrome"): [PASS, FAIL]
-      if flag_specific == "disable-site-isolation-trials": [FAIL, PASS]
-      if flag_specific == "disable-layout-ng": [PASS, FAIL]
diff --git a/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-audiobuffer-interface/acquire-the-content.html.ini b/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-audiobuffer-interface/acquire-the-content.html.ini
index d9caee5c..9f772764 100644
--- a/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-audiobuffer-interface/acquire-the-content.html.ini
+++ b/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-audiobuffer-interface/acquire-the-content.html.ini
@@ -1,6 +1,4 @@
 [acquire-the-content.html]
-  expected:
-    if flag_specific == "disable-layout-ng": CRASH
   [AudioBufferSourceNode setter set with non-null buffer]
     expected: FAIL
 
diff --git a/third_party/blink/web_tests/external/wpt/webcodecs/videoFrame-canvasImageSource.html.ini b/third_party/blink/web_tests/external/wpt/webcodecs/videoFrame-canvasImageSource.html.ini
index 9a09fd3..177c5acd 100644
--- a/third_party/blink/web_tests/external/wpt/webcodecs/videoFrame-canvasImageSource.html.ini
+++ b/third_party/blink/web_tests/external/wpt/webcodecs/videoFrame-canvasImageSource.html.ini
@@ -1,3 +1,3 @@
 [videoFrame-canvasImageSource.html]
   expected:
-    if flag_specific == "highdpi": CRASH
+    if os == "win": [OK, CRASH]
diff --git a/third_party/blink/web_tests/external/wpt/webmessaging/multi-globals/broadcastchannel-incumbent.sub.html.ini b/third_party/blink/web_tests/external/wpt/webmessaging/multi-globals/broadcastchannel-incumbent.sub.html.ini
index b7ed4395..6742e102 100644
--- a/third_party/blink/web_tests/external/wpt/webmessaging/multi-globals/broadcastchannel-incumbent.sub.html.ini
+++ b/third_party/blink/web_tests/external/wpt/webmessaging/multi-globals/broadcastchannel-incumbent.sub.html.ini
@@ -1,7 +1,6 @@
 [broadcastchannel-incumbent.sub.html]
-  expected:
-    if os == "win": TIMEOUT
   [The incumbent page being cross-origin must not prevent the BroadcastChannel message from being seen]
     expected:
-      if (flag_specific == "") and (product == "content_shell"): FAIL
-      if flag_specific == "disable-layout-ng": FAIL
+      if (flag_specific == "") and (product == "chrome"): PASS
+      if flag_specific == "disable-site-isolation-trials": PASS
+      FAIL
diff --git a/third_party/blink/web_tests/external/wpt/webmessaging/postMessage_Date.sub.htm.ini b/third_party/blink/web_tests/external/wpt/webmessaging/postMessage_Date.sub.htm.ini
index 2ab22ee..dd186255 100644
--- a/third_party/blink/web_tests/external/wpt/webmessaging/postMessage_Date.sub.htm.ini
+++ b/third_party/blink/web_tests/external/wpt/webmessaging/postMessage_Date.sub.htm.ini
@@ -1,3 +1,3 @@
 [postMessage_Date.sub.htm]
   expected:
-    if os == "win": [OK, TIMEOUT]
+    if os == "win": TIMEOUT
diff --git a/third_party/blink/web_tests/external/wpt/webmessaging/with-ports/020.html.ini b/third_party/blink/web_tests/external/wpt/webmessaging/with-ports/020.html.ini
index 921da39..b35e66e 100644
--- a/third_party/blink/web_tests/external/wpt/webmessaging/with-ports/020.html.ini
+++ b/third_party/blink/web_tests/external/wpt/webmessaging/with-ports/020.html.ini
@@ -1,4 +1,4 @@
 [020.html]
   [cross-origin test]
     expected:
-      if flag_specific == "disable-layout-ng": [PASS, FAIL]
+      if (flag_specific == "") and (os == "linux") and (product == "content_shell"): [PASS, FAIL]
diff --git a/third_party/blink/web_tests/external/wpt/webrtc-stats/supported-stats.https.html.ini b/third_party/blink/web_tests/external/wpt/webrtc-stats/supported-stats.https.html.ini
index 66af6f83..2051dec 100644
--- a/third_party/blink/web_tests/external/wpt/webrtc-stats/supported-stats.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/webrtc-stats/supported-stats.https.html.ini
@@ -58,7 +58,7 @@
 
   [transport's remoteCertificateId]
     expected:
-      if flag_specific == "disable-layout-ng": [PASS, FAIL]
+      if flag_specific == "disable-site-isolation-trials": [PASS, FAIL]
 
   [candidate-pair's availableIncomingBitrate]
     expected: PRECONDITION_FAILED
diff --git a/third_party/blink/web_tests/external/wpt/websockets/stream/tentative/abort.any.js.ini b/third_party/blink/web_tests/external/wpt/websockets/stream/tentative/abort.any.js.ini
index 0de3ca02..99ed37c 100644
--- a/third_party/blink/web_tests/external/wpt/websockets/stream/tentative/abort.any.js.ini
+++ b/third_party/blink/web_tests/external/wpt/websockets/stream/tentative/abort.any.js.ini
@@ -1,4 +1,6 @@
 [abort.any.serviceworker.html?wss]
+  expected:
+    if flag_specific == "disable-layout-ng": [OK, CRASH]
 
 [abort.any.html?wss]
 
@@ -11,7 +13,5 @@
 [abort.any.worker.html?wss]
 
 [abort.any.html?wpt_flags=h2]
-  expected:
-    if flag_specific == "disable-layout-ng": [OK, CRASH]
 
 [abort.any.serviceworker.html?wpt_flags=h2]
diff --git a/third_party/blink/web_tests/external/wpt/websockets/stream/tentative/close.any.js.ini b/third_party/blink/web_tests/external/wpt/websockets/stream/tentative/close.any.js.ini
index a1a918feb..46e8e6a 100644
--- a/third_party/blink/web_tests/external/wpt/websockets/stream/tentative/close.any.js.ini
+++ b/third_party/blink/web_tests/external/wpt/websockets/stream/tentative/close.any.js.ini
@@ -4,12 +4,14 @@
 
 [close.any.worker.html?wpt_flags=h2]
   expected:
-    if flag_specific == "disable-site-isolation-trials": [OK, TIMEOUT]
+    if (flag_specific == "") and (product == "chrome"): [OK, TIMEOUT]
+    if flag_specific == "disable-layout-ng": [OK, TIMEOUT]
   [canceling the readable with a code should send that code]
     expected: [PASS, NOTRUN]
 
   [canceling the readable with an invalid code should be ignored]
-    expected: [PASS, TIMEOUT, NOTRUN]
+    expected:
+      if product == "chrome": [PASS, TIMEOUT]
 
   [canceling the readable should result in a clean close]
     expected: [PASS, TIMEOUT]
@@ -21,10 +23,12 @@
     expected: [PASS, NOTRUN]
 
   [canceling the readable with a DOMException should be ignored]
-    expected: [PASS, NOTRUN]
+    expected:
+      if product == "chrome": [PASS, NOTRUN]
 
   [canceling the readable with an invalid reason should be ignored]
-    expected: [PASS, NOTRUN]
+    expected:
+      if product == "chrome": [PASS, NOTRUN]
 
 
 [close.any.serviceworker.html?wss]
@@ -32,11 +36,10 @@
 [close.any.worker.html?wss]
 
 [close.any.html?wpt_flags=h2]
-  expected:
-    if flag_specific == "disable-site-isolation-trials": [OK, TIMEOUT]
 
 [close.any.sharedworker.html?wpt_flags=h2]
-  expected: [OK, TIMEOUT]
+  expected:
+    if (flag_specific == "") and (os == "linux") and (product == "content_shell"): TIMEOUT
   [canceling the readable with a code should send that code]
     expected: [PASS, NOTRUN]
 
@@ -63,5 +66,28 @@
 
 
 [close.any.serviceworker.html?wpt_flags=h2]
+  expected:
+    if product == "chrome": [OK, TIMEOUT]
   [canceling the readable with a DOMException should be ignored]
-    expected: [PASS, TIMEOUT]
+    expected:
+      if product == "chrome": [PASS, NOTRUN]
+
+  [canceling the readable with a code should send that code]
+    expected:
+      if product == "chrome": [PASS, TIMEOUT]
+
+  [canceling the readable with a code and reason should use them]
+    expected:
+      if product == "chrome": [PASS, NOTRUN]
+
+  [canceling the readable with an invalid reason should be ignored]
+    expected:
+      if product == "chrome": [PASS, NOTRUN]
+
+  [canceling the readable with a reason but no code should be ignored]
+    expected:
+      if product == "chrome": [PASS, NOTRUN]
+
+  [canceling the readable with an invalid code should be ignored]
+    expected:
+      if product == "chrome": [PASS, NOTRUN]
diff --git a/third_party/blink/web_tests/external/wpt/webvtt/rendering/cues-with-video/processing-model/audio_has_no_subtitles.html.ini b/third_party/blink/web_tests/external/wpt/webvtt/rendering/cues-with-video/processing-model/audio_has_no_subtitles.html.ini
deleted file mode 100644
index 6a131e9..0000000
--- a/third_party/blink/web_tests/external/wpt/webvtt/rendering/cues-with-video/processing-model/audio_has_no_subtitles.html.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-[audio_has_no_subtitles.html]
-  expected:
-    if product == "chrome": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/webxr/xrStationaryReferenceSpace_floorlevel_updates.https.html.ini b/third_party/blink/web_tests/external/wpt/webxr/xrStationaryReferenceSpace_floorlevel_updates.https.html.ini
index 288ec60..5011c0d 100644
--- a/third_party/blink/web_tests/external/wpt/webxr/xrStationaryReferenceSpace_floorlevel_updates.https.html.ini
+++ b/third_party/blink/web_tests/external/wpt/webxr/xrStationaryReferenceSpace_floorlevel_updates.https.html.ini
@@ -2,3 +2,11 @@
   ['floor-level' XRStationaryReferenceSpace updates properly when the transform changes for immersive sessions - webgl]
     expected:
       if product == "chrome": FAIL
+
+  ['floor-level' XRStationaryReferenceSpace updates properly when the transform changes for non-immersive sessions - webgl2]
+    expected:
+      if product == "chrome": [PASS, FAIL]
+
+  ['floor-level' XRStationaryReferenceSpace updates properly when the transform changes for non-immersive sessions - webgl]
+    expected:
+      if product == "chrome": [PASS, FAIL]
diff --git a/third_party/blink/web_tests/external/wpt/window-placement/fullscreen-companion-window-manual.tentative.https.html b/third_party/blink/web_tests/external/wpt/window-placement/fullscreen-companion-window-manual.tentative.https.html
index 07f768384..10f30a19 100644
--- a/third_party/blink/web_tests/external/wpt/window-placement/fullscreen-companion-window-manual.tentative.https.html
+++ b/third_party/blink/web_tests/external/wpt/window-placement/fullscreen-companion-window-manual.tentative.https.html
@@ -43,8 +43,13 @@
     return;
   assert_equals(!popup.closed, expectPopup, 'Popup open');
   popups.push(popup);
-  if (autoCleanUp.checked)
-    popupTest.add_cleanup(popup.close);
+  if (autoCleanUp.checked) {
+    // TODO(crbug.com/1338645): Remove this workaround (delay) after browser code is
+    // fixed.
+    popupTest.add_cleanup(()=>{
+      setTimeout(popup.close, 1000);
+    });
+  }
 }
 
 promise_test(async setUpTest => {
diff --git a/third_party/blink/web_tests/external/wpt/workers/dedicated-worker-from-blob-url.window.js.ini b/third_party/blink/web_tests/external/wpt/workers/dedicated-worker-from-blob-url.window.js.ini
index 282999e..b6f8e24 100644
--- a/third_party/blink/web_tests/external/wpt/workers/dedicated-worker-from-blob-url.window.js.ini
+++ b/third_party/blink/web_tests/external/wpt/workers/dedicated-worker-from-blob-url.window.js.ini
@@ -1,3 +1,4 @@
 [dedicated-worker-from-blob-url.window.html]
   expected:
+    if (flag_specific == "") and (os == "win"): [OK, TIMEOUT]
     if flag_specific == "disable-layout-ng": TIMEOUT
diff --git a/third_party/blink/web_tests/external/wpt/workers/interfaces/WorkerUtils/importScripts/report-error-setTimeout-same-origin.sub.any.js.ini b/third_party/blink/web_tests/external/wpt/workers/interfaces/WorkerUtils/importScripts/report-error-setTimeout-same-origin.sub.any.js.ini
index c377da75..19de124d 100644
--- a/third_party/blink/web_tests/external/wpt/workers/interfaces/WorkerUtils/importScripts/report-error-setTimeout-same-origin.sub.any.js.ini
+++ b/third_party/blink/web_tests/external/wpt/workers/interfaces/WorkerUtils/importScripts/report-error-setTimeout-same-origin.sub.any.js.ini
@@ -2,4 +2,4 @@
 
 [report-error-setTimeout-same-origin.sub.any.worker.html]
   expected:
-    if product == "chrome": [OK, ERROR]
+    if product == "chrome": ERROR
diff --git a/third_party/blink/web_tests/external/wpt/workers/semantics/reporting-errors/001.html.ini b/third_party/blink/web_tests/external/wpt/workers/semantics/reporting-errors/001.html.ini
new file mode 100644
index 0000000..6dd41a7
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/workers/semantics/reporting-errors/001.html.ini
@@ -0,0 +1,3 @@
+[001.html]
+  expected:
+    if (flag_specific == "") and (os == "linux") and (product == "content_shell"): [OK, TIMEOUT]
diff --git a/third_party/blink/web_tests/external/wpt/workers/semantics/structured-clone/shared.html.ini b/third_party/blink/web_tests/external/wpt/workers/semantics/structured-clone/shared.html.ini
index a499611..de991947 100644
--- a/third_party/blink/web_tests/external/wpt/workers/semantics/structured-clone/shared.html.ini
+++ b/third_party/blink/web_tests/external/wpt/workers/semantics/structured-clone/shared.html.ini
@@ -1,3 +1,3 @@
 [shared.html]
   expected:
-    if flag_specific == "disable-site-isolation-trials": [OK, TIMEOUT]
+    if flag_specific == "disable-site-isolation-trials": TIMEOUT
diff --git a/third_party/blink/web_tests/external/wpt/worklets/animation-worklet-import.https.html.ini b/third_party/blink/web_tests/external/wpt/worklets/animation-worklet-import.https.html.ini
deleted file mode 100644
index 0b70485..0000000
--- a/third_party/blink/web_tests/external/wpt/worklets/animation-worklet-import.https.html.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-[animation-worklet-import.https.html]
-  expected:
-    if (flag_specific == "") and (os == "linux") and (product == "content_shell"): TIMEOUT
diff --git a/third_party/blink/web_tests/external/wpt/xhr/abort-after-send.any.js.ini b/third_party/blink/web_tests/external/wpt/xhr/abort-after-send.any.js.ini
index a0be8075..e3c68023 100644
--- a/third_party/blink/web_tests/external/wpt/xhr/abort-after-send.any.js.ini
+++ b/third_party/blink/web_tests/external/wpt/xhr/abort-after-send.any.js.ini
@@ -1,4 +1,6 @@
 [abort-after-send.any.html]
+  expected:
+    if (flag_specific == "") and (os == "linux") and (product == "content_shell"): [OK, TIMEOUT]
   [XMLHttpRequest: abort() after send()]
     expected: FAIL
 
diff --git a/third_party/blink/web_tests/external/wpt/xhr/formdata/set-blob.any.js.ini b/third_party/blink/web_tests/external/wpt/xhr/formdata/set-blob.any.js.ini
index 94d35ed..f188fd27 100644
--- a/third_party/blink/web_tests/external/wpt/xhr/formdata/set-blob.any.js.ini
+++ b/third_party/blink/web_tests/external/wpt/xhr/formdata/set-blob.any.js.ini
@@ -4,7 +4,5 @@
 
 
 [set-blob.any.worker.html]
-  expected:
-    if (flag_specific == "") and (product == "content_shell"): TIMEOUT
   [blob without type]
     expected: FAIL
diff --git a/third_party/blink/web_tests/fast/url/idna2003-expected.txt b/third_party/blink/web_tests/fast/url/idna2003-expected.txt
index 428ae58..b18baec 100644
--- a/third_party/blink/web_tests/fast/url/idna2003-expected.txt
+++ b/third_party/blink/web_tests/fast/url/idna2003-expected.txt
@@ -3,10 +3,10 @@
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
 
 The PASS/FAIL results of this test are set to the behavior in IDNA2003.
-PASS canonicalize('http://faß.de/') is 'http://fass.de/'
-PASS canonicalize('http://βόλος.com/') is 'http://xn--nxasmq6b.com/'
-PASS canonicalize('http://ශ්‍රී.com/') is 'http://xn--10cl1a0b.com/'
-PASS canonicalize('http://نامه‌ای.com/') is 'http://xn--mgba3gch31f.com/'
+FAIL canonicalize('http://faß.de/') should be http://fass.de/. Was http://xn--fa-hia.de/.
+FAIL canonicalize('http://βόλος.com/') should be http://xn--nxasmq6b.com/. Was http://xn--nxasmm1c.com/.
+FAIL canonicalize('http://ශ්‍රී.com/') should be http://xn--10cl1a0b.com/. Was http://xn--10cl1a0b660p.com/.
+FAIL canonicalize('http://نامه‌ای.com/') should be http://xn--mgba3gch31f.com/. Was http://xn--mgba3gch31f060k.com/.
 PASS canonicalize('http://www.looĸout.net/') is 'http://www.xn--looout-5bb.net/'
 PASS canonicalize('http://ᗯᗯᗯ.lookout.net/') is 'http://xn--1qeaa.lookout.net/'
 PASS canonicalize('http://www.lookout.сом/') is 'http://www.lookout.xn--l1adi/'
diff --git a/third_party/blink/web_tests/fast/url/idna2008-expected.txt b/third_party/blink/web_tests/fast/url/idna2008-expected.txt
index 5af555b0..33d4bcd2 100644
--- a/third_party/blink/web_tests/fast/url/idna2008-expected.txt
+++ b/third_party/blink/web_tests/fast/url/idna2008-expected.txt
@@ -4,10 +4,10 @@
 
 The PASS/FAIL results of this test are set to the behavior in IDNA2008.
 PASS canonicalize('http://Bücher.de/') is 'http://xn--bcher-kva.de/'
-FAIL canonicalize('http://faß.de/') should be http://xn--fa-hia.de/. Was http://fass.de/.
-FAIL canonicalize('http://βόλος.com/') should be http://xn--nxasmm1c.com/. Was http://xn--nxasmq6b.com/.
-FAIL canonicalize('http://ශ්‍රී.com/') should be http://xn--10cl1a0b660p.com/. Was http://xn--10cl1a0b.com/.
-FAIL canonicalize('http://نامه‌ای.com/') should be http://xn--mgba3gch31f060k.com/. Was http://xn--mgba3gch31f.com/.
+PASS canonicalize('http://faß.de/') is 'http://xn--fa-hia.de/'
+PASS canonicalize('http://βόλος.com/') is 'http://xn--nxasmm1c.com/'
+PASS canonicalize('http://ශ්‍රී.com/') is 'http://xn--10cl1a0b660p.com/'
+PASS canonicalize('http://نامه‌ای.com/') is 'http://xn--mgba3gch31f060k.com/'
 FAIL canonicalize('http://♥.net/') should be http://�.net/. Was http://xn--g6h.net/.
 FAIL canonicalize('http://͸.net/') should be http://�.net/. Was http://%CD%B8.net/.
 FAIL canonicalize('http://Ӏ.com/') should be http://�.com/. Was http://%D3%80.com/.
diff --git a/third_party/blink/web_tests/flag-specific/disable-layout-ng/external/wpt/html/semantics/forms/the-selectmenu-element/selectmenu-many-options.tentative-expected.txt b/third_party/blink/web_tests/flag-specific/disable-layout-ng/external/wpt/html/semantics/forms/the-selectmenu-element/selectmenu-many-options.tentative-expected.txt
new file mode 100644
index 0000000..522e2c46
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/disable-layout-ng/external/wpt/html/semantics/forms/the-selectmenu-element/selectmenu-many-options.tentative-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL The popover should be bottom left positioned assert_equals: expected 36 but got 20
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/flag-specific/disable-layout-ng/external/wpt/html/semantics/forms/the-selectmenu-element/selectmenu-popup-position-with-zoom.tentative-expected.txt b/third_party/blink/web_tests/flag-specific/disable-layout-ng/external/wpt/html/semantics/forms/the-selectmenu-element/selectmenu-popup-position-with-zoom.tentative-expected.txt
new file mode 100644
index 0000000..3577e72
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/disable-layout-ng/external/wpt/html/semantics/forms/the-selectmenu-element/selectmenu-popup-position-with-zoom.tentative-expected.txt
@@ -0,0 +1,7 @@
+This is a testharness.js-based test.
+PASS The popover should be bottom left positioned
+FAIL The popover should be top left positioned assert_equals: expected 0 but got 216
+FAIL The popover should be bottom right positioned assert_equals: expected 0 but got 7
+FAIL The popover should be top right positioned assert_equals: expected 0 but got 17
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/flag-specific/disable-layout-ng/external/wpt/html/semantics/forms/the-selectmenu-element/selectmenu-popup-position.tentative-expected.txt b/third_party/blink/web_tests/flag-specific/disable-layout-ng/external/wpt/html/semantics/forms/the-selectmenu-element/selectmenu-popup-position.tentative-expected.txt
new file mode 100644
index 0000000..378967e2
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/disable-layout-ng/external/wpt/html/semantics/forms/the-selectmenu-element/selectmenu-popup-position.tentative-expected.txt
@@ -0,0 +1,7 @@
+This is a testharness.js-based test.
+FAIL The popover should be bottom left positioned assert_equals: expected 0 but got 16
+FAIL The popover should be top left positioned assert_equals: expected 0 but got 467
+FAIL The popover should be bottom right positioned assert_equals: expected 0 but got 16
+FAIL The popover should be top right positioned assert_equals: expected 0 but got 467
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/html/dialog/close-multiple-dialogs-usecounter.html b/third_party/blink/web_tests/html/dialog/close-multiple-dialogs-usecounter.html
new file mode 100644
index 0000000..42840099
--- /dev/null
+++ b/third_party/blink/web_tests/html/dialog/close-multiple-dialogs-usecounter.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://bugs.chromium.org/p/chromium/issues/detail?id=1383001">
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+
+<dialog id=one></dialog>
+<dialog id=two></dialog>
+
+<script>
+test(() => {
+  one.showModal();
+  two.showModal();
+  eventSender.keyDown('Escape');
+  assert_true(internals.isUseCounted(document, 4424));
+});
+</script>
diff --git a/third_party/blink/web_tests/html/dialog/closewatcher-skip-cancel-usecounter.html b/third_party/blink/web_tests/html/dialog/closewatcher-skip-cancel-usecounter.html
new file mode 100644
index 0000000..0dba38d9
--- /dev/null
+++ b/third_party/blink/web_tests/html/dialog/closewatcher-skip-cancel-usecounter.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://bugs.chromium.org/p/chromium/issues/detail?id=1383001">
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+
+<dialog id=dialog></dialog>
+
+<script>
+test(() => {
+  dialog.showModal();
+  eventSender.keyDown('Escape');
+  assert_false(internals.isUseCounted(document, 4422));
+  assert_false(internals.isUseCounted(document, 4423));
+
+  dialog.showModal();
+  dialog.addEventListener('cancel', () => console.log('hello world'));
+  eventSender.keyDown('Escape');
+  assert_true(internals.isUseCounted(document, 4422));
+  assert_false(internals.isUseCounted(document, 4423));
+
+  dialog.showModal();
+  dialog.addEventListener('cancel', event => event.preventDefault());
+  eventSender.keyDown('Escape');
+  assert_true(internals.isUseCounted(document, 4422));
+  assert_true(internals.isUseCounted(document, 4423));
+});
+</script>
diff --git a/third_party/blink/web_tests/html/dialog/closewatcher-user-activation-usecounter.html b/third_party/blink/web_tests/html/dialog/closewatcher-user-activation-usecounter.html
new file mode 100644
index 0000000..6a1d7a0
--- /dev/null
+++ b/third_party/blink/web_tests/html/dialog/closewatcher-user-activation-usecounter.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://bugs.chromium.org/p/chromium/issues/detail?id=1383001">
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<script src="../../resources/testdriver.js"></script>
+<script src="../../resources/testdriver-actions.js"></script>
+<script src="../../resources/testdriver-vendor.js"></script>
+
+<dialog id=dialog>
+  <button id=button>user activation button</button>
+</dialog>
+
+<script>
+promise_test(async () => {
+  dialog.showModal();
+  await test_driver.click(button);
+  eventSender.keyDown('Escape');
+  assert_false(internals.isUseCounted(document, 4422));
+  assert_false(internals.isUseCounted(document, 4423));
+
+  dialog.showModal();
+  dialog.addEventListener('cancel', () => console.log('hello world'));
+  await test_driver.click(button);
+  eventSender.keyDown('Escape');
+  assert_false(internals.isUseCounted(document, 4422));
+  assert_false(internals.isUseCounted(document, 4423));
+
+  dialog.showModal();
+  dialog.addEventListener('cancel', event => event.preventDefault());
+  await test_driver.click(button);
+  eventSender.keyDown('Escape');
+  assert_false(internals.isUseCounted(document, 4422));
+  assert_false(internals.isUseCounted(document, 4423));
+});
+</script>
diff --git a/third_party/blink/web_tests/platform/linux/external/wpt/url/a-element-expected.txt b/third_party/blink/web_tests/platform/linux/external/wpt/url/a-element-expected.txt
index 413a284..88efd0c 100644
--- a/third_party/blink/web_tests/platform/linux/external/wpt/url/a-element-expected.txt
+++ b/third_party/blink/web_tests/platform/linux/external/wpt/url/a-element-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 736 tests; 418 PASS, 318 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 736 tests; 419 PASS, 317 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Loading data…
 PASS Parsing: <http://example	.
 org> against <http://example.org/foo/bar>
@@ -287,7 +287,7 @@
 FAIL Parsing: <http://%00.com> against <http://other.com/> assert_equals: failure should set href to input expected "http://%00.com" but got "http://%00.com/"
 FAIL Parsing: <http://%ef%bc%85%ef%bc%90%ef%bc%90.com> against <http://other.com/> assert_equals: failure should set href to input expected "http://%ef%bc%85%ef%bc%90%ef%bc%90.com" but got "http://%00.com/"
 PASS Parsing: <http://你好你好> against <http://other.com/>
-FAIL Parsing: <https://faß.ExAmPlE/> against <about:blank> assert_equals: href expected "https://xn--fa-hia.example/" but got "https://fass.example/"
+PASS Parsing: <https://faß.ExAmPlE/> against <about:blank>
 FAIL Parsing: <sc://faß.ExAmPlE/> against <about:blank> assert_equals: host expected "fa%C3%9F.ExAmPlE" but got ""
 FAIL Parsing: <http://%zz%66%a.com> against <http://other.com/> assert_equals: failure should set href to input expected "http://%zz%66%a.com" but got "http://%25zzf%25a.com/"
 FAIL Parsing: <http://%25> against <http://other.com/> assert_equals: failure should set href to input expected "http://%25" but got "http://%25/"
diff --git a/third_party/blink/web_tests/platform/linux/external/wpt/url/a-element-origin-expected.txt b/third_party/blink/web_tests/platform/linux/external/wpt/url/a-element-origin-expected.txt
index 616f292..fb60c03b 100644
--- a/third_party/blink/web_tests/platform/linux/external/wpt/url/a-element-origin-expected.txt
+++ b/third_party/blink/web_tests/platform/linux/external/wpt/url/a-element-origin-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 329 tests; 324 PASS, 5 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 329 tests; 325 PASS, 4 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Loading data…
 PASS Parsing origin: <http://example	.
 org> against <http://example.org/foo/bar>
@@ -214,7 +214,7 @@
 PASS Parsing origin: <https://x/�?�#�> against <about:blank>
 PASS Parsing origin: <http://Go.com> against <http://other.com/>
 PASS Parsing origin: <http://你好你好> against <http://other.com/>
-FAIL Parsing origin: <https://faß.ExAmPlE/> against <about:blank> assert_equals: origin expected "https://xn--fa-hia.example" but got "https://fass.example"
+PASS Parsing origin: <https://faß.ExAmPlE/> against <about:blank>
 PASS Parsing origin: <sc://faß.ExAmPlE/> against <about:blank>
 PASS Parsing origin: <http://%30%78%63%30%2e%30%32%35%30.01> against <http://other.com/>
 PASS Parsing origin: <http://%30%78%63%30%2e%30%32%35%30.01%2e> against <http://other.com/>
diff --git a/third_party/blink/web_tests/platform/linux/external/wpt/url/a-element-origin-xhtml-expected.txt b/third_party/blink/web_tests/platform/linux/external/wpt/url/a-element-origin-xhtml-expected.txt
index 616f292..fb60c03b 100644
--- a/third_party/blink/web_tests/platform/linux/external/wpt/url/a-element-origin-xhtml-expected.txt
+++ b/third_party/blink/web_tests/platform/linux/external/wpt/url/a-element-origin-xhtml-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 329 tests; 324 PASS, 5 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 329 tests; 325 PASS, 4 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Loading data…
 PASS Parsing origin: <http://example	.
 org> against <http://example.org/foo/bar>
@@ -214,7 +214,7 @@
 PASS Parsing origin: <https://x/�?�#�> against <about:blank>
 PASS Parsing origin: <http://Go.com> against <http://other.com/>
 PASS Parsing origin: <http://你好你好> against <http://other.com/>
-FAIL Parsing origin: <https://faß.ExAmPlE/> against <about:blank> assert_equals: origin expected "https://xn--fa-hia.example" but got "https://fass.example"
+PASS Parsing origin: <https://faß.ExAmPlE/> against <about:blank>
 PASS Parsing origin: <sc://faß.ExAmPlE/> against <about:blank>
 PASS Parsing origin: <http://%30%78%63%30%2e%30%32%35%30.01> against <http://other.com/>
 PASS Parsing origin: <http://%30%78%63%30%2e%30%32%35%30.01%2e> against <http://other.com/>
diff --git a/third_party/blink/web_tests/platform/linux/external/wpt/url/a-element-xhtml-expected.txt b/third_party/blink/web_tests/platform/linux/external/wpt/url/a-element-xhtml-expected.txt
index 413a284..88efd0c 100644
--- a/third_party/blink/web_tests/platform/linux/external/wpt/url/a-element-xhtml-expected.txt
+++ b/third_party/blink/web_tests/platform/linux/external/wpt/url/a-element-xhtml-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 736 tests; 418 PASS, 318 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 736 tests; 419 PASS, 317 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Loading data…
 PASS Parsing: <http://example	.
 org> against <http://example.org/foo/bar>
@@ -287,7 +287,7 @@
 FAIL Parsing: <http://%00.com> against <http://other.com/> assert_equals: failure should set href to input expected "http://%00.com" but got "http://%00.com/"
 FAIL Parsing: <http://%ef%bc%85%ef%bc%90%ef%bc%90.com> against <http://other.com/> assert_equals: failure should set href to input expected "http://%ef%bc%85%ef%bc%90%ef%bc%90.com" but got "http://%00.com/"
 PASS Parsing: <http://你好你好> against <http://other.com/>
-FAIL Parsing: <https://faß.ExAmPlE/> against <about:blank> assert_equals: href expected "https://xn--fa-hia.example/" but got "https://fass.example/"
+PASS Parsing: <https://faß.ExAmPlE/> against <about:blank>
 FAIL Parsing: <sc://faß.ExAmPlE/> against <about:blank> assert_equals: host expected "fa%C3%9F.ExAmPlE" but got ""
 FAIL Parsing: <http://%zz%66%a.com> against <http://other.com/> assert_equals: failure should set href to input expected "http://%zz%66%a.com" but got "http://%25zzf%25a.com/"
 FAIL Parsing: <http://%25> against <http://other.com/> assert_equals: failure should set href to input expected "http://%25" but got "http://%25/"
diff --git a/third_party/blink/web_tests/platform/linux/external/wpt/url/url-constructor.any-expected.txt b/third_party/blink/web_tests/platform/linux/external/wpt/url/url-constructor.any-expected.txt
index 5581385..88e3db6b 100644
--- a/third_party/blink/web_tests/platform/linux/external/wpt/url/url-constructor.any-expected.txt
+++ b/third_party/blink/web_tests/platform/linux/external/wpt/url/url-constructor.any-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 738 tests; 551 PASS, 187 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 738 tests; 552 PASS, 186 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Loading data…
 PASS Parsing: <http://example	.
 org> against <http://example.org/foo/bar>
@@ -311,7 +311,7 @@
 PASS Parsing: <http://%00.com> against <http://other.com/>
 PASS Parsing: <http://%ef%bc%85%ef%bc%90%ef%bc%90.com> against <http://other.com/>
 PASS Parsing: <http://你好你好> against <http://other.com/>
-FAIL Parsing: <https://faß.ExAmPlE/> against <about:blank> assert_equals: href expected "https://xn--fa-hia.example/" but got "https://fass.example/"
+PASS Parsing: <https://faß.ExAmPlE/> against <about:blank>
 FAIL Parsing: <sc://faß.ExAmPlE/> against <about:blank> assert_equals: host expected "fa%C3%9F.ExAmPlE" but got ""
 PASS Parsing: <http://%zz%66%a.com> against <http://other.com/>
 PASS Parsing: <http://%25> against <http://other.com/>
diff --git a/third_party/blink/web_tests/platform/linux/external/wpt/url/url-constructor.any.worker-expected.txt b/third_party/blink/web_tests/platform/linux/external/wpt/url/url-constructor.any.worker-expected.txt
index 5581385..88e3db6b 100644
--- a/third_party/blink/web_tests/platform/linux/external/wpt/url/url-constructor.any.worker-expected.txt
+++ b/third_party/blink/web_tests/platform/linux/external/wpt/url/url-constructor.any.worker-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 738 tests; 551 PASS, 187 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 738 tests; 552 PASS, 186 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Loading data…
 PASS Parsing: <http://example	.
 org> against <http://example.org/foo/bar>
@@ -311,7 +311,7 @@
 PASS Parsing: <http://%00.com> against <http://other.com/>
 PASS Parsing: <http://%ef%bc%85%ef%bc%90%ef%bc%90.com> against <http://other.com/>
 PASS Parsing: <http://你好你好> against <http://other.com/>
-FAIL Parsing: <https://faß.ExAmPlE/> against <about:blank> assert_equals: href expected "https://xn--fa-hia.example/" but got "https://fass.example/"
+PASS Parsing: <https://faß.ExAmPlE/> against <about:blank>
 FAIL Parsing: <sc://faß.ExAmPlE/> against <about:blank> assert_equals: host expected "fa%C3%9F.ExAmPlE" but got ""
 PASS Parsing: <http://%zz%66%a.com> against <http://other.com/>
 PASS Parsing: <http://%25> against <http://other.com/>
diff --git a/third_party/blink/web_tests/platform/linux/external/wpt/url/url-origin.any-expected.txt b/third_party/blink/web_tests/platform/linux/external/wpt/url/url-origin.any-expected.txt
index 43b0fd7..c8edf0f 100644
--- a/third_party/blink/web_tests/platform/linux/external/wpt/url/url-origin.any-expected.txt
+++ b/third_party/blink/web_tests/platform/linux/external/wpt/url/url-origin.any-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 329 tests; 321 PASS, 8 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 329 tests; 322 PASS, 7 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Loading data…
 PASS Origin parsing: <http://example	.
 org> against <http://example.org/foo/bar>
@@ -214,7 +214,7 @@
 PASS Origin parsing: <https://x/�?�#�> against <about:blank>
 PASS Origin parsing: <http://Go.com> against <http://other.com/>
 PASS Origin parsing: <http://你好你好> against <http://other.com/>
-FAIL Origin parsing: <https://faß.ExAmPlE/> against <about:blank> assert_equals: origin expected "https://xn--fa-hia.example" but got "https://fass.example"
+PASS Origin parsing: <https://faß.ExAmPlE/> against <about:blank>
 PASS Origin parsing: <sc://faß.ExAmPlE/> against <about:blank>
 PASS Origin parsing: <http://%30%78%63%30%2e%30%32%35%30.01> against <http://other.com/>
 PASS Origin parsing: <http://%30%78%63%30%2e%30%32%35%30.01%2e> against <http://other.com/>
diff --git a/third_party/blink/web_tests/platform/linux/external/wpt/url/url-origin.any.worker-expected.txt b/third_party/blink/web_tests/platform/linux/external/wpt/url/url-origin.any.worker-expected.txt
index 43b0fd7..c8edf0f 100644
--- a/third_party/blink/web_tests/platform/linux/external/wpt/url/url-origin.any.worker-expected.txt
+++ b/third_party/blink/web_tests/platform/linux/external/wpt/url/url-origin.any.worker-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 329 tests; 321 PASS, 8 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 329 tests; 322 PASS, 7 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Loading data…
 PASS Origin parsing: <http://example	.
 org> against <http://example.org/foo/bar>
@@ -214,7 +214,7 @@
 PASS Origin parsing: <https://x/�?�#�> against <about:blank>
 PASS Origin parsing: <http://Go.com> against <http://other.com/>
 PASS Origin parsing: <http://你好你好> against <http://other.com/>
-FAIL Origin parsing: <https://faß.ExAmPlE/> against <about:blank> assert_equals: origin expected "https://xn--fa-hia.example" but got "https://fass.example"
+PASS Origin parsing: <https://faß.ExAmPlE/> against <about:blank>
 PASS Origin parsing: <sc://faß.ExAmPlE/> against <about:blank>
 PASS Origin parsing: <http://%30%78%63%30%2e%30%32%35%30.01> against <http://other.com/>
 PASS Origin parsing: <http://%30%78%63%30%2e%30%32%35%30.01%2e> against <http://other.com/>
diff --git a/third_party/blink/web_tests/platform/linux/external/wpt/url/url-setters-a-area.window-expected.txt b/third_party/blink/web_tests/platform/linux/external/wpt/url/url-setters-a-area.window-expected.txt
index 9e5c14fe..2cd4f4f 100644
--- a/third_party/blink/web_tests/platform/linux/external/wpt/url/url-setters-a-area.window-expected.txt
+++ b/third_party/blink/web_tests/platform/linux/external/wpt/url/url-setters-a-area.window-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 435 tests; 265 PASS, 170 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 435 tests; 267 PASS, 168 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Loading data…
 PASS <a>: Setting <a://example.net>.protocol = '' The empty string is not a valid scheme. Setter leaves the URL unchanged.
 PASS <area>: Setting <a://example.net>.protocol = '' The empty string is not a valid scheme. Setter leaves the URL unchanged.
@@ -135,8 +135,8 @@
 FAIL <area>: Setting <sc://x/>.host = '@' assert_equals: expected "x" but got ""
 FAIL <a>: Setting <sc://x/>.host = 'ß' assert_equals: expected "sc://%C3%9F/" but got "sc://x/"
 FAIL <area>: Setting <sc://x/>.host = 'ß' assert_equals: expected "sc://%C3%9F/" but got "sc://x/"
-FAIL <a>: Setting <https://x/>.host = 'ß' IDNA Nontransitional_Processing assert_equals: expected "https://xn--zca/" but got "https://ss/"
-FAIL <area>: Setting <https://x/>.host = 'ß' IDNA Nontransitional_Processing assert_equals: expected "https://xn--zca/" but got "https://ss/"
+PASS <a>: Setting <https://x/>.host = 'ß' IDNA Nontransitional_Processing
+PASS <area>: Setting <https://x/>.host = 'ß' IDNA Nontransitional_Processing
 PASS <a>: Setting <mailto:me@example.net>.host = 'example.com' Cannot-be-a-base means no host
 PASS <area>: Setting <mailto:me@example.net>.host = 'example.com' Cannot-be-a-base means no host
 PASS <a>: Setting <data:text/plain,Stuff>.host = 'example.net' Cannot-be-a-base means no host
diff --git a/third_party/blink/web_tests/platform/linux/external/wpt/url/url-setters.any-expected.txt b/third_party/blink/web_tests/platform/linux/external/wpt/url/url-setters.any-expected.txt
index d783e19..68bf2c4e 100644
--- a/third_party/blink/web_tests/platform/linux/external/wpt/url/url-setters.any-expected.txt
+++ b/third_party/blink/web_tests/platform/linux/external/wpt/url/url-setters.any-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 218 tests; 135 PASS, 83 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 218 tests; 136 PASS, 82 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Loading data…
 PASS URL: Setting <a://example.net>.protocol = '' The empty string is not a valid scheme. Setter leaves the URL unchanged.
 PASS URL: Setting <a://example.net>.protocol = 'b'
@@ -68,7 +68,7 @@
 FAIL URL: Setting <sc://x/>.host = '?' assert_equals: expected "sc:///" but got "sc://x/"
 FAIL URL: Setting <sc://x/>.host = '@' assert_equals: expected "x" but got ""
 FAIL URL: Setting <sc://x/>.host = 'ß' assert_equals: expected "sc://%C3%9F/" but got "sc://x/"
-FAIL URL: Setting <https://x/>.host = 'ß' IDNA Nontransitional_Processing assert_equals: expected "https://xn--zca/" but got "https://ss/"
+PASS URL: Setting <https://x/>.host = 'ß' IDNA Nontransitional_Processing
 PASS URL: Setting <mailto:me@example.net>.host = 'example.com' Cannot-be-a-base means no host
 PASS URL: Setting <data:text/plain,Stuff>.host = 'example.net' Cannot-be-a-base means no host
 PASS URL: Setting <http://example.net>.host = 'example.com:8080'
diff --git a/third_party/blink/web_tests/platform/linux/external/wpt/url/url-setters.any.worker-expected.txt b/third_party/blink/web_tests/platform/linux/external/wpt/url/url-setters.any.worker-expected.txt
index d783e19..68bf2c4e 100644
--- a/third_party/blink/web_tests/platform/linux/external/wpt/url/url-setters.any.worker-expected.txt
+++ b/third_party/blink/web_tests/platform/linux/external/wpt/url/url-setters.any.worker-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 218 tests; 135 PASS, 83 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 218 tests; 136 PASS, 82 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Loading data…
 PASS URL: Setting <a://example.net>.protocol = '' The empty string is not a valid scheme. Setter leaves the URL unchanged.
 PASS URL: Setting <a://example.net>.protocol = 'b'
@@ -68,7 +68,7 @@
 FAIL URL: Setting <sc://x/>.host = '?' assert_equals: expected "sc:///" but got "sc://x/"
 FAIL URL: Setting <sc://x/>.host = '@' assert_equals: expected "x" but got ""
 FAIL URL: Setting <sc://x/>.host = 'ß' assert_equals: expected "sc://%C3%9F/" but got "sc://x/"
-FAIL URL: Setting <https://x/>.host = 'ß' IDNA Nontransitional_Processing assert_equals: expected "https://xn--zca/" but got "https://ss/"
+PASS URL: Setting <https://x/>.host = 'ß' IDNA Nontransitional_Processing
 PASS URL: Setting <mailto:me@example.net>.host = 'example.com' Cannot-be-a-base means no host
 PASS URL: Setting <data:text/plain,Stuff>.host = 'example.net' Cannot-be-a-base means no host
 PASS URL: Setting <http://example.net>.host = 'example.com:8080'
diff --git a/third_party/blink/web_tests/platform/mac/external/wpt/url/a-element-expected.txt b/third_party/blink/web_tests/platform/mac/external/wpt/url/a-element-expected.txt
index 413a284..88efd0c 100644
--- a/third_party/blink/web_tests/platform/mac/external/wpt/url/a-element-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/external/wpt/url/a-element-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 736 tests; 418 PASS, 318 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 736 tests; 419 PASS, 317 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Loading data…
 PASS Parsing: <http://example	.
 org> against <http://example.org/foo/bar>
@@ -287,7 +287,7 @@
 FAIL Parsing: <http://%00.com> against <http://other.com/> assert_equals: failure should set href to input expected "http://%00.com" but got "http://%00.com/"
 FAIL Parsing: <http://%ef%bc%85%ef%bc%90%ef%bc%90.com> against <http://other.com/> assert_equals: failure should set href to input expected "http://%ef%bc%85%ef%bc%90%ef%bc%90.com" but got "http://%00.com/"
 PASS Parsing: <http://你好你好> against <http://other.com/>
-FAIL Parsing: <https://faß.ExAmPlE/> against <about:blank> assert_equals: href expected "https://xn--fa-hia.example/" but got "https://fass.example/"
+PASS Parsing: <https://faß.ExAmPlE/> against <about:blank>
 FAIL Parsing: <sc://faß.ExAmPlE/> against <about:blank> assert_equals: host expected "fa%C3%9F.ExAmPlE" but got ""
 FAIL Parsing: <http://%zz%66%a.com> against <http://other.com/> assert_equals: failure should set href to input expected "http://%zz%66%a.com" but got "http://%25zzf%25a.com/"
 FAIL Parsing: <http://%25> against <http://other.com/> assert_equals: failure should set href to input expected "http://%25" but got "http://%25/"
diff --git a/third_party/blink/web_tests/platform/mac/external/wpt/url/a-element-origin-expected.txt b/third_party/blink/web_tests/platform/mac/external/wpt/url/a-element-origin-expected.txt
index 616f292..fb60c03b 100644
--- a/third_party/blink/web_tests/platform/mac/external/wpt/url/a-element-origin-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/external/wpt/url/a-element-origin-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 329 tests; 324 PASS, 5 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 329 tests; 325 PASS, 4 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Loading data…
 PASS Parsing origin: <http://example	.
 org> against <http://example.org/foo/bar>
@@ -214,7 +214,7 @@
 PASS Parsing origin: <https://x/�?�#�> against <about:blank>
 PASS Parsing origin: <http://Go.com> against <http://other.com/>
 PASS Parsing origin: <http://你好你好> against <http://other.com/>
-FAIL Parsing origin: <https://faß.ExAmPlE/> against <about:blank> assert_equals: origin expected "https://xn--fa-hia.example" but got "https://fass.example"
+PASS Parsing origin: <https://faß.ExAmPlE/> against <about:blank>
 PASS Parsing origin: <sc://faß.ExAmPlE/> against <about:blank>
 PASS Parsing origin: <http://%30%78%63%30%2e%30%32%35%30.01> against <http://other.com/>
 PASS Parsing origin: <http://%30%78%63%30%2e%30%32%35%30.01%2e> against <http://other.com/>
diff --git a/third_party/blink/web_tests/platform/mac/external/wpt/url/a-element-origin-xhtml-expected.txt b/third_party/blink/web_tests/platform/mac/external/wpt/url/a-element-origin-xhtml-expected.txt
index 616f292..fb60c03b 100644
--- a/third_party/blink/web_tests/platform/mac/external/wpt/url/a-element-origin-xhtml-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/external/wpt/url/a-element-origin-xhtml-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 329 tests; 324 PASS, 5 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 329 tests; 325 PASS, 4 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Loading data…
 PASS Parsing origin: <http://example	.
 org> against <http://example.org/foo/bar>
@@ -214,7 +214,7 @@
 PASS Parsing origin: <https://x/�?�#�> against <about:blank>
 PASS Parsing origin: <http://Go.com> against <http://other.com/>
 PASS Parsing origin: <http://你好你好> against <http://other.com/>
-FAIL Parsing origin: <https://faß.ExAmPlE/> against <about:blank> assert_equals: origin expected "https://xn--fa-hia.example" but got "https://fass.example"
+PASS Parsing origin: <https://faß.ExAmPlE/> against <about:blank>
 PASS Parsing origin: <sc://faß.ExAmPlE/> against <about:blank>
 PASS Parsing origin: <http://%30%78%63%30%2e%30%32%35%30.01> against <http://other.com/>
 PASS Parsing origin: <http://%30%78%63%30%2e%30%32%35%30.01%2e> against <http://other.com/>
diff --git a/third_party/blink/web_tests/platform/mac/external/wpt/url/a-element-xhtml-expected.txt b/third_party/blink/web_tests/platform/mac/external/wpt/url/a-element-xhtml-expected.txt
index 413a284..88efd0c 100644
--- a/third_party/blink/web_tests/platform/mac/external/wpt/url/a-element-xhtml-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/external/wpt/url/a-element-xhtml-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 736 tests; 418 PASS, 318 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 736 tests; 419 PASS, 317 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Loading data…
 PASS Parsing: <http://example	.
 org> against <http://example.org/foo/bar>
@@ -287,7 +287,7 @@
 FAIL Parsing: <http://%00.com> against <http://other.com/> assert_equals: failure should set href to input expected "http://%00.com" but got "http://%00.com/"
 FAIL Parsing: <http://%ef%bc%85%ef%bc%90%ef%bc%90.com> against <http://other.com/> assert_equals: failure should set href to input expected "http://%ef%bc%85%ef%bc%90%ef%bc%90.com" but got "http://%00.com/"
 PASS Parsing: <http://你好你好> against <http://other.com/>
-FAIL Parsing: <https://faß.ExAmPlE/> against <about:blank> assert_equals: href expected "https://xn--fa-hia.example/" but got "https://fass.example/"
+PASS Parsing: <https://faß.ExAmPlE/> against <about:blank>
 FAIL Parsing: <sc://faß.ExAmPlE/> against <about:blank> assert_equals: host expected "fa%C3%9F.ExAmPlE" but got ""
 FAIL Parsing: <http://%zz%66%a.com> against <http://other.com/> assert_equals: failure should set href to input expected "http://%zz%66%a.com" but got "http://%25zzf%25a.com/"
 FAIL Parsing: <http://%25> against <http://other.com/> assert_equals: failure should set href to input expected "http://%25" but got "http://%25/"
diff --git a/third_party/blink/web_tests/platform/mac/external/wpt/url/url-constructor.any-expected.txt b/third_party/blink/web_tests/platform/mac/external/wpt/url/url-constructor.any-expected.txt
index 5581385..88e3db6b 100644
--- a/third_party/blink/web_tests/platform/mac/external/wpt/url/url-constructor.any-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/external/wpt/url/url-constructor.any-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 738 tests; 551 PASS, 187 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 738 tests; 552 PASS, 186 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Loading data…
 PASS Parsing: <http://example	.
 org> against <http://example.org/foo/bar>
@@ -311,7 +311,7 @@
 PASS Parsing: <http://%00.com> against <http://other.com/>
 PASS Parsing: <http://%ef%bc%85%ef%bc%90%ef%bc%90.com> against <http://other.com/>
 PASS Parsing: <http://你好你好> against <http://other.com/>
-FAIL Parsing: <https://faß.ExAmPlE/> against <about:blank> assert_equals: href expected "https://xn--fa-hia.example/" but got "https://fass.example/"
+PASS Parsing: <https://faß.ExAmPlE/> against <about:blank>
 FAIL Parsing: <sc://faß.ExAmPlE/> against <about:blank> assert_equals: host expected "fa%C3%9F.ExAmPlE" but got ""
 PASS Parsing: <http://%zz%66%a.com> against <http://other.com/>
 PASS Parsing: <http://%25> against <http://other.com/>
diff --git a/third_party/blink/web_tests/platform/mac/external/wpt/url/url-constructor.any.worker-expected.txt b/third_party/blink/web_tests/platform/mac/external/wpt/url/url-constructor.any.worker-expected.txt
index 5581385..88e3db6b 100644
--- a/third_party/blink/web_tests/platform/mac/external/wpt/url/url-constructor.any.worker-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/external/wpt/url/url-constructor.any.worker-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 738 tests; 551 PASS, 187 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 738 tests; 552 PASS, 186 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Loading data…
 PASS Parsing: <http://example	.
 org> against <http://example.org/foo/bar>
@@ -311,7 +311,7 @@
 PASS Parsing: <http://%00.com> against <http://other.com/>
 PASS Parsing: <http://%ef%bc%85%ef%bc%90%ef%bc%90.com> against <http://other.com/>
 PASS Parsing: <http://你好你好> against <http://other.com/>
-FAIL Parsing: <https://faß.ExAmPlE/> against <about:blank> assert_equals: href expected "https://xn--fa-hia.example/" but got "https://fass.example/"
+PASS Parsing: <https://faß.ExAmPlE/> against <about:blank>
 FAIL Parsing: <sc://faß.ExAmPlE/> against <about:blank> assert_equals: host expected "fa%C3%9F.ExAmPlE" but got ""
 PASS Parsing: <http://%zz%66%a.com> against <http://other.com/>
 PASS Parsing: <http://%25> against <http://other.com/>
diff --git a/third_party/blink/web_tests/platform/mac/external/wpt/url/url-origin.any-expected.txt b/third_party/blink/web_tests/platform/mac/external/wpt/url/url-origin.any-expected.txt
index 43b0fd7..c8edf0f 100644
--- a/third_party/blink/web_tests/platform/mac/external/wpt/url/url-origin.any-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/external/wpt/url/url-origin.any-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 329 tests; 321 PASS, 8 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 329 tests; 322 PASS, 7 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Loading data…
 PASS Origin parsing: <http://example	.
 org> against <http://example.org/foo/bar>
@@ -214,7 +214,7 @@
 PASS Origin parsing: <https://x/�?�#�> against <about:blank>
 PASS Origin parsing: <http://Go.com> against <http://other.com/>
 PASS Origin parsing: <http://你好你好> against <http://other.com/>
-FAIL Origin parsing: <https://faß.ExAmPlE/> against <about:blank> assert_equals: origin expected "https://xn--fa-hia.example" but got "https://fass.example"
+PASS Origin parsing: <https://faß.ExAmPlE/> against <about:blank>
 PASS Origin parsing: <sc://faß.ExAmPlE/> against <about:blank>
 PASS Origin parsing: <http://%30%78%63%30%2e%30%32%35%30.01> against <http://other.com/>
 PASS Origin parsing: <http://%30%78%63%30%2e%30%32%35%30.01%2e> against <http://other.com/>
diff --git a/third_party/blink/web_tests/platform/mac/external/wpt/url/url-origin.any.worker-expected.txt b/third_party/blink/web_tests/platform/mac/external/wpt/url/url-origin.any.worker-expected.txt
index 43b0fd7..c8edf0f 100644
--- a/third_party/blink/web_tests/platform/mac/external/wpt/url/url-origin.any.worker-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/external/wpt/url/url-origin.any.worker-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 329 tests; 321 PASS, 8 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 329 tests; 322 PASS, 7 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Loading data…
 PASS Origin parsing: <http://example	.
 org> against <http://example.org/foo/bar>
@@ -214,7 +214,7 @@
 PASS Origin parsing: <https://x/�?�#�> against <about:blank>
 PASS Origin parsing: <http://Go.com> against <http://other.com/>
 PASS Origin parsing: <http://你好你好> against <http://other.com/>
-FAIL Origin parsing: <https://faß.ExAmPlE/> against <about:blank> assert_equals: origin expected "https://xn--fa-hia.example" but got "https://fass.example"
+PASS Origin parsing: <https://faß.ExAmPlE/> against <about:blank>
 PASS Origin parsing: <sc://faß.ExAmPlE/> against <about:blank>
 PASS Origin parsing: <http://%30%78%63%30%2e%30%32%35%30.01> against <http://other.com/>
 PASS Origin parsing: <http://%30%78%63%30%2e%30%32%35%30.01%2e> against <http://other.com/>
diff --git a/third_party/blink/web_tests/platform/mac/external/wpt/url/url-setters-a-area.window-expected.txt b/third_party/blink/web_tests/platform/mac/external/wpt/url/url-setters-a-area.window-expected.txt
index 9e5c14fe..2cd4f4f 100644
--- a/third_party/blink/web_tests/platform/mac/external/wpt/url/url-setters-a-area.window-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/external/wpt/url/url-setters-a-area.window-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 435 tests; 265 PASS, 170 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 435 tests; 267 PASS, 168 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Loading data…
 PASS <a>: Setting <a://example.net>.protocol = '' The empty string is not a valid scheme. Setter leaves the URL unchanged.
 PASS <area>: Setting <a://example.net>.protocol = '' The empty string is not a valid scheme. Setter leaves the URL unchanged.
@@ -135,8 +135,8 @@
 FAIL <area>: Setting <sc://x/>.host = '@' assert_equals: expected "x" but got ""
 FAIL <a>: Setting <sc://x/>.host = 'ß' assert_equals: expected "sc://%C3%9F/" but got "sc://x/"
 FAIL <area>: Setting <sc://x/>.host = 'ß' assert_equals: expected "sc://%C3%9F/" but got "sc://x/"
-FAIL <a>: Setting <https://x/>.host = 'ß' IDNA Nontransitional_Processing assert_equals: expected "https://xn--zca/" but got "https://ss/"
-FAIL <area>: Setting <https://x/>.host = 'ß' IDNA Nontransitional_Processing assert_equals: expected "https://xn--zca/" but got "https://ss/"
+PASS <a>: Setting <https://x/>.host = 'ß' IDNA Nontransitional_Processing
+PASS <area>: Setting <https://x/>.host = 'ß' IDNA Nontransitional_Processing
 PASS <a>: Setting <mailto:me@example.net>.host = 'example.com' Cannot-be-a-base means no host
 PASS <area>: Setting <mailto:me@example.net>.host = 'example.com' Cannot-be-a-base means no host
 PASS <a>: Setting <data:text/plain,Stuff>.host = 'example.net' Cannot-be-a-base means no host
diff --git a/third_party/blink/web_tests/platform/mac/external/wpt/url/url-setters.any-expected.txt b/third_party/blink/web_tests/platform/mac/external/wpt/url/url-setters.any-expected.txt
index d783e19..68bf2c4e 100644
--- a/third_party/blink/web_tests/platform/mac/external/wpt/url/url-setters.any-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/external/wpt/url/url-setters.any-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 218 tests; 135 PASS, 83 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 218 tests; 136 PASS, 82 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Loading data…
 PASS URL: Setting <a://example.net>.protocol = '' The empty string is not a valid scheme. Setter leaves the URL unchanged.
 PASS URL: Setting <a://example.net>.protocol = 'b'
@@ -68,7 +68,7 @@
 FAIL URL: Setting <sc://x/>.host = '?' assert_equals: expected "sc:///" but got "sc://x/"
 FAIL URL: Setting <sc://x/>.host = '@' assert_equals: expected "x" but got ""
 FAIL URL: Setting <sc://x/>.host = 'ß' assert_equals: expected "sc://%C3%9F/" but got "sc://x/"
-FAIL URL: Setting <https://x/>.host = 'ß' IDNA Nontransitional_Processing assert_equals: expected "https://xn--zca/" but got "https://ss/"
+PASS URL: Setting <https://x/>.host = 'ß' IDNA Nontransitional_Processing
 PASS URL: Setting <mailto:me@example.net>.host = 'example.com' Cannot-be-a-base means no host
 PASS URL: Setting <data:text/plain,Stuff>.host = 'example.net' Cannot-be-a-base means no host
 PASS URL: Setting <http://example.net>.host = 'example.com:8080'
diff --git a/third_party/blink/web_tests/platform/mac/external/wpt/url/url-setters.any.worker-expected.txt b/third_party/blink/web_tests/platform/mac/external/wpt/url/url-setters.any.worker-expected.txt
index d783e19..68bf2c4e 100644
--- a/third_party/blink/web_tests/platform/mac/external/wpt/url/url-setters.any.worker-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/external/wpt/url/url-setters.any.worker-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 218 tests; 135 PASS, 83 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 218 tests; 136 PASS, 82 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Loading data…
 PASS URL: Setting <a://example.net>.protocol = '' The empty string is not a valid scheme. Setter leaves the URL unchanged.
 PASS URL: Setting <a://example.net>.protocol = 'b'
@@ -68,7 +68,7 @@
 FAIL URL: Setting <sc://x/>.host = '?' assert_equals: expected "sc:///" but got "sc://x/"
 FAIL URL: Setting <sc://x/>.host = '@' assert_equals: expected "x" but got ""
 FAIL URL: Setting <sc://x/>.host = 'ß' assert_equals: expected "sc://%C3%9F/" but got "sc://x/"
-FAIL URL: Setting <https://x/>.host = 'ß' IDNA Nontransitional_Processing assert_equals: expected "https://xn--zca/" but got "https://ss/"
+PASS URL: Setting <https://x/>.host = 'ß' IDNA Nontransitional_Processing
 PASS URL: Setting <mailto:me@example.net>.host = 'example.com' Cannot-be-a-base means no host
 PASS URL: Setting <data:text/plain,Stuff>.host = 'example.net' Cannot-be-a-base means no host
 PASS URL: Setting <http://example.net>.host = 'example.com:8080'
diff --git a/third_party/blink/web_tests/platform/win/external/wpt/url/a-element-expected.txt b/third_party/blink/web_tests/platform/win/external/wpt/url/a-element-expected.txt
index be86047..ee3c058b 100644
--- a/third_party/blink/web_tests/platform/win/external/wpt/url/a-element-expected.txt
+++ b/third_party/blink/web_tests/platform/win/external/wpt/url/a-element-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 736 tests; 405 PASS, 331 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 736 tests; 406 PASS, 330 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Loading data…
 PASS Parsing: <http://example	.
 org> against <http://example.org/foo/bar>
@@ -287,7 +287,7 @@
 FAIL Parsing: <http://%00.com> against <http://other.com/> assert_equals: failure should set href to input expected "http://%00.com" but got "http://%00.com/"
 FAIL Parsing: <http://%ef%bc%85%ef%bc%90%ef%bc%90.com> against <http://other.com/> assert_equals: failure should set href to input expected "http://%ef%bc%85%ef%bc%90%ef%bc%90.com" but got "http://%00.com/"
 PASS Parsing: <http://你好你好> against <http://other.com/>
-FAIL Parsing: <https://faß.ExAmPlE/> against <about:blank> assert_equals: href expected "https://xn--fa-hia.example/" but got "https://fass.example/"
+PASS Parsing: <https://faß.ExAmPlE/> against <about:blank>
 FAIL Parsing: <sc://faß.ExAmPlE/> against <about:blank> assert_equals: host expected "fa%C3%9F.ExAmPlE" but got ""
 FAIL Parsing: <http://%zz%66%a.com> against <http://other.com/> assert_equals: failure should set href to input expected "http://%zz%66%a.com" but got "http://%25zzf%25a.com/"
 FAIL Parsing: <http://%25> against <http://other.com/> assert_equals: failure should set href to input expected "http://%25" but got "http://%25/"
diff --git a/third_party/blink/web_tests/platform/win/external/wpt/url/a-element-origin-expected.txt b/third_party/blink/web_tests/platform/win/external/wpt/url/a-element-origin-expected.txt
index 3546efd..4b87b439 100644
--- a/third_party/blink/web_tests/platform/win/external/wpt/url/a-element-origin-expected.txt
+++ b/third_party/blink/web_tests/platform/win/external/wpt/url/a-element-origin-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 329 tests; 321 PASS, 8 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 329 tests; 322 PASS, 7 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Loading data…
 PASS Parsing origin: <http://example	.
 org> against <http://example.org/foo/bar>
@@ -214,7 +214,7 @@
 PASS Parsing origin: <https://x/�?�#�> against <about:blank>
 PASS Parsing origin: <http://Go.com> against <http://other.com/>
 PASS Parsing origin: <http://你好你好> against <http://other.com/>
-FAIL Parsing origin: <https://faß.ExAmPlE/> against <about:blank> assert_equals: origin expected "https://xn--fa-hia.example" but got "https://fass.example"
+PASS Parsing origin: <https://faß.ExAmPlE/> against <about:blank>
 PASS Parsing origin: <sc://faß.ExAmPlE/> against <about:blank>
 PASS Parsing origin: <http://%30%78%63%30%2e%30%32%35%30.01> against <http://other.com/>
 PASS Parsing origin: <http://%30%78%63%30%2e%30%32%35%30.01%2e> against <http://other.com/>
diff --git a/third_party/blink/web_tests/platform/win/external/wpt/url/a-element-origin-xhtml-expected.txt b/third_party/blink/web_tests/platform/win/external/wpt/url/a-element-origin-xhtml-expected.txt
index 3546efd..4b87b439 100644
--- a/third_party/blink/web_tests/platform/win/external/wpt/url/a-element-origin-xhtml-expected.txt
+++ b/third_party/blink/web_tests/platform/win/external/wpt/url/a-element-origin-xhtml-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 329 tests; 321 PASS, 8 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 329 tests; 322 PASS, 7 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Loading data…
 PASS Parsing origin: <http://example	.
 org> against <http://example.org/foo/bar>
@@ -214,7 +214,7 @@
 PASS Parsing origin: <https://x/�?�#�> against <about:blank>
 PASS Parsing origin: <http://Go.com> against <http://other.com/>
 PASS Parsing origin: <http://你好你好> against <http://other.com/>
-FAIL Parsing origin: <https://faß.ExAmPlE/> against <about:blank> assert_equals: origin expected "https://xn--fa-hia.example" but got "https://fass.example"
+PASS Parsing origin: <https://faß.ExAmPlE/> against <about:blank>
 PASS Parsing origin: <sc://faß.ExAmPlE/> against <about:blank>
 PASS Parsing origin: <http://%30%78%63%30%2e%30%32%35%30.01> against <http://other.com/>
 PASS Parsing origin: <http://%30%78%63%30%2e%30%32%35%30.01%2e> against <http://other.com/>
diff --git a/third_party/blink/web_tests/platform/win/external/wpt/url/a-element-xhtml-expected.txt b/third_party/blink/web_tests/platform/win/external/wpt/url/a-element-xhtml-expected.txt
index be86047..ee3c058b 100644
--- a/third_party/blink/web_tests/platform/win/external/wpt/url/a-element-xhtml-expected.txt
+++ b/third_party/blink/web_tests/platform/win/external/wpt/url/a-element-xhtml-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 736 tests; 405 PASS, 331 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 736 tests; 406 PASS, 330 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Loading data…
 PASS Parsing: <http://example	.
 org> against <http://example.org/foo/bar>
@@ -287,7 +287,7 @@
 FAIL Parsing: <http://%00.com> against <http://other.com/> assert_equals: failure should set href to input expected "http://%00.com" but got "http://%00.com/"
 FAIL Parsing: <http://%ef%bc%85%ef%bc%90%ef%bc%90.com> against <http://other.com/> assert_equals: failure should set href to input expected "http://%ef%bc%85%ef%bc%90%ef%bc%90.com" but got "http://%00.com/"
 PASS Parsing: <http://你好你好> against <http://other.com/>
-FAIL Parsing: <https://faß.ExAmPlE/> against <about:blank> assert_equals: href expected "https://xn--fa-hia.example/" but got "https://fass.example/"
+PASS Parsing: <https://faß.ExAmPlE/> against <about:blank>
 FAIL Parsing: <sc://faß.ExAmPlE/> against <about:blank> assert_equals: host expected "fa%C3%9F.ExAmPlE" but got ""
 FAIL Parsing: <http://%zz%66%a.com> against <http://other.com/> assert_equals: failure should set href to input expected "http://%zz%66%a.com" but got "http://%25zzf%25a.com/"
 FAIL Parsing: <http://%25> against <http://other.com/> assert_equals: failure should set href to input expected "http://%25" but got "http://%25/"
diff --git a/third_party/blink/web_tests/platform/win/external/wpt/url/url-constructor.any-expected.txt b/third_party/blink/web_tests/platform/win/external/wpt/url/url-constructor.any-expected.txt
index 044d317d..d3f14b0 100644
--- a/third_party/blink/web_tests/platform/win/external/wpt/url/url-constructor.any-expected.txt
+++ b/third_party/blink/web_tests/platform/win/external/wpt/url/url-constructor.any-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 738 tests; 534 PASS, 204 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 738 tests; 535 PASS, 203 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Loading data…
 PASS Parsing: <http://example	.
 org> against <http://example.org/foo/bar>
@@ -311,7 +311,7 @@
 PASS Parsing: <http://%00.com> against <http://other.com/>
 PASS Parsing: <http://%ef%bc%85%ef%bc%90%ef%bc%90.com> against <http://other.com/>
 PASS Parsing: <http://你好你好> against <http://other.com/>
-FAIL Parsing: <https://faß.ExAmPlE/> against <about:blank> assert_equals: href expected "https://xn--fa-hia.example/" but got "https://fass.example/"
+PASS Parsing: <https://faß.ExAmPlE/> against <about:blank>
 FAIL Parsing: <sc://faß.ExAmPlE/> against <about:blank> assert_equals: host expected "fa%C3%9F.ExAmPlE" but got ""
 PASS Parsing: <http://%zz%66%a.com> against <http://other.com/>
 PASS Parsing: <http://%25> against <http://other.com/>
diff --git a/third_party/blink/web_tests/platform/win/external/wpt/url/url-constructor.any.worker-expected.txt b/third_party/blink/web_tests/platform/win/external/wpt/url/url-constructor.any.worker-expected.txt
index 044d317d..d3f14b0 100644
--- a/third_party/blink/web_tests/platform/win/external/wpt/url/url-constructor.any.worker-expected.txt
+++ b/third_party/blink/web_tests/platform/win/external/wpt/url/url-constructor.any.worker-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 738 tests; 534 PASS, 204 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 738 tests; 535 PASS, 203 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Loading data…
 PASS Parsing: <http://example	.
 org> against <http://example.org/foo/bar>
@@ -311,7 +311,7 @@
 PASS Parsing: <http://%00.com> against <http://other.com/>
 PASS Parsing: <http://%ef%bc%85%ef%bc%90%ef%bc%90.com> against <http://other.com/>
 PASS Parsing: <http://你好你好> against <http://other.com/>
-FAIL Parsing: <https://faß.ExAmPlE/> against <about:blank> assert_equals: href expected "https://xn--fa-hia.example/" but got "https://fass.example/"
+PASS Parsing: <https://faß.ExAmPlE/> against <about:blank>
 FAIL Parsing: <sc://faß.ExAmPlE/> against <about:blank> assert_equals: host expected "fa%C3%9F.ExAmPlE" but got ""
 PASS Parsing: <http://%zz%66%a.com> against <http://other.com/>
 PASS Parsing: <http://%25> against <http://other.com/>
diff --git a/third_party/blink/web_tests/platform/win/external/wpt/url/url-origin.any-expected.txt b/third_party/blink/web_tests/platform/win/external/wpt/url/url-origin.any-expected.txt
index ee24f2a..a78f7ce 100644
--- a/third_party/blink/web_tests/platform/win/external/wpt/url/url-origin.any-expected.txt
+++ b/third_party/blink/web_tests/platform/win/external/wpt/url/url-origin.any-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 329 tests; 318 PASS, 11 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 329 tests; 319 PASS, 10 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Loading data…
 PASS Origin parsing: <http://example	.
 org> against <http://example.org/foo/bar>
@@ -214,7 +214,7 @@
 PASS Origin parsing: <https://x/�?�#�> against <about:blank>
 PASS Origin parsing: <http://Go.com> against <http://other.com/>
 PASS Origin parsing: <http://你好你好> against <http://other.com/>
-FAIL Origin parsing: <https://faß.ExAmPlE/> against <about:blank> assert_equals: origin expected "https://xn--fa-hia.example" but got "https://fass.example"
+PASS Origin parsing: <https://faß.ExAmPlE/> against <about:blank>
 PASS Origin parsing: <sc://faß.ExAmPlE/> against <about:blank>
 PASS Origin parsing: <http://%30%78%63%30%2e%30%32%35%30.01> against <http://other.com/>
 PASS Origin parsing: <http://%30%78%63%30%2e%30%32%35%30.01%2e> against <http://other.com/>
diff --git a/third_party/blink/web_tests/platform/win/external/wpt/url/url-origin.any.worker-expected.txt b/third_party/blink/web_tests/platform/win/external/wpt/url/url-origin.any.worker-expected.txt
index ee24f2a..a78f7ce 100644
--- a/third_party/blink/web_tests/platform/win/external/wpt/url/url-origin.any.worker-expected.txt
+++ b/third_party/blink/web_tests/platform/win/external/wpt/url/url-origin.any.worker-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 329 tests; 318 PASS, 11 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 329 tests; 319 PASS, 10 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Loading data…
 PASS Origin parsing: <http://example	.
 org> against <http://example.org/foo/bar>
@@ -214,7 +214,7 @@
 PASS Origin parsing: <https://x/�?�#�> against <about:blank>
 PASS Origin parsing: <http://Go.com> against <http://other.com/>
 PASS Origin parsing: <http://你好你好> against <http://other.com/>
-FAIL Origin parsing: <https://faß.ExAmPlE/> against <about:blank> assert_equals: origin expected "https://xn--fa-hia.example" but got "https://fass.example"
+PASS Origin parsing: <https://faß.ExAmPlE/> against <about:blank>
 PASS Origin parsing: <sc://faß.ExAmPlE/> against <about:blank>
 PASS Origin parsing: <http://%30%78%63%30%2e%30%32%35%30.01> against <http://other.com/>
 PASS Origin parsing: <http://%30%78%63%30%2e%30%32%35%30.01%2e> against <http://other.com/>
diff --git a/third_party/blink/web_tests/platform/win/external/wpt/url/url-setters-a-area.window-expected.txt b/third_party/blink/web_tests/platform/win/external/wpt/url/url-setters-a-area.window-expected.txt
index 6763308..19a3a15 100644
--- a/third_party/blink/web_tests/platform/win/external/wpt/url/url-setters-a-area.window-expected.txt
+++ b/third_party/blink/web_tests/platform/win/external/wpt/url/url-setters-a-area.window-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 435 tests; 245 PASS, 190 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 435 tests; 247 PASS, 188 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Loading data…
 FAIL <a>: Setting <a://example.net>.protocol = '' The empty string is not a valid scheme. Setter leaves the URL unchanged. assert_equals: expected "a://example.net" but got "file:///A://example.net"
 FAIL <area>: Setting <a://example.net>.protocol = '' The empty string is not a valid scheme. Setter leaves the URL unchanged. assert_equals: expected "a://example.net" but got "file:///A://example.net"
@@ -135,8 +135,8 @@
 FAIL <area>: Setting <sc://x/>.host = '@' assert_equals: expected "x" but got ""
 FAIL <a>: Setting <sc://x/>.host = 'ß' assert_equals: expected "sc://%C3%9F/" but got "sc://x/"
 FAIL <area>: Setting <sc://x/>.host = 'ß' assert_equals: expected "sc://%C3%9F/" but got "sc://x/"
-FAIL <a>: Setting <https://x/>.host = 'ß' IDNA Nontransitional_Processing assert_equals: expected "https://xn--zca/" but got "https://ss/"
-FAIL <area>: Setting <https://x/>.host = 'ß' IDNA Nontransitional_Processing assert_equals: expected "https://xn--zca/" but got "https://ss/"
+PASS <a>: Setting <https://x/>.host = 'ß' IDNA Nontransitional_Processing
+PASS <area>: Setting <https://x/>.host = 'ß' IDNA Nontransitional_Processing
 PASS <a>: Setting <mailto:me@example.net>.host = 'example.com' Cannot-be-a-base means no host
 PASS <area>: Setting <mailto:me@example.net>.host = 'example.com' Cannot-be-a-base means no host
 PASS <a>: Setting <data:text/plain,Stuff>.host = 'example.net' Cannot-be-a-base means no host
diff --git a/third_party/blink/web_tests/platform/win/external/wpt/url/url-setters.any-expected.txt b/third_party/blink/web_tests/platform/win/external/wpt/url/url-setters.any-expected.txt
index 2a64886..f023759 100644
--- a/third_party/blink/web_tests/platform/win/external/wpt/url/url-setters.any-expected.txt
+++ b/third_party/blink/web_tests/platform/win/external/wpt/url/url-setters.any-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 218 tests; 125 PASS, 93 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 218 tests; 126 PASS, 92 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Loading data…
 FAIL URL: Setting <a://example.net>.protocol = '' The empty string is not a valid scheme. Setter leaves the URL unchanged. assert_equals: expected "a://example.net" but got "file:///A://example.net"
 FAIL URL: Setting <a://example.net>.protocol = 'b' assert_equals: expected "b://example.net" but got "file:///B:///A://example.net"
@@ -68,7 +68,7 @@
 FAIL URL: Setting <sc://x/>.host = '?' assert_equals: expected "sc:///" but got "sc://x/"
 FAIL URL: Setting <sc://x/>.host = '@' assert_equals: expected "x" but got ""
 FAIL URL: Setting <sc://x/>.host = 'ß' assert_equals: expected "sc://%C3%9F/" but got "sc://x/"
-FAIL URL: Setting <https://x/>.host = 'ß' IDNA Nontransitional_Processing assert_equals: expected "https://xn--zca/" but got "https://ss/"
+PASS URL: Setting <https://x/>.host = 'ß' IDNA Nontransitional_Processing
 PASS URL: Setting <mailto:me@example.net>.host = 'example.com' Cannot-be-a-base means no host
 PASS URL: Setting <data:text/plain,Stuff>.host = 'example.net' Cannot-be-a-base means no host
 PASS URL: Setting <http://example.net>.host = 'example.com:8080'
diff --git a/third_party/blink/web_tests/platform/win/external/wpt/url/url-setters.any.worker-expected.txt b/third_party/blink/web_tests/platform/win/external/wpt/url/url-setters.any.worker-expected.txt
index 2a64886..f023759 100644
--- a/third_party/blink/web_tests/platform/win/external/wpt/url/url-setters.any.worker-expected.txt
+++ b/third_party/blink/web_tests/platform/win/external/wpt/url/url-setters.any.worker-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 218 tests; 125 PASS, 93 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 218 tests; 126 PASS, 92 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Loading data…
 FAIL URL: Setting <a://example.net>.protocol = '' The empty string is not a valid scheme. Setter leaves the URL unchanged. assert_equals: expected "a://example.net" but got "file:///A://example.net"
 FAIL URL: Setting <a://example.net>.protocol = 'b' assert_equals: expected "b://example.net" but got "file:///B:///A://example.net"
@@ -68,7 +68,7 @@
 FAIL URL: Setting <sc://x/>.host = '?' assert_equals: expected "sc:///" but got "sc://x/"
 FAIL URL: Setting <sc://x/>.host = '@' assert_equals: expected "x" but got ""
 FAIL URL: Setting <sc://x/>.host = 'ß' assert_equals: expected "sc://%C3%9F/" but got "sc://x/"
-FAIL URL: Setting <https://x/>.host = 'ß' IDNA Nontransitional_Processing assert_equals: expected "https://xn--zca/" but got "https://ss/"
+PASS URL: Setting <https://x/>.host = 'ß' IDNA Nontransitional_Processing
 PASS URL: Setting <mailto:me@example.net>.host = 'example.com' Cannot-be-a-base means no host
 PASS URL: Setting <data:text/plain,Stuff>.host = 'example.net' Cannot-be-a-base means no host
 PASS URL: Setting <http://example.net>.host = 'example.com:8080'
diff --git a/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/a-element-expected.txt b/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/a-element-expected.txt
index 88efd0c..413a284 100644
--- a/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/a-element-expected.txt
+++ b/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/a-element-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 736 tests; 419 PASS, 317 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 736 tests; 418 PASS, 318 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Loading data…
 PASS Parsing: <http://example	.
 org> against <http://example.org/foo/bar>
@@ -287,7 +287,7 @@
 FAIL Parsing: <http://%00.com> against <http://other.com/> assert_equals: failure should set href to input expected "http://%00.com" but got "http://%00.com/"
 FAIL Parsing: <http://%ef%bc%85%ef%bc%90%ef%bc%90.com> against <http://other.com/> assert_equals: failure should set href to input expected "http://%ef%bc%85%ef%bc%90%ef%bc%90.com" but got "http://%00.com/"
 PASS Parsing: <http://你好你好> against <http://other.com/>
-PASS Parsing: <https://faß.ExAmPlE/> against <about:blank>
+FAIL Parsing: <https://faß.ExAmPlE/> against <about:blank> assert_equals: href expected "https://xn--fa-hia.example/" but got "https://fass.example/"
 FAIL Parsing: <sc://faß.ExAmPlE/> against <about:blank> assert_equals: host expected "fa%C3%9F.ExAmPlE" but got ""
 FAIL Parsing: <http://%zz%66%a.com> against <http://other.com/> assert_equals: failure should set href to input expected "http://%zz%66%a.com" but got "http://%25zzf%25a.com/"
 FAIL Parsing: <http://%25> against <http://other.com/> assert_equals: failure should set href to input expected "http://%25" but got "http://%25/"
diff --git a/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/a-element-origin-expected.txt b/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/a-element-origin-expected.txt
index fb60c03b..616f292 100644
--- a/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/a-element-origin-expected.txt
+++ b/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/a-element-origin-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 329 tests; 325 PASS, 4 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 329 tests; 324 PASS, 5 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Loading data…
 PASS Parsing origin: <http://example	.
 org> against <http://example.org/foo/bar>
@@ -214,7 +214,7 @@
 PASS Parsing origin: <https://x/�?�#�> against <about:blank>
 PASS Parsing origin: <http://Go.com> against <http://other.com/>
 PASS Parsing origin: <http://你好你好> against <http://other.com/>
-PASS Parsing origin: <https://faß.ExAmPlE/> against <about:blank>
+FAIL Parsing origin: <https://faß.ExAmPlE/> against <about:blank> assert_equals: origin expected "https://xn--fa-hia.example" but got "https://fass.example"
 PASS Parsing origin: <sc://faß.ExAmPlE/> against <about:blank>
 PASS Parsing origin: <http://%30%78%63%30%2e%30%32%35%30.01> against <http://other.com/>
 PASS Parsing origin: <http://%30%78%63%30%2e%30%32%35%30.01%2e> against <http://other.com/>
diff --git a/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/a-element-origin-xhtml-expected.txt b/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/a-element-origin-xhtml-expected.txt
index fb60c03b..616f292 100644
--- a/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/a-element-origin-xhtml-expected.txt
+++ b/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/a-element-origin-xhtml-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 329 tests; 325 PASS, 4 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 329 tests; 324 PASS, 5 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Loading data…
 PASS Parsing origin: <http://example	.
 org> against <http://example.org/foo/bar>
@@ -214,7 +214,7 @@
 PASS Parsing origin: <https://x/�?�#�> against <about:blank>
 PASS Parsing origin: <http://Go.com> against <http://other.com/>
 PASS Parsing origin: <http://你好你好> against <http://other.com/>
-PASS Parsing origin: <https://faß.ExAmPlE/> against <about:blank>
+FAIL Parsing origin: <https://faß.ExAmPlE/> against <about:blank> assert_equals: origin expected "https://xn--fa-hia.example" but got "https://fass.example"
 PASS Parsing origin: <sc://faß.ExAmPlE/> against <about:blank>
 PASS Parsing origin: <http://%30%78%63%30%2e%30%32%35%30.01> against <http://other.com/>
 PASS Parsing origin: <http://%30%78%63%30%2e%30%32%35%30.01%2e> against <http://other.com/>
diff --git a/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/a-element-xhtml-expected.txt b/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/a-element-xhtml-expected.txt
index 88efd0c..413a284 100644
--- a/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/a-element-xhtml-expected.txt
+++ b/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/a-element-xhtml-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 736 tests; 419 PASS, 317 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 736 tests; 418 PASS, 318 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Loading data…
 PASS Parsing: <http://example	.
 org> against <http://example.org/foo/bar>
@@ -287,7 +287,7 @@
 FAIL Parsing: <http://%00.com> against <http://other.com/> assert_equals: failure should set href to input expected "http://%00.com" but got "http://%00.com/"
 FAIL Parsing: <http://%ef%bc%85%ef%bc%90%ef%bc%90.com> against <http://other.com/> assert_equals: failure should set href to input expected "http://%ef%bc%85%ef%bc%90%ef%bc%90.com" but got "http://%00.com/"
 PASS Parsing: <http://你好你好> against <http://other.com/>
-PASS Parsing: <https://faß.ExAmPlE/> against <about:blank>
+FAIL Parsing: <https://faß.ExAmPlE/> against <about:blank> assert_equals: href expected "https://xn--fa-hia.example/" but got "https://fass.example/"
 FAIL Parsing: <sc://faß.ExAmPlE/> against <about:blank> assert_equals: host expected "fa%C3%9F.ExAmPlE" but got ""
 FAIL Parsing: <http://%zz%66%a.com> against <http://other.com/> assert_equals: failure should set href to input expected "http://%zz%66%a.com" but got "http://%25zzf%25a.com/"
 FAIL Parsing: <http://%25> against <http://other.com/> assert_equals: failure should set href to input expected "http://%25" but got "http://%25/"
diff --git a/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/toascii.window-expected.txt b/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/toascii.window-expected.txt
index 2ca7f866..9229523 100644
--- a/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/toascii.window-expected.txt
+++ b/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/toascii.window-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 352 tests; 279 PASS, 73 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 352 tests; 198 PASS, 154 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Loading data…
 PASS aa-- (using URL)
 PASS aa-- (using URL.host)
@@ -55,15 +55,15 @@
 PASS -x.xn--zca (using <area>)
 PASS -x.xn--zca (using <area>.host)
 PASS -x.xn--zca (using <area>.hostname)
-PASS -x.ß (using URL)
-PASS -x.ß (using URL.host)
-PASS -x.ß (using URL.hostname)
-PASS -x.ß (using <a>)
-PASS -x.ß (using <a>.host)
-PASS -x.ß (using <a>.hostname)
-PASS -x.ß (using <area>)
-PASS -x.ß (using <area>.host)
-PASS -x.ß (using <area>.hostname)
+FAIL -x.ß (using URL) assert_equals: expected "-x.xn--zca" but got "-x.ss"
+FAIL -x.ß (using URL.host) assert_equals: expected "-x.xn--zca" but got "-x.ss"
+FAIL -x.ß (using URL.hostname) assert_equals: expected "-x.xn--zca" but got "-x.ss"
+FAIL -x.ß (using <a>) assert_equals: expected "-x.xn--zca" but got "-x.ss"
+FAIL -x.ß (using <a>.host) assert_equals: expected "-x.xn--zca" but got "-x.ss"
+FAIL -x.ß (using <a>.hostname) assert_equals: expected "-x.xn--zca" but got "-x.ss"
+FAIL -x.ß (using <area>) assert_equals: expected "-x.xn--zca" but got "-x.ss"
+FAIL -x.ß (using <area>.host) assert_equals: expected "-x.xn--zca" but got "-x.ss"
+FAIL -x.ß (using <area>.hostname) assert_equals: expected "-x.xn--zca" but got "-x.ss"
 PASS x-.xn--zca (using URL)
 PASS x-.xn--zca (using URL.host)
 PASS x-.xn--zca (using URL.hostname)
@@ -73,15 +73,15 @@
 PASS x-.xn--zca (using <area>)
 PASS x-.xn--zca (using <area>.host)
 PASS x-.xn--zca (using <area>.hostname)
-PASS x-.ß (using URL)
-PASS x-.ß (using URL.host)
-PASS x-.ß (using URL.hostname)
-PASS x-.ß (using <a>)
-PASS x-.ß (using <a>.host)
-PASS x-.ß (using <a>.hostname)
-PASS x-.ß (using <area>)
-PASS x-.ß (using <area>.host)
-PASS x-.ß (using <area>.hostname)
+FAIL x-.ß (using URL) assert_equals: expected "x-.xn--zca" but got "x-.ss"
+FAIL x-.ß (using URL.host) assert_equals: expected "x-.xn--zca" but got "x-.ss"
+FAIL x-.ß (using URL.hostname) assert_equals: expected "x-.xn--zca" but got "x-.ss"
+FAIL x-.ß (using <a>) assert_equals: expected "x-.xn--zca" but got "x-.ss"
+FAIL x-.ß (using <a>.host) assert_equals: expected "x-.xn--zca" but got "x-.ss"
+FAIL x-.ß (using <a>.hostname) assert_equals: expected "x-.xn--zca" but got "x-.ss"
+FAIL x-.ß (using <area>) assert_equals: expected "x-.xn--zca" but got "x-.ss"
+FAIL x-.ß (using <area>.host) assert_equals: expected "x-.xn--zca" but got "x-.ss"
+FAIL x-.ß (using <area>.hostname) assert_equals: expected "x-.xn--zca" but got "x-.ss"
 PASS x..xn--zca (using URL)
 PASS x..xn--zca (using URL.host)
 PASS x..xn--zca (using URL.hostname)
@@ -91,15 +91,15 @@
 PASS x..xn--zca (using <area>)
 PASS x..xn--zca (using <area>.host)
 PASS x..xn--zca (using <area>.hostname)
-PASS x..ß (using URL)
-PASS x..ß (using URL.host)
-PASS x..ß (using URL.hostname)
-PASS x..ß (using <a>)
-PASS x..ß (using <a>.host)
-PASS x..ß (using <a>.hostname)
-PASS x..ß (using <area>)
-PASS x..ß (using <area>.host)
-PASS x..ß (using <area>.hostname)
+FAIL x..ß (using URL) assert_equals: expected "x..xn--zca" but got "x..ss"
+FAIL x..ß (using URL.host) assert_equals: expected "x..xn--zca" but got "x..ss"
+FAIL x..ß (using URL.hostname) assert_equals: expected "x..xn--zca" but got "x..ss"
+FAIL x..ß (using <a>) assert_equals: expected "x..xn--zca" but got "x..ss"
+FAIL x..ß (using <a>.host) assert_equals: expected "x..xn--zca" but got "x..ss"
+FAIL x..ß (using <a>.hostname) assert_equals: expected "x..xn--zca" but got "x..ss"
+FAIL x..ß (using <area>) assert_equals: expected "x..xn--zca" but got "x..ss"
+FAIL x..ß (using <area>.host) assert_equals: expected "x..xn--zca" but got "x..ss"
+FAIL x..ß (using <area>.hostname) assert_equals: expected "x..xn--zca" but got "x..ss"
 FAIL xn--a (using URL) assert_throws_js: function "() => makeURL("url", hostTest.input)" did not throw
 FAIL xn--a (using URL.host) assert_equals: expected "x" but got "xn--a"
 FAIL xn--a (using URL.hostname) assert_equals: expected "x" but got "xn--a"
@@ -145,15 +145,15 @@
 PASS xn--zca.xn--zca (using <area>)
 PASS xn--zca.xn--zca (using <area>.host)
 PASS xn--zca.xn--zca (using <area>.hostname)
-PASS xn--zca.ß (using URL)
-PASS xn--zca.ß (using URL.host)
-PASS xn--zca.ß (using URL.hostname)
-PASS xn--zca.ß (using <a>)
-PASS xn--zca.ß (using <a>.host)
-PASS xn--zca.ß (using <a>.hostname)
-PASS xn--zca.ß (using <area>)
-PASS xn--zca.ß (using <area>.host)
-PASS xn--zca.ß (using <area>.hostname)
+FAIL xn--zca.ß (using URL) assert_equals: expected "xn--zca.xn--zca" but got "xn--zca.ss"
+FAIL xn--zca.ß (using URL.host) assert_equals: expected "xn--zca.xn--zca" but got "xn--zca.ss"
+FAIL xn--zca.ß (using URL.hostname) assert_equals: expected "xn--zca.xn--zca" but got "xn--zca.ss"
+FAIL xn--zca.ß (using <a>) assert_equals: expected "xn--zca.xn--zca" but got "xn--zca.ss"
+FAIL xn--zca.ß (using <a>.host) assert_equals: expected "xn--zca.xn--zca" but got "xn--zca.ss"
+FAIL xn--zca.ß (using <a>.hostname) assert_equals: expected "xn--zca.xn--zca" but got "xn--zca.ss"
+FAIL xn--zca.ß (using <area>) assert_equals: expected "xn--zca.xn--zca" but got "xn--zca.ss"
+FAIL xn--zca.ß (using <area>.host) assert_equals: expected "xn--zca.xn--zca" but got "xn--zca.ss"
+FAIL xn--zca.ß (using <area>.hostname) assert_equals: expected "xn--zca.xn--zca" but got "xn--zca.ss"
 PASS ab--c.xn--zca (using URL)
 PASS ab--c.xn--zca (using URL.host)
 PASS ab--c.xn--zca (using URL.hostname)
@@ -163,24 +163,24 @@
 PASS ab--c.xn--zca (using <area>)
 PASS ab--c.xn--zca (using <area>.host)
 PASS ab--c.xn--zca (using <area>.hostname)
-PASS ab--c.ß (using URL)
-PASS ab--c.ß (using URL.host)
-PASS ab--c.ß (using URL.hostname)
-PASS ab--c.ß (using <a>)
-PASS ab--c.ß (using <a>.host)
-PASS ab--c.ß (using <a>.hostname)
-PASS ab--c.ß (using <area>)
-PASS ab--c.ß (using <area>.host)
-PASS ab--c.ß (using <area>.hostname)
+FAIL ab--c.ß (using URL) assert_equals: expected "ab--c.xn--zca" but got "ab--c.ss"
+FAIL ab--c.ß (using URL.host) assert_equals: expected "ab--c.xn--zca" but got "ab--c.ss"
+FAIL ab--c.ß (using URL.hostname) assert_equals: expected "ab--c.xn--zca" but got "ab--c.ss"
+FAIL ab--c.ß (using <a>) assert_equals: expected "ab--c.xn--zca" but got "ab--c.ss"
+FAIL ab--c.ß (using <a>.host) assert_equals: expected "ab--c.xn--zca" but got "ab--c.ss"
+FAIL ab--c.ß (using <a>.hostname) assert_equals: expected "ab--c.xn--zca" but got "ab--c.ss"
+FAIL ab--c.ß (using <area>) assert_equals: expected "ab--c.xn--zca" but got "ab--c.ss"
+FAIL ab--c.ß (using <area>.host) assert_equals: expected "ab--c.xn--zca" but got "ab--c.ss"
+FAIL ab--c.ß (using <area>.hostname) assert_equals: expected "ab--c.xn--zca" but got "ab--c.ss"
 FAIL ‍.example (using URL) assert_throws_js: function "() => makeURL("url", hostTest.input)" did not throw
-FAIL ‍.example (using URL.host) assert_equals: expected "x" but got "xn--1ug.example"
-FAIL ‍.example (using URL.hostname) assert_equals: expected "x" but got "xn--1ug.example"
-FAIL ‍.example (using <a>) assert_equals: expected "" but got "xn--1ug.example"
-FAIL ‍.example (using <a>.host) assert_equals: expected "x" but got "xn--1ug.example"
-FAIL ‍.example (using <a>.hostname) assert_equals: expected "x" but got "xn--1ug.example"
-FAIL ‍.example (using <area>) assert_equals: expected "" but got "xn--1ug.example"
-FAIL ‍.example (using <area>.host) assert_equals: expected "x" but got "xn--1ug.example"
-FAIL ‍.example (using <area>.hostname) assert_equals: expected "x" but got "xn--1ug.example"
+FAIL ‍.example (using URL.host) assert_equals: expected "x" but got ".example"
+FAIL ‍.example (using URL.hostname) assert_equals: expected "x" but got ".example"
+FAIL ‍.example (using <a>) assert_equals: expected "" but got ".example"
+FAIL ‍.example (using <a>.host) assert_equals: expected "x" but got ".example"
+FAIL ‍.example (using <a>.hostname) assert_equals: expected "x" but got ".example"
+FAIL ‍.example (using <area>) assert_equals: expected "" but got ".example"
+FAIL ‍.example (using <area>.host) assert_equals: expected "x" but got ".example"
+FAIL ‍.example (using <area>.hostname) assert_equals: expected "x" but got ".example"
 FAIL xn--1ug.example (using URL) assert_throws_js: function "() => makeURL("url", hostTest.input)" did not throw
 FAIL xn--1ug.example (using URL.host) assert_equals: expected "x" but got "xn--1ug.example"
 FAIL xn--1ug.example (using URL.hostname) assert_equals: expected "x" but got "xn--1ug.example"
@@ -208,24 +208,24 @@
 FAIL xn--a-yoc (using <area>) assert_equals: expected "" but got "xn--a-yoc"
 FAIL xn--a-yoc (using <area>.host) assert_equals: expected "x" but got "xn--a-yoc"
 FAIL xn--a-yoc (using <area>.hostname) assert_equals: expected "x" but got "xn--a-yoc"
-PASS ශ්‍රී (using URL)
-PASS ශ්‍රී (using URL.host)
-PASS ශ්‍රී (using URL.hostname)
-PASS ශ්‍රී (using <a>)
-PASS ශ්‍රී (using <a>.host)
-PASS ශ්‍රී (using <a>.hostname)
-PASS ශ්‍රී (using <area>)
-PASS ශ්‍රී (using <area>.host)
-PASS ශ්‍රී (using <area>.hostname)
-PASS نامه‌ای (using URL)
-PASS نامه‌ای (using URL.host)
-PASS نامه‌ای (using URL.hostname)
-PASS نامه‌ای (using <a>)
-PASS نامه‌ای (using <a>.host)
-PASS نامه‌ای (using <a>.hostname)
-PASS نامه‌ای (using <area>)
-PASS نامه‌ای (using <area>.host)
-PASS نامه‌ای (using <area>.hostname)
+FAIL ශ්‍රී (using URL) assert_equals: expected "xn--10cl1a0b660p" but got "xn--10cl1a0b"
+FAIL ශ්‍රී (using URL.host) assert_equals: expected "xn--10cl1a0b660p" but got "xn--10cl1a0b"
+FAIL ශ්‍රී (using URL.hostname) assert_equals: expected "xn--10cl1a0b660p" but got "xn--10cl1a0b"
+FAIL ශ්‍රී (using <a>) assert_equals: expected "xn--10cl1a0b660p" but got "xn--10cl1a0b"
+FAIL ශ්‍රී (using <a>.host) assert_equals: expected "xn--10cl1a0b660p" but got "xn--10cl1a0b"
+FAIL ශ්‍රී (using <a>.hostname) assert_equals: expected "xn--10cl1a0b660p" but got "xn--10cl1a0b"
+FAIL ශ්‍රී (using <area>) assert_equals: expected "xn--10cl1a0b660p" but got "xn--10cl1a0b"
+FAIL ශ්‍රී (using <area>.host) assert_equals: expected "xn--10cl1a0b660p" but got "xn--10cl1a0b"
+FAIL ශ්‍රී (using <area>.hostname) assert_equals: expected "xn--10cl1a0b660p" but got "xn--10cl1a0b"
+FAIL نامه‌ای (using URL) assert_equals: expected "xn--mgba3gch31f060k" but got "xn--mgba3gch31f"
+FAIL نامه‌ای (using URL.host) assert_equals: expected "xn--mgba3gch31f060k" but got "xn--mgba3gch31f"
+FAIL نامه‌ای (using URL.hostname) assert_equals: expected "xn--mgba3gch31f060k" but got "xn--mgba3gch31f"
+FAIL نامه‌ای (using <a>) assert_equals: expected "xn--mgba3gch31f060k" but got "xn--mgba3gch31f"
+FAIL نامه‌ای (using <a>.host) assert_equals: expected "xn--mgba3gch31f060k" but got "xn--mgba3gch31f"
+FAIL نامه‌ای (using <a>.hostname) assert_equals: expected "xn--mgba3gch31f060k" but got "xn--mgba3gch31f"
+FAIL نامه‌ای (using <area>) assert_equals: expected "xn--mgba3gch31f060k" but got "xn--mgba3gch31f"
+FAIL نامه‌ای (using <area>.host) assert_equals: expected "xn--mgba3gch31f060k" but got "xn--mgba3gch31f"
+FAIL نامه‌ای (using <area>.hostname) assert_equals: expected "xn--mgba3gch31f060k" but got "xn--mgba3gch31f"
 PASS �.com (using URL)
 PASS �.com (using URL.host)
 PASS �.com (using URL.hostname)
@@ -271,15 +271,15 @@
 PASS x01234567890123456789012345678901234567890123456789012345678901x.xn--zca (using <area>)
 PASS x01234567890123456789012345678901234567890123456789012345678901x.xn--zca (using <area>.host)
 PASS x01234567890123456789012345678901234567890123456789012345678901x.xn--zca (using <area>.hostname)
-PASS x01234567890123456789012345678901234567890123456789012345678901x.ß (using URL)
-PASS x01234567890123456789012345678901234567890123456789012345678901x.ß (using URL.host)
-PASS x01234567890123456789012345678901234567890123456789012345678901x.ß (using URL.hostname)
-PASS x01234567890123456789012345678901234567890123456789012345678901x.ß (using <a>)
-PASS x01234567890123456789012345678901234567890123456789012345678901x.ß (using <a>.host)
-PASS x01234567890123456789012345678901234567890123456789012345678901x.ß (using <a>.hostname)
-PASS x01234567890123456789012345678901234567890123456789012345678901x.ß (using <area>)
-PASS x01234567890123456789012345678901234567890123456789012345678901x.ß (using <area>.host)
-PASS x01234567890123456789012345678901234567890123456789012345678901x.ß (using <area>.hostname)
+FAIL x01234567890123456789012345678901234567890123456789012345678901x.ß (using URL) assert_equals: expected "x01234567890123456789012345678901234567890123456789012345678901x.xn--zca" but got "x01234567890123456789012345678901234567890123456789012345678901x.ss"
+FAIL x01234567890123456789012345678901234567890123456789012345678901x.ß (using URL.host) assert_equals: expected "x01234567890123456789012345678901234567890123456789012345678901x.xn--zca" but got "x01234567890123456789012345678901234567890123456789012345678901x.ss"
+FAIL x01234567890123456789012345678901234567890123456789012345678901x.ß (using URL.hostname) assert_equals: expected "x01234567890123456789012345678901234567890123456789012345678901x.xn--zca" but got "x01234567890123456789012345678901234567890123456789012345678901x.ss"
+FAIL x01234567890123456789012345678901234567890123456789012345678901x.ß (using <a>) assert_equals: expected "x01234567890123456789012345678901234567890123456789012345678901x.xn--zca" but got "x01234567890123456789012345678901234567890123456789012345678901x.ss"
+FAIL x01234567890123456789012345678901234567890123456789012345678901x.ß (using <a>.host) assert_equals: expected "x01234567890123456789012345678901234567890123456789012345678901x.xn--zca" but got "x01234567890123456789012345678901234567890123456789012345678901x.ss"
+FAIL x01234567890123456789012345678901234567890123456789012345678901x.ß (using <a>.hostname) assert_equals: expected "x01234567890123456789012345678901234567890123456789012345678901x.xn--zca" but got "x01234567890123456789012345678901234567890123456789012345678901x.ss"
+FAIL x01234567890123456789012345678901234567890123456789012345678901x.ß (using <area>) assert_equals: expected "x01234567890123456789012345678901234567890123456789012345678901x.xn--zca" but got "x01234567890123456789012345678901234567890123456789012345678901x.ss"
+FAIL x01234567890123456789012345678901234567890123456789012345678901x.ß (using <area>.host) assert_equals: expected "x01234567890123456789012345678901234567890123456789012345678901x.xn--zca" but got "x01234567890123456789012345678901234567890123456789012345678901x.ss"
+FAIL x01234567890123456789012345678901234567890123456789012345678901x.ß (using <area>.hostname) assert_equals: expected "x01234567890123456789012345678901234567890123456789012345678901x.xn--zca" but got "x01234567890123456789012345678901234567890123456789012345678901x.ss"
 PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.x (using URL)
 PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.x (using URL.host)
 PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.x (using URL.hostname)
@@ -298,15 +298,15 @@
 PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.xn--zca (using <area>)
 PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.xn--zca (using <area>.host)
 PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.xn--zca (using <area>.hostname)
-PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ß (using URL)
-PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ß (using URL.host)
-PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ß (using URL.hostname)
-PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ß (using <a>)
-PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ß (using <a>.host)
-PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ß (using <a>.hostname)
-PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ß (using <area>)
-PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ß (using <area>.host)
-PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ß (using <area>.hostname)
+FAIL 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ß (using URL) assert_equals: expected "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.xn--zca" but got "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ss"
+FAIL 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ß (using URL.host) assert_equals: expected "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.xn--zca" but got "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ss"
+FAIL 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ß (using URL.hostname) assert_equals: expected "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.xn--zca" but got "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ss"
+FAIL 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ß (using <a>) assert_equals: expected "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.xn--zca" but got "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ss"
+FAIL 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ß (using <a>.host) assert_equals: expected "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.xn--zca" but got "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ss"
+FAIL 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ß (using <a>.hostname) assert_equals: expected "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.xn--zca" but got "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ss"
+FAIL 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ß (using <area>) assert_equals: expected "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.xn--zca" but got "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ss"
+FAIL 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ß (using <area>.host) assert_equals: expected "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.xn--zca" but got "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ss"
+FAIL 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ß (using <area>.hostname) assert_equals: expected "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.xn--zca" but got "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ss"
 PASS a­b (using URL)
 PASS a­b (using URL.host)
 PASS a­b (using URL.hostname)
diff --git a/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-constructor.any-expected.txt b/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-constructor.any-expected.txt
index 88e3db6b..5581385 100644
--- a/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-constructor.any-expected.txt
+++ b/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-constructor.any-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 738 tests; 552 PASS, 186 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 738 tests; 551 PASS, 187 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Loading data…
 PASS Parsing: <http://example	.
 org> against <http://example.org/foo/bar>
@@ -311,7 +311,7 @@
 PASS Parsing: <http://%00.com> against <http://other.com/>
 PASS Parsing: <http://%ef%bc%85%ef%bc%90%ef%bc%90.com> against <http://other.com/>
 PASS Parsing: <http://你好你好> against <http://other.com/>
-PASS Parsing: <https://faß.ExAmPlE/> against <about:blank>
+FAIL Parsing: <https://faß.ExAmPlE/> against <about:blank> assert_equals: href expected "https://xn--fa-hia.example/" but got "https://fass.example/"
 FAIL Parsing: <sc://faß.ExAmPlE/> against <about:blank> assert_equals: host expected "fa%C3%9F.ExAmPlE" but got ""
 PASS Parsing: <http://%zz%66%a.com> against <http://other.com/>
 PASS Parsing: <http://%25> against <http://other.com/>
diff --git a/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-constructor.any.worker-expected.txt b/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-constructor.any.worker-expected.txt
index 88e3db6b..5581385 100644
--- a/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-constructor.any.worker-expected.txt
+++ b/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-constructor.any.worker-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 738 tests; 552 PASS, 186 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 738 tests; 551 PASS, 187 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Loading data…
 PASS Parsing: <http://example	.
 org> against <http://example.org/foo/bar>
@@ -311,7 +311,7 @@
 PASS Parsing: <http://%00.com> against <http://other.com/>
 PASS Parsing: <http://%ef%bc%85%ef%bc%90%ef%bc%90.com> against <http://other.com/>
 PASS Parsing: <http://你好你好> against <http://other.com/>
-PASS Parsing: <https://faß.ExAmPlE/> against <about:blank>
+FAIL Parsing: <https://faß.ExAmPlE/> against <about:blank> assert_equals: href expected "https://xn--fa-hia.example/" but got "https://fass.example/"
 FAIL Parsing: <sc://faß.ExAmPlE/> against <about:blank> assert_equals: host expected "fa%C3%9F.ExAmPlE" but got ""
 PASS Parsing: <http://%zz%66%a.com> against <http://other.com/>
 PASS Parsing: <http://%25> against <http://other.com/>
diff --git a/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-origin.any-expected.txt b/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-origin.any-expected.txt
index c8edf0f..43b0fd7 100644
--- a/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-origin.any-expected.txt
+++ b/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-origin.any-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 329 tests; 322 PASS, 7 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 329 tests; 321 PASS, 8 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Loading data…
 PASS Origin parsing: <http://example	.
 org> against <http://example.org/foo/bar>
@@ -214,7 +214,7 @@
 PASS Origin parsing: <https://x/�?�#�> against <about:blank>
 PASS Origin parsing: <http://Go.com> against <http://other.com/>
 PASS Origin parsing: <http://你好你好> against <http://other.com/>
-PASS Origin parsing: <https://faß.ExAmPlE/> against <about:blank>
+FAIL Origin parsing: <https://faß.ExAmPlE/> against <about:blank> assert_equals: origin expected "https://xn--fa-hia.example" but got "https://fass.example"
 PASS Origin parsing: <sc://faß.ExAmPlE/> against <about:blank>
 PASS Origin parsing: <http://%30%78%63%30%2e%30%32%35%30.01> against <http://other.com/>
 PASS Origin parsing: <http://%30%78%63%30%2e%30%32%35%30.01%2e> against <http://other.com/>
diff --git a/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-origin.any.worker-expected.txt b/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-origin.any.worker-expected.txt
index c8edf0f..43b0fd7 100644
--- a/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-origin.any.worker-expected.txt
+++ b/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-origin.any.worker-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 329 tests; 322 PASS, 7 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 329 tests; 321 PASS, 8 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Loading data…
 PASS Origin parsing: <http://example	.
 org> against <http://example.org/foo/bar>
@@ -214,7 +214,7 @@
 PASS Origin parsing: <https://x/�?�#�> against <about:blank>
 PASS Origin parsing: <http://Go.com> against <http://other.com/>
 PASS Origin parsing: <http://你好你好> against <http://other.com/>
-PASS Origin parsing: <https://faß.ExAmPlE/> against <about:blank>
+FAIL Origin parsing: <https://faß.ExAmPlE/> against <about:blank> assert_equals: origin expected "https://xn--fa-hia.example" but got "https://fass.example"
 PASS Origin parsing: <sc://faß.ExAmPlE/> against <about:blank>
 PASS Origin parsing: <http://%30%78%63%30%2e%30%32%35%30.01> against <http://other.com/>
 PASS Origin parsing: <http://%30%78%63%30%2e%30%32%35%30.01%2e> against <http://other.com/>
diff --git a/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-setters-a-area.window-expected.txt b/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-setters-a-area.window-expected.txt
index 2cd4f4f..9e5c14fe 100644
--- a/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-setters-a-area.window-expected.txt
+++ b/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-setters-a-area.window-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 435 tests; 267 PASS, 168 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 435 tests; 265 PASS, 170 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Loading data…
 PASS <a>: Setting <a://example.net>.protocol = '' The empty string is not a valid scheme. Setter leaves the URL unchanged.
 PASS <area>: Setting <a://example.net>.protocol = '' The empty string is not a valid scheme. Setter leaves the URL unchanged.
@@ -135,8 +135,8 @@
 FAIL <area>: Setting <sc://x/>.host = '@' assert_equals: expected "x" but got ""
 FAIL <a>: Setting <sc://x/>.host = 'ß' assert_equals: expected "sc://%C3%9F/" but got "sc://x/"
 FAIL <area>: Setting <sc://x/>.host = 'ß' assert_equals: expected "sc://%C3%9F/" but got "sc://x/"
-PASS <a>: Setting <https://x/>.host = 'ß' IDNA Nontransitional_Processing
-PASS <area>: Setting <https://x/>.host = 'ß' IDNA Nontransitional_Processing
+FAIL <a>: Setting <https://x/>.host = 'ß' IDNA Nontransitional_Processing assert_equals: expected "https://xn--zca/" but got "https://ss/"
+FAIL <area>: Setting <https://x/>.host = 'ß' IDNA Nontransitional_Processing assert_equals: expected "https://xn--zca/" but got "https://ss/"
 PASS <a>: Setting <mailto:me@example.net>.host = 'example.com' Cannot-be-a-base means no host
 PASS <area>: Setting <mailto:me@example.net>.host = 'example.com' Cannot-be-a-base means no host
 PASS <a>: Setting <data:text/plain,Stuff>.host = 'example.net' Cannot-be-a-base means no host
diff --git a/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-setters.any-expected.txt b/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-setters.any-expected.txt
index 68bf2c4e..d783e19 100644
--- a/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-setters.any-expected.txt
+++ b/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-setters.any-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 218 tests; 136 PASS, 82 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 218 tests; 135 PASS, 83 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Loading data…
 PASS URL: Setting <a://example.net>.protocol = '' The empty string is not a valid scheme. Setter leaves the URL unchanged.
 PASS URL: Setting <a://example.net>.protocol = 'b'
@@ -68,7 +68,7 @@
 FAIL URL: Setting <sc://x/>.host = '?' assert_equals: expected "sc:///" but got "sc://x/"
 FAIL URL: Setting <sc://x/>.host = '@' assert_equals: expected "x" but got ""
 FAIL URL: Setting <sc://x/>.host = 'ß' assert_equals: expected "sc://%C3%9F/" but got "sc://x/"
-PASS URL: Setting <https://x/>.host = 'ß' IDNA Nontransitional_Processing
+FAIL URL: Setting <https://x/>.host = 'ß' IDNA Nontransitional_Processing assert_equals: expected "https://xn--zca/" but got "https://ss/"
 PASS URL: Setting <mailto:me@example.net>.host = 'example.com' Cannot-be-a-base means no host
 PASS URL: Setting <data:text/plain,Stuff>.host = 'example.net' Cannot-be-a-base means no host
 PASS URL: Setting <http://example.net>.host = 'example.com:8080'
diff --git a/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-setters.any.worker-expected.txt b/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-setters.any.worker-expected.txt
index 68bf2c4e..d783e19 100644
--- a/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-setters.any.worker-expected.txt
+++ b/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-setters.any.worker-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 218 tests; 136 PASS, 82 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 218 tests; 135 PASS, 83 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Loading data…
 PASS URL: Setting <a://example.net>.protocol = '' The empty string is not a valid scheme. Setter leaves the URL unchanged.
 PASS URL: Setting <a://example.net>.protocol = 'b'
@@ -68,7 +68,7 @@
 FAIL URL: Setting <sc://x/>.host = '?' assert_equals: expected "sc:///" but got "sc://x/"
 FAIL URL: Setting <sc://x/>.host = '@' assert_equals: expected "x" but got ""
 FAIL URL: Setting <sc://x/>.host = 'ß' assert_equals: expected "sc://%C3%9F/" but got "sc://x/"
-PASS URL: Setting <https://x/>.host = 'ß' IDNA Nontransitional_Processing
+FAIL URL: Setting <https://x/>.host = 'ß' IDNA Nontransitional_Processing assert_equals: expected "https://xn--zca/" but got "https://ss/"
 PASS URL: Setting <mailto:me@example.net>.host = 'example.com' Cannot-be-a-base means no host
 PASS URL: Setting <data:text/plain,Stuff>.host = 'example.net' Cannot-be-a-base means no host
 PASS URL: Setting <http://example.net>.host = 'example.com:8080'
diff --git a/third_party/blink/web_tests/virtual/idna-2008/fast/url/idna2003-expected.txt b/third_party/blink/web_tests/virtual/idna-2008/fast/url/idna2003-expected.txt
index b18baec..428ae58 100644
--- a/third_party/blink/web_tests/virtual/idna-2008/fast/url/idna2003-expected.txt
+++ b/third_party/blink/web_tests/virtual/idna-2008/fast/url/idna2003-expected.txt
@@ -3,10 +3,10 @@
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
 
 The PASS/FAIL results of this test are set to the behavior in IDNA2003.
-FAIL canonicalize('http://faß.de/') should be http://fass.de/. Was http://xn--fa-hia.de/.
-FAIL canonicalize('http://βόλος.com/') should be http://xn--nxasmq6b.com/. Was http://xn--nxasmm1c.com/.
-FAIL canonicalize('http://ශ්‍රී.com/') should be http://xn--10cl1a0b.com/. Was http://xn--10cl1a0b660p.com/.
-FAIL canonicalize('http://نامه‌ای.com/') should be http://xn--mgba3gch31f.com/. Was http://xn--mgba3gch31f060k.com/.
+PASS canonicalize('http://faß.de/') is 'http://fass.de/'
+PASS canonicalize('http://βόλος.com/') is 'http://xn--nxasmq6b.com/'
+PASS canonicalize('http://ශ්‍රී.com/') is 'http://xn--10cl1a0b.com/'
+PASS canonicalize('http://نامه‌ای.com/') is 'http://xn--mgba3gch31f.com/'
 PASS canonicalize('http://www.looĸout.net/') is 'http://www.xn--looout-5bb.net/'
 PASS canonicalize('http://ᗯᗯᗯ.lookout.net/') is 'http://xn--1qeaa.lookout.net/'
 PASS canonicalize('http://www.lookout.сом/') is 'http://www.lookout.xn--l1adi/'
diff --git a/third_party/blink/web_tests/virtual/idna-2008/fast/url/idna2008-expected.txt b/third_party/blink/web_tests/virtual/idna-2008/fast/url/idna2008-expected.txt
index 33d4bcd2..5af555b0 100644
--- a/third_party/blink/web_tests/virtual/idna-2008/fast/url/idna2008-expected.txt
+++ b/third_party/blink/web_tests/virtual/idna-2008/fast/url/idna2008-expected.txt
@@ -4,10 +4,10 @@
 
 The PASS/FAIL results of this test are set to the behavior in IDNA2008.
 PASS canonicalize('http://Bücher.de/') is 'http://xn--bcher-kva.de/'
-PASS canonicalize('http://faß.de/') is 'http://xn--fa-hia.de/'
-PASS canonicalize('http://βόλος.com/') is 'http://xn--nxasmm1c.com/'
-PASS canonicalize('http://ශ්‍රී.com/') is 'http://xn--10cl1a0b660p.com/'
-PASS canonicalize('http://نامه‌ای.com/') is 'http://xn--mgba3gch31f060k.com/'
+FAIL canonicalize('http://faß.de/') should be http://xn--fa-hia.de/. Was http://fass.de/.
+FAIL canonicalize('http://βόλος.com/') should be http://xn--nxasmm1c.com/. Was http://xn--nxasmq6b.com/.
+FAIL canonicalize('http://ශ්‍රී.com/') should be http://xn--10cl1a0b660p.com/. Was http://xn--10cl1a0b.com/.
+FAIL canonicalize('http://نامه‌ای.com/') should be http://xn--mgba3gch31f060k.com/. Was http://xn--mgba3gch31f.com/.
 FAIL canonicalize('http://♥.net/') should be http://�.net/. Was http://xn--g6h.net/.
 FAIL canonicalize('http://͸.net/') should be http://�.net/. Was http://%CD%B8.net/.
 FAIL canonicalize('http://Ӏ.com/') should be http://�.com/. Was http://%D3%80.com/.
diff --git a/third_party/blink/web_tests/webexposed/feature-policy-features-expected.txt b/third_party/blink/web_tests/webexposed/feature-policy-features-expected.txt
index fc11d96..842a115d 100644
--- a/third_party/blink/web_tests/webexposed/feature-policy-features-expected.txt
+++ b/third_party/blink/web_tests/webexposed/feature-policy-features-expected.txt
@@ -56,6 +56,7 @@
 screen-wake-lock
 serial
 shared-autofill
+smart-card
 storage-access
 sync-xhr
 unload
diff --git a/third_party/blink/web_tests/wpt_internal/fenced_frame/config-installation-triggers-navigation-of-navigated-fenced-frame.https.html.ini b/third_party/blink/web_tests/wpt_internal/fenced_frame/config-installation-triggers-navigation-of-navigated-fenced-frame.https.html.ini
index 748540c..6221535 100644
--- a/third_party/blink/web_tests/wpt_internal/fenced_frame/config-installation-triggers-navigation-of-navigated-fenced-frame.https.html.ini
+++ b/third_party/blink/web_tests/wpt_internal/fenced_frame/config-installation-triggers-navigation-of-navigated-fenced-frame.https.html.ini
@@ -1,3 +1,8 @@
 [inner-config-installation-triggers-navigation-of-navigated-fenced-frame.https.html]
   [Installing an inner config to a fenced frame that has navigated triggers]
     expected: FAIL
+
+
+[config-installation-triggers-navigation-of-navigated-fenced-frame.https.html]
+  [Installing an inner config to a fenced frame that has navigated triggers]
+    expected: FAIL
diff --git a/third_party/blink/web_tests/wpt_internal/fenced_frame/config-installation-triggers-navigation.https.html.ini b/third_party/blink/web_tests/wpt_internal/fenced_frame/config-installation-triggers-navigation.https.html.ini
index dbbb073d..b1a77d03 100644
--- a/third_party/blink/web_tests/wpt_internal/fenced_frame/config-installation-triggers-navigation.https.html.ini
+++ b/third_party/blink/web_tests/wpt_internal/fenced_frame/config-installation-triggers-navigation.https.html.ini
@@ -1,3 +1,3 @@
 [config-installation-triggers-navigation.https.html]
-  [Installing a config to a fenced frame triggers navigation.]
+  [Installing an inner config to a fenced frame triggers navigation.]
     expected: FAIL
diff --git a/third_party/blink/web_tests/wpt_internal/fenced_frame/cspee.https.html.ini b/third_party/blink/web_tests/wpt_internal/fenced_frame/cspee.https.html.ini
index 763de8e..98a4f2d8 100644
--- a/third_party/blink/web_tests/wpt_internal/fenced_frame/cspee.https.html.ini
+++ b/third_party/blink/web_tests/wpt_internal/fenced_frame/cspee.https.html.ini
@@ -1,6 +1,6 @@
 [cspee.https.html]
   expected:
-    if (flag_specific == "") and (os == "linux") and (product == "content_shell"): [OK, TIMEOUT]
+    if flag_specific == "disable-site-isolation-trials": [OK, TIMEOUT]
   [canLoadOpaqueURL considers CSPEE headers]
     expected: FAIL
 
diff --git a/third_party/blink/web_tests/wpt_internal/forms/file/file-input-webkitdirectory-key-space.html.ini b/third_party/blink/web_tests/wpt_internal/forms/file/file-input-webkitdirectory-key-space.html.ini
index 41ad029..c238f449 100644
--- a/third_party/blink/web_tests/wpt_internal/forms/file/file-input-webkitdirectory-key-space.html.ini
+++ b/third_party/blink/web_tests/wpt_internal/forms/file/file-input-webkitdirectory-key-space.html.ini
@@ -1,6 +1,4 @@
 [file-input-webkitdirectory-key-space.html]
-  expected:
-    if flag_specific == "disable-layout-ng": TIMEOUT
   [pressing Space on a focused webkitdirectory file input element launches a file chooser]
     expected:
       if product == "chrome": FAIL
diff --git a/third_party/blink/web_tests/wpt_internal/in-order-script-scheduling/force-in-order/async-script.html.ini b/third_party/blink/web_tests/wpt_internal/in-order-script-scheduling/force-in-order/async-script.html.ini
new file mode 100644
index 0000000..baf3324
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/in-order-script-scheduling/force-in-order/async-script.html.ini
@@ -0,0 +1,5 @@
+[async-script.html?reload]
+  expected:
+    if product == "chrome": [ERROR, OK]
+
+[async-script.html]
diff --git a/third_party/blink/web_tests/wpt_internal/prerender/restriction-pointer-lock.html.ini b/third_party/blink/web_tests/wpt_internal/prerender/restriction-pointer-lock.html.ini
index 5d2846e..3f40dcc8 100644
--- a/third_party/blink/web_tests/wpt_internal/prerender/restriction-pointer-lock.html.ini
+++ b/third_party/blink/web_tests/wpt_internal/prerender/restriction-pointer-lock.html.ini
@@ -3,8 +3,7 @@
   disabled:
     if flag_specific == "force-renderer-accessibility": @False
   expected:
-    if (os == "linux") and (product == "chrome"): TIMEOUT
-    if os == "win": TIMEOUT
+    if product == "chrome": TIMEOUT
   [prerendering pages should not be able to request pointer lock]
     expected:
       if product == "chrome": TIMEOUT
diff --git a/third_party/blink/web_tests/wpt_internal/speculation-rules/prefetch/link-selection-heuristics.https.html.ini b/third_party/blink/web_tests/wpt_internal/speculation-rules/prefetch/link-selection-heuristics.https.html.ini
new file mode 100644
index 0000000..5a555e7
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/speculation-rules/prefetch/link-selection-heuristics.https.html.ini
@@ -0,0 +1,13 @@
+[link-selection-heuristics.https.html?event_type=pointer-hover]
+  [test on pointer event link selection heuristics.]
+    expected: FAIL
+
+
+[link-selection-heuristics.https.html?event_type=none]
+  [test on pointer event link selection heuristics.]
+    expected: FAIL
+
+
+[link-selection-heuristics.https.html?event_type=pointer-down]
+  [test on pointer event link selection heuristics.]
+    expected: FAIL
diff --git a/third_party/blink/web_tests/wpt_internal/storage/estimate-usage-details-filesystem.https.tentative.any.js.ini b/third_party/blink/web_tests/wpt_internal/storage/estimate-usage-details-filesystem.https.tentative.any.js.ini
index d0deadf..31cc11b6 100644
--- a/third_party/blink/web_tests/wpt_internal/storage/estimate-usage-details-filesystem.https.tentative.any.js.ini
+++ b/third_party/blink/web_tests/wpt_internal/storage/estimate-usage-details-filesystem.https.tentative.any.js.ini
@@ -5,4 +5,5 @@
 
 [estimate-usage-details-filesystem.https.tentative.any.worker.html]
   [estimate() usage details reflects increase in fileSystem after write  operation]
-    expected: [PASS, FAIL]
+    expected:
+      if (flag_specific == "") and (os == "linux") and (product == "content_shell"): FAIL
diff --git a/third_party/blink/web_tests/wpt_internal/view-transition-on-navigation/root-and-nested-element-transition.html.ini b/third_party/blink/web_tests/wpt_internal/view-transition-on-navigation/root-and-nested-element-transition.html.ini
index e1161b9b..eab0b68 100644
--- a/third_party/blink/web_tests/wpt_internal/view-transition-on-navigation/root-and-nested-element-transition.html.ini
+++ b/third_party/blink/web_tests/wpt_internal/view-transition-on-navigation/root-and-nested-element-transition.html.ini
@@ -1,4 +1,4 @@
 [root-and-nested-element-transition.html]
   expected:
-    if product == "chrome": [FAIL, ERROR]
+    if product == "chrome": ERROR
     FAIL
diff --git a/third_party/closure_compiler/externs/passwords_private.js b/third_party/closure_compiler/externs/passwords_private.js
index 5e5429e..df82fdb 100644
--- a/third_party/closure_compiler/externs/passwords_private.js
+++ b/third_party/closure_compiler/externs/passwords_private.js
@@ -142,7 +142,16 @@
 
 /**
  * @typedef {{
+ *   name: string,
+ *   url: string
+ * }}
+ */
+chrome.passwordsPrivate.DomainInfo;
+
+/**
+ * @typedef {{
  *   urls: !chrome.passwordsPrivate.UrlCollection,
+ *   affiliatedDomains: (!Array<!chrome.passwordsPrivate.DomainInfo>|undefined),
  *   username: string,
  *   password: (string|undefined),
  *   federationText: (string|undefined),
diff --git a/third_party/libaom/README.chromium b/third_party/libaom/README.chromium
index b4493d36..98c2590e 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: 3.5.0
-Date: Tuesday November 29 2022
-Revision: bee1caded272127a6d6b70ac79479083d183d5d0
+Date: Thursday December 01 2022
+Revision: c0239a23c24796ddc003f2a3199a9014a1930a80
 CPEPrefix: cpe:/a:aomedia:aomedia:3.5.0
 License: BSD
 License File: source/libaom/LICENSE
@@ -25,6 +25,16 @@
 
    Use the generated commit message for the roll.
 
+Note: When running roll_dep.py on a Chrome release branch, we get the
+error message:
+   error: Ensure chromium/src is clean first (no non-merged commits).
+
+because the is_prestine() function in roll_dep.py runs git diff against
+origin/main. A workaround is to edit roll_dep.py and change
+'origin/main' to the release branch, e.g., 'branch-heads/5414' for the
+Chrome M109 release branch. Remember to change it back to 'origin/main'
+after running roll_dep.py.
+
 2. Generate the config files:
      # See prerequisites in file comments.
      ./cmake_update.sh
diff --git a/third_party/libaom/source/config/config/aom_version.h b/third_party/libaom/source/config/config/aom_version.h
index 69bd897..0ef82065 100644
--- a/third_party/libaom/source/config/config/aom_version.h
+++ b/third_party/libaom/source/config/config/aom_version.h
@@ -12,8 +12,8 @@
 #define VERSION_MAJOR 3
 #define VERSION_MINOR 5
 #define VERSION_PATCH 0
-#define VERSION_EXTRA "480-gbee1caded"
+#define VERSION_EXTRA "493-gc0239a23c"
 #define VERSION_PACKED \
   ((VERSION_MAJOR << 16) | (VERSION_MINOR << 8) | (VERSION_PATCH))
-#define VERSION_STRING_NOSP "3.5.0-480-gbee1caded"
-#define VERSION_STRING " 3.5.0-480-gbee1caded"
+#define VERSION_STRING_NOSP "3.5.0-493-gc0239a23c"
+#define VERSION_STRING " 3.5.0-493-gc0239a23c"
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 1122512..318cd0e 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
@@ -1109,6 +1109,16 @@
 unsigned int aom_get_mb_ss_c(const int16_t*);
 #define aom_get_mb_ss aom_get_mb_ss_c
 
+void aom_get_var_sse_sum_16x16_dual_c(const uint8_t* src_ptr,
+                                      int source_stride,
+                                      const uint8_t* ref_ptr,
+                                      int ref_stride,
+                                      uint32_t* sse16x16,
+                                      unsigned int* tot_sse,
+                                      int* tot_sum,
+                                      uint32_t* var16x16);
+#define aom_get_var_sse_sum_16x16_dual aom_get_var_sse_sum_16x16_dual_c
+
 void aom_get_var_sse_sum_8x8_quad_c(const uint8_t* src_ptr,
                                     int source_stride,
                                     const uint8_t* ref_ptr,
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 1122512..318cd0e 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
@@ -1109,6 +1109,16 @@
 unsigned int aom_get_mb_ss_c(const int16_t*);
 #define aom_get_mb_ss aom_get_mb_ss_c
 
+void aom_get_var_sse_sum_16x16_dual_c(const uint8_t* src_ptr,
+                                      int source_stride,
+                                      const uint8_t* ref_ptr,
+                                      int ref_stride,
+                                      uint32_t* sse16x16,
+                                      unsigned int* tot_sse,
+                                      int* tot_sum,
+                                      uint32_t* var16x16);
+#define aom_get_var_sse_sum_16x16_dual aom_get_var_sse_sum_16x16_dual_c
+
 void aom_get_var_sse_sum_8x8_quad_c(const uint8_t* src_ptr,
                                     int source_stride,
                                     const uint8_t* ref_ptr,
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 4a0ae3d..393168f 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
@@ -1201,6 +1201,16 @@
 unsigned int aom_get_mb_ss_c(const int16_t*);
 #define aom_get_mb_ss aom_get_mb_ss_c
 
+void aom_get_var_sse_sum_16x16_dual_c(const uint8_t* src_ptr,
+                                      int source_stride,
+                                      const uint8_t* ref_ptr,
+                                      int ref_stride,
+                                      uint32_t* sse16x16,
+                                      unsigned int* tot_sse,
+                                      int* tot_sum,
+                                      uint32_t* var16x16);
+#define aom_get_var_sse_sum_16x16_dual aom_get_var_sse_sum_16x16_dual_c
+
 void aom_get_var_sse_sum_8x8_quad_c(const uint8_t* src_ptr,
                                     int source_stride,
                                     const uint8_t* ref_ptr,
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 1122512..318cd0e 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
@@ -1109,6 +1109,16 @@
 unsigned int aom_get_mb_ss_c(const int16_t*);
 #define aom_get_mb_ss aom_get_mb_ss_c
 
+void aom_get_var_sse_sum_16x16_dual_c(const uint8_t* src_ptr,
+                                      int source_stride,
+                                      const uint8_t* ref_ptr,
+                                      int ref_stride,
+                                      uint32_t* sse16x16,
+                                      unsigned int* tot_sse,
+                                      int* tot_sum,
+                                      uint32_t* var16x16);
+#define aom_get_var_sse_sum_16x16_dual aom_get_var_sse_sum_16x16_dual_c
+
 void aom_get_var_sse_sum_8x8_quad_c(const uint8_t* src_ptr,
                                     int source_stride,
                                     const uint8_t* ref_ptr,
diff --git a/third_party/libaom/source/config/linux/arm/config/aom_dsp_rtcd.h b/third_party/libaom/source/config/linux/arm/config/aom_dsp_rtcd.h
index 151201f..df03d7ab 100644
--- a/third_party/libaom/source/config/linux/arm/config/aom_dsp_rtcd.h
+++ b/third_party/libaom/source/config/linux/arm/config/aom_dsp_rtcd.h
@@ -995,6 +995,16 @@
 unsigned int aom_get_mb_ss_c(const int16_t*);
 #define aom_get_mb_ss aom_get_mb_ss_c
 
+void aom_get_var_sse_sum_16x16_dual_c(const uint8_t* src_ptr,
+                                      int source_stride,
+                                      const uint8_t* ref_ptr,
+                                      int ref_stride,
+                                      uint32_t* sse16x16,
+                                      unsigned int* tot_sse,
+                                      int* tot_sum,
+                                      uint32_t* var16x16);
+#define aom_get_var_sse_sum_16x16_dual aom_get_var_sse_sum_16x16_dual_c
+
 void aom_get_var_sse_sum_8x8_quad_c(const uint8_t* src_ptr,
                                     int source_stride,
                                     const uint8_t* ref_ptr,
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 1122512..318cd0e 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
@@ -1109,6 +1109,16 @@
 unsigned int aom_get_mb_ss_c(const int16_t*);
 #define aom_get_mb_ss aom_get_mb_ss_c
 
+void aom_get_var_sse_sum_16x16_dual_c(const uint8_t* src_ptr,
+                                      int source_stride,
+                                      const uint8_t* ref_ptr,
+                                      int ref_stride,
+                                      uint32_t* sse16x16,
+                                      unsigned int* tot_sse,
+                                      int* tot_sum,
+                                      uint32_t* var16x16);
+#define aom_get_var_sse_sum_16x16_dual aom_get_var_sse_sum_16x16_dual_c
+
 void aom_get_var_sse_sum_8x8_quad_c(const uint8_t* src_ptr,
                                     int source_stride,
                                     const uint8_t* ref_ptr,
diff --git a/third_party/libaom/source/config/linux/generic/config/aom_dsp_rtcd.h b/third_party/libaom/source/config/linux/generic/config/aom_dsp_rtcd.h
index 0a63791..6b5bd4e 100644
--- a/third_party/libaom/source/config/linux/generic/config/aom_dsp_rtcd.h
+++ b/third_party/libaom/source/config/linux/generic/config/aom_dsp_rtcd.h
@@ -995,6 +995,16 @@
 unsigned int aom_get_mb_ss_c(const int16_t*);
 #define aom_get_mb_ss aom_get_mb_ss_c
 
+void aom_get_var_sse_sum_16x16_dual_c(const uint8_t* src_ptr,
+                                      int source_stride,
+                                      const uint8_t* ref_ptr,
+                                      int ref_stride,
+                                      uint32_t* sse16x16,
+                                      unsigned int* tot_sse,
+                                      int* tot_sum,
+                                      uint32_t* var16x16);
+#define aom_get_var_sse_sum_16x16_dual aom_get_var_sse_sum_16x16_dual_c
+
 void aom_get_var_sse_sum_8x8_quad_c(const uint8_t* src_ptr,
                                     int source_stride,
                                     const uint8_t* ref_ptr,
diff --git a/third_party/libaom/source/config/linux/ia32/config/aom_dsp_rtcd.h b/third_party/libaom/source/config/linux/ia32/config/aom_dsp_rtcd.h
index 1f50060..1634f18 100644
--- a/third_party/libaom/source/config/linux/ia32/config/aom_dsp_rtcd.h
+++ b/third_party/libaom/source/config/linux/ia32/config/aom_dsp_rtcd.h
@@ -2223,6 +2223,31 @@
 unsigned int aom_get_mb_ss_sse2(const int16_t*);
 #define aom_get_mb_ss aom_get_mb_ss_sse2
 
+void aom_get_var_sse_sum_16x16_dual_c(const uint8_t* src_ptr,
+                                      int source_stride,
+                                      const uint8_t* ref_ptr,
+                                      int ref_stride,
+                                      uint32_t* sse16x16,
+                                      unsigned int* tot_sse,
+                                      int* tot_sum,
+                                      uint32_t* var16x16);
+void aom_get_var_sse_sum_16x16_dual_avx2(const uint8_t* src_ptr,
+                                         int source_stride,
+                                         const uint8_t* ref_ptr,
+                                         int ref_stride,
+                                         uint32_t* sse16x16,
+                                         unsigned int* tot_sse,
+                                         int* tot_sum,
+                                         uint32_t* var16x16);
+RTCD_EXTERN void (*aom_get_var_sse_sum_16x16_dual)(const uint8_t* src_ptr,
+                                                   int source_stride,
+                                                   const uint8_t* ref_ptr,
+                                                   int ref_stride,
+                                                   uint32_t* sse16x16,
+                                                   unsigned int* tot_sse,
+                                                   int* tot_sum,
+                                                   uint32_t* var16x16);
+
 void aom_get_var_sse_sum_8x8_quad_c(const uint8_t* src_ptr,
                                     int source_stride,
                                     const uint8_t* ref_ptr,
@@ -9457,6 +9482,9 @@
   aom_get_blk_sse_sum = aom_get_blk_sse_sum_sse2;
   if (flags & HAS_AVX2)
     aom_get_blk_sse_sum = aom_get_blk_sse_sum_avx2;
+  aom_get_var_sse_sum_16x16_dual = aom_get_var_sse_sum_16x16_dual_c;
+  if (flags & HAS_AVX2)
+    aom_get_var_sse_sum_16x16_dual = aom_get_var_sse_sum_16x16_dual_avx2;
   aom_get_var_sse_sum_8x8_quad = aom_get_var_sse_sum_8x8_quad_sse2;
   if (flags & HAS_AVX2)
     aom_get_var_sse_sum_8x8_quad = aom_get_var_sse_sum_8x8_quad_avx2;
diff --git a/third_party/libaom/source/config/linux/x64/config/aom_dsp_rtcd.h b/third_party/libaom/source/config/linux/x64/config/aom_dsp_rtcd.h
index 8182fda..a134596 100644
--- a/third_party/libaom/source/config/linux/x64/config/aom_dsp_rtcd.h
+++ b/third_party/libaom/source/config/linux/x64/config/aom_dsp_rtcd.h
@@ -2226,6 +2226,31 @@
 unsigned int aom_get_mb_ss_sse2(const int16_t*);
 #define aom_get_mb_ss aom_get_mb_ss_sse2
 
+void aom_get_var_sse_sum_16x16_dual_c(const uint8_t* src_ptr,
+                                      int source_stride,
+                                      const uint8_t* ref_ptr,
+                                      int ref_stride,
+                                      uint32_t* sse16x16,
+                                      unsigned int* tot_sse,
+                                      int* tot_sum,
+                                      uint32_t* var16x16);
+void aom_get_var_sse_sum_16x16_dual_avx2(const uint8_t* src_ptr,
+                                         int source_stride,
+                                         const uint8_t* ref_ptr,
+                                         int ref_stride,
+                                         uint32_t* sse16x16,
+                                         unsigned int* tot_sse,
+                                         int* tot_sum,
+                                         uint32_t* var16x16);
+RTCD_EXTERN void (*aom_get_var_sse_sum_16x16_dual)(const uint8_t* src_ptr,
+                                                   int source_stride,
+                                                   const uint8_t* ref_ptr,
+                                                   int ref_stride,
+                                                   uint32_t* sse16x16,
+                                                   unsigned int* tot_sse,
+                                                   int* tot_sum,
+                                                   uint32_t* var16x16);
+
 void aom_get_var_sse_sum_8x8_quad_c(const uint8_t* src_ptr,
                                     int source_stride,
                                     const uint8_t* ref_ptr,
@@ -9496,6 +9521,9 @@
   aom_get_blk_sse_sum = aom_get_blk_sse_sum_sse2;
   if (flags & HAS_AVX2)
     aom_get_blk_sse_sum = aom_get_blk_sse_sum_avx2;
+  aom_get_var_sse_sum_16x16_dual = aom_get_var_sse_sum_16x16_dual_c;
+  if (flags & HAS_AVX2)
+    aom_get_var_sse_sum_16x16_dual = aom_get_var_sse_sum_16x16_dual_avx2;
   aom_get_var_sse_sum_8x8_quad = aom_get_var_sse_sum_8x8_quad_sse2;
   if (flags & HAS_AVX2)
     aom_get_var_sse_sum_8x8_quad = aom_get_var_sse_sum_8x8_quad_avx2;
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 1122512..318cd0e 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
@@ -1109,6 +1109,16 @@
 unsigned int aom_get_mb_ss_c(const int16_t*);
 #define aom_get_mb_ss aom_get_mb_ss_c
 
+void aom_get_var_sse_sum_16x16_dual_c(const uint8_t* src_ptr,
+                                      int source_stride,
+                                      const uint8_t* ref_ptr,
+                                      int ref_stride,
+                                      uint32_t* sse16x16,
+                                      unsigned int* tot_sse,
+                                      int* tot_sum,
+                                      uint32_t* var16x16);
+#define aom_get_var_sse_sum_16x16_dual aom_get_var_sse_sum_16x16_dual_c
+
 void aom_get_var_sse_sum_8x8_quad_c(const uint8_t* src_ptr,
                                     int source_stride,
                                     const uint8_t* ref_ptr,
diff --git a/third_party/libaom/source/config/win/ia32/config/aom_dsp_rtcd.h b/third_party/libaom/source/config/win/ia32/config/aom_dsp_rtcd.h
index 1f50060..1634f18 100644
--- a/third_party/libaom/source/config/win/ia32/config/aom_dsp_rtcd.h
+++ b/third_party/libaom/source/config/win/ia32/config/aom_dsp_rtcd.h
@@ -2223,6 +2223,31 @@
 unsigned int aom_get_mb_ss_sse2(const int16_t*);
 #define aom_get_mb_ss aom_get_mb_ss_sse2
 
+void aom_get_var_sse_sum_16x16_dual_c(const uint8_t* src_ptr,
+                                      int source_stride,
+                                      const uint8_t* ref_ptr,
+                                      int ref_stride,
+                                      uint32_t* sse16x16,
+                                      unsigned int* tot_sse,
+                                      int* tot_sum,
+                                      uint32_t* var16x16);
+void aom_get_var_sse_sum_16x16_dual_avx2(const uint8_t* src_ptr,
+                                         int source_stride,
+                                         const uint8_t* ref_ptr,
+                                         int ref_stride,
+                                         uint32_t* sse16x16,
+                                         unsigned int* tot_sse,
+                                         int* tot_sum,
+                                         uint32_t* var16x16);
+RTCD_EXTERN void (*aom_get_var_sse_sum_16x16_dual)(const uint8_t* src_ptr,
+                                                   int source_stride,
+                                                   const uint8_t* ref_ptr,
+                                                   int ref_stride,
+                                                   uint32_t* sse16x16,
+                                                   unsigned int* tot_sse,
+                                                   int* tot_sum,
+                                                   uint32_t* var16x16);
+
 void aom_get_var_sse_sum_8x8_quad_c(const uint8_t* src_ptr,
                                     int source_stride,
                                     const uint8_t* ref_ptr,
@@ -9457,6 +9482,9 @@
   aom_get_blk_sse_sum = aom_get_blk_sse_sum_sse2;
   if (flags & HAS_AVX2)
     aom_get_blk_sse_sum = aom_get_blk_sse_sum_avx2;
+  aom_get_var_sse_sum_16x16_dual = aom_get_var_sse_sum_16x16_dual_c;
+  if (flags & HAS_AVX2)
+    aom_get_var_sse_sum_16x16_dual = aom_get_var_sse_sum_16x16_dual_avx2;
   aom_get_var_sse_sum_8x8_quad = aom_get_var_sse_sum_8x8_quad_sse2;
   if (flags & HAS_AVX2)
     aom_get_var_sse_sum_8x8_quad = aom_get_var_sse_sum_8x8_quad_avx2;
diff --git a/third_party/libaom/source/config/win/x64/config/aom_dsp_rtcd.h b/third_party/libaom/source/config/win/x64/config/aom_dsp_rtcd.h
index 8182fda..a134596 100644
--- a/third_party/libaom/source/config/win/x64/config/aom_dsp_rtcd.h
+++ b/third_party/libaom/source/config/win/x64/config/aom_dsp_rtcd.h
@@ -2226,6 +2226,31 @@
 unsigned int aom_get_mb_ss_sse2(const int16_t*);
 #define aom_get_mb_ss aom_get_mb_ss_sse2
 
+void aom_get_var_sse_sum_16x16_dual_c(const uint8_t* src_ptr,
+                                      int source_stride,
+                                      const uint8_t* ref_ptr,
+                                      int ref_stride,
+                                      uint32_t* sse16x16,
+                                      unsigned int* tot_sse,
+                                      int* tot_sum,
+                                      uint32_t* var16x16);
+void aom_get_var_sse_sum_16x16_dual_avx2(const uint8_t* src_ptr,
+                                         int source_stride,
+                                         const uint8_t* ref_ptr,
+                                         int ref_stride,
+                                         uint32_t* sse16x16,
+                                         unsigned int* tot_sse,
+                                         int* tot_sum,
+                                         uint32_t* var16x16);
+RTCD_EXTERN void (*aom_get_var_sse_sum_16x16_dual)(const uint8_t* src_ptr,
+                                                   int source_stride,
+                                                   const uint8_t* ref_ptr,
+                                                   int ref_stride,
+                                                   uint32_t* sse16x16,
+                                                   unsigned int* tot_sse,
+                                                   int* tot_sum,
+                                                   uint32_t* var16x16);
+
 void aom_get_var_sse_sum_8x8_quad_c(const uint8_t* src_ptr,
                                     int source_stride,
                                     const uint8_t* ref_ptr,
@@ -9496,6 +9521,9 @@
   aom_get_blk_sse_sum = aom_get_blk_sse_sum_sse2;
   if (flags & HAS_AVX2)
     aom_get_blk_sse_sum = aom_get_blk_sse_sum_avx2;
+  aom_get_var_sse_sum_16x16_dual = aom_get_var_sse_sum_16x16_dual_c;
+  if (flags & HAS_AVX2)
+    aom_get_var_sse_sum_16x16_dual = aom_get_var_sse_sum_16x16_dual_avx2;
   aom_get_var_sse_sum_8x8_quad = aom_get_var_sse_sum_8x8_quad_sse2;
   if (flags & HAS_AVX2)
     aom_get_var_sse_sum_8x8_quad = aom_get_var_sse_sum_8x8_quad_avx2;
diff --git a/third_party/ninja/README.chromium b/third_party/ninja/README.chromium
new file mode 100644
index 0000000..f3d82c2
--- /dev/null
+++ b/third_party/ninja/README.chromium
@@ -0,0 +1,17 @@
+Name: ninja
+Short Name: ninja
+URL: https://github.com/ninja-build/ninja
+Version: v1.8.2
+License: Apache License 2.0
+License File: https://github.com/ninja-build/ninja/blob/master/COPYING
+Security Critical: no
+
+Description:
+Ninja is a small build system with a focus on speed, and is used to build
+this project.
+
+The CIPD packages are built using the 3pp pipeline.
+https://chromium.googlesource.com/infra/infra/+/refs/heads/main/3pp/ninja/
+
+Local Modifications:
+None
diff --git a/third_party/node/node_modules.tar.gz.sha1 b/third_party/node/node_modules.tar.gz.sha1
index 384fc8e..3f871f9 100644
--- a/third_party/node/node_modules.tar.gz.sha1
+++ b/third_party/node/node_modules.tar.gz.sha1
@@ -1 +1 @@
-1db973bb2eb17011c69c7eb6649cc0e0ad8325bc
+32dc8abec1486818f6ef4afc4996abc002b5218e
diff --git a/third_party/node/npm_include.txt b/third_party/node/npm_include.txt
index 325d9e0..ea9dedf 100644
--- a/third_party/node/npm_include.txt
+++ b/third_party/node/npm_include.txt
@@ -22,6 +22,7 @@
 @types/w3c-image-capture/*.d.ts
 @types/webrtc
 @types/webrtc/*.d.ts
+@types/wicg-file-system-access/index.d.ts
 svgo/.svgo.yml
 typescript/lib/lib.d.ts
 typescript/lib/lib.*.d.ts
diff --git a/third_party/node/package-lock.json b/third_party/node/package-lock.json
index a93f37b..4b858e6 100644
--- a/third_party/node/package-lock.json
+++ b/third_party/node/package-lock.json
@@ -21,6 +21,7 @@
         "@types/w3c-css-typed-object-model-level-1": "20180410.0.4",
         "@types/w3c-image-capture": "1.0.4",
         "@types/webrtc": "0.0.31",
+        "@types/wicg-file-system-access": "2020.09.05",
         "@typescript-eslint/eslint-plugin": "5.12.0",
         "@typescript-eslint/parser": "5.12.0",
         "babel-eslint": "10.0.2",
@@ -570,6 +571,11 @@
       "resolved": "https://registry.npmjs.org/@types/webrtc/-/webrtc-0.0.31.tgz",
       "integrity": "sha512-GEoJIoM/ddxdaGnu0enJVDTdvwwr3/tPkpXz0LBTSi282gqxWDg1ffk4fzHU2dosNshp+Slq1kfGxpKs5083Nw=="
     },
+    "node_modules/@types/wicg-file-system-access": {
+      "version": "2020.9.5",
+      "resolved": "https://registry.npmjs.org/@types/wicg-file-system-access/-/wicg-file-system-access-2020.9.5.tgz",
+      "integrity": "sha512-UYK244awtmcUYQfs7FR8710MJcefL2WvkyHMjA8yJzxd1mo0Gfn88sRZ1Bls7hiUhA2w7ne1gpJ9T5g3G0wOyA=="
+    },
     "node_modules/@typescript-eslint/eslint-plugin": {
       "version": "5.12.0",
       "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.12.0.tgz",
@@ -3047,6 +3053,11 @@
       "resolved": "https://registry.npmjs.org/@types/webrtc/-/webrtc-0.0.31.tgz",
       "integrity": "sha512-GEoJIoM/ddxdaGnu0enJVDTdvwwr3/tPkpXz0LBTSi282gqxWDg1ffk4fzHU2dosNshp+Slq1kfGxpKs5083Nw=="
     },
+    "@types/wicg-file-system-access": {
+      "version": "2020.9.5",
+      "resolved": "https://registry.npmjs.org/@types/wicg-file-system-access/-/wicg-file-system-access-2020.9.5.tgz",
+      "integrity": "sha512-UYK244awtmcUYQfs7FR8710MJcefL2WvkyHMjA8yJzxd1mo0Gfn88sRZ1Bls7hiUhA2w7ne1gpJ9T5g3G0wOyA=="
+    },
     "@typescript-eslint/eslint-plugin": {
       "version": "5.12.0",
       "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.12.0.tgz",
diff --git a/third_party/node/package.json b/third_party/node/package.json
index 8539288..9841052 100644
--- a/third_party/node/package.json
+++ b/third_party/node/package.json
@@ -16,6 +16,7 @@
     "@types/w3c-css-typed-object-model-level-1": "20180410.0.4",
     "@types/w3c-image-capture": "1.0.4",
     "@types/webrtc": "0.0.31",
+    "@types/wicg-file-system-access": "2020.09.05",
     "@typescript-eslint/eslint-plugin": "5.12.0",
     "@typescript-eslint/parser": "5.12.0",
     "babel-eslint": "10.0.2",
diff --git a/tools/cast3p/cast_core.version b/tools/cast3p/cast_core.version
index 66d9d66..bdf2f672 100644
--- a/tools/cast3p/cast_core.version
+++ b/tools/cast3p/cast_core.version
@@ -1 +1 @@
-cast_20221128_0600_RC01
+cast_20221208_0600_RC00
diff --git a/tools/clang/plugins/RawPtrHelpers.cpp b/tools/clang/plugins/RawPtrHelpers.cpp
index 3d83b785..6b13bc43 100644
--- a/tools/clang/plugins/RawPtrHelpers.cpp
+++ b/tools/clang/plugins/RawPtrHelpers.cpp
@@ -111,10 +111,12 @@
       fieldDecl(hasType(pointerType(pointee(qualType(allOf(
           isConstQualified(), hasUnqualifiedDesugaredType(anyCharType())))))));
 
+  // TODO(keishi): Skip field declarations in scratch space for now as we can't
+  // tell the correct file path.
   auto field_decl_matcher =
       fieldDecl(
           allOf(hasType(supported_pointer_types_matcher),
-                unless(anyOf(const_char_pointer_matcher,
+                unless(anyOf(const_char_pointer_matcher, isInScratchSpace(),
                              isExpansionInSystemHeader(), isInExternCContext(),
                              isRawPtrExclusionAnnotated(),
                              isInThirdPartyLocation(), isInGeneratedLocation(),
diff --git a/tools/clang/plugins/RawPtrHelpers.h b/tools/clang/plugins/RawPtrHelpers.h
index 9e885c5..7536268 100644
--- a/tools/clang/plugins/RawPtrHelpers.h
+++ b/tools/clang/plugins/RawPtrHelpers.h
@@ -62,6 +62,17 @@
   return Node.isAnyCharacterType();
 }
 
+AST_MATCHER(clang::FieldDecl, isInScratchSpace) {
+  const clang::SourceManager& source_manager =
+      Finder->getASTContext().getSourceManager();
+  clang::SourceLocation location = Node.getSourceRange().getBegin();
+  if (location.isInvalid())
+    return false;
+  clang::SourceLocation spelling_location =
+      source_manager.getSpellingLoc(location);
+  return source_manager.isWrittenInScratchSpace(spelling_location);
+}
+
 AST_MATCHER(clang::FieldDecl, isInThirdPartyLocation) {
   std::string filename = GetFilename(Finder->getASTContext().getSourceManager(),
                                      Node.getSourceRange().getBegin());
@@ -80,7 +91,8 @@
   std::string filename = GetFilename(Finder->getASTContext().getSourceManager(),
                                      Node.getSourceRange().getBegin());
 
-  return filename.find("/gen/") != std::string::npos;
+  return filename.find("/gen/") != std::string::npos ||
+         filename.rfind("gen/", 0) == 0;
 }
 
 AST_MATCHER_P(clang::FieldDecl,
@@ -95,7 +107,7 @@
               const FilterFile*,
               Filter) {
   clang::SourceLocation loc = Node.getSourceRange().getBegin();
-  if (loc.isInvalid() || !loc.isFileID())
+  if (loc.isInvalid())
     return false;
   std::string file_path =
       GetFilename(Finder->getASTContext().getSourceManager(), loc);
diff --git a/tools/clang/plugins/tests/raw_ptr_fields_scratch_space.cpp b/tools/clang/plugins/tests/raw_ptr_fields_scratch_space.cpp
new file mode 100644
index 0000000..47957f6
--- /dev/null
+++ b/tools/clang/plugins/tests/raw_ptr_fields_scratch_space.cpp
@@ -0,0 +1,11 @@
+// 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.
+
+// // Based on third_party/brotli/enc/metablock_inc.h
+
+class HistogramLiteral;
+
+#define FN(X) X##Literal
+#include "raw_ptr_fields_scratch_space_inc.h" /* NOLINT(build/include) */
+#undef FN
diff --git a/tools/clang/plugins/tests/raw_ptr_fields_scratch_space.flags b/tools/clang/plugins/tests/raw_ptr_fields_scratch_space.flags
new file mode 100644
index 0000000..cb3c8b65
--- /dev/null
+++ b/tools/clang/plugins/tests/raw_ptr_fields_scratch_space.flags
@@ -0,0 +1 @@
+-Xclang -plugin-arg-find-bad-constructs -Xclang check-raw-ptr-fields
diff --git a/tools/clang/plugins/tests/raw_ptr_fields_scratch_space.txt b/tools/clang/plugins/tests/raw_ptr_fields_scratch_space.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tools/clang/plugins/tests/raw_ptr_fields_scratch_space.txt
diff --git a/tools/clang/plugins/tests/raw_ptr_fields_scratch_space_inc.h b/tools/clang/plugins/tests/raw_ptr_fields_scratch_space_inc.h
new file mode 100644
index 0000000..f9dc447
--- /dev/null
+++ b/tools/clang/plugins/tests/raw_ptr_fields_scratch_space_inc.h
@@ -0,0 +1,20 @@
+// 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.
+
+// NOLINT(build/header_guard)
+// no-include-guard-because-multiply-included
+
+// FN is a macro that appends a suffix to a given name. In the real code (in
+// metablock.c), FN is redefined, then this file is included reapeatedly to
+// generate the same code for names with different suffixes.
+
+// For this test, HistogramType will expand to HistogramLiteral.
+#define HistogramType FN(Histogram)
+
+// For this test, FN(BlockSplitter) will expand to BlockSplitterLiteral.
+struct FN(BlockSplitter) {
+  // No error expected, as the source location for this field declaration will
+  // be "<scratch space>" and the real file path cannot be detected.
+  HistogramType* histograms_;
+};
diff --git a/tools/clang/rewrite_raw_ptr_fields/manual-paths-to-ignore.txt b/tools/clang/rewrite_raw_ptr_fields/manual-paths-to-ignore.txt
index b19d65c..4eada82 100644
--- a/tools/clang/rewrite_raw_ptr_fields/manual-paths-to-ignore.txt
+++ b/tools/clang/rewrite_raw_ptr_fields/manual-paths-to-ignore.txt
@@ -74,6 +74,14 @@
 third_party/blink/public/web/  # TODO: Consider renaming this directory to
                                # public/renderer?
 
+# Contains sysroot dirs like debian_bullseye_amd64-sysroot/ that are not part of the repository.
+build/linux/
+
+# glslang_tab.cpp.h uses #line directive and modifies the file path to
+# "MachineIndependent/glslang.y" so the isInThirdPartyLocation() filter cannot
+# catch it even though glslang_tab.cpp.h is in third_party/
+MachineIndependent/
+
 # Exclude paths in separate repositories - i.e. in directories that
 # 1. Contain a ".git" subdirectory
 # 2. And hasn't been excluded via "third_party/" substring in their path
diff --git a/tools/clang/scripts/build.py b/tools/clang/scripts/build.py
index 5f7f38d..518b4558 100755
--- a/tools/clang/scripts/build.py
+++ b/tools/clang/scripts/build.py
@@ -393,9 +393,10 @@
                            PINNED_CLANG_VERSION)
 
 
-# TODO(crbug.com/929645): Remove once we don't need gcc's libstdc++.
+# TODO(crbug.com/1013560): Consider linking with libc++ instead of libstdc++.
 def MaybeDownloadHostGcc(args):
-  """Download a modern GCC host compiler on Linux."""
+  """Download the libstdc++ packaged with GCC, which we must link into the clang
+  we are building on Linux."""
   assert sys.platform.startswith('linux')
   if args.gcc_toolchain:
     return
diff --git a/tools/flags/list_flags.py b/tools/flags/list_flags.py
index 1693feb..6d44ccb 100755
--- a/tools/flags/list_flags.py
+++ b/tools/flags/list_flags.py
@@ -43,8 +43,6 @@
   """
 
   owners_db = owners_client.GetCodeOwnersClient(
-      root=utils.ROOT_PATH,
-      upstream="",
       host="chromium-review.googlesource.com",
       project="chromium/src",
       branch="main")
diff --git a/tools/mac/power/benchmark.py b/tools/mac/power/benchmark.py
index 145f125..be682cfa 100755
--- a/tools/mac/power/benchmark.py
+++ b/tools/mac/power/benchmark.py
@@ -104,7 +104,14 @@
 
   parser.add_argument('--extra-command-line',
                       dest='extra_command_line',
-                      action='store')
+                      action='append',
+                      help="Multiple values are suported.")
+
+  parser.add_argument('--tag',
+                      dest='tag',
+                      default="",
+                      action='store',
+                      help='Tag to be added to metada to identify run.')
 
   args = parser.parse_args()
 
@@ -143,6 +150,8 @@
                                   BrowserFactory,
                                   meet_meeting_id=args.meet_meeting_id):
 
+      scenario.tag = args.tag
+
       if kasa_plug_controller:
         kasa_plug_controller.charge_to(80)
 
diff --git a/tools/mac/power/browsers.py b/tools/mac/power/browsers.py
index 82a2c41..84e5bebd 100644
--- a/tools/mac/power/browsers.py
+++ b/tools/mac/power/browsers.py
@@ -198,7 +198,10 @@
       ]
 
     if extra_command_line:
-      chrome_extra_arg += [extra_command_line]
+      for command in extra_command_line:
+        # Quotes are needed to avoid to avoid cli replacement.
+        command = command.replace('"', '')
+        chrome_extra_arg += [command]
 
     if browser_name == "chrome":
       return Chrome(variation, extra_args=chrome_extra_arg)
diff --git a/tools/mac/power/scenarios.py b/tools/mac/power/scenarios.py
index 986dcb9a..8c9d784 100644
--- a/tools/mac/power/scenarios.py
+++ b/tools/mac/power/scenarios.py
@@ -35,6 +35,7 @@
     self.script_process = None
     self.osa_script = None
     self.duration = duration
+    self.tag = ""
 
   def Launch(self):
     """Starts the driver script.
@@ -106,7 +107,7 @@
   def Summary(self):
     """Returns a dictionary describing the scenarios parameters.
     """
-    return {'name': self.name, **self._args}
+    return {'name': self.name, 'tag': self.tag, **self._args}
 
 
 class ScenarioWithBrowserOSADriver(ScenarioOSADriver):
diff --git a/tools/mb/mb.py b/tools/mb/mb.py
index 9b59d52..a534890 100755
--- a/tools/mb/mb.py
+++ b/tools/mb/mb.py
@@ -1720,6 +1720,23 @@
     is_win = self.platform == 'win32' or 'target_os="win"' in vals['gn_args']
     is_lacros = 'chromeos_is_browser_only=true' in vals['gn_args']
 
+    # This should be true if tests with type='windowed_test_launcher' are
+    # expected to run using xvfb. For example, Linux Desktop, X11 CrOS and
+    # Ozone CrOS builds on Linux (xvfb is not used on CrOS HW or VMs). Note
+    # that one Ozone build can be used to run different backends. Currently,
+    # tests are executed for the headless and X11 backends and both can run
+    # under Xvfb on Linux.
+    use_xvfb = (self.platform.startswith('linux') and not is_android
+                and not is_fuchsia and not is_cros_device)
+
+    asan = 'is_asan=true' in vals['gn_args']
+    msan = 'is_msan=true' in vals['gn_args']
+    tsan = 'is_tsan=true' in vals['gn_args']
+    cfi_diag = 'use_cfi_diag=true' in vals['gn_args']
+    clang_coverage = 'use_clang_coverage=true' in vals['gn_args']
+    java_coverage = 'use_jacoco_coverage=true' in vals['gn_args']
+    javascript_coverage = 'use_javascript_coverage=true' in vals['gn_args']
+
     test_type = isolate_map[target]['type']
 
     if self.use_luci_auth:
@@ -1740,29 +1757,14 @@
       # generated_scripts.
       cmdline += [script] + isolate_map[target].get('args', [])
 
+      if java_coverage:
+        cmdline += ['--coverage-dir', '${ISOLATED_OUTDIR}/coverage']
+
       return cmdline, []
 
 
     # TODO(crbug.com/816629): Convert all targets to generated_scripts
     # and delete the rest of this function.
-
-    # This should be true if tests with type='windowed_test_launcher' are
-    # expected to run using xvfb. For example, Linux Desktop, X11 CrOS and
-    # Ozone CrOS builds on Linux (xvfb is not used on CrOS HW or VMs). Note
-    # that one Ozone build can be used to run different backends. Currently,
-    # tests are executed for the headless and X11 backends and both can run
-    # under Xvfb on Linux.
-    use_xvfb = (self.platform.startswith('linux') and not is_android
-                and not is_fuchsia and not is_cros_device)
-
-    asan = 'is_asan=true' in vals['gn_args']
-    msan = 'is_msan=true' in vals['gn_args']
-    tsan = 'is_tsan=true' in vals['gn_args']
-    cfi_diag = 'use_cfi_diag=true' in vals['gn_args']
-    clang_coverage = 'use_clang_coverage=true' in vals['gn_args']
-    java_coverage = 'use_jacoco_coverage=true' in vals['gn_args']
-    javascript_coverage = 'use_javascript_coverage=true' in vals['gn_args']
-
     executable = isolate_map[target].get('executable', target)
     executable_suffix = isolate_map[target].get(
         'executable_suffix', '.exe' if is_win else '')
diff --git a/tools/mb/mb_config.pyl b/tools/mb/mb_config.pyl
index 61f19b4b..e6065a8 100644
--- a/tools/mb/mb_config.pyl
+++ b/tools/mb/mb_config.pyl
@@ -142,6 +142,10 @@
       'win-angle-chromium-x86-builder': 'gpu_tests_release_bot_dcheck_always_on_x86_reclient',
     },
 
+    'chromium.cft': {
+      'mac-rel-cft': 'gpu_tests_release_bot_minimal_symbols_chrome_for_testing_no_nacl_reclient',
+    },
+
     'chromium.chromiumos': {
       'Linux ChromiumOS Full': 'chromeos_with_codecs_release_bot_reclient',
 
@@ -970,7 +974,7 @@
       'android-12-x64-rel': 'android_release_trybot_x64_fastbuild_webview_trichrome_reclient',
       'android-12l-x64-dbg': 'android_debug_trybot_x64_webview_trichrome_webview_shell',
       'android-arm64-all-targets-dbg': 'android_debug_trybot_compile_only_arm64_fastbuild_reclient',
-      'android-arm64-rel': 'android_release_trybot_arm64_webview_monochrome_expectations_fastbuild',
+      'android-arm64-rel': 'android_release_trybot_arm64_webview_monochrome_expectations_fastbuild_reclient',
       'android-arm64-rel-inverse-fyi': 'android_release_trybot_arm64_webview_monochrome_expectations_fastbuild',
       'android-asan': 'android_clang_asan_release_trybot_reclient',
       'android-bfcache-rel': 'android_release_trybot_x86_fastbuild_webview_monochrome_reclient',
@@ -1047,6 +1051,10 @@
       'win-angle-chromium-x86-try': 'gpu_tests_release_trybot_x86_reclient',
     },
 
+    'tryserver.chromium.cft': {
+      'mac-rel-cft': 'gpu_tests_release_trybot_no_symbols_mac_code_coverage_chrome_for_testing',
+    },
+
     'tryserver.chromium.chromiumos': {
       # TODO(crbug.com/913750): Enable DCHECKS on the two amd64-generic bots
       # and two kevin bots when the PFQ has it enabled.
@@ -1247,7 +1255,6 @@
       'mac-osxbeta-rel': 'gpu_tests_debug_trybot',
       'mac-perfetto-rel': 'perfetto_release_trybot',
       'mac-rel': 'gpu_tests_release_trybot_no_symbols_mac_code_coverage',
-      'mac-rel-cft': 'gpu_tests_release_trybot_no_symbols_mac_code_coverage_chrome_for_testing',
       'mac-rel-inverse-fyi': 'gpu_tests_release_trybot_no_symbols_mac_code_coverage',
       'mac11-arm64-rel': 'mac_arm64_gpu_tests_release_trybot_no_symbols',
       'mac12-arm64-rel': 'mac_arm64_gpu_tests_release_trybot_no_symbols',
@@ -2670,6 +2677,11 @@
       'gpu_tests', 'release_bot', 'minimal_symbols',
     ],
 
+    'gpu_tests_release_bot_minimal_symbols_chrome_for_testing_no_nacl_reclient': [
+      'gpu_tests', 'release_bot_reclient', 'minimal_symbols', 'disable_nacl',
+      'chrome_for_testing',
+    ],
+
     'gpu_tests_release_bot_minimal_symbols_code_coverage': [
       'gpu_tests', 'release_bot', 'minimal_symbols', 'use_clang_coverage',
       'partial_code_coverage_instrumentation',
diff --git a/tools/mb/mb_config_expectations/chromium.cft.json b/tools/mb/mb_config_expectations/chromium.cft.json
new file mode 100644
index 0000000..a3c61b6
--- /dev/null
+++ b/tools/mb/mb_config_expectations/chromium.cft.json
@@ -0,0 +1,15 @@
+{
+  "mac-rel-cft": {
+    "gn_args": {
+      "dcheck_always_on": false,
+      "enable_nacl": false,
+      "ffmpeg_branding": "Chrome",
+      "is_chrome_for_testing_branded": true,
+      "is_component_build": false,
+      "is_debug": false,
+      "proprietary_codecs": true,
+      "symbol_level": 1,
+      "use_remoteexec": true
+    }
+  }
+}
\ No newline at end of file
diff --git a/tools/mb/mb_config_expectations/tryserver.chromium.android.json b/tools/mb/mb_config_expectations/tryserver.chromium.android.json
index 67d3de8a..cb7a8a6 100644
--- a/tools/mb/mb_config_expectations/tryserver.chromium.android.json
+++ b/tools/mb/mb_config_expectations/tryserver.chromium.android.json
@@ -113,7 +113,7 @@
       "system_webview_package_name": "com.google.android.apps.chrome",
       "target_cpu": "arm64",
       "target_os": "android",
-      "use_goma": true
+      "use_remoteexec": true
     }
   },
   "android-arm64-rel-inverse-fyi": {
diff --git a/tools/mb/mb_config_expectations/tryserver.chromium.cft.json b/tools/mb/mb_config_expectations/tryserver.chromium.cft.json
new file mode 100644
index 0000000..e53c41e
--- /dev/null
+++ b/tools/mb/mb_config_expectations/tryserver.chromium.cft.json
@@ -0,0 +1,15 @@
+{
+  "mac-rel-cft": {
+    "gn_args": {
+      "dcheck_always_on": true,
+      "ffmpeg_branding": "Chrome",
+      "is_chrome_for_testing_branded": true,
+      "is_component_build": false,
+      "is_debug": false,
+      "proprietary_codecs": true,
+      "symbol_level": 0,
+      "use_clang_coverage": true,
+      "use_goma": true
+    }
+  }
+}
\ No newline at end of file
diff --git a/tools/mb/mb_config_expectations/tryserver.chromium.mac.json b/tools/mb/mb_config_expectations/tryserver.chromium.mac.json
index 0cdbb31..a6fba0b 100644
--- a/tools/mb/mb_config_expectations/tryserver.chromium.mac.json
+++ b/tools/mb/mb_config_expectations/tryserver.chromium.mac.json
@@ -530,19 +530,6 @@
       "use_goma": true
     }
   },
-  "mac-rel-cft": {
-    "gn_args": {
-      "dcheck_always_on": true,
-      "ffmpeg_branding": "Chrome",
-      "is_chrome_for_testing_branded": true,
-      "is_component_build": false,
-      "is_debug": false,
-      "proprietary_codecs": true,
-      "symbol_level": 0,
-      "use_clang_coverage": true,
-      "use_goma": true
-    }
-  },
   "mac-rel-inverse-fyi": {
     "gn_args": {
       "coverage_instrumentation_input_file": "//.code-coverage/files_to_instrument.txt",
diff --git a/tools/metrics/actions/actions.xml b/tools/metrics/actions/actions.xml
index 8c5a994..cd29a22 100644
--- a/tools/metrics/actions/actions.xml
+++ b/tools/metrics/actions/actions.xml
@@ -21817,6 +21817,22 @@
   <description>Please enter the description of this user action.</description>
 </action>
 
+<action name="MultitaskMenu_PartialSplit_OneThird">
+  <owner>sophiewen@chromium.org</owner>
+  <owner>shidi@chromium.org</owner>
+  <description>
+    Recorded when a window is partial split to one third.
+  </description>
+</action>
+
+<action name="MultitaskMenu_PartialSplit_TwoThirds">
+  <owner>sophiewen@chromium.org</owner>
+  <owner>shidi@chromium.org</owner>
+  <description>
+    Recorded when a window is partial split to two thirds.
+  </description>
+</action>
+
 <action name="MultiWindowResizerClick">
   <owner>michelefan@chromium.org</owner>
   <owner>xdai@chromium.org</owner>
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 012731d..d337e158 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -12751,6 +12751,11 @@
   <int value="1" label="Conditions changed"/>
 </enum>
 
+<enum name="BooleanOrdered">
+  <int value="0" label="Not ordered"/>
+  <int value="1" label="Ordered"/>
+</enum>
+
 <enum name="BooleanOriginHeaderSameAsRequestOrigin">
   <int value="0" label="Headers differ"/>
   <int value="1" label="Headers match"/>
@@ -32246,7 +32251,6 @@
   <int value="1037" label="ThrottleNonVisibleCrossOriginIframesAllowed"/>
   <int value="1038" label="PdfLocalFileAccessAllowedForDomains"/>
   <int value="1039" label="FloatingWorkspaceV2Enabled"/>
-  <int value="1040" label="DeviceKeyboardBrightness"/>
   <int value="1041" label="NewBaseUrlInheritanceBehaviorAllowed"/>
   <int value="1042" label="ShowCastSessionsStartedByOtherDevices"/>
 </enum>
@@ -35429,6 +35433,7 @@
   <int value="1738" label="OS_DIAGNOSTICS_RUNFINGERPRINTALIVEROUTINE"/>
   <int value="1739" label="DEVELOPERPRIVATE_UPDATESITEACCESS"/>
   <int value="1740" label="AUTOTESTPRIVATE_REFRESHREMOTECOMMANDS"/>
+  <int value="1741" label="FILESYSTEMPROVIDERINTERNAL_RESPONDTOMOUNTREQUEST"/>
 </enum>
 
 <enum name="ExtensionIconState">
@@ -36502,6 +36507,19 @@
   <int value="3" label="Handshake completed successfully"/>
 </enum>
 
+<enum name="FastPairInitialSuccessFunnelEvent">
+  <int value="0" label="Notification clicked"/>
+  <int value="1" label="V1 device detected"/>
+  <int value="2" label="Initialization started"/>
+  <int value="3" label="Pairing started"/>
+  <int value="4" label="Pairing complete"/>
+  <int value="5" label="Guest mode detected"/>
+  <int value="6" label="Device already associated to account"/>
+  <int value="7" label="Preparing to write the account key"/>
+  <int value="8" label="Account key written"/>
+  <int value="9" label="Process complete"/>
+</enum>
+
 <enum name="FastPairPairFailure">
   <int value="0" label="Failed to create a GATT connection to the device"/>
   <int value="1" label="Failed to find the expected GATT service"/>
@@ -41469,6 +41487,9 @@
   <int value="4419" label="Scrollend"/>
   <int value="4420" label="DOMWindowOpenCrossOriginIframe"/>
   <int value="4421" label="StreamingDeclarativeShadowDOM"/>
+  <int value="4422" label="DialogCloseWatcherCancelSkipped"/>
+  <int value="4423" label="DialogCloseWatcherCancelSkippedAndDefaultPrevented"/>
+  <int value="4424" label="DialogCloseWatcherCloseSignalClosedMultiple"/>
 </enum>
 
 <enum name="FeaturePolicyAllowlistType">
@@ -41595,6 +41616,7 @@
   <int value="104" label="Unload"/>
   <int value="105" label="ClientHintPrefersReducedMotion"/>
   <int value="106" label="ComputePressure"/>
+  <int value="107" label="SmartCard"/>
 </enum>
 
 <enum name="FeaturePolicyImageCompressionFormat">
@@ -41853,15 +41875,23 @@
 </enum>
 
 <enum name="FeedEngagementType">
-  <int value="0" label="Engaged - scrolled an inch or interacted"/>
-  <int value="1" label="Engaged Simple - scrolled any amount or interacted"/>
-  <int value="2" label="Interacted - opened page or used menu"/>
-  <int value="3" label="DEPRECATED: Scrolled - scrolled any amount"/>
-  <int value="4" label="Scrolled - scrolled any amount"/>
-  <int value="5"
-      label="Good Visit - User (a) had a good explicit interaction, (b) spent
-             a long time in the feed and scrolled, or (c) spent a long time
-             viewing or opening a piece of content."/>
+  <int value="0" label="Engaged">
+    Visit - user scrolled an inch or interacted
+  </int>
+  <int value="1" label="Engaged Simple">
+    Visit - user scrolled any amount or interacted&quot;
+  </int>
+  <int value="2" label="Interacted">
+    Interaction - Opening a page or using the menu&quot;
+  </int>
+<!-- <int value="3" label="Scrolled">Deprecated - formerly scrolled any amount</int> -->
+
+  <int value="4" label="Scrolled">Interaction - Scrolled any amount</int>
+  <int value="5" label="Good Visit">
+    Visit - user (a) had a good explicit interaction, (b) spent a minute in the
+    feed and scrolled, or (c) spent ten seconds viewing or opening a piece of
+    content.
+  </int>
 </enum>
 
 <enum name="FeedHostMismatch">
@@ -52860,6 +52890,7 @@
   <int value="1" label="New Incognito Tab"/>
   <int value="2" label="Voice Search"/>
   <int value="3" label="QR Code Scanner"/>
+  <int value="4" label="Set Default Browser"/>
 </enum>
 
 <enum name="IOSSpotlightAvailability">
@@ -54122,6 +54153,13 @@
   <int value="2" label="Opened regular browser"/>
 </enum>
 
+<enum name="KioskInternetAccessInfo">
+  <int value="0" label="Online and app requires Internet"/>
+  <int value="1" label="Online and app supports offline"/>
+  <int value="2" label="Offline and app requires Internet"/>
+  <int value="3" label="Offline and app supports offline"/>
+</enum>
+
 <enum name="KioskLaunchError">
   <int value="0" label="No error"/>
   <int value="1" label="Has pending launch"/>
@@ -57385,6 +57423,7 @@
   <int value="-1813088913" label="CCTResizable90MaximumHeight:disabled"/>
   <int value="-1813070975" label="PageContentAnnotations:disabled"/>
   <int value="-1812838429" label="DragWindowToNewDesk:disabled"/>
+  <int value="-1812736745" label="OverviewDeskNavigation:enabled"/>
   <int value="-1812579951" label="ContentSuggestionsCategoryRanker:enabled"/>
   <int value="-1811949013" label="SameSiteByDefaultCookies:disabled"/>
   <int value="-1811394154" label="disable-webrtc-hw-vp8-encoding"/>
@@ -58229,6 +58268,7 @@
   <int value="-1325887476" label="NewPrintPreview:enabled"/>
   <int value="-1324982303" label="MacAddressRandomization:disabled"/>
   <int value="-1324634193" label="EnablePalmOnMaxTouchMajor:disabled"/>
+  <int value="-1324313449" label="ScreenSaverPreview:enabled"/>
   <int value="-1324029365" label="EnableTLS13EarlyData:enabled"/>
   <int value="-1322882747" label="disable-datasaver-prompt"/>
   <int value="-1322830330" label="ContextualSearchNewSettings:enabled"/>
@@ -59082,6 +59122,7 @@
   <int value="-842438090" label="enable-md-feedback"/>
   <int value="-841549378" label="DisableLacrosTtsSupport:disabled"/>
   <int value="-839664591" label="enable-web-authentication-testing-api"/>
+  <int value="-839544057" label="WebRtcCombinedNetworkAndWorkerThread:enabled"/>
   <int value="-838719204"
       label="ForceMajorVersionInMinorPositionInUserAgent:disabled"/>
   <int value="-837650216" label="DisableCryptAuthV1DeviceSync:disabled"/>
@@ -60006,6 +60047,7 @@
   <int value="-290329565" label="CrosVmCupsProxy:disabled"/>
   <int value="-288316828" label="enable-delegated-renderer"/>
   <int value="-286603268" label="hide-android-files-in-files-app"/>
+  <int value="-284694765" label="JourneysIncludeSyncedVisits:disabled"/>
   <int value="-284547865" label="UnifiedConsent:enabled"/>
   <int value="-284470280" label="ShelfPalmRejectionTouchArea:enabled"/>
   <int value="-284328071" label="LocalWebApprovals:enabled"/>
@@ -60030,7 +60072,6 @@
   <int value="-275619817"
       label="disable-proximity-auth-bluetooth-low-energy-discovery"/>
   <int value="-275164173" label="QuickUnlockPinSignin:enabled"/>
-  <int value="-275050010" label="LensInstructionChipImprovements:disabled"/>
   <int value="-273821346"
       label="MaintainShelfStateWhenEnteringOverview:disabled"/>
   <int value="-273570157" label="EnableBloom:enabled"/>
@@ -60455,6 +60496,7 @@
   <int value="-20267582" label="ResourceLoadingHints:disabled"/>
   <int value="-19498390"
       label="RunVideoCaptureServiceInBrowserProcess:disabled"/>
+  <int value="-18757749" label="CalendarJelly:disabled"/>
   <int value="-18464041" label="AutofillPrefilledFields:disabled"/>
   <int value="-18380160" label="AutofillEnableVirtualCardMetadata:enabled"/>
   <int value="-17698200" label="DoubleTapToZoomInTabletMode:disabled"/>
@@ -60920,6 +60962,7 @@
   <int value="265830810" label="BackgroundTaskComponentUpdate:enabled"/>
   <int value="266322815" label="ChromeModernDesign:disabled"/>
   <int value="266702296" label="disable-plugin-power-saver"/>
+  <int value="267313220" label="ScreenSaverPreview:disabled"/>
   <int value="267332914" label="AvoidUnnecessaryBeforeUnloadCheck:disabled"/>
   <int value="267706621" label="WallpaperPerDesk:disabled"/>
   <int value="268535107"
@@ -61144,6 +61187,7 @@
   <int value="385339273" label="TextBasedAudioDescription:disabled"/>
   <int value="385969127" label="disable-win32k-lockdown"/>
   <int value="386138870" label="DesktopPWAsOmniboxInstall:enabled"/>
+  <int value="386328035" label="CalendarJelly:enabled"/>
   <int value="386946105" label="NtpMiddleSlotPromoDismissal:disabled"/>
   <int value="387178525" label="VideoFullscreenOrientationLock:enabled"/>
   <int value="387233614" label="HomePageButtonForceEnabled:disabled"/>
@@ -62048,6 +62092,7 @@
   <int value="923947923" label="ReadPrinterCapabilitiesWithXps:disabled"/>
   <int value="924769517" label="AppServiceAdaptiveIcon:disabled"/>
   <int value="925712999" label="V8Orinoco:enabled"/>
+  <int value="925950257" label="WebRtcCombinedNetworkAndWorkerThread:disabled"/>
   <int value="926844887" label="QuickDim:disabled"/>
   <int value="926852901" label="DataReductionProxyMainMenu:disabled"/>
   <int value="928900043" label="OmniboxLooseMaxLimitOnDedicatedRows:disabled"/>
@@ -62216,7 +62261,6 @@
   <int value="1026169964" label="MicMuteNotifications:enabled"/>
   <int value="1026981579" label="TLS13HardeningForLocalAnchors:enabled"/>
   <int value="1027252926" label="SyncSupportSecondaryAccount:enabled"/>
-  <int value="1029425023" label="LensInstructionChipImprovements:enabled"/>
   <int value="1030608602" label="AutofillAssistantProactiveHelp:enabled"/>
   <int value="1030922819" label="AlignWakeUps:enabled"/>
   <int value="1031239808" label="ForceMajorVersion100InUserAgent:disabled"/>
@@ -63578,6 +63622,7 @@
   <int value="1831835753" label="MaterialDesignIncognitoNTP:enabled"/>
   <int value="1832552562" label="EnableVirtualKeyboardUkm:enabled"/>
   <int value="1833340282" label="SmartSuggestionForLargeDownloads:enabled"/>
+  <int value="1833845461" label="OverviewDeskNavigation:disabled"/>
   <int value="1834968807" label="OmniboxAutocompleteTitles:enabled"/>
   <int value="1835075399" label="PostQuantumCECPQ2:disabled"/>
   <int value="1835523483" label="OmniboxUIExperimentSwapTitleAndUrl:enabled"/>
@@ -63983,6 +64028,7 @@
   <int value="2071229145" label="BloatedRendererDetection:enabled"/>
   <int value="2071340353" label="progress-bar-completion"/>
   <int value="2071461362" label="disable-credit-card-scan"/>
+  <int value="2072050792" label="JourneysIncludeSyncedVisits:enabled"/>
   <int value="2072057275" label="UpdateHoverAtBeginFrame:disabled"/>
   <int value="2072231406" label="SingleProcessMash:disabled"/>
   <int value="2073113207" label="SensorContentSetting:disabled"/>
@@ -88059,6 +88105,8 @@
   <int value="1" label="Revalidate"/>
   <int value="2" label="Reload"/>
   <int value="3" label="Load"/>
+  <int value="4" label="Defer"/>
+  <int value="5" label="PreviouslyDeferredLoad"/>
 </enum>
 
 <enum name="RevokeTokenAction">
diff --git a/tools/metrics/histograms/metadata/android/histograms.xml b/tools/metrics/histograms/metadata/android/histograms.xml
index 590e9273..29c4021e 100644
--- a/tools/metrics/histograms/metadata/android/histograms.xml
+++ b/tools/metrics/histograms/metadata/android/histograms.xml
@@ -23,6 +23,7 @@
 <histograms>
 
 <variants name="ActivityType">
+  <variant name="" summary="activity"/>
   <variant name=".ChromeTabbedActivity" summary="ChromeTabbedActivity"/>
   <variant name=".CustomTabActivity" summary="CustomTabActivity"/>
   <variant name=".FirstRunActivity" summary="FirstRunActivity"/>
@@ -1532,6 +1533,19 @@
 </histogram>
 
 <histogram
+    name="Android.Fonts.TimeDownloadableFontsRetrievedAfterFirstDraw{ActivityType}"
+    units="ms" expires_after="2023-06-15">
+  <owner>sinansahin@google.com</owner>
+  <owner>twellington@chromium.org</owner>
+  <summary>
+    Time it took to retrieve the downloadable fonts after the first draw of
+    {ActivityType}'s content view. Mutually exclusive with *BeforeFirstDraw.
+    Recorded when all downloadable fonts are retrieved.
+  </summary>
+  <token key="ActivityType" variants="ActivityType"/>
+</histogram>
+
+<histogram
     name="Android.Fonts.TimeDownloadableFontsRetrievedAfterPostInflationStartup{ActivityType}"
     units="ms" expires_after="2023-06-15">
   <owner>sinansahin@google.com</owner>
@@ -1545,6 +1559,19 @@
 </histogram>
 
 <histogram
+    name="Android.Fonts.TimeDownloadableFontsRetrievedBeforeFirstDraw{ActivityType}"
+    units="ms" expires_after="2023-06-15">
+  <owner>sinansahin@google.com</owner>
+  <owner>twellington@chromium.org</owner>
+  <summary>
+    Amount of time the downloadable fonts were retrieved before the first draw.
+    Mutually exclusive with *AfterFirstDraw. Recorded when the first #onDraw
+    call happens for the {ActivityType}'s content view.
+  </summary>
+  <token key="ActivityType" variants="ActivityType"/>
+</histogram>
+
+<histogram
     name="Android.Fonts.TimeDownloadableFontsRetrievedBeforePostInflationStartup{ActivityType}"
     units="ms" expires_after="2023-06-15">
   <owner>sinansahin@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/blink/histograms.xml b/tools/metrics/histograms/metadata/blink/histograms.xml
index ac257d6..c3ddb718 100644
--- a/tools/metrics/histograms/metadata/blink/histograms.xml
+++ b/tools/metrics/histograms/metadata/blink/histograms.xml
@@ -2093,35 +2093,42 @@
 </histogram>
 
 <histogram name="Blink.MemoryCache.RevalidationPolicy"
-    enum="RevalidationPolicy" expires_after="2021-04-21">
+    enum="RevalidationPolicy" expires_after="2023-04-01">
   <owner>hiroshige@chromium.org</owner>
+  <owner>gjc@chromium.org</owner>
   <owner>loading-dev@chromium.org</owner>
   <summary>
     RevalidationPolicy used for non-preloading requests for each resource type.
-    https://crbug.com/579496
+    https://crbug.com/579496 https://crbug.com/1376866 Warning: this histogram
+    was expired from 2021/04/21 to 2022/12/09; data may be missing.
   </summary>
 </histogram>
 
 <histogram name="Blink.MemoryCache.RevalidationPolicy.AsyncScript"
-    enum="RevalidationPolicy" expires_after="2021-10-25">
+    enum="RevalidationPolicy" expires_after="2023-04-01">
   <owner>hiroshige@chromium.org</owner>
   <owner>lizeb@chromium.org</owner>
+  <owner>gjc@chromium.org</owner>
   <owner>loading-dev@chromium.org</owner>
   <summary>
     RevalidationPolicy used for non-preloading requests of async and defer
-    scripts. https://crbug.com/1043679
+    scripts. https://crbug.com/1043679 https://crbug.com/1376866 Warning: this
+    histogram was expired from 2021/10/25 to 2022/12/09; data may be missing.
   </summary>
 </histogram>
 
 <histogram name="Blink.MemoryCache.RevalidationPolicy.Dead"
-    enum="RevalidationPolicy" expires_after="2021-04-21">
+    enum="RevalidationPolicy" expires_after="2023-04-01">
   <owner>hiroshige@chromium.org</owner>
+  <owner>gjc@chromium.org</owner>
   <owner>loading-dev@chromium.org</owner>
   <summary>
     RevalidationPolicy used for requests that hit Resource only referenced from
     MemoryCache for each resource type. https://crbug.com/579496 Requests
     counted by this are also counted by Blink.MemoryCache.RevalidationPolicy or
-    Blink.MemoryCache.RevalidationPolicy.Preload.
+    Blink.MemoryCache.RevalidationPolicy.Preload. https://crbug.com/1376866
+    Warning: this histogram was expired from 2021/04/21 to 2022/12/09; data may
+    be missing.
   </summary>
 </histogram>
 
@@ -2137,12 +2144,14 @@
 </histogram>
 
 <histogram name="Blink.MemoryCache.RevalidationPolicy.Preload"
-    enum="RevalidationPolicy" expires_after="2021-04-21">
+    enum="RevalidationPolicy" expires_after="2023-04-01">
   <owner>hiroshige@chromium.org</owner>
+  <owner>gjc@chromium.org</owner>
   <owner>loading-dev@chromium.org</owner>
   <summary>
     RevalidationPolicy used for preloading requests for each resource type.
-    https://crbug.com/579496
+    https://crbug.com/579496 https://crbug.com/1376866 Warning: this histogram
+    was expired from 2021/04/21 to 2022/12/09; data may be missing.
   </summary>
 </histogram>
 
diff --git a/tools/metrics/histograms/metadata/content/histograms.xml b/tools/metrics/histograms/metadata/content/histograms.xml
index afaf61ef..da0dc32 100644
--- a/tools/metrics/histograms/metadata/content/histograms.xml
+++ b/tools/metrics/histograms/metadata/content/histograms.xml
@@ -2083,18 +2083,16 @@
   <owner>harringtond@chromium.org</owner>
   <owner>feed@chromium.org</owner>
   <summary>
-    Tracks each instance of user engagement with {FeedType}, by scrolling or
-    interacting with a card (opening the URL or using the menu). Note that each
-    event type reports samples very differently and buckets can't be directly
-    compared. We're mostly interested in unique user counts. We track every
-    interaction, and we report ENGAGED and ENGAGED_SIMPLE once per user visit. A
-    user visit is when the user scrolls or interacts with the NTP more than 5
-    minutes after the previous visit. The SCROLLED engagement type represents a
-    single instance of the user scrolling, and is reported once per Feed visit.
-    The INTERACTED engagement type represents one instance of using the menu or
-    opening a feed article into its own tab. SCROLLED and INTERACTED are
-    reported for every occurrence, thus we could end up with more scrolled and
-    interected than ENGAGED or ENGAGED_SIMPLE samples.
+    Tracks user behavior with {FeedType}. Each bucket is one of two types of
+    metric. Per-visit metrics sample once per &quot;visit&quot;, which is a
+    cluster of user activities separated by less than 5 minutes of inactivity.
+    Per-visit metrics record a sample at the moment that the visit's criteria
+    are first met. Per-interaction metrics are not clustered and report samples
+    immediately. In the UI, Bucket Proportion and Total Count are not meaningful
+    as these metrics are not independent. All buckets of this metric can be
+    moved by changes in the Discover service as well as by client changes.
+    go/discover-oncall describes how to get in touch with Discover oncall
+    engineers.
   </summary>
   <token key="FeedType">
     <variant name="Feed" summary="the For-You Feed"/>
diff --git a/tools/metrics/histograms/metadata/fastpair/histograms.xml b/tools/metrics/histograms/metadata/fastpair/histograms.xml
index f91dddd..10f6c8e 100644
--- a/tools/metrics/histograms/metadata/fastpair/histograms.xml
+++ b/tools/metrics/histograms/metadata/fastpair/histograms.xml
@@ -34,6 +34,20 @@
   </summary>
 </histogram>
 
+<histogram name="FastPair.InitialPairing"
+    enum="FastPairInitialSuccessFunnelEvent" expires_after="2023-06-01">
+  <owner>jackshira@chromium.org</owner>
+  <owner>julietlevesque@google.com</owner>
+  <owner>chromeos-cross-device-eng@google.com</owner>
+  <summary>
+    Records each successful step in the Initial Pairing flow. These steps are:
+    on notification click, on initialization start, on pairing start, on pairing
+    complete, V1 device detected, guest mode detected, device already associated
+    with the user's account, preparing to write the account key, account key
+    written successfully, and on completion of the entire pairing process.
+  </summary>
+</histogram>
+
 <histogram name="FastPair.SubsequentPairing"
     enum="FastPairSubsequentSuccessFunnelEvent" expires_after="2023-06-01">
   <owner>jackshira@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/history/histograms.xml b/tools/metrics/histograms/metadata/history/histograms.xml
index 605f361..66c055a1 100644
--- a/tools/metrics/histograms/metadata/history/histograms.xml
+++ b/tools/metrics/histograms/metadata/history/histograms.xml
@@ -1092,6 +1092,19 @@
   </token>
 </histogram>
 
+<histogram
+    name="History.Clusters.ContextClusterer.ClusterCleanedUpBeforePersistence"
+    enum="Boolean" expires_after="2023-10-01">
+  <owner>sophiechang@chromium.org</owner>
+  <owner>chrome-journeys@google.com</owner>
+  <summary>
+    When the user visits a page, `ContextClustererHistoryServiceObserver`
+    considers whether to add the visit to a new cluster or an existing cluster.
+    If it decides to create a new cluster, this is logged. Therefore, logged at
+    most once per page visit.
+  </summary>
+</histogram>
+
 <histogram name="History.Clusters.ContextClusterer.NumClusters.AtCleanUp"
     units="num clusters" expires_after="2023-10-01">
   <owner>sophiechang@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/kiosk/histograms.xml b/tools/metrics/histograms/metadata/kiosk/histograms.xml
index e8c0535..9f90834 100644
--- a/tools/metrics/histograms/metadata/kiosk/histograms.xml
+++ b/tools/metrics/histograms/metadata/kiosk/histograms.xml
@@ -336,6 +336,18 @@
   </summary>
 </histogram>
 
+<histogram name="Kiosk.SessionRestart.InternetAccess"
+    enum="KioskInternetAccessInfo" expires_after="2023-12-08">
+  <owner>irfedorova@google.com</owner>
+  <owner>chromeos-kiosk-eng@google.com</owner>
+  <summary>
+    ChromeOS only. Records the Internet access info of the previous kiosk
+    session during the session restart. The Internet access info is saved to the
+    local state every hour. The Internet access info contains information
+    whether device is offline and kiosk session supports offline mode.
+  </summary>
+</histogram>
+
 <histogram name="Kiosk.SessionRestart.Reason" enum="KioskSessionRestartReason"
     expires_after="2023-11-10">
   <owner>irfedorova@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/navigation/histograms.xml b/tools/metrics/histograms/metadata/navigation/histograms.xml
index 55e8e0f3..bb72331 100644
--- a/tools/metrics/histograms/metadata/navigation/histograms.xml
+++ b/tools/metrics/histograms/metadata/navigation/histograms.xml
@@ -818,7 +818,7 @@
 </histogram>
 
 <histogram name="Navigation.IsSameBrowsingInstance"
-    enum="NavigationIsSameBrowsingInstance" expires_after="2023-01-15">
+    enum="NavigationIsSameBrowsingInstance" expires_after="2025-01-01">
   <owner>arthursonzogni@chromium.org</owner>
   <owner>clamy@chromium.org</owner>
   <owner>nasko@chromium.org</owner>
@@ -835,7 +835,7 @@
 </histogram>
 
 <histogram name="Navigation.IsSameSiteInstance"
-    enum="NavigationIsSameSiteInstance" expires_after="2023-05-07">
+    enum="NavigationIsSameSiteInstance" expires_after="2025-01-01">
   <owner>arthursonzogni@chromium.org</owner>
   <owner>clamy@chromium.org</owner>
   <owner>nasko@chromium.org</owner>
@@ -1452,6 +1452,9 @@
 <histogram
     name="Navigation.UrlParamFilter.ApplicableClassificationCount{UrlParamFilterClassificationRole}"
     units="count" expires_after="2023-01-02">
+  <obsolete>
+    Obsolete and no longer written as of m109.
+  </obsolete>
   <owner>mreichhoff@chromium.org</owner>
   <owner>wanderview@chromium.org</owner>
   <owner>bcl@chromium.org</owner>
@@ -1466,7 +1469,10 @@
 </histogram>
 
 <histogram name="Navigation.UrlParamFilter.ClassificationListValidationResult"
-    enum="ClassificationListValidationResult" expires_after="2023-03-26">
+    enum="ClassificationListValidationResult" expires_after="2023-01-02">
+  <obsolete>
+    Obsolete and no longer written as of m109.
+  </obsolete>
   <owner>kaklilu@chromium.org</owner>
   <owner>src/chrome/browser/url_param_filter/OWNERS</owner>
   <summary>
@@ -1482,7 +1488,10 @@
 </histogram>
 
 <histogram name="Navigation.UrlParamFilter.FilteredParamCount" units="count"
-    expires_after="2023-05-07">
+    expires_after="2023-01-02">
+  <obsolete>
+    Obsolete and no longer written as of m109.
+  </obsolete>
   <owner>mreichhoff@chromium.org</owner>
   <owner>wanderview@chromium.org</owner>
   <owner>bcl@chromium.org</owner>
@@ -1495,7 +1504,10 @@
 </histogram>
 
 <histogram name="Navigation.UrlParamFilter.FilteredParamCountExperimental"
-    units="count" expires_after="2023-05-07">
+    units="count" expires_after="2023-01-02">
+  <obsolete>
+    Obsolete and no longer written as of m109.
+  </obsolete>
   <owner>mreichhoff@chromium.org</owner>
   <owner>wanderview@chromium.org</owner>
   <owner>bcl@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/net/histograms.xml b/tools/metrics/histograms/metadata/net/histograms.xml
index 2a9f75f8..ecf57d84 100644
--- a/tools/metrics/histograms/metadata/net/histograms.xml
+++ b/tools/metrics/histograms/metadata/net/histograms.xml
@@ -1070,7 +1070,11 @@
 </histogram>
 
 <histogram name="Net.DNS.H3SupportedGoogleHost.TaskTypeMetadataAvailability"
-    enum="DNS.TaskTypeMetadataAvailability" expires_after="2023-04-30">
+    enum="DNS.TaskTypeMetadataAvailability" expires_after="M110">
+  <obsolete>
+    Replaced by Net.DNS.H3SupportedGoogleHost.TaskTypeMetadataAvailability2 on
+    2022-12.
+  </obsolete>
   <owner>horo@chromium.org</owner>
   <owner>src/net/OWNERS</owner>
   <summary>
@@ -1083,6 +1087,20 @@
   </summary>
 </histogram>
 
+<histogram name="Net.DNS.H3SupportedGoogleHost.TaskTypeMetadataAvailability2"
+    enum="DNS.TaskTypeMetadataAvailability" expires_after="2023-05-31">
+  <owner>horo@chromium.org</owner>
+  <owner>src/net/OWNERS</owner>
+  <summary>
+    The succeeded DNS Task type of HostResolverManager::Job (the basic internal
+    unit of work for non-local host resolutions, potentially merging redundant
+    requests from Chrome) that succeeded with or without valid HTTPS DNS
+    metadata. This is recorded for successful https- or wss-schemed queries to a
+    set of Google hosts expected to have HTTPS DNS metadata, if the query type
+    requests the HTTPS record.
+  </summary>
+</histogram>
+
 <histogram name="Net.DNS.HTTPSSVC.RecordHttps.{secure}.ExpectNoerror.DnsRcode"
     enum="HttpssvcDnsRcode" expires_after="2023-10-01">
   <owner>horo@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/others/histograms.xml b/tools/metrics/histograms/metadata/others/histograms.xml
index 211ae2e6..d8f4d19 100644
--- a/tools/metrics/histograms/metadata/others/histograms.xml
+++ b/tools/metrics/histograms/metadata/others/histograms.xml
@@ -150,12 +150,24 @@
 
 <variants name="UrlParamFilterClassificationRole">
   <variant name=".Destination"
-      summary="parameters classified as having the destination role"/>
+      summary="parameters classified as having the destination role">
+    <obsolete>
+      Obsolete and no longer written as of m109.
+    </obsolete>
+  </variant>
   <variant name=".Invalid"
       summary="parameters with an unknown role, likely indicating a
-               compatibility issue"/>
+               compatibility issue">
+    <obsolete>
+      Obsolete and no longer written as of m109.
+    </obsolete>
+  </variant>
   <variant name=".Source"
-      summary="parameters classified as having the source role"/>
+      summary="parameters classified as having the source role">
+    <obsolete>
+      Obsolete and no longer written as of m109.
+    </obsolete>
+  </variant>
 </variants>
 
 <variants name="VoiceIntentTargetVariant">
diff --git a/tools/metrics/histograms/metadata/power/histograms.xml b/tools/metrics/histograms/metadata/power/histograms.xml
index d5e6cd7..074cfaf 100644
--- a/tools/metrics/histograms/metadata/power/histograms.xml
+++ b/tools/metrics/histograms/metadata/power/histograms.xml
@@ -22,6 +22,21 @@
 
 <histograms>
 
+<variants name="BatteryCapacityType">
+<!--
+    Variants describing the type of battery capacity.
+  -->
+
+  <variant name="Actual"
+      summary="Actual capacity of the battery pack. This is usually a few
+               percentage over the design capacity for a new battery and
+               gradully degrade to 80-90% over the course of 2-4 years."/>
+  <variant name="Design"
+      summary="The design capacity of the battery pack from the manufacturer.
+               This is used to calculated promised battery life claim when
+               the device is released."/>
+</variants>
+
 <variants name="IntervalType">
 <!--
     Variants describing if the histogram contains all samples, only the first
@@ -794,6 +809,15 @@
   </token>
 </histogram>
 
+<histogram name="Power.BatteryCapacity.{BatteryCapacityType}" units="mWh"
+    expires_after="2023-05-12">
+  <owner>puthik@chromium.org</owner>
+  <owner>chromeos-platform-power@google.com</owner>
+  <summary>
+    Chrome OS {BatteryCapacityType} battery capacity in milliwatt-hours.
+  </summary>
+</histogram>
+
 <histogram name="Power.BatteryChargeHealth" units="%"
     expires_after="2023-05-13">
   <owner>puthik@chromium.org</owner>
@@ -804,15 +828,13 @@
   </summary>
 </histogram>
 
-<histogram name="Power.BatteryDischargeGranularity" units="MWh"
-    expires_after="2023-06-04">
+<histogram name="Power.BatteryDischargeGranularityAvailable"
+    enum="BooleanAvailable" expires_after="2023-06-04">
   <owner>pmonette@chromium.org</owner>
   <owner>fdoray@chromium.org</owner>
   <summary>
-    The granularity of the current battery discharge rate value as reported by
-    the operating system, if available. The battery discharge value can become
-    more precise as the charge level of the battery decreases. This histogram
-    reflects this precision.
+    The availability of the battery discharge granularity. If true,
+    Power.BatteryDischargeGranularity is reported for this client.
 
     This is reported at the end of every valid 1 minute interval. An invalid
     interval is one that deviate too much from 1 minute, which can be caused by
@@ -824,13 +846,48 @@
   </summary>
 </histogram>
 
-<histogram name="Power.BatteryDischargeGranularityAvailable"
-    enum="BooleanAvailable" expires_after="2023-06-04">
+<histogram name="Power.BatteryDischargeGranularityIsOrdered"
+    enum="BooleanOrdered" expires_after="2023-06-04">
   <owner>pmonette@chromium.org</owner>
   <owner>fdoray@chromium.org</owner>
   <summary>
-    The availability of the battery discharge granularity. If true,
-    Power.BatteryDischargeGranularity is reported for this client.
+    Reports if the battery discharge granularities are ordered according to the
+    MSDN documentation.
+
+    The documentation states that the most coarse granularity is the first
+    element among all reporting scales.
+
+    Reported only on Windows, every time the battery state is queried.
+  </summary>
+</histogram>
+
+<histogram name="Power.BatteryDischargeGranularityMilliwattHours"
+    units="milliwatt-hours" expires_after="2023-06-04">
+  <owner>pmonette@chromium.org</owner>
+  <owner>fdoray@chromium.org</owner>
+  <summary>
+    The granularity of the battery discharge rate value as reported by the
+    operating system, if available. Always the most coarse granularity among all
+    the reporting scales of the battery, regardless of the current capacity.
+
+    This is reported at the end of every valid 1 minute interval. An invalid
+    interval is one that deviate too much from 1 minute, which can be caused by
+    the computer going to sleep, or the OS sending multiple notifications in a
+    row.
+
+    Reported only on Windows, when a single battery is installed and the
+    operating system says that the unit is mWh.
+  </summary>
+</histogram>
+
+<histogram name="Power.BatteryDischargeGranularityRelative"
+    units="hundredth of a percent" expires_after="2023-06-04">
+  <owner>pmonette@chromium.org</owner>
+  <owner>fdoray@chromium.org</owner>
+  <summary>
+    The granularity of the battery discharge rate value as reported by the
+    operating system, if available. Always the most coarse granularity among all
+    the reporting scales of the battery, regardless of the current capacity.
 
     This is reported at the end of every valid 1 minute interval. An invalid
     interval is one that deviate too much from 1 minute, which can be caused by
@@ -959,6 +1016,33 @@
   </summary>
 </histogram>
 
+<histogram name="Power.BatteryLife.{BatteryCapacityType}" units="minute"
+    expires_after="2023-05-12">
+  <owner>puthik@chromium.org</owner>
+  <owner>chromeos-platform-power@google.com</owner>
+  <summary>
+    Chrome OS battery life while in active state, measured in minutes using
+    {BatteryCapacityType} full capacity of the battery pack.
+
+    This is calculated using size of the battery divide by battery discharge
+    rate sampling in Power.BatteryDischargeRate. Note that the size of the
+    battery is derated by the low battery shutdown percent.
+  </summary>
+</histogram>
+
+<histogram name="Power.BatteryLifeWhileSuspended.{BatteryCapacityType}"
+    units="hour" expires_after="2023-05-12">
+  <owner>puthik@chromium.org</owner>
+  <owner>chromeos-platform-power@google.com</owner>
+  <summary>
+    Chrome OS battery life while suspended, measured in hours using
+    {BatteryCapacityType} full capacity of the battery pack.
+
+    This is calculated using size of the battery divide by battery discharge
+    rate sampling in Power.BatteryDischargeRateWhileSuspended.
+  </summary>
+</histogram>
+
 <histogram name="Power.BatteryPercentDrop" units="%" expires_after="2023-04-23">
   <owner>ryansturm@chromium.org</owner>
   <owner>tbansal@chromium.org</owner>
@@ -1943,25 +2027,6 @@
   </summary>
 </histogram>
 
-<histogram name="Power.MaxBatteryDischargeGranularity" units="MWh"
-    expires_after="2023-06-04">
-  <owner>pmonette@chromium.org</owner>
-  <owner>fdoray@chromium.org</owner>
-  <summary>
-    The maximum granularity of the battery discharge rate as reported by the
-    operating system, if available. This value is independant of the current
-    battery capacity.
-
-    This is reported at the end of every valid 1 minute interval. An invalid
-    interval is one that deviate too much from 1 minute, which can be caused by
-    the computer going to sleep, or the OS sending multiple notifications in a
-    row.
-
-    Reported only on Windows, when a single battery is installed and the
-    operating system says that the unit is mWh.
-  </summary>
-</histogram>
-
 <histogram name="Power.MetricsDailyEventInterval" enum="DailyEventIntervalType"
     expires_after="M100">
   <owner>puthik@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/quick_answers/histograms.xml b/tools/metrics/histograms/metadata/quick_answers/histograms.xml
index 7de5253..0feec29 100644
--- a/tools/metrics/histograms/metadata/quick_answers/histograms.xml
+++ b/tools/metrics/histograms/metadata/quick_answers/histograms.xml
@@ -49,7 +49,7 @@
 
 <histogram
     name="QuickAnswers.ActiveImpression.Duration{QuickAnswersClickResultType}"
-    units="ms" expires_after="2023-01-16">
+    units="ms" expires_after="2023-12-01">
   <owner>updowndota@chromium.org</owner>
   <owner>llin@google.com</owner>
   <owner>croissant-eng@chromium.org</owner>
@@ -168,7 +168,7 @@
 </histogram>
 
 <histogram name="QuickAnswers.Loading.Duration" units="ms"
-    expires_after="2023-01-23">
+    expires_after="2023-12-01">
   <owner>updowndota@chromium.org</owner>
   <owner>llin@google.com</owner>
   <owner>croissant-eng@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/safe_browsing/histograms.xml b/tools/metrics/histograms/metadata/safe_browsing/histograms.xml
index 803c1d2..05c2b5f1 100644
--- a/tools/metrics/histograms/metadata/safe_browsing/histograms.xml
+++ b/tools/metrics/histograms/metadata/safe_browsing/histograms.xml
@@ -110,30 +110,6 @@
       summary="url allowlist on client side phishing detection"/>
 </variants>
 
-<histogram name="SafeBrowsing.AdvancedProtection.APTokenFetchStatus"
-    enum="GoogleServiceAuthError" expires_after="2022-12-18">
-  <owner>drubery@chromium.org</owner>
-  <owner>chrome-counter-abuse-alerts@google.com</owner>
-  <summary>
-    For users known to be already enrolled in Advanced Protection, records the
-    service error of refreshing OAuth2 access token during fetching the user's
-    advanced protection status. Logged once a day and will retry on transient
-    failures. This is a subset of
-    SafeBrowsing.AdvancedProtection.TokenFetchStatus.
-  </summary>
-</histogram>
-
-<histogram name="SafeBrowsing.AdvancedProtection.TokenFetchStatus"
-    enum="GoogleServiceAuthError" expires_after="2023-03-05">
-  <owner>drubery@chromium.org</owner>
-  <owner>chrome-counter-abuse-alerts@google.com</owner>
-  <summary>
-    Records the service error of refreshing OAuth2 access token during fetching
-    the user's advanced protection status. Logged at startup for all users, once
-    a day for AP users, and will retry on transient failures.
-  </summary>
-</histogram>
-
 <histogram
     name="SafeBrowsing.Android.RealTimeAllowlist.InstallerLoadFromDiskPbFileEmpty"
     units="bool" expires_after="2023-10-26">
diff --git a/tools/metrics/histograms/metadata/sb_client/histograms.xml b/tools/metrics/histograms/metadata/sb_client/histograms.xml
index c86d0f58..b8ec3d2 100644
--- a/tools/metrics/histograms/metadata/sb_client/histograms.xml
+++ b/tools/metrics/histograms/metadata/sb_client/histograms.xml
@@ -256,10 +256,9 @@
 </histogram>
 
 <histogram name="SBClientDownload.DownloadRequestNetworkDuration" units="ms"
-    expires_after="2022-12-04">
-  <owner>vakh@chromium.org</owner>
+    expires_after="2023-06-04">
+  <owner>drubery@chromium.org</owner>
   <owner>chrome-counter-abuse-alerts@google.com</owner>
-  <owner>mattm@chromium.org</owner>
   <summary>
     Records the time it takes for the SafeBrowsing download service ping. It is
     not recorded for requests that were cancelled.
@@ -270,10 +269,9 @@
 </histogram>
 
 <histogram name="SBClientDownload.DownloadRequestNetworkStats"
-    enum="SBClientDownloadCheckDownloadStats" expires_after="2022-12-04">
-  <owner>vakh@chromium.org</owner>
+    enum="SBClientDownloadCheckDownloadStats" expires_after="2023-06-04">
+  <owner>drubery@chromium.org</owner>
   <owner>chrome-counter-abuse-alerts@google.com</owner>
-  <owner>mattm@chromium.org</owner>
   <summary>
     Records the results of SafeBrowsing binary download checks which caused a
     server ping.
diff --git a/tools/metrics/histograms/metadata/tab/histograms.xml b/tools/metrics/histograms/metadata/tab/histograms.xml
index e902570..46b5270 100644
--- a/tools/metrics/histograms/metadata/tab/histograms.xml
+++ b/tools/metrics/histograms/metadata/tab/histograms.xml
@@ -834,6 +834,10 @@
 
 <histogram name="TabHoverCards.LastTabHoverCardPreviewTime{TabCountMetrics}"
     units="ms" expires_after="2023-01-01">
+  <obsolete>
+    Removed 12/2022. Not actively used because reported values are 0 most of the
+    time. It would be more useful to report number of discards per day.
+  </obsolete>
   <owner>dfried@chromium.org</owner>
   <owner>chrome-desktop-ui-sea@google.com</owner>
   <summary>
@@ -863,6 +867,10 @@
 
 <histogram name="TabHoverCards.LastTabHoverCardViewedTime{TabCountMetrics}"
     units="ms" expires_after="2023-01-01">
+  <obsolete>
+    Removed 12/2022. Not actively used because reported values are 0 most of the
+    time. It would be more useful to report number of discards per day.
+  </obsolete>
   <owner>dfried@chromium.org</owner>
   <owner>chrome-desktop-ui-sea@google.com</owner>
   <summary>
@@ -890,6 +898,10 @@
 
 <histogram name="TabHoverCards.TabHoverCardPreviewTime{TabCountMetrics}"
     units="ms" expires_after="2023-01-01">
+  <obsolete>
+    Removed 12/2022. Not actively used because reported values are 0 most of the
+    time. It would be more useful to report number of discards per day.
+  </obsolete>
   <owner>dfried@chromium.org</owner>
   <owner>chrome-desktop-ui-sea@google.com</owner>
   <summary>
@@ -919,6 +931,10 @@
 <histogram
     name="TabHoverCards.TabHoverCardsSeenBeforeTabSelection{TabCountMetrics}"
     units="hover cards" expires_after="2023-01-01">
+  <obsolete>
+    Removed 12/2022. Not actively used because reported values are 0 most of the
+    time. It would be more useful to report number of discards per day.
+  </obsolete>
   <owner>dfried@chromium.org</owner>
   <owner>chrome-desktop-ui-sea@google.com</owner>
   <summary>
@@ -944,6 +960,10 @@
 
 <histogram name="TabHoverCards.TabHoverCardViewedTime{TabCountMetrics}"
     units="ms" expires_after="2023-01-01">
+  <obsolete>
+    Removed 12/2022. Not actively used because reported values are 0 most of the
+    time. It would be more useful to report number of discards per day.
+  </obsolete>
   <owner>dfried@chromium.org</owner>
   <owner>chrome-desktop-ui-sea@google.com</owner>
   <summary>
@@ -972,6 +992,10 @@
 <histogram
     name="TabHoverCards.TabPreviewsSeenBeforeTabSelection{TabCountMetrics}"
     units="previews" expires_after="2023-01-01">
+  <obsolete>
+    Removed 12/2022. Not actively used because reported values are 0 most of the
+    time. It would be more useful to report number of discards per day.
+  </obsolete>
   <owner>dfried@chromium.org</owner>
   <owner>chrome-desktop-ui-sea@google.com</owner>
   <summary>
@@ -998,6 +1022,10 @@
 
 <histogram name="TabHoverCards.TimeSinceLastVisible" units="ms"
     expires_after="2023-01-01">
+  <obsolete>
+    Removed 12/2022. Not actively used because reported values are 0 most of the
+    time. It would be more useful to report number of discards per day.
+  </obsolete>
   <owner>corising@chromium.org</owner>
   <owner>chrome-desktop-ui-sea@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/uma/histograms.xml b/tools/metrics/histograms/metadata/uma/histograms.xml
index e803bf5..c190868e 100644
--- a/tools/metrics/histograms/metadata/uma/histograms.xml
+++ b/tools/metrics/histograms/metadata/uma/histograms.xml
@@ -484,6 +484,106 @@
   </summary>
 </histogram>
 
+<histogram name="UMA.MetricsService.MaybeCleanUpAndStoreFinalizedLog.Time"
+    units="ms" expires_after="2023-06-01">
+  <owner>lucnguyen@google.com</owner>
+  <owner>src/base/metrics/OWNERS</owner>
+  <summary>
+    The amount of time the main thread spends on storing a finalized async log
+    and marking its unlogged samples as logged. I.e., the run time of
+    MetricsService::MaybeCleanUpAndStoreFinalizedLog(). This is not emitted if
+    the async log is not stored (see the UMA.MetricsService.ShouldStoreAsyncLog
+    histogram).
+  </summary>
+</histogram>
+
+<histogram name="UMA.MetricsService.PendingOngoingLog" enum="Boolean"
+    expires_after="2023-06-01">
+  <owner>lucnguyen@google.com</owner>
+  <owner>src/base/metrics/OWNERS</owner>
+  <summary>
+    Whether there is an ongoing log being finalized right as we are about to
+    close a non-periodic log. For example, due to Chrome being shut down.
+    Emitted every time MetricsService::PushPendingLogsToPersistentStorage() is
+    called.
+  </summary>
+</histogram>
+
+<histogram name="UMA.MetricsService.PendingOngoingLogOnBackgrounded"
+    enum="Boolean" expires_after="2023-06-01">
+  <owner>lucnguyen@google.com</owner>
+  <owner>src/base/metrics/OWNERS</owner>
+  <summary>
+    Whether there is an ongoing log being finalized right as we are about to
+    close a non-periodic log due to Chrome being backgrounded. This should be a
+    subset of the UMA.MetricsService.PendingOngoingLog histogram. Only emitted
+    on mobile platforms.
+  </summary>
+</histogram>
+
+<histogram name="UMA.MetricsService.PendingOngoingLogOnDisable" enum="Boolean"
+    expires_after="2023-06-01">
+  <owner>lucnguyen@google.com</owner>
+  <owner>src/base/metrics/OWNERS</owner>
+  <summary>
+    Whether there is an ongoing log being finalized right as we are about to
+    close a non-periodic log due to the metrics service being disabled. Most
+    commonly, this happens when Chrome is shutting down cleanly. Otherwise, this
+    can happen when the user manually disables UMA. Emitted every time
+    MetricsService::DisableRecording() is called. This is only emitted on mobile
+    platforms. This should be a subset of the
+    UMA.MetricsService.PendingOngoingLog histogram.
+  </summary>
+</histogram>
+
+<histogram name="UMA.MetricsService.PendingOngoingLogOnForegrounded"
+    enum="Boolean" expires_after="2023-06-01">
+  <owner>lucnguyen@google.com</owner>
+  <owner>src/base/metrics/OWNERS</owner>
+  <summary>
+    Whether there is an ongoing log being finalized right as we are about to
+    close a non-periodic log due to Chrome being foregrounded. This should be a
+    subset of the UMA.MetricsService.PendingOngoingLog histogram. Only emitted
+    on mobile platforms.
+  </summary>
+</histogram>
+
+<histogram name="UMA.MetricsService.PeriodicOngoingLog.CloseTime" units="ms"
+    expires_after="2023-06-01">
+  <owner>lucnguyen@google.com</owner>
+  <owner>src/base/metrics/OWNERS</owner>
+  <summary>
+    The amount of time the main thread spent on finalizing a periodic ongoing
+    log. I.e., the run time of MetricsService::OnFinalLogInfoCollectionDone().
+    This is not emitted if UMA was turned off while collecting some final
+    metrics for the log.
+  </summary>
+</histogram>
+
+<histogram name="UMA.MetricsService.PeriodicOngoingLog.ReplyTime" units="ms"
+    expires_after="2023-06-01">
+  <owner>lucnguyen@google.com</owner>
+  <owner>src/base/metrics/OWNERS</owner>
+  <summary>
+    The time it took between posting a task to finalize a periodic ongoing log,
+    and when the reply task was run (on the main thread) to store the finalized
+    log. This is only emitted if the periodic ongoing log was created
+    asynchronously.
+  </summary>
+</histogram>
+
+<histogram name="UMA.MetricsService.ShouldStoreAsyncLog" enum="Boolean"
+    expires_after="2023-06-01">
+  <owner>lucnguyen@google.com</owner>
+  <owner>src/base/metrics/OWNERS</owner>
+  <summary>
+    Whether a log that was created asynchronously is going to be stored. An
+    async log will not be stored if a synchronous log was created while the
+    async one was being finalized. Emitted every time the
+    MetricsService::MaybeCleanUpAndStoreFinalizedLog() callback runs.
+  </summary>
+</histogram>
+
 <histogram name="UMA.NegativeSamples.Histogram" enum="HistogramNameHash"
     expires_after="2023-11-01">
   <owner>asvitkine@chromium.org</owner>
diff --git a/tools/metrics/ukm/ukm.xml b/tools/metrics/ukm/ukm.xml
index 0a61c4da..4eda809 100644
--- a/tools/metrics/ukm/ukm.xml
+++ b/tools/metrics/ukm/ukm.xml
@@ -905,11 +905,17 @@
     </summary>
   </metric>
   <metric name="SubFrame.MobileFriendliness.AllowUserZoom" enum="Boolean">
+    <obsolete>
+      Deprecated 12/2022.
+    </obsolete>
     <summary>
       Whether the page allows the user to zoom in/out, in the AMP subframe.
     </summary>
   </metric>
   <metric name="SubFrame.MobileFriendliness.BadTapTargetsRatio">
+    <obsolete>
+      Deprecated 12/2022.
+    </obsolete>
     <summary>
       Percentage of tap targets whose center position is within another tap
       target (expanded by a margin), in the AMP subframe. The detail of the
@@ -918,6 +924,9 @@
     </summary>
   </metric>
   <metric name="SubFrame.MobileFriendliness.SmallTextRatio">
+    <obsolete>
+      Deprecated 12/2022.
+    </obsolete>
     <summary>
       Percentage of small font text area in total text area, in the AMP
       subframe.
@@ -925,23 +934,35 @@
   </metric>
   <metric
       name="SubFrame.MobileFriendliness.TextContentOutsideViewportPercentage">
+    <obsolete>
+      Deprecated 12/2022.
+    </obsolete>
     <summary>
       Percentage of pixels of text and images horizontally outside the viewport,
       relative to the frame width, in the AMP subframe.
     </summary>
   </metric>
   <metric name="SubFrame.MobileFriendliness.ViewportDeviceWidth" enum="Boolean">
+    <obsolete>
+      Deprecated 12/2022.
+    </obsolete>
     <summary>
       Whether the width of the viewport is specified as device-width or not, in
       the AMP subframe.
     </summary>
   </metric>
   <metric name="SubFrame.MobileFriendliness.ViewportHardcodedWidth">
+    <obsolete>
+      Deprecated 12/2022.
+    </obsolete>
     <summary>
       Specified hardcoded viewport width in CSS pixels, in the AMP subframe.
     </summary>
   </metric>
   <metric name="SubFrame.MobileFriendliness.ViewportInitialScaleX10">
+    <obsolete>
+      Deprecated 12/2022.
+    </obsolete>
     <summary>
       Specified initial viewport scaling, in the AMP subframe, multiplied by 10.
       1 means 0.1, 100 means 10. [1-100].
@@ -12627,6 +12648,9 @@
     </summary>
   </metric>
   <metric name="BadTapTargetsRatio">
+    <obsolete>
+      Deprecated 12/2022 in favor of MobileFriendliness.TappedBadTargets.
+    </obsolete>
     <summary>
       Percentage of tap targets whose center position is within another tap
       target (expanded by a margin). The detail of the algorithm is
diff --git a/tools/perf/contrib/power/battery.py b/tools/perf/contrib/power/battery.py
index d305d58..f219e848 100644
--- a/tools/perf/contrib/power/battery.py
+++ b/tools/perf/contrib/power/battery.py
@@ -4,13 +4,13 @@
 
 from core import platforms
 import contrib.power.stories as stories
-from contrib.power.power_perf_benchmark_base import _PowerPerfBenchmarkBase
+from contrib.power.power_perf_benchmark_base import PowerPerfBenchmarkBase
 from telemetry import benchmark
 from telemetry import story
 
 
 @benchmark.Info(emails=['chrometto-team@google.com'])
-class ContribPowerBattery(_PowerPerfBenchmarkBase):
+class ContribPowerBattery(PowerPerfBenchmarkBase):
 
   SUPPORTED_PLATFORMS = [story.expectations.ALL_ANDROID]
   SUPPORTED_PLATFORM_TAGS = [platforms.ANDROID]
diff --git a/tools/perf/contrib/power/ipc.py b/tools/perf/contrib/power/ipc.py
index 45b3f077..0e7dcd2 100644
--- a/tools/perf/contrib/power/ipc.py
+++ b/tools/perf/contrib/power/ipc.py
@@ -4,13 +4,13 @@
 
 from core import platforms
 import contrib.power.stories as stories
-from contrib.power.power_perf_benchmark_base import _PowerPerfBenchmarkBase
+from contrib.power.power_perf_benchmark_base import PowerPerfBenchmarkBase
 from telemetry import benchmark
 from telemetry import story
 
 
 @benchmark.Info(emails=['chrometto-team@google.com'])
-class ContribPowerIpc(_PowerPerfBenchmarkBase):
+class ContribPowerIpc(PowerPerfBenchmarkBase):
 
   SUPPORTED_PLATFORMS = [story.expectations.ALL_ANDROID]
   SUPPORTED_PLATFORM_TAGS = [platforms.ANDROID]
diff --git a/tools/perf/contrib/power/perf_profile.py b/tools/perf/contrib/power/perf_profile.py
index 4374f0d6e..ad91a84 100644
--- a/tools/perf/contrib/power/perf_profile.py
+++ b/tools/perf/contrib/power/perf_profile.py
@@ -3,14 +3,14 @@
 # found in the LICENSE file.
 
 import contrib.power.stories as stories
-from contrib.power.power_perf_benchmark_base import _PowerPerfBenchmarkBase
+from contrib.power.power_perf_benchmark_base import PowerPerfBenchmarkBase
 from core import platforms
 from telemetry import benchmark
 from telemetry import story
 
 
 @benchmark.Info(emails=['chrometto-team@google.com'])
-class ContribPowerPerfProfile(_PowerPerfBenchmarkBase):
+class ContribPowerPerfProfile(PowerPerfBenchmarkBase):
 
   SUPPORTED_PLATFORMS = [story.expectations.ALL_ANDROID]
   SUPPORTED_PLATFORM_TAGS = [platforms.ANDROID]
diff --git a/tools/perf/contrib/power/power_perf_benchmark_base.py b/tools/perf/contrib/power/power_perf_benchmark_base.py
index 2da0b3e..89ad5050 100644
--- a/tools/perf/contrib/power/power_perf_benchmark_base.py
+++ b/tools/perf/contrib/power/power_perf_benchmark_base.py
@@ -6,9 +6,9 @@
 from telemetry.web_perf import timeline_based_measurement
 
 
-class _PowerPerfBenchmarkBase(PerfBenchmark):
+class PowerPerfBenchmarkBase(PerfBenchmark):
   def __init__(self, *args, **kwargs):
-    super(_PowerPerfBenchmarkBase, self).__init__(*args, **kwargs)
+    super(PowerPerfBenchmarkBase, self).__init__(*args, **kwargs)
     # The browser selected for benchmarking.
     self._browser_package = None
 
@@ -16,7 +16,7 @@
     raise NotImplementedError("GetTraceConfig not implemented")
 
   def CustomizeOptions(self, finder_options, possible_browser=None):
-    super(_PowerPerfBenchmarkBase,
+    super(PowerPerfBenchmarkBase,
           self).CustomizeOptions(finder_options, possible_browser)
 
     if finder_options is None or possible_browser is None:
diff --git a/tools/perf/contrib/power/wakeups.py b/tools/perf/contrib/power/wakeups.py
index ab3e6f4f..810c620 100644
--- a/tools/perf/contrib/power/wakeups.py
+++ b/tools/perf/contrib/power/wakeups.py
@@ -4,13 +4,13 @@
 
 from core import platforms
 import contrib.power.stories as stories
-from contrib.power.power_perf_benchmark_base import _PowerPerfBenchmarkBase
+from contrib.power.power_perf_benchmark_base import PowerPerfBenchmarkBase
 from telemetry import benchmark
 from telemetry import story
 
 
 @benchmark.Info(emails=['chrometto-team@google.com'])
-class ContribPowerWakeups(_PowerPerfBenchmarkBase):
+class ContribPowerWakeups(PowerPerfBenchmarkBase):
 
   SUPPORTED_PLATFORMS = [story.expectations.ALL_ANDROID]
   SUPPORTED_PLATFORM_TAGS = [platforms.ANDROID]
diff --git a/tools/perf/core/benchmark_finders.py b/tools/perf/core/benchmark_finders.py
index f692b19..06629f6 100644
--- a/tools/perf/core/benchmark_finders.py
+++ b/tools/perf/core/benchmark_finders.py
@@ -65,6 +65,8 @@
       # other benchmarks, and not a standalone benchmark.
       'perf_benchmark_with_profiling.PerfBenchmarkWithProfiling',
       'perf_benchmark_with_profiling_unittest.PerfBenchmarkForTesting',
+      # This benchmark is a base class for power measurements.
+      'power_perf_benchmark_base.PowerPerfBenchmarkBase',
   }
 
   benchmarks = list(
diff --git a/tools/perf/core/bot_platforms.py b/tools/perf/core/bot_platforms.py
index 6c3af8b..d83fe216 100644
--- a/tools/perf/core/bot_platforms.py
+++ b/tools/perf/core/bot_platforms.py
@@ -507,7 +507,6 @@
 ])
 _FUCHSIA_ATLAS_PERF_FYI_BENCHMARK_CONFIGS = PerfSuite([
     _GetBenchmarkConfig('system_health.common_desktop'),
-    _GetBenchmarkConfig('power.desktop'),
     _GetBenchmarkConfig('speedometer'),
     _GetBenchmarkConfig('speedometer2'),
     _GetBenchmarkConfig('jetstream'),
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index 300c29e..d1041fc 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/v31.0/linux-arm64/trace_processor_shell"
         },
         "win": {
-            "hash": "d769eee21661aeccb865b4315136a8d61b0374da",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/591266c4ffd77425d8da9f8b982941e23faf115d/trace_processor_shell.exe"
+            "hash": "2144e9b098f496dabeb62b99c9349dd5fd5b42b8",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/b5161f639122c92cba8384ed62f2de376f6d1e73/trace_processor_shell.exe"
         },
         "linux_arm": {
             "hash": "6373f26144aad58f230d11d6a91efda5a09c9873",
             "full_remote_path": "perfetto-luci-artifacts/v31.0/linux-arm/trace_processor_shell"
         },
         "mac": {
-            "hash": "67067844bff4df77622e76d06a6f94202c89c68c",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/mac/df18f444084aa1704b313ef9430026e81afb9677/trace_processor_shell"
+            "hash": "ae6fddc2a0dd8dc0d17b06b4a943f6e7c7b9409c",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/mac/b5161f639122c92cba8384ed62f2de376f6d1e73/trace_processor_shell"
         },
         "mac_arm64": {
             "hash": "5f47ee79e59d00bf3889d30ca52315522c158040",
             "full_remote_path": "perfetto-luci-artifacts/v31.0/mac-arm64/trace_processor_shell"
         },
         "linux": {
-            "hash": "b96d4205afb7cff52ed1ff6f4667bcf18ee9a202",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/591266c4ffd77425d8da9f8b982941e23faf115d/trace_processor_shell"
+            "hash": "65a02708c56e73c5e98fb54a4b238d4a96ef6963",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/b5161f639122c92cba8384ed62f2de376f6d1e73/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/tools/perf/core/shard_maps/fuchsia-perf-atlas-fyi_map.json b/tools/perf/core/shard_maps/fuchsia-perf-atlas-fyi_map.json
index f471afd..76cb5507 100644
--- a/tools/perf/core/shard_maps/fuchsia-perf-atlas-fyi_map.json
+++ b/tools/perf/core/shard_maps/fuchsia-perf-atlas-fyi_map.json
@@ -22,9 +22,6 @@
             "jetstream2": {
                 "abridged": false
             },
-            "power.desktop": {
-                "abridged": false
-            },
             "speedometer": {
                 "abridged": false
             },
@@ -32,7 +29,7 @@
                 "abridged": false
             },
             "system_health.common_desktop": {
-                "end": 14,
+                "end": 24,
                 "abridged": false
             }
         }
@@ -40,8 +37,8 @@
     "2": {
         "benchmarks": {
             "system_health.common_desktop": {
-                "begin": 14,
-                "end": 48,
+                "begin": 24,
+                "end": 53,
                 "abridged": false
             }
         }
@@ -49,20 +46,20 @@
     "3": {
         "benchmarks": {
             "system_health.common_desktop": {
-                "begin": 48,
+                "begin": 53,
                 "abridged": false
             }
         }
     },
     "extra_infos": {
-        "num_stories": 101,
-        "predicted_min_shard_time": 330,
+        "num_stories": 86,
+        "predicted_min_shard_time": 280,
         "predicted_min_shard_index": 1,
         "predicted_max_shard_time": 900.0,
         "predicted_max_shard_index": 0,
         "shard #0": 900.0,
-        "shard #1": 330,
-        "shard #2": 340,
-        "shard #3": 330
+        "shard #1": 280,
+        "shard #2": 290,
+        "shard #3": 280
     }
 }
\ No newline at end of file
diff --git a/tools/rust/build_crubit.py b/tools/rust/build_crubit.py
index 429bec44..a4470c6 100755
--- a/tools/rust/build_crubit.py
+++ b/tools/rust/build_crubit.py
@@ -193,10 +193,12 @@
         help='skip Crubit git checkout. Useful for trying local changes')
     args, rest = parser.parse_known_args()
 
-    # Fetch GCC package to build against same libstdc++ as Clang. This function
-    # will only download it if necessary.
     args.gcc_toolchain = None
-    MaybeDownloadHostGcc(args)
+    if sys.platform.startswith('linux'):
+        # Fetch GCC package to build against same libstdc++ as Clang. This
+        # function will only download it if necessary, and it will set the
+        # `args.gcc_toolchain` if so.
+        MaybeDownloadHostGcc(args)
 
     if not args.skip_checkout:
         CheckoutCrubit(CRUBIT_REVISION, CRUBIT_SRC_DIR)
diff --git a/tools/rust/build_rust.py b/tools/rust/build_rust.py
index 8c6322a5..4669e1b 100755
--- a/tools/rust/build_rust.py
+++ b/tools/rust/build_rust.py
@@ -170,8 +170,12 @@
     # * https://reviews.llvm.org/D116528
     RUSTENV['RUSTFLAGS_BOOTSTRAP'] = (
         f'-Clinker={clang_path} -Clink-arg=-fuse-ld=lld '
-        f'-Clink-arg=-Wl,--no-gc-sections -Clink-arg={gcc_toolchain_flag} '
-        f'-L native={gcc_toolchain_path}/lib64')
+        f'-Clink-arg=-Wl,--no-gc-sections)')
+    if gcc_toolchain_flag:
+        RUSTENV['RUSTFLAGS_BOOTSTRAP'] += f' -Clink-arg={gcc_toolchain_flag} '
+    if gcc_toolchain_path:
+        RUSTENV['RUSTFLAGS_BOOTSTRAP'] += (
+            f' -L native={gcc_toolchain_path}/lib64')
     RUSTENV['RUSTFLAGS_NOT_BOOTSTRAP'] = RUSTENV['RUSTFLAGS_BOOTSTRAP']
     os.chdir(RUST_SRC_DIR)
     cmd = [sys.executable, 'x.py', sub]
@@ -261,10 +265,12 @@
     if args.fetch_llvm_libs:
         UpdatePackage('clang-libs', GetDefaultHostOs())
 
-    # Fetch GCC package to build against same libstdc++ as Clang. This function
-    # will only download it if necessary.
     args.gcc_toolchain = None
-    build.MaybeDownloadHostGcc(args)
+    if sys.platform.startswith('linux'):
+        # Fetch GCC package to build against same libstdc++ as Clang. This
+        # function will only download it if necessary, and it will set the
+        # `args.gcc_toolchain` if so.
+        build.MaybeDownloadHostGcc(args)
 
     # Set up config.toml in Rust source tree to configure build.
     Configure(llvm_libs_root)
diff --git a/tools/traffic_annotation/summary/annotations.xml b/tools/traffic_annotation/summary/annotations.xml
index f7d24c40..4d9be508 100644
--- a/tools/traffic_annotation/summary/annotations.xml
+++ b/tools/traffic_annotation/summary/annotations.xml
@@ -33,7 +33,7 @@
  <item id="cablev2_websocket_from_authenticator" added_in_milestone="87" content_hash_code="0724f93c" os_list="linux,windows,chromeos,android" file_path="device/fido/cable/v2_authenticator.cc" />
  <item id="cablev2_websocket_from_client" added_in_milestone="86" content_hash_code="06f37377" os_list="windows,linux,chromeos" file_path="device/fido/cable/fido_tunnel_device.cc" />
  <item id="captive_portal_service" added_in_milestone="62" content_hash_code="04375eac" os_list="linux,windows,chromeos" file_path="components/captive_portal/content/captive_portal_service.cc" />
- <item id="cast_socket" added_in_milestone="66" content_hash_code="028e5273" os_list="linux,windows,chromeos,android" file_path="components/media_router/common/providers/cast/channel/cast_socket.cc" />
+ <item id="cast_socket" added_in_milestone="66" content_hash_code="01cbc995" os_list="linux,windows,chromeos,android" file_path="components/media_router/common/providers/cast/channel/cast_socket.cc" />
  <item id="cast_udp_socket" added_in_milestone="66" content_hash_code="047d6b2d" os_list="linux,windows,chromeos,android" file_path="components/mirroring/service/udp_socket_client.cc" />
  <item id="cast_udp_transport" added_in_milestone="65" content_hash_code="066a8189" os_list="linux,windows,chromeos,android" file_path="media/cast/net/udp_transport_impl.cc" />
  <item id="certificate_verifier_url_loader" added_in_milestone="80" content_hash_code="05856cc0" os_list="linux,windows,chromeos,android" file_path="services/cert_verifier/cert_net_url_loader/cert_net_fetcher_url_loader.cc" />
@@ -297,7 +297,7 @@
  <item id="network_traversal_ice_config_fetcher" added_in_milestone="98" content_hash_code="0065f89d" os_list="chromeos" file_path="chrome/browser/nearby_sharing/network_traversal_ice_config_fetcher.cc" />
  <item id="tachyon_ice_config_fetcher" added_in_milestone="98" content_hash_code="01e878b0" os_list="chromeos" file_path="chrome/browser/nearby_sharing/tachyon_ice_config_fetcher.cc" />
  <item id="supervised_users_denylist" added_in_milestone="98" content_hash_code="01cebaff" os_list="chromeos,android" file_path="chrome/browser/supervised_user/supervised_user_service.cc" />
- <item id="app_suggestion_get_favicon" added_in_milestone="98" content_hash_code="07fca800" os_list="chromeos" file_path="chrome/browser/ui/app_list/search/app_service_app_result.cc" />
+ <item id="app_suggestion_get_favicon" added_in_milestone="98" content_hash_code="07fca800" os_list="chromeos" file_path="chrome/browser/ash/app_list/search/app_service_app_result.cc" />
  <item id="launcher_item_suggest" added_in_milestone="98" content_hash_code="04a4041e" os_list="chromeos" file_path="chrome/browser/ash/app_list/search/files/item_suggest_cache.cc" />
  <item id="ambient_client" added_in_milestone="98" content_hash_code="062d821f" os_list="chromeos" file_path="chrome/browser/ui/ash/ambient/ambient_client_impl.cc" />
  <item id="calendar_get_events" added_in_milestone="98" content_hash_code="0603f52a" os_list="chromeos" file_path="chrome/browser/ui/ash/calendar/calendar_keyed_service.cc" />
diff --git a/tools/typescript/definitions/bookmarks.d.ts b/tools/typescript/definitions/bookmarks.d.ts
index 489d1ba..07fb946 100644
--- a/tools/typescript/definitions/bookmarks.d.ts
+++ b/tools/typescript/definitions/bookmarks.d.ts
@@ -54,9 +54,9 @@
 
       export function search(
           query: string|{
-            query: string | undefined,
-            url: string|undefined,
-            title: string|undefined,
+            query?: string,
+            url?: string,
+            title?: string,
           },
           callback: (p1: BookmarkTreeNode[]) => void): void;
 
diff --git a/tools/typescript/definitions/passwords_private.d.ts b/tools/typescript/definitions/passwords_private.d.ts
index 2d9d1984..442ad11f 100644
--- a/tools/typescript/definitions/passwords_private.d.ts
+++ b/tools/typescript/definitions/passwords_private.d.ts
@@ -100,8 +100,14 @@
         isMuted: boolean;
       }
 
+      export interface DomainInfo {
+        name: string;
+        url: string;
+      }
+
       export interface PasswordUiEntry {
         urls: UrlCollection;
+        affiliatedDomains?: DomainInfo[];
         username: string;
         password?: string;
         federationText?: string;
diff --git a/ui/accessibility/accessibility_features.cc b/ui/accessibility/accessibility_features.cc
index 83dc76e..bd42918 100644
--- a/ui/accessibility/accessibility_features.cc
+++ b/ui/accessibility/accessibility_features.cc
@@ -131,7 +131,7 @@
 
 BASE_FEATURE(kExperimentalAccessibilityDictationWithPumpkin,
              "ExperimentalAccessibilityDictationWithPumpkin",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 bool IsExperimentalAccessibilityDictationWithPumpkinEnabled() {
   return base::FeatureList::IsEnabled(
@@ -140,7 +140,7 @@
 
 BASE_FEATURE(kExperimentalAccessibilityDictationMoreCommands,
              "ExperimentalAccessibilityDictationMoreCommands",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 bool IsExperimentalAccessibilityDictationMoreCommandsEnabled() {
   return base::FeatureList::IsEnabled(
diff --git a/ui/accessibility/platform/ax_platform_node_auralinux.cc b/ui/accessibility/platform/ax_platform_node_auralinux.cc
index 012f439a..a7b3756 100644
--- a/ui/accessibility/platform/ax_platform_node_auralinux.cc
+++ b/ui/accessibility/platform/ax_platform_node_auralinux.cc
@@ -3305,7 +3305,7 @@
       continue;
 
     std::set<AXPlatformNode*> target_ids =
-        GetDelegate()->GetReverseRelations(relation.attribute);
+        GetDelegate()->GetSourceNodesForReverseRelations(relation.attribute);
     for (AXPlatformNode* target : target_ids) {
       AddRelationToSet(relation_set, relation.reverse_relation.value(), target);
     }
@@ -3324,7 +3324,7 @@
       continue;
 
     std::set<AXPlatformNode*> reverse_target_ids =
-        GetDelegate()->GetReverseRelations(relation.attribute);
+        GetDelegate()->GetSourceNodesForReverseRelations(relation.attribute);
     for (AXPlatformNode* target : reverse_target_ids) {
       AddRelationToSet(relation_set, relation.reverse_relation.value(), target);
     }
diff --git a/ui/accessibility/platform/ax_platform_node_base.cc b/ui/accessibility/platform/ax_platform_node_base.cc
index 70192cb..cc623757 100644
--- a/ui/accessibility/platform/ax_platform_node_base.cc
+++ b/ui/accessibility/platform/ax_platform_node_base.cc
@@ -839,7 +839,7 @@
   // The node represents a structured annotation if it can trace back to a
   // target node that is being annotated.
   std::set<AXPlatformNode*> reverse_relations =
-      GetDelegate()->GetReverseRelations(
+      GetDelegate()->GetSourceNodesForReverseRelations(
           ax::mojom::IntListAttribute::kDetailsIds);
 
   return !reverse_relations.empty();
diff --git a/ui/accessibility/platform/ax_platform_node_delegate.cc b/ui/accessibility/platform/ax_platform_node_delegate.cc
index 23b26e0a..a7011fd 100644
--- a/ui/accessibility/platform/ax_platform_node_delegate.cc
+++ b/ui/accessibility/platform/ax_platform_node_delegate.cc
@@ -443,7 +443,8 @@
   return IsDescendantOfAtomicTextField();
 }
 
-std::set<AXPlatformNode*> AXPlatformNodeDelegate::GetReverseRelations(
+std::set<AXPlatformNode*>
+AXPlatformNodeDelegate::GetSourceNodesForReverseRelations(
     ax::mojom::IntAttribute attr) {
   // TODO(accessibility) Implement these if views ever use relations more
   // widely. The use so far has been for the Omnibox to the suggestion
@@ -453,7 +454,8 @@
   return std::set<AXPlatformNode*>();
 }
 
-std::set<AXPlatformNode*> AXPlatformNodeDelegate::GetReverseRelations(
+std::set<AXPlatformNode*>
+AXPlatformNodeDelegate::GetSourceNodesForReverseRelations(
     ax::mojom::IntListAttribute attr) {
   return std::set<AXPlatformNode*>();
 }
diff --git a/ui/accessibility/platform/ax_platform_node_delegate.h b/ui/accessibility/platform/ax_platform_node_delegate.h
index 52cce0a..2a6d3aa 100644
--- a/ui/accessibility/platform/ax_platform_node_delegate.h
+++ b/ui/accessibility/platform/ax_platform_node_delegate.h
@@ -500,17 +500,13 @@
   // Given an attribute which could be used to establish a reverse relationship
   // between this node and a set of other nodes (AKA the source nodes), return
   // the list of source nodes if any.
-  //
-  // TODO(accessibility): Rename this to `GetSourceNodesForReverseRelation`.
-  virtual std::set<AXPlatformNode*> GetReverseRelations(
+  virtual std::set<AXPlatformNode*> GetSourceNodesForReverseRelations(
       ax::mojom::IntAttribute attr);
 
   // Given an attribute which could be used to establish a reverse relationship
   // between this node and a set of other nodes (AKA the source nodes), return
   // the list of source nodes if any.
-  //
-  // TODO(accessibility): Rename this to `GetSourceNodesForReverseRelation`.
-  virtual std::set<AXPlatformNode*> GetReverseRelations(
+  virtual std::set<AXPlatformNode*> GetSourceNodesForReverseRelations(
       ax::mojom::IntListAttribute attr);
 
   // Returns the string representation of the unique ID assigned by the author,
diff --git a/ui/accessibility/platform/ax_platform_node_win.cc b/ui/accessibility/platform/ax_platform_node_win.cc
index 678ff1e..999ed8a 100644
--- a/ui/accessibility/platform/ax_platform_node_win.cc
+++ b/ui/accessibility/platform/ax_platform_node_win.cc
@@ -501,7 +501,7 @@
 SAFEARRAY* AXPlatformNodeWin::CreateUIAElementsArrayForReverseRelation(
     const ax::mojom::IntListAttribute& attribute) {
   std::set<AXPlatformNode*> reverse_relations =
-      GetDelegate()->GetReverseRelations(attribute);
+      GetDelegate()->GetSourceNodesForReverseRelations(attribute);
 
   std::vector<int32_t> id_list;
   std::transform(
@@ -2485,7 +2485,7 @@
   WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_ANNOTATION_GET_TARGET);
   UIA_VALIDATE_CALL_1_ARG(target);
   std::set<AXPlatformNode*> reverse_relations =
-      GetDelegate()->GetReverseRelations(
+      GetDelegate()->GetSourceNodesForReverseRelations(
           ax::mojom::IntListAttribute::kDetailsIds);
 
   // If there is no reverse relation target, IAnnotationProvider
diff --git a/ui/accessibility/platform/ax_platform_relation_win.cc b/ui/accessibility/platform/ax_platform_relation_win.cc
index 8fe7371..ae5e4aa 100644
--- a/ui/accessibility/platform/ax_platform_relation_win.cc
+++ b/ui/accessibility/platform/ax_platform_relation_win.cc
@@ -175,7 +175,7 @@
        int_attributes_with_reverse_relations) {
     std::wstring relation = GetIA2ReverseRelationFromIntAttr(int_attribute);
     std::set<AXPlatformNode*> targets =
-        delegate->GetReverseRelations(int_attribute);
+        delegate->GetSourceNodesForReverseRelations(int_attribute);
     // Erase reflexive relations.
     targets.erase(node);
     if (targets.size()) {
@@ -222,7 +222,7 @@
     std::wstring relation =
         GetIA2ReverseRelationFromIntListAttr(intlist_attribute);
     std::set<AXPlatformNode*> targets =
-        delegate->GetReverseRelations(intlist_attribute);
+        delegate->GetSourceNodesForReverseRelations(intlist_attribute);
     // Erase reflexive relations.
     targets.erase(node);
     if (targets.size()) {
diff --git a/ui/accessibility/platform/test_ax_node_wrapper.cc b/ui/accessibility/platform/test_ax_node_wrapper.cc
index 7aba1582d..f56f71e 100644
--- a/ui/accessibility/platform/test_ax_node_wrapper.cc
+++ b/ui/accessibility/platform/test_ax_node_wrapper.cc
@@ -864,13 +864,13 @@
   return node_->HasVisibleCaretOrSelection();
 }
 
-std::set<AXPlatformNode*> TestAXNodeWrapper::GetReverseRelations(
+std::set<AXPlatformNode*> TestAXNodeWrapper::GetSourceNodesForReverseRelations(
     ax::mojom::IntAttribute attr) {
   DCHECK(IsNodeIdIntAttribute(attr));
   return GetNodesForNodeIds(tree_->GetReverseRelations(attr, GetData().id));
 }
 
-std::set<AXPlatformNode*> TestAXNodeWrapper::GetReverseRelations(
+std::set<AXPlatformNode*> TestAXNodeWrapper::GetSourceNodesForReverseRelations(
     ax::mojom::IntListAttribute attr) {
   DCHECK(IsNodeIdIntListAttribute(attr));
   return GetNodesForNodeIds(tree_->GetReverseRelations(attr, GetData().id));
diff --git a/ui/accessibility/platform/test_ax_node_wrapper.h b/ui/accessibility/platform/test_ax_node_wrapper.h
index 5c80d90e..a68514a 100644
--- a/ui/accessibility/platform/test_ax_node_wrapper.h
+++ b/ui/accessibility/platform/test_ax_node_wrapper.h
@@ -146,9 +146,9 @@
   bool ShouldIgnoreHoveredStateForTesting() override;
   const ui::AXUniqueId& GetUniqueId() const override;
   bool HasVisibleCaretOrSelection() const override;
-  std::set<AXPlatformNode*> GetReverseRelations(
+  std::set<AXPlatformNode*> GetSourceNodesForReverseRelations(
       ax::mojom::IntAttribute attr) override;
-  std::set<AXPlatformNode*> GetReverseRelations(
+  std::set<AXPlatformNode*> GetSourceNodesForReverseRelations(
       ax::mojom::IntListAttribute attr) override;
   bool IsOrderedSetItem() const override;
   bool IsOrderedSet() const override;
diff --git a/ui/chromeos/strings/network/network_element_localized_strings_provider.cc b/ui/chromeos/strings/network/network_element_localized_strings_provider.cc
index ecdc68b..a5199051 100644
--- a/ui/chromeos/strings/network/network_element_localized_strings_provider.cc
+++ b/ui/chromeos/strings/network/network_element_localized_strings_provider.cc
@@ -319,6 +319,7 @@
       {"apnMoreActionsTitle", IDS_SETTINGS_APN_MORE_ACTIONS_TITLE},
       {"apnDetailAddApnDialogTitle", IDS_SETTINGS_ADD_APN_DIALOG_TITLE},
       {"apnDetailViewApnDialogTitle", IDS_SETTINGS_VIEW_APN_DIALOG_TITLE},
+      {"apnDetailEditApnDialogTitle", IDS_SETTINGS_EDIT_APN_DIALOG_TITLE},
       {"apnDetailAdvancedSettings", IDS_SETTINGS_APN_DIALOG_ADVANCED_SETTING},
       {"apnDetailApnTypes", IDS_SETTINGS_APN_DIALOG_APN_TYPES},
       {"apnDetailApnTypeDefault",
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 23985ad5..ddeb76d8 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
@@ -6,7 +6,7 @@
 
 import {getUniqueParents} from '../../common/js/api.js';
 import {AsyncQueue, RateLimiter} from '../../common/js/async_util.js';
-import {notifications} from '../../common/js/notifications_browser_proxy.js';
+import {notifications} from '../../common/js/notifications.js';
 import {ProgressCenterItem, ProgressItemState, ProgressItemType} from '../../common/js/progress_center_common.js';
 import {getFilesAppIconURL, toFilesAppURL} from '../../common/js/url_constants.js';
 import {str, strf, util} from '../../common/js/util.js';
diff --git a/ui/file_manager/file_manager/background/js/progress_center.js b/ui/file_manager/file_manager/background/js/progress_center.js
index 4dc149e..c894ff2 100644
--- a/ui/file_manager/file_manager/background/js/progress_center.js
+++ b/ui/file_manager/file_manager/background/js/progress_center.js
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 import {AsyncQueue} from '../../common/js/async_util.js';
-import {notifications} from '../../common/js/notifications_browser_proxy.js';
+import {notifications} from '../../common/js/notifications.js';
 import {ProgressCenterItem, ProgressItemState} from '../../common/js/progress_center_common.js';
 import {getFilesAppIconURL} from '../../common/js/url_constants.js';
 import {str} from '../../common/js/util.js';
diff --git a/ui/file_manager/file_manager/common/js/BUILD.gn b/ui/file_manager/file_manager/common/js/BUILD.gn
index eaea000..343214c 100644
--- a/ui/file_manager/file_manager/common/js/BUILD.gn
+++ b/ui/file_manager/file_manager/common/js/BUILD.gn
@@ -37,7 +37,7 @@
     ":lru_cache",
     ":metrics",
     ":metrics_base",
-    ":notifications_browser_proxy",
+    ":notifications",
     ":progress_center_common",
     ":recent_date_bucket",
     ":storage",
@@ -66,7 +66,7 @@
     ":metrics_base",
     ":mock_entry",
     ":mock_util",
-    ":notifications_browser_proxy",
+    ":notifications",
     ":progress_center_common",
     ":recent_date_bucket",
     ":storage",
@@ -257,7 +257,7 @@
   deps = [ ":util" ]
 }
 
-js_library("notifications_browser_proxy") {
+js_library("notifications") {
   externs_list =
       [ "//ui/file_manager/file_manager/externs/app_window_common.js" ]
 }
diff --git a/ui/file_manager/file_manager/common/js/entry_utils.ts b/ui/file_manager/file_manager/common/js/entry_utils.ts
index e149073..93bfd86 100644
--- a/ui/file_manager/file_manager/common/js/entry_utils.ts
+++ b/ui/file_manager/file_manager/common/js/entry_utils.ts
@@ -2,6 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import {EntryType, FileData} from '../../externs/ts/state.js';
+
+import type {VolumeEntry} from './files_app_entry_types.js';
+
 /**
  * Type guard used to identify if a generic FileSystemEntry is actually a
  * FileSystemDirectoryEntry.
@@ -19,3 +23,17 @@
     entry is FileSystemFileEntry {
   return entry.isFile;
 }
+
+/**
+ * Returns the native entry (aka FileEntry) from the Store. It returns `null`
+ * for entries that aren't native.
+ */
+export function getNativeEntry(fileData: FileData): Entry|null {
+  if (fileData.type === EntryType.FS_API) {
+    return fileData.entry as Entry;
+  }
+  if (fileData.type === EntryType.VOLUME_ROOT) {
+    return (fileData.entry as VolumeEntry).getNativeEntry();
+  }
+  return null;
+}
diff --git a/ui/file_manager/file_manager/common/js/notifications_browser_proxy.js b/ui/file_manager/file_manager/common/js/notifications.js
similarity index 100%
rename from ui/file_manager/file_manager/common/js/notifications_browser_proxy.js
rename to ui/file_manager/file_manager/common/js/notifications.js
diff --git a/ui/file_manager/file_manager/containers/search_container.ts b/ui/file_manager/file_manager/containers/search_container.ts
index 2aec05b3..f95793d 100644
--- a/ui/file_manager/file_manager/containers/search_container.ts
+++ b/ui/file_manager/file_manager/containers/search_container.ts
@@ -5,8 +5,8 @@
 import {CrInputElement} from 'chrome://resources/cr_elements/cr_input/cr_input.js';
 
 import {queryRequiredElement} from '../common/js/dom_utils.js';
-import {util} from '../common/js/util.js';
-import {PropStatus, SearchData, SearchFileType, SearchLocation, SearchOptions, SearchRecency, SearchStatus, State} from '../externs/ts/state.js';
+import {str, util} from '../common/js/util.js';
+import {PropStatus, SearchData, SearchFileType, SearchLocation, SearchOptions, SearchRecency, State} from '../externs/ts/state.js';
 import {SearchAutocompleteList} from '../foreground/js/ui/search_autocomplete_list.js';
 import {updateSearch} from '../state/actions.js';
 import {getStore, Store} from '../state/store.js';
@@ -215,6 +215,7 @@
     // Cache the last received search state for future comparisons.
     this.searchState_ = search;
     if (!search) {
+      this.closeSearch();
       return;
     }
     const query = search.query;
@@ -222,15 +223,15 @@
       this.setQuery(query);
     }
     if (util.isSearchV2Enabled()) {
-      const status = search.status;
-      if (status === PropStatus.STARTED && query) {
+      if (search.status === PropStatus.STARTED && query) {
         this.showOptions_();
-      } else if (status === SearchStatus.INACTIVE) {
-        this.hideOptions_();
       }
     }
   }
 
+  /**
+   * Hides the element that allows users to manipulate search options.
+   */
   private hideOptions_() {
     const element = this.getSearchOptionsElement_();
     if (element) {
@@ -238,6 +239,10 @@
     }
   }
 
+  /**
+   * Shows or creates the element that allows the user to manipulate search
+   * options.
+   */
   private showOptions_() {
     let element = this.getSearchOptionsElement_();
     if (!element) {
@@ -262,27 +267,69 @@
     this.optionsContainer_.appendChild(element);
 
     element.id = 'search-options';
-    // TODO(majewski): Get strings from loadTimeData.
-    element.getLocationSelector().setOptions([
-      {value: SearchLocation.EVERYWHERE, text: 'Everywhere'},
-      {value: SearchLocation.THIS_CHROMEBOOK, text: 'This Chromebook'},
-      {value: SearchLocation.THIS_FOLDER, text: 'This folder', default: true},
-    ]);
-    element.getRecencySelector().setOptions([
-      {value: SearchRecency.ANYTIME, text: 'Anytime'},
-      {value: SearchRecency.TODAY, text: 'Today'},
-      {value: SearchRecency.YESTERDAY, text: 'Yesterday'},
-      {value: SearchRecency.LAST_WEEK, text: 'Last week'},
-      {value: SearchRecency.LAST_MONTH, text: 'Last month'},
-      {value: SearchRecency.LAST_YEAR, text: 'Last year'},
-    ]);
-    element.getFileTypeSelector().setOptions([
-      {value: SearchFileType.ALL_TYPES, text: 'Any'},
-      {value: SearchFileType.AUDIO, text: 'Audio'},
-      {value: SearchFileType.DOCUMENTS, text: 'Documents'},
-      {value: SearchFileType.IMAGES, text: 'Images'},
-      {value: SearchFileType.VIDEOS, text: 'Videos'},
-    ]);
+    element.getLocationSelector().options = [
+      {
+        value: SearchLocation.EVERYWHERE,
+        text: str('SEARCH_OPTIONS_LOCATION_EVERYWHERE'),
+      },
+      {
+        value: SearchLocation.THIS_CHROMEBOOK,
+        text: str('SEARCH_OPTIONS_LOCATION_THIS_CHROMEBOOK'),
+      },
+      {
+        value: SearchLocation.THIS_FOLDER,
+        text: str('SEARCH_OPTIONS_LOCATION_THIS_FOLDER'),
+        default: true,
+      },
+    ];
+    element.getRecencySelector().options = [
+      {
+        value: SearchRecency.ANYTIME,
+        text: str('SEARCH_OPTIONS_RECENCY_ALL_TIME'),
+      },
+      {
+        value: SearchRecency.TODAY,
+        text: str('SEARCH_OPTIONS_RECENCY_TODAY'),
+      },
+      {
+        value: SearchRecency.YESTERDAY,
+        text: str('SEARCH_OPTIONS_RECENCY_YESTERDAY'),
+      },
+      {
+        value: SearchRecency.LAST_WEEK,
+        text: str('SEARCH_OPTIONS_RECENCY_LAST_WEEK'),
+      },
+      {
+        value: SearchRecency.LAST_MONTH,
+        text: str('SEARCH_OPTIONS_RECENCY_LAST_MONTH'),
+      },
+      {
+        value: SearchRecency.LAST_YEAR,
+        text: str('SEARCH_OPTIONS_RECENCY_LAST_YEAR'),
+      },
+    ];
+    element.getFileTypeSelector().options = [
+      {
+        value: SearchFileType.ALL_TYPES,
+        text: str('SEARCH_OPTIONS_TYPES_ALL_TYPES'),
+      },
+      {
+        value: SearchFileType.AUDIO,
+        text: str('SEARCH_OPTIONS_TYPES_AUDIO'),
+      },
+      {
+        value: SearchFileType.DOCUMENTS,
+        text: str('SEARCH_OPTIONS_TYPES_DOCUMENTS'),
+      },
+      {
+        value: SearchFileType.IMAGES,
+        text: str('SEARCH_OPTIONS_TYPES_IMAGES'),
+      },
+      {
+        value: SearchFileType.VIDEOS,
+        text: str('SEARCH_OPTIONS_TYPES_VIDEOS'),
+      },
+    ];
     element.addEventListener(
         SEARCH_OPTIONS_CHANGED, this.onOptionsChanged_.bind(this));
     this.searchOptions_ = element;
@@ -426,13 +473,7 @@
     // 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) {
-      if (util.isSearchV2Enabled()) {
-        this.store_.dispatch(updateSearch({
-          query: undefined,  // do not change
-          status: SearchStatus.INACTIVE,
-          options: undefined,  // do not change
-        }));
-      }
+      this.hideOptions_();
       this.inputState_ = SearchInputState.CLOSING;
       this.inputElement_.tabIndex = -1;
       this.inputElement_.disabled = true;
diff --git a/ui/file_manager/file_manager/externs/ts/state.js b/ui/file_manager/file_manager/externs/ts/state.js
index d8515bd..c0559e6 100644
--- a/ui/file_manager/file_manager/externs/ts/state.js
+++ b/ui/file_manager/file_manager/externs/ts/state.js
@@ -81,16 +81,6 @@
 };
 
 /**
- * The additional property states understood by the search container. When the
- * user actions cause search elements to be hidden, the search state in the
- * store becomes INACTIVE.
- * @enum {string}
- */
-export const SearchStatus = {
-  INACTIVE: 'INACTIVE',
-};
-
-/**
  * Task type is the source of the task, or what type of the app is this type
  * from. It has to match the `taskType` returned in the FileManagerPrivate.
  *
@@ -242,7 +232,7 @@
 /**
  * Data for search. It should be empty `{}` when the user isn't searching.
  * @typedef {{
- *   status: (PropStatus|SearchStatus|undefined),
+ *   status: (PropStatus|undefined),
  *   query: (string|undefined),
  *   options: (!SearchOptions|undefined),
  * }}
diff --git a/ui/file_manager/file_manager/foreground/js/task_controller.ts b/ui/file_manager/file_manager/foreground/js/task_controller.ts
index 34e8633..6b980f8 100644
--- a/ui/file_manager/file_manager/foreground/js/task_controller.ts
+++ b/ui/file_manager/file_manager/foreground/js/task_controller.ts
@@ -96,6 +96,16 @@
   }
 
   /**
+   * Exposes the TaskHistory instance for the ActionsProducer.
+   *
+   * NOTE: This is a temporary workaround until the TaskHistory is migrated to
+   * the store.
+   */
+  get taskHistory(): TaskHistory {
+    return this.taskHistory_;
+  }
+
+  /**
    * Task combobox handler.
    *
    * @param event Event containing task which was clicked.
diff --git a/ui/file_manager/file_manager/lib/actions_producer.ts b/ui/file_manager/file_manager/lib/actions_producer.ts
index 6ec9386..8ed3f09 100644
--- a/ui/file_manager/file_manager/lib/actions_producer.ts
+++ b/ui/file_manager/file_manager/lib/actions_producer.ts
@@ -26,11 +26,15 @@
 /**
  * This is the type of the generator that is returned by the ActionsProducer.
  *
- * This is used to enforce the same type for yield and return, since the
- * built-in template accepts different types for both.
+ * This is used to enforce the type for the yield.
+ * The return should be always void, because consuming the generator using `for
+ * await()` doesn't consume the value from the return.
+ *
+ * Yielding `undefined` or a bare yield like `yield;` gives the concurrency
+ * model a chance to interrupt the ActionsProducer, when it's invalidated.
  * @template T the type for the action yielded by the ActionsProducer.
  */
-export type ActionsProducerGen<T> = AsyncGenerator<void|T, void|T>;
+export type ActionsProducerGen<T> = AsyncGenerator<void|T, void>;
 
 /**
  * Exception used to stop ActionsProducer when they're no longer valid.
diff --git a/ui/file_manager/file_manager/lib/concurrency_models.ts b/ui/file_manager/file_manager/lib/concurrency_models.ts
index 937dc126..1a30fc64 100644
--- a/ui/file_manager/file_manager/lib/concurrency_models.ts
+++ b/ui/file_manager/file_manager/lib/concurrency_models.ts
@@ -86,10 +86,12 @@
         yield producedAction;
       }
     } catch (error) {
-      if (error instanceof ConcurrentActionInvalidatedError) {
-        // This error we don't want to clear the `inFlightKey`.
-        throw error;
+      if (!(error instanceof ConcurrentActionInvalidatedError)) {
+        // This error we don't want to clear the `inFlightKey`, because it's
+        // pointing to the actually valid AP instance.
+        inFlightKey = null;
       }
+      throw error;
     }
 
     // Clear the key if it wasn't invalidated.
diff --git a/ui/file_manager/file_manager/state/actions.ts b/ui/file_manager/file_manager/state/actions.ts
index 050401e..a622b97 100644
--- a/ui/file_manager/file_manager/state/actions.ts
+++ b/ui/file_manager/file_manager/state/actions.ts
@@ -6,7 +6,7 @@
 import {BaseAction} from '../lib/base_store.js';
 
 import {ClearStaleCachedEntriesAction} from './actions/all_entries.js';
-import {ChangeDirectoryAction, ChangeSelectionAction} from './actions/current_directory.js';
+import {ChangeDirectoryAction, ChangeFileTasksAction, ChangeSelectionAction} from './actions/current_directory.js';
 
 /**
  * Union of all types of Actions in Files app.
@@ -16,13 +16,14 @@
  * https://mariusschulz.com/blog/tagged-union-types-in-typescript
  */
 export type Action = ChangeDirectoryAction|ChangeSelectionAction|
-    ClearStaleCachedEntriesAction|SearchAction;
+    ChangeFileTasksAction|ClearStaleCachedEntriesAction|SearchAction;
 
 
 /** Enum to identify every Action in Files app. */
 export const enum ActionType {
   CHANGE_DIRECTORY = 'change-directory',
   CHANGE_SELECTION = 'change-selection',
+  CHANGE_FILE_TASKS = 'change-file-tasks',
   CLEAR_STALE_CACHED_ENTRIES = 'clear-stale-cached-entries',
   SEARCH = 'search',
 }
diff --git a/ui/file_manager/file_manager/state/actions/current_directory.ts b/ui/file_manager/file_manager/state/actions/current_directory.ts
index f626853..497f6e0 100644
--- a/ui/file_manager/file_manager/state/actions/current_directory.ts
+++ b/ui/file_manager/file_manager/state/actions/current_directory.ts
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 import {FilesAppDirEntry, FilesAppEntry} from '../../externs/files_app_entry_interfaces.js';
-import {PropStatus} from '../../externs/ts/state.js';
+import {FileTasks, PropStatus} from '../../externs/ts/state.js';
 import {BaseAction} from '../../lib/base_store.js';
 import {ActionType} from '../actions.js';
 import {FileKey} from '../file_key.js';
@@ -27,6 +27,12 @@
   };
 }
 
+/** Action to update the FileTasks in the selection. */
+export interface ChangeFileTasksAction extends BaseAction {
+  type: ActionType.CHANGE_FILE_TASKS;
+  payload: FileTasks;
+}
+
 /** Factory for the ChangeDirectoryAction. */
 export function changeDirectory({to, toKey, status}: {
   to?: DirectoryEntry|FilesAppDirEntry, toKey: FileKey,
@@ -50,3 +56,11 @@
     payload,
   };
 }
+
+/** Factory for the ChangeFileTasksAction. */
+export function updateFileTasks(payload: FileTasks): ChangeFileTasksAction {
+  return {
+    type: ActionType.CHANGE_FILE_TASKS,
+    payload,
+  };
+}
diff --git a/ui/file_manager/file_manager/state/actions_producers/current_directory.ts b/ui/file_manager/file_manager/state/actions_producers/current_directory.ts
new file mode 100644
index 0000000..a905b7d1
--- /dev/null
+++ b/ui/file_manager/file_manager/state/actions_producers/current_directory.ts
@@ -0,0 +1,119 @@
+// 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.
+
+import {getFileTasks} from '../../common/js/api.js';
+import {DialogType} from '../../common/js/dialog_type.js';
+import {getNativeEntry} from '../../common/js/entry_utils.js';
+import {util} from '../../common/js/util.js';
+import {VolumeManagerCommon} from '../../common/js/volume_manager_types.js';
+import {FakeEntry} from '../../externs/files_app_entry_interfaces.js';
+import {FileData, PropStatus} from '../../externs/ts/state.js';
+import {constants} from '../../foreground/js/constants.js';
+import {annotateTasks, getDefaultTask, INSTALL_LINUX_PACKAGE_TASK_DESCRIPTOR} from '../../foreground/js/file_tasks.js';
+import {ActionsProducerGen} from '../../lib/actions_producer.js';
+import {keyedKeepFirst} from '../../lib/concurrency_models.js';
+import {ActionType} from '../actions.js';
+import {ChangeFileTasksAction} from '../actions/current_directory.js';
+import {getStore} from '../store.js';
+
+/**
+ * Linux package installation is currently only supported for a single file
+ * which is inside the Linux container, or in a shareable volume.
+ * TODO(timloh): Instead of filtering these out, we probably should show a
+ * dialog with an error message, similar to when attempting to run Crostini
+ * tasks with non-Crostini entries.
+ */
+function allowCrostiniTask(filesData: FileData[]) {
+  if (filesData.length !== 1) {
+    return false;
+  }
+  const fileData = filesData[0]!;
+  const rootType = (fileData.entry as FakeEntry).rootType;
+  if (rootType !== VolumeManagerCommon.RootType.CROSTINI) {
+    return false;
+  }
+  const crostini = window.fileManager.crostini;
+  return crostini.canSharePath(
+      constants.DEFAULT_CROSTINI_VM, (fileData.entry as Entry),
+      /*persiste=*/ false);
+}
+
+function emptyAction(status: PropStatus): ChangeFileTasksAction {
+  return {
+    type: ActionType.CHANGE_FILE_TASKS,
+    payload: {
+      tasks: [],
+      policyDefaultHandlerStatus: undefined,
+      defaultTask: undefined,
+      status,
+    },
+  };
+}
+
+export async function*
+    fetchFileTasksInternal(filesData: FileData[]):
+        ActionsProducerGen<ChangeFileTasksAction> {
+  // Filters out the non-native entries.
+  filesData = filesData.filter(getNativeEntry);
+  const state = getStore().getState();
+  const currentRootType = state.currentDirectory?.rootType;
+  const dialogType = window.fileManager.dialogType;
+  const shouldDisableTasks = (
+      // File Picker/Save As doesn't show the "Open" button.
+      dialogType !== DialogType.FULL_PAGE ||
+      // The list of available tasks should not be available to trashed items.
+      currentRootType === VolumeManagerCommon.RootType.TRASH ||
+      filesData.length === 0);
+  if (shouldDisableTasks) {
+    yield emptyAction(PropStatus.SUCCESS);
+    return;
+  }
+  const selectionHandler = window.fileManager.selectionHandler;
+  const selection = selectionHandler.selection;
+  await selection.computeAdditional(window.fileManager.metadataModel);
+  yield;
+  try {
+    const resultingTasks = await getFileTasks(filesData.map(fd => fd.entry));
+    if (!resultingTasks || !resultingTasks.tasks) {
+      return;
+    }
+    yield;
+    if (filesData.length === 0 || resultingTasks.tasks.length === 0) {
+      yield emptyAction(PropStatus.SUCCESS);
+      return;
+    }
+    if (!allowCrostiniTask(filesData)) {
+      resultingTasks.tasks = resultingTasks.tasks.filter(
+          (task: chrome.fileManagerPrivate.FileTask) => !util.descriptorEqual(
+              task.descriptor, INSTALL_LINUX_PACKAGE_TASK_DESCRIPTOR));
+    }
+    const tasks = annotateTasks(resultingTasks.tasks, filesData);
+    resultingTasks.tasks = tasks;
+    // TODO: Migrate TaskHistory to the store.
+    const taskHistory = window.fileManager.taskController.taskHistory;
+    const defaultTask =
+        getDefaultTask(
+            tasks, resultingTasks.policyDefaultHandlerStatus, taskHistory) ??
+        undefined;
+    yield {
+      type: ActionType.CHANGE_FILE_TASKS,
+      payload: {
+        tasks,
+        policyDefaultHandlerStatus: resultingTasks.policyDefaultHandlerStatus,
+        defaultTask: defaultTask,
+        status: PropStatus.SUCCESS,
+      },
+    };
+  } catch (error) {
+    yield emptyAction(PropStatus.ERROR);
+  }
+}
+
+/** Generates key based on each FileKey (entry.toURL()). */
+function getSelectionKey(filesData: FileData[]): string {
+  return filesData.map(f => f?.entry.toURL()).join('|');
+}
+
+export const fetchFileTasks =
+    keyedKeepFirst(fetchFileTasksInternal, getSelectionKey);
diff --git a/ui/file_manager/file_manager/state/for_tests.ts b/ui/file_manager/file_manager/state/for_tests.ts
index 516e4a00..0cd2da3 100644
--- a/ui/file_manager/file_manager/state/for_tests.ts
+++ b/ui/file_manager/file_manager/state/for_tests.ts
@@ -8,7 +8,7 @@
 import {PropStatus} from '../externs/ts/state.js';
 
 import {changeDirectory, updateSelection} from './actions/current_directory.js';
-import {Store} from './store.js';
+import {StateSelector, Store, waitForState} from './store.js';
 
 /**
  * Compares 2 State objects and fails with nicely formatted message when it
@@ -34,3 +34,41 @@
     entries,
   }));
 }
+
+/**
+ * Waits for a part of the Store to be in the expected state.
+ *
+ * Waits a maximum of 10 seconds, since in the unittest the Store manipulation
+ * has all async APIs mocked.
+ *
+ * Usage:
+ * let want: StoreSomething = {somePartOfStore: 'desired state'};
+ * store.dispatch(someActionsProducer(...));
+ * await waitDeepEquals(store, want, (state) => state.something);
+ */
+export async function waitDeepEquals(
+    store: Store, want: any, stateSelection: StateSelector) {
+  let got: any;
+  const timeout = new Promise((_, reject) => {
+    setTimeout(() => {
+      reject(new Error(`waitDeepEquals timed out waiting for \n${want}`));
+    }, 10000);
+  });
+
+  const checker = waitForState(store, (state) => {
+    try {
+      got = stateSelection(state);
+      assertDeepEquals(want, got);
+      return true;
+    } catch (error: any) {
+      if (error.constructor?.name === 'AssertionError') {
+        return false;
+      }
+      console.log(error.stack);
+      console.error(error);
+      throw error;
+    }
+  });
+
+  await Promise.race([checker, timeout]);
+}
diff --git a/ui/file_manager/file_manager/state/reducers/current_directory.ts b/ui/file_manager/file_manager/state/reducers/current_directory.ts
index 940bc88..62d7d77 100644
--- a/ui/file_manager/file_manager/state/reducers/current_directory.ts
+++ b/ui/file_manager/file_manager/state/reducers/current_directory.ts
@@ -2,9 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {CurrentDirectory, FileKey, PropStatus, Selection, State} from '../../externs/ts/state.js';
+import {CurrentDirectory, FileKey, FileTasks, PropStatus, Selection, State} from '../../externs/ts/state.js';
 import {PathComponent} from '../../foreground/js/path_component.js';
-import {ChangeDirectoryAction, ChangeSelectionAction} from '../actions/current_directory.js';
+import {ChangeDirectoryAction, ChangeFileTasksAction, ChangeSelectionAction} from '../actions/current_directory.js';
 
 /**
  * @fileoverview
@@ -149,3 +149,31 @@
     currentDirectory,
   };
 }
+
+/** Updates the FileTasks in the selection for the current directory. */
+export function updateFileTasks(
+    currentState: State, action: ChangeFileTasksAction): State {
+  const initialSelection =
+      currentState.currentDirectory?.selection ?? getEmptySelection();
+
+  // Apply the changes over the current selection.
+  const fileTasks: FileTasks = {
+    ...initialSelection.fileTasks,
+    ...action.payload,
+  };
+
+  // Update the selection and current directory objects.
+  const selection: Selection = {
+    ...initialSelection,
+    fileTasks,
+  };
+  const currentDirectory: CurrentDirectory = {
+    ...currentState.currentDirectory,
+    selection,
+  } as CurrentDirectory;
+
+  return {
+    ...currentState,
+    currentDirectory,
+  };
+}
diff --git a/ui/file_manager/file_manager/state/reducers/current_directory_unittest.ts b/ui/file_manager/file_manager/state/reducers/current_directory_unittest.ts
index dda6830..6ebbeee 100644
--- a/ui/file_manager/file_manager/state/reducers/current_directory_unittest.ts
+++ b/ui/file_manager/file_manager/state/reducers/current_directory_unittest.ts
@@ -9,14 +9,17 @@
 import {MockFileSystem} from '../../common/js/mock_entry.js';
 import {VolumeManagerCommon} from '../../common/js/volume_manager_types.js';
 import {Crostini} from '../../externs/background/crostini.js';
-import {CurrentDirectory, PropStatus} from '../../externs/ts/state.js';
+import {CurrentDirectory, FileTasks, PropStatus} from '../../externs/ts/state.js';
+import {FakeFileSelectionHandler} from '../../foreground/js/fake_file_selection_handler.js';
 import {FileSelectionHandler} from '../../foreground/js/file_selection.js';
 import {MetadataModel} from '../../foreground/js/metadata/metadata_model.js';
 import {MockMetadataModel} from '../../foreground/js/metadata/mock_metadata.js';
 import {TaskController} from '../../foreground/js/task_controller.js';
 import {changeDirectory, updateSelection} from '../actions/current_directory.js';
-import {assertStateEquals} from '../for_tests.js';
-import {getEmptyState, getStore, Store} from '../store.js';
+import {fetchFileTasks} from '../actions_producers/current_directory.js';
+import {assertStateEquals, waitDeepEquals} from '../for_tests.js';
+import {getEmptyState, getFilesData, getStore, Store} from '../store.js';
+
 
 let fileSystem: MockFileSystem;
 
@@ -27,7 +30,8 @@
     volumeManager: volumeManager,
     metadataModel: new MockMetadataModel({}) as unknown as MetadataModel,
     crostini: {} as unknown as Crostini,
-    selectionHandler: {} as unknown as FileSelectionHandler,
+    selectionHandler: new FakeFileSelectionHandler() as unknown as
+        FileSelectionHandler,
     taskController: {} as unknown as TaskController,
     dialogType: DialogType.FULL_PAGE,
   };
@@ -137,7 +141,6 @@
   assertStateEquals(want, store.getState().currentDirectory);
 }
 
-
 export function testChangeSelection() {
   const store = setupStore();
   const dir2 = fileSystem.entries['/dir-2'];
@@ -184,3 +187,84 @@
   want.selection.fileCount = 1;
   assertStateEquals(want, store.getState().currentDirectory);
 }
+
+function mockGetFileTasks(tasks: chrome.fileManagerPrivate.FileTask[]) {
+  const mocked =
+      (_entries: Entry[],
+       callback: (resultingTasks: chrome.fileManagerPrivate.ResultingTasks) =>
+           void) => {
+        setTimeout(callback, 0, {tasks});
+      };
+  chrome.fileManagerPrivate.getFileTasks = mocked;
+}
+
+const fakeFileTasks: chrome.fileManagerPrivate.FileTask = {
+  descriptor: {
+    appId: 'handler-extension-id1',
+    taskType: 'app',
+    actionId: 'any',
+  },
+  isDefault: false,
+  isGenericFileHandler: false,
+  title: 'app 1',
+  iconUrl: undefined,
+};
+
+export async function testFetchTasks(done: () => void) {
+  const store = setupStore();
+  const dir2 = fileSystem.entries['/dir-2'];
+  const subDir = fileSystem.entries['/dir-2/sub-dir'];
+  const file = fileSystem.entries['/dir-2/file.txt'];
+  cd(store, dir2);
+  changeSelection(store, [subDir, file]);
+
+  const filesData = getFilesData(store.getState(), [file.toURL()]);
+  const want: FileTasks = {
+    policyDefaultHandlerStatus: undefined,
+    defaultTask: undefined,
+    tasks: [],
+    status: PropStatus.SUCCESS,
+  };
+
+  // Mock returning 0 tasks, returns SUCCESS and empty tasks.
+  mockGetFileTasks([]);
+  store.dispatch(fetchFileTasks(filesData));
+  await waitDeepEquals(store, want, (state) => {
+    return state.currentDirectory?.selection.fileTasks;
+  });
+
+  // Mock the private API results with one task which is the default task.
+  mockGetFileTasks([fakeFileTasks]);
+  want.tasks = [
+    {
+      iconType: '',
+      descriptor: {
+        appId: 'handler-extension-id1',
+        taskType: 'app',
+        actionId: 'any',
+      },
+      isDefault: false,
+      isGenericFileHandler: false,
+      title: 'app 1',
+      iconUrl: undefined,
+    },
+  ];
+  want.defaultTask = {...want.tasks[0]!};
+  store.dispatch(fetchFileTasks(filesData));
+  await waitDeepEquals(
+      store, want, (state) => state.currentDirectory?.selection.fileTasks);
+
+  // Mock the API task as genericFileHandler, so it shouldn't be a default task.
+  const genericTask = {
+    ...fakeFileTasks,
+    isGenericFileHandler: true,
+  };
+  mockGetFileTasks([genericTask]);
+  want.tasks[0]!.isGenericFileHandler = true;
+  want.defaultTask = undefined;
+  store.dispatch(fetchFileTasks(filesData));
+  await waitDeepEquals(
+      store, want, (state) => state.currentDirectory?.selection.fileTasks);
+
+  done();
+}
diff --git a/ui/file_manager/file_manager/state/reducers/root.ts b/ui/file_manager/file_manager/state/reducers/root.ts
index b61ec27..c97cb23 100644
--- a/ui/file_manager/file_manager/state/reducers/root.ts
+++ b/ui/file_manager/file_manager/state/reducers/root.ts
@@ -6,7 +6,7 @@
 import {Action, ActionType} from '../actions.js';
 
 import {cacheEntries, clearCachedEntries} from './all_entries.js';
-import {changeDirectory, updateSelection} from './current_directory.js';
+import {changeDirectory, updateFileTasks, updateSelection} from './current_directory.js';
 import {search} from './search.js';
 
 /**
@@ -27,6 +27,8 @@
       return changeDirectory(state, action);
     case ActionType.CHANGE_SELECTION:
       return updateSelection(state, action);
+    case ActionType.CHANGE_FILE_TASKS:
+      return updateFileTasks(state, action);
     case ActionType.CLEAR_STALE_CACHED_ENTRIES:
       return clearCachedEntries(state, action);
     case ActionType.SEARCH:
diff --git a/ui/file_manager/file_manager/state/store.ts b/ui/file_manager/file_manager/state/store.ts
index 6448292..0bbe6798 100644
--- a/ui/file_manager/file_manager/state/store.ts
+++ b/ui/file_manager/file_manager/state/store.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 {State} from '../externs/ts/state.js';
+import {FileData, FileKey, State} from '../externs/ts/state.js';
 import {BaseStore} from '../lib/base_store.js';
 
 import {Action} from './actions.js';
@@ -48,3 +48,62 @@
     },
   };
 }
+
+/**
+ * Promise resolved when the store's state in an desired condition.
+ *
+ * For each Store update the `checker` function is called, when it returns True
+ * the promise is resolved.
+ *
+ * Resolves with the State when it's in the desired condition.
+ */
+export async function waitForState(
+    store: Store, checker: (state: State) => boolean): Promise<State> {
+  // Check if the store is already in the desired state.
+  if (checker(store.getState())) {
+    return store.getState();
+  }
+
+  return new Promise((resolve) => {
+    const observer = {
+      onStateChanged(newState: State) {
+        if (checker(newState)) {
+          resolve(newState);
+          store.unsubscribe(this);
+        }
+      },
+    };
+    store.subscribe(observer);
+  });
+}
+
+/**
+ * Returns the `FileData` from a FileKey.
+ * @throws {Error} if it can't find the FileData.
+ */
+export function getFileData(state: State, key: FileKey): FileData {
+  const entry = state.allEntries[key];
+  if (!entry) {
+    throw new Error(`Key ${key} not found in the store`);
+  }
+  return entry;
+}
+
+/**
+ * Returns FileData for each key.
+ * @throws {Error} if it can't find the FileData.
+ */
+export function getFilesData(state: State, keys: FileKey[]): FileData[] {
+  const filesData: FileData[] = [];
+  for (const key of keys) {
+    filesData.push(getFileData(state, key));
+  }
+
+  return filesData;
+}
+
+/**
+ * A function used to select part of the Store.
+ * TODO: Can the return type be stronger than `any`?
+ */
+export type StateSelector = (state: State) => any;
diff --git a/ui/file_manager/file_manager/widgets/xf_base.ts b/ui/file_manager/file_manager/widgets/xf_base.ts
index 8f22b7e..9363639c 100644
--- a/ui/file_manager/file_manager/widgets/xf_base.ts
+++ b/ui/file_manager/file_manager/widgets/xf_base.ts
@@ -13,12 +13,13 @@
 import {classMap} from 'chrome://resources/mwc/lit/directives/class-map.js';
 import {ifDefined} from 'chrome://resources/mwc/lit/directives/if-defined.js';
 import {styleMap} from 'chrome://resources/mwc/lit/directives/style-map.js';
-import {css, CSSResult, html, LitElement, PropertyValues} from 'chrome://resources/mwc/lit/index.js';
+import {css, CSSResult, CSSResultGroup, html, LitElement, PropertyValues} from 'chrome://resources/mwc/lit/index.js';
 
 export {
   classMap,
   css,
   CSSResult,
+  CSSResultGroup,
   customElement,
   html,
   ifDefined,
diff --git a/ui/file_manager/file_manager/widgets/xf_icon.ts b/ui/file_manager/file_manager/widgets/xf_icon.ts
new file mode 100644
index 0000000..67f5a91
--- /dev/null
+++ b/ui/file_manager/file_manager/widgets/xf_icon.ts
@@ -0,0 +1,340 @@
+// 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.
+
+import {classMap, css, customElement, html, property, XfBase} from './xf_base.js';
+
+@customElement('xf-icon')
+export class XfIcon extends XfBase {
+  /** The icon size, can be "small" or "large" (from `XfIcon.size`). */
+  @property({type: String, reflect: true}) size: string = XfIcon.sizes.SMALL;
+
+  /**
+   * The icon type, different type will render different SVG file
+   * (from `XfIcon.types`).
+   */
+  @property({type: String, reflect: true}) type = '';
+
+  static get sizes() {
+    return {
+      SMALL: 'small',
+      LARGE: 'large',
+    } as const;
+  }
+
+  static get types() {
+    return {
+      ANDROID_FILES: 'android_files',
+      ARCHIVE: 'archive',
+      AUDIO: 'audio',
+      BRUSCHETTA: 'bruschetta',
+      CAMERA_FOLDER: 'camera-folder',
+      COMPUTER: 'computer',
+      COMPUTERS_GRAND_ROOT: 'computers_grand_root',
+      CROSTINI: 'crostini',
+      DOWNLOADS: 'downloads',
+      DRIVE_OFFLINE: 'drive_offline',
+      DRIVE_RECENT: 'drive_recent',
+      DRIVE_SHARED_WITH_ME: 'drive_shared_with_me',
+      DRIVE: 'drive',
+      EXCEL: 'excel',
+      EXTERNAL_MEDIA: 'external_media',
+      FOLDER: 'folder',
+      GENERIC: 'generic',
+      GOOGLE_DOC: 'gdoc',
+      GOOGLE_DRAW: 'gdraw',
+      GOOGLE_FORM: 'gform',
+      GOOGLE_LINK: 'glink',
+      GOOGLE_MAP: 'gmap',
+      GOOGLE_SHEET: 'gsheet',
+      GOOGLE_SITE: 'gsite',
+      GOOGLE_SLIDES: 'gslides',
+      GOOGLE_TABLE: 'gtable',
+      IMAGE: 'image',
+      MTP: 'mtp',
+      MY_FILES: 'my_files',
+      OPTICAL: 'optical',
+      PDF: 'pdf',
+      PLUGIN_VM: 'plugin_vm',
+      POWERPOINT: 'ppt',
+      RAW: 'raw',
+      RECENT: 'recent',
+      REMOVABLE: 'removable',
+      SCRIPT: 'script',
+      SD_CARD: 'sd',
+      SERVICE_DRIVE: 'service_drive',
+      SHARED_DRIVE: 'shared_drive',
+      SHARED_DRIVES_GRAND_ROOT: 'shared_drives_grand_root',
+      SHARED_FOLDER: 'shared_folder',
+      SHORTCUT: 'shortcut',
+      SITES: 'sites',
+      SMB: 'smb',
+      TEAM_DRIVE: 'team_drive',
+      THUMBNAIL_GENERIC: 'thumbnail_generic',
+      TINI: 'tini',
+      TRASH: 'trash',
+      UNKNOWN_REMOVABLE: 'unknown_removable',
+      USB: 'usb',
+      VIDEO: 'video',
+      WORD: 'word',
+    };
+  }
+
+  static override get styles() {
+    return getCSS();
+  }
+
+  override render() {
+    const shouldKeepColor = [
+      XfIcon.types.EXCEL,
+      XfIcon.types.POWERPOINT,
+      XfIcon.types.WORD,
+    ].includes(this.type);
+    const spanClass = {'keep-color': shouldKeepColor};
+
+    return html`
+      <span class=${classMap(spanClass)}></span>
+    `;
+  }
+}
+
+function getCSS() {
+  return css`
+    :host {
+      display: inline-block;
+    }
+
+    span {
+      display: block;
+    }
+
+    span:not(.keep-color) {
+      -webkit-mask-position: center;
+      -webkit-mask-repeat: no-repeat;
+      background-color: var(--cros-icon-color-primary);
+    }
+
+    span.keep-color {
+      background-position: center center;
+      background-repeat: no-repeat;
+    }
+
+    :host([size="small"]) span {
+      height: 20px;
+      width: 20px;
+    }
+
+    :host([size="small"]) span:not(.keep-color) {
+      -webkit-mask-size: 20px;
+    }
+
+    :host([size="small"]) span.keep-color {
+      background-size: 20px;
+    }
+
+    :host([size="large"]) span {
+      height: 48px;
+      width: 48px;
+    }
+
+    :host([size="large"]) span:not(.keep-color) {
+      -webkit-mask-size: 48px;
+    }
+
+    :host([size="large"]) span.keep-color {
+      background-size: 48px;
+    }
+
+    :host([type="android_files"]) span {
+      -webkit-mask-image: url(../foreground/images/volumes/android.svg);
+    }
+
+    :host([type="archive"]) span {
+      -webkit-mask-image: url(../foreground/images/filetype/filetype_archive.svg);
+    }
+
+    :host([type="audio"]) span {
+      -webkit-mask-image: url(../foreground/images/filetype/filetype_audio.svg);
+    }
+
+    :host([type="bruschetta"]) span, :host([type="crostini"]) span {
+      -webkit-mask-image: url(../foreground/images/volumes/linux_files.svg);
+    }
+
+    :host([type="camera-folder"]) span {
+      -webkit-mask-image: url(../foreground/images/volumes/camera.svg);
+    }
+
+    :host([type="computer"]) span {
+      -webkit-mask-image: url(../foreground/images/volumes/computer.svg);
+    }
+
+    :host([type="computers_grand_root"]) span {
+      -webkit-mask-image: url(../foreground/images/volumes/devices.svg);
+    }
+
+    :host([type="downloads"]) span {
+      -webkit-mask-image: url(../foreground/images/volumes/downloads.svg);
+    }
+
+    :host([type="drive_offline"]) span {
+      -webkit-mask-image: url(../foreground/images/volumes/offline.svg);
+    }
+
+    :host([type="drive_shared_with_me"]) span {
+      -webkit-mask-image: url(../foreground/images/volumes/shared.svg);
+    }
+
+    :host([type="drive"]) span {
+      -webkit-mask-image: url(../foreground/images/volumes/shared.svg);
+    }
+
+    :host([type="excel"]) span {
+      background-image: url(../foreground/images/filetype/filetype_excel.svg);
+    }
+
+    :host([type="external_media"]) span,
+    :host([type="removable"]) span,
+    :host([type="usb"]) span {
+      -webkit-mask-image: url(../foreground/images/volumes/usb.svg);
+    }
+
+    :host([type="drive_recent"]) span, :host([type="recent"]) span {
+      -webkit-mask-image: url(../foreground/images/volumes/recent.svg);
+    }
+
+    :host([type="folder"]) span {
+      -webkit-mask-image: url(../foreground/images/filetype/filetype_folder.svg);
+    }
+
+    :host([type="generic"]) span, :host([type="glink"]) span {
+      -webkit-mask-image: url(../foreground/images/filetype/filetype_generic.svg);
+    }
+
+    :host([type="gdoc"]) span {
+      -webkit-mask-image: url(../foreground/images/filetype/filetype_gdoc.svg);
+    }
+
+    :host([type="gdraw"]) span {
+      -webkit-mask-image: url(../foreground/images/filetype/filetype_gdraw.svg);
+    }
+
+    :host([type="gform"]) span {
+      -webkit-mask-image: url(../foreground/images/filetype/filetype_gform.svg);
+    }
+
+    :host([type="gmap"]) span {
+      -webkit-mask-image: url(../foreground/images/filetype/filetype_gmap.svg);
+    }
+
+    :host([type="gsheet"]) span {
+      -webkit-mask-image: url(../foreground/images/filetype/filetype_gsheet.svg);
+    }
+
+    :host([type="gsite"]) span {
+      -webkit-mask-image: url(../foreground/images/filetype/filetype_gsite.svg);
+    }
+
+    :host([type="gslides"]) span {
+      -webkit-mask-image: url(../foreground/images/filetype/filetype_gslides.svg);
+    }
+
+    :host([type="gtable"]) span {
+      -webkit-mask-image: url(../foreground/images/filetype/filetype_gtable.svg);
+    }
+
+    :host([type="image"]) span, :host([type="raw"]) span {
+      -webkit-mask-image: url(../foreground/images/filetype/filetype_image.svg);
+    }
+
+    :host([type="mtp"]) span {
+      -webkit-mask-image: url(../foreground/images/volumes/phone.svg);
+    }
+
+    :host([type="my_files"]) span {
+      -webkit-mask-image: url(../foreground/images/volumes/my_files.svg);
+    }
+
+    :host([type="optical"]) span {
+      -webkit-mask-image: url(../foreground/images/volumes/cd.svg);
+    }
+
+    :host([type="pdf"]) span {
+      -webkit-mask-image: url(../foreground/images/filetype/filetype_pdf.svg);
+    }
+
+    :host([type="plugin_vm"]) span {
+      -webkit-mask-image: url(../foreground/images/volumes/plugin_vm_ng.svg);
+    }
+
+    :host([type="ppt"]) span {
+      background-image: url(../foreground/images/filetype/filetype_ppt.svg);
+    }
+
+    :host([type="script"]) span {
+      -webkit-mask-image: url(../foreground/images/filetype/filetype_script.svg);
+    }
+
+    :host([type="sd"]) span {
+      -webkit-mask-image: url(../foreground/images/volumes/sd.svg);
+    }
+
+    :host([type="service_drive"]) span {
+      -webkit-mask-image: url(../foreground/images/volumes/service_drive.svg);
+    }
+
+    :host([type="shared_drive"]) span {
+      -webkit-mask-image: url(../foreground/images/filetype/filetype_team_drive.svg);
+    }
+
+    :host([type="shared_drives_grand_root"]) span {
+      -webkit-mask-image: url(../foreground/images/volumes/team_drive.svg);
+    }
+
+    :host([type="shared_folder"]) span {
+      -webkit-mask-image: url(../foreground/images/filetype/filetype_folder_shared.svg);
+    }
+
+    :host([type="shortcut"]) span {
+      -webkit-mask-image: url(../foreground/images/volumes/shortcut.svg);
+    }
+
+    :host([type="sites"]) span {
+      -webkit-mask-image: url(../foreground/images/filetype/filetype_sites.svg);
+    }
+
+    :host([type="smb"]) span {
+      -webkit-mask-image: url(../foreground/images/volumes/smb.svg);
+    }
+
+    :host([type="team_drive"]) span, :host([type="unknown_removable"]) span {
+      -webkit-mask-image: url(../foreground/images/volumes/hard_drive.svg);
+    }
+
+    :host([type="thumbnail_generic"]) span {
+      -webkit-mask-image: url(../foreground/images/files/ui/filetype_placeholder_generic.svg);
+    }
+
+    :host([type="tini"]) span {
+      -webkit-mask-image: url(../foreground/images/filetype/filetype_tini.svg);
+    }
+
+    :host([type="trash"]) span {
+      -webkit-mask-image: url(../foreground/images/files/ui/delete_ng.svg);
+    }
+
+    :host([type="video"]) span {
+      -webkit-mask-image: url(../foreground/images/filetype/filetype_video.svg);
+    }
+
+    :host([type="word"]) span {
+      background-image: url(../foreground/images/filetype/filetype_word.svg);
+    }
+  `;
+}
+
+declare global {
+  interface HTMLElementTagNameMap {
+    'xf-icon': XfIcon;
+  }
+}
diff --git a/ui/file_manager/file_manager/widgets/xf_icon_unittest.ts b/ui/file_manager/file_manager/widgets/xf_icon_unittest.ts
new file mode 100644
index 0000000..f7e05f3
--- /dev/null
+++ b/ui/file_manager/file_manager/widgets/xf_icon_unittest.ts
@@ -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.
+
+import {assertEquals, assertFalse, assertNotEquals, assertTrue} from 'chrome://webui-test/chai_assert.js';
+
+import {waitForElementUpdate} from '../common/js/unittest_util.js';
+
+import {XfIcon} from './xf_icon.js';
+
+export function setUp() {
+  document.body.innerHTML = '<xf-icon></xf-icon>';
+}
+
+async function getIcon(): Promise<XfIcon> {
+  const element = document.querySelector('xf-icon');
+  assertNotEquals(null, element);
+  await waitForElementUpdate(element!);
+  return element!;
+}
+
+function getSpanFromIcon(icon: XfIcon): HTMLSpanElement {
+  return icon.shadowRoot!.querySelector<HTMLSpanElement>('span')!;
+}
+
+export async function testIconType(done: () => void) {
+  const icon = await getIcon();
+  const span = getSpanFromIcon(icon);
+
+  // Check for all office icons, there should be a keep-color class.
+  icon.type = XfIcon.types.WORD;
+  await waitForElementUpdate(icon);
+  assertTrue(span.classList.contains('keep-color'));
+
+  icon.type = XfIcon.types.EXCEL;
+  await waitForElementUpdate(icon);
+  assertTrue(span.classList.contains('keep-color'));
+
+  icon.type = XfIcon.types.POWERPOINT;
+  await waitForElementUpdate(icon);
+  assertTrue(span.classList.contains('keep-color'));
+
+  // Check no keep-color class for other icon types.
+  icon.type = XfIcon.types.ANDROID_FILES;
+  await waitForElementUpdate(icon);
+  assertFalse(span.classList.contains('keep-color'));
+
+  done();
+}
+
+export async function testIconSize(done: () => void) {
+  const icon = await getIcon();
+  const span = getSpanFromIcon(icon);
+
+  // By default the size should be small.
+  assertEquals(XfIcon.sizes.SMALL, icon.size);
+  assertEquals('20px', window.getComputedStyle(span).width);
+  assertEquals('20px', window.getComputedStyle(span).height);
+
+  // Check large size should change the width/height.
+  icon.size = XfIcon.sizes.LARGE;
+  await waitForElementUpdate(icon);
+  assertEquals('48px', window.getComputedStyle(span).width);
+  assertEquals('48px', window.getComputedStyle(span).height);
+
+  done();
+}
diff --git a/ui/file_manager/file_manager/widgets/xf_search_options.html b/ui/file_manager/file_manager/widgets/xf_search_options.html
index b13da63..2933ba26 100644
--- a/ui/file_manager/file_manager/widgets/xf_search_options.html
+++ b/ui/file_manager/file_manager/widgets/xf_search_options.html
@@ -6,10 +6,13 @@
   :host {
     display: flex;
     flex-direction: row;
-    margin: 5px 2px;
+    margin: 12px 16px;
   }
 </style>
 
-<xf-select id="location-selector"></xf-select>
-<xf-select id="recency-selector"></xf-select>
-<xf-select id="type-selector"></xf-select>
+<xf-select id="location-selector" icon="select-location">
+</xf-select>
+<xf-select id="recency-selector" icon="select-time">
+</xf-select>
+<xf-select id="type-selector" icon="select-filetype">
+</xf-select>
diff --git a/ui/file_manager/file_manager/widgets/xf_search_options.ts b/ui/file_manager/file_manager/widgets/xf_search_options.ts
index 3b1485f..917a9f6f4 100644
--- a/ui/file_manager/file_manager/widgets/xf_search_options.ts
+++ b/ui/file_manager/file_manager/widgets/xf_search_options.ts
@@ -9,11 +9,10 @@
  */
 
 import {getTemplate} from './xf_search_options.html.js';
-import {SELECTION_CHANGED, XfSelect} from './xf_select.js';
+import {XfSelect} from './xf_select.js';
 
 /**
  * The enumeration of strings used to identify the kind of option that changed.
- * @enum {string}
  */
 export enum OptionKind {
   LOCATION = 'location',
@@ -88,18 +87,21 @@
    * closed.
    */
   connectedCallback(): void {
-    this.getLocationSelector().addEventListener(SELECTION_CHANGED, (event) => {
-      this.dispatchEvent(newSearchOptionsChangedEvent(
-          OptionKind.LOCATION, event.detail.value));
-    });
-    this.getRecencySelector().addEventListener(SELECTION_CHANGED, (event) => {
-      this.dispatchEvent(
-          newSearchOptionsChangedEvent(OptionKind.RECENCY, event.detail.value));
-    });
-    this.getFileTypeSelector().addEventListener(SELECTION_CHANGED, (event) => {
-      this.dispatchEvent(newSearchOptionsChangedEvent(
-          OptionKind.FILE_TYPE, event.detail.value));
-    });
+    this.getLocationSelector().addEventListener(
+        XfSelect.events.SELECTION_CHANGED, (event) => {
+          this.dispatchEvent(newSearchOptionsChangedEvent(
+              OptionKind.LOCATION, event.detail.value));
+        });
+    this.getRecencySelector().addEventListener(
+        XfSelect.events.SELECTION_CHANGED, (event) => {
+          this.dispatchEvent(newSearchOptionsChangedEvent(
+              OptionKind.RECENCY, event.detail.value));
+        });
+    this.getFileTypeSelector().addEventListener(
+        XfSelect.events.SELECTION_CHANGED, (event) => {
+          this.dispatchEvent(newSearchOptionsChangedEvent(
+              OptionKind.FILE_TYPE, event.detail.value));
+        });
   }
 
   /**
diff --git a/ui/file_manager/file_manager/widgets/xf_search_options_unittest.ts b/ui/file_manager/file_manager/widgets/xf_search_options_unittest.ts
index 9a502f6..efd2644 100644
--- a/ui/file_manager/file_manager/widgets/xf_search_options_unittest.ts
+++ b/ui/file_manager/file_manager/widgets/xf_search_options_unittest.ts
@@ -26,10 +26,10 @@
 export async function testChangeLocation(done: () => void) {
   const element = getSearchOptionsElement();
   const locationSelector = element.getLocationSelector();
-  locationSelector.setOptions([
+  locationSelector.options = [
     {value: SearchLocation.EVERYWHERE, text: 'Everywhere'},
     {value: SearchLocation.THIS_FOLDER, text: 'This folder', default: true},
-  ]);
+  ];
 
   element.addEventListener(SEARCH_OPTIONS_CHANGED, (event) => {
     const want = {kind: OptionKind.LOCATION, value: SearchLocation.EVERYWHERE};
@@ -42,10 +42,10 @@
 export async function testChangeRecency(done: () => void) {
   const element = getSearchOptionsElement();
   const recencySelector = element.getRecencySelector();
-  recencySelector.setOptions([
+  recencySelector.options = [
     {value: SearchRecency.ANYTIME, text: 'Any time'},
     {value: SearchRecency.YESTERDAY, text: 'Yesterday'},
-  ]);
+  ];
 
   element.addEventListener(SEARCH_OPTIONS_CHANGED, (event) => {
     const want = {kind: OptionKind.RECENCY, value: SearchRecency.YESTERDAY};
@@ -58,10 +58,10 @@
 export async function testChangeFileType(done: () => void) {
   const element = getSearchOptionsElement();
   const fileTypeSelector = element.getFileTypeSelector();
-  fileTypeSelector.setOptions([
+  fileTypeSelector.options = [
     {value: SearchFileType.ALL_TYPES, text: 'All types'},
     {value: SearchFileType.IMAGES, text: 'Images'},
-  ]);
+  ];
 
   element.addEventListener(SEARCH_OPTIONS_CHANGED, (event) => {
     const want = {kind: OptionKind.FILE_TYPE, value: SearchFileType.IMAGES};
diff --git a/ui/file_manager/file_manager/widgets/xf_select.html b/ui/file_manager/file_manager/widgets/xf_select.html
deleted file mode 100644
index 48d60667..0000000
--- a/ui/file_manager/file_manager/widgets/xf_select.html
+++ /dev/null
@@ -1,17 +0,0 @@
-<style>
-  :host([hidden]) {
-    display: none !important;
-  }
-  :host {
-    display: block;
-    margin: 5px 2px;
-  }
-  select {
-    border: 1px solid var(--cros-button-stroke-color-secondary);
-    border-radius: 4px;
-    color: var(--cros-text-color-secondary);
-    margin: 5px;
-    padding: 3px;
-  }
-</style>
-<select id="select"></select>
diff --git a/ui/file_manager/file_manager/widgets/xf_select.ts b/ui/file_manager/file_manager/widgets/xf_select.ts
index e99c11b67..cdc1f286 100644
--- a/ui/file_manager/file_manager/widgets/xf_select.ts
+++ b/ui/file_manager/file_manager/widgets/xf_select.ts
@@ -2,7 +2,16 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {getTemplate} from './xf_select.html.js';
+/**
+ * @fileoverview xf-select element which is ChromeOS <select>..</select>.
+ * Disable type checking for closure, as it is done by the typescript compiler.
+ * @suppress{missingProperties}
+ */
+
+import {CrActionMenuElement} from 'chrome://resources/cr_elements/cr_action_menu/cr_action_menu.js';
+import {CrButtonElement} from 'chrome://resources/cr_elements/cr_button/cr_button.js';
+
+import {css, CSSResultGroup, customElement, html, property, query, state, XfBase} from './xf_base.js';
 
 /**
  * The data structure used to set the new options on the select element.
@@ -36,10 +45,11 @@
  * text visible to the user.
  *
  * const element = document.createElement('xf-select');
- * element.setOptions([
+ * element.options = [
  *   {value: 'value-a', text: 'Text of value A'},
  *   ...
- * ]);
+ * ];
+ * element.icon = 'select-location';
  * element.addEventListener(
  *     SELECTION_CHANGED, (event) => {
  *       if (event.detail.value === 'value-a') {
@@ -47,55 +57,229 @@
  *       }
  *     });
  *
- * We explicitly disable type checking for closure, as it is done by the
- * typescript compiler.
- * @suppress{missingProperties}
  */
-export class XfSelect extends HTMLElement {
-  private boundSelectionListener_: (event: Event) => void;
+@customElement('xf-select')
+export class XfSelect extends XfBase {
+  /**
+   * The name of the icon to be used by the xf-select. This icon name must match
+   * the name of an icon in the
+   * //ui/file_manager/file_manager/foreground/images/files/ui/
+   */
+  @property({type: String, reflect: false}) icon = '';
 
-  constructor() {
-    super();
-    // Create element content.
-    const template = document.createElement('template');
-    template.innerHTML = getTemplate() as unknown as string;
-    const fragment = template.content.cloneNode(true);
-    this.attachShadow({mode: 'open'}).appendChild(fragment);
-    this.boundSelectionListener_ = this.onSelectionChanged.bind(this);
+  /**
+   * The options available for selection.
+   */
+  @property({type: Array, reflect: false}) options: XfOption[] = [];
+
+  /**
+   * The current selected value.
+   */
+  @property({type: String, reflect: true}) value: string = '';
+
+  static get events() {
+    return {
+      /** emits when the currently selected option changed. */
+      SELECTION_CHANGED: 'selection_changed',
+    } as const;
   }
 
   /**
-   * Initializes event listeners on the components of the widget.
+   * The button that toggles the options menu.
    */
-  connectedCallback(): void {
-    this.getSelect_().addEventListener('change', this.boundSelectionListener_);
-  }
+  @query('cr-button#dropdown-toggle')
+  private $toggleDropdownButton_?: CrButtonElement;
 
   /**
-   * Disconnects event listeners registered in the connectedCallback method.
+   * The options menu.
    */
-  disconnectedCallback(): void {
-    this.getSelect_().removeEventListener(
-        'change', this.boundSelectionListener_);
-  }
+  @query('cr-action-menu') private $optionsMenu_?: CrActionMenuElement;
 
   /**
-   * Sets the given value text pairs as the selectable options.
+   * Keeps track of whether we are showing the options menu.
    */
-  setOptions(optionList: XfOption[]) {
-    // Create new options.
-    const newOptions: Node[] = [];
-    for (const option of optionList) {
-      const optionElement = document.createElement('option');
-      optionElement.value = option.value;
-      optionElement.text = option.text;
-      if (option.default) {
-        optionElement.selected = true;
-      }
-      newOptions.push(optionElement);
+  @state() private optionsVisible_: boolean = false;
+
+  /**
+   * The currently selected option.
+   */
+  private selectedOption_: XfSelectedValue = {
+    index: -1,
+    value: '',
+    text: '',
+  };
+
+  static override get styles(): CSSResultGroup {
+    return getCSS();
+  }
+
+  override render() {
+    const selectedIndex = this.computeSelectedIndex_();
+    return html`
+        ${this.renderFilterChip_(selectedIndex)}
+        ${this.renderDropdown_()}
+    `;
+  }
+
+  override click() {
+    if (this.$toggleDropdownButton_) {
+      this.$toggleDropdownButton_.click();
     }
-    // Use new options to replace any children the select element may have.
-    this.getSelect_().replaceChildren(...newOptions);
+  }
+
+  /**
+   * Returns whether the component is expanded, with options visible, or
+   * collapsed.
+   */
+  get expanded(): boolean {
+    return this.optionsVisible_;
+  }
+
+  /**
+   * Returns a template of the chip that shows the currently selected filter
+   * value.
+   */
+  private renderFilterChip_(selectedIndex: number) {
+    const buttonLabel =
+        selectedIndex === -1 ? '' : this.options[selectedIndex]!.text;
+    const iconPart = this.icon ?
+        html`<span id="xf-select-icon" class="xf-select-icon ${
+            this.icon}"></span>` :
+        html``;
+    const labelPart = html`<span id="selected-option">${buttonLabel}</span>`;
+
+    return html`
+      <cr-button id="dropdown-toggle"
+              aria-haspopup="menu"
+              aria-expanded=${this.optionsVisible_}
+              @click=${this.onToggleOptions_}>
+        ${iconPart}${labelPart}<span id="dropdown-icon"></span>
+      </cr-button>`;
+  }
+
+  /**
+   * Returns a template of the dropdown which shows available choices.
+   */
+  private renderDropdown_() {
+    return html`<cr-action-menu>
+        ${this.options.map((option, index) => html`
+          <cr-button
+              class="dropdown-item"
+              role="menuitem"
+              @click=${() => this.onOptionSelected_(index)}
+              ?selected=${this.selectedOption_!.value === option.value}>
+            ${option.text}
+          </cr-button>`)}
+      </cr-action-menu>`;
+  }
+
+  override updated(changedProperties: Map<string, any>) {
+    if (changedProperties.has('value')) {
+      this.updateSelectedOption_(this.computeIndexForValue_(this.value));
+    }
+    if (changedProperties.has('options')) {
+      this.updateSelectedOption_(this.computeSelectedIndex_());
+    }
+  }
+
+  /**
+   * Attempts to find the index of the value among options.
+   */
+  private computeIndexForValue_(value: string|null): number {
+    let selectedIndex = -1;
+    if (value) {
+      selectedIndex = this.options.findIndex(e => e.value === this.value);
+    }
+    return selectedIndex;
+  }
+
+  /**
+   * If the index is within range of option list, updates the selected value to
+   * the one at the given index.
+   */
+  private updateSelectedOption_(index: number) {
+    if (index != this.selectedOption_.index) {
+      if (index >= 0 && index < this.options.length) {
+        this.selectedOption_ = {
+          index: index,
+          value: this.options[index]!.value,
+          text: this.options[index]!.text,
+        };
+        this.dispatchSelectionChanged_();
+      }
+    }
+  }
+
+  /**
+   * Attempts to establish the index of the selected item. The priority is given
+   * the the value attribute. If set, it decides which option is selected. If
+   * not set we pick either the first option, or the option with the default set
+   * to true.
+   */
+  private computeSelectedIndex_(): number {
+    let selectedIndex = this.computeIndexForValue_(this.value);
+    // If we could not match the value, look for the default option.
+    if (selectedIndex === -1) {
+      for (let i = this.options.length - 1; i >= 0; --i) {
+        if (this.options[i]!.default) {
+          selectedIndex = i;
+          break;
+        }
+      }
+    }
+    if (selectedIndex === -1 && this.options.length > 0) {
+      selectedIndex = 0;
+    }
+    this.updateSelectedOption_(selectedIndex);
+    return selectedIndex;
+  }
+
+  /**
+   * Invoked when the toggle button is clicked. Toggles the visibility of the
+   * dropdown options.
+   */
+  private onToggleOptions_(): void {
+    if (this.optionsVisible_) {
+      this.closeOptions_();
+    } else {
+      this.openOptions_();
+    }
+  }
+
+  /**
+   * Opens the dropdown options, providing they were closed.
+   */
+  private openOptions_() {
+    if (!this.optionsVisible_) {
+      const element: HTMLElement = this.$toggleDropdownButton_!;
+      const top = element.offsetTop + element.offsetHeight + 8;
+      this.$optionsMenu_!.showAt(element, {top: top});
+      this.optionsVisible_ = true;
+    }
+  }
+
+  /**
+   * Closes the dropdown options, providing they were open.
+   */
+  private closeOptions_() {
+    if (this.optionsVisible_) {
+      this.$optionsMenu_!.close();
+      this.optionsVisible_ = false;
+    }
+  }
+
+  /**
+   * Reacs to one of the options being selected. If the selection changed the
+   * currently selected option, it updates the value, which prompts
+   * re-rendering. It also posts a selection change event. Finally it always
+   * closes the option, regardless of change.
+   */
+  private onOptionSelected_(index: number) {
+    if (index !== this.selectedOption_.index) {
+      this.updateSelectedOption_(index);
+      this.value = this.selectedOption_.value;
+    }
+    this.closeOptions_();
   }
 
   /**
@@ -103,64 +287,72 @@
    * set to -1, and text and value are set to an empty string.
    */
   getSelectedOption(): XfSelectedValue {
-    return this.getSelectedOptionOfSelect_(this.getSelect_());
+    return this.selectedOption_;
   }
 
   /**
-   * Sets the selected value of the xf-select. The |optionValue| should be one
-   * of the values of the options set on the select element. If the value
-   * changes, this element will trigger a new SELECTION_CHANGED event.
+   * Dispatches SELECTION_CHANGED event with the current value of the selected
+   * options.
    */
-  set value(newValue: string) {
-    const select = this.getSelect_();
-    const currentValue = select.value;
-    if (currentValue !== newValue) {
-      select.value = newValue;
-      select.dispatchEvent(new Event('change'));
-    }
-  }
-
-  /**
-   * Extracts the currently selected option for the given select element.
-   */
-  private getSelectedOptionOfSelect_(element: HTMLSelectElement):
-      XfSelectedValue {
-    const index = element.selectedIndex;
-    let value = '';
-    let text = '';
-
-    if (index !== -1) {
-      const option = element.options[index];
-      if (option) {
-        value = option.value;
-        text = option.text;
-      }
-    }
-    return {index, value, text};
-  }
-
-  private getSelect_(): HTMLSelectElement {
-    const element = this.shadowRoot!.querySelector('#select');
-    if (element) {
-      return element as HTMLSelectElement;
-    }
-    throw new Error('Failed to locate the select element');
-  }
-
-  private onSelectionChanged(event: Event): void {
-    this.dispatchEvent(new CustomEvent(SELECTION_CHANGED, {
+  private dispatchSelectionChanged_(): void {
+    this.dispatchEvent(new CustomEvent(XfSelect.events.SELECTION_CHANGED, {
       bubbles: true,
       composed: true,
-      detail:
-          this.getSelectedOptionOfSelect_(event.target as HTMLSelectElement),
+      detail: this.selectedOption_,
     }));
   }
 }
 
 /**
- * The name of the even generated by this widget.
+ * CSS used by the xf-select widget.
  */
-export const SELECTION_CHANGED = 'selection_changed';
+function getCSS(): CSSResultGroup {
+  return css`
+    cr-button {
+      --hover-bg-color: var(--cros-ripple-color);
+      --hover-border-color: var(--cros-button-stroke-color-secondary);
+      --text-color: var(--cros-text-color-secondary);
+      --ink-color: var(--cros-ripple-color);
+    }
+    #dropdown-toggle {
+      --border-color: var(--cros-button-stroke-color-secondary);
+      --cr-button-height: 29px;
+      --ripple-opacity: 100%;
+      border-radius: 20px;
+      margin-inline: 4px;
+      min-width: auto;
+      outline: none;
+      padding: 8px 12px;
+    }
+    .xf-select-icon {
+      height: 20px;
+      width: 20px;
+      margin-inline: 0 8px;
+    }
+    #xf-select-icon.select-location {
+      background:
+        url(/foreground/images/files/ui/select_location.svg) no-repeat;
+    }
+    #xf-select-icon.select-time {
+      background:
+        url(/foreground/images/files/ui/select_time.svg) no-repeat;
+    }
+    #xf-select-icon.select-filetype {
+      background:
+        url(/foreground/images/files/ui/select_filetype.svg) no-repeat;
+    }
+    #dropdown-icon {
+      background:
+        url(/foreground/images/files/ui/xf_select_dropdown.svg) no-repeat;
+      height: 20px;
+      width: 20px;
+      margin-inline: 8px 0;
+    }
+    cr-button.dropdown-item {
+      --focus-shadow-color: none;
+    }
+  `;
+}
 
 /**
  * A custom event that informs the container which option kind change to what
@@ -170,12 +362,10 @@
 
 declare global {
   interface HTMLElementEventMap {
-    [SELECTION_CHANGED]: SelectionChangedEvent;
+    [XfSelect.events.SELECTION_CHANGED]: SelectionChangedEvent;
   }
 
   interface HTMLElementTagNameMap {
     'xf-select': XfSelect;
   }
 }
-
-customElements.define('xf-select', XfSelect);
diff --git a/ui/file_manager/file_manager/widgets/xf_select_unittest.ts b/ui/file_manager/file_manager/widgets/xf_select_unittest.ts
index aaafeee..37d5fbe 100644
--- a/ui/file_manager/file_manager/widgets/xf_select_unittest.ts
+++ b/ui/file_manager/file_manager/widgets/xf_select_unittest.ts
@@ -4,13 +4,16 @@
 
 
 import {assertDeepEquals, assertEquals} from 'chrome://webui-test/chromeos/chai_assert.js';
+import {eventToPromise} from 'chrome://webui-test/test_util.js';
 
-import {SELECTION_CHANGED, XfSelect} from './xf_select.js';
+import {waitForElementUpdate} from '../common/js/unittest_util.js';
+
+import {XfSelect} from './xf_select.js';
 
 /**
  * Creates new <xf-search-options> element for each test.
  */
-export function setUp() {
+export function setUp(): void {
   document.body.innerHTML = '<xf-select></xf-select>';
 }
 
@@ -21,39 +24,84 @@
   return document.querySelector<XfSelect>('xf-select')!;
 }
 
-export function testElementCreated() {
+export function testElementCreated(): void {
   const element = getSearchOptionsElement();
   assertEquals('XF-SELECT', element.tagName);
 }
 
-export function testSetOptions() {
+export async function testSetOptions(): Promise<void> {
   const element = getSearchOptionsElement();
-  element.setOptions([
+
+  // Test 1: Expect the option with default: true to be the selected one.
+  element.options = [
     {value: 'value-a', text: 'Text A'},
     {value: 'value-b', text: 'Text B', default: true},
-  ]);
+  ];
 
-  const want = {index: 1, value: 'value-b', text: 'Text B'};
-  const got = element.getSelectedOption();
+  await waitForElementUpdate(element);
+  const want1 = {index: 1, value: 'value-b', text: 'Text B'};
+  const got1 = element.getSelectedOption();
 
   assertDeepEquals(
+      want1, got1, `${JSON.stringify(want1)} != ${JSON.stringify(got1)}`);
+
+  // Test 2: No option has default: true; expect the first one to be selected.
+  element.options = [
+    {value: 'value-c', text: 'Text C'},
+    {value: 'value-d', text: 'Text D'},
+  ];
+
+  await waitForElementUpdate(element);
+  const want2 = {index: 0, value: 'value-c', text: 'Text C'};
+  const got2 = element.getSelectedOption();
+
+  assertDeepEquals(
+      want2, got2, `${JSON.stringify(want2)} != ${JSON.stringify(got2)}`);
+}
+
+export async function testEvents(): Promise<void> {
+  const element = getSearchOptionsElement();
+  element.options = [
+    {value: 'value-a', text: 'Text A'},
+    {value: 'value-b', text: 'Text B', default: true},
+  ];
+
+  const selectionChangedPromise =
+      eventToPromise(XfSelect.events.SELECTION_CHANGED, element);
+
+  element.value = 'value-a';
+  const event = await selectionChangedPromise;
+  const want = {index: 0, value: 'value-a', text: 'Text A'};
+  const got = event.detail;
+  assertDeepEquals(
       want, got, `${JSON.stringify(want)} != ${JSON.stringify(got)}`);
 }
 
-export async function testEvents(done: () => void) {
+export async function testInteractionViaUI(): Promise<void> {
   const element = getSearchOptionsElement();
-  element.setOptions([
+  element.options = [
     {value: 'value-a', text: 'Text A'},
     {value: 'value-b', text: 'Text B', default: true},
-  ]);
+  ];
 
-  element.addEventListener(SELECTION_CHANGED, (event) => {
-    const want = {index: 0, value: 'value-a', text: 'Text A'};
-    const got = event.detail;
-    assertDeepEquals(
-        want, got, `${JSON.stringify(want)} != ${JSON.stringify(got)}`);
-    done();
-  });
+  const selectionChangedDueOptionsPromise =
+      eventToPromise(XfSelect.events.SELECTION_CHANGED, element);
+  await selectionChangedDueOptionsPromise;
 
-  element.value = 'value-a';
+  const selectionChangedDueMenuClickPromise =
+      eventToPromise(XfSelect.events.SELECTION_CHANGED, element);
+
+  // Open.
+  element.click();
+  // Click the first option.
+  const menuButtons: HTMLButtonElement[] = Array.from(
+      element.shadowRoot!.querySelectorAll('cr-action-menu cr-button'));
+  assertEquals(2, menuButtons.length);
+  menuButtons[0]?.click();
+
+  const event = await selectionChangedDueMenuClickPromise;
+  const want = {index: 0, value: 'value-a', text: 'Text A'};
+  const got = event.detail;
+  assertDeepEquals(
+      want, got, `${JSON.stringify(want)} != ${JSON.stringify(got)}`);
 }
diff --git a/ui/file_manager/file_names.gni b/ui/file_manager/file_names.gni
index 861574d..31c1e8f 100644
--- a/ui/file_manager/file_names.gni
+++ b/ui/file_manager/file_names.gni
@@ -59,7 +59,7 @@
   "file_manager/common/js/metrics_base.js",
   "file_manager/common/js/mock_chrome.js",
   "file_manager/common/js/mock_entry.js",
-  "file_manager/common/js/notifications_browser_proxy.js",
+  "file_manager/common/js/notifications.js",
   "file_manager/common/js/progress_center_common.js",
   "file_manager/common/js/recent_date_bucket.js",
   "file_manager/common/js/storage.js",
@@ -257,6 +257,9 @@
   "file_manager/state/actions/all_entries.ts",
   "file_manager/state/actions/current_directory.ts",
 
+  # ActionsProducers.
+  "file_manager/state/actions_producers/current_directory.ts",
+
   # Reducers.
   "file_manager/state/reducers/root.ts",
   "file_manager/state/reducers/all_entries.ts",
@@ -273,6 +276,7 @@
   "file_manager/widgets/xf_breadcrumb.ts",
   "file_manager/widgets/xf_conflict_dialog.ts",
   "file_manager/widgets/xf_dlp_restriction_details_dialog.ts",
+  "file_manager/widgets/xf_icon.ts",
   "file_manager/widgets/xf_nudge.ts",
   "file_manager/widgets/xf_search_options.ts",
   "file_manager/widgets/xf_select.ts",
@@ -299,7 +303,6 @@
   "file_manager/widgets/xf_dlp_restriction_details_dialog.html",
   "file_manager/widgets/xf_nudge.html",
   "file_manager/widgets/xf_search_options.html",
-  "file_manager/widgets/xf_select.html",
 ]
 
 ts_test_files = [
@@ -321,6 +324,7 @@
   "file_manager/widgets/xf_breadcrumb_unittest.ts",
   "file_manager/widgets/xf_conflict_dialog_unittest.ts",
   "file_manager/widgets/xf_dlp_restriction_details_dialog_unittest.ts",
+  "file_manager/widgets/xf_icon_unittest.ts",
   "file_manager/widgets/xf_nudge_unittest.ts",
   "file_manager/widgets/xf_search_options_unittest.ts",
   "file_manager/widgets/xf_select_unittest.ts",
diff --git a/ui/file_manager/integration_tests/file_manager/office.js b/ui/file_manager/integration_tests/file_manager/office.js
index 4ef309e..e206a32 100644
--- a/ui/file_manager/integration_tests/file_manager/office.js
+++ b/ui/file_manager/integration_tests/file_manager/office.js
@@ -89,6 +89,10 @@
   const appId = await setupAndWaitUntilReady(
       RootPath.DRIVE, [], [ENTRIES.smallDocxHosted]);
 
+  // Disable office setup flow so the dialog doesn't open when the file is
+  // opened.
+  await sendTestMessage({name: 'setOfficeSetupComplete', complete: true});
+
   // Open file.
   chrome.test.assertTrue(await remoteCall.callRemoteTestUtil(
       'openFile', appId, [ENTRIES.smallDocxHosted.nameText]));
diff --git a/ui/gl/dcomp_presenter_unittest.cc b/ui/gl/dcomp_presenter_unittest.cc
index 75d2c23..8d587db 100644
--- a/ui/gl/dcomp_presenter_unittest.cc
+++ b/ui/gl/dcomp_presenter_unittest.cc
@@ -140,6 +140,18 @@
   return texture;
 }
 
+bool AreColorsSimilar(int a, int b) {
+  // The precise colors may differ depending on the video processor, so allow
+  // a margin for error.
+  const int kMargin = 10;
+  return abs(SkColorGetA(a) - SkColorGetA(b)) < kMargin &&
+         abs(SkColorGetR(a) - SkColorGetR(b)) < kMargin &&
+         abs(SkColorGetG(a) - SkColorGetG(b)) < kMargin &&
+         abs(SkColorGetB(a) - SkColorGetB(b)) < kMargin;
+}
+
+}  // namespace
+
 class DCompPresenterTest : public testing::Test {
  public:
   DCompPresenterTest() : parent_window_(ui::GetHiddenWindow()) {}
@@ -195,6 +207,12 @@
     return context;
   }
 
+  // Helper to allow for easy friending of the below restricted function.
+  void SetColorSpaceOnGLImage(gl::GLImage* gl_image,
+                              const gfx::ColorSpace& color_space) {
+    gl_image->SetColorSpace(color_space);
+  }
+
   HWND parent_window_;
   scoped_refptr<DCompPresenter> surface_;
   scoped_refptr<GLContext> context_;
@@ -216,7 +234,7 @@
 
   scoped_refptr<GLImageDXGI> image_dxgi(new GLImageDXGI(texture_size, nullptr));
   image_dxgi->SetTexture(texture, 0);
-  image_dxgi->SetColorSpace(gfx::ColorSpace::CreateREC709());
+  SetColorSpaceOnGLImage(image_dxgi.get(), gfx::ColorSpace::CreateREC709());
 
   {
     std::unique_ptr<ui::DCRendererLayerParams> params =
@@ -268,7 +286,7 @@
   scoped_refptr<GLImageDXGI> image_dxgi2(
       new GLImageDXGI(texture_size, nullptr));
   image_dxgi2->SetTexture(texture, 0);
-  image_dxgi2->SetColorSpace(gfx::ColorSpace::CreateREC709());
+  SetColorSpaceOnGLImage(image_dxgi2.get(), gfx::ColorSpace::CreateREC709());
 
   {
     std::unique_ptr<ui::DCRendererLayerParams> params =
@@ -305,7 +323,7 @@
 
   scoped_refptr<GLImageDXGI> image_dxgi(new GLImageDXGI(texture_size, nullptr));
   image_dxgi->SetTexture(texture, 0);
-  image_dxgi->SetColorSpace(gfx::ColorSpace::CreateREC709());
+  SetColorSpaceOnGLImage(image_dxgi.get(), gfx::ColorSpace::CreateREC709());
 
   // HW supports scaled overlays.
   // The input texture size is maller than the window size.
@@ -381,7 +399,7 @@
 
   scoped_refptr<GLImageDXGI> image_dxgi(new GLImageDXGI(texture_size, nullptr));
   image_dxgi->SetTexture(texture, 0);
-  image_dxgi->SetColorSpace(gfx::ColorSpace::CreateREC709());
+  SetColorSpaceOnGLImage(image_dxgi.get(), gfx::ColorSpace::CreateREC709());
 
   gfx::Rect quad_rect = gfx::Rect(42, 42);
 
@@ -445,7 +463,7 @@
 
   scoped_refptr<GLImageDXGI> image_dxgi(new GLImageDXGI(texture_size, nullptr));
   image_dxgi->SetTexture(texture, 0);
-  image_dxgi->SetColorSpace(gfx::ColorSpace::CreateREC709());
+  SetColorSpaceOnGLImage(image_dxgi.get(), gfx::ColorSpace::CreateREC709());
   gfx::Size window_size(640, 360);
 
   // Clear video
@@ -610,16 +628,6 @@
   ui::WinWindow window_;
 };
 
-bool AreColorsSimilar(int a, int b) {
-  // The precise colors may differ depending on the video processor, so allow
-  // a margin for error.
-  const int kMargin = 10;
-  return abs(SkColorGetA(a) - SkColorGetA(b)) < kMargin &&
-         abs(SkColorGetR(a) - SkColorGetR(b)) < kMargin &&
-         abs(SkColorGetG(a) - SkColorGetG(b)) < kMargin &&
-         abs(SkColorGetB(a) - SkColorGetB(b)) < kMargin;
-}
-
 class DCompPresenterVideoPixelTest : public DCompPresenterPixelTest {
  protected:
   void TestVideo(const gfx::ColorSpace& color_space,
@@ -641,7 +649,7 @@
     scoped_refptr<GLImageDXGI> image_dxgi(
         new GLImageDXGI(texture_size, nullptr));
     image_dxgi->SetTexture(texture, 0);
-    image_dxgi->SetColorSpace(color_space);
+    SetColorSpaceOnGLImage(image_dxgi.get(), color_space);
 
     {
       std::unique_ptr<ui::DCRendererLayerParams> params =
@@ -729,7 +737,7 @@
   auto uv_image = base::MakeRefCounted<GLImageRefCountedMemory>(uv_size);
   uv_image->Initialize(new base::RefCountedBytes(uv_data),
                        gfx::BufferFormat::RG_88);
-  y_image->SetColorSpace(gfx::ColorSpace::CreateREC709());
+  SetColorSpaceOnGLImage(y_image.get(), gfx::ColorSpace::CreateREC709());
 
   std::unique_ptr<ui::DCRendererLayerParams> params =
       std::make_unique<ui::DCRendererLayerParams>();
@@ -1344,5 +1352,4 @@
                          testing::Bool(),
                          &DCompPresenterBufferCountTest::GetParamName);
 
-}  // namespace
 }  // namespace gl
diff --git a/ui/gl/direct_composition_surface_win_unittest.cc b/ui/gl/direct_composition_surface_win_unittest.cc
index 766d2f8c..001ab05a 100644
--- a/ui/gl/direct_composition_surface_win_unittest.cc
+++ b/ui/gl/direct_composition_surface_win_unittest.cc
@@ -139,6 +139,18 @@
   return texture;
 }
 
+bool AreColorsSimilar(int a, int b) {
+  // The precise colors may differ depending on the video processor, so allow
+  // a margin for error.
+  const int kMargin = 10;
+  return abs(SkColorGetA(a) - SkColorGetA(b)) < kMargin &&
+         abs(SkColorGetR(a) - SkColorGetR(b)) < kMargin &&
+         abs(SkColorGetG(a) - SkColorGetG(b)) < kMargin &&
+         abs(SkColorGetB(a) - SkColorGetB(b)) < kMargin;
+}
+
+}  // namespace
+
 class DirectCompositionSurfaceTest : public testing::Test {
  public:
   DirectCompositionSurfaceTest() : parent_window_(ui::GetHiddenWindow()) {}
@@ -197,6 +209,12 @@
     return context;
   }
 
+  // Helper to allow for easy friending of the below restricted function.
+  void SetColorSpaceOnGLImage(gl::GLImage* gl_image,
+                              const gfx::ColorSpace& color_space) {
+    gl_image->SetColorSpace(color_space);
+  }
+
   HWND parent_window_;
   scoped_refptr<DirectCompositionSurfaceWin> surface_;
   scoped_refptr<GLContext> context_;
@@ -345,7 +363,7 @@
 
   scoped_refptr<GLImageDXGI> image_dxgi(new GLImageDXGI(texture_size, nullptr));
   image_dxgi->SetTexture(texture, 0);
-  image_dxgi->SetColorSpace(gfx::ColorSpace::CreateREC709());
+  SetColorSpaceOnGLImage(image_dxgi.get(), gfx::ColorSpace::CreateREC709());
 
   {
     std::unique_ptr<ui::DCRendererLayerParams> params =
@@ -397,7 +415,7 @@
   scoped_refptr<GLImageDXGI> image_dxgi2(
       new GLImageDXGI(texture_size, nullptr));
   image_dxgi2->SetTexture(texture, 0);
-  image_dxgi2->SetColorSpace(gfx::ColorSpace::CreateREC709());
+  SetColorSpaceOnGLImage(image_dxgi2.get(), gfx::ColorSpace::CreateREC709());
 
   {
     std::unique_ptr<ui::DCRendererLayerParams> params =
@@ -434,7 +452,7 @@
 
   scoped_refptr<GLImageDXGI> image_dxgi(new GLImageDXGI(texture_size, nullptr));
   image_dxgi->SetTexture(texture, 0);
-  image_dxgi->SetColorSpace(gfx::ColorSpace::CreateREC709());
+  SetColorSpaceOnGLImage(image_dxgi.get(), gfx::ColorSpace::CreateREC709());
 
   // HW supports scaled overlays.
   // The input texture size is maller than the window size.
@@ -510,7 +528,7 @@
 
   scoped_refptr<GLImageDXGI> image_dxgi(new GLImageDXGI(texture_size, nullptr));
   image_dxgi->SetTexture(texture, 0);
-  image_dxgi->SetColorSpace(gfx::ColorSpace::CreateREC709());
+  SetColorSpaceOnGLImage(image_dxgi.get(), gfx::ColorSpace::CreateREC709());
 
   gfx::Rect quad_rect = gfx::Rect(42, 42);
 
@@ -574,7 +592,7 @@
 
   scoped_refptr<GLImageDXGI> image_dxgi(new GLImageDXGI(texture_size, nullptr));
   image_dxgi->SetTexture(texture, 0);
-  image_dxgi->SetColorSpace(gfx::ColorSpace::CreateREC709());
+  SetColorSpaceOnGLImage(image_dxgi.get(), gfx::ColorSpace::CreateREC709());
   gfx::Size window_size(640, 360);
 
   // Clear video
@@ -734,16 +752,6 @@
   PixelTestSwapChain(false);
 }
 
-bool AreColorsSimilar(int a, int b) {
-  // The precise colors may differ depending on the video processor, so allow
-  // a margin for error.
-  const int kMargin = 10;
-  return abs(SkColorGetA(a) - SkColorGetA(b)) < kMargin &&
-         abs(SkColorGetR(a) - SkColorGetR(b)) < kMargin &&
-         abs(SkColorGetG(a) - SkColorGetG(b)) < kMargin &&
-         abs(SkColorGetB(a) - SkColorGetB(b)) < kMargin;
-}
-
 class DirectCompositionVideoPixelTest : public DirectCompositionPixelTest {
  protected:
   void TestVideo(const gfx::ColorSpace& color_space,
@@ -765,7 +773,7 @@
     scoped_refptr<GLImageDXGI> image_dxgi(
         new GLImageDXGI(texture_size, nullptr));
     image_dxgi->SetTexture(texture, 0);
-    image_dxgi->SetColorSpace(color_space);
+    SetColorSpaceOnGLImage(image_dxgi.get(), color_space);
 
     {
       std::unique_ptr<ui::DCRendererLayerParams> params =
@@ -853,7 +861,7 @@
   auto uv_image = base::MakeRefCounted<GLImageRefCountedMemory>(uv_size);
   uv_image->Initialize(new base::RefCountedBytes(uv_data),
                        gfx::BufferFormat::RG_88);
-  y_image->SetColorSpace(gfx::ColorSpace::CreateREC709());
+  SetColorSpaceOnGLImage(y_image.get(), gfx::ColorSpace::CreateREC709());
 
   std::unique_ptr<ui::DCRendererLayerParams> params =
       std::make_unique<ui::DCRendererLayerParams>();
@@ -1578,5 +1586,4 @@
   RunBufferCountTest(surface_, /*buffer_count=*/3u, /*for_video=*/true);
 }
 
-}  // namespace
 }  // namespace gl
diff --git a/ui/gl/gl_image.cc b/ui/gl/gl_image.cc
index b64f3a4..53cd0deb 100644
--- a/ui/gl/gl_image.cc
+++ b/ui/gl/gl_image.cc
@@ -10,6 +10,46 @@
 
 namespace gl {
 
+// NOTE: It is not possible to use static_cast in the below "safe downcast"
+// functions, as the compiler doesn't know that the various GLImage subclasses
+// do in fact inherit from GLImage. However, the reinterpret_casts used are
+// safe, as |image| actually is an instance of the type in question.
+
+// static
+GLImageD3D* GLImage::ToGLImageD3D(GLImage* image) {
+  if (!image || image->GetType() != Type::D3D)
+    return nullptr;
+  return reinterpret_cast<GLImageD3D*>(image);
+}
+
+// static
+GLImageMemory* GLImage::ToGLImageMemory(GLImage* image) {
+  if (!image || image->GetType() != Type::MEMORY)
+    return nullptr;
+  return reinterpret_cast<GLImageMemory*>(image);
+}
+
+// static
+GLImageIOSurface* GLImage::ToGLImageIOSurface(GLImage* image) {
+  if (!image || image->GetType() != Type::IOSURFACE)
+    return nullptr;
+  return reinterpret_cast<GLImageIOSurface*>(image);
+}
+
+// static
+GLImageDXGI* GLImage::ToGLImageDXGI(GLImage* image) {
+  if (!image || image->GetType() != Type::DXGI_IMAGE)
+    return nullptr;
+  return reinterpret_cast<GLImageDXGI*>(image);
+}
+
+// static
+media::GLImagePbuffer* GLImage::ToGLImagePbuffer(GLImage* image) {
+  if (!image || image->GetType() != Type::PBUFFER)
+    return nullptr;
+  return reinterpret_cast<media::GLImagePbuffer*>(image);
+}
+
 gfx::Size GLImage::GetSize() {
   NOTREACHED();
   return gfx::Size();
diff --git a/ui/gl/gl_image.h b/ui/gl/gl_image.h
index 527c3f9..0372723 100644
--- a/ui/gl/gl_image.h
+++ b/ui/gl/gl_image.h
@@ -9,6 +9,7 @@
 
 #include <string>
 
+#include "base/gtest_prod_util.h"
 #include "base/memory/ref_counted.h"
 #include "build/build_config.h"
 #include "ui/gfx/buffer_types.h"
@@ -29,27 +30,63 @@
 }  // namespace trace_event
 }  // namespace base
 
+namespace gl {
+class GLImage;
+}
+
 namespace gpu {
 class DawnEGLImageRepresentation;
 class D3DImageBacking;
+class D3DImageBackingFactoryTest;
+class GpuMemoryBufferFactoryAndroidHardwareBuffer;
+class GpuMemoryBufferFactoryDXGI;
+class GpuMemoryBufferFactoryIOSurface;
+class IOSurfaceImageBackingFactory;
+class IOSurfaceImageBackingFactoryNewTestBase;
+class OverlayD3DImageRepresentation;
 class TestOverlayImageRepresentation;
+void SetColorSpaceOnGLImage(gl::GLImage* gl_image,
+                            const gfx::ColorSpace& color_space);
+FORWARD_DECLARE_TEST(CALayerTreeTest, HDRTrigger);
+FORWARD_DECLARE_TEST(CompoundImageBackingTest, NoUploadOnOverlayMemoryAccess);
+FORWARD_DECLARE_TEST(D3DImageBackingFactoryTestSwapChain,
+                     CreateAndPresentSwapChain);
+FORWARD_DECLARE_TEST(D3DImageBackingFactoryTest, CreateFromSharedMemory);
 }  // namespace gpu
 
 namespace gpu::gles2 {
+class GLES2DecoderImpl;
+class GLES2DecoderPassthroughImpl;
 class Texture;
 }
 
 namespace media {
+class GLImagePbuffer;
+class DXVAVideoDecodeAccelerator;
 class VTVideoDecodeAccelerator;
 }
 
 namespace ui {
+class NativePixmapEGLBinding;
 class SurfacelessGlRenderer;
 class SurfacelessSkiaGlRenderer;
 }  // namespace ui
 
+namespace viz {
+class ImageContextImpl;
+class SkiaOutputDeviceDComp;
+}  // namespace viz
+
 namespace gl {
 
+class DCompPresenterTest;
+class DirectCompositionSurfaceTest;
+class GLImageD3D;
+class GLImageDXGI;
+class GLImageIOSurface;
+class GLImageMemory;
+class SwapChainPresenter;
+
 // Encapsulates an image that can be bound and/or copied to a texture, hiding
 // platform specific management.
 class GL_EXPORT GLImage : public base::RefCounted<GLImage> {
@@ -82,6 +119,16 @@
   // Release image from texture currently bound to |target|.
   virtual void ReleaseTexImage(unsigned target);
 
+ protected:
+  // NOTE: We are in the process of eliminating client usage of GLImage. As part
+  // of this effort, we are incrementally moving its public interface to be
+  // protected with friend'ing of existing users. DO NOT ADD MORE client usage -
+  // instead, reach out to shared-image-team@ with your use case.
+  // See crbug.com/1382031.
+  GLImage() = default;
+
+  virtual ~GLImage() = default;
+
   // Define texture currently bound to |target| by copying image into it.
   // Returns true on success. It is valid for an implementation to always
   // return false.
@@ -101,6 +148,12 @@
   // this.
   virtual void SetColorSpace(const gfx::ColorSpace& color_space);
 
+  // Dumps information about the memory backing the GLImage to a dump named
+  // |dump_name|.
+  virtual void OnMemoryDump(base::trace_event::ProcessMemoryDump* pmd,
+                            uint64_t process_tracing_id,
+                            const std::string& dump_name);
+
   // An identifier for subclasses. Necessary for safe downcasting.
   enum class Type {
     NONE,
@@ -113,22 +166,6 @@
   };
   virtual Type GetType() const;
 
- protected:
-  // NOTE: We are in the process of eliminating client usage of GLImage. As part
-  // of this effort, we are incrementally moving its public interface to be
-  // protected with friend'ing of existing users. DO NOT ADD MORE client usage -
-  // instead, reach out to shared-image-team@ with your use case.
-  // See crbug.com/1382031.
-  GLImage() = default;
-
-  virtual ~GLImage() = default;
-
-  // Dumps information about the memory backing the GLImage to a dump named
-  // |dump_name|.
-  virtual void OnMemoryDump(base::trace_event::ProcessMemoryDump* pmd,
-                            uint64_t process_tracing_id,
-                            const std::string& dump_name);
-
   // Returns the NativePixmap backing the GLImage. If not backed by a
   // NativePixmap, returns null.
   virtual scoped_refptr<gfx::NativePixmap> GetNativePixmap();
@@ -138,13 +175,46 @@
   gfx::ColorSpace color_space_;
 
  private:
+  // Safe downcasts. All functions return nullptr if |image| does not exist or
+  // does not have the specified type.
+  static GLImageD3D* ToGLImageD3D(GLImage* image);
+  static GLImageMemory* ToGLImageMemory(GLImage* image);
+  static GLImageIOSurface* ToGLImageIOSurface(GLImage* image);
+  static GLImageDXGI* ToGLImageDXGI(GLImage* image);
+  static media::GLImagePbuffer* ToGLImagePbuffer(GLImage* image);
+
+  friend class DCompPresenterTest;
+  friend class DirectCompositionSurfaceTest;
+  friend class SwapChainPresenter;
   friend class gpu::DawnEGLImageRepresentation;
   friend class gpu::D3DImageBacking;
+  friend class gpu::D3DImageBackingFactoryTest;
+  friend class gpu::GpuMemoryBufferFactoryAndroidHardwareBuffer;
+  friend class gpu::GpuMemoryBufferFactoryDXGI;
+  friend class gpu::GpuMemoryBufferFactoryIOSurface;
+  friend class gpu::IOSurfaceImageBackingFactory;
+  friend class gpu::IOSurfaceImageBackingFactoryNewTestBase;
+  friend class gpu::OverlayD3DImageRepresentation;
   friend class gpu::TestOverlayImageRepresentation;
+  friend class gpu::gles2::GLES2DecoderImpl;
+  friend class gpu::gles2::GLES2DecoderPassthroughImpl;
   friend class gpu::gles2::Texture;
+  friend class media::DXVAVideoDecodeAccelerator;
   friend class media::VTVideoDecodeAccelerator;
+  friend class ui::NativePixmapEGLBinding;
   friend class ui::SurfacelessGlRenderer;
   friend class ui::SurfacelessSkiaGlRenderer;
+  friend class viz::ImageContextImpl;
+  friend class viz::SkiaOutputDeviceDComp;
+  friend void gpu::SetColorSpaceOnGLImage(GLImage* gl_image,
+                                          const gfx::ColorSpace& color_space);
+  FRIEND_TEST_ALL_PREFIXES(gpu::CALayerTreeTest, HDRTrigger);
+  FRIEND_TEST_ALL_PREFIXES(gpu::CompoundImageBackingTest,
+                           NoUploadOnOverlayMemoryAccess);
+  FRIEND_TEST_ALL_PREFIXES(gpu::D3DImageBackingFactoryTestSwapChain,
+                           CreateAndPresentSwapChain);
+  FRIEND_TEST_ALL_PREFIXES(gpu::D3DImageBackingFactoryTest,
+                           CreateFromSharedMemory);
 
   friend class base::RefCounted<GLImage>;
 };
diff --git a/ui/gl/gl_image_d3d.cc b/ui/gl/gl_image_d3d.cc
index d7e718d3..9ad41387 100644
--- a/ui/gl/gl_image_d3d.cc
+++ b/ui/gl/gl_image_d3d.cc
@@ -67,13 +67,6 @@
   return true;
 }
 
-// static
-GLImageD3D* GLImageD3D::FromGLImage(GLImage* image) {
-  if (!image || image->GetType() != Type::D3D)
-    return nullptr;
-  return static_cast<GLImageD3D*>(image);
-}
-
 GLImage::Type GLImageD3D::GetType() const {
   return Type::D3D;
 }
diff --git a/ui/gl/gl_image_d3d.h b/ui/gl/gl_image_d3d.h
index 33f9a3b..65e016c 100644
--- a/ui/gl/gl_image_d3d.h
+++ b/ui/gl/gl_image_d3d.h
@@ -40,9 +40,6 @@
   GLImageD3D(const GLImageD3D&) = delete;
   GLImageD3D& operator=(const GLImageD3D&) = delete;
 
-  // Safe downcast. Returns nullptr on failure.
-  static GLImageD3D* FromGLImage(GLImage* image);
-
   bool Initialize();
 
   // GLImage implementation
diff --git a/ui/gl/gl_image_dxgi.cc b/ui/gl/gl_image_dxgi.cc
index fc246617..33a117d 100644
--- a/ui/gl/gl_image_dxgi.cc
+++ b/ui/gl/gl_image_dxgi.cc
@@ -134,13 +134,6 @@
 GLImageDXGI::GLImageDXGI(const gfx::Size& size, EGLStreamKHR stream)
     : size_(size), stream_(stream) {}
 
-// static
-GLImageDXGI* GLImageDXGI::FromGLImage(GLImage* image) {
-  if (!image || image->GetType() != Type::DXGI_IMAGE)
-    return nullptr;
-  return static_cast<GLImageDXGI*>(image);
-}
-
 GLImageDXGI::BindOrCopy GLImageDXGI::ShouldBindOrCopy() {
   return BIND;
 }
diff --git a/ui/gl/gl_image_dxgi.h b/ui/gl/gl_image_dxgi.h
index 61118ca..83bf67da 100644
--- a/ui/gl/gl_image_dxgi.h
+++ b/ui/gl/gl_image_dxgi.h
@@ -23,9 +23,6 @@
  public:
   GLImageDXGI(const gfx::Size& size, EGLStreamKHR stream);
 
-  // Safe downcast. Returns nullptr on failure.
-  static GLImageDXGI* FromGLImage(GLImage* image);
-
   // GLImage implementation.
   BindOrCopy ShouldBindOrCopy() override;
   bool BindTexImage(unsigned target) override;
diff --git a/ui/gl/gl_image_io_surface.h b/ui/gl/gl_image_io_surface.h
index e245f7a..bcad4544 100644
--- a/ui/gl/gl_image_io_surface.h
+++ b/ui/gl/gl_image_io_surface.h
@@ -75,9 +75,6 @@
     return cv_pixel_buffer_;
   }
 
-  // Downcasts from |image|. Returns |nullptr| on failure.
-  static GLImageIOSurface* FromGLImage(GLImage* image);
-
  protected:
   GLImageIOSurface(const gfx::Size& size);
   ~GLImageIOSurface() override;
diff --git a/ui/gl/gl_image_io_surface.mm b/ui/gl/gl_image_io_surface.mm
index bffb9a9..b36693ca 100644
--- a/ui/gl/gl_image_io_surface.mm
+++ b/ui/gl/gl_image_io_surface.mm
@@ -212,11 +212,4 @@
   IOSurfaceSetColorSpace(io_surface_, color_space);
 }
 
-// static
-GLImageIOSurface* GLImageIOSurface::FromGLImage(GLImage* image) {
-  if (!image || image->GetType() != Type::IOSURFACE)
-    return nullptr;
-  return static_cast<GLImageIOSurface*>(image);
-}
-
 }  // namespace gl
diff --git a/ui/gl/gl_image_memory.cc b/ui/gl/gl_image_memory.cc
index 8bff9c9..826ea05 100644
--- a/ui/gl/gl_image_memory.cc
+++ b/ui/gl/gl_image_memory.cc
@@ -241,13 +241,6 @@
   }
 }
 
-// static
-GLImageMemory* GLImageMemory::FromGLImage(GLImage* image) {
-  if (!image || image->GetType() != Type::MEMORY)
-    return nullptr;
-  return static_cast<GLImageMemory*>(image);
-}
-
 bool GLImageMemory::Initialize(const unsigned char* memory,
                                gfx::BufferFormat format,
                                size_t stride,
diff --git a/ui/gl/gl_image_memory.h b/ui/gl/gl_image_memory.h
index f7bf906..f54411e 100644
--- a/ui/gl/gl_image_memory.h
+++ b/ui/gl/gl_image_memory.h
@@ -33,9 +33,6 @@
                   size_t stride,
                   bool disable_pbo_upload = false);
 
-  // Safe downcast. Returns |nullptr| on failure.
-  static GLImageMemory* FromGLImage(GLImage* image);
-
   // Overridden from GLImage:
   gfx::Size GetSize() override;
   unsigned GetInternalFormat() override;
diff --git a/ui/gl/gl_surface.h b/ui/gl/gl_surface.h
index 7f5911b..6f2a3c68 100644
--- a/ui/gl/gl_surface.h
+++ b/ui/gl/gl_surface.h
@@ -78,13 +78,14 @@
 // Contains per frame data, and is passed along with SwapBuffer, PostSubbuffer,
 // CommitOverlayPlanes type methods.
 struct FrameData {
-  FrameData() = default;
+  explicit FrameData(int64_t seq = -1) : seq(seq) {}
   ~FrameData() = default;
 
-  FrameData(const FrameData&) = delete;
-  FrameData& operator=(const FrameData&) = delete;
-  FrameData(FrameData&& other) = default;
-  FrameData& operator=(FrameData&& other) = default;
+  // Sequence number for this frame. The reserved value of -1 means that there
+  // is no sequence number specified (that is, corresponds to no sequence
+  // point). This may happen for some cases, like the ozone demo, tests, or
+  // users of GLSurface other than SkiaRenderer.
+  int64_t seq = -1;
 };
 
 // Encapsulates a surface that can be rendered to with GL, hiding platform
diff --git a/ui/gl/mojom/BUILD.gn b/ui/gl/mojom/BUILD.gn
index 3613c635..b9d053f5 100644
--- a/ui/gl/mojom/BUILD.gn
+++ b/ui/gl/mojom/BUILD.gn
@@ -7,7 +7,10 @@
 mojom("mojom") {
   generate_java = true
 
-  sources = [ "gpu_preference.mojom" ]
+  sources = [
+    "frame_data.mojom",
+    "gpu_preference.mojom",
+  ]
   public_deps = [ "//mojo/public/mojom/base" ]
   cpp_typemaps = [
     {
@@ -16,8 +19,15 @@
           mojom = "gl.mojom.GpuPreference"
           cpp = "::gl::GpuPreference"
         },
+        {
+          mojom = "gl.mojom.FrameData"
+          cpp = "::gl::FrameData"
+        },
       ]
-      traits_headers = [ "gpu_preference_mojom_traits.h" ]
+      traits_headers = [
+        "gpu_preference_mojom_traits.h",
+        "frame_data_mojom_traits.h",
+      ]
       traits_public_deps = [ "//ui/gl" ]
     },
   ]
diff --git a/ui/gl/mojom/frame_data.mojom b/ui/gl/mojom/frame_data.mojom
new file mode 100644
index 0000000..5db7af59c
--- /dev/null
+++ b/ui/gl/mojom/frame_data.mojom
@@ -0,0 +1,11 @@
+// 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.
+
+module gl.mojom;
+
+// This corresponds to gl::FrameData. It contains frame specific information
+// and is passed through calls to SwapBuffers and similar.
+struct FrameData {
+  int64 seq;
+};
diff --git a/ui/gl/mojom/frame_data_mojom_traits.h b/ui/gl/mojom/frame_data_mojom_traits.h
new file mode 100644
index 0000000..5a250f2
--- /dev/null
+++ b/ui/gl/mojom/frame_data_mojom_traits.h
@@ -0,0 +1,26 @@
+// 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 UI_GL_MOJOM_FRAME_DATA_MOJOM_TRAITS_H_
+#define UI_GL_MOJOM_FRAME_DATA_MOJOM_TRAITS_H_
+
+#include "mojo/public/cpp/bindings/struct_traits.h"
+#include "ui/gl/gl_surface.h"
+#include "ui/gl/mojom/frame_data.mojom-shared.h"
+
+namespace mojo {
+
+template <>
+struct StructTraits<gl::mojom::FrameDataDataView, gl::FrameData> {
+  static int64_t seq(const gl::FrameData& data) { return data.seq; }
+
+  static bool Read(gl::mojom::FrameDataDataView data, gl::FrameData* out) {
+    out->seq = data.seq();
+    return true;
+  }
+};
+
+}  // namespace mojo
+
+#endif  // UI_GL_MOJOM_FRAME_DATA_MOJOM_TRAITS_H_
diff --git a/ui/gl/swap_chain_presenter.cc b/ui/gl/swap_chain_presenter.cc
index e79138f..54a8dfd 100644
--- a/ui/gl/swap_chain_presenter.cc
+++ b/ui/gl/swap_chain_presenter.cc
@@ -1061,17 +1061,17 @@
   ReleaseDCOMPSurfaceResourcesIfNeeded();
 
   GLImageDXGI* image_dxgi =
-      GLImageDXGI::FromGLImage(params.images[kNV12ImageIndex].get());
+      GLImage::ToGLImageDXGI(params.images[kNV12ImageIndex].get());
   GLImageD3D* image_d3d =
-      GLImageD3D::FromGLImage(params.images[kNV12ImageIndex].get());
+      GLImage::ToGLImageD3D(params.images[kNV12ImageIndex].get());
 
   GLImageMemory* y_image_memory =
-      GLImageMemory::FromGLImage(params.images[kYPlaneImageIndex].get());
+      GLImage::ToGLImageMemory(params.images[kYPlaneImageIndex].get());
   GLImageMemory* uv_image_memory =
-      GLImageMemory::FromGLImage(params.images[kUVPlaneImageIndex].get());
+      GLImage::ToGLImageMemory(params.images[kUVPlaneImageIndex].get());
 
   GLImageD3D* swap_chain_image =
-      GLImageD3D::FromGLImage(params.images[kSwapChainImageIndex].get());
+      GLImage::ToGLImageD3D(params.images[kSwapChainImageIndex].get());
   if (swap_chain_image && !swap_chain_image->swap_chain())
     swap_chain_image = nullptr;
 
diff --git a/ui/gtk/window_frame_provider_gtk.cc b/ui/gtk/window_frame_provider_gtk.cc
index c7857a3..9180d273 100644
--- a/ui/gtk/window_frame_provider_gtk.cc
+++ b/ui/gtk/window_frame_provider_gtk.cc
@@ -5,6 +5,7 @@
 #include "ui/gtk/window_frame_provider_gtk.h"
 
 #include "base/logging.h"
+#include "third_party/skia/include/core/SkRRect.h"
 #include "ui/gfx/canvas.h"
 #include "ui/gfx/geometry/insets.h"
 #include "ui/gfx/geometry/rect.h"
diff --git a/ui/native_theme/native_theme_base.cc b/ui/native_theme/native_theme_base.cc
index aee53ab..169c60c 100644
--- a/ui/native_theme/native_theme_base.cc
+++ b/ui/native_theme/native_theme_base.cc
@@ -17,6 +17,7 @@
 #include "cc/paint/paint_flags.h"
 #include "cc/paint/paint_shader.h"
 #include "third_party/skia/include/core/SkPath.h"
+#include "third_party/skia/include/core/SkRRect.h"
 #include "third_party/skia/include/effects/SkGradientShader.h"
 #include "ui/base/layout.h"
 #include "ui/base/resource/resource_bundle.h"
diff --git a/ui/ozone/platform/wayland/gpu/gbm_surfaceless_wayland.cc b/ui/ozone/platform/wayland/gpu/gbm_surfaceless_wayland.cc
index c6355fb4..f98b822b 100644
--- a/ui/ozone/platform/wayland/gpu/gbm_surfaceless_wayland.cc
+++ b/ui/ozone/platform/wayland/gpu/gbm_surfaceless_wayland.cc
@@ -258,7 +258,7 @@
     PresentationCallback presentation_callback,
     gl::FrameData data) {
   SwapBuffersAsync(std::move(completion_callback),
-                   std::move(presentation_callback), std::move(data));
+                   std::move(presentation_callback), data);
 }
 
 EGLConfig GbmSurfacelessWayland::GetConfig() {
@@ -345,6 +345,7 @@
     }
 
     buffer_manager_->CommitOverlays(widget_, submitted_frame->frame_id,
+                                    submitted_frame->data,
                                     std::move(submitted_frame->configs));
     submitted_frames_.push_back(std::move(submitted_frame));
   }
diff --git a/ui/ozone/platform/wayland/gpu/gl_surface_egl_readback_wayland.cc b/ui/ozone/platform/wayland/gpu/gl_surface_egl_readback_wayland.cc
index f65ca846..4e4debc5 100644
--- a/ui/ozone/platform/wayland/gpu/gl_surface_egl_readback_wayland.cc
+++ b/ui/ozone/platform/wayland/gpu/gl_surface_egl_readback_wayland.cc
@@ -123,9 +123,9 @@
 
   const auto bounds = gfx::Rect(GetSize());
   buffer_manager_->CommitBuffer(widget_, next_buffer->buffer_id_,
-                                /*frame_id*/ next_buffer->buffer_id_, bounds,
-                                gfx::RoundedCornersF(), surface_scale_factor_,
-                                bounds);
+                                /*frame_id*/ next_buffer->buffer_id_, data,
+                                bounds, gfx::RoundedCornersF(),
+                                surface_scale_factor_, bounds);
 }
 
 gfx::SurfaceOrigin GLSurfaceEglReadbackWayland::GetOrigin() const {
diff --git a/ui/ozone/platform/wayland/gpu/wayland_buffer_manager_gpu.cc b/ui/ozone/platform/wayland/gpu/wayland_buffer_manager_gpu.cc
index 0c24d01..29e4d41c 100644
--- a/ui/ozone/platform/wayland/gpu/wayland_buffer_manager_gpu.cc
+++ b/ui/ozone/platform/wayland/gpu/wayland_buffer_manager_gpu.cc
@@ -247,6 +247,7 @@
 void WaylandBufferManagerGpu::CommitBuffer(gfx::AcceleratedWidget widget,
                                            uint32_t frame_id,
                                            uint32_t buffer_id,
+                                           gl::FrameData data,
                                            const gfx::Rect& bounds_rect,
                                            const gfx::RoundedCornersF& corners,
                                            float surface_scale_factor,
@@ -262,12 +263,13 @@
           gfx::RRectF(gfx::RectF(bounds_rect), corners), gfx::ColorSpace(),
           absl::nullopt),
       nullptr, buffer_id, surface_scale_factor);
-  CommitOverlays(widget, frame_id, std::move(overlay_configs));
+  CommitOverlays(widget, frame_id, data, std::move(overlay_configs));
 }
 
 void WaylandBufferManagerGpu::CommitOverlays(
     gfx::AcceleratedWidget widget,
     uint32_t frame_id,
+    gl::FrameData data,
     std::vector<wl::WaylandOverlayConfig> overlays) {
   DCHECK(gpu_thread_runner_);
   if (!gpu_thread_runner_->BelongsToCurrentThread()) {
@@ -275,13 +277,13 @@
     gpu_thread_runner_->PostTask(
         FROM_HERE, base::BindOnce(&WaylandBufferManagerGpu::CommitOverlays,
                                   base::Unretained(this), widget, frame_id,
-                                  std::move(overlays)));
+                                  data, std::move(overlays)));
     return;
   }
 
   base::OnceClosure task = base::BindOnce(
       &WaylandBufferManagerGpu::CommitOverlaysTask, base::Unretained(this),
-      widget, frame_id, std::move(overlays));
+      widget, frame_id, data, std::move(overlays));
   RunOrQueueTask(std::move(task));
 }
 
@@ -497,11 +499,12 @@
 void WaylandBufferManagerGpu::CommitOverlaysTask(
     gfx::AcceleratedWidget widget,
     uint32_t frame_id,
+    gl::FrameData data,
     std::vector<wl::WaylandOverlayConfig> overlays) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(gpu_sequence_checker_);
   DCHECK(remote_host_);
 
-  remote_host_->CommitOverlays(widget, frame_id, std::move(overlays));
+  remote_host_->CommitOverlays(widget, frame_id, data, std::move(overlays));
 }
 
 void WaylandBufferManagerGpu::DestroyBufferTask(uint32_t buffer_id) {
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 82ebd696..60b6e511 100644
--- a/ui/ozone/platform/wayland/gpu/wayland_buffer_manager_gpu.h
+++ b/ui/ozone/platform/wayland/gpu/wayland_buffer_manager_gpu.h
@@ -19,6 +19,7 @@
 #include "mojo/public/cpp/bindings/remote.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/gfx/native_widget_types.h"
+#include "ui/gl/gl_surface.h"
 #include "ui/ozone/platform/wayland/common/wayland_util.h"
 #include "ui/ozone/platform/wayland/mojom/wayland_buffer_manager.mojom.h"
 
@@ -124,6 +125,7 @@
   void CommitBuffer(gfx::AcceleratedWidget widget,
                     uint32_t frame_id,
                     uint32_t buffer_id,
+                    gl::FrameData data,
                     const gfx::Rect& bounds_rect,
                     const gfx::RoundedCornersF& corners,
                     float surface_scale_factor,
@@ -132,6 +134,7 @@
   // |widget|.
   void CommitOverlays(gfx::AcceleratedWidget widget,
                       uint32_t frame_id,
+                      gl::FrameData data,
                       std::vector<wl::WaylandOverlayConfig> overlays);
 
   // Asks Wayland to destroy a wl_buffer.
@@ -228,6 +231,7 @@
                                   uint32_t buf_id);
   void CommitOverlaysTask(gfx::AcceleratedWidget widget,
                           uint32_t frame_id,
+                          gl::FrameData data,
                           std::vector<wl::WaylandOverlayConfig> overlays);
   void DestroyBufferTask(uint32_t buffer_id);
 
diff --git a/ui/ozone/platform/wayland/gpu/wayland_canvas_surface.cc b/ui/ozone/platform/wayland/gpu/wayland_canvas_surface.cc
index a2b949f..71c3097e 100644
--- a/ui/ozone/platform/wayland/gpu/wayland_canvas_surface.cc
+++ b/ui/ozone/platform/wayland/gpu/wayland_canvas_surface.cc
@@ -98,8 +98,8 @@
 
   void CommitBuffer(const gfx::Rect& damage, float buffer_scale) {
     buffer_manager_->CommitBuffer(widget_, buffer_id_, /*frame_id*/ buffer_id_,
-                                  gfx::Rect(size_), gfx::RoundedCornersF(),
-                                  buffer_scale, damage);
+                                  frame_data_, gfx::Rect(size_),
+                                  gfx::RoundedCornersF(), buffer_scale, damage);
   }
 
   void OnUse() {
@@ -140,6 +140,8 @@
     return pending_damage_region_;
   }
 
+  void set_frame_data(const gl::FrameData& data) { frame_data_ = data; }
+
  private:
   // The size of the buffer.
   gfx::Size size_;
@@ -167,6 +169,9 @@
 
   // Pending damage region if the buffer is pending to be submitted.
   gfx::Rect pending_damage_region_;
+
+  // Frame data.
+  gl::FrameData frame_data_;
 };
 
 class WaylandCanvasSurface::VSyncProvider : public gfx::VSyncProvider {
@@ -276,6 +281,7 @@
 void WaylandCanvasSurface::OnSwapBuffers(SwapBuffersCallback swap_ack_callback,
                                          gl::FrameData data) {
   if (pending_buffer_) {
+    pending_buffer_->set_frame_data(data);
     unsubmitted_buffers_.push_back(pending_buffer_);
     pending_buffer_ = nullptr;
   }
diff --git a/ui/ozone/platform/wayland/gpu/wayland_surface_factory.cc b/ui/ozone/platform/wayland/gpu/wayland_surface_factory.cc
index 64d861c5..c192a83 100644
--- a/ui/ozone/platform/wayland/gpu/wayland_surface_factory.cc
+++ b/ui/ozone/platform/wayland/gpu/wayland_surface_factory.cc
@@ -113,8 +113,7 @@
       !connection_)
     return nullptr;
 
-  WaylandWindow* window =
-      connection_->wayland_window_manager()->GetWindow(widget);
+  WaylandWindow* window = connection_->window_manager()->GetWindow(widget);
   if (!window)
     return nullptr;
 
@@ -200,10 +199,14 @@
     // be requested, which is not supported with this backend yet.
     impls.emplace_back(
         gl::GLImplementationParts(gl::ANGLEImplementation::kSwiftShader));
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+    // TODO(crbug.com/1231934): --use-angle=gl results in gles, resolve that and
+    // use the correct config/testsuite on Lacros-like Linux bots.
     impls.emplace_back(
         gl::GLImplementationParts(gl::ANGLEImplementation::kOpenGL));
     impls.emplace_back(
         gl::GLImplementationParts(gl::ANGLEImplementation::kOpenGLES));
+#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
   }
   return impls;
 }
diff --git a/ui/ozone/platform/wayland/host/linux_ui_delegate_wayland.cc b/ui/ozone/platform/wayland/host/linux_ui_delegate_wayland.cc
index 19172c7..b94a9d5 100644
--- a/ui/ozone/platform/wayland/host/linux_ui_delegate_wayland.cc
+++ b/ui/ozone/platform/wayland/host/linux_ui_delegate_wayland.cc
@@ -32,8 +32,7 @@
 bool LinuxUiDelegateWayland::ExportWindowHandle(
     gfx::AcceleratedWidget parent,
     base::OnceCallback<void(const std::string&)> callback) {
-  auto* parent_window =
-      connection_->wayland_window_manager()->GetWindow(parent);
+  auto* parent_window = connection_->window_manager()->GetWindow(parent);
   auto* foreign = connection_->xdg_foreign();
   if (!parent_window || !foreign)
     return false;
diff --git a/ui/ozone/platform/wayland/host/proxy/wayland_proxy_impl.cc b/ui/ozone/platform/wayland/host/proxy/wayland_proxy_impl.cc
index dee90b1a..2a163cd 100644
--- a/ui/ozone/platform/wayland/host/proxy/wayland_proxy_impl.cc
+++ b/ui/ozone/platform/wayland/host/proxy/wayland_proxy_impl.cc
@@ -19,16 +19,16 @@
 WaylandProxyImpl::~WaylandProxyImpl() {
   WaylandProxy::SetInstance(nullptr);
   if (delegate_)
-    connection_->wayland_window_manager()->RemoveObserver(this);
+    connection_->window_manager()->RemoveObserver(this);
 }
 
 void WaylandProxyImpl::SetDelegate(WaylandProxy::Delegate* delegate) {
   DCHECK(!delegate_);
   delegate_ = delegate;
   if (delegate_)
-    connection_->wayland_window_manager()->AddObserver(this);
+    connection_->window_manager()->AddObserver(this);
   else
-    connection_->wayland_window_manager()->RemoveObserver(this);
+    connection_->window_manager()->RemoveObserver(this);
 }
 
 wl_display* WaylandProxyImpl::GetDisplay() {
@@ -45,14 +45,14 @@
 
 wl_surface* WaylandProxyImpl::GetWlSurfaceForAcceleratedWidget(
     gfx::AcceleratedWidget widget) {
-  auto* window = connection_->wayland_window_manager()->GetWindow(widget);
+  auto* window = connection_->window_manager()->GetWindow(widget);
   DCHECK(window);
   return window->root_surface()->surface();
 }
 
 ui::WaylandWindow* WaylandProxyImpl::GetWaylandWindowForAcceleratedWidget(
     gfx::AcceleratedWidget widget) {
-  auto* window = connection_->wayland_window_manager()->GetWindow(widget);
+  auto* window = connection_->window_manager()->GetWindow(widget);
   DCHECK(window);
   return window;
 }
@@ -80,19 +80,19 @@
 
 ui::PlatformWindowType WaylandProxyImpl::GetWindowType(
     gfx::AcceleratedWidget widget) {
-  auto* window = connection_->wayland_window_manager()->GetWindow(widget);
+  auto* window = connection_->window_manager()->GetWindow(widget);
   DCHECK(window);
   return window->type();
 }
 
 bool WaylandProxyImpl::WindowHasPointerFocus(gfx::AcceleratedWidget widget) {
-  auto* window = connection_->wayland_window_manager()->GetWindow(widget);
+  auto* window = connection_->window_manager()->GetWindow(widget);
   DCHECK(window);
   return window->HasPointerFocus();
 }
 
 bool WaylandProxyImpl::WindowHasKeyboardFocus(gfx::AcceleratedWidget widget) {
-  auto* window = connection_->wayland_window_manager()->GetWindow(widget);
+  auto* window = connection_->window_manager()->GetWindow(widget);
   DCHECK(window);
   return window->HasKeyboardFocus();
 }
diff --git a/ui/ozone/platform/wayland/host/shell_popup_wrapper.h b/ui/ozone/platform/wayland/host/shell_popup_wrapper.h
index 29cc5128..25ca4f0 100644
--- a/ui/ozone/platform/wayland/host/shell_popup_wrapper.h
+++ b/ui/ozone/platform/wayland/host/shell_popup_wrapper.h
@@ -69,6 +69,10 @@
   // Decorates the surface with a drop shadow.
   virtual void Decorate() = 0;
 
+  // Sets the scale factor for the next commit. Scale factor persists until a
+  // new one is set.
+  virtual void SetScaleFactor(float scale_factor) = 0;
+
  protected:
   // Asks the compositor to take explicit-grab for this popup.
   virtual void Grab(uint32_t serial) = 0;
diff --git a/ui/ozone/platform/wayland/host/shell_toplevel_wrapper.h b/ui/ozone/platform/wayland/host/shell_toplevel_wrapper.h
index c4f2b72..18adac8 100644
--- a/ui/ozone/platform/wayland/host/shell_toplevel_wrapper.h
+++ b/ui/ozone/platform/wayland/host/shell_toplevel_wrapper.h
@@ -151,6 +151,10 @@
   virtual bool SupportsActivation() = 0;
   virtual void Activate() = 0;
   virtual void Deactivate() = 0;
+
+  // Sets the scale factor for the next commit. Scale factor persists until a
+  // new one is set.
+  virtual void SetScaleFactor(float scale_factor) = 0;
 };
 
 // Look for |value| in |wl_array| in C++ style.
diff --git a/ui/ozone/platform/wayland/host/wayland_buffer_manager_host.cc b/ui/ozone/platform/wayland/host/wayland_buffer_manager_host.cc
index 1f4cef0..c930e83 100644
--- a/ui/ozone/platform/wayland/host/wayland_buffer_manager_host.cc
+++ b/ui/ozone/platform/wayland/host/wayland_buffer_manager_host.cc
@@ -64,7 +64,7 @@
   DCHECK(base::CurrentUIThread::IsSet());
 
   buffer_backings_.clear();
-  for (auto* window : connection_->wayland_window_manager()->GetAllWindows())
+  for (auto* window : connection_->window_manager()->GetAllWindows())
     window->OnChannelDestroyed();
 
   buffer_manager_gpu_associated_.reset();
@@ -266,6 +266,7 @@
 void WaylandBufferManagerHost::CommitOverlays(
     gfx::AcceleratedWidget widget,
     uint32_t frame_id,
+    const gl::FrameData& data,
     std::vector<wl::WaylandOverlayConfig> overlays) {
   DCHECK(base::CurrentUIThread::IsSet());
 
@@ -277,15 +278,14 @@
     error_message_ = "Invalid widget.";
     TerminateGpuProcess();
   }
-  WaylandWindow* window =
-      connection_->wayland_window_manager()->GetWindow(widget);
+  WaylandWindow* window = connection_->window_manager()->GetWindow(widget);
   // In tab dragging, window may have been destroyed when buffers reach here. We
   // omit buffer commits and OnSubmission, because the corresponding buffer
   // queue in gpu process should be destroyed soon.
   if (!window)
     return;
 
-  window->CommitOverlays(frame_id, overlays);
+  window->CommitOverlays(frame_id, data.seq, overlays);
 }
 
 void WaylandBufferManagerHost::DestroyBuffer(uint32_t buffer_id) {
diff --git a/ui/ozone/platform/wayland/host/wayland_buffer_manager_host.h b/ui/ozone/platform/wayland/host/wayland_buffer_manager_host.h
index 09f4becd..5368a320 100644
--- a/ui/ozone/platform/wayland/host/wayland_buffer_manager_host.h
+++ b/ui/ozone/platform/wayland/host/wayland_buffer_manager_host.h
@@ -22,6 +22,7 @@
 #include "ui/gfx/native_widget_types.h"
 #include "ui/gfx/presentation_feedback.h"
 #include "ui/gfx/swap_result.h"
+#include "ui/gl/gl_surface.h"
 #include "ui/ozone/platform/wayland/common/wayland_object.h"
 #include "ui/ozone/platform/wayland/common/wayland_util.h"
 #include "ui/ozone/platform/wayland/mojom/wayland_buffer_manager.mojom.h"
@@ -113,6 +114,7 @@
   // and OnPresentation on successful swap and pixels presented.
   void CommitOverlays(gfx::AcceleratedWidget widget,
                       uint32_t frame_id,
+                      const gl::FrameData& data,
                       std::vector<wl::WaylandOverlayConfig> overlays) override;
 
   // Ensures a WaylandBufferHandle of |buffer_id| is created for the
diff --git a/ui/ozone/platform/wayland/host/wayland_clipboard_unittest.cc b/ui/ozone/platform/wayland/host/wayland_clipboard_unittest.cc
index 8310d298..61c66905 100644
--- a/ui/ozone/platform/wayland/host/wayland_clipboard_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_clipboard_unittest.cc
@@ -300,7 +300,7 @@
         SendTouchUp();
       })};
 
-  auto* window_manager = connection_->wayland_window_manager();
+  auto* window_manager = connection_->window_manager();
 
   // Triggering copy on touch-down event.
   for (auto send_input_event : send_input_event_closures) {
diff --git a/ui/ozone/platform/wayland/host/wayland_connection.cc b/ui/ozone/platform/wayland/host/wayland_connection.cc
index 8fd9fe8..f1749aa 100644
--- a/ui/ozone/platform/wayland/host/wayland_connection.cc
+++ b/ui/ozone/platform/wayland/host/wayland_connection.cc
@@ -205,7 +205,7 @@
   // estabilished, initialize the event source and input objects.
   DCHECK(!event_source_);
   event_source_ = std::make_unique<WaylandEventSource>(
-      display(), event_queue_.get(), wayland_window_manager(), this);
+      display(), event_queue_.get(), window_manager(), this);
 
   // Create the buffer factory before registry listener is set so that shm, drm,
   // zwp_linux_dmabuf objects are able to be stored.
diff --git a/ui/ozone/platform/wayland/host/wayland_connection.h b/ui/ozone/platform/wayland/host/wayland_connection.h
index b8376d17..7e53a4b6 100644
--- a/ui/ozone/platform/wayland/host/wayland_connection.h
+++ b/ui/ozone/platform/wayland/host/wayland_connection.h
@@ -196,9 +196,7 @@
     return zcr_touchpad_haptics_.get();
   }
 
-  WaylandWindowManager* wayland_window_manager() {
-    return &wayland_window_manager_;
-  }
+  WaylandWindowManager* window_manager() { return &window_manager_; }
 
   WaylandBufferFactory* wayland_buffer_factory() const {
     return wayland_buffer_factory_.get();
@@ -390,7 +388,7 @@
   wl::Object<zxdg_output_manager_v1> xdg_output_manager_;
 
   // Manages Wayland windows.
-  WaylandWindowManager wayland_window_manager_{this};
+  WaylandWindowManager window_manager_{this};
 
   // Event source instance. Must be declared before input objects so it
   // outlives them so thus being able to properly handle their destruction.
diff --git a/ui/ozone/platform/wayland/host/wayland_data_drag_controller.cc b/ui/ozone/platform/wayland/host/wayland_data_drag_controller.cc
index 128b464..148a7b4 100644
--- a/ui/ozone/platform/wayland/host/wayland_data_drag_controller.cc
+++ b/ui/ozone/platform/wayland/host/wayland_data_drag_controller.cc
@@ -93,7 +93,7 @@
     : connection_(connection),
       data_device_manager_(data_device_manager),
       data_device_(data_device_manager->GetDevice()),
-      window_manager_(connection->wayland_window_manager()),
+      window_manager_(connection->window_manager()),
       pointer_delegate_(pointer_delegate),
       touch_delegate_(touch_delegate) {
   DCHECK(connection_);
diff --git a/ui/ozone/platform/wayland/host/wayland_data_drag_controller_unittest.cc b/ui/ozone/platform/wayland/host/wayland_data_drag_controller_unittest.cc
index 3a56dae..9dfb392f 100644
--- a/ui/ozone/platform/wayland/host/wayland_data_drag_controller_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_data_drag_controller_unittest.cc
@@ -1048,7 +1048,7 @@
   OSExchangeData os_exchange_data;
   os_exchange_data.SetString(sample_text_for_dnd());
 
-  auto* const window_manager = connection_->wayland_window_manager();
+  auto* const window_manager = connection_->window_manager();
   ASSERT_FALSE(window_manager->GetCurrentPointerFocusedWindow());
   ASSERT_FALSE(window_manager->GetCurrentTouchFocusedWindow());
 
diff --git a/ui/ozone/platform/wayland/host/wayland_frame_manager.cc b/ui/ozone/platform/wayland/host/wayland_frame_manager.cc
index 8d701f2..1b8cbe4 100644
--- a/ui/ozone/platform/wayland/host/wayland_frame_manager.cc
+++ b/ui/ozone/platform/wayland/host/wayland_frame_manager.cc
@@ -62,6 +62,7 @@
 
 WaylandFrame::WaylandFrame(
     uint32_t frame_id,
+    int64_t seq,
     WaylandSurface* root_surface,
     wl::WaylandOverlayConfig root_config,
     base::circular_deque<
@@ -72,7 +73,8 @@
       root_config(std::move(root_config)),
       subsurfaces_to_overlays(std::move(subsurfaces_to_overlays)),
       submission_acked(false),
-      presentation_acked(false) {}
+      presentation_acked(false),
+      seq(seq) {}
 
 WaylandFrame::WaylandFrame(
     WaylandSurface* root_surface,
@@ -336,7 +338,7 @@
       &WaylandFrameManager::FeedbackDiscarded};
 
   surface->set_buffer_transform(config.transform);
-  surface->set_surface_buffer_scale(ceil(config.surface_scale_factor));
+  surface->set_surface_buffer_scale(config.surface_scale_factor);
   surface->set_buffer_crop(config.crop_rect);
   surface->set_viewport_destination(config.bounds_rect.size());
   surface->set_opacity(config.opacity);
diff --git a/ui/ozone/platform/wayland/host/wayland_frame_manager.h b/ui/ozone/platform/wayland/host/wayland_frame_manager.h
index 7afc2bab..f25b166d10 100644
--- a/ui/ozone/platform/wayland/host/wayland_frame_manager.h
+++ b/ui/ozone/platform/wayland/host/wayland_frame_manager.h
@@ -36,6 +36,7 @@
  public:
   // A frame originated from gpu process, and hence, requires acknowledgements.
   WaylandFrame(uint32_t frame_id,
+               int64_t seq,
                WaylandSurface* root_surface,
                wl::WaylandOverlayConfig root_config,
                base::circular_deque<
@@ -87,6 +88,10 @@
   absl::optional<gfx::PresentationFeedback> feedback = absl::nullopt;
   // Whether this frame has had OnPresentation sent for it.
   bool presentation_acked;
+
+  // The sequence ID for this frame. This is used to know when the proper
+  // buffers associated with a configure arrive.
+  [[maybe_unused]] int64_t seq = -1;
 };
 
 // This is the frame update manager that configures graphical window/surface
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 ae25d11..eba965d 100644
--- a/ui/ozone/platform/wayland/host/wayland_input_method_context.cc
+++ b/ui/ozone/platform/wayland/host/wayland_input_method_context.cc
@@ -129,7 +129,7 @@
       key_delegate_(key_delegate),
       ime_delegate_(ime_delegate),
       text_input_(nullptr) {
-  connection_->wayland_window_manager()->AddObserver(this);
+  connection_->window_manager()->AddObserver(this);
   Init();
 }
 
@@ -138,7 +138,7 @@
     DismissVirtualKeyboard();
     text_input_->Deactivate();
   }
-  connection_->wayland_window_manager()->RemoveObserver(this);
+  connection_->window_manager()->RemoveObserver(this);
 }
 
 void WaylandInputMethodContext::Init(bool initialize_for_testing) {
@@ -703,9 +703,9 @@
     return;
 
   WaylandWindow* window =
-      connection_->wayland_window_manager()->GetCurrentKeyboardFocusedWindow();
+      connection_->window_manager()->GetCurrentKeyboardFocusedWindow();
   if (!window && !connection_->seat()->keyboard())
-    window = connection_->wayland_window_manager()->GetCurrentActiveWindow();
+    window = connection_->window_manager()->GetCurrentActiveWindow();
   // Activate Wayland IME only if 1) InputMethod in Chrome has some
   // TextInputClient connected, and 2) the actual keyboard focus of Wayland
   // is given to Chrome, which is notified via wl_keyboard::enter.
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 8932350..d5557a99 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
@@ -261,7 +261,7 @@
     wl::SyncDisplay(connection_->display_wrapper(), *connection_->display());
 
     // Unset Keyboard focus.
-    connection_->wayland_window_manager()->SetKeyboardFocusedWindow(nullptr);
+    connection_->window_manager()->SetKeyboardFocusedWindow(nullptr);
 
     PostToServerAndWait([](wl::TestWaylandServerThread* server) {
       ASSERT_TRUE(server->text_input_manager_v1()->text_input());
@@ -311,8 +311,7 @@
     EXPECT_CALL(*zwp_text_input, ShowInputPanel());
   });
 
-  connection_->wayland_window_manager()->SetKeyboardFocusedWindow(
-      window_.get());
+  connection_->window_manager()->SetKeyboardFocusedWindow(window_.get());
   connection_->Flush();
 
   PostToServerAndWait([](wl::TestWaylandServerThread* server) {
@@ -323,7 +322,7 @@
     EXPECT_CALL(*zwp_text_input, Deactivate());
   });
 
-  connection_->wayland_window_manager()->SetKeyboardFocusedWindow(nullptr);
+  connection_->window_manager()->SetKeyboardFocusedWindow(nullptr);
   connection_->Flush();
 
   PostToServerAndWait([](wl::TestWaylandServerThread* server) {
@@ -350,8 +349,7 @@
     EXPECT_CALL(*zwp_text_input, ShowInputPanel()).Times(0);
   });
 
-  connection_->wayland_window_manager()->SetKeyboardFocusedWindow(
-      window_.get());
+  connection_->window_manager()->SetKeyboardFocusedWindow(window_.get());
   connection_->Flush();
 
   PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
@@ -387,7 +385,7 @@
     EXPECT_CALL(*zwp_text_input, Deactivate()).Times(0);
   });
 
-  connection_->wayland_window_manager()->SetKeyboardFocusedWindow(nullptr);
+  connection_->window_manager()->SetKeyboardFocusedWindow(nullptr);
   connection_->Flush();
 
   PostToServerAndWait([](wl::TestWaylandServerThread* server) {
diff --git a/ui/ozone/platform/wayland/host/wayland_output_manager.cc b/ui/ozone/platform/wayland/host/wayland_output_manager.cc
index 843fa74..8150272 100644
--- a/ui/ozone/platform/wayland/host/wayland_output_manager.cc
+++ b/ui/ozone/platform/wayland/host/wayland_output_manager.cc
@@ -68,7 +68,7 @@
   // 1. from `WaylandSurface::entered_outputs_`
   // 2. from `WaylandScreen::display_list_`
   // 3. from `WaylandOutputManager::output_list_`
-  auto* wayland_window_manager = connection_->wayland_window_manager();
+  auto* wayland_window_manager = connection_->window_manager();
   for (auto* window : wayland_window_manager->GetAllWindows())
     window->RemoveEnteredOutput(output_id);
 
@@ -157,7 +157,7 @@
   const bool is_primary =
       wayland_screen_ &&
       metrics.display_id == wayland_screen_->GetPrimaryDisplay().id();
-  for (auto* window : connection_->wayland_window_manager()->GetAllWindows()) {
+  for (auto* window : connection_->window_manager()->GetAllWindows()) {
     auto entered_output = window->GetPreferredEnteredOutputId();
     if (entered_output == metrics.output_id || (!entered_output && is_primary))
       window->UpdateWindowScale(true);
diff --git a/ui/ozone/platform/wayland/host/wayland_popup.cc b/ui/ozone/platform/wayland/host/wayland_popup.cc
index acdf67be..6b221ad 100644
--- a/ui/ozone/platform/wayland/host/wayland_popup.cc
+++ b/ui/ozone/platform/wayland/host/wayland_popup.cc
@@ -180,10 +180,11 @@
   gfx::Rect pending_bounds_dip(bounds_dip);
   if (pending_bounds_dip.IsEmpty())
     pending_bounds_dip.set_size(GetBoundsInDIP().size());
-  set_pending_bounds_dip(wl::TranslateBoundsToTopLevelCoordinates(
-      pending_bounds_dip, parent_window()->GetBoundsInDIP()));
-  set_pending_size_px(
-      delegate()->ConvertRectToPixels(pending_bounds_dip).size());
+  pending_configure_state_.bounds_dip =
+      wl::TranslateBoundsToTopLevelCoordinates(
+          pending_bounds_dip, parent_window()->GetBoundsInDIP());
+  pending_configure_state_.size_px =
+      delegate()->ConvertRectToPixels(pending_bounds_dip).size();
 }
 
 void WaylandPopup::HandleSurfaceConfigure(uint32_t serial) {
@@ -217,6 +218,17 @@
   root_surface()->set_opaque_region(IsOpaqueWindow() ? &region : nullptr);
 }
 
+void WaylandPopup::PropagateBufferScale(float new_scale) {
+  if (!IsSurfaceConfigured())
+    return;
+
+  if (!last_sent_buffer_scale_ ||
+      last_sent_buffer_scale_.value() != new_scale) {
+    shell_popup()->SetScaleFactor(new_scale);
+    last_sent_buffer_scale_ = new_scale;
+  }
+}
+
 void WaylandPopup::OnCloseRequest() {
   // Before calling OnCloseRequest, the |shell_popup_| must become hidden and
   // only then call OnCloseRequest().
@@ -240,11 +252,10 @@
   return shell_popup() ? shell_popup()->IsConfigured() : false;
 }
 
-void WaylandPopup::SetWindowGeometry(gfx::Rect bounds_dip) {
+void WaylandPopup::SetWindowGeometry(gfx::Size size_dip) {
   DCHECK(shell_popup_);
   const auto insets = GetDecorationInsetsInDIP();
-  shell_popup_->SetWindowGeometry(
-      {{insets.left(), insets.top()}, bounds_dip.size()});
+  shell_popup_->SetWindowGeometry({{insets.left(), insets.top()}, size_dip});
 }
 
 void WaylandPopup::AckConfigure(uint32_t serial) {
diff --git a/ui/ozone/platform/wayland/host/wayland_popup.h b/ui/ozone/platform/wayland/host/wayland_popup.h
index 1166fa8..1e1028c 100644
--- a/ui/ozone/platform/wayland/host/wayland_popup.h
+++ b/ui/ozone/platform/wayland/host/wayland_popup.h
@@ -25,6 +25,23 @@
 
   ShellPopupWrapper* shell_popup() const { return shell_popup_.get(); }
 
+  // WaylandWindow overrides:
+
+  // Configure related:
+  void HandleSurfaceConfigure(uint32_t serial) override;
+  void HandlePopupConfigure(const gfx::Rect& bounds) override;
+  bool IsSurfaceConfigured() override;
+  void AckConfigure(uint32_t serial) override;
+  void UpdateVisualSize(const gfx::Size& size_px) override;
+  void ApplyPendingBounds() override;
+
+  void OnCloseRequest() override;
+  bool OnInitialize(PlatformWindowInitProperties properties) override;
+  WaylandPopup* AsWaylandPopup() override;
+  void SetWindowGeometry(gfx::Size size_dip) override;
+  void UpdateWindowMask() override;
+  void PropagateBufferScale(float new_scale) override;
+
   // PlatformWindow
   void Show(bool inactive) override;
   void Hide() override;
@@ -32,19 +49,6 @@
   void SetBoundsInDIP(const gfx::Rect& bounds) override;
 
  private:
-  // WaylandWindow overrides:
-  void HandlePopupConfigure(const gfx::Rect& bounds) override;
-  void HandleSurfaceConfigure(uint32_t serial) override;
-  void OnCloseRequest() override;
-  bool OnInitialize(PlatformWindowInitProperties properties) override;
-  WaylandPopup* AsWaylandPopup() override;
-  bool IsSurfaceConfigured() override;
-  void SetWindowGeometry(gfx::Rect bounds) override;
-  void AckConfigure(uint32_t serial) override;
-  void UpdateVisualSize(const gfx::Size& size_px) override;
-  void ApplyPendingBounds() override;
-  void UpdateWindowMask() override;
-
   // Creates a popup window, which is visible as a menu window.
   bool CreateShellPopup();
 
@@ -72,6 +76,9 @@
   // Ozone/Wayland may not do so. Otherwise, a new state (if bounds has been
   // changed) won't be applied.
   bool schedule_redraw_ = false;
+
+  // The last buffer scale sent to the wayland server.
+  absl::optional<float> last_sent_buffer_scale_;
 };
 
 }  // namespace ui
diff --git a/ui/ozone/platform/wayland/host/wayland_screen.cc b/ui/ozone/platform/wayland/host/wayland_screen.cc
index ab984d7..665e7ce 100644
--- a/ui/ozone/platform/wayland/host/wayland_screen.cc
+++ b/ui/ozone/platform/wayland/host/wayland_screen.cc
@@ -295,7 +295,7 @@
 
 display::Display WaylandScreen::GetDisplayForAcceleratedWidget(
     gfx::AcceleratedWidget widget) const {
-  auto* window = connection_->wayland_window_manager()->GetWindow(widget);
+  auto* window = connection_->window_manager()->GetWindow(widget);
   // A window might be destroyed by this time on shutting down the browser.
   if (!window)
     return GetPrimaryDisplay();
@@ -340,8 +340,7 @@
   // If a pointer is located in any of the existing wayland windows, return
   // the last known cursor position.
   auto* cursor_position = connection_->wayland_cursor_position();
-  if (connection_->wayland_window_manager()
-          ->GetCurrentPointerOrTouchFocusedWindow() &&
+  if (connection_->window_manager()->GetCurrentPointerOrTouchFocusedWindow() &&
       cursor_position)
     return cursor_position->GetCursorSurfacePoint();
 
@@ -349,8 +348,7 @@
   // outside of largest window bounds.
   // TODO(oshima): Change this for the case that screen coordinates is
   // available.
-  auto* window =
-      connection_->wayland_window_manager()->GetWindowWithLargestBounds();
+  auto* window = connection_->window_manager()->GetWindowWithLargestBounds();
   DCHECK(window);
   const gfx::Rect bounds = window->GetBoundsInDIP();
   return gfx::Point(bounds.width() + 10, bounds.height() + 10);
@@ -360,8 +358,8 @@
     const gfx::Point& point) const {
   // It is safe to check only for focused windows and test if they contain the
   // point or not.
-  auto* window = connection_->wayland_window_manager()
-                     ->GetCurrentPointerOrTouchFocusedWindow();
+  auto* window =
+      connection_->window_manager()->GetCurrentPointerOrTouchFocusedWindow();
   if (window && window->GetBoundsInDIP().Contains(point))
     return window->GetWidget();
   return gfx::kNullAcceleratedWidget;
@@ -436,7 +434,7 @@
     // We assume that the idle lock is initiated by the user, and therefore the
     // surface that we should use is the one owned by the window that is focused
     // currently.
-    const auto* window_manager = connection_->wayland_window_manager();
+    const auto* window_manager = connection_->window_manager();
     DCHECK(window_manager);
     const auto* current_window = window_manager->GetCurrentFocusedWindow();
     if (!current_window) {
diff --git a/ui/ozone/platform/wayland/host/wayland_screen_unittest.cc b/ui/ozone/platform/wayland/host/wayland_screen_unittest.cc
index a9e2fd7..2387522 100644
--- a/ui/ozone/platform/wayland/host/wayland_screen_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_screen_unittest.cc
@@ -204,6 +204,8 @@
         output3->Flush();
       });
 
+  WaitForAllDisplaysReady();
+
   EXPECT_EQ(3u, platform_screen_->GetAllDisplays().size());
 
   const uint32_t surface_id = window_->root_surface()->get_surface_id();
@@ -302,6 +304,8 @@
         output2->Flush();
       });
 
+  WaitForAllDisplaysReady();
+
   // Ensure that second display is not a primary one and have a different id.
   int64_t added_display_id = observer.GetDisplay().id();
   EXPECT_NE(platform_screen_->GetPrimaryDisplay().id(), added_display_id);
@@ -330,6 +334,8 @@
         output2->Flush();
       });
 
+  WaitForAllDisplaysReady();
+
   // The newly added display is not a primary yet.
   added_display_id = observer.GetDisplay().id();
   EXPECT_NE(platform_screen_->GetPrimaryDisplay().id(), added_display_id);
@@ -558,6 +564,8 @@
         output2->Flush();
       });
 
+  WaitForAllDisplaysReady();
+
   const display::Display second_display = observer.GetDisplay();
   EXPECT_EQ(second_display.bounds(), output2_rect);
 
@@ -670,6 +678,8 @@
         output2->Flush();
       });
 
+  WaitForAllDisplaysReady();
+
   const display::Display secondary_display = observer.GetDisplay();
   EXPECT_EQ(secondary_display.bounds(), output2_rect);
 
diff --git a/ui/ozone/platform/wayland/host/wayland_surface.cc b/ui/ozone/platform/wayland/host/wayland_surface.cc
index 3ca00de..93f690c 100644
--- a/ui/ozone/platform/wayland/host/wayland_surface.cc
+++ b/ui/ozone/platform/wayland/host/wayland_surface.cc
@@ -91,7 +91,9 @@
                                WaylandWindow* root_window)
     : connection_(connection),
       root_window_(root_window),
-      surface_(connection->CreateSurface()) {}
+      surface_(connection->CreateSurface()),
+      surface_submission_in_pixel_coordinates_(
+          connection->surface_submission_in_pixel_coordinates()) {}
 
 WaylandSurface::~WaylandSurface() {
   for (auto& release : linux_buffer_releases_) {
@@ -271,14 +273,14 @@
 }
 
 void WaylandSurface::set_surface_buffer_scale(float scale) {
-  if (SurfaceSubmissionInPixelCoordinates())
-    return;
-
-  pending_state_.buffer_scale = (scale < 1.0f) ? 1 : static_cast<int>(scale);
+  pending_state_.buffer_scale_float = scale;
 
   if (apply_state_immediately_) {
-    state_.buffer_scale = pending_state_.buffer_scale;
-    wl_surface_set_buffer_scale(surface_.get(), state_.buffer_scale);
+    state_.buffer_scale_float = pending_state_.buffer_scale_float;
+    if (!surface_submission_in_pixel_coordinates_)
+      wl_surface_set_buffer_scale(surface_.get(), GetWaylandScale(state_));
+    if (root_window_)
+      root_window_->PropagateBufferScale(scale);
   }
 }
 
@@ -298,7 +300,7 @@
         pending_state_.opaque_region_px.empty()
             ? nullptr
             : CreateAndAddRegion(pending_state_.opaque_region_px,
-                                 pending_state_.buffer_scale)
+                                 GetWaylandScale(pending_state_))
                   .get());
   }
 }
@@ -320,31 +322,33 @@
         surface_.get(),
         pending_state_.input_region_px.has_value()
             ? CreateAndAddRegion({pending_state_.input_region_px.value()},
-                                 pending_state_.buffer_scale)
+                                 GetWaylandScale(pending_state_))
                   .get()
             : nullptr);
   }
 }
 
+int WaylandSurface::GetWaylandScale(const State& state) {
+  if (surface_submission_in_pixel_coordinates_)
+    return 1;
+  return (state.buffer_scale_float < 1.0f)
+             ? 1
+             : std::ceil(state.buffer_scale_float);
+}
+
 wl::Object<wl_region> WaylandSurface::CreateAndAddRegion(
     const std::vector<gfx::Rect>& region_px,
     int32_t buffer_scale) {
   DCHECK(root_window_);
+  DCHECK(!surface_submission_in_pixel_coordinates_ || buffer_scale == 1);
 
   wl::Object<wl_region> region(
       wl_compositor_create_region(connection_->compositor()));
 
-  const bool surface_submission_in_pixel_coordinates =
-      SurfaceSubmissionInPixelCoordinates();
   for (const auto& rect_px : region_px) {
-    if (surface_submission_in_pixel_coordinates) {
-      wl_region_add(region.get(), rect_px.x(), rect_px.y(), rect_px.width(),
-                    rect_px.height());
-    } else {
-      gfx::Rect rect = gfx::ScaleToEnclosingRect(rect_px, 1.f / buffer_scale);
-      wl_region_add(region.get(), rect.x(), rect.y(), rect.width(),
-                    rect.height());
-    }
+    gfx::Rect rect = gfx::ScaleToEnclosingRect(rect_px, 1.f / buffer_scale);
+    wl_region_add(region.get(), rect.x(), rect.y(), rect.width(),
+                  rect.height());
   }
   return region;
 }
@@ -462,7 +466,7 @@
 
   // Don't set input region when use_native_frame is enabled.
   if (pending_state_.input_region_px != state_.input_region_px ||
-      pending_state_.buffer_scale != state_.buffer_scale) {
+      GetWaylandScale(pending_state_) != GetWaylandScale(state_)) {
     // Sets input region for input events to allow go through and
     // for the compositor to ignore the parts of the input region that fall
     // outside of the surface.
@@ -470,7 +474,7 @@
         surface_.get(),
         pending_state_.input_region_px.has_value()
             ? CreateAndAddRegion({pending_state_.input_region_px.value()},
-                                 pending_state_.buffer_scale)
+                                 GetWaylandScale(pending_state_))
                   .get()
             : nullptr);
   }
@@ -478,13 +482,13 @@
   // It's important to set opaque region for opaque windows (provides
   // optimization hint for the Wayland compositor).
   if (pending_state_.opaque_region_px != state_.opaque_region_px ||
-      pending_state_.buffer_scale != state_.buffer_scale) {
+      GetWaylandScale(pending_state_) != GetWaylandScale(state_)) {
     wl_surface_set_opaque_region(
         surface_.get(),
         pending_state_.opaque_region_px.empty()
             ? nullptr
             : CreateAndAddRegion(pending_state_.opaque_region_px,
-                                 pending_state_.buffer_scale)
+                                 GetWaylandScale(pending_state_))
                   .get());
   }
 
@@ -511,7 +515,7 @@
     if (augmented_surface_get_version(get_augmented_surface()) >=
         AUGMENTED_SURFACE_SET_ROUNDED_CLIP_BOUNDS_SINCE_VERSION) {
       gfx::RRectF rounded_clip_bounds = pending_state_.rounded_clip_bounds;
-      rounded_clip_bounds.Scale(1.f / pending_state_.buffer_scale);
+      rounded_clip_bounds.Scale(1.f / GetWaylandScale(pending_state_));
 
       augmented_surface_set_rounded_clip_bounds(
           get_augmented_surface(), rounded_clip_bounds.rect().x(),
@@ -563,16 +567,20 @@
     // Unset buffer scale if wp_viewport.destination will be set.
     applying_surface_scale = 1;
   } else {
-    applying_surface_scale = pending_state_.buffer_scale;
-    bounds = gfx::ScaleSize(bounds, 1.f / pending_state_.buffer_scale);
+    applying_surface_scale = GetWaylandScale(pending_state_);
+    bounds = gfx::ScaleSize(bounds, 1.f / GetWaylandScale(pending_state_));
   }
-  if (!SurfaceSubmissionInPixelCoordinates() &&
+  if (!surface_submission_in_pixel_coordinates_ &&
       surface_scale_set_ != applying_surface_scale) {
     wl_surface_set_buffer_scale(surface_.get(), applying_surface_scale);
     surface_scale_set_ = applying_surface_scale;
   }
   DCHECK_GE(surface_scale_set_, 1);
 
+  // If this is not a subsurface, propagate the buffer scale.
+  if (root_window_ && root_window_->root_surface() == this)
+    root_window_->PropagateBufferScale(pending_state_.buffer_scale_float);
+
   gfx::RectF viewport_src_dip;
   wl_fixed_t src_to_set[4] = {wl_fixed_from_int(-1), wl_fixed_from_int(-1),
                               wl_fixed_from_int(-1), wl_fixed_from_int(-1)};
@@ -628,7 +636,7 @@
       pending_state_.viewport_px.IsEmpty()
           ? viewport_src_dip.size()
           : gfx::ScaleSize(pending_state_.viewport_px,
-                           1.f / pending_state_.buffer_scale);
+                           1.f / GetWaylandScale(pending_state_));
   float dst_to_set[2] = {-1.f, -1.f};
   if (viewport_dst_dip != viewport_src_dip.size()) {
     dst_to_set[0] = viewport_dst_dip.width();
@@ -750,7 +758,7 @@
   buffer_id = other.buffer_id;
   buffer = other.buffer;
   buffer_size_px = other.buffer_size_px;
-  buffer_scale = other.buffer_scale;
+  buffer_scale_float = other.buffer_scale_float;
   buffer_transform = other.buffer_transform;
   crop = other.crop;
   viewport_px = other.viewport_px;
@@ -829,10 +837,6 @@
     root_window_->OnLeftOutput();
 }
 
-bool WaylandSurface::SurfaceSubmissionInPixelCoordinates() const {
-  return connection_->surface_submission_in_pixel_coordinates();
-}
-
 void WaylandSurface::set_color_space(gfx::ColorSpace color_space) {
   if (!connection_->zcr_color_manager())
     return;
diff --git a/ui/ozone/platform/wayland/host/wayland_surface.h b/ui/ozone/platform/wayland/host/wayland_surface.h
index 3878f3d..9957bfc 100644
--- a/ui/ozone/platform/wayland/host/wayland_surface.h
+++ b/ui/ozone/platform/wayland/host/wayland_surface.h
@@ -59,7 +59,6 @@
   zcr_blending_v1* blending() const { return blending_.get(); }
 
   uint32_t buffer_id() const { return state_.buffer_id; }
-  int32_t buffer_scale() const { return state_.buffer_scale; }
   float opacity() const { return state_.opacity; }
   bool use_blending() const { return state_.use_blending; }
 
@@ -268,8 +267,9 @@
     raw_ptr<wl_buffer, DanglingUntriaged> buffer = nullptr;
     gfx::Size buffer_size_px;
 
-    // Current scale factor of a next attached buffer used by the GPU process.
-    int32_t buffer_scale = 1;
+    // The buffer scale refers to the ratio between the buffer size and the
+    // window size. This allows support for high-DPI displays.
+    float buffer_scale_float = 1;
 
     // Transformation for how the compositor interprets the contents of the
     // buffer.
@@ -306,12 +306,20 @@
     bool contains_video = false;
   };
 
+  // The wayland scale refers to the scale factor used for calls to wayland
+  // apis such as wl_region_add. When SurfaceSubmissionInPixelCoordinates is
+  // true, this is always 1. Otherwise this is buffer_scale_float casted to an
+  // integer.
+  int GetWaylandScale(const State& state);
+
   // Tracks the last sent src and dst values across wayland protocol s.t. we
   // skip resending them when possible.
   wl_fixed_t src_set_[4] = {wl_fixed_from_int(-1), wl_fixed_from_int(-1),
                             wl_fixed_from_int(-1), wl_fixed_from_int(-1)};
   float dst_set_[2] = {-1.f, -1.f};
   // Tracks the last sent surface_scale value s.t. we skip resending.
+  // This is used by wl_surface_set_buffer_scale which only supports integer
+  // scales.
   int32_t surface_scale_set_ = 1;
 
   wl::Object<wl_region> CreateAndAddRegion(
@@ -326,8 +334,6 @@
   // called.
   State state_;
 
-  bool SurfaceSubmissionInPixelCoordinates() const;
-
   // Creates (if not created) the synchronization surface and returns a pointer
   // to it.
   zwp_linux_surface_synchronization_v1* GetOrCreateSurfaceSync();
@@ -352,6 +358,11 @@
       linux_buffer_releases_;
   ExplicitReleaseCallback next_explicit_release_request_;
 
+  // A cached copy of connection->SurfaceSubmissionInPixelCoordinates(). While
+  // it is technically possible to handle this value as mutable, in practice
+  // it's constant.
+  const bool surface_submission_in_pixel_coordinates_;
+
   // For top level window, stores outputs that the window is currently rendered
   // at.
   //
diff --git a/ui/ozone/platform/wayland/host/wayland_toplevel_window.cc b/ui/ozone/platform/wayland/host/wayland_toplevel_window.cc
index 926a629..c104525 100644
--- a/ui/ozone/platform/wayland/host/wayland_toplevel_window.cc
+++ b/ui/ozone/platform/wayland/host/wayland_toplevel_window.cc
@@ -460,7 +460,8 @@
   // the fullscreen mode, wayland may set the width and height to be 1. Instead,
   // explicitly set the bounds to the current desired ones or the previous
   // bounds.
-  gfx::Rect bounds_dip(pending_bounds_dip());
+  gfx::Rect bounds_dip(
+      pending_configure_state_.bounds_dip.value_or(gfx::Rect()));
   if (width_dip > 1 && height_dip > 1) {
     bounds_dip.SetRect(x, y, width_dip, height_dip);
     const auto insets = GetDecorationInsetsInDIP();
@@ -473,9 +474,10 @@
                                                 : GetBoundsInDIP();
   }
 
-  set_pending_bounds_dip(AdjustBoundsToConstraintsDIP(bounds_dip));
-  set_pending_size_px(
-      delegate()->ConvertRectToPixels(pending_bounds_dip()).size());
+  bounds_dip = AdjustBoundsToConstraintsDIP(bounds_dip);
+  pending_configure_state_.bounds_dip = bounds_dip;
+  pending_configure_state_.size_px =
+      delegate()->ConvertRectToPixels(bounds_dip).size();
 
   // Store the restored bounds if current state differs from the normal state.
   // It can be client or compositor side change from normal to something else.
@@ -514,8 +516,6 @@
 
 void WaylandToplevelWindow::HandleSurfaceConfigure(uint32_t serial) {
   ProcessPendingBoundsDip(serial);
-  set_pending_bounds_dip({});
-  set_pending_size_px({});
 }
 
 void WaylandToplevelWindow::UpdateVisualSize(const gfx::Size& size_px) {
@@ -532,8 +532,7 @@
 
     if (set_geometry_on_next_frame_) {
       auto size_dip = gfx::ScaleToRoundedSize(size_px, 1.f / window_scale());
-      // TODO(crbug.com/3814157): Use DIP bounds instead.
-      SetWindowGeometry(gfx::Rect(size_dip));
+      SetWindowGeometry(size_dip);
       set_geometry_on_next_frame_ = false;
     }
   }
@@ -587,13 +586,13 @@
   return shell_toplevel() ? shell_toplevel()->IsConfigured() : false;
 }
 
-void WaylandToplevelWindow::SetWindowGeometry(gfx::Rect bounds_dip) {
+void WaylandToplevelWindow::SetWindowGeometry(gfx::Size size_dip) {
   DCHECK(connection()->SupportsSetWindowGeometry());
 
   if (!shell_toplevel_)
     return;
 
-  gfx::Rect geometry_dip(bounds_dip.size());
+  gfx::Rect geometry_dip(size_dip);
 
   const auto insets = GetDecorationInsetsInDIP();
   if (state_ == PlatformWindowState::kNormal && !insets.IsEmpty())
@@ -610,6 +609,17 @@
     set_geometry_on_next_frame_ = true;
 }
 
+void WaylandToplevelWindow::PropagateBufferScale(float new_scale) {
+  if (!IsSurfaceConfigured())
+    return;
+
+  if (!last_sent_buffer_scale_ ||
+      last_sent_buffer_scale_.value() != new_scale) {
+    shell_toplevel()->SetScaleFactor(new_scale);
+    last_sent_buffer_scale_ = new_scale;
+  }
+}
+
 bool WaylandToplevelWindow::IsClientControlledWindowMovementSupported() const {
   auto* window_drag_controller = connection()->window_drag_controller();
   DCHECK(window_drag_controller);
diff --git a/ui/ozone/platform/wayland/host/wayland_toplevel_window.h b/ui/ozone/platform/wayland/host/wayland_toplevel_window.h
index dcbb4c7..f3e317e 100644
--- a/ui/ozone/platform/wayland/host/wayland_toplevel_window.h
+++ b/ui/ozone/platform/wayland/host/wayland_toplevel_window.h
@@ -53,10 +53,40 @@
 
   ShellToplevelWrapper* shell_toplevel() const { return shell_toplevel_.get(); }
 
+  // Sets the window's origin.
+  void SetOrigin(const gfx::Point& origin);
+
+  // WaylandWindow overrides:
+  void UpdateWindowScale(bool update_bounds) override;
+
+  // Configure related:
+  void HandleToplevelConfigure(int32_t width,
+                               int32_t height,
+                               const WindowStates& window_states) override;
+  void HandleAuraToplevelConfigure(int32_t x,
+                                   int32_t y,
+                                   int32_t width,
+                                   int32_t height,
+                                   const WindowStates& window_states) override;
+  void HandleSurfaceConfigure(uint32_t serial) override;
+  void UpdateVisualSize(const gfx::Size& size_px) override;
+  bool IsSurfaceConfigured() override;
+  void AckConfigure(uint32_t serial) override;
+  void UpdateDecorations() override;
+  void PropagateBufferScale(float new_scale) override;
+
   // Apply the bounds specified in the most recent configure event. This should
   // be called after processing all pending events in the wayland connection.
   void ApplyPendingBounds() override;
 
+  bool OnInitialize(PlatformWindowInitProperties properties) override;
+  bool IsActive() const override;
+  void SetWindowGeometry(gfx::Size size_dip) override;
+  bool IsScreenCoordinatesEnabled() const override;
+
+  // WmDragHandler overrides:
+  bool ShouldReleaseCaptureForDrag(ui::OSExchangeData* data) const override;
+
   // WmMoveResizeHandler
   void DispatchHostWindowDragMovement(
       int hittest,
@@ -88,69 +118,12 @@
   bool CanSetDecorationInsets() const override;
   void SetOpaqueRegion(const std::vector<gfx::Rect>* region_px) override;
   void SetInputRegion(const gfx::Rect* region_px) override;
+  bool IsClientControlledWindowMovementSupported() const override;
   void NotifyStartupComplete(const std::string& startup_id) override;
   void SetAspectRatio(const gfx::SizeF& aspect_ratio) override;
   void SetBoundsInPixels(const gfx::Rect& bounds) override;
   void SetBoundsInDIP(const gfx::Rect& bounds) override;
 
-  // Sets the window's origin.
-  void SetOrigin(const gfx::Point& origin);
-
-  // WaylandWindow overrides:
-  bool IsScreenCoordinatesEnabled() const override;
-
- private:
-  // WaylandWindow overrides:
-  void UpdateWindowScale(bool update_bounds) override;
-  void HandleToplevelConfigure(int32_t width,
-                               int32_t height,
-                               const WindowStates& window_states) override;
-  void HandleAuraToplevelConfigure(int32_t x,
-                                   int32_t y,
-                                   int32_t width,
-                                   int32_t height,
-                                   const WindowStates& window_states) override;
-  void HandleSurfaceConfigure(uint32_t serial) override;
-  void UpdateVisualSize(const gfx::Size& size_px) override;
-  bool OnInitialize(PlatformWindowInitProperties properties) override;
-  bool IsActive() const override;
-  bool IsSurfaceConfigured() override;
-  void SetWindowGeometry(gfx::Rect bounds) override;
-  void AckConfigure(uint32_t serial) override;
-  void UpdateDecorations() override;
-
-  // PlatformWindow overrides:
-  bool IsClientControlledWindowMovementSupported() const override;
-
-  // WmDragHandler overrides:
-  bool ShouldReleaseCaptureForDrag(ui::OSExchangeData* data) const override;
-
-  // zaura_surface listeners
-  static void OcclusionChanged(void* data,
-                               zaura_surface* surface,
-                               wl_fixed_t occlusion_fraction,
-                               uint32_t occlusion_reason);
-  static void LockFrame(void* data, zaura_surface* surface);
-  static void UnlockFrame(void* data, zaura_surface* surface);
-  static void OcclusionStateChanged(void* data,
-                                    zaura_surface* surface,
-                                    uint32_t mode);
-  static void DeskChanged(void* data, zaura_surface* surface, int state);
-  static void StartThrottle(void* data, zaura_surface* surface);
-  static void EndThrottle(void* data, zaura_surface* surface);
-  static void TooltipShown(void* data,
-                           zaura_surface* surface,
-                           const char* text,
-                           int32_t x,
-                           int32_t y,
-                           int32_t width,
-                           int32_t height) {}
-  static void TooltipHidden(void* data, zaura_surface* surface) {}
-
-  // Calls UpdateWindowShape, set_input_region and set_opaque_region for this
-  // toplevel window.
-  void UpdateWindowMask() override;
-
   // WmMoveLoopHandler:
   bool RunMoveLoop(const gfx::Vector2d& drag_offset) override;
   void EndMoveLoop() override;
@@ -191,6 +164,35 @@
 
   // SystemModalExtension:
   void SetSystemModal(bool modal) override;
+
+ private:
+  // WaylandWindow protected overrides:
+  // Calls UpdateWindowShape, set_input_region and set_opaque_region for this
+  // toplevel window.
+  void UpdateWindowMask() override;
+
+  // zaura_surface listeners
+  static void OcclusionChanged(void* data,
+                               zaura_surface* surface,
+                               wl_fixed_t occlusion_fraction,
+                               uint32_t occlusion_reason);
+  static void LockFrame(void* data, zaura_surface* surface);
+  static void UnlockFrame(void* data, zaura_surface* surface);
+  static void OcclusionStateChanged(void* data,
+                                    zaura_surface* surface,
+                                    uint32_t mode);
+  static void DeskChanged(void* data, zaura_surface* surface, int state);
+  static void StartThrottle(void* data, zaura_surface* surface);
+  static void EndThrottle(void* data, zaura_surface* surface);
+  static void TooltipShown(void* data,
+                           zaura_surface* surface,
+                           const char* text,
+                           int32_t x,
+                           int32_t y,
+                           int32_t width,
+                           int32_t height) {}
+  static void TooltipHidden(void* data, zaura_surface* surface) {}
+
   void UpdateSystemModal();
 
   void TriggerStateChanges();
@@ -321,6 +323,9 @@
   // True when screen coordinates is enabled.
   bool screen_coordinates_enabled_;
 
+  // The last buffer scale sent to the wayland server.
+  absl::optional<float> last_sent_buffer_scale_;
+
   raw_ptr<WorkspaceExtensionDelegate> workspace_extension_delegate_ = nullptr;
 };
 
diff --git a/ui/ozone/platform/wayland/host/wayland_window.cc b/ui/ozone/platform/wayland/host/wayland_window.cc
index 975c667a..e1eb1624 100644
--- a/ui/ozone/platform/wayland/host/wayland_window.cc
+++ b/ui/ozone/platform/wayland/host/wayland_window.cc
@@ -74,7 +74,7 @@
       wayland_overlay_delegation_enabled_(connection->viewporter() &&
                                           IsWaylandOverlayDelegationEnabled()),
       accelerated_widget_(
-          connection->wayland_window_manager()->AllocateAcceleratedWidget()),
+          connection->window_manager()->AllocateAcceleratedWidget()),
       ui_task_runner_(base::SingleThreadTaskRunner::GetCurrentDefault()) {
   // Set a class property key, which allows |this| to be used for drag action.
   SetWmDragHandler(this, this);
@@ -87,15 +87,15 @@
   PlatformEventSource::GetInstance()->RemovePlatformEventDispatcher(this);
 
   if (wayland_overlay_delegation_enabled_) {
-    connection_->wayland_window_manager()->RemoveSubsurface(
-        GetWidget(), primary_subsurface_.get());
+    connection_->window_manager()->RemoveSubsurface(GetWidget(),
+                                                    primary_subsurface_.get());
   }
   for (const auto& widget_subsurface : wayland_subsurfaces()) {
-    connection_->wayland_window_manager()->RemoveSubsurface(
-        GetWidget(), widget_subsurface.get());
+    connection_->window_manager()->RemoveSubsurface(GetWidget(),
+                                                    widget_subsurface.get());
   }
   if (root_surface_)
-    connection_->wayland_window_manager()->RemoveWindow(GetWidget());
+    connection_->window_manager()->RemoveWindow(GetWidget());
 
   // This might have already been hidden and another window has been shown.
   // Thus, the parent will have another child window. Do not reset it.
@@ -207,13 +207,13 @@
 }
 
 bool WaylandWindow::HasPointerFocus() const {
-  return this == connection_->wayland_window_manager()
-                     ->GetCurrentPointerFocusedWindow();
+  return this ==
+         connection_->window_manager()->GetCurrentPointerFocusedWindow();
 }
 
 bool WaylandWindow::HasKeyboardFocus() const {
-  return this == connection_->wayland_window_manager()
-                     ->GetCurrentKeyboardFocusedWindow();
+  return this ==
+         connection_->window_manager()->GetCurrentKeyboardFocusedWindow();
 }
 
 void WaylandWindow::RemoveEnteredOutput(uint32_t output_id) {
@@ -357,18 +357,17 @@
   // this specific window has grabbed the events, and they will be rerouted in
   // WaylandWindow::DispatchEvent method.
   if (!HasCapture())
-    connection_->wayland_window_manager()->GrabLocatedEvents(this);
+    connection_->window_manager()->GrabLocatedEvents(this);
 }
 
 void WaylandWindow::ReleaseCapture() {
   if (HasCapture())
-    connection_->wayland_window_manager()->UngrabLocatedEvents(this);
+    connection_->window_manager()->UngrabLocatedEvents(this);
   // See comment in SetCapture() for details on wayland and grabs.
 }
 
 bool WaylandWindow::HasCapture() const {
-  return connection_->wayland_window_manager()->located_events_grabber() ==
-         this;
+  return connection_->window_manager()->located_events_grabber() == this;
 }
 
 void WaylandWindow::SetFullscreen(bool fullscreen, int64_t target_display_id) {}
@@ -474,7 +473,7 @@
 
   if (event->IsLocatedEvent()) {
     auto* event_grabber =
-        connection_->wayland_window_manager()->located_events_grabber();
+        connection_->window_manager()->located_events_grabber();
     auto* root_parent_window = GetRootParentWindow();
 
     // We must reroute the events to the event grabber iff these windows belong
@@ -662,7 +661,7 @@
   opacity_ = properties.opacity;
   type_ = properties.type;
 
-  connection_->wayland_window_manager()->AddWindow(GetWidget(), this);
+  connection_->window_manager()->AddWindow(GetWidget(), this);
 
   if (!OnInitialize(std::move(properties)))
     return false;
@@ -672,8 +671,8 @@
         std::make_unique<WaylandSubsurface>(connection_, this);
     if (!primary_subsurface_->surface())
       return false;
-    connection_->wayland_window_manager()->AddSubsurface(
-        GetWidget(), primary_subsurface_.get());
+    connection_->window_manager()->AddSubsurface(GetWidget(),
+                                                 primary_subsurface_.get());
   }
 
   PlatformEventSource::GetInstance()->AddPlatformEventDispatcher(this);
@@ -687,7 +686,7 @@
   return true;
 }
 
-void WaylandWindow::SetWindowGeometry(gfx::Rect bounds) {}
+void WaylandWindow::SetWindowGeometry(gfx::Size size_dip) {}
 
 gfx::Vector2d WaylandWindow::GetWindowGeometryOffsetInDIP() const {
   if (!frame_insets_px_.has_value())
@@ -769,8 +768,7 @@
   auto subsurface = std::make_unique<WaylandSubsurface>(connection_, this);
   if (!subsurface->surface())
     return false;
-  connection_->wayland_window_manager()->AddSubsurface(GetWidget(),
-                                                       subsurface.get());
+  connection_->window_manager()->AddSubsurface(GetWidget(), subsurface.get());
   subsurface_stack_above_.push_back(subsurface.get());
   auto result = wayland_subsurfaces_.emplace(std::move(subsurface));
   DCHECK(result.second);
@@ -810,6 +808,7 @@
 
 bool WaylandWindow::CommitOverlays(
     uint32_t frame_id,
+    int64_t seq,
     std::vector<wl::WaylandOverlayConfig>& overlays) {
   if (overlays.empty())
     return true;
@@ -842,7 +841,7 @@
   if (!wayland_overlay_delegation_enabled_) {
     DCHECK_EQ(overlays.size(), 1u);
     frame_manager_->RecordFrame(std::make_unique<WaylandFrame>(
-        frame_id, root_surface(), std::move(*overlays.begin())));
+        frame_id, seq, root_surface(), std::move(*overlays.begin())));
     return true;
   }
 
@@ -905,7 +904,7 @@
   }
 
   frame_manager_->RecordFrame(std::make_unique<WaylandFrame>(
-      frame_id, root_surface(), std::move(root_config),
+      frame_id, seq, root_surface(), std::move(root_config),
       std::move(subsurfaces_to_overlays)));
 
   return true;
@@ -948,7 +947,11 @@
 }
 
 void WaylandWindow::ProcessPendingBoundsDip(uint32_t serial) {
-  if (pending_bounds_dip_.IsEmpty() &&
+  auto pending_bounds_dip =
+      pending_configure_state_.bounds_dip.value_or(gfx::Rect());
+  auto pending_size_px = pending_configure_state_.size_px.value_or(gfx::Size());
+
+  if (pending_bounds_dip.IsEmpty() &&
       GetPlatformWindowState() == PlatformWindowState::kMinimized &&
       pending_configures_.empty()) {
     // In exo, widget creation is deferred until the surface has contents and
@@ -960,15 +963,15 @@
     // As per spec, width and height must be greater than zero.
     if (bounds_in_dip.IsEmpty())
       bounds_in_dip = gfx::Rect(0, 0, 1, 1);
-    SetWindowGeometry(bounds_in_dip);
+    SetWindowGeometry(bounds_in_dip.size());
     AckConfigure(serial);
     root_surface()->Commit();
-  } else if (delegate()->ConvertRectToPixels(pending_bounds_dip_) ==
+  } else if (delegate()->ConvertRectToPixels(pending_bounds_dip) ==
                  GetBoundsInPixels() &&
              pending_configures_.empty()) {
-    // If |pending_bounds_dip_| matches the current window bounds, and
+    // If |pending_bounds_dip| matches the current window bounds, and
     // |pending_configures_| is empty, which implies that the window is already
-    // rendering at |pending_bounds_dip_|, then a new frame matching it may take
+    // rendering at |pending_bounds_dip|, then a new frame matching it may take
     // some time to arrive, despite the window delegate receives the updated
     // bounds. Without a new frame, UpdateVisualSize() is not invoked, leaving
     // this configure sequence unacknowledged. E.g: With static window content,
@@ -976,11 +979,11 @@
     // the window to redraw. Hence, acknowledge this configure sequence now to
     // tell the Wayland compositor that the requested configuration for this
     // window has been applied.
-    SetWindowGeometry(pending_bounds_dip_);
+    SetWindowGeometry(pending_bounds_dip.size());
     AckConfigure(serial);
     connection()->Flush();
   } else if (!pending_configures_.empty() &&
-             pending_bounds_dip_.size() ==
+             pending_bounds_dip.size() ==
                  pending_configures_.back().bounds_dip.size()) {
     // There is an existing pending_configure with the same size, do not push a
     // new one. Instead, update the serial of the pending_configure.
@@ -991,7 +994,7 @@
     LOG_IF(WARNING, pending_configures_.size() > 100u)
         << "The queue of configures is longer than 100!";
     pending_configures_.push_back(
-        {pending_bounds_dip_, pending_size_px_, serial});
+        {pending_bounds_dip, pending_size_px, serial});
     // The Wayland compositor can generate xdg-shell.configure events more
     // frequently than frame updates from gpu process. Throttle
     // ApplyPendingBounds() such that we forward new bounds to
@@ -999,6 +1002,9 @@
     if (pending_configures_.size() <= 1)
       ApplyPendingBounds();
   }
+
+  // Reset pending state.
+  pending_configure_state_ = PendingConfigureState();
 }
 
 gfx::Rect WaylandWindow::AdjustBoundsToConstraintsPx(
@@ -1066,7 +1072,7 @@
 
   if (result != pending_configures_.end()) {
     auto serial = result->serial;
-    SetWindowGeometry(result->bounds_dip);
+    SetWindowGeometry(result->bounds_dip.size());
     AckConfigure(serial);
     connection()->Flush();
     pending_configures_.erase(pending_configures_.begin(), ++result);
diff --git a/ui/ozone/platform/wayland/host/wayland_window.h b/ui/ozone/platform/wayland/host/wayland_window.h
index c1ac860..78ffeba 100644
--- a/ui/ozone/platform/wayland/host/wayland_window.h
+++ b/ui/ozone/platform/wayland/host/wayland_window.h
@@ -82,6 +82,9 @@
   // to do so (this is not needed upon window initialization).
   virtual void UpdateWindowScale(bool update_bounds);
 
+  // Propagates the buffer scale of the next commit to exo.
+  virtual void PropagateBufferScale(float new_scale) = 0;
+
   WaylandSurface* root_surface() const { return root_surface_.get(); }
   WaylandSubsurface* primary_subsurface() const {
     return primary_subsurface_.get();
@@ -109,6 +112,7 @@
   // subsurface_stack_below_.size() >= below.
   bool ArrangeSubsurfaceStack(size_t above, size_t below);
   bool CommitOverlays(uint32_t frame_id,
+                      int64_t seq,
                       std::vector<wl::WaylandOverlayConfig>& overlays);
 
   // Called when the focus changed on this window.
@@ -234,6 +238,7 @@
     WindowTiledEdges tiled_edges;
   };
 
+  // Configure related:
   virtual void HandleToplevelConfigure(int32_t width,
                                        int32_t height,
                                        const WindowStates& window_states);
@@ -262,6 +267,23 @@
   // size that GPU renders at.
   virtual void UpdateVisualSize(const gfx::Size& size_px);
 
+  // Called by shell surfaces to indicate that this window can start submitting
+  // frames. Updating state based on configure is handled separately to this.
+  void OnSurfaceConfigureEvent();
+
+  // Tells if the surface has already been configured. This will be true after
+  // the first set of configure event and ack request, meaning that wl_surface
+  // can attach buffers.
+  virtual bool IsSurfaceConfigured() = 0;
+
+  // Sends configure acknowledgement to the wayland server.
+  virtual void AckConfigure(uint32_t serial) = 0;
+
+  // Updates the window decorations, if possible at the moment. Denotes that
+  // window will request new window_geometry, if there're no existing state
+  // changes in flight to server.
+  virtual void UpdateDecorations();
+
   // Handles close requests.
   virtual void OnCloseRequest();
 
@@ -275,25 +297,12 @@
   virtual void OnDragLeave();
   virtual void OnDragSessionClose(ui::mojom::DragOperation operation);
 
-  // Tells if the surface has already been configured.
-  virtual bool IsSurfaceConfigured() = 0;
-
-  // Called by shell surfaces to indicate that this window can start submitting
-  // frames.
-  void OnSurfaceConfigureEvent();
-
   // Sets the window geometry.
-  virtual void SetWindowGeometry(gfx::Rect bounds);
+  virtual void SetWindowGeometry(gfx::Size size_dip);
 
   // Returns the offset of the window geometry within the window surface.
   gfx::Vector2d GetWindowGeometryOffsetInDIP() const;
 
-  // Sends configure acknowledgement to the wayland server.
-  virtual void AckConfigure(uint32_t serial) = 0;
-
-  // Updates the window decorations, if possible at the moment.
-  virtual void UpdateDecorations();
-
   // Returns the effective decoration insets.
   gfx::Insets GetDecorationInsetsInDIP() const;
 
@@ -381,9 +390,6 @@
   // Updates mask for this window.
   virtual void UpdateWindowMask() = 0;
 
-  // Processes the pending bounds in dip.
-  void ProcessPendingBoundsDip(uint32_t serial);
-
   // [Deprecated]
   // If the given |bounds_px| violates size constraints set for this window,
   // fixes them so they don't.
@@ -393,6 +399,12 @@
   // fixes them so they don't.
   gfx::Rect AdjustBoundsToConstraintsDIP(const gfx::Rect& bounds_dip);
 
+  const gfx::Size& restored_size_dip() const { return restored_size_dip_; }
+
+  // Configure related:
+  // Processes the pending bounds in dip.
+  void ProcessPendingBoundsDip(uint32_t serial);
+
   // Processes the size information form visual size update and returns true if
   // any pending configure is fulfilled.
   bool ProcessVisualSizeUpdate(const gfx::Size& size_px);
@@ -400,14 +412,17 @@
   // Applies pending bounds.
   virtual void ApplyPendingBounds();
 
-  gfx::Rect pending_bounds_dip() const { return pending_bounds_dip_; }
-  void set_pending_bounds_dip(const gfx::Rect& rect) {
-    pending_bounds_dip_ = rect;
-  }
-  gfx::Size pending_size_px() const { return pending_size_px_; }
-  void set_pending_size_px(const gfx::Size& size) { pending_size_px_ = size; }
+  // PendingConfigureState describes the content of a configure sent from the
+  // wayland server.
+  struct PendingConfigureState {
+    absl::optional<gfx::Rect> bounds_dip;
+    absl::optional<gfx::Size> size_px;
+  };
 
-  const gfx::Size& restored_size_dip() const { return restored_size_dip_; }
+  // This holds the requested state for the next configure from the server.
+  // The window may get several configuration events that update the pending
+  // bounds or other state.
+  PendingConfigureState pending_configure_state_;
 
  private:
   friend class WaylandBufferManagerViewportTest;
@@ -527,19 +542,6 @@
   // be invoked during UpdateVisualSize() in unit tests.
   bool apply_pending_state_on_update_visual_size_for_testing_ = false;
 
-  // These bounds attributes below have suffixes that indicate units used.
-  // Wayland operates in DIP but the platform operates in physical pixels so
-  // our WaylandWindow is the link that has to translate the units. See also
-  // comments in the implementation.
-  //
-  // Bounds that will be applied when the window state is finalized. The window
-  // may get several configuration events that update the pending bounds, and
-  // only upon finalizing the state is the latest value stored as the current
-  // bounds via |ApplyPendingBounds|. Measured in DIP because updated in the
-  // handler that receives DIP from Wayland.
-  gfx::Rect pending_bounds_dip_;
-  gfx::Size pending_size_px_;
-
   // The size of the platform window before it went maximized or fullscreen in
   // dip.
   gfx::Size restored_size_dip_;
diff --git a/ui/ozone/platform/wayland/host/wayland_window_drag_controller.cc b/ui/ozone/platform/wayland/host/wayland_window_drag_controller.cc
index 28f5b65b..176d5d01 100644
--- a/ui/ozone/platform/wayland/host/wayland_window_drag_controller.cc
+++ b/ui/ozone/platform/wayland/host/wayland_window_drag_controller.cc
@@ -3,7 +3,6 @@
 // found in the LICENSE file.
 
 #include "ui/ozone/platform/wayland/host/wayland_window_drag_controller.h"
-#include "base/memory/raw_ref.h"
 
 #include <extended-drag-unstable-v1-client-protocol.h>
 #include <wayland-client-protocol.h>
@@ -17,6 +16,7 @@
 #include "base/check.h"
 #include "base/containers/contains.h"
 #include "base/logging.h"
+#include "base/memory/raw_ref.h"
 #include "base/memory/weak_ptr.h"
 #include "base/notreached.h"
 #include "base/run_loop.h"
@@ -101,7 +101,7 @@
     : connection_(connection),
       data_device_manager_(device_manager),
       data_device_(device_manager->GetDevice()),
-      window_manager_(connection_->wayland_window_manager()),
+      window_manager_(connection_->window_manager()),
       pointer_delegate_(pointer_delegate),
       touch_delegate_(touch_delegate) {
   DCHECK(data_device_);
@@ -115,25 +115,21 @@
   if (state_ != State::kIdle)
     return true;
 
-  auto serial = connection_->serial_tracker().GetSerial(
-      {wl::SerialType::kTouchPress, wl::SerialType::kMousePress});
-  if (!serial.has_value()) {
-    LOG(ERROR) << "Failed to retrieve touch/mouse press serial.";
+  // TODO(crbug.com/1246529): Drop the heuristic below which detects the "drag
+  // source" info in favor of having it injected by the upper level layers.
+  auto [serial, origin] = GetSerialAndOrigin();
+  if (!serial || !origin) {
+    LOG(ERROR) << "Failed to retrieve dnd serial / origin window.";
     return false;
   }
 
   DVLOG(1) << "Starting DND session.";
   state_ = State::kAttached;
+  origin_window_ = origin;
   drag_source_ = serial->type == wl::SerialType::kTouchPress
                      ? DragSource::kTouch
                      : DragSource::kMouse;
 
-  origin_window_ = window_manager_->GetCurrentPointerOrTouchFocusedWindow();
-  if (!origin_window_) {
-    LOG(ERROR) << "Failed to get origin window.";
-    return false;
-  }
-
   DCHECK(!data_source_);
   data_source_ = data_device_manager_->CreateSource(this);
   data_source_->Offer({kMimeTypeChromiumWindow});
@@ -577,4 +573,20 @@
   return out << static_cast<int>(state);
 }
 
+std::pair<absl::optional<wl::Serial>, WaylandWindow*>
+WaylandWindowDragController::GetSerialAndOrigin() {
+  std::pair<absl::optional<wl::Serial>, WaylandWindow*> result{};
+  for (auto type : {wl::SerialType::kTouchPress, wl::SerialType::kMousePress}) {
+    auto serial = connection_->serial_tracker().GetSerial(type);
+    auto* window = type == wl::SerialType::kTouchPress
+                       ? window_manager_->GetCurrentTouchFocusedWindow()
+                       : window_manager_->GetCurrentPointerFocusedWindow();
+    if (window && serial &&
+        (!result.first || serial->timestamp > result.first->timestamp)) {
+      result = {serial, window};
+    }
+  }
+  return result;
+}
+
 }  // namespace ui
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 a80ae46b..352d9fbc 100644
--- a/ui/ozone/platform/wayland/host/wayland_window_drag_controller.h
+++ b/ui/ozone/platform/wayland/host/wayland_window_drag_controller.h
@@ -23,6 +23,7 @@
 #include "ui/ozone/platform/wayland/host/wayland_data_device.h"
 #include "ui/ozone/platform/wayland/host/wayland_data_source.h"
 #include "ui/ozone/platform/wayland/host/wayland_pointer.h"
+#include "ui/ozone/platform/wayland/host/wayland_serial_tracker.h"
 #include "ui/ozone/platform/wayland/host/wayland_toplevel_window.h"
 #include "ui/ozone/platform/wayland/host/wayland_touch.h"
 #include "ui/ozone/platform/wayland/host/wayland_window_observer.h"
@@ -98,6 +99,7 @@
 
   FRIEND_TEST_ALL_PREFIXES(WaylandWindowDragControllerTest,
                            HandleDraggedWindowDestructionAfterMoveLoop);
+  FRIEND_TEST_ALL_PREFIXES(WaylandWindowDragControllerTest, GetSerialAndOrigin);
 
   // WaylandDataDevice::DragDelegate:
   bool IsDragSource() const override;
@@ -143,6 +145,11 @@
   // |extended_drag_available_for_testing_|.
   bool IsExtendedDragAvailableInternal() const;
 
+  // Returns the serial and origin window based on both how recent is the serial
+  // and the input focus information.
+  // TODO(crbug.com/1246529): Drop once drag source is supplied by the callers.
+  std::pair<absl::optional<wl::Serial>, WaylandWindow*> GetSerialAndOrigin();
+
   const raw_ptr<WaylandConnection> connection_;
   const raw_ptr<WaylandDataDeviceManager> data_device_manager_;
   const raw_ptr<WaylandDataDevice> data_device_;
diff --git a/ui/ozone/platform/wayland/host/wayland_window_drag_controller_unittest.cc b/ui/ozone/platform/wayland/host/wayland_window_drag_controller_unittest.cc
index 546cf99..93a2918 100644
--- a/ui/ozone/platform/wayland/host/wayland_window_drag_controller_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_window_drag_controller_unittest.cc
@@ -13,6 +13,7 @@
 #include "base/bind.h"
 #include "base/notreached.h"
 #include "base/test/bind.h"
+#include "base/time/time.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/events/base_event_utils.h"
 #include "ui/events/event.h"
@@ -25,6 +26,7 @@
 #include "ui/ozone/platform/wayland/host/wayland_output.h"
 #include "ui/ozone/platform/wayland/host/wayland_output_manager.h"
 #include "ui/ozone/platform/wayland/host/wayland_screen.h"
+#include "ui/ozone/platform/wayland/host/wayland_serial_tracker.h"
 #include "ui/ozone/platform/wayland/host/wayland_window.h"
 #include "ui/ozone/platform/wayland/host/wayland_window_drag_controller.h"
 #include "ui/ozone/platform/wayland/host/wayland_window_manager.h"
@@ -1098,6 +1100,8 @@
     output2->SetScale(2);
   });
 
+  WaitForAllDisplaysReady();
+
   const std::vector<display::Display>& displays = screen_->GetAllDisplays();
   EXPECT_EQ(displays.size(), 2u);
 
@@ -1382,6 +1386,120 @@
             window_manager()->GetCurrentPointerOrTouchFocusedWindow());
 }
 
+TEST_P(WaylandWindowDragControllerTest, GetSerialAndOrigin) {
+  auto& serial_tracker = connection_->serial_tracker();
+  auto& window_manager = *connection_->window_manager();
+
+  window_manager.SetPointerFocusedWindow(nullptr);
+  window_manager.SetTouchFocusedWindow(nullptr);
+  serial_tracker.ClearForTesting();
+  {  // No serial, no window focused.
+    auto [serial, origin] = drag_controller()->GetSerialAndOrigin();
+    EXPECT_FALSE(serial.has_value());
+    EXPECT_FALSE(origin);
+  }
+
+  // Check cases where only pointer focus info is set.
+  {  // Serial available, but no window focused.
+    serial_tracker.UpdateSerial(wl::SerialType::kMousePress, 1u);
+    auto [serial, origin] = drag_controller()->GetSerialAndOrigin();
+    EXPECT_FALSE(serial.has_value());
+    EXPECT_FALSE(origin);
+  }
+  {  // Both serial and focused window available.
+    window_manager.SetPointerFocusedWindow(window_.get());
+    auto [serial, origin] = drag_controller()->GetSerialAndOrigin();
+    EXPECT_EQ(origin, window_.get());
+    ASSERT_TRUE(serial.has_value());
+    EXPECT_EQ(wl::SerialType::kMousePress, serial->type);
+    EXPECT_EQ(1u, serial->value);
+  }
+
+  // Reset and check cases where only touch focus info is set.
+  window_manager.SetPointerFocusedWindow(nullptr);
+  window_manager.SetTouchFocusedWindow(nullptr);
+  serial_tracker.ClearForTesting();
+  {  // Serial available, but no window focused.
+    serial_tracker.UpdateSerial(wl::SerialType::kTouchPress, 2u);
+    auto [serial, origin] = drag_controller()->GetSerialAndOrigin();
+    EXPECT_FALSE(serial.has_value());
+    EXPECT_FALSE(origin);
+  }
+  {  // Both serial and focused window available.
+    window_manager.SetTouchFocusedWindow(window_.get());
+    auto [serial, origin] = drag_controller()->GetSerialAndOrigin();
+    EXPECT_EQ(origin, window_.get());
+    ASSERT_TRUE(serial.has_value());
+    EXPECT_EQ(wl::SerialType::kTouchPress, serial->type);
+    EXPECT_EQ(2u, serial->value);
+  }
+
+  // Check cases where both touch and mouse info are available.
+  {  // Mouse press serial is newer, though no window focused.
+    task_environment_.FastForwardBy(base::Seconds(1));
+    serial_tracker.UpdateSerial(wl::SerialType::kMousePress, 3u);
+    auto [serial, origin] = drag_controller()->GetSerialAndOrigin();
+    EXPECT_EQ(origin, window_.get());
+    ASSERT_TRUE(serial.has_value());
+    EXPECT_EQ(wl::SerialType::kTouchPress, serial->type);
+    EXPECT_EQ(2u, serial->value);
+  }
+  {  // Mouse press serial is newer and window focused is set.
+    window_manager.SetPointerFocusedWindow(window_.get());
+    auto [serial, origin] = drag_controller()->GetSerialAndOrigin();
+    EXPECT_EQ(origin, window_.get());
+    ASSERT_TRUE(serial.has_value());
+    EXPECT_EQ(wl::SerialType::kMousePress, serial->type);
+    EXPECT_EQ(3u, serial->value);
+  }
+  {  // Touch press serial is now the newest and window focused is set.
+    task_environment_.FastForwardBy(base::Seconds(1));
+    serial_tracker.UpdateSerial(wl::SerialType::kTouchPress, 4u);
+    auto [serial, origin] = drag_controller()->GetSerialAndOrigin();
+    EXPECT_EQ(origin, window_.get());
+    ASSERT_TRUE(serial.has_value());
+    EXPECT_EQ(wl::SerialType::kTouchPress, serial->type);
+    EXPECT_EQ(4u, serial->value);
+  }
+  {  // Touch info available, mouse press serial not set.
+    serial_tracker.ResetSerial(wl::SerialType::kMousePress);
+    auto [serial, origin] = drag_controller()->GetSerialAndOrigin();
+    EXPECT_EQ(origin, window_.get());
+    ASSERT_TRUE(serial.has_value());
+    EXPECT_EQ(wl::SerialType::kTouchPress, serial->type);
+    EXPECT_EQ(4u, serial->value);
+  }
+  // Reset focused window and serial information.
+  window_manager.SetPointerFocusedWindow(nullptr);
+  window_manager.SetTouchFocusedWindow(nullptr);
+  serial_tracker.ClearForTesting();
+}
+
+TEST_P(WaylandWindowDragControllerTest, DetectCorrectDragSource) {
+  EXPECT_FALSE(window_manager()->GetCurrentPointerOrTouchFocusedWindow());
+
+  // Press left mouse button within |window_|.
+  SendPointerEnter(window_.get(), &delegate_);
+  SendPointerPress(window_.get(), &delegate_, BTN_LEFT);
+
+  SendTouchDown(window_.get(), &delegate_, /*id=*/0, /*location=*/{0, 0});
+  SendTouchUp(/*id=*/0);
+
+  GetWaylandExtension(*window_)->StartWindowDraggingSessionIfNeeded(
+      /*allow_system_drag=*/false);
+  ASSERT_EQ(State::kAttached, drag_controller()->state());
+
+  ASSERT_TRUE(drag_controller()->drag_source().has_value());
+  EXPECT_EQ(WaylandWindowDragController::DragSource::kMouse,
+            *drag_controller()->drag_source());
+  EXPECT_EQ(window_.get(), drag_controller()->origin_window_for_testing());
+
+  SendDndMotion({40, 40});
+
+  SendDndDrop();
+  EXPECT_EQ(State::kIdle, drag_controller()->state());
+}
+
 INSTANTIATE_TEST_SUITE_P(XdgVersionStableTest,
                          WaylandWindowDragControllerTest,
                          Values(wl::ServerConfig{}));
diff --git a/ui/ozone/platform/wayland/host/wayland_window_factory.cc b/ui/ozone/platform/wayland/host/wayland_window_factory.cc
index 4857125bb..b8e0a3b 100644
--- a/ui/ozone/platform/wayland/host/wayland_window_factory.cc
+++ b/ui/ozone/platform/wayland/host/wayland_window_factory.cc
@@ -4,7 +4,6 @@
 
 #include <memory>
 
-#include "base/compiler_specific.h"
 #include "base/logging.h"
 #include "ui/gfx/native_widget_types.h"
 #include "ui/ozone/platform/wayland/host/wayland_connection.h"
@@ -15,15 +14,6 @@
 
 namespace ui {
 
-namespace {
-
-WaylandWindow* GetParentWindow(WaylandConnection* connection,
-                               gfx::AcceleratedWidget widget) {
-  return connection->wayland_window_manager()->GetWindow(widget);
-}
-
-}  // namespace
-
 // static
 std::unique_ptr<WaylandWindow> WaylandWindow::Create(
     PlatformWindowDelegate* delegate,
@@ -39,8 +29,8 @@
       // kPopup can be created by MessagePopupView without a parent window set.
       // It looks like it ought to be a global notification window. Thus, use a
       // toplevel window instead.
-      if (auto* parent =
-              GetParentWindow(connection, properties.parent_widget)) {
+      if (auto* parent = connection->window_manager()->GetWindow(
+              properties.parent_widget)) {
         window = std::make_unique<WaylandPopup>(delegate, connection, parent);
       } else {
         DLOG(WARNING) << "Failed to determine for menu/popup window.";
@@ -50,8 +40,8 @@
     case PlatformWindowType::kWindow:
     case PlatformWindowType::kBubble:
     case PlatformWindowType::kDrag:
-      // TODO(msisov): Figure out what kind of surface we need to create for
-      // bubble and drag windows.
+      // TODO(crbug.com/1399419): Figure out what kind of surface we need to
+      // create for kBubble and kDrag windows.
       window = std::make_unique<WaylandToplevelWindow>(delegate, connection);
       break;
     default:
diff --git a/ui/ozone/platform/wayland/host/wayland_window_manager_unittests.cc b/ui/ozone/platform/wayland/host/wayland_window_manager_unittests.cc
index bbbbde1..b7d4444 100644
--- a/ui/ozone/platform/wayland/host/wayland_window_manager_unittests.cc
+++ b/ui/ozone/platform/wayland/host/wayland_window_manager_unittests.cc
@@ -33,7 +33,7 @@
   void SetUp() override {
     WaylandTest::SetUp();
 
-    manager_ = connection_->wayland_window_manager();
+    manager_ = connection_->window_manager();
     ASSERT_TRUE(manager_);
   }
 
@@ -84,7 +84,7 @@
   auto window1 = CreateWaylandWindowWithParams(PlatformWindowType::kWindow,
                                                kDefaultBounds, &delegate);
   // When window is shown, it automatically gets keyboard focus. Reset it.
-  connection_->wayland_window_manager()->SetKeyboardFocusedWindow(nullptr);
+  connection_->window_manager()->SetKeyboardFocusedWindow(nullptr);
 
   wl::SyncDisplay(connection_->display_wrapper(), *connection_->display());
 
@@ -135,7 +135,7 @@
   auto window1 = CreateWaylandWindowWithParams(PlatformWindowType::kWindow,
                                                kDefaultBounds, &delegate);
   // When window is shown, it automatically gets keyboard focus. Reset it.
-  connection_->wayland_window_manager()->SetKeyboardFocusedWindow(nullptr);
+  connection_->window_manager()->SetKeyboardFocusedWindow(nullptr);
 
   wl::SyncDisplay(connection_->display_wrapper(), *connection_->display());
 
diff --git a/ui/ozone/platform/wayland/host/wayland_window_unittest.cc b/ui/ozone/platform/wayland/host/wayland_window_unittest.cc
index 4ce2b46..6f2f59b 100644
--- a/ui/ozone/platform/wayland/host/wayland_window_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_window_unittest.cc
@@ -233,7 +233,7 @@
       WaylandWindow* dispatching_window,
       const std::vector<WaylandWindow*>& non_dispatching_windows) {
     auto* pointer_focused_window =
-        connection_->wayland_window_manager()->GetCurrentPointerFocusedWindow();
+        connection_->window_manager()->GetCurrentPointerFocusedWindow();
 
     ASSERT_TRUE(pointer_focused_window);
     Event::DispatcherApi(&test_mouse_event_).set_target(pointer_focused_window);
@@ -247,7 +247,7 @@
       const std::vector<WaylandWindow*>& non_dispatching_windows) {
     ASSERT_LT(dispatching_windows.size(), 2u);
     auto* touch_focused_window =
-        connection_->wayland_window_manager()->GetCurrentTouchFocusedWindow();
+        connection_->window_manager()->GetCurrentTouchFocusedWindow();
     // There must be focused window to dispatch.
     if (dispatching_windows.size() == 0)
       EXPECT_FALSE(touch_focused_window);
@@ -273,8 +273,8 @@
       const std::vector<WaylandWindow*>& dispatching_windows,
       const std::vector<WaylandWindow*>& non_dispatching_windows) {
     ASSERT_LT(dispatching_windows.size(), 2u);
-    auto* keyboard_focused_window = connection_->wayland_window_manager()
-                                        ->GetCurrentKeyboardFocusedWindow();
+    auto* keyboard_focused_window =
+        connection_->window_manager()->GetCurrentKeyboardFocusedWindow();
 
     // There must be focused window to dispatch.
     if (dispatching_windows.size() == 0)
@@ -2557,6 +2557,8 @@
     output2->SetRect(gfx::Rect(1921, 0, 1920, 1080));
   });
 
+  WaitForAllDisplaysReady();
+
   // Client side WaylandOutput ids.
   ASSERT_EQ(2u, screen_->GetAllDisplays().size());
   const uint32_t output1_id =
@@ -2599,6 +2601,8 @@
     output3->SetRect(gfx::Rect(0, 1081, 1920, 1080));
   });
 
+  WaitForAllDisplaysReady();
+
   ASSERT_EQ(3u, screen_->GetAllDisplays().size());
   const uint32_t output3_id =
       screen_->GetOutputIdForDisplayId(screen_->GetAllDisplays().at(2).id());
@@ -2694,6 +2698,8 @@
     output2->SetRect(gfx::Rect(1921, 0, 1920, 1080));
   });
 
+  WaitForAllDisplaysReady();
+
   // Client side WaylandOutput ids.
   ASSERT_EQ(2u, screen_->GetAllDisplays().size());
   const uint32_t output1_id =
@@ -3223,7 +3229,7 @@
   background.z_order = INT32_MIN;
   background.buffer_id = buffer_id1;
   overlays.push_back(std::move(background));
-  buffer_manager_gpu_->CommitOverlays(window->GetWidget(), 1u,
+  buffer_manager_gpu_->CommitOverlays(window->GetWidget(), 1u, gl::FrameData(),
                                       std::move(overlays));
   PostToServerAndWait([surface_id](wl::TestWaylandServerThread* server) {
     auto* mock_surface = server->GetObject<wl::MockSurface>(surface_id);
@@ -3261,7 +3267,7 @@
   primary.z_order = 0;
   primary.buffer_id = buffer_id2;
   overlays.push_back(std::move(primary));
-  buffer_manager_gpu_->CommitOverlays(window->GetWidget(), 2u,
+  buffer_manager_gpu_->CommitOverlays(window->GetWidget(), 2u, gl::FrameData(),
                                       std::move(overlays));
 
   PostToServerAndWait([surface_id](wl::TestWaylandServerThread* server) {
diff --git a/ui/ozone/platform/wayland/host/wayland_zaura_output_unittest.cc b/ui/ozone/platform/wayland/host/wayland_zaura_output_unittest.cc
index 650bc6a9..f2a9f54 100644
--- a/ui/ozone/platform/wayland/host/wayland_zaura_output_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_zaura_output_unittest.cc
@@ -162,6 +162,8 @@
     secondary_id = secondary->GetAuraOutput()->display_id();
   });
 
+  WaitForAllDisplaysReady();
+
   auto* platform_screen = output_manager_->wayland_screen();
   DCHECK(platform_screen);
   ASSERT_EQ(2u, platform_screen->GetAllDisplays().size());
diff --git a/ui/ozone/platform/wayland/host/wayland_zaura_shell.cc b/ui/ozone/platform/wayland/host/wayland_zaura_shell.cc
index 44c194d2..c118e306 100644
--- a/ui/ozone/platform/wayland/host/wayland_zaura_shell.cc
+++ b/ui/ozone/platform/wayland/host/wayland_zaura_shell.cc
@@ -20,7 +20,7 @@
 
 namespace {
 constexpr uint32_t kMinVersion = 1;
-constexpr uint32_t kMaxVersion = 45;
+constexpr uint32_t kMaxVersion = 46;
 }
 
 // static
diff --git a/ui/ozone/platform/wayland/host/xdg_activation.cc b/ui/ozone/platform/wayland/host/xdg_activation.cc
index 3385758..68c8ef0b 100644
--- a/ui/ozone/platform/wayland/host/xdg_activation.cc
+++ b/ui/ozone/platform/wayland/host/xdg_activation.cc
@@ -81,7 +81,7 @@
 
 void XdgActivation::Activate(wl_surface* surface) const {
   const WaylandWindow* const active_window =
-      connection_->wayland_window_manager()->GetCurrentActiveWindow();
+      connection_->window_manager()->GetCurrentActiveWindow();
   if (!active_window) {
     LOG(WARNING) << "Cannot activate a window because no active windows found!";
     return;
diff --git a/ui/ozone/platform/wayland/host/xdg_popup_wrapper_impl.cc b/ui/ozone/platform/wayland/host/xdg_popup_wrapper_impl.cc
index 8164761..f0c1e96 100644
--- a/ui/ozone/platform/wayland/host/xdg_popup_wrapper_impl.cc
+++ b/ui/ozone/platform/wayland/host/xdg_popup_wrapper_impl.cc
@@ -171,8 +171,7 @@
                                          positioner.get()));
   if (!xdg_popup_)
     return false;
-  connection_->wayland_window_manager()->NotifyWindowRoleAssigned(
-      wayland_window_);
+  connection_->window_manager()->NotifyWindowRoleAssigned(wayland_window_);
 
   if (connection_->zaura_shell()) {
     uint32_t version =
@@ -255,6 +254,14 @@
                              ZAURA_POPUP_DECORATION_TYPE_SHADOW);
 }
 
+void XDGPopupWrapperImpl::SetScaleFactor(float scale_factor) {
+  if (aura_popup_ && zaura_popup_get_version(aura_popup_.get()) >=
+                         ZAURA_POPUP_SET_SCALE_FACTOR_SINCE_VERSION) {
+    uint32_t value = *reinterpret_cast<uint32_t*>(&scale_factor);
+    zaura_popup_set_scale_factor(aura_popup_.get(), value);
+  }
+}
+
 wl::Object<xdg_positioner> XDGPopupWrapperImpl::CreatePositioner() {
   wl::Object<xdg_positioner> positioner(
       xdg_wm_base_create_positioner(connection_->shell()));
diff --git a/ui/ozone/platform/wayland/host/xdg_popup_wrapper_impl.h b/ui/ozone/platform/wayland/host/xdg_popup_wrapper_impl.h
index e359e67..159e874 100644
--- a/ui/ozone/platform/wayland/host/xdg_popup_wrapper_impl.h
+++ b/ui/ozone/platform/wayland/host/xdg_popup_wrapper_impl.h
@@ -37,6 +37,7 @@
   void Grab(uint32_t serial) override;
   bool SupportsDecoration() override;
   void Decorate() override;
+  void SetScaleFactor(float scale_factor) override;
 
  private:
   wl::Object<xdg_positioner> CreatePositioner();
diff --git a/ui/ozone/platform/wayland/host/xdg_surface_wrapper_impl.cc b/ui/ozone/platform/wayland/host/xdg_surface_wrapper_impl.cc
index 048071b4..e88ff37 100644
--- a/ui/ozone/platform/wayland/host/xdg_surface_wrapper_impl.cc
+++ b/ui/ozone/platform/wayland/host/xdg_surface_wrapper_impl.cc
@@ -18,8 +18,7 @@
 
 XDGSurfaceWrapperImpl::~XDGSurfaceWrapperImpl() {
   is_configured_ = false;
-  connection_->wayland_window_manager()->NotifyWindowConfigured(
-      wayland_window_);
+  connection_->window_manager()->NotifyWindowConfigured(wayland_window_);
 }
 
 bool XDGSurfaceWrapperImpl::Initialize() {
@@ -49,8 +48,7 @@
   xdg_surface_ack_configure(xdg_surface_.get(), serial);
 
   is_configured_ = true;
-  connection_->wayland_window_manager()->NotifyWindowConfigured(
-      wayland_window_);
+  connection_->window_manager()->NotifyWindowConfigured(wayland_window_);
 }
 
 bool XDGSurfaceWrapperImpl::IsConfigured() {
diff --git a/ui/ozone/platform/wayland/host/xdg_toplevel_wrapper_impl.cc b/ui/ozone/platform/wayland/host/xdg_toplevel_wrapper_impl.cc
index ad66167..aed3f976 100644
--- a/ui/ozone/platform/wayland/host/xdg_toplevel_wrapper_impl.cc
+++ b/ui/ozone/platform/wayland/host/xdg_toplevel_wrapper_impl.cc
@@ -115,8 +115,7 @@
     LOG(ERROR) << "Failed to create xdg_toplevel";
     return false;
   }
-  connection_->wayland_window_manager()->NotifyWindowRoleAssigned(
-      wayland_window_);
+  connection_->window_manager()->NotifyWindowRoleAssigned(wayland_window_);
 
   if (connection_->zaura_shell()) {
     uint32_t version =
@@ -523,6 +522,14 @@
   }
 }
 
+void XDGToplevelWrapperImpl::SetScaleFactor(float scale_factor) {
+  if (aura_toplevel_ && zaura_toplevel_get_version(aura_toplevel_.get()) >=
+                            ZAURA_TOPLEVEL_SET_SCALE_FACTOR_SINCE_VERSION) {
+    uint32_t value = *reinterpret_cast<uint32_t*>(&scale_factor);
+    zaura_toplevel_set_scale_factor(aura_toplevel_.get(), value);
+  }
+}
+
 void XDGToplevelWrapperImpl::SetRestoreInfo(int32_t restore_session_id,
                                             int32_t restore_window_id) {
   if (aura_toplevel_ && zaura_toplevel_get_version(aura_toplevel_.get()) >=
diff --git a/ui/ozone/platform/wayland/host/xdg_toplevel_wrapper_impl.h b/ui/ozone/platform/wayland/host/xdg_toplevel_wrapper_impl.h
index 35c14de..df09c188 100644
--- a/ui/ozone/platform/wayland/host/xdg_toplevel_wrapper_impl.h
+++ b/ui/ozone/platform/wayland/host/xdg_toplevel_wrapper_impl.h
@@ -62,6 +62,7 @@
   bool SupportsActivation() override;
   void Activate() override;
   void Deactivate() override;
+  void SetScaleFactor(float scale_factor) override;
 
   XDGSurfaceWrapperImpl* xdg_surface_wrapper() const;
 
diff --git a/ui/ozone/platform/wayland/mojom/BUILD.gn b/ui/ozone/platform/wayland/mojom/BUILD.gn
index 00c1bc7..5fc0e661 100644
--- a/ui/ozone/platform/wayland/mojom/BUILD.gn
+++ b/ui/ozone/platform/wayland/mojom/BUILD.gn
@@ -15,6 +15,7 @@
     "//skia/public/mojom",
     "//ui/gfx/geometry/mojom",
     "//ui/gfx/mojom",
+    "//ui/gl/mojom",
   ]
 
   cpp_typemaps = [
diff --git a/ui/ozone/platform/wayland/mojom/wayland_buffer_manager.mojom b/ui/ozone/platform/wayland/mojom/wayland_buffer_manager.mojom
index 2a0ff912..996481d0 100644
--- a/ui/ozone/platform/wayland/mojom/wayland_buffer_manager.mojom
+++ b/ui/ozone/platform/wayland/mojom/wayland_buffer_manager.mojom
@@ -11,6 +11,7 @@
 import "ui/gfx/mojom/gpu_fence_handle.mojom";
 import "ui/gfx/mojom/presentation_feedback.mojom";
 import "ui/gfx/mojom/swap_result.mojom";
+import "ui/gl/mojom/frame_data.mojom";
 import "ui/ozone/platform/wayland/mojom/wayland_overlay_config.mojom";
 
 // Used by the GPU for communication with the WaylandBufferManagerHost in
@@ -79,9 +80,10 @@
   DestroyBuffer(uint32 buffer_id);
 
   // Send overlay configurations for a frame to a WaylandWindow with the
-  // following |widget| and |frame_id|.
+  // following |widget|, |frame_id|, and |overlays|.
   CommitOverlays(gfx.mojom.AcceleratedWidget widget,
                  uint32 frame_id,
+                 gl.mojom.FrameData data,
                  array<wl.mojom.WaylandOverlayConfig> overlays);
 };
 
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 debbbb6..4151878 100644
--- a/ui/ozone/platform/wayland/test/test_wayland_server_thread.h
+++ b/ui/ozone/platform/wayland/test/test_wayland_server_thread.h
@@ -62,6 +62,7 @@
       ShouldUseExplicitSynchronizationProtocol::kUse;
   EnableAuraShellProtocol enable_aura_shell =
       EnableAuraShellProtocol::kDisabled;
+  bool surface_submission_in_pixel_coordinates = true;
 };
 
 class TestWaylandServerThread;
diff --git a/ui/ozone/platform/wayland/test/wayland_drag_drop_test.h b/ui/ozone/platform/wayland/test/wayland_drag_drop_test.h
index 8591d78b..1312f1a 100644
--- a/ui/ozone/platform/wayland/test/wayland_drag_drop_test.h
+++ b/ui/ozone/platform/wayland/test/wayland_drag_drop_test.h
@@ -77,7 +77,7 @@
   void ScheduleTestTask(base::OnceClosure test_task);
 
   WaylandWindowManager* window_manager() const {
-    return connection_->wayland_window_manager();
+    return connection_->window_manager();
   }
 
  private:
diff --git a/ui/ozone/platform/wayland/test/wayland_test.cc b/ui/ozone/platform/wayland/test/wayland_test.cc
index 938580a..4a21cb6 100644
--- a/ui/ozone/platform/wayland/test/wayland_test.cc
+++ b/ui/ozone/platform/wayland/test/wayland_test.cc
@@ -127,11 +127,11 @@
 }
 
 void WaylandTestBase::SetPointerFocusedWindow(WaylandWindow* window) {
-  connection_->wayland_window_manager()->SetPointerFocusedWindow(window);
+  connection_->window_manager()->SetPointerFocusedWindow(window);
 }
 
 void WaylandTestBase::SetKeyboardFocusedWindow(WaylandWindow* window) {
-  connection_->wayland_window_manager()->SetKeyboardFocusedWindow(window);
+  connection_->window_manager()->SetKeyboardFocusedWindow(window);
 }
 
 void WaylandTestBase::SendConfigureEvent(uint32_t surface_id,
@@ -216,6 +216,26 @@
 #endif
 }
 
+void WaylandTestBase::WaitForAllDisplaysReady() {
+  // First, make sure all outputs are created and are ready.
+  base::RunLoop loop;
+  base::RepeatingTimer timer;
+  timer.Start(
+      FROM_HERE, base::Milliseconds(1), base::BindLambdaForTesting([&]() {
+        auto& outputs = connection_->wayland_output_manager()->GetAllOutputs();
+        for (auto& output : outputs) {
+          // Displays are updated when the output is ready.
+          if (!output.second->IsReady())
+            return;
+        }
+        return loop.Quit();
+      }));
+  loop.Run();
+
+  // Secondly, make sure all events after 'done' are processed.
+  wl::SyncDisplay(connection_->display_wrapper(), *connection_->display());
+}
+
 std::unique_ptr<WaylandWindow> WaylandTestBase::CreateWaylandWindowWithParams(
     PlatformWindowType type,
     const gfx::Rect bounds,
diff --git a/ui/ozone/platform/wayland/test/wayland_test.h b/ui/ozone/platform/wayland/test/wayland_test.h
index fb88a11..ad27dd8 100644
--- a/ui/ozone/platform/wayland/test/wayland_test.h
+++ b/ui/ozone/platform/wayland/test/wayland_test.h
@@ -92,6 +92,10 @@
   // Does nothing if XkbCommon is not used.
   void MaybeSetUpXkb();
 
+  // A helper method to ensure that information for all displays are populated
+  // and ready.
+  void WaitForAllDisplaysReady();
+
   // Creates a Wayland window with the specified delegate, type, and bounds.
   std::unique_ptr<WaylandWindow> CreateWaylandWindowWithParams(
       PlatformWindowType type,
diff --git a/ui/ozone/platform/wayland/wayland_buffer_manager_unittest.cc b/ui/ozone/platform/wayland/wayland_buffer_manager_unittest.cc
index 7445f70..48b50558 100644
--- a/ui/ozone/platform/wayland/wayland_buffer_manager_unittest.cc
+++ b/ui/ozone/platform/wayland/wayland_buffer_manager_unittest.cc
@@ -109,6 +109,12 @@
   ~WaylandBufferManagerTest() override = default;
 
   void SetUp() override {
+    // Surface submission in pixel coordinates is only checked once on surface
+    // creation and persisted, so we must make sure the configuration is done
+    // before we create the surface.
+    connection_->set_surface_submission_in_pixel_coordinates(
+        GetParam().surface_submission_in_pixel_coordinates);
+
     // Set this bug fix so that WaylandFrameManager does not use a freeze
     // counter. Otherwise, we won't be able to have a reliable test order of
     // frame submissions. This must be set before any window is created
@@ -313,13 +319,14 @@
   void CommitBuffer(gfx::AcceleratedWidget widget,
                     uint32_t frame_id,
                     uint32_t buffer_id,
+                    gl::FrameData data,
                     const gfx::Rect& bounds_rect,
                     const gfx::RoundedCornersF& corners,
                     float surface_scale_factor,
                     const gfx::Rect& damage_region) {
-    buffer_manager_gpu_->CommitBuffer(widget, frame_id, buffer_id, bounds_rect,
-                                      corners, surface_scale_factor,
-                                      damage_region);
+    buffer_manager_gpu_->CommitBuffer(widget, frame_id, buffer_id,
+                                      std::move(data), bounds_rect, corners,
+                                      surface_scale_factor, damage_region);
     // Let the mojo message to be processed.
     base::RunLoop().RunUntilIdle();
   }
@@ -470,9 +477,9 @@
     CreateDmabufBasedBufferAndSetTerminateExpectation(false /*fail*/,
                                                       kBufferId1);
 
-    CommitBuffer(widget, kBufferId1, kBufferId1, window_->GetBoundsInPixels(),
-                 gfx::RoundedCornersF(), kDefaultScale,
-                 gfx::Rect(window_->size_px()));
+    CommitBuffer(widget, kBufferId1, kBufferId1, gl::FrameData(),
+                 window_->GetBoundsInPixels(), gfx::RoundedCornersF(),
+                 kDefaultScale, gfx::Rect(window_->size_px()));
 
     CreateDmabufBasedBufferAndSetTerminateExpectation(true /*fail*/,
                                                       kBufferId1);
@@ -496,9 +503,9 @@
     CreateDmabufBasedBufferAndSetTerminateExpectation(false /*fail*/,
                                                       kBufferId1);
 
-    CommitBuffer(widget, kBufferId1, kBufferId1, window_->GetBoundsInPixels(),
-                 gfx::RoundedCornersF(), kDefaultScale,
-                 gfx::Rect(window_->size_px()));
+    CommitBuffer(widget, kBufferId1, kBufferId1, gl::FrameData(),
+                 window_->GetBoundsInPixels(), gfx::RoundedCornersF(),
+                 kDefaultScale, gfx::Rect(window_->size_px()));
 
     DestroyBufferAndSetTerminateExpectation(kBufferId1, false /*fail*/);
   }
@@ -524,9 +531,9 @@
     CreateDmabufBasedBufferAndSetTerminateExpectation(false /*fail*/,
                                                       kBufferId1);
     // Attach to a surface.
-    CommitBuffer(widget, kBufferId1, kBufferId1, window_->GetBoundsInPixels(),
-                 gfx::RoundedCornersF(), kDefaultScale,
-                 gfx::Rect(window_->size_px()));
+    CommitBuffer(widget, kBufferId1, kBufferId1, gl::FrameData(),
+                 window_->GetBoundsInPixels(), gfx::RoundedCornersF(),
+                 kDefaultScale, gfx::Rect(window_->size_px()));
 
     // Created non-attached buffer as well.
     CreateDmabufBasedBufferAndSetTerminateExpectation(false /*fail*/,
@@ -567,9 +574,9 @@
     EXPECT_CALL(*mock_surface, Commit()).Times(kNumberOfCommits);
   });
 
-  CommitBuffer(window_->GetWidget(), 1u, 5u, window_->GetBoundsInPixels(),
-               gfx::RoundedCornersF(), kDefaultScale,
-               gfx::Rect(window_->size_px()));
+  CommitBuffer(window_->GetWidget(), 1u, 5u, gl::FrameData(),
+               window_->GetBoundsInPixels(), gfx::RoundedCornersF(),
+               kDefaultScale, gfx::Rect(window_->size_px()));
 
   // Let the mojo call to go through.
   base::RunLoop().RunUntilIdle();
@@ -602,7 +609,7 @@
   // Non-existing buffer id
   overlay_configs.emplace_back(
       CreateBasicWaylandOverlayConfig(0, 2u, window_->GetBoundsInPixels()));
-  buffer_manager_gpu_->CommitOverlays(window_->GetWidget(), 1u,
+  buffer_manager_gpu_->CommitOverlays(window_->GetWidget(), 1u, gl::FrameData(),
                                       std::move(overlay_configs));
 
   // Let the mojo call to go through.
@@ -634,7 +641,7 @@
   overlay_configs.emplace_back(
       CreateBasicWaylandOverlayConfig(1, 1u, window_->GetBoundsInPixels()));
 
-  buffer_manager_gpu_->CommitOverlays(window_->GetWidget(), 1u,
+  buffer_manager_gpu_->CommitOverlays(window_->GetWidget(), 1u, gl::FrameData(),
                                       std::move(overlay_configs));
 
   // Let the mojo call to go through.
@@ -653,7 +660,7 @@
 
   // Can't commit for non-existing widget.
   SetTerminateCallbackExpectationAndDestroyChannel(&callback_, true /*fail*/);
-  CommitBuffer(gfx::kNullAcceleratedWidget, 1u, kBufferId,
+  CommitBuffer(gfx::kNullAcceleratedWidget, 1u, kBufferId, gl::FrameData(),
                window_->GetBoundsInPixels(), gfx::RoundedCornersF(),
                kDefaultScale, gfx::Rect(window_->size_px()));
 
@@ -718,6 +725,7 @@
             z_order++, kBufferId2, window_->GetBoundsInPixels()));
       }
       buffer_manager_gpu_->CommitOverlays(window_->GetWidget(), 1u,
+                                          gl::FrameData(),
                                           std::move(overlay_configs));
 
       base::RunLoop().RunUntilIdle();
@@ -797,8 +805,8 @@
   ASSERT_TRUE(!connection_->presentation());
   EXPECT_CALL(mock_surface_gpu, OnPresentation(kBufferId1, _)).Times(1);
 
-  CommitBuffer(widget, kBufferId1, kBufferId1, bounds, gfx::RoundedCornersF(),
-               kDefaultScale, bounds);
+  CommitBuffer(widget, kBufferId1, kBufferId1, gl::FrameData(), bounds,
+               gfx::RoundedCornersF(), kDefaultScale, bounds);
 
   base::RunLoop().RunUntilIdle();
 
@@ -807,8 +815,8 @@
   SendFrameCallbackForSurface(surface_id_);
 
   // Commit second buffer now.
-  CommitBuffer(widget, kBufferId2, kBufferId2, bounds, gfx::RoundedCornersF(),
-               kDefaultScale, bounds);
+  CommitBuffer(widget, kBufferId2, kBufferId2, gl::FrameData(), bounds,
+               gfx::RoundedCornersF(), kDefaultScale, bounds);
 
   base::RunLoop().RunUntilIdle();
 
@@ -839,8 +847,8 @@
   ASSERT_TRUE(connection_->presentation());
 
   // Commit second buffer now.
-  CommitBuffer(widget, kBufferId1, kBufferId1, bounds, gfx::RoundedCornersF(),
-               kDefaultScale, bounds);
+  CommitBuffer(widget, kBufferId1, kBufferId1, gl::FrameData(), bounds,
+               gfx::RoundedCornersF(), kDefaultScale, bounds);
 
   base::RunLoop().RunUntilIdle();
 
@@ -917,8 +925,8 @@
   EXPECT_CALL(mock_surface_gpu,
               OnSubmission(kBufferId1, gfx::SwapResult::SWAP_ACK, _))
       .Times(1);
-  CommitBuffer(widget, kBufferId1, kBufferId1, bounds, gfx::RoundedCornersF(),
-               kDefaultScale, bounds);
+  CommitBuffer(widget, kBufferId1, kBufferId1, gl::FrameData(), bounds,
+               gfx::RoundedCornersF(), kDefaultScale, bounds);
 
   SendFrameCallbackForSurface(surface_id_);
 
@@ -934,8 +942,8 @@
   testing::Mock::VerifyAndClearExpectations(&mock_surface_gpu);
 
   // Commit second buffer now.
-  CommitBuffer(widget, kBufferId2, kBufferId2, bounds, gfx::RoundedCornersF(),
-               kDefaultScale, bounds);
+  CommitBuffer(widget, kBufferId2, kBufferId2, gl::FrameData(), bounds,
+               gfx::RoundedCornersF(), kDefaultScale, bounds);
 
   SendFrameCallbackForSurface(surface_id_);
 
@@ -985,8 +993,8 @@
               ::testing::Eq(gfx::PresentationFeedback::Flags::kFailure))))
       .Times(1);
   EXPECT_CALL(mock_surface_gpu, OnPresentation(_, _)).Times(0);
-  CommitBuffer(widget, kBufferId3, kBufferId3, bounds, gfx::RoundedCornersF(),
-               kDefaultScale, bounds);
+  CommitBuffer(widget, kBufferId3, kBufferId3, gl::FrameData(), bounds,
+               gfx::RoundedCornersF(), kDefaultScale, bounds);
   PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
     server->GetObject<wl::MockSurface>(id)->SendFrameCallback();
     server->EnsureAndGetWpPresentation()->SendPresentationCallback();
@@ -1067,8 +1075,8 @@
   EXPECT_CALL(mock_surface_gpu, OnPresentation(_, _)).Times(0);
 
   // Commit first buffer
-  CommitBuffer(widget, kBufferId1, kBufferId1, bounds, gfx::RoundedCornersF(),
-               kDefaultScale, bounds);
+  CommitBuffer(widget, kBufferId1, kBufferId1, gl::FrameData(), bounds,
+               gfx::RoundedCornersF(), kDefaultScale, bounds);
 
   // Let the mojo message for OnSubmission go back.
   base::RunLoop().RunUntilIdle();
@@ -1090,8 +1098,8 @@
   EXPECT_CALL(mock_surface_gpu, OnPresentation(_, _)).Times(0);
 
   // Commit second buffer
-  CommitBuffer(widget, kBufferId2, kBufferId2, bounds, gfx::RoundedCornersF(),
-               kDefaultScale, bounds);
+  CommitBuffer(widget, kBufferId2, kBufferId2, gl::FrameData(), bounds,
+               gfx::RoundedCornersF(), kDefaultScale, bounds);
 
   PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
     // Verify we have a presentation callback now. This will be sent later.
@@ -1116,8 +1124,8 @@
   EXPECT_CALL(mock_surface_gpu, OnPresentation(_, _)).Times(0);
 
   // Commit third buffer
-  CommitBuffer(widget, kBufferId3, kBufferId3, bounds, gfx::RoundedCornersF(),
-               kDefaultScale, bounds);
+  CommitBuffer(widget, kBufferId3, kBufferId3, gl::FrameData(), bounds,
+               gfx::RoundedCornersF(), kDefaultScale, bounds);
 
   PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
     auto* mock_surface = server->GetObject<wl::MockSurface>(id);
@@ -1227,7 +1235,7 @@
     EXPECT_CALL(*mock_surface, Commit()).Times(0);
   });
 
-  CommitBuffer(widget, kDmabufBufferId, kDmabufBufferId,
+  CommitBuffer(widget, kDmabufBufferId, kDmabufBufferId, gl::FrameData(),
                window_->GetBoundsInPixels(), gfx::RoundedCornersF(),
                kDefaultScale, gfx::Rect(window_->size_px()));
 
@@ -1268,7 +1276,7 @@
     EXPECT_CALL(*mock_surface, Commit()).Times(0);
   });
 
-  CommitBuffer(widget, kDmabufBufferId2, kDmabufBufferId2,
+  CommitBuffer(widget, kDmabufBufferId2, kDmabufBufferId2, gl::FrameData(),
                window_->GetBoundsInPixels(), gfx::RoundedCornersF(),
                kDefaultScale, gfx::Rect(window_->size_px()));
 
@@ -1344,7 +1352,7 @@
           EXPECT_CALL(*mock_surface, Commit()).Times(0);
         });
 
-    CommitBuffer(widget, kDmabufBufferId, kDmabufBufferId,
+    CommitBuffer(widget, kDmabufBufferId, kDmabufBufferId, gl::FrameData(),
                  window_->GetBoundsInPixels(), gfx::RoundedCornersF(),
                  kDefaultScale, window_->GetBoundsInPixels());
     PostToServerAndWait(
@@ -1461,12 +1469,14 @@
     EXPECT_CALL(*mock_surface, Commit()).Times(1);
   });
 
-  CommitBuffer(widget, kDmabufBufferId, kDmabufBufferId, gfx::Rect{55, 55},
-               gfx::RoundedCornersF(), kDefaultScale, gfx::Rect{55, 55});
+  CommitBuffer(widget, kDmabufBufferId, kDmabufBufferId, gl::FrameData(),
+               gfx::Rect{55, 55}, gfx::RoundedCornersF(), kDefaultScale,
+               gfx::Rect{55, 55});
   ActivateSurface(surface_id, kActivateSerial);
 
-  CommitBuffer(widget, kDmabufBufferId, kDmabufBufferId, kRestoredBounds,
-               gfx::RoundedCornersF(), kDefaultScale, kRestoredBounds);
+  CommitBuffer(widget, kDmabufBufferId, kDmabufBufferId, gl::FrameData(),
+               kRestoredBounds, gfx::RoundedCornersF(), kDefaultScale,
+               kRestoredBounds);
   SetPointerFocusedWindow(nullptr);
   window.reset();
   DestroyBufferAndSetTerminateExpectation(kDmabufBufferId, false /*fail*/);
@@ -1512,8 +1522,8 @@
   EXPECT_CALL(mock_surface_gpu, OnPresentation(kBufferId1, _)).Times(1);
 
   // Commit second buffer now.
-  CommitBuffer(widget, kBufferId1, kBufferId1, bounds, gfx::RoundedCornersF(),
-               kDefaultScale, bounds);
+  CommitBuffer(widget, kBufferId1, kBufferId1, gl::FrameData(), bounds,
+               gfx::RoundedCornersF(), kDefaultScale, bounds);
 
   SendFrameCallbackForSurface(surface_id_);
 
@@ -1539,8 +1549,8 @@
   EXPECT_CALL(mock_surface_gpu, OnPresentation(kBufferId2, _)).Times(1);
 
   // Commit second buffer now.
-  CommitBuffer(widget, kBufferId2, kBufferId2, bounds, gfx::RoundedCornersF(),
-               kDefaultScale, bounds);
+  CommitBuffer(widget, kBufferId2, kBufferId2, gl::FrameData(), bounds,
+               gfx::RoundedCornersF(), kDefaultScale, bounds);
 
   PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
     auto* mock_surface = server->GetObject<wl::MockSurface>(id);
@@ -1568,8 +1578,8 @@
       .Times(0);
   EXPECT_CALL(mock_surface_gpu, OnPresentation(kBufferId3, _)).Times(0);
 
-  CommitBuffer(widget, kBufferId3, kBufferId3, bounds, gfx::RoundedCornersF(),
-               kDefaultScale, bounds);
+  CommitBuffer(widget, kBufferId3, kBufferId3, gl::FrameData(), bounds,
+               gfx::RoundedCornersF(), kDefaultScale, bounds);
 
   // Let mojo messages to be processed from host to gpu (if any).
   base::RunLoop().RunUntilIdle();
@@ -1611,9 +1621,9 @@
   });
   CreateDmabufBasedBufferAndSetTerminateExpectation(false /*fail*/, kBufferId);
 
-  CommitBuffer(widget, kBufferId, kBufferId, temp_window->GetBoundsInPixels(),
-               gfx::RoundedCornersF(), kDefaultScale,
-               temp_window->GetBoundsInPixels());
+  CommitBuffer(widget, kBufferId, kBufferId, gl::FrameData(),
+               temp_window->GetBoundsInPixels(), gfx::RoundedCornersF(),
+               kDefaultScale, temp_window->GetBoundsInPixels());
 
   temp_window.reset();
   DestroyBufferAndSetTerminateExpectation(kBufferId, false /*fail*/);
@@ -1643,8 +1653,8 @@
 
   temp_window.reset();
 
-  CommitBuffer(widget, kBufferId, kBufferId, bounds, gfx::RoundedCornersF(),
-               kDefaultScale, bounds);
+  CommitBuffer(widget, kBufferId, kBufferId, gl::FrameData(), bounds,
+               gfx::RoundedCornersF(), kDefaultScale, bounds);
 
   DestroyBufferAndSetTerminateExpectation(kBufferId, false /*fail*/);
 }
@@ -1682,8 +1692,8 @@
   EXPECT_CALL(mock_surface_gpu, OnSubmission(_, _, _)).Times(1);
   EXPECT_CALL(mock_surface_gpu, OnPresentation(_, _)).Times(1);
 
-  CommitBuffer(widget, kBufferId1, kBufferId1, bounds, gfx::RoundedCornersF(),
-               kDefaultScale, bounds);
+  CommitBuffer(widget, kBufferId1, kBufferId1, gl::FrameData(), bounds,
+               gfx::RoundedCornersF(), kDefaultScale, bounds);
 
   // Let mojo messages to be processed back to the gpu.
   base::RunLoop().RunUntilIdle();
@@ -1706,8 +1716,8 @@
       .Times(1);
   EXPECT_CALL(mock_surface_gpu, OnPresentation(kBufferId2, _)).Times(1);
 
-  CommitBuffer(widget, kBufferId2, kBufferId2, bounds, gfx::RoundedCornersF(),
-               kDefaultScale, bounds);
+  CommitBuffer(widget, kBufferId2, kBufferId2, gl::FrameData(), bounds,
+               gfx::RoundedCornersF(), kDefaultScale, bounds);
 
   PostToServerAndWait(
       [temp_window_surface_id](wl::TestWaylandServerThread* server) {
@@ -1725,8 +1735,8 @@
   EXPECT_CALL(mock_surface_gpu, OnPresentation(_, _)).Times(0);
   temp_window.reset();
 
-  CommitBuffer(widget, kBufferId1, kBufferId1, bounds, gfx::RoundedCornersF(),
-               kDefaultScale, bounds);
+  CommitBuffer(widget, kBufferId1, kBufferId1, gl::FrameData(), bounds,
+               gfx::RoundedCornersF(), kDefaultScale, bounds);
 
   // Let mojo messages to be processed back to the gpu.
   base::RunLoop().RunUntilIdle();
@@ -1761,8 +1771,8 @@
       .Times(1);
   EXPECT_CALL(mock_surface_gpu, OnPresentation(kBufferId1, _)).Times(1);
 
-  CommitBuffer(widget, kBufferId1, kBufferId1, bounds, gfx::RoundedCornersF(),
-               kDefaultScale, bounds);
+  CommitBuffer(widget, kBufferId1, kBufferId1, gl::FrameData(), bounds,
+               gfx::RoundedCornersF(), kDefaultScale, bounds);
 
   SendFrameCallbackForSurface(surface_id_);
 
@@ -1775,12 +1785,12 @@
   EXPECT_CALL(mock_surface_gpu, OnSubmission(_, _, _)).Times(0);
   EXPECT_CALL(mock_surface_gpu, OnPresentation(_, _)).Times(0);
 
-  CommitBuffer(widget, kBufferId2, kBufferId2, bounds, gfx::RoundedCornersF(),
-               kDefaultScale, bounds);
+  CommitBuffer(widget, kBufferId2, kBufferId2, gl::FrameData(), bounds,
+               gfx::RoundedCornersF(), kDefaultScale, bounds);
   SendFrameCallbackForSurface(surface_id_);
 
-  CommitBuffer(widget, kBufferId2, kBufferId2, bounds, gfx::RoundedCornersF(),
-               kDefaultScale, bounds);
+  CommitBuffer(widget, kBufferId2, kBufferId2, gl::FrameData(), bounds,
+               gfx::RoundedCornersF(), kDefaultScale, bounds);
   SendFrameCallbackForSurface(surface_id_);
 
   // Destroying buffer2 should do nothing yet.
@@ -1828,8 +1838,8 @@
       .Times(1);
   EXPECT_CALL(mock_surface_gpu, OnPresentation(kBufferId1, _)).Times(1);
 
-  CommitBuffer(widget, kBufferId1, kBufferId1, bounds, gfx::RoundedCornersF(),
-               kDefaultScale, bounds);
+  CommitBuffer(widget, kBufferId1, kBufferId1, gl::FrameData(), bounds,
+               gfx::RoundedCornersF(), kDefaultScale, bounds);
   SendFrameCallbackForSurface(surface_id_);
 
   // Let mojo messages to be processed back to the gpu.
@@ -1845,13 +1855,12 @@
   EXPECT_CALL(mock_surface_gpu, OnSubmission(_, _, _)).Times(0);
   EXPECT_CALL(mock_surface_gpu, OnPresentation(_, _)).Times(0);
 
-  CommitBuffer(widget, kBufferId2, kBufferId2, bounds, gfx::RoundedCornersF(),
-               kDefaultScale, bounds);
+  CommitBuffer(widget, kBufferId2, kBufferId2, gl::FrameData(), bounds,
+               gfx::RoundedCornersF(), kDefaultScale, bounds);
   SendFrameCallbackForSurface(surface_id_);
 
-  CommitBuffer(widget, kBufferId2, kBufferId2, bounds, gfx::RoundedCornersF(),
-               kDefaultScale, bounds);
-  SendFrameCallbackForSurface(surface_id_);
+  CommitBuffer(widget, kBufferId2, kBufferId2, gl::FrameData(), bounds,
+               gfx::RoundedCornersF(), kDefaultScale, bounds);
 
   // Let mojo messages to be processed back to the gpu.
   base::RunLoop().RunUntilIdle();
@@ -1907,8 +1916,8 @@
         .Times(1);
   });
 
-  CommitBuffer(widget, kBufferId1, kBufferId1, bounds, gfx::RoundedCornersF(),
-               kDefaultScale, bounds);
+  CommitBuffer(widget, kBufferId1, kBufferId1, gl::FrameData(), bounds,
+               gfx::RoundedCornersF(), kDefaultScale, bounds);
 
   SendFrameCallbackForSurface(surface_id_);
 
@@ -1929,8 +1938,8 @@
         .Times(2);
   });
 
-  CommitBuffer(widget, kBufferId2, kBufferId2, bounds, gfx::RoundedCornersF(),
-               kDefaultScale, bounds);
+  CommitBuffer(widget, kBufferId2, kBufferId2, gl::FrameData(), bounds,
+               gfx::RoundedCornersF(), kDefaultScale, bounds);
 
   SendFrameCallbackForSurface(surface_id_);
 
@@ -1939,8 +1948,8 @@
   wl_resource* wl_buffer2 = GetSurfaceAttachedBuffer(surface_id_);
   ASSERT_TRUE(wl_buffer2);
 
-  CommitBuffer(widget, kBufferId3, kBufferId3, bounds, gfx::RoundedCornersF(),
-               kDefaultScale, bounds);
+  CommitBuffer(widget, kBufferId3, kBufferId3, gl::FrameData(), bounds,
+               gfx::RoundedCornersF(), kDefaultScale, bounds);
   SendFrameCallbackForSurface(surface_id_);
 
   // Let mojo messages to be processed and passed from host to gpu.
@@ -2022,8 +2031,8 @@
         EXPECT_CALL(*mock_surface, Commit()).Times(1);
       });
 
-  CommitBuffer(widget, kBufferId1, kBufferId1, bounds, gfx::RoundedCornersF(),
-               kDefaultScale, bounds);
+  CommitBuffer(widget, kBufferId1, kBufferId1, gl::FrameData(), bounds,
+               gfx::RoundedCornersF(), kDefaultScale, bounds);
 
   // Let mojo messages to be passed from host to gpu.
   base::RunLoop().RunUntilIdle();
@@ -2047,8 +2056,8 @@
       });
 
   // Commit second buffer now.
-  CommitBuffer(widget, kBufferId2, kBufferId2, bounds, gfx::RoundedCornersF(),
-               kDefaultScale, bounds);
+  CommitBuffer(widget, kBufferId2, kBufferId2, gl::FrameData(), bounds,
+               gfx::RoundedCornersF(), kDefaultScale, bounds);
 
   // Let mojo messages to be passed from host to gpu.
   base::RunLoop().RunUntilIdle();
@@ -2098,8 +2107,8 @@
       });
 
   // Commit second buffer now.
-  CommitBuffer(widget, kBufferId2, kBufferId2, bounds, gfx::RoundedCornersF(),
-               kDefaultScale, bounds);
+  CommitBuffer(widget, kBufferId2, kBufferId2, gl::FrameData(), bounds,
+               gfx::RoundedCornersF(), kDefaultScale, bounds);
 
   // Let mojo messages to be passed from host to gpu.
   base::RunLoop().RunUntilIdle();
@@ -2142,8 +2151,8 @@
         EXPECT_CALL(*mock_surface, Commit()).Times(1);
       });
 
-  CommitBuffer(widget, kBufferId1, kBufferId1, bounds, gfx::RoundedCornersF(),
-               kDefaultScale, bounds);
+  CommitBuffer(widget, kBufferId1, kBufferId1, gl::FrameData(), bounds,
+               gfx::RoundedCornersF(), kDefaultScale, bounds);
 
   // Let mojo messages to be passed from host to gpu.
   base::RunLoop().RunUntilIdle();
@@ -2195,8 +2204,8 @@
       .Times(1);
   EXPECT_CALL(mock_surface_gpu, OnPresentation(kBufferId1, _)).Times(1);
 
-  CommitBuffer(widget, kBufferId1, kBufferId1, bounds, gfx::RoundedCornersF(),
-               kDefaultScale, bounds);
+  CommitBuffer(widget, kBufferId1, kBufferId1, gl::FrameData(), bounds,
+               gfx::RoundedCornersF(), kDefaultScale, bounds);
 
   // Let mojo messages to be passed from host to gpu.
   base::RunLoop().RunUntilIdle();
@@ -2249,7 +2258,7 @@
       CreateBasicWaylandOverlayConfig(0, kBufferId2, bounds));
   overlay_configs.emplace_back(
       CreateBasicWaylandOverlayConfig(1, kBufferId3, bounds));
-  buffer_manager_gpu_->CommitOverlays(window_->GetWidget(), 1u,
+  buffer_manager_gpu_->CommitOverlays(window_->GetWidget(), 1u, gl::FrameData(),
                                       std::move(overlay_configs));
   // Let mojo messages from gpu to host to go through.
   base::RunLoop().RunUntilIdle();
@@ -2310,8 +2319,8 @@
       OnSubmission(kBufferId1, gfx::SwapResult::SWAP_ACK,
                    Truly([](const auto& fence) { return fence.is_null(); })))
       .Times(1);
-  CommitBuffer(widget, kBufferId1, kBufferId1, bounds, gfx::RoundedCornersF(),
-               kDefaultScale, bounds);
+  CommitBuffer(widget, kBufferId1, kBufferId1, gl::FrameData(), bounds,
+               gfx::RoundedCornersF(), kDefaultScale, bounds);
 
   // Let mojo messages from gpu to host to go through.
   base::RunLoop().RunUntilIdle();
@@ -2320,8 +2329,8 @@
   SendFrameCallbackForSurface(surface_id_);
 
   // Commit the second buffer now.
-  CommitBuffer(widget, kBufferId2, kBufferId2, bounds, gfx::RoundedCornersF(),
-               kDefaultScale, bounds);
+  CommitBuffer(widget, kBufferId2, kBufferId2, gl::FrameData(), bounds,
+               gfx::RoundedCornersF(), kDefaultScale, bounds);
 
   SendFrameCallbackForSurface(surface_id_);
 
@@ -2349,8 +2358,8 @@
   testing::Mock::VerifyAndClearExpectations(&mock_surface_gpu);
 
   // Commit the third buffer now.
-  CommitBuffer(widget, kBufferId3, kBufferId3, bounds, gfx::RoundedCornersF(),
-               kDefaultScale, bounds);
+  CommitBuffer(widget, kBufferId3, kBufferId3, gl::FrameData(), bounds,
+               gfx::RoundedCornersF(), kDefaultScale, bounds);
 
   SendFrameCallbackForSurface(surface_id_);
 
@@ -2418,8 +2427,8 @@
       .Times(1);
   EXPECT_CALL(*mock_surface_gpu.get(), OnPresentation(kBufferId1, _)).Times(1);
 
-  CommitBuffer(widget, kBufferId1, kBufferId1, bounds, gfx::RoundedCornersF(),
-               kDefaultScale, bounds);
+  CommitBuffer(widget, kBufferId1, kBufferId1, gl::FrameData(), bounds,
+               gfx::RoundedCornersF(), kDefaultScale, bounds);
 
   // Let mojo messages from host to gpu to go through.
   base::RunLoop().RunUntilIdle();
@@ -2477,8 +2486,8 @@
       .Times(1);
   EXPECT_CALL(*mock_surface_gpu.get(), OnPresentation(kBufferId1, _)).Times(1);
 
-  CommitBuffer(widget, kBufferId1, kBufferId1, bounds, gfx::RoundedCornersF(),
-               kDefaultScale, bounds);
+  CommitBuffer(widget, kBufferId1, kBufferId1, gl::FrameData(), bounds,
+               gfx::RoundedCornersF(), kDefaultScale, bounds);
 
   // Let mojo messages from host to gpu to go through.
   base::RunLoop().RunUntilIdle();
@@ -2519,7 +2528,7 @@
       CreateBasicWaylandOverlayConfig(0, kBufferId2, bounds));
   overlay_configs.emplace_back(
       CreateBasicWaylandOverlayConfig(1, kBufferId3, bounds));
-  buffer_manager_gpu_->CommitOverlays(window_->GetWidget(), 1u,
+  buffer_manager_gpu_->CommitOverlays(window_->GetWidget(), 1u, gl::FrameData(),
                                       std::move(overlay_configs));
 
   // Let mojo messages from gpu to host to go through.
@@ -2578,7 +2587,7 @@
   std::vector<wl::WaylandOverlayConfig> overlay_configs2;
   overlay_configs2.push_back(
       CreateBasicWaylandOverlayConfig(INT32_MIN, kBufferId1, bounds));
-  buffer_manager_gpu_->CommitOverlays(window_->GetWidget(), 2u,
+  buffer_manager_gpu_->CommitOverlays(window_->GetWidget(), 2u, gl::FrameData(),
                                       std::move(overlay_configs2));
 
   base::RunLoop().RunUntilIdle();
@@ -2658,6 +2667,7 @@
     }
 
     buffer_manager_gpu_->CommitOverlays(window_->GetWidget(), ++frame_id,
+                                        gl::FrameData(),
                                         std::move(overlay_configs));
 
     base::RunLoop().RunUntilIdle();
@@ -2726,47 +2736,45 @@
   std::vector<bool> in_pixels = {true, false};
 
   uint32_t frame_id = 0u;
-  for (auto is_in_px : in_pixels) {
-    connection_->set_surface_submission_in_pixel_coordinates(is_in_px);
-    for (auto scale_factor : scale_factors) {
-      for (const auto& rounded_corners : rounded_corners_vec) {
-        std::vector<wl::WaylandOverlayConfig> overlay_configs;
-        for (auto id : kBufferIds) {
-          overlay_configs.emplace_back(CreateBasicWaylandOverlayConfig(
-              id == 1 ? INT32_MIN : id, id, window_->GetBoundsInPixels()));
-          overlay_configs.back().surface_scale_factor = scale_factor;
-          overlay_configs.back().rounded_clip_bounds = rounded_corners;
-        }
-
-        buffer_manager_gpu_->CommitOverlays(window_->GetWidget(), ++frame_id,
-                                            std::move(overlay_configs));
-
-        base::RunLoop().RunUntilIdle();
-
-        for (auto& subsurface : window_->wayland_subsurfaces_) {
-          gfx::RRectF rounded_clip_bounds_dip = rounded_corners;
-          // If submission in px is allowed, there is no need to convert px to
-          // dip.
-          if (!is_in_px) {
-            // Ozone/Wayland applies ceiled scale factor if it's fractional.
-            rounded_clip_bounds_dip.Scale(1.f / std::ceil(scale_factor));
-          }
-          PostToServerAndWait(
-              [subsurface_id = subsurface->wayland_surface()->get_surface_id(),
-               &rounded_clip_bounds_dip](wl::TestWaylandServerThread* server) {
-                auto* mock_surface_of_subsurface =
-                    server->GetObject<wl::MockSurface>(subsurface_id);
-                EXPECT_TRUE(mock_surface_of_subsurface);
-
-                EXPECT_EQ(mock_surface_of_subsurface->augmented_surface()
-                              ->rounded_clip_bounds(),
-                          rounded_clip_bounds_dip);
-                mock_surface_of_subsurface->SendFrameCallback();
-              });
-        }
-
-        SendFrameCallbackForSurface(surface_id_);
+  for (auto scale_factor : scale_factors) {
+    for (const auto& rounded_corners : rounded_corners_vec) {
+      std::vector<wl::WaylandOverlayConfig> overlay_configs;
+      for (auto id : kBufferIds) {
+        overlay_configs.emplace_back(CreateBasicWaylandOverlayConfig(
+            id == 1 ? INT32_MIN : id, id, window_->GetBoundsInPixels()));
+        overlay_configs.back().surface_scale_factor = scale_factor;
+        overlay_configs.back().rounded_clip_bounds = rounded_corners;
       }
+
+      buffer_manager_gpu_->CommitOverlays(window_->GetWidget(), ++frame_id,
+                                          gl::FrameData(),
+                                          std::move(overlay_configs));
+
+      base::RunLoop().RunUntilIdle();
+
+      for (auto& subsurface : window_->wayland_subsurfaces_) {
+        gfx::RRectF rounded_clip_bounds_dip = rounded_corners;
+        // If submission in px is allowed, there is no need to convert px to
+        // dip.
+        if (!GetParam().surface_submission_in_pixel_coordinates) {
+          // Ozone/Wayland applies ceiled scale factor if it's fractional.
+          rounded_clip_bounds_dip.Scale(1.f / std::ceil(scale_factor));
+        }
+        PostToServerAndWait(
+            [subsurface_id = subsurface->wayland_surface()->get_surface_id(),
+             &rounded_clip_bounds_dip](wl::TestWaylandServerThread* server) {
+              auto* mock_surface_of_subsurface =
+                  server->GetObject<wl::MockSurface>(subsurface_id);
+              EXPECT_TRUE(mock_surface_of_subsurface);
+
+              EXPECT_EQ(mock_surface_of_subsurface->augmented_surface()
+                            ->rounded_clip_bounds(),
+                        rounded_clip_bounds_dip);
+              mock_surface_of_subsurface->SendFrameCallback();
+            });
+      }
+
+      SendFrameCallbackForSurface(surface_id_);
     }
   }
 }
@@ -2844,8 +2852,9 @@
       EXPECT_CALL(mock_surface_gpu, OnPresentation(_, _)).Times(0);
     }
 
-    CommitBuffer(widget, next_buffer_id_commit, next_buffer_id_commit, bounds,
-                 gfx::RoundedCornersF(), kDefaultScale, bounds);
+    CommitBuffer(widget, next_buffer_id_commit, next_buffer_id_commit,
+                 gl::FrameData(), bounds, gfx::RoundedCornersF(), kDefaultScale,
+                 bounds);
 
     PostToServerAndWait(
         [id = surface_id_](wl::TestWaylandServerThread* server) {
@@ -2881,8 +2890,9 @@
   CreateDmabufBasedBufferAndSetTerminateExpectation(false /*fail*/,
                                                     kDmabufBufferId);
   CommitBuffer(window_->GetWidget(), kDmabufBufferId, kDmabufBufferId,
-               window_->GetBoundsInPixels(), gfx::RoundedCornersF(),
-               kDefaultScale, window_->GetBoundsInPixels());
+               gl::FrameData(), window_->GetBoundsInPixels(),
+               gfx::RoundedCornersF(), kDefaultScale,
+               window_->GetBoundsInPixels());
   DestroyBufferAndSetTerminateExpectation(kDmabufBufferId, false /*fail*/);
 
   base::RunLoop().RunUntilIdle();
@@ -2927,7 +2937,8 @@
   auto bounds = window_->GetBoundsInPixels();
   overlay_configs.emplace_back(CreateBasicWaylandOverlayConfig(
       INT32_MIN, solid_color_buffer_id, bounds));
-  buffer_manager_gpu_->CommitOverlays(widget_, 1u, std::move(overlay_configs));
+  buffer_manager_gpu_->CommitOverlays(widget_, 1u, gl::FrameData(),
+                                      std::move(overlay_configs));
 
   base::RunLoop().RunUntilIdle();
 
@@ -2977,6 +2988,7 @@
     overlay_configs.emplace_back(
         CreateBasicWaylandOverlayConfig(1, kBufferId1, bounds_rect));
     buffer_manager_gpu_->CommitOverlays(temp_window->GetWidget(), 1u,
+                                        gl::FrameData(),
                                         std::move(overlay_configs));
 
     base::RunLoop().RunUntilIdle();
@@ -3081,4 +3093,9 @@
         .use_explicit_synchronization =
             wl::ShouldUseExplicitSynchronizationProtocol::kUse}));
 
+INSTANTIATE_TEST_SUITE_P(
+    XdgVersionStableTestWithSurfaceSubmissionInPixelCoordinatesDisabled,
+    WaylandBufferManagerTest,
+    Values(wl::ServerConfig{.surface_submission_in_pixel_coordinates = false}));
+
 }  // namespace ui
diff --git a/ui/ozone/platform/x11/x11_window.cc b/ui/ozone/platform/x11/x11_window.cc
index 01203bc..cbf300d 100644
--- a/ui/ozone/platform/x11/x11_window.cc
+++ b/ui/ozone/platform/x11/x11_window.cc
@@ -13,6 +13,7 @@
 #include "base/trace_event/trace_event.h"
 #include "build/chromeos_buildflags.h"
 #include "net/base/network_interfaces.h"
+#include "third_party/skia/include/core/SkPath.h"
 #include "third_party/skia/include/core/SkRegion.h"
 #include "ui/base/buildflags.h"
 #include "ui/base/cursor/cursor.h"
diff --git a/ui/qt/DEPS b/ui/qt/DEPS
index 1d8a7b31..5d7b954 100644
--- a/ui/qt/DEPS
+++ b/ui/qt/DEPS
@@ -1,4 +1,5 @@
 include_rules = [
+  "+cc/paint/paint_canvas.h",
   "+chrome/browser/themes/theme_properties.h",
   "+printing",
   "+third_party/skia",
diff --git a/ui/qt/qt_ui.cc b/ui/qt/qt_ui.cc
index 9c14540..5f5a0a9 100644
--- a/ui/qt/qt_ui.cc
+++ b/ui/qt/qt_ui.cc
@@ -17,6 +17,7 @@
 #include "base/notreached.h"
 #include "base/path_service.h"
 #include "base/time/time.h"
+#include "cc/paint/paint_canvas.h"
 #include "chrome/browser/themes/theme_properties.h"  // nogncheck
 #include "third_party/skia/include/core/SkBitmap.h"
 #include "ui/base/ime/linux/linux_input_method_context.h"
diff --git a/ui/views/BUILD.gn b/ui/views/BUILD.gn
index 99aefe2..e4200f6 100644
--- a/ui/views/BUILD.gn
+++ b/ui/views/BUILD.gn
@@ -27,7 +27,6 @@
     "checkbox_normal.icon",
     "close.icon",
     "drag_general_selection.icon",
-    "drag_image_selection.icon",
     "ic_close.icon",
     "info.icon",
     "launch.icon",
@@ -232,6 +231,7 @@
     "mouse_watcher.h",
     "mouse_watcher_view_host.h",
     "native_theme_delegate.h",
+    "native_window_tracker.h",
     "paint_info.h",
     "painter.h",
     "rect_based_targeting_utils.h",
@@ -622,6 +622,8 @@
       "cocoa/native_widget_mac_event_monitor.h",
       "cocoa/native_widget_mac_event_monitor.mm",
       "cocoa/native_widget_mac_ns_window_host.mm",
+      "cocoa/native_window_tracker_cocoa.h",
+      "cocoa/native_window_tracker_cocoa.mm",
       "cocoa/text_input_host.h",
       "cocoa/text_input_host.mm",
       "cocoa/tooltip_manager_mac.h",
@@ -721,6 +723,7 @@
       "corewm/tooltip_state_manager.h",
       "corewm/tooltip_win.h",
       "event_monitor_aura.h",
+      "native_window_tracker_aura.h",
       "touchui/touch_selection_controller_impl.h",
       "touchui/touch_selection_menu_runner_views.h",
       "touchui/touch_selection_menu_views.h",
@@ -751,6 +754,7 @@
       "drag_utils_aura.cc",
       "event_monitor_aura.cc",
       "metrics_aura.cc",
+      "native_window_tracker_aura.cc",
       "touchui/touch_selection_controller_impl.cc",
       "touchui/touch_selection_menu_runner_views.cc",
       "touchui/touch_selection_menu_views.cc",
diff --git a/chrome/browser/ui/cocoa/native_window_tracker_cocoa.h b/ui/views/cocoa/native_window_tracker_cocoa.h
similarity index 63%
rename from chrome/browser/ui/cocoa/native_window_tracker_cocoa.h
rename to ui/views/cocoa/native_window_tracker_cocoa.h
index d95bd8b..a9dd037 100644
--- a/chrome/browser/ui/cocoa/native_window_tracker_cocoa.h
+++ b/ui/views/cocoa/native_window_tracker_cocoa.h
@@ -2,15 +2,18 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_UI_COCOA_NATIVE_WINDOW_TRACKER_COCOA_H_
-#define CHROME_BROWSER_UI_COCOA_NATIVE_WINDOW_TRACKER_COCOA_H_
+#ifndef UI_VIEWS_COCOA_NATIVE_WINDOW_TRACKER_COCOA_H_
+#define UI_VIEWS_COCOA_NATIVE_WINDOW_TRACKER_COCOA_H_
 
 #include "base/mac/scoped_nsobject.h"
-#include "chrome/browser/ui/native_window_tracker.h"
+#include "ui/views/native_window_tracker.h"
+#include "ui/views/views_export.h"
 
 @class BridgedNativeWindowTracker;
 
-class NativeWindowTrackerCocoa : public NativeWindowTracker {
+namespace views {
+
+class VIEWS_EXPORT NativeWindowTrackerCocoa : public NativeWindowTracker {
  public:
   explicit NativeWindowTrackerCocoa(gfx::NativeWindow window);
 
@@ -26,4 +29,6 @@
   base::scoped_nsobject<BridgedNativeWindowTracker> bridge_;
 };
 
-#endif  // CHROME_BROWSER_UI_COCOA_NATIVE_WINDOW_TRACKER_COCOA_H_
+}  // namespace views
+
+#endif  // UI_VIEWS_COCOA_NATIVE_WINDOW_TRACKER_COCOA_H_
diff --git a/chrome/browser/ui/cocoa/native_window_tracker_cocoa.mm b/ui/views/cocoa/native_window_tracker_cocoa.mm
similarity index 86%
rename from chrome/browser/ui/cocoa/native_window_tracker_cocoa.mm
rename to ui/views/cocoa/native_window_tracker_cocoa.mm
index f8c045a1..a9282d9 100644
--- a/chrome/browser/ui/cocoa/native_window_tracker_cocoa.mm
+++ b/ui/views/cocoa/native_window_tracker_cocoa.mm
@@ -2,10 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/ui/cocoa/native_window_tracker_cocoa.h"
+#include "ui/views/cocoa/native_window_tracker_cocoa.h"
 
 #import <AppKit/AppKit.h>
 
+#include <memory>
+
 @interface BridgedNativeWindowTracker : NSObject {
  @private
   NSWindow* _window;
@@ -48,14 +50,15 @@
 
 @end
 
+namespace views {
+
 NativeWindowTrackerCocoa::NativeWindowTrackerCocoa(
     gfx::NativeWindow native_window) {
   NSWindow* window = native_window.GetNativeNSWindow();
   bridge_.reset([[BridgedNativeWindowTracker alloc] initWithNSWindow:window]);
 }
 
-NativeWindowTrackerCocoa::~NativeWindowTrackerCocoa() {
-}
+NativeWindowTrackerCocoa::~NativeWindowTrackerCocoa() {}
 
 bool NativeWindowTrackerCocoa::WasNativeWindowClosed() const {
   return [bridge_ wasNSWindowClosed];
@@ -64,6 +67,7 @@
 // static
 std::unique_ptr<NativeWindowTracker> NativeWindowTracker::Create(
     gfx::NativeWindow window) {
-  return std::unique_ptr<NativeWindowTracker>(
-      new NativeWindowTrackerCocoa(window));
+  return std::make_unique<NativeWindowTrackerCocoa>(window);
 }
+
+}  // namespace views
diff --git a/ui/views/controls/animated_image_view_unittest.cc b/ui/views/controls/animated_image_view_unittest.cc
index ec3d460..b54319d 100644
--- a/ui/views/controls/animated_image_view_unittest.cc
+++ b/ui/views/controls/animated_image_view_unittest.cc
@@ -7,7 +7,7 @@
 #include "base/memory/raw_ptr.h"
 #include "base/memory/scoped_refptr.h"
 #include "cc/paint/display_item_list.h"
-#include "cc/paint/paint_op_buffer.h"
+#include "cc/paint/paint_op_buffer_iterator.h"
 #include "cc/test/skia_common.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
diff --git a/ui/views/controls/button/label_button.cc b/ui/views/controls/button/label_button.cc
index c9c780e7..7c4b47ed 100644
--- a/ui/views/controls/button/label_button.cc
+++ b/ui/views/controls/button/label_button.cc
@@ -130,6 +130,10 @@
   ResetColorsFromNativeTheme();
 }
 
+void LabelButton::SetEnabledTextColorReadabilityAdjustment(bool enabled) {
+  label_->SetAutoColorReadabilityEnabled(enabled);
+}
+
 SkColor LabelButton::GetCurrentTextColor() const {
   return label_->GetEnabledColor();
 }
diff --git a/ui/views/controls/button/label_button.h b/ui/views/controls/button/label_button.h
index 4004c67..ef134f3 100644
--- a/ui/views/controls/button/label_button.h
+++ b/ui/views/controls/button/label_button.h
@@ -71,6 +71,10 @@
   // Sets the text colors shown for the non-disabled states to |color|.
   virtual void SetEnabledTextColors(absl::optional<SkColor> color);
 
+  // Enable the text colors to auto adjust for readability for the non-disabled
+  // states. Default to false.
+  void SetEnabledTextColorReadabilityAdjustment(bool enabled);
+
   // Gets the current state text color.
   SkColor GetCurrentTextColor() const;
 
diff --git a/ui/views/controls/button/md_text_button.cc b/ui/views/controls/button/md_text_button.cc
index 2110ef3..21ab6b7 100644
--- a/ui/views/controls/button/md_text_button.cc
+++ b/ui/views/controls/button/md_text_button.cc
@@ -13,6 +13,7 @@
 #include "base/memory/ptr_util.h"
 #include "build/build_config.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
+#include "ui/base/ui_base_features.h"
 #include "ui/color/color_id.h"
 #include "ui/color/color_provider.h"
 #include "ui/gfx/canvas.h"
@@ -49,7 +50,10 @@
       },
       this));
 
-  SetCornerRadius(LayoutProvider::Get()->GetCornerRadiusMetric(Emphasis::kLow));
+  if (!features::IsChromeRefresh2023())
+    SetCornerRadius(
+        LayoutProvider::Get()->GetCornerRadiusMetric(Emphasis::kLow));
+
   SetHorizontalAlignment(gfx::ALIGN_CENTER);
 
   const int minimum_width = LayoutProvider::Get()->GetDistanceMetric(
@@ -106,6 +110,8 @@
   InkDrop::Get(this)->SetLargeCornerRadius(corner_radius_);
   views::InstallRoundRectHighlightPathGenerator(this, gfx::Insets(),
                                                 corner_radius_);
+  // UpdateColors also updates the background border radius.
+  UpdateColors();
   OnPropertyChanged(&corner_radius_, kPropertyEffectsPaint);
 }
 
@@ -133,6 +139,15 @@
   UpdateColors();
 }
 
+void MdTextButton::OnBoundsChanged(const gfx::Rect& previous_bounds) {
+  LabelButton::OnBoundsChanged(previous_bounds);
+
+  if (features::IsChromeRefresh2023()) {
+    SetCornerRadius(LayoutProvider::Get()->GetCornerRadiusMetric(
+        Emphasis::kMaximum, size()));
+  }
+}
+
 void MdTextButton::SetEnabledTextColors(absl::optional<SkColor> color) {
   LabelButton::SetEnabledTextColors(std::move(color));
   UpdateColors();
diff --git a/ui/views/controls/button/md_text_button.h b/ui/views/controls/button/md_text_button.h
index 430f27a0..099160f 100644
--- a/ui/views/controls/button/md_text_button.h
+++ b/ui/views/controls/button/md_text_button.h
@@ -56,6 +56,7 @@
   // View:
   void OnFocus() override;
   void OnBlur() override;
+  void OnBoundsChanged(const gfx::Rect& previous_bounds) override;
 
  private:
   void UpdatePadding();
diff --git a/chrome/browser/ui/native_window_tracker.h b/ui/views/native_window_tracker.h
similarity index 62%
rename from chrome/browser/ui/native_window_tracker.h
rename to ui/views/native_window_tracker.h
index ec0ecb6..dba1b88 100644
--- a/chrome/browser/ui/native_window_tracker.h
+++ b/ui/views/native_window_tracker.h
@@ -2,17 +2,20 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_UI_NATIVE_WINDOW_TRACKER_H_
-#define CHROME_BROWSER_UI_NATIVE_WINDOW_TRACKER_H_
+#ifndef UI_VIEWS_NATIVE_WINDOW_TRACKER_H_
+#define UI_VIEWS_NATIVE_WINDOW_TRACKER_H_
 
 #include <memory>
 
 #include "ui/gfx/native_widget_types.h"
+#include "ui/views/views_export.h"
+
+namespace views {
 
 // An observer which detects when a gfx::NativeWindow is closed.
-class NativeWindowTracker {
+class VIEWS_EXPORT NativeWindowTracker {
  public:
-  virtual ~NativeWindowTracker() {}
+  virtual ~NativeWindowTracker() = default;
 
   static std::unique_ptr<NativeWindowTracker> Create(gfx::NativeWindow window);
 
@@ -20,4 +23,6 @@
   virtual bool WasNativeWindowClosed() const = 0;
 };
 
-#endif  // CHROME_BROWSER_UI_NATIVE_WINDOW_TRACKER_H_
+}  // namespace views
+
+#endif  // UI_VIEWS_NATIVE_WINDOW_TRACKER_H_
diff --git a/chrome/browser/ui/aura/native_window_tracker_aura.cc b/ui/views/native_window_tracker_aura.cc
similarity index 65%
rename from chrome/browser/ui/aura/native_window_tracker_aura.cc
rename to ui/views/native_window_tracker_aura.cc
index dfc168a..802c014 100644
--- a/chrome/browser/ui/aura/native_window_tracker_aura.cc
+++ b/ui/views/native_window_tracker_aura.cc
@@ -2,12 +2,15 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/ui/aura/native_window_tracker_aura.h"
+#include "ui/views/native_window_tracker_aura.h"
+
+#include <memory>
 
 #include "ui/aura/window.h"
 
-NativeWindowTrackerAura::NativeWindowTrackerAura(
-    gfx::NativeWindow window)
+namespace views {
+
+NativeWindowTrackerAura::NativeWindowTrackerAura(gfx::NativeWindow window)
     : window_(window) {
   window->AddObserver(this);
 }
@@ -21,8 +24,7 @@
   return window_ == nullptr;
 }
 
-void NativeWindowTrackerAura::OnWindowDestroying(
-    aura::Window* window) {
+void NativeWindowTrackerAura::OnWindowDestroying(aura::Window* window) {
   window_->RemoveObserver(this);
   window_ = nullptr;
 }
@@ -30,6 +32,7 @@
 // static
 std::unique_ptr<NativeWindowTracker> NativeWindowTracker::Create(
     gfx::NativeWindow window) {
-  return std::unique_ptr<NativeWindowTracker>(
-      new NativeWindowTrackerAura(window));
+  return std::make_unique<NativeWindowTrackerAura>(window);
 }
+
+}  // namespace views
diff --git a/chrome/browser/ui/aura/native_window_tracker_aura.h b/ui/views/native_window_tracker_aura.h
similarity index 62%
rename from chrome/browser/ui/aura/native_window_tracker_aura.h
rename to ui/views/native_window_tracker_aura.h
index ec6e492c..e526adc8 100644
--- a/chrome/browser/ui/aura/native_window_tracker_aura.h
+++ b/ui/views/native_window_tracker_aura.h
@@ -2,15 +2,18 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_UI_AURA_NATIVE_WINDOW_TRACKER_AURA_H_
-#define CHROME_BROWSER_UI_AURA_NATIVE_WINDOW_TRACKER_AURA_H_
+#ifndef UI_VIEWS_NATIVE_WINDOW_TRACKER_AURA_H_
+#define UI_VIEWS_NATIVE_WINDOW_TRACKER_AURA_H_
 
 #include "base/memory/raw_ptr.h"
-#include "chrome/browser/ui/native_window_tracker.h"
 #include "ui/aura/window_observer.h"
+#include "ui/views/native_window_tracker.h"
+#include "ui/views/views_export.h"
 
-class NativeWindowTrackerAura : public NativeWindowTracker,
-                                public aura::WindowObserver {
+namespace views {
+
+class VIEWS_EXPORT NativeWindowTrackerAura : public NativeWindowTracker,
+                                             public aura::WindowObserver {
  public:
   explicit NativeWindowTrackerAura(gfx::NativeWindow window);
 
@@ -29,4 +32,6 @@
   raw_ptr<aura::Window> window_;
 };
 
-#endif  // CHROME_BROWSER_UI_AURA_NATIVE_WINDOW_TRACKER_AURA_H_
+}  // namespace views
+
+#endif  // UI_VIEWS_NATIVE_WINDOW_TRACKER_AURA_H_
diff --git a/ui/views/vector_icons/drag_image_selection.icon b/ui/views/vector_icons/drag_image_selection.icon
deleted file mode 100644
index 8d25db9..0000000
--- a/ui/views/vector_icons/drag_image_selection.icon
+++ /dev/null
@@ -1,64 +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.
-
-CANVAS_DIMENSIONS, 20,
-MOVE_TO, 4.5f, 17,
-R_QUADRATIC_TO, -0.62f, 0, -1.06f, -0.44f,
-QUADRATIC_TO, 3, 16.13f, 3, 15.5f,
-V_LINE_TO, 6,
-R_H_LINE_TO, 11,
-R_V_LINE_TO, 11,
-CLOSE,
-R_MOVE_TO, 11, 0,
-R_V_LINE_TO, -1.5f,
-H_LINE_TO, 17,
-R_QUADRATIC_TO, 0, 0.63f, -0.44f, 1.06f,
-QUADRATIC_TO, 16.13f, 17, 15.5f, 17,
-CLOSE,
-R_MOVE_TO, -11, -2,
-R_H_LINE_TO, 8,
-R_LINE_TO, -2.62f, -3.5f,
-LINE_TO, 8, 14,
-R_LINE_TO, -1.37f, -1.83f,
-CLOSE,
-R_MOVE_TO, 11, -1.12f,
-R_V_LINE_TO, -1.5f,
-H_LINE_TO, 17,
-R_V_LINE_TO, 1.5f,
-CLOSE,
-R_MOVE_TO, 0, -3.12f,
-R_V_LINE_TO, -1.5f,
-H_LINE_TO, 17,
-R_V_LINE_TO, 1.5f,
-CLOSE,
-R_MOVE_TO, 0, -3.12f,
-R_V_LINE_TO, -1.5f,
-H_LINE_TO, 17,
-R_V_LINE_TO, 1.5f,
-CLOSE,
-MOVE_TO, 3, 4.5f,
-R_QUADRATIC_TO, 0, -0.62f, 0.44f, -1.06f,
-QUADRATIC_TO, 3.88f, 3, 4.5f, 3,
-R_V_LINE_TO, 1.5f,
-CLOSE,
-R_MOVE_TO, 3.13f, 0,
-V_LINE_TO, 3,
-R_H_LINE_TO, 1.5f,
-R_V_LINE_TO, 1.5f,
-CLOSE,
-R_MOVE_TO, 3.13f, 0,
-V_LINE_TO, 3,
-R_H_LINE_TO, 1.5f,
-R_V_LINE_TO, 1.5f,
-CLOSE,
-R_MOVE_TO, 3.13f, 0,
-V_LINE_TO, 3,
-R_H_LINE_TO, 1.5f,
-R_V_LINE_TO, 1.5f,
-CLOSE,
-R_MOVE_TO, 3.13f, 0,
-V_LINE_TO, 3,
-R_QUADRATIC_TO, 0.63f, 0, 1.06f, 0.44f,
-QUADRATIC_TO, 17, 3.88f, 17, 4.5f,
-CLOSE
diff --git a/ui/webui/resources/BUILD.gn b/ui/webui/resources/BUILD.gn
index 724a595..84895fde6 100644
--- a/ui/webui/resources/BUILD.gn
+++ b/ui/webui/resources/BUILD.gn
@@ -139,10 +139,11 @@
 
 preprocessed_folder = "$target_gen_dir/preprocessed"
 
-checked_in_dts_files = [
-  "js/cr.m.d.ts",
-  "js/load_time_data.m.d.ts",
-]
+checked_in_dts_files = [ "js/load_time_data.m.d.ts" ]
+
+if (is_chromeos_ash) {
+  checked_in_dts_files += [ "js/cr.m.d.ts" ]
+}
 
 # Copies checked-in .d.ts files to the preprocess folder so that they are
 # discovered by TSC the same way generated .d.ts files are.
diff --git a/ui/webui/resources/cr_components/app_management/permission_item.ts b/ui/webui/resources/cr_components/app_management/permission_item.ts
index 3cb15874..a7a95cc 100644
--- a/ui/webui/resources/cr_components/app_management/permission_item.ts
+++ b/ui/webui/resources/cr_components/app_management/permission_item.ts
@@ -140,11 +140,11 @@
     const permissionValue = getPermission(this.app, this.permissionType).value;
     if (isBoolValue(permissionValue)) {
       newPermission =
-          this.getUIPermissionBoolean_(this.app, this.permissionType);
+          this.getUiPermissionBoolean_(this.app, this.permissionType);
       newBoolState = getBoolPermissionValue(newPermission.value);
     } else if (isTriStateValue(permissionValue)) {
       newPermission =
-          this.getUIPermissionTriState_(this.app, this.permissionType);
+          this.getUiPermissionTriState_(this.app, this.permissionType);
 
       newBoolState =
           getTriStatePermissionValue(newPermission.value) === TriState.kAllow;
@@ -164,7 +164,7 @@
   /**
    * Gets the permission boolean based on the toggle's UI state.
    */
-  private getUIPermissionBoolean_(
+  private getUiPermissionBoolean_(
       app: App, permissionType: PermissionTypeIndex): Permission {
     const currentPermission = getPermission(app, permissionType);
 
@@ -180,7 +180,7 @@
   /**
    * Gets the permission tristate based on the toggle's UI state.
    */
-  private getUIPermissionTriState_(
+  private getUiPermissionTriState_(
       app: App, permissionType: PermissionTypeIndex): Permission {
     let newPermissionValue;
     const currentPermission = getPermission(app, permissionType);
diff --git a/ui/webui/resources/cr_components/certificate_manager/certificate_manager.ts b/ui/webui/resources/cr_components/certificate_manager/certificate_manager.ts
index d024530..00222b2 100644
--- a/ui/webui/resources/cr_components/certificate_manager/certificate_manager.ts
+++ b/ui/webui/resources/cr_components/certificate_manager/certificate_manager.ts
@@ -172,7 +172,7 @@
         'client-import-allowed-changed',
         this.setClientImportAllowed.bind(this));
     this.addWebUiListener(
-        'ca-import-allowed-changed', this.setCAImportAllowed.bind(this));
+        'ca-import-allowed-changed', this.setCaImportAllowed.bind(this));
     CertificatesBrowserProxyImpl.getInstance().refreshCertificates();
   }
 
@@ -180,7 +180,7 @@
     this.clientImportAllowed = allowed;
   }
 
-  private setCAImportAllowed(allowed: boolean) {
+  private setCaImportAllowed(allowed: boolean) {
     this.caImportAllowed = allowed;
   }
 
diff --git a/ui/webui/resources/cr_components/localized_link/localized_link.html b/ui/webui/resources/cr_components/localized_link/localized_link.html
index f74e6947..ef4886c 100644
--- a/ui/webui/resources/cr_components/localized_link/localized_link.html
+++ b/ui/webui/resources/cr_components/localized_link/localized_link.html
@@ -26,5 +26,5 @@
     pointer-events: none;
   }
 </style>
-<!-- innerHTML is set via setContainerInnerHTML_. -->
+<!-- innerHTML is set via setContainerInnerHtml_. -->
 <div id="container"></div>
diff --git a/ui/webui/resources/cr_components/localized_link/localized_link.ts b/ui/webui/resources/cr_components/localized_link/localized_link.ts
index b14b15c..84c2e2d0 100644
--- a/ui/webui/resources/cr_components/localized_link/localized_link.ts
+++ b/ui/webui/resources/cr_components/localized_link/localized_link.ts
@@ -79,7 +79,7 @@
         type: String,
         value: '',
         computed: 'getAriaLabelledContent_(localizedString, linkUrl)',
-        observer: 'setContainerInnerHTML_',
+        observer: 'setContainerInnerHtml_',
       },
     };
   }
@@ -148,7 +148,7 @@
     return tempEl.innerHTML;
   }
 
-  private setContainerInnerHTML_() {
+  private setContainerInnerHtml_() {
     this.$.container.innerHTML = sanitizeInnerHtml(this.containerInnerHTML_, {
       attrs: [
         'aria-hidden',
diff --git a/ui/webui/resources/cr_elements/cr_lottie/cr_lottie.ts b/ui/webui/resources/cr_elements/cr_lottie/cr_lottie.ts
index 8a4039e..629d74d 100644
--- a/ui/webui/resources/cr_elements/cr_lottie/cr_lottie.ts
+++ b/ui/webui/resources/cr_elements/cr_lottie/cr_lottie.ts
@@ -5,7 +5,11 @@
 /**
  * @fileoverview 'cr-lottie' is a wrapper around the player for lottie
  * animations. Since the player runs on a worker thread, 'cr-lottie' requires
- * the document CSP to be set to "worker-src blob: 'self';".
+ * the document CSP to be set to "worker-src blob: chrome://resources 'self';".
+ *
+ * For documents that have TrustedTypes CSP checks enabled, it also requires the
+ * document CSP to be set to "trusted-types lottie-worker-script-loader;".
+ *
  * Fires a 'cr-lottie-initialized' event when the animation was successfully
  * initialized.
  * Fires a 'cr-lottie-playing' event when the animation starts playing.
@@ -18,7 +22,6 @@
 import {PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {assert, assertNotReached} from '../../js/assert_ts.js';
-import {getTrustedScriptURL} from '../../js/static_types.js';
 
 import {getTemplate} from './cr_lottie.html.js';
 
@@ -29,13 +32,8 @@
     workerLoaderPolicy =
         window.trustedTypes!.createPolicy('lottie-worker-script-loader', {
           createScriptURL: (_ignore: string) => {
-            const workerUrl =
-                getTrustedScriptURL`chrome://resources/lottie/lottie_worker.min.js`;
-            // Need to add a try-catch clause because in tests the parent
-            // element can be removed from the DOM before the importScripts()
-            // call  has finished loading, resulting in test errors.
-            const script = `try{ importScripts('${
-                workerUrl}'); } catch(e) { console.warn(e); };`;
+            const script =
+                `import 'chrome://resources/lottie/lottie_worker.min.js';`;
             // CORS blocks loading worker script from a different origin, even
             // if chrome://resources/ is added in the 'worker-src' CSP header.
             // (see https://crbug.com/1385477). Loading scripts as blob and then
@@ -153,7 +151,8 @@
   override connectedCallback() {
     super.connectedCallback();
 
-    this.worker_ = new Worker(getLottieWorkerURL() as unknown as URL);
+    this.worker_ =
+        new Worker(getLottieWorkerURL() as unknown as URL, {type: 'module'});
     this.worker_.onmessage = this.onMessage_.bind(this);
     this.initialize_();
   }
diff --git a/ui/webui/resources/js/BUILD.gn b/ui/webui/resources/js/BUILD.gn
index f9b14094..373a9a8 100644
--- a/ui/webui/resources/js/BUILD.gn
+++ b/ui/webui/resources/js/BUILD.gn
@@ -86,8 +86,8 @@
     "load_time_data_deprecated.js",
   ]
 
-  if (is_chromeos_ash || is_android) {
-    # Used by ChromeOS and Android Closure UIs.
+  if (is_chromeos_ash) {
+    # Used by ChromeOS UIs.
     in_files += [ "cr.m.js" ]
   }
 
@@ -137,7 +137,7 @@
     ":load_time_data.m",
   ]
 
-  if (is_chromeos_ash || is_android) {
+  if (is_chromeos_ash) {
     deps += [ ":cr.m" ]
   }
 }
@@ -145,7 +145,7 @@
 js_library("assert") {
 }
 
-if (is_chromeos_ash || is_android) {
+if (is_chromeos_ash) {
   js_library("cr.m") {
   }
 }
diff --git a/url/url_features.cc b/url/url_features.cc
index 62e68d3..39000c8 100644
--- a/url/url_features.cc
+++ b/url/url_features.cc
@@ -8,7 +8,7 @@
 
 BASE_FEATURE(kUseIDNA2008NonTransitional,
              "UseIDNA2008NonTransitional",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 bool IsUsingIDNA2008NonTransitional() {
   return base::FeatureList::IsEnabled(kUseIDNA2008NonTransitional);
diff --git a/weblayer/browser/js_communication/web_message_host_factory_wrapper.cc b/weblayer/browser/js_communication/web_message_host_factory_wrapper.cc
index ab60581..46356de2 100644
--- a/weblayer/browser/js_communication/web_message_host_factory_wrapper.cc
+++ b/weblayer/browser/js_communication/web_message_host_factory_wrapper.cc
@@ -13,6 +13,7 @@
 #include "components/js_injection/common/interfaces.mojom.h"
 #include "content/public/browser/page.h"
 #include "content/public/browser/render_frame_host.h"
+#include "third_party/abseil-cpp/absl/types/variant.h"
 #include "weblayer/browser/page_impl.h"
 #include "weblayer/public/js_communication/web_message.h"
 #include "weblayer/public/js_communication/web_message_host.h"
@@ -40,11 +41,11 @@
       std::unique_ptr<js_injection::WebMessage> message) override {
     std::unique_ptr<WebMessage> m = std::make_unique<WebMessage>();
     auto& payload = message->message;
-    if (!payload->is_string_value()) {
+    if (!absl::holds_alternative<std::u16string>(payload)) {
       // Ignore non-string messages, not supported by weblayer.
       return;
     }
-    m->message = std::move(payload->get_string_value());
+    m->message = std::move(absl::get<std::u16string>(payload));
     connection_->OnPostMessage(std::move(m));
   }
   void OnBackForwardCacheStateChanged() override {
@@ -53,10 +54,7 @@
 
   // WebMessageReplyProxy:
   void PostWebMessage(std::unique_ptr<WebMessage> message) override {
-    js_injection::mojom::JsWebMessagePtr js_message =
-        js_injection::mojom::JsWebMessage::NewStringValue(
-            std::move(message->message));
-    proxy_->PostWebMessage(std::move(js_message));
+    proxy_->PostWebMessage(std::move(message->message));
   }
   bool IsInBackForwardCache() override {
     return proxy_->IsInBackForwardCache();