diff --git a/DEPS b/DEPS
index 51db2be..b34b928b 100644
--- a/DEPS
+++ b/DEPS
@@ -275,11 +275,11 @@
   # 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': '9f85e0e12d708c453c7abd6c9bd9768444c7640f',
+  'skia_revision': 'd881def3e544be7db5524a80e5dce7ca0a970f09',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': 'fb15e275ae4d8cac652df87550c50cd151cef2c1',
+  'v8_revision': 'dca05c50dc29a73c4396bb79318dd6b387974c25',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
@@ -287,7 +287,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
-  'swiftshader_revision': 'ee0d0b41a62640e0c37d1611f84e7533072c7256',
+  'swiftshader_revision': 'd3b44fe1edef36ce3169daf9ea178c99784abe2f',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
@@ -326,7 +326,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling freetype
   # and whatever else without interference from each other.
-  'freetype_revision': 'd6857981239ea5f6e95cb4eb4402307f3527760a',
+  'freetype_revision': 'c26872ed59cba3af2f407b5eefc92fcec92aa52b',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling freetype
   # and whatever else without interference from each other.
@@ -354,7 +354,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': '5e91454d1bdee651a57057728c9584a47b0186a0',
+  'devtools_frontend_revision': 'ae1192b6309e632d35ff71b4e8ab28daa1b057e0',
   # 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.
@@ -841,7 +841,7 @@
       'packages': [
         {
           'package': 'chromium/rts/model/linux-amd64',
-          'version': '8zqTXxzVGxAv_4yJH62SDu338NH2a9Sj8E-Ue8Od1cQC',
+          'version': 'mwQUySZTAjUh63HUF4elgaSEXQmxEgTrzt0aEQlqE7oC',
         },
       ],
       'dep_type': 'cipd',
@@ -852,7 +852,7 @@
       'packages': [
         {
           'package': 'chromium/rts/model/mac-amd64',
-          'version': 'SI7tqcfHGyKVRq_68Hdai7SqtG-ddU5RFVc313lMD_UC',
+          'version': 'VyKiTzbwRVHNrfBYHduT0eAthyj9joVN-umACpbgqS4C',
         },
       ],
       'dep_type': 'cipd',
@@ -863,7 +863,7 @@
       'packages': [
         {
           'package': 'chromium/rts/model/windows-amd64',
-          'version': 'CHdWfS54WS0s0BeEADqbqHQ5mQqzzmMwGA7qUcJ5lQ0C',
+          'version': '_Na4ODRyZ59BKH95r5cxBPSTd3F2mFFiO3kLcmj0LncC',
         },
       ],
       'dep_type': 'cipd',
@@ -1113,7 +1113,7 @@
   # Tools used when building Chrome for Chrome OS. This affects both the Simple
   # Chrome workflow, as well as the chromeos-chrome ebuild.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '97c3857f4712c249543d99f627312a5b8bc3cf70',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'c0a200192d7742fd7f5cb4ebd9dd158061c57645',
       'condition': 'checkout_chromeos',
   },
 
@@ -1136,7 +1136,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '8f1fea025d78c26046d3135e026f097dc2022698',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '2a229719c26bbf2cbc65c91b372ea03c6bef3b9c',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
@@ -1781,7 +1781,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@a9269965b79bd8a657080f295bfc677383ecb122',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@f51a1dbc4cf0ffbd7b12a3296c39538a5ec0b2ce',
     'condition': 'checkout_src_internal',
   },
 
@@ -1811,7 +1811,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/help_app/app',
-        'version': 'aJEXXGyUnjQdOYV24rRrtQV8x3UK-KMkW9nExv9j4rcC',
+        'version': 'L3ofmGEfbGxTWEs5VF9UqJT56YKWYcVA_R_rxOzwW80C',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -3703,6 +3703,7 @@
         'src/third_party/blink/renderer/build/scripts',
         'src/third_party/blink/tools',  # See http://crbug.com/625877.
         'src/third_party/catapult',
+        'src/third_party/mako', # Some failures triggered by crrev.com/c/3686969
         'src/tools',
     ],
   },
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index 2358721..b378757 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -2356,7 +2356,7 @@
             r"^extensions[\\/]renderer[\\/]logging_native_handler\.cc$",
             r"^fuchsia[\\/]base[\\/]init_logging.cc$",
             r"^fuchsia[\\/]engine[\\/]browser[\\/]frame_impl.cc$",
-            r"^fuchsia[\\/]runners[\\/]common[\\/]web_component.cc$",
+            r"^fuchsia_web[\\/]runners[\\/]common[\\/]web_component.cc$",
             r"^headless[\\/]app[\\/]headless_shell\.cc$",
             r"^ipc[\\/]ipc_logging\.cc$",
             r"^native_client_sdk[\\/]",
diff --git a/android_webview/browser/BUILD.gn b/android_webview/browser/BUILD.gn
index 6e4daf2..aaf26bd 100644
--- a/android_webview/browser/BUILD.gn
+++ b/android_webview/browser/BUILD.gn
@@ -183,8 +183,6 @@
     "safe_browsing/aw_safe_browsing_ui_manager.h",
     "safe_browsing/aw_url_checker_delegate_impl.cc",
     "safe_browsing/aw_url_checker_delegate_impl.h",
-    "scoped_add_feature_flags.cc",
-    "scoped_add_feature_flags.h",
     "state_serializer.cc",
     "state_serializer.h",
     "tracing/aw_background_tracing_metrics_provider.cc",
diff --git a/android_webview/browser/cookie_manager.cc b/android_webview/browser/cookie_manager.cc
index e9c2286..3b8f56e 100644
--- a/android_webview/browser/cookie_manager.cc
+++ b/android_webview/browser/cookie_manager.cc
@@ -469,7 +469,7 @@
 
   std::unique_ptr<net::CanonicalCookie> cc(net::CanonicalCookie::Create(
       new_host, value, base::Time::Now(), absl::nullopt /* server_time */,
-      net::CookiePartitionKey::Todo()));
+      absl::nullopt /* cookie_partition_key */));
 
   if (!cc || !should_allow_cookie) {
     MaybeRunCookieCallback(std::move(callback), false);
diff --git a/android_webview/browser/scoped_add_feature_flags.cc b/android_webview/browser/scoped_add_feature_flags.cc
deleted file mode 100644
index 84c3477..0000000
--- a/android_webview/browser/scoped_add_feature_flags.cc
+++ /dev/null
@@ -1,82 +0,0 @@
-// Copyright 2019 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 "android_webview/browser/scoped_add_feature_flags.h"
-
-#include "base/base_switches.h"
-#include "base/command_line.h"
-#include "base/containers/contains.h"
-#include "base/strings/string_util.h"
-
-namespace android_webview {
-
-ScopedAddFeatureFlags::ScopedAddFeatureFlags(base::CommandLine* cl) : cl_(cl) {
-  std::string enabled_features =
-      cl->GetSwitchValueASCII(switches::kEnableFeatures);
-  std::string disabled_features =
-      cl->GetSwitchValueASCII(switches::kDisableFeatures);
-  for (auto& sp : base::FeatureList::SplitFeatureListString(enabled_features))
-    enabled_features_.emplace_back(sp);
-  for (auto& sp : base::FeatureList::SplitFeatureListString(disabled_features))
-    disabled_features_.emplace_back(sp);
-}
-
-ScopedAddFeatureFlags::~ScopedAddFeatureFlags() {
-  cl_->AppendSwitchASCII(switches::kEnableFeatures,
-                         base::JoinString(enabled_features_, ","));
-  cl_->AppendSwitchASCII(switches::kDisableFeatures,
-                         base::JoinString(disabled_features_, ","));
-}
-
-void ScopedAddFeatureFlags::EnableIfNotSet(const base::Feature& feature) {
-  AddFeatureIfNotSet(feature, /*suffix=*/"", /*enable=*/true);
-}
-
-void ScopedAddFeatureFlags::EnableIfNotSetWithParameter(
-    const base::Feature& feature,
-    std::string name,
-    std::string value) {
-  std::string suffix = ":" + name + "/" + value;
-  AddFeatureIfNotSet(feature, suffix, true /* enable */);
-}
-
-void ScopedAddFeatureFlags::DisableIfNotSet(const base::Feature& feature) {
-  AddFeatureIfNotSet(feature, /*suffix=*/"", /*enable=*/false);
-}
-
-bool ScopedAddFeatureFlags::IsEnabled(const base::Feature& feature) {
-  return IsEnabledWithParameter(feature, /*name=*/"", /*value=*/"");
-}
-
-bool ScopedAddFeatureFlags::IsEnabledWithParameter(const base::Feature& feature,
-                                                   const std::string& name,
-                                                   const std::string& value) {
-  std::string feature_name = feature.name;
-  if (!name.empty()) {
-    feature_name += ":" + name + "/" + value;
-  }
-  if (base::Contains(disabled_features_, feature_name))
-    return false;
-  if (base::Contains(enabled_features_, feature_name))
-    return true;
-  return feature.default_state == base::FEATURE_ENABLED_BY_DEFAULT;
-}
-
-void ScopedAddFeatureFlags::AddFeatureIfNotSet(const base::Feature& feature,
-                                               const std::string& suffix,
-                                               bool enable) {
-  std::string feature_name = feature.name;
-  feature_name += suffix;
-  if (base::Contains(enabled_features_, feature_name) ||
-      base::Contains(disabled_features_, feature_name)) {
-    return;
-  }
-  if (enable) {
-    enabled_features_.emplace_back(feature_name);
-  } else {
-    disabled_features_.emplace_back(feature_name);
-  }
-}
-
-}  // namespace android_webview
diff --git a/android_webview/browser/scoped_add_feature_flags.h b/android_webview/browser/scoped_add_feature_flags.h
deleted file mode 100644
index 9cff556b1..0000000
--- a/android_webview/browser/scoped_add_feature_flags.h
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright 2019 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 ANDROID_WEBVIEW_BROWSER_SCOPED_ADD_FEATURE_FLAGS_H_
-#define ANDROID_WEBVIEW_BROWSER_SCOPED_ADD_FEATURE_FLAGS_H_
-
-#include <string>
-#include <vector>
-
-#include "base/feature_list.h"
-#include "base/memory/raw_ptr.h"
-
-namespace base {
-class CommandLine;
-}
-
-namespace android_webview {
-
-class ScopedAddFeatureFlags {
- public:
-  explicit ScopedAddFeatureFlags(base::CommandLine* cl);
-
-  ScopedAddFeatureFlags(const ScopedAddFeatureFlags&) = delete;
-  ScopedAddFeatureFlags& operator=(const ScopedAddFeatureFlags&) = delete;
-
-  ~ScopedAddFeatureFlags();
-
-  // Any existing (user set) enable/disable takes precedence.
-  void EnableIfNotSet(const base::Feature& feature);
-  void DisableIfNotSet(const base::Feature& feature);
-  void EnableIfNotSetWithParameter(const base::Feature& feature,
-                                   std::string name,
-                                   std::string value);
-  // Check if the feature is enabled from command line or functions above
-  bool IsEnabled(const base::Feature& feature);
-  bool IsEnabledWithParameter(const base::Feature& feature,
-                              const std::string& name,
-                              const std::string& value);
-
- private:
-  void AddFeatureIfNotSet(const base::Feature& feature,
-                          const std::string& suffix,
-                          bool enable);
-
-  const raw_ptr<base::CommandLine> cl_;
-  std::vector<std::string> enabled_features_;
-  std::vector<std::string> disabled_features_;
-};
-
-}  // namespace android_webview
-
-#endif  // ANDROID_WEBVIEW_BROWSER_SCOPED_ADD_FEATURE_FLAGS_H_
diff --git a/android_webview/java/src/org/chromium/android_webview/AwWebContentsObserver.java b/android_webview/java/src/org/chromium/android_webview/AwWebContentsObserver.java
index da81a84e..6bec03b 100644
--- a/android_webview/java/src/org/chromium/android_webview/AwWebContentsObserver.java
+++ b/android_webview/java/src/org/chromium/android_webview/AwWebContentsObserver.java
@@ -167,7 +167,7 @@
             });
         }
 
-        if (client != null && navigation.isFragmentNavigation()) {
+        if (client != null && navigation.isPrimaryMainFrameFragmentNavigation()) {
             // Note fragment navigations do not have a matching onPageStarted.
             client.getCallbackHelper().postOnPageFinished(url);
         }
diff --git a/android_webview/lib/aw_main_delegate.cc b/android_webview/lib/aw_main_delegate.cc
index c4520c5..4aec4d7 100644
--- a/android_webview/lib/aw_main_delegate.cc
+++ b/android_webview/lib/aw_main_delegate.cc
@@ -12,7 +12,6 @@
 #include "android_webview/browser/gfx/browser_view_renderer.h"
 #include "android_webview/browser/gfx/gpu_service_webview.h"
 #include "android_webview/browser/gfx/viz_compositor_thread_runner_webview.h"
-#include "android_webview/browser/scoped_add_feature_flags.h"
 #include "android_webview/browser/tracing/aw_trace_event_args_allowlist.h"
 #include "android_webview/common/aw_descriptors.h"
 #include "android_webview/common/aw_features.h"
@@ -32,6 +31,7 @@
 #include "base/i18n/icu_util.h"
 #include "base/i18n/rtl.h"
 #include "base/posix/global_descriptors.h"
+#include "base/scoped_add_feature_flags.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/threading/thread_restrictions.h"
 #include "build/build_config.h"
@@ -185,7 +185,7 @@
   }
 
   {
-    ScopedAddFeatureFlags features(cl);
+    base::ScopedAddFeatureFlags features(cl);
 
     if (base::android::BuildInfo::GetInstance()->sdk_int() >=
         base::android::SDK_VERSION_OREO) {
@@ -298,6 +298,11 @@
     // Have the network service in the browser process even if we have separate
     // renderer processes. See also: switches::kInProcessGPU above.
     features.EnableIfNotSet(::features::kNetworkServiceInProcess);
+
+    // Disable Event.path on Canary and Dev to help the deprecation and removal.
+    // See crbug.com/1277431 for more details.
+    if (version_info::android::GetChannel() < version_info::Channel::BETA)
+      features.DisableIfNotSet(blink::features::kEventPath);
   }
 
   android_webview::RegisterPathProvider();
diff --git a/android_webview/test/BUILD.gn b/android_webview/test/BUILD.gn
index 540fc16..d9bc885d 100644
--- a/android_webview/test/BUILD.gn
+++ b/android_webview/test/BUILD.gn
@@ -617,7 +617,6 @@
     "../browser/renderer_host/auto_login_parser_unittest.cc",
     "../browser/safe_browsing/aw_ping_manager_unittest.cc",
     "../browser/safe_browsing/aw_safe_browsing_allowlist_manager_unittest.cc",
-    "../browser/scoped_add_feature_flags_unittests.cc",
     "../browser/state_serializer_unittest.cc",
     "../browser/tracing/aw_background_tracing_metrics_provider_unittest.cc",
     "../browser/tracing/aw_tracing_delegate_unittest.cc",
diff --git a/android_webview/tools/PRESUBMIT.py b/android_webview/tools/PRESUBMIT.py
index 61cd833..84d2e4e 100644
--- a/android_webview/tools/PRESUBMIT.py
+++ b/android_webview/tools/PRESUBMIT.py
@@ -13,7 +13,8 @@
       output_api,
       input_api.PresubmitLocalPath(),
       files_to_check=['.*_test\\.py$'],
-      files_to_skip=[])
+      files_to_skip=[],
+      run_on_python2=False)
 
 
 def CommonChecks(input_api, output_api):
diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd
index 958abc1..cd938ad 100644
--- a/ash/ash_strings.grd
+++ b/ash/ash_strings.grd
@@ -1633,6 +1633,9 @@
       <message name="IDS_ASH_STYLUS_TOOLS_METALAYER_TOAST_LOADING" desc="Message content on the toast that appears when the user presses the stylus button to activate the Assistant but it is still loading.">
         Assistant is loading...
       </message>
+      <message name="IDS_ASH_STYLUS_TOOLS_METALAYER_TOAST_DEPRECATE" desc="Message content on the toast that appears when the user presses the stylus button to activate the Assistant but the feature is deprecated.">
+        Searching what's on my screen with Google Assistant is no longer supported.
+      </message>
       <message name="IDS_ASH_STYLUS_TOOLS_TITLE" desc="The title of the stylus tools dialog in the ash shelf.">
         Stylus tools
       </message>
diff --git a/ash/ash_strings_grd/IDS_ASH_STYLUS_TOOLS_METALAYER_TOAST_DEPRECATE.png.sha1 b/ash/ash_strings_grd/IDS_ASH_STYLUS_TOOLS_METALAYER_TOAST_DEPRECATE.png.sha1
new file mode 100644
index 0000000..0f7638b
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_STYLUS_TOOLS_METALAYER_TOAST_DEPRECATE.png.sha1
@@ -0,0 +1 @@
+b866b07b84bb155bf2ffafb2871e773c0ec33f56
\ No newline at end of file
diff --git a/ash/components/arc/session/arc_container_client_adapter.cc b/ash/components/arc/session/arc_container_client_adapter.cc
index 1a038b74..bfaacce 100644
--- a/ash/components/arc/session/arc_container_client_adapter.cc
+++ b/ash/components/arc/session/arc_container_client_adapter.cc
@@ -167,6 +167,7 @@
     request.set_packages_cache_mode(
         ToLoginManagerPackageCacheMode(params.packages_cache_mode));
     request.set_skip_gms_core_cache(params.skip_gms_core_cache);
+    request.set_skip_tts_cache(params.skip_tts_cache);
     request.set_is_demo_session(params.is_demo_session);
     request.set_demo_session_apps_path(params.demo_session_apps_path.value());
     request.set_locale(params.locale);
diff --git a/ash/components/arc/session/arc_container_client_adapter_unittest.cc b/ash/components/arc/session/arc_container_client_adapter_unittest.cc
index d7c4a5c..7f6ff379 100644
--- a/ash/components/arc/session/arc_container_client_adapter_unittest.cc
+++ b/ash/components/arc/session/arc_container_client_adapter_unittest.cc
@@ -196,6 +196,26 @@
   EXPECT_TRUE(request.enable_tts_caching());
 }
 
+TEST_F(ArcContainerClientAdapterTest, ConvertUpgradeParams_SkipTtsCacheSetup) {
+  UpgradeParams upgrade_params;
+  upgrade_params.skip_tts_cache = true;
+  client_adapter()->UpgradeArc(std::move(upgrade_params),
+                               base::BindOnce(&OnMiniInstanceStarted));
+  const auto& upgrade_request =
+      chromeos::FakeSessionManagerClient::Get()->last_upgrade_arc_request();
+  EXPECT_TRUE(upgrade_request.skip_tts_cache());
+}
+
+TEST_F(ArcContainerClientAdapterTest,
+       ConvertUpgradeParams_EnableTtsCacheSetup) {
+  UpgradeParams upgrade_params;
+  client_adapter()->UpgradeArc(std::move(upgrade_params),
+                               base::BindOnce(&OnMiniInstanceStarted));
+  const auto& upgrade_request =
+      chromeos::FakeSessionManagerClient::Get()->last_upgrade_arc_request();
+  EXPECT_FALSE(upgrade_request.skip_tts_cache());
+}
+
 struct DalvikMemoryProfileTestParam {
   // Requested profile.
   StartParams::DalvikMemoryProfile profile;
diff --git a/ash/components/arc/session/arc_upgrade_params.cc b/ash/components/arc/session/arc_upgrade_params.cc
index db9b3be..980dba98 100644
--- a/ash/components/arc/session/arc_upgrade_params.cc
+++ b/ash/components/arc/session/arc_upgrade_params.cc
@@ -36,6 +36,8 @@
       packages_cache_mode(GetPackagesCacheMode()),
       skip_gms_core_cache(base::CommandLine::ForCurrentProcess()->HasSwitch(
           ash::switches::kArcDisableGmsCoreCache)),
+      skip_tts_cache(base::CommandLine::ForCurrentProcess()->HasSwitch(
+          ash::switches::kArcDisableTtsCache)),
       enable_arc_nearby_share(
           base::FeatureList::IsEnabled(arc::kEnableArcNearbyShare)) {}
 
diff --git a/ash/components/arc/session/arc_upgrade_params.h b/ash/components/arc/session/arc_upgrade_params.h
index 4dc07d9..191b4c6 100644
--- a/ash/components/arc/session/arc_upgrade_params.h
+++ b/ash/components/arc/session/arc_upgrade_params.h
@@ -67,6 +67,10 @@
   // The constructor automatically populates this from command-line.
   bool skip_gms_core_cache;
 
+  // Option to disable TTS cache.
+  // The constructor automatically populates this from command-line.
+  bool skip_tts_cache;
+
   // The supervision transition state for this account. Indicates whether
   // child account should become regular, regular account should become child
   // or neither.
diff --git a/ash/components/arc/session/arc_vm_client_adapter.cc b/ash/components/arc/session/arc_vm_client_adapter.cc
index 00c3413..f09f575c 100644
--- a/ash/components/arc/session/arc_vm_client_adapter.cc
+++ b/ash/components/arc/session/arc_vm_client_adapter.cc
@@ -160,6 +160,8 @@
           static_cast<int>(upgrade_params.management_transition)),
       base::StringPrintf("%s.serialno=%s", prefix.c_str(),
                          serial_number.c_str()),
+      base::StringPrintf("%s.skip_tts_cache=%d", prefix.c_str(),
+                         upgrade_params.skip_tts_cache),
   };
   // Conditionally sets more properties based on |upgrade_params|.
   if (!upgrade_params.locale.empty()) {
diff --git a/ash/components/arc/session/arc_vm_client_adapter_unittest.cc b/ash/components/arc/session/arc_vm_client_adapter_unittest.cc
index a8d6a6e2..29d3b82 100644
--- a/ash/components/arc/session/arc_vm_client_adapter_unittest.cc
+++ b/ash/components/arc/session/arc_vm_client_adapter_unittest.cc
@@ -2544,5 +2544,22 @@
       base::Contains(request.params(), "androidboot.arc.tts.caching=1"));
 }
 
+TEST_F(ArcVmClientAdapterTest, ConvertUpgradeParams_SkipTtsCacheSetup) {
+  StartMiniArc();
+  UpgradeParams upgrade_params = GetPopulatedUpgradeParams();
+  upgrade_params.skip_tts_cache = true;
+  UpgradeArcWithParams(true, std::move(upgrade_params));
+  EXPECT_TRUE(base::Contains(boot_notification_server()->received_data(),
+                             "ro.boot.skip_tts_cache=1"));
+}
+
+TEST_F(ArcVmClientAdapterTest, ConvertUpgradeParams_EnableTtsCacheSetup) {
+  StartMiniArc();
+  UpgradeParams upgrade_params = GetPopulatedUpgradeParams();
+  UpgradeArcWithParams(true, std::move(upgrade_params));
+  EXPECT_TRUE(base::Contains(boot_notification_server()->received_data(),
+                             "ro.boot.skip_tts_cache=0"));
+}
+
 }  // namespace
 }  // namespace arc
diff --git a/ash/constants/ash_switches.cc b/ash/constants/ash_switches.cc
index 9389f09..8137354 100644
--- a/ash/constants/ash_switches.cc
+++ b/ash/constants/ash_switches.cc
@@ -101,6 +101,9 @@
 // apps silently. Used in autotests to resolve racy conditions.
 const char kArcDisablePlayAutoInstall[] = "arc-disable-play-auto-install";
 
+// Used in autotest to disable TTS cache which is on by default.
+const char kArcDisableTtsCache[] = "arc-disable-tts-cache";
+
 // Flag that disables ureadahead completely, including host and guest parts.
 // See also |kArcVmUreadaheadMode|.
 const char kArcDisableUreadahead[] = "arc-disable-ureadahead";
diff --git a/ash/constants/ash_switches.h b/ash/constants/ash_switches.h
index 8604d5a9..dad1b84 100644
--- a/ash/constants/ash_switches.h
+++ b/ash/constants/ash_switches.h
@@ -40,6 +40,7 @@
 COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const char kArcDisableMediaStoreMaintenance[];
 COMPONENT_EXPORT(ASH_CONSTANTS) extern const char kArcDisablePlayAutoInstall[];
+COMPONENT_EXPORT(ASH_CONSTANTS) extern const char kArcDisableTtsCache[];
 COMPONENT_EXPORT(ASH_CONSTANTS) extern const char kArcDisableUreadahead[];
 COMPONENT_EXPORT(ASH_CONSTANTS) extern const char kArcForceShowOptInUi[];
 COMPONENT_EXPORT(ASH_CONSTANTS) extern const char kArcGeneratePlayAutoInstall[];
diff --git a/ash/public/cpp/system/toast_catalog.h b/ash/public/cpp/system/toast_catalog.h
index 0401fcf1f..c4a3277 100644
--- a/ash/public/cpp/system/toast_catalog.h
+++ b/ash/public/cpp/system/toast_catalog.h
@@ -47,7 +47,8 @@
   kDeskTemplateTooLarge = 33,
   kUndoCloseAll = 34,
   kEcheAppToast = 35,
-  kMaxValue = kEcheAppToast,
+  kDeprecateAssistantStylus = 36,
+  kMaxValue = kDeprecateAssistantStylus,
 };
 
 }  // namespace ash
diff --git a/ash/system/ime_menu/ime_menu_tray.cc b/ash/system/ime_menu/ime_menu_tray.cc
index 1a85192d..d15778f 100644
--- a/ash/system/ime_menu/ime_menu_tray.cc
+++ b/ash/system/ime_menu/ime_menu_tray.cc
@@ -398,12 +398,12 @@
   // 2) login/lock screen.
   // 3) password input client.
 
-  bool should_show_buttom_buttoms =
+  const bool should_show_bottom_buttons =
       ime_controller_->is_extra_input_options_enabled() &&
       !ime_controller_->current_ime().third_party && !IsInLoginOrLockScreen() &&
       !IsInPasswordInputContext();
 
-  if (!should_show_buttom_buttoms) {
+  if (!should_show_bottom_buttons) {
     is_emoji_enabled_ = is_handwriting_enabled_ = is_voice_enabled_ = false;
     return false;
   }
diff --git a/ash/system/network/fake_network_detailed_network_view.cc b/ash/system/network/fake_network_detailed_network_view.cc
index 5f2e981..327c92c6 100644
--- a/ash/system/network/fake_network_detailed_network_view.cc
+++ b/ash/system/network/fake_network_detailed_network_view.cc
@@ -8,7 +8,9 @@
 #include "ash/system/network/fake_network_list_wifi_header_view.h"
 #include "ash/system/network/network_detailed_network_view.h"
 #include "ash/system/network/network_list_item_view.h"
+#include "ash/system/network/network_list_mobile_header_view_impl.h"
 #include "ash/system/network/network_list_network_item_view.h"
+#include "ash/system/network/network_list_wifi_header_view_impl.h"
 
 namespace ash {
 
@@ -41,16 +43,16 @@
       new NetworkListNetworkItemView(/*listener=*/nullptr));
 };
 
-NetworkListNetworkHeaderView*
+NetworkListWifiHeaderView*
 FakeNetworkDetailedNetworkView::AddWifiSectionHeader() {
   return network_list_->AddChildView(
       new FakeNetworkListWifiHeaderView(/*delegate=*/nullptr));
 };
 
-NetworkListNetworkHeaderView*
+NetworkListMobileHeaderView*
 FakeNetworkDetailedNetworkView::AddMobileSectionHeader() {
   return network_list_->AddChildView(
       new FakeNetworkListMobileHeaderView(/*delegate=*/nullptr));
-};
+}
 
 }  // namespace ash
\ No newline at end of file
diff --git a/ash/system/network/fake_network_detailed_network_view.h b/ash/system/network/fake_network_detailed_network_view.h
index 324ef2e..26ba724 100644
--- a/ash/system/network/fake_network_detailed_network_view.h
+++ b/ash/system/network/fake_network_detailed_network_view.h
@@ -8,7 +8,9 @@
 #include "ash/ash_export.h"
 #include "ash/system/network/network_detailed_network_view.h"
 #include "ash/system/network/network_list_item_view.h"
+#include "ash/system/network/network_list_mobile_header_view_impl.h"
 #include "ash/system/network/network_list_network_item_view.h"
+#include "ash/system/network/network_list_wifi_header_view_impl.h"
 #include "ui/views/view.h"
 
 namespace ash {
@@ -41,14 +43,14 @@
   void NotifyNetworkListChanged() override;
   views::View* GetAsView() override;
   NetworkListNetworkItemView* AddNetworkListItem() override;
-  NetworkListNetworkHeaderView* AddWifiSectionHeader() override;
-  NetworkListNetworkHeaderView* AddMobileSectionHeader() override;
+  NetworkListWifiHeaderView* AddWifiSectionHeader() override;
+  NetworkListMobileHeaderView* AddMobileSectionHeader() override;
 
   // ViewClickListener:
   void OnViewClicked(views::View* view) override;
 
   std::unique_ptr<views::View> network_list_;
-  size_t notify_network_list_changed_call_count_;
+  size_t notify_network_list_changed_call_count_ = 0;
   NetworkListItemView* last_clicked_network_list_item_ = nullptr;
 };
 
diff --git a/ash/system/network/fake_network_list_mobile_header_view.cc b/ash/system/network/fake_network_list_mobile_header_view.cc
index 4111c96..39833c6 100644
--- a/ash/system/network/fake_network_list_mobile_header_view.cc
+++ b/ash/system/network/fake_network_list_mobile_header_view.cc
@@ -15,10 +15,9 @@
 
 FakeNetworkListMobileHeaderView::~FakeNetworkListMobileHeaderView() = default;
 
-void FakeNetworkListMobileHeaderView::SetToggleState(bool enabled,
-                                                     bool visible) {
+void FakeNetworkListMobileHeaderView::SetToggleState(bool enabled, bool is_on) {
   is_toggle_enabled_ = enabled;
-  is_toggle_visible_ = visible;
+  is_toggle_on_ = is_on;
   set_toggle_state_count_++;
 };
 
diff --git a/ash/system/network/fake_network_list_mobile_header_view.h b/ash/system/network/fake_network_list_mobile_header_view.h
index d8dcbde..b8de2e73 100644
--- a/ash/system/network/fake_network_list_mobile_header_view.h
+++ b/ash/system/network/fake_network_list_mobile_header_view.h
@@ -27,7 +27,7 @@
 
   bool is_toggle_enabled() { return is_toggle_enabled_; }
 
-  bool is_toggle_visible() { return is_toggle_visible_; }
+  bool is_toggle_on() { return is_toggle_on_; }
 
   size_t set_toggle_state_count() { return set_toggle_state_count_; }
 
@@ -41,13 +41,13 @@
 
  private:
   // NetworkListNetworkHeaderView:
-  void SetToggleState(bool enabled, bool visible) override;
+  void SetToggleState(bool enabled, bool is_on) override;
 
   // NetworkListMobileHeaderView:
   void SetAddESimButtonState(bool enabled, bool visible) override;
 
   bool is_toggle_enabled_;
-  bool is_toggle_visible_;
+  bool is_toggle_on_;
   size_t set_toggle_state_count_;
 
   bool is_add_esim_enabled_;
diff --git a/ash/system/network/fake_network_list_wifi_header_view.cc b/ash/system/network/fake_network_list_wifi_header_view.cc
index a0a91d8..5e79e89d 100644
--- a/ash/system/network/fake_network_list_wifi_header_view.cc
+++ b/ash/system/network/fake_network_list_wifi_header_view.cc
@@ -15,9 +15,9 @@
 
 FakeNetworkListWifiHeaderView::~FakeNetworkListWifiHeaderView() = default;
 
-void FakeNetworkListWifiHeaderView::SetToggleState(bool enabled, bool visible) {
+void FakeNetworkListWifiHeaderView::SetToggleState(bool enabled, bool is_on) {
   is_toggle_enabled_ = enabled;
-  is_toggle_visible_ = visible;
+  is_toggle_on_ = is_on;
   set_toggle_state_count_++;
 };
 
diff --git a/ash/system/network/fake_network_list_wifi_header_view.h b/ash/system/network/fake_network_list_wifi_header_view.h
index 7fc1c9a8e..276a9f6 100644
--- a/ash/system/network/fake_network_list_wifi_header_view.h
+++ b/ash/system/network/fake_network_list_wifi_header_view.h
@@ -26,7 +26,7 @@
 
   bool is_toggle_enabled() { return is_toggle_enabled_; }
 
-  bool is_toggle_visible() { return is_toggle_visible_; }
+  bool is_toggle_is_on() { return is_toggle_on_; }
 
   size_t set_toggle_state_count() { return set_toggle_state_count_; }
 
@@ -46,7 +46,7 @@
   void SetJoinWifiButtonState(bool enabled, bool visible) override;
 
   bool is_toggle_enabled_;
-  bool is_toggle_visible_;
+  bool is_toggle_on_;
   size_t set_toggle_state_count_;
 
   bool is_join_wifi_enabled_;
diff --git a/ash/system/network/network_detailed_network_view.h b/ash/system/network/network_detailed_network_view.h
index eb460bb..cbfa170 100644
--- a/ash/system/network/network_detailed_network_view.h
+++ b/ash/system/network/network_detailed_network_view.h
@@ -7,8 +7,10 @@
 
 #include "ash/ash_export.h"
 #include "ash/system/network/network_detailed_view.h"
+#include "ash/system/network/network_list_mobile_header_view_impl.h"
 #include "ash/system/network/network_list_network_header_view.h"
 #include "ash/system/network/network_list_network_item_view.h"
+#include "ash/system/network/network_list_wifi_header_view_impl.h"
 #include "ui/views/view.h"
 
 namespace ash {
@@ -70,12 +72,12 @@
   // Creates, adds and returns a Wifi sticky sub-header to the end of the
   // network list. The client is expected to use the returned pointer for
   // removing and rearranging the sub-header.
-  virtual NetworkListNetworkHeaderView* AddWifiSectionHeader() = 0;
+  virtual NetworkListWifiHeaderView* AddWifiSectionHeader() = 0;
 
   // Creates, adds and returns a Mobile sticky sub-header to the end of the
   // network list. The client is expected to use the returned pointer for
   // removing and rearranging the sub-header.
-  virtual NetworkListNetworkHeaderView* AddMobileSectionHeader() = 0;
+  virtual NetworkListMobileHeaderView* AddMobileSectionHeader() = 0;
 
   // Returns the network list.
   virtual views::View* network_list() = 0;
diff --git a/ash/system/network/network_detailed_network_view_impl.cc b/ash/system/network/network_detailed_network_view_impl.cc
index 3652229..a7623ab 100644
--- a/ash/system/network/network_detailed_network_view_impl.cc
+++ b/ash/system/network/network_detailed_network_view_impl.cc
@@ -41,13 +41,13 @@
       new NetworkListNetworkItemView(/*listener=*/this));
 }
 
-NetworkListNetworkHeaderView*
+NetworkListWifiHeaderView*
 NetworkDetailedNetworkViewImpl::AddWifiSectionHeader() {
   return scroll_content()->AddChildView(
       new NetworkListWifiHeaderViewImpl(/*delegate=*/this));
 }
 
-NetworkListNetworkHeaderView*
+NetworkListMobileHeaderView*
 NetworkDetailedNetworkViewImpl::AddMobileSectionHeader() {
   return scroll_content()->AddChildView(
       new NetworkListMobileHeaderViewImpl(/*delegate=*/this));
diff --git a/ash/system/network/network_detailed_network_view_impl.h b/ash/system/network/network_detailed_network_view_impl.h
index 79487785..d1813e2d 100644
--- a/ash/system/network/network_detailed_network_view_impl.h
+++ b/ash/system/network/network_detailed_network_view_impl.h
@@ -8,8 +8,9 @@
 #include "ash/ash_export.h"
 
 #include "ash/system/network/network_detailed_network_view.h"
-#include "ash/system/network/network_list_network_header_view.h"
+#include "ash/system/network/network_list_mobile_header_view_impl.h"
 #include "ash/system/network/network_list_network_item_view.h"
+#include "ash/system/network/network_list_wifi_header_view_impl.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/views/view.h"
 
@@ -41,8 +42,8 @@
   void NotifyNetworkListChanged() override;
   views::View* GetAsView() override;
   NetworkListNetworkItemView* AddNetworkListItem() override;
-  NetworkListNetworkHeaderView* AddMobileSectionHeader() override;
-  NetworkListNetworkHeaderView* AddWifiSectionHeader() override;
+  NetworkListMobileHeaderView* AddMobileSectionHeader() override;
+  NetworkListWifiHeaderView* AddWifiSectionHeader() override;
   views::View* network_list() override;
 
   // NetworkListNetworkHeaderView::Delegate:
diff --git a/ash/system/network/network_detailed_network_view_unittest.cc b/ash/system/network/network_detailed_network_view_unittest.cc
index 2a9649be..4364f67 100644
--- a/ash/system/network/network_detailed_network_view_unittest.cc
+++ b/ash/system/network/network_detailed_network_view_unittest.cc
@@ -9,8 +9,9 @@
 #include "ash/constants/ash_features.h"
 #include "ash/system/network/network_detailed_network_view.h"
 #include "ash/system/network/network_detailed_view.h"
-#include "ash/system/network/network_list_network_header_view.h"
+#include "ash/system/network/network_list_mobile_header_view.h"
 #include "ash/system/network/network_list_network_item_view.h"
+#include "ash/system/network/network_list_wifi_header_view.h"
 #include "ash/system/tray/detailed_view_delegate.h"
 #include "ash/test/ash_test_base.h"
 #include "base/test/scoped_feature_list.h"
@@ -138,11 +139,11 @@
     network_detailed_network_view()->NotifyNetworkListChanged();
   }
 
-  NetworkListNetworkHeaderView* AddWifiSectionHeader() {
+  NetworkListWifiHeaderView* AddWifiSectionHeader() {
     return network_detailed_network_view()->AddWifiSectionHeader();
   }
 
-  NetworkListNetworkHeaderView* AddMobileSectionHeader() {
+  NetworkListMobileHeaderView* AddMobileSectionHeader() {
     return network_detailed_network_view()->AddMobileSectionHeader();
   }
 
@@ -180,11 +181,11 @@
   ASSERT_NE(nullptr, network_list_item);
   EXPECT_STREQ("NetworkListNetworkItemView", network_list_item->GetClassName());
 
-  NetworkListNetworkHeaderView* wifi_section = AddWifiSectionHeader();
+  NetworkListWifiHeaderView* wifi_section = AddWifiSectionHeader();
   ASSERT_NE(nullptr, wifi_section);
   EXPECT_STREQ("NetworkListWifiHeaderView", wifi_section->GetClassName());
 
-  NetworkListNetworkHeaderView* mobile_section = AddMobileSectionHeader();
+  NetworkListMobileHeaderView* mobile_section = AddMobileSectionHeader();
   ASSERT_NE(nullptr, mobile_section);
   EXPECT_STREQ("NetworkListMobileHeaderView", mobile_section->GetClassName());
 }
diff --git a/ash/system/network/network_list_mobile_header_view_impl.cc b/ash/system/network/network_list_mobile_header_view_impl.cc
index 5e418be..685e727 100644
--- a/ash/system/network/network_list_mobile_header_view_impl.cc
+++ b/ash/system/network/network_list_mobile_header_view_impl.cc
@@ -83,7 +83,8 @@
       IconButton::Type::kSmall, &icon, GetAddESimTooltipMessageId());
   add_esim_button.get()->SetID(kAddESimButtonId);
   add_esim_button_ = add_esim_button.get();
-  container()->AddView(TriView::Container::END, add_esim_button.release());
+  container()->AddViewAt(TriView::Container::END, add_esim_button.release(),
+                         /*index=*/0);
 };
 
 void NetworkListMobileHeaderViewImpl::OnToggleToggled(bool is_on) {
diff --git a/ash/system/network/network_list_network_header_view.cc b/ash/system/network/network_list_network_header_view.cc
index 2f5e1409..97379f91 100644
--- a/ash/system/network/network_list_network_header_view.cc
+++ b/ash/system/network/network_list_network_header_view.cc
@@ -13,6 +13,7 @@
 #include "ash/system/tray/tray_popup_utils.h"
 #include "ash/system/tray/tray_toggle_button.h"
 #include "ash/system/tray/tri_view.h"
+#include "base/memory/weak_ptr.h"
 #include "ui/views/view.h"
 
 namespace ash {
@@ -22,14 +23,17 @@
     : NetworkListHeaderView(label_id),
       model_(Shell::Get()->system_tray_model()->network_state_model()),
       delegate_(delegate) {
-  toggle_ = new TrayToggleButton(
+  std::unique_ptr<TrayToggleButton> toggle = std::make_unique<TrayToggleButton>(
       base::BindRepeating(&NetworkListNetworkHeaderView::ToggleButtonPressed,
-                          base::Unretained(this)),
+                          weak_factory_.GetWeakPtr()),
       label_id);
-  toggle_->SetID(kToggleButtonId);
-  container()->AddView(TriView::Container::END, toggle_);
+  toggle->SetID(kToggleButtonId);
+  toggle_ = toggle.get();
+  container()->AddView(TriView::Container::END, toggle.release());
 }
 
+NetworkListNetworkHeaderView::~NetworkListNetworkHeaderView() = default;
+
 void NetworkListNetworkHeaderView::SetToggleState(bool enabled, bool is_on) {
   toggle_->SetEnabled(enabled);
   toggle_->SetAcceptsEvents(enabled);
diff --git a/ash/system/network/network_list_network_header_view.h b/ash/system/network/network_list_network_header_view.h
index 81d7df1..04de5cf 100644
--- a/ash/system/network/network_list_network_header_view.h
+++ b/ash/system/network/network_list_network_header_view.h
@@ -9,6 +9,7 @@
 #include "ash/system/network/network_list_header_view.h"
 #include "ash/system/network/network_row_title_view.h"
 #include "ash/system/tray/tri_view.h"
+#include "base/memory/weak_ptr.h"
 #include "ui/views/controls/button/toggle_button.h"
 #include "ui/views/view.h"
 
@@ -34,10 +35,12 @@
   NetworkListNetworkHeaderView(const NetworkListNetworkHeaderView&) = delete;
   NetworkListNetworkHeaderView& operator=(const NetworkListNetworkHeaderView&) =
       delete;
-  ~NetworkListNetworkHeaderView() override = default;
+  ~NetworkListNetworkHeaderView() override;
 
   virtual void SetToggleState(bool enabled, bool is_on);
 
+  void SetToggleVisibility(bool visible);
+
  protected:
   virtual void AddExtraButtons();
 
@@ -45,13 +48,10 @@
   // enabled/disable their respective technology.
   virtual void OnToggleToggled(bool is_on);
 
-  void SetToggleVisibility(bool visible);
-
   Delegate* delegate() const { return delegate_; };
 
   TrayNetworkStateModel* model() { return model_; }
 
- protected:
   // Used for testing.
   static constexpr int kToggleButtonId =
       NetworkListHeaderView::kTitleLabelViewId + 1;
@@ -60,6 +60,7 @@
   friend class NetworkListNetworkHeaderViewTest;
   friend class NetworkListMobileHeaderViewTest;
   friend class NetworkListWifiHeaderViewTest;
+  friend class NetworkListViewControllerTest;
 
   void ToggleButtonPressed();
 
@@ -69,6 +70,8 @@
   views::ToggleButton* toggle_ = nullptr;
 
   Delegate* delegate_ = nullptr;
+
+  base::WeakPtrFactory<NetworkListNetworkHeaderView> weak_factory_{this};
 };
 
 }  // namespace ash
diff --git a/ash/system/network/network_list_network_item_view.h b/ash/system/network/network_list_network_item_view.h
index e44034a1..ff182d4 100644
--- a/ash/system/network/network_list_network_item_view.h
+++ b/ash/system/network/network_list_network_item_view.h
@@ -30,14 +30,14 @@
       delete;
   ~NetworkListNetworkItemView() override;
 
- private:
-  friend class NetworkListNetworkItemViewTest;
-
   // NetworkListItemView:
   void UpdateViewForNetwork(
       const chromeos::network_config::mojom::NetworkStatePropertiesPtr&
           network_properties) override;
 
+ private:
+  friend class NetworkListNetworkItemViewTest;
+
   void SetupCellularSubtext();
   void SetupNetworkSubtext();
   void UpdateDisabledTextColor();
diff --git a/ash/system/network/network_list_view_controller_impl.cc b/ash/system/network/network_list_view_controller_impl.cc
index c24d64f..1a4cc693 100644
--- a/ash/system/network/network_list_view_controller_impl.cc
+++ b/ash/system/network/network_list_view_controller_impl.cc
@@ -5,19 +5,120 @@
 #include "ash/system/network/network_list_view_controller_impl.h"
 
 #include "ash/constants/ash_features.h"
+#include "ash/public/cpp/bluetooth_config_service.h"
+#include "ash/resources/vector_icons/vector_icons.h"
+#include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
+#include "ash/strings/grit/ash_strings.h"
+#include "ash/style/ash_color_provider.h"
 #include "ash/system/model/system_tray_model.h"
-#include "ash/system/network/network_detailed_network_view.h"
+#include "ash/system/network/network_detailed_network_view_impl.h"
+#include "ash/system/network/network_list_mobile_header_view.h"
+#include "ash/system/network/network_list_network_header_view.h"
+#include "ash/system/network/network_list_network_item_view.h"
 #include "ash/system/network/tray_network_state_model.h"
+#include "ash/system/tray/tray_info_label.h"
+#include "ash/system/tray/tri_view.h"
+#include "chromeos/dbus/hermes/hermes_manager_client.h"
+#include "chromeos/services/network_config/public/cpp/cros_network_config_util.h"
+#include "chromeos/services/network_config/public/mojom/cros_network_config.mojom.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/gfx/image/image_skia_operations.h"
+#include "ui/gfx/paint_vector_icon.h"
+#include "ui/views/controls/image_view.h"
+#include "ui/views/controls/separator.h"
 
 namespace ash {
+namespace {
+
+using chromeos::network_config::NetworkTypeMatchesType;
+using chromeos::network_config::StateIsConnected;
+
+using chromeos::network_config::mojom::DeviceStateProperties;
+using chromeos::network_config::mojom::DeviceStateType;
+using chromeos::network_config::mojom::FilterType;
+using chromeos::network_config::mojom::GlobalPolicy;
+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::ProxyMode;
+
+using chromeos::bluetooth_config::mojom::BluetoothSystemPropertiesPtr;
+using chromeos::bluetooth_config::mojom::BluetoothSystemState;
+
+// Helper function to remove |*view| from its view hierarchy, delete the view,
+// and reset the value of |*view| to be |nullptr|.
+template <class T>
+void RemoveAndResetViewIfExists(T** view) {
+  DCHECK(view);
+
+  if (!*view)
+    return;
+
+  views::View* parent = (*view)->parent();
+
+  if (parent) {
+    parent->RemoveChildViewT(*view);
+    *view = nullptr;
+  }
+}
+
+bool IsSecondaryUser() {
+  SessionControllerImpl* session_controller =
+      Shell::Get()->session_controller();
+  return session_controller->IsActiveUserSessionStarted() &&
+         !session_controller->IsUserPrimary();
+}
+
+bool IsCellularDeviceInhibited() {
+  const DeviceStateProperties* cellular_device =
+      Shell::Get()->system_tray_model()->network_state_model()->GetDevice(
+          NetworkType::kCellular);
+  if (!cellular_device)
+    return false;
+  return cellular_device->inhibit_reason !=
+         chromeos::network_config::mojom::InhibitReason::kNotInhibited;
+}
+
+bool IsESimSupported() {
+  const DeviceStateProperties* cellular_device =
+      Shell::Get()->system_tray_model()->network_state_model()->GetDevice(
+          NetworkType::kCellular);
+
+  if (!cellular_device || !cellular_device->sim_infos)
+    return false;
+
+  // Check both the SIM slot infos and the number of EUICCs because the former
+  // comes from Shill and the latter from Hermes, and so there may be instances
+  // where one may be true while they other isn't.
+  if (chromeos::HermesManagerClient::Get()->GetAvailableEuiccs().empty())
+    return false;
+
+  for (const auto& sim_info : *cellular_device->sim_infos) {
+    if (!sim_info->eid.empty())
+      return true;
+  }
+  return false;
+}
+
+}  // namespace
 
 NetworkListViewControllerImpl::NetworkListViewControllerImpl(
     NetworkDetailedNetworkView* network_detailed_network_view)
-    : network_detailed_network_view_(network_detailed_network_view) {
+    : model_(Shell::Get()->system_tray_model()->network_state_model()),
+      network_detailed_network_view_(network_detailed_network_view) {
   DCHECK(ash::features::IsQuickSettingsNetworkRevampEnabled());
+  DCHECK(ash::features::IsBluetoothRevampEnabled());
   DCHECK(network_detailed_network_view_);
   Shell::Get()->system_tray_model()->network_state_model()->AddObserver(this);
+
+  GetBluetoothConfigService(
+      remote_cros_bluetooth_config_.BindNewPipeAndPassReceiver());
+  remote_cros_bluetooth_config_->ObserveSystemProperties(
+      cros_system_properties_observer_receiver_.BindNewPipeAndPassRemote());
+
+  GetNetworkStateList();
 }
 
 NetworkListViewControllerImpl::~NetworkListViewControllerImpl() {
@@ -26,15 +127,419 @@
 }
 
 void NetworkListViewControllerImpl::ActiveNetworkStateChanged() {
-  // TODO(b/207089013): Implement this function.
+  GetNetworkStateList();
 }
 
 void NetworkListViewControllerImpl::NetworkListChanged() {
-  // TODO(b/207089013): Implement this function.
+  GetNetworkStateList();
 }
 
-void NetworkListViewControllerImpl::DeviceStateListChanged() {
-  // TODO(b/207089013): Implement this function.
+void NetworkListViewControllerImpl::GlobalPolicyChanged() {
+  UpdateMobileSectionHeader();
+};
+
+void NetworkListViewControllerImpl::OnPropertiesUpdated(
+    BluetoothSystemPropertiesPtr properties) {
+  if (bluetooth_system_state_ == properties->system_state)
+    return;
+
+  bluetooth_system_state_ = properties->system_state;
+  UpdateMobileSectionHeader();
+}
+
+void NetworkListViewControllerImpl::GetNetworkStateList() {
+  model()->cros_network_config()->GetNetworkStateList(
+      NetworkFilter::New(FilterType::kVisible, NetworkType::kAll,
+                         chromeos::network_config::mojom::kNoLimit),
+      base::BindOnce(&NetworkListViewControllerImpl::OnGetNetworkStateList,
+                     weak_ptr_factory_.GetWeakPtr()));
+}
+
+void NetworkListViewControllerImpl::OnGetNetworkStateList(
+    std::vector<NetworkStatePropertiesPtr> networks) {
+  // Indicates the current position a view will be added to in
+  // NetworkDetailedNetworkView scroll list.
+  int index = 0;
+
+  // Store current views in |previous_network_views|, views which have
+  // a corresponding network in |networks| will be added back to
+  // |network_id_to_view_map_| any remaining views in |previous_network_views|
+  // would be deleted.
+  NetworkIdToViewMap previous_network_views =
+      std::move(network_id_to_view_map_);
+  network_id_to_view_map_.clear();
+
+  UpdateNetworkTypeExistence(networks);
+
+  // Show a warning that the connection might be monitored if connected to a VPN
+  // or if the default network has a proxy installed.
+  index = ShowConnectionWarningIfVpnOrProxy(index);
+
+  if (ShouldMobileDataSectionBeShown()) {
+    // Add separator if mobile section is not the first view child, else
+    // delete unused separator.
+    if (index > 0) {
+      if (!mobile_separator_view_)
+        CreateMobileSeparator();
+      network_detailed_network_view()->network_list()->ReorderChildView(
+          mobile_separator_view_, index++);
+    } else {
+      RemoveAndResetViewIfExists(&mobile_separator_view_);
+    }
+
+    if (!mobile_header_view_) {
+      mobile_header_view_ =
+          network_detailed_network_view()->AddMobileSectionHeader();
+      mobile_header_view_->SetID(static_cast<int>(
+          NetworkListViewControllerViewChildId::kMobileSectionHeader));
+    }
+
+    UpdateMobileSectionHeader();
+
+    network_detailed_network_view()->network_list()->ReorderChildView(
+        mobile_header_view_, index++);
+
+    index = CreateItemViewsIfMissingAndReorder(
+        NetworkType::kMobile, index, networks, &previous_network_views);
+
+    // Add mobile status message to NetworkDetailedNetworkView scroll list if it
+    // exist.
+    if (mobile_status_message_) {
+      network_detailed_network_view()->network_list()->ReorderChildView(
+          mobile_status_message_, index++);
+    }
+
+  } else {
+    RemoveAndResetViewIfExists(&mobile_header_view_);
+    RemoveAndResetViewIfExists(&mobile_separator_view_);
+  }
+
+  // Remaining views in |previous_network_views| are no longer needed
+  // and should be deleted.
+  for (const auto& id_and_view : previous_network_views) {
+    network_detailed_network_view()->network_list()->RemoveChildViewT(
+        id_and_view.second);
+  }
+
+  FocusLastSelectedView();
+  network_detailed_network_view()->NotifyNetworkListChanged();
+}
+
+void NetworkListViewControllerImpl::UpdateNetworkTypeExistence(
+    const std::vector<NetworkStatePropertiesPtr>& networks) {
+  has_mobile_networks_ = false;
+  is_vpn_connected_ = false;
+
+  for (auto& network : networks) {
+    if (NetworkTypeMatchesType(network->type, NetworkType::kMobile)) {
+      has_mobile_networks_ = true;
+    } else if (NetworkTypeMatchesType(network->type, NetworkType::kVPN) &&
+               StateIsConnected(network->connection_state)) {
+      is_vpn_connected_ = true;
+    }
+  }
+
+  is_mobile_network_enabled_ =
+      model()->GetDeviceState(NetworkType::kCellular) ==
+          DeviceStateType::kEnabled ||
+      model()->GetDeviceState(NetworkType::kTether) ==
+          DeviceStateType::kEnabled;
+}
+
+int NetworkListViewControllerImpl::ShowConnectionWarningIfVpnOrProxy(
+    int index) {
+  const NetworkStateProperties* default_network = model()->default_network();
+  bool using_proxy =
+      default_network && default_network->proxy_mode != ProxyMode::kDirect;
+
+  if (is_vpn_connected_ || using_proxy) {
+    if (!connection_warning_)
+      ShowConnectionWarning();
+
+    network_detailed_network_view()->network_list()->ReorderChildView(
+        connection_warning_, index++);
+  } else if (!is_vpn_connected_ && !using_proxy) {
+    RemoveAndResetViewIfExists(&connection_warning_);
+  }
+
+  return index;
+}
+
+bool NetworkListViewControllerImpl::ShouldMobileDataSectionBeShown() {
+  // The section should always be shown if Cellular networks are available.
+  if (model()->GetDeviceState(NetworkType::kCellular) !=
+      DeviceStateType::kUnavailable) {
+    return true;
+  }
+
+  const DeviceStateType tether_state =
+      model()->GetDeviceState(NetworkType::kTether);
+
+  // Hide the section if both Cellular and Tether are UNAVAILABLE.
+  if (tether_state == DeviceStateType::kUnavailable)
+    return false;
+
+  // Hide the section if Tether is PROHIBITED.
+  if (tether_state == DeviceStateType::kProhibited)
+    return false;
+
+  // Secondary users cannot enable Bluetooth, and Tether is only UNINITIALIZED
+  // if Bluetooth is disabled. Hide the section in this case.
+  if (tether_state == DeviceStateType::kUninitialized && IsSecondaryUser())
+    return false;
+
+  return true;
+}
+
+void NetworkListViewControllerImpl::CreateMobileSeparator() {
+  DCHECK(!mobile_separator_view_);
+
+  std::unique_ptr<views::Separator> mobile_separator_view =
+      base::WrapUnique(TrayPopupUtils::CreateListSubHeaderSeparator());
+  mobile_separator_view->SetID(
+      static_cast<int>(NetworkListViewControllerViewChildId::kMobileSeperator));
+  mobile_separator_view_ =
+      network_detailed_network_view()->network_list()->AddChildView(
+          std::move(mobile_separator_view));
+}
+
+void NetworkListViewControllerImpl::UpdateMobileSectionHeader() {
+  if (!mobile_header_view_)
+    return;
+
+  const bool is_add_esim_enabled =
+      is_mobile_network_enabled_ && !IsCellularDeviceInhibited();
+
+  bool is_add_esim_visible = IsESimSupported();
+  const GlobalPolicy* global_policy = model()->global_policy();
+
+  // Adding new cellular networks is disallowed when only policy cellular
+  // networks are allowed by admin.
+  if (ash::features::IsESimPolicyEnabled() &&
+      (!global_policy || global_policy->allow_only_policy_cellular_networks)) {
+    is_add_esim_visible = false;
+  }
+
+  mobile_header_view_->SetAddESimButtonState(/*enabled=*/is_add_esim_enabled,
+                                             /*visible=*/is_add_esim_visible);
+
+  UpdateMobileToggleAndSetStatusMessage();
+}
+
+void NetworkListViewControllerImpl::UpdateMobileToggleAndSetStatusMessage() {
+  if (!mobile_header_view_)
+    return;
+
+  const DeviceStateType cellular_state =
+      model()->GetDeviceState(NetworkType::kCellular);
+  const DeviceStateType tether_state =
+      model()->GetDeviceState(NetworkType::kTether);
+
+  const bool is_secondary_user = IsSecondaryUser();
+
+  if (cellular_state == DeviceStateType::kUninitialized) {
+    CreateMobileInfoLabelIfMissingAndUpdate(
+        IDS_ASH_STATUS_TRAY_INITIALIZING_CELLULAR);
+    mobile_header_view_->SetToggleVisibility(/*visible=*/false);
+    return;
+  }
+
+  if (cellular_state != DeviceStateType::kUnavailable) {
+    if (IsCellularDeviceInhibited()) {
+      // When a device is inhibited, it cannot process any new operations. Thus,
+      // keep the toggle on to show users that the device is active, but set it
+      // to be disabled to make it clear that users cannot update it until it
+      // becomes uninhibited.
+      mobile_header_view_->SetToggleVisibility(/*visible=*/true);
+      mobile_header_view_->SetToggleState(/*enabled=*/false,
+                                          /*is_on=*/true);
+      RemoveAndResetViewIfExists(&mobile_status_message_);
+      return;
+    }
+
+    const bool toggle_enabled =
+        !is_secondary_user && (cellular_state == DeviceStateType::kEnabled ||
+                               cellular_state == DeviceStateType::kDisabled);
+    const bool cellular_enabled = cellular_state == DeviceStateType::kEnabled;
+    mobile_header_view_->SetToggleVisibility(/*visibility=*/true);
+    mobile_header_view_->SetToggleState(/*enabled=*/toggle_enabled,
+                                        /*is_on=*/cellular_enabled);
+
+    if (cellular_state == DeviceStateType::kDisabling) {
+      CreateMobileInfoLabelIfMissingAndUpdate(
+          IDS_ASH_STATUS_TRAY_NETWORK_MOBILE_DISABLING);
+      return;
+    }
+
+    if (cellular_enabled) {
+      if (has_mobile_networks_) {
+        RemoveAndResetViewIfExists(&mobile_status_message_);
+        return;
+      }
+
+      CreateMobileInfoLabelIfMissingAndUpdate(
+          IDS_ASH_STATUS_TRAY_NO_MOBILE_NETWORKS);
+      return;
+    }
+
+    CreateMobileInfoLabelIfMissingAndUpdate(
+        IDS_ASH_STATUS_TRAY_NETWORK_MOBILE_DISABLED);
+    return;
+  }
+
+  // When Cellular is not available, always show the toggle.
+  mobile_header_view_->SetToggleVisibility(/*visibility=*/true);
+
+  // Otherwise, toggle state and status message reflect Tether.
+  if (tether_state == DeviceStateType::kUninitialized) {
+    if (bluetooth_system_state_ == BluetoothSystemState::kEnabling) {
+      mobile_header_view_->SetToggleState(/*toggle_enabled=*/false,
+                                          /*is_on=*/true);
+      CreateMobileInfoLabelIfMissingAndUpdate(
+          IDS_ASH_STATUS_TRAY_INITIALIZING_CELLULAR);
+      return;
+    }
+    mobile_header_view_->SetToggleState(
+        /*toggle_enabled=*/!is_secondary_user, /*is_on=*/false);
+    CreateMobileInfoLabelIfMissingAndUpdate(
+        IDS_ASH_STATUS_TRAY_ENABLING_MOBILE_ENABLES_BLUETOOTH);
+    return;
+  }
+
+  const bool tether_enabled = tether_state == DeviceStateType::kEnabled;
+
+  // Ensure that the toggle state and status message match the tether state.
+  mobile_header_view_->SetToggleState(/*toggle_enabled=*/!is_secondary_user,
+                                      /*is_on=*/tether_enabled);
+  if (tether_enabled && !has_mobile_networks_) {
+    CreateMobileInfoLabelIfMissingAndUpdate(
+        IDS_ASH_STATUS_TRAY_NO_MOBILE_DEVICES_FOUND);
+    return;
+  }
+
+  RemoveAndResetViewIfExists(&mobile_status_message_);
+}
+
+void NetworkListViewControllerImpl::CreateMobileInfoLabelIfMissingAndUpdate(
+    int message_id) {
+  DCHECK(message_id);
+
+  if (mobile_status_message_) {
+    mobile_status_message_->Update(message_id);
+    return;
+  }
+
+  std::unique_ptr<TrayInfoLabel> info =
+      std::make_unique<TrayInfoLabel>(message_id);
+  info->SetID(static_cast<int>(
+      NetworkListViewControllerViewChildId::kMobileStatusMessage));
+  mobile_status_message_ =
+      network_detailed_network_view()->network_list()->AddChildView(
+          std::move(info));
+}
+
+int NetworkListViewControllerImpl::CreateItemViewsIfMissingAndReorder(
+    NetworkType type,
+    int index,
+    std::vector<NetworkStatePropertiesPtr>& networks,
+    NetworkIdToViewMap* previous_views) {
+  NetworkIdToViewMap id_to_view_map;
+  NetworkListNetworkItemView* network_view = nullptr;
+
+  for (const auto& network : networks) {
+    if (!NetworkTypeMatchesType(network->type, type))
+      continue;
+
+    const std::string& network_id = network->guid;
+    auto it = previous_views->find(network_id);
+
+    if (it == previous_views->end()) {
+      network_view = network_detailed_network_view()->AddNetworkListItem();
+    } else {
+      network_view = it->second;
+      previous_views->erase(it);
+    }
+    network_id_to_view_map_.emplace(network_id, network_view);
+
+    network_view->UpdateViewForNetwork(network);
+    network_detailed_network_view()->network_list()->ReorderChildView(
+        network_view, index);
+
+    // Increment |index| since this position was taken by |network_view|.
+    index++;
+  }
+
+  return index;
+}
+
+void NetworkListViewControllerImpl::ShowConnectionWarning() {
+  // Set up layout and apply sticky row property.
+  std::unique_ptr<TriView> connection_warning(
+      TrayPopupUtils::CreateDefaultRowView());
+  TrayPopupUtils::ConfigureAsStickyHeader(connection_warning.get());
+
+  // Set 'info' icon on left side.
+  std::unique_ptr<views::ImageView> image_view =
+      base::WrapUnique(TrayPopupUtils::CreateMainImageView());
+  image_view->SetImage(gfx::CreateVectorIcon(
+      kSystemMenuInfoIcon,
+      AshColorProvider::Get()->GetContentLayerColor(
+          AshColorProvider::ContentLayerType::kIconColorPrimary)));
+  image_view->SetBackground(views::CreateSolidBackground(SK_ColorTRANSPARENT));
+  connection_warning->AddView(TriView::Container::START, image_view.release());
+
+  // Set message label in middle of row.
+  std::unique_ptr<views::Label> label =
+      base::WrapUnique(TrayPopupUtils::CreateDefaultLabel());
+  label->SetText(
+      l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_NETWORK_MONITORED_WARNING));
+  label->SetBackground(views::CreateSolidBackground(SK_ColorTRANSPARENT));
+  label->SetEnabledColor(AshColorProvider::Get()->GetContentLayerColor(
+      AshColorProvider::ContentLayerType::kTextColorPrimary));
+  TrayPopupUtils::SetLabelFontList(
+      label.get(), TrayPopupUtils::FontStyle::kDetailedViewLabel);
+  label->SetID(static_cast<int>(
+      NetworkListViewControllerViewChildId::kConnectionWarningLabel));
+
+  connection_warning->AddView(TriView::Container::CENTER, label.release());
+  connection_warning->SetContainerBorder(
+      TriView::Container::CENTER, views::CreateEmptyBorder(gfx::Insets::TLBR(
+                                      0, 0, 0, kTrayPopupLabelRightPadding)));
+
+  // Nothing to the right of the text.
+  connection_warning->SetContainerVisible(TriView::Container::END, false);
+
+  connection_warning->SetID(static_cast<int>(
+      NetworkListViewControllerViewChildId::kConnectionWarning));
+  connection_warning_ =
+      network_detailed_network_view()->network_list()->AddChildView(
+          std::move(connection_warning));
+}
+
+void NetworkListViewControllerImpl::FocusLastSelectedView() {
+  views::View* selected_view = nullptr;
+  views::View* parent_view = network_detailed_network_view()->network_list();
+  for (const auto& [network_id, view] : network_id_to_view_map_) {
+    // The within_bounds check is necessary when the network list goes beyond
+    // the visible area (i.e. scrolling) and the mouse is below the tray pop-up.
+    // The items not in view in the tray pop-up keep going down and have
+    // View::GetVisibility() == true but they are masked and not seen by the
+    // user. When the mouse is below the list where the item would be if the
+    // list continued downward, IsMouseHovered() is true and this will trigger
+    // an incorrect programmatic scroll if we don't stop it. The bounds check
+    // ensures the view is actually visible within the tray pop-up.
+    bool within_bounds =
+        parent_view->GetBoundsInScreen().Intersects(view->GetBoundsInScreen());
+    if (within_bounds && view->IsMouseHovered()) {
+      selected_view = view;
+      break;
+    }
+  }
+
+  parent_view->SizeToPreferredSize();
+  parent_view->Layout();
+  if (selected_view)
+    parent_view->ScrollRectToVisible(selected_view->bounds());
 }
 
 }  // namespace ash
diff --git a/ash/system/network/network_list_view_controller_impl.h b/ash/system/network/network_list_view_controller_impl.h
index fca975b..704462e 100644
--- a/ash/system/network/network_list_view_controller_impl.h
+++ b/ash/system/network/network_list_view_controller_impl.h
@@ -7,17 +7,31 @@
 
 #include "ash/ash_export.h"
 
+#include "ash/system/network/network_detailed_network_view_impl.h"
+#include "ash/system/network/network_list_mobile_header_view.h"
+#include "ash/system/network/network_list_network_header_view.h"
+#include "ash/system/network/network_list_network_item_view.h"
 #include "ash/system/network/network_list_view_controller.h"
 #include "ash/system/network/tray_network_state_observer.h"
+#include "ash/system/tray/tray_info_label.h"
+#include "ash/system/tray/tray_popup_utils.h"
+#include "ash/system/tray/tray_utils.h"
+#include "ash/system/tray/tri_view.h"
+#include "chromeos/services/bluetooth_config/public/mojom/cros_bluetooth_config.mojom.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "ui/views/controls/separator.h"
 
 namespace ash {
 
 class NetworkDetailedNetworkView;
 
-// Implementation of NetworkListViewController
+// Implementation of NetworkListViewController.
 class ASH_EXPORT NetworkListViewControllerImpl
     : public TrayNetworkStateObserver,
-      public NetworkListViewController {
+      public NetworkListViewController,
+      public chromeos::bluetooth_config::mojom::SystemPropertiesObserver {
  public:
   NetworkListViewControllerImpl(
       NetworkDetailedNetworkView* network_detailed_network_view);
@@ -27,17 +41,110 @@
   ~NetworkListViewControllerImpl() override;
 
  protected:
+  TrayNetworkStateModel* model() { return model_; }
+
   NetworkDetailedNetworkView* network_detailed_network_view() {
     return network_detailed_network_view_;
   }
 
  private:
+  friend class NetworkListViewControllerTest;
+  friend class FakeNetworkDetailedNetworkView;
+
+  // Used for testing. Starts at 1 because 0 is default view id.
+  enum class NetworkListViewControllerViewChildId {
+    kConnectionWarning = 1,
+    kConnectionWarningLabel = 2,
+    kMobileSeperator = 3,
+    kMobileStatusMessage = 4,
+    kMobileSectionHeader = 5,
+  };
+
+  // Map of network guids and their corresponding list item views.
+  using NetworkIdToViewMap =
+      base::flat_map<std::string, NetworkListNetworkItemView*>;
+
   // TrayNetworkStateObserver:
   void ActiveNetworkStateChanged() override;
   void NetworkListChanged() override;
-  void DeviceStateListChanged() override;
+  void GlobalPolicyChanged() override;
+
+  // chromeos::bluetooth_config::mojom::SystemPropertiesObserver:
+  void OnPropertiesUpdated(
+      chromeos::bluetooth_config::mojom::BluetoothSystemPropertiesPtr
+          properties) override;
+
+  // Called to initialize views and when network list is recently updated.
+  void GetNetworkStateList();
+  void OnGetNetworkStateList(
+      std::vector<chromeos::network_config::mojom::NetworkStatePropertiesPtr>
+          networks);
+
+  // Checks |networks| and caches whether mobile network exist in the list
+  // of |networks|. Also caches if a Mobile networks are enabled.
+  void UpdateNetworkTypeExistence(
+      const std::vector<
+          chromeos::network_config::mojom::NetworkStatePropertiesPtr>&
+          networks);
+
+  // Adds a warning indicator if connected to a VPN or if the default network
+  // has a proxy installed.
+  int ShowConnectionWarningIfVpnOrProxy(int index);
+
+  // Returns true if mobile data section should be added to view.
+  bool ShouldMobileDataSectionBeShown();
+
+  // Creates and adds a Mobile separator to the view.
+  void CreateMobileSeparator();
+
+  // Updates mobile data section, updates add eSIM button states and
+  // calls UpdateMobileToggleAndSetStatusMessage().
+  void UpdateMobileSectionHeader();
+
+  // Updated mobile data toggle states and sets mobile data status message.
+  void UpdateMobileToggleAndSetStatusMessage();
+  void CreateMobileInfoLabelIfMissingAndUpdate(int message_id);
+
+  // Creates a NetworkListNetworkItem if it does not exist else uses the
+  // existing view, also reorders it in NetworkDetailedNetworkView scroll list.
+  int CreateItemViewsIfMissingAndReorder(
+      chromeos::network_config::mojom::NetworkType type,
+      int index,
+      std::vector<chromeos::network_config::mojom::NetworkStatePropertiesPtr>&
+          networks,
+      NetworkIdToViewMap* previous_views);
+
+  // Creates a view that indicates connections might be monitored if
+  // connected to a VPN or if the default network has a proxy installed.
+  void ShowConnectionWarning();
+
+  // Focuses on last selected view in NetworkDetailedNetworkView scroll list.
+  void FocusLastSelectedView();
+
+  TrayNetworkStateModel* model_;
+
+  mojo::Remote<chromeos::bluetooth_config::mojom::CrosBluetoothConfig>
+      remote_cros_bluetooth_config_;
+  mojo::Receiver<chromeos::bluetooth_config::mojom::SystemPropertiesObserver>
+      cros_system_properties_observer_receiver_{this};
+
+  chromeos::bluetooth_config::mojom::BluetoothSystemState
+      bluetooth_system_state_ =
+          chromeos::bluetooth_config::mojom::BluetoothSystemState::kUnavailable;
+
+  TrayInfoLabel* mobile_status_message_ = nullptr;
+  NetworkListMobileHeaderView* mobile_header_view_ = nullptr;
+  views::Separator* mobile_separator_view_ = nullptr;
+  TriView* connection_warning_ = nullptr;
+
+  bool has_mobile_networks_;
+  bool is_vpn_connected_;
+  bool is_mobile_network_enabled_;
 
   NetworkDetailedNetworkView* network_detailed_network_view_;
+  NetworkIdToViewMap network_id_to_view_map_;
+
+  base::WeakPtrFactory<NetworkListViewControllerImpl> weak_ptr_factory_{this};
 };
 
 }  // namespace ash
diff --git a/ash/system/network/network_list_view_controller_unittest.cc b/ash/system/network/network_list_view_controller_unittest.cc
index 2e5f2421..75252ee 100644
--- a/ash/system/network/network_list_view_controller_unittest.cc
+++ b/ash/system/network/network_list_view_controller_unittest.cc
@@ -7,15 +7,126 @@
 #include <memory>
 
 #include "ash/constants/ash_features.h"
+#include "ash/session/session_controller_impl.h"
+#include "ash/shell.h"
+#include "ash/strings/grit/ash_strings.h"
+#include "ash/system/model/system_tray_model.h"
 #include "ash/system/network/fake_network_detailed_network_view.h"
+#include "ash/system/network/fake_network_list_mobile_header_view.h"
+#include "ash/system/network/tray_network_state_model.h"
+#include "ash/system/tray/tray_info_label.h"
+#include "ash/system/tray/tri_view.h"
 #include "ash/test/ash_test_base.h"
+#include "ash/test/ash_test_helper.h"
+#include "base/run_loop.h"
+#include "base/test/bind.h"
 #include "base/test/scoped_feature_list.h"
+#include "chromeos/network/mock_managed_network_configuration_handler.h"
+#include "chromeos/network/network_state_handler.h"
+#include "chromeos/services/bluetooth_config/fake_adapter_state_controller.h"
+#include "chromeos/services/bluetooth_config/public/mojom/cros_bluetooth_config.mojom.h"
+#include "chromeos/services/bluetooth_config/scoped_bluetooth_config_test_helper.h"
+#include "chromeos/services/network_config/public/cpp/cros_network_config_test_helper.h"
+#include "chromeos/services/network_config/public/mojom/cros_network_config.mojom.h"
+#include "components/session_manager/session_manager_types.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "third_party/cros_system_api/dbus/shill/dbus-constants.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/views/controls/button/toggle_button.h"
 
 namespace ash {
 
+namespace {
+
+using testing::_;
+using testing::Return;
+
+using chromeos::bluetooth_config::ScopedBluetoothConfigTestHelper;
+using chromeos::bluetooth_config::mojom::BluetoothSystemState;
+
+using chromeos::network_config::CrosNetworkConfigTestHelper;
+
+using chromeos::network_config::mojom::ActivationStateType;
+using chromeos::network_config::mojom::ConnectionStateType;
+using chromeos::network_config::mojom::NetworkStatePropertiesPtr;
+using chromeos::network_config::mojom::NetworkType;
+using chromeos::network_config::mojom::OncSource;
+using chromeos::network_config::mojom::SecurityType;
+
+const std::string kCellularName = "cellular";
+const std::string kCellularName2 = "cellular_2";
+const char kCellularDeviceName[] = "cellular_device";
+const char kCellularDevicePath[] = "/device/cellular_device";
+const char kCellularTestIccid[] = "1234567890";
+
+const char kTetherName[] = "tether";
+const char kTetherGuid[] = "tetherNetworkGuid";
+const char kTetherCarrier[] = "TetherNetworkCarrier";
+const char kWifiServiceGuid[] = "wifiServiceGuid";
+
+const char kVpnName[] = "vpn";
+const char kVpnDevicePath[] = "device/vpn";
+
+const char kTestEuiccBasePath[] = "/org/chromium/Hermes/Euicc/";
+const char kTestBaseEid[] = "12345678901234567890123456789012";
+
+const int kSignalStrength = 50;
+constexpr char kUser1Email[] = "user1@quicksettings.com";
+
+// Delay used to simulate running process when setting device technology state.
+constexpr base::TimeDelta kInteractiveDelay = base::Milliseconds(3000);
+
+std::string CreateConfigurationJsonString(const std::string& guid,
+                                          const std::string& type,
+                                          const std::string& state) {
+  std::stringstream ss;
+  ss << "{"
+     << "  \"GUID\": \"" << guid << "\","
+     << "  \"Type\": \"" << type << "\","
+     << "  \"State\": \"" << state << "\""
+     << "}";
+  return ss.str();
+}
+
+std::string CreateTestEuiccPath(int euicc_num) {
+  return base::StringPrintf("%s%d", kTestEuiccBasePath, euicc_num);
+}
+
+std::string CreateTestEid(int euicc_num) {
+  return base::StringPrintf("%s%d", kTestBaseEid, euicc_num);
+}
+
+}  // namespace
+
 class NetworkListViewControllerTest : public AshTestBase {
  public:
+  NetworkListViewControllerTest()
+      : AshTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
+  NetworkListViewControllerTest(const NetworkListViewControllerTest&) = delete;
+  NetworkListViewControllerTest& operator=(
+      const NetworkListViewControllerTest&) = delete;
+  ~NetworkListViewControllerTest() override = default;
+
   void SetUp() override {
+    // Initialize CrosNetworkConfigTestHelper here, so we can use
+    // MockManagedNetworkConfigurationHandler.
+    cros_network_config_test_helper_ =
+        std::make_unique<network_config::CrosNetworkConfigTestHelper>(
+            /*initialize=*/false);
+
+    mock_managed_network_configuration_manager_ = base::WrapUnique(
+        new testing::NiceMock<MockManagedNetworkConfigurationHandler>);
+
+    SetGlobalPolicyConfig(/*allow_only_policy=*/false);
+
+    ON_CALL(*mock_managed_network_configuration_manager_,
+            GetGlobalConfigFromPolicy(_))
+        .WillByDefault(Return(&global_config_));
+
+    cros_network_config_test_helper_->Initialize(
+        mock_managed_network_configuration_manager_.get());
+    base::RunLoop().RunUntilIdle();
+
     AshTestBase::SetUp();
 
     feature_list_.InitAndEnableFeature(features::kQuickSettingsNetworkRevamp);
@@ -29,31 +140,562 @@
             fake_network_detailed_network_view_.get());
   }
 
+  void SetGlobalPolicyConfig(bool allow_only_policy) {
+    base::Value::Dict global_config_dict;
+    global_config_dict.Set(
+        ::onc::global_network_config::kAllowOnlyPolicyCellularNetworks,
+        allow_only_policy);
+
+    global_config_ = base::Value(std::move(global_config_dict));
+
+    // This function can be called before AshTestBase::SetUp(), Shell is not
+    // initialized, make sure to only call FlushGlobalPolicyForTesting
+    // after initialization.
+    if (Shell::HasInstance()) {
+      Shell::Get()
+          ->system_tray_model()
+          ->network_state_model()
+          ->FlushGlobalPolicyForTesting();
+      base::RunLoop().RunUntilIdle();
+    }
+  }
+
   void TearDown() override {
     network_list_view_controller_impl_.reset();
     fake_network_detailed_network_view_.reset();
+    cros_network_config_test_helper_.reset();
 
     AshTestBase::TearDown();
   }
 
-  FakeNetworkDetailedNetworkView* fake_network_detailed_network_view() {
-    return fake_network_detailed_network_view_.get();
+  views::ToggleButton* GetToggleButton() {
+    return static_cast<views::ToggleButton*>(GetMobileSubHeader()->GetViewByID(
+        static_cast<int>(NetworkListNetworkHeaderView::kToggleButtonId)));
   }
 
-  NetworkListViewController* network_list_view_controller_impl() {
-    return network_list_view_controller_impl_.get();
+  FakeNetworkListMobileHeaderView* GetMobileSubHeader() {
+    return FindViewById<FakeNetworkListMobileHeaderView*>(
+        NetworkListViewControllerImpl::NetworkListViewControllerViewChildId::
+            kMobileSectionHeader);
   }
 
+  views::Separator* GetMobileSeparator() {
+    return FindViewById<views::Separator*>(
+        NetworkListViewControllerImpl::NetworkListViewControllerViewChildId::
+            kMobileSeperator);
+  }
+
+  TrayInfoLabel* GetMobileStatusMessage() {
+    return FindViewById<TrayInfoLabel*>(
+        NetworkListViewControllerImpl::NetworkListViewControllerViewChildId::
+            kMobileStatusMessage);
+  }
+
+  TriView* GetConnectionWarning() {
+    return FindViewById<TriView*>(
+        NetworkListViewControllerImpl::NetworkListViewControllerViewChildId::
+            kConnectionWarning);
+  }
+
+  views::Label* GetConnectionLabelView() {
+    return FindViewById<views::Label*>(
+        NetworkListViewControllerImpl::NetworkListViewControllerViewChildId::
+            kConnectionWarningLabel);
+  }
+
+  views::View* GetViewInNetworkList(std::string id) {
+    return network_list_view_controller_impl_->network_id_to_view_map_[id];
+  }
+
+  void UpdateNetworkList(
+      const std::vector<NetworkStatePropertiesPtr>& networks) {
+    network_list_view_controller_impl_->OnGetNetworkStateList(
+        mojo::Clone(networks));
+  }
+
+  // Checks that network list items are in the right order. This function
+  // assumes that Mobile device is present and enabled.
+  void CheckNetworkListOrdering(size_t mobile_network_count) {
+    // TODO(tjohnsonkanu): add Ethernet and WiFi networks.
+    if (mobile_network_count) {
+      // We expect to see all network item views and Mobile subheader.
+      // Separator is null because only mobile networks are available.
+      EXPECT_EQ(mobile_network_count + 1, network_list()->children().size());
+      EXPECT_NE(nullptr, GetMobileSubHeader());
+      EXPECT_EQ(nullptr, GetMobileStatusMessage());
+      EXPECT_EQ(nullptr, GetMobileSeparator());
+
+      EXPECT_EQ(network_list()->children().at(0), GetMobileSubHeader());
+      return;
+    }
+
+    // Mobile header is shown and mobile status message is also shown.
+    EXPECT_EQ(2u, network_list()->children().size());
+    EXPECT_NE(nullptr, GetMobileSubHeader());
+    EXPECT_NE(nullptr, GetMobileStatusMessage());
+
+    EXPECT_EQ(network_list()->children().at(0), GetMobileSubHeader());
+    EXPECT_EQ(network_list()->children().at(1), GetMobileStatusMessage());
+  }
+
+  void CheckNetworkListItem(size_t index, const std::string& guid) {
+    ASSERT_GT(network_list()->children().size(), index);
+    EXPECT_STREQ(network_list()->children().at(index)->GetClassName(),
+                 "NetworkListNetworkItemView");
+    EXPECT_EQ(static_cast<NetworkListNetworkItemView*>(
+                  network_list()->children().at(index))
+                  ->network_properties()
+                  ->guid,
+              guid);
+  }
+
+  void SetupCellular() {
+    network_state_helper()->manager_test()->AddTechnology(shill::kTypeCellular,
+                                                          /*enabled=*/true);
+    network_state_helper()->device_test()->AddDevice(
+        kCellularDevicePath, shill::kTypeCellular, kCellularDeviceName);
+
+    base::Value::ListStorage sim_slot_infos;
+    base::Value slot_info_item(base::Value::Type::DICTIONARY);
+    slot_info_item.SetKey(shill::kSIMSlotInfoICCID,
+                          base::Value(kCellularTestIccid));
+    slot_info_item.SetBoolKey(shill::kSIMSlotInfoPrimary, true);
+    slot_info_item.SetStringKey(shill::kSIMSlotInfoEID, kTestBaseEid);
+    sim_slot_infos.push_back(std::move(slot_info_item));
+    network_state_helper()->device_test()->SetDeviceProperty(
+        kCellularDevicePath, shill::kSIMSlotInfoProperty,
+        base::Value(sim_slot_infos), /*notify_changed=*/true);
+
+    // Wait for network state and device change events to be handled.
+    base::RunLoop().RunUntilIdle();
+  }
+
+  void AddEuicc() {
+    network_state_helper()->hermes_manager_test()->AddEuicc(
+        dbus::ObjectPath(CreateTestEuiccPath(/*euicc_num=*/1)),
+        CreateTestEid(/*euicc_num=*/1), /*is_active=*/true,
+        /*physical_slot=*/0);
+
+    // Wait for network state change events to be handled.
+    base::RunLoop().RunUntilIdle();
+  }
+
+  // Adds a Tether network state, adds a Wifi network to be used as the Wifi
+  // hotspot, and associates the two networks.
+  void AddTetherNetworkState() {
+    network_state_handler()->SetTetherTechnologyState(
+        chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED);
+    network_state_handler()->AddTetherNetworkState(
+        kTetherGuid, kTetherName, kTetherCarrier, /*battery_percentage=*/100,
+        kSignalStrength, /*has_connected_to_host=*/false);
+    network_state_helper()->ConfigureService(CreateConfigurationJsonString(
+        kWifiServiceGuid, shill::kTypeWifi, shill::kStateReady));
+    network_state_handler()->AssociateTetherNetworkStateWithWifiNetwork(
+        kTetherGuid, kWifiServiceGuid);
+  }
+
+  void AddVpnDevice() {
+    network_state_helper()->manager_test()->AddTechnology(shill::kTypeVPN,
+                                                          /*enabled=*/true);
+    network_state_helper()->device_test()->AddDevice(kVpnDevicePath,
+                                                     shill::kTypeVPN, kVpnName);
+
+    // Wait for network state and device change events to be handled.
+    base::RunLoop().RunUntilIdle();
+  }
+
+  std::unique_ptr<CellularInhibitor::InhibitLock> InhibitCellularScanning() {
+    base::RunLoop inhibit_loop;
+    CellularInhibitor::InhibitReason inhibit_reason =
+        CellularInhibitor::InhibitReason::kInstallingProfile;
+    std::unique_ptr<CellularInhibitor::InhibitLock> inhibit_lock;
+    cros_network_config_test_helper_->cellular_inhibitor()
+        ->InhibitCellularScanning(
+            inhibit_reason,
+            base::BindLambdaForTesting(
+                [&](std::unique_ptr<CellularInhibitor::InhibitLock> result) {
+                  inhibit_lock = std::move(result);
+                  inhibit_loop.Quit();
+                }));
+    inhibit_loop.Run();
+    return inhibit_lock;
+  }
+
+  NetworkStatePropertiesPtr CreateStandaloneNetworkProperties(
+      const std::string& id,
+      NetworkType type,
+      ConnectionStateType connection_state) {
+    return cros_network_config_test_helper_->CreateStandaloneNetworkProperties(
+        id, type, connection_state, kSignalStrength);
+  }
+
+  void SetBluetoothAdapterState(BluetoothSystemState system_state) {
+    bluetooth_config_test_helper()
+        ->fake_adapter_state_controller()
+        ->SetSystemState(system_state);
+    base::RunLoop().RunUntilIdle();
+  }
+
+  void LoginAsSecondaryUser() {
+    GetSessionControllerClient()->AddUserSession(kUser1Email);
+    SimulateUserLogin(kUser1Email);
+    GetSessionControllerClient()->SetSessionState(
+        session_manager::SessionState::LOGIN_SECONDARY);
+    base::RunLoop().RunUntilIdle();
+  }
+
+  chromeos::NetworkStateHandler* network_state_handler() {
+    return network_state_helper()->network_state_handler();
+  }
+
+  chromeos::NetworkStateTestHelper* network_state_helper() {
+    return &cros_network_config_test_helper_->network_state_helper();
+  }
+
+  views::View* network_list() {
+    return static_cast<NetworkDetailedNetworkView*>(
+               fake_network_detailed_network_view_.get())
+        ->network_list();
+  }
+
+ protected:
+  const std::vector<NetworkStatePropertiesPtr> empty_list_;
+
  private:
+  template <class T>
+  T FindViewById(
+      NetworkListViewControllerImpl::NetworkListViewControllerViewChildId id) {
+    return static_cast<T>(network_list()->GetViewByID(static_cast<int>(id)));
+  }
+
+  ScopedBluetoothConfigTestHelper* bluetooth_config_test_helper() {
+    return ash_test_helper()->bluetooth_config_test_helper();
+  }
+
   base::test::ScopedFeatureList feature_list_;
   std::unique_ptr<FakeNetworkDetailedNetworkView>
       fake_network_detailed_network_view_;
   std::unique_ptr<NetworkListViewControllerImpl>
       network_list_view_controller_impl_;
+
+  std::unique_ptr<network_config::CrosNetworkConfigTestHelper>
+      cros_network_config_test_helper_;
+
+  std::unique_ptr<MockManagedNetworkConfigurationHandler>
+      mock_managed_network_configuration_manager_;
+
+  base::Value global_config_;
 };
 
-TEST_F(NetworkListViewControllerTest, CanConstruct) {
-  EXPECT_TRUE(true);
+TEST_F(NetworkListViewControllerTest, MobileDataSectionIsShown) {
+  EXPECT_EQ(nullptr, GetMobileSubHeader());
+  EXPECT_EQ(nullptr, GetMobileSeparator());
+
+  AddEuicc();
+  SetupCellular();
+  EXPECT_NE(nullptr, GetMobileSubHeader());
+
+  // Mobile separator is still null because mobile data is at index 0.
+  EXPECT_EQ(nullptr, GetMobileSeparator());
+
+  // Clear device list and check if Mobile subheader is shown with just
+  // tether device.
+  network_state_helper()->ClearDevices();
+  EXPECT_EQ(nullptr, GetMobileSubHeader());
+
+  // Add tether networks
+  AddTetherNetworkState();
+  EXPECT_NE(nullptr, GetMobileSubHeader());
+
+  // Tether device is prohibited.
+  network_state_handler()->SetTetherTechnologyState(
+      chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_PROHIBITED);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(nullptr, GetMobileSubHeader());
+
+  // Tether device is uninitialized but is primary user.
+  network_state_handler()->SetTetherTechnologyState(
+      chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_UNINITIALIZED);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_NE(nullptr, GetMobileSubHeader());
+
+  // Simulate login as secondary user.
+  LoginAsSecondaryUser();
+  UpdateNetworkList(empty_list_);
+  EXPECT_EQ(nullptr, GetMobileSubHeader());
+
+  // Add tether networks
+  AddTetherNetworkState();
+  EXPECT_NE(nullptr, GetMobileSubHeader());
+}
+
+TEST_F(NetworkListViewControllerTest, MobileSectionHeaderAddEsimButtonStates) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndDisableFeature(ash::features::kESimPolicy);
+  EXPECT_EQ(nullptr, GetMobileSubHeader());
+  EXPECT_EQ(nullptr, GetMobileStatusMessage());
+
+  SetupCellular();
+  EXPECT_NE(nullptr, GetMobileSubHeader());
+  EXPECT_TRUE(GetMobileSubHeader()->is_add_esim_enabled());
+
+  // Since no Euicc was added, this means device is not eSIM capable, do not
+  // show add eSIM button.
+  EXPECT_FALSE(GetMobileSubHeader()->is_add_esim_visible());
+
+  AddEuicc();
+  UpdateNetworkList(empty_list_);
+
+  EXPECT_TRUE(GetMobileSubHeader()->is_add_esim_visible());
+  EXPECT_EQ(nullptr, GetMobileSeparator());
+  EXPECT_NE(nullptr, GetMobileStatusMessage());
+
+  // Add eSIM button is not enabled when inhibited.
+  std::unique_ptr<CellularInhibitor::InhibitLock> inhibit_lock =
+      InhibitCellularScanning();
+  EXPECT_TRUE(inhibit_lock);
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_FALSE(GetMobileSubHeader()->is_add_esim_enabled());
+  EXPECT_TRUE(GetMobileSubHeader()->is_add_esim_visible());
+
+  // Uninhibit the device.
+  inhibit_lock.reset();
+  base::RunLoop().RunUntilIdle();
+  EXPECT_TRUE(GetMobileSubHeader()->is_add_esim_enabled());
+  EXPECT_TRUE(GetMobileSubHeader()->is_add_esim_visible());
+
+  // When no Mobile networks are available and eSIM policy is set to allow only
+  // cellular devices which means adding a new eSIM is disallowed by enterprise
+  // policy, add eSIM button is not displayed.
+  SetGlobalPolicyConfig(/*allow_only_policy=*/true);
+  scoped_feature_list.Reset();
+  scoped_feature_list.InitAndEnableFeature(ash::features::kESimPolicy);
+  UpdateNetworkList(empty_list_);
+  EXPECT_FALSE(GetMobileSubHeader()->is_add_esim_visible());
+}
+
+TEST_F(NetworkListViewControllerTest, HasCorrectNetworkList) {
+  EXPECT_EQ(0u, network_list()->children().size());
+  EXPECT_EQ(nullptr, GetMobileSubHeader());
+  EXPECT_EQ(nullptr, GetMobileStatusMessage());
+
+  AddEuicc();
+  SetupCellular();
+
+  CheckNetworkListOrdering(/*mobile_network_count=*/0u);
+
+  std::vector<NetworkStatePropertiesPtr> networks;
+
+  NetworkStatePropertiesPtr cellular_network =
+      CreateStandaloneNetworkProperties(kCellularName, NetworkType::kCellular,
+                                        ConnectionStateType::kConnected);
+  networks.push_back(std::move(cellular_network));
+  UpdateNetworkList(networks);
+
+  CheckNetworkListOrdering(/*mobile_network_count=*/1u);
+  CheckNetworkListItem(/*index=*/1u, /*guid=*/kCellularName);
+
+  cellular_network = CreateStandaloneNetworkProperties(
+      kCellularName2, NetworkType::kCellular, ConnectionStateType::kConnected);
+  networks.push_back(std::move(cellular_network));
+  UpdateNetworkList(networks);
+
+  CheckNetworkListOrdering(/*mobile_network_count=*/2u);
+  CheckNetworkListItem(/*index=*/2u, /*guid=*/kCellularName2);
+
+  // Update a network and make sure it is still in network list.
+  networks.front()->connection_state = ConnectionStateType::kNotConnected;
+  UpdateNetworkList(networks);
+
+  CheckNetworkListOrdering(/*mobile_network_count=*/2u);
+  CheckNetworkListItem(/*index=*/1u, /*guid=*/kCellularName);
+  CheckNetworkListItem(/*index=*/2u, /*guid=*/kCellularName2);
+
+  // Remove all networks and add Tether networks. Only one network should be in
+  // list.
+  networks.clear();
+  NetworkStatePropertiesPtr tether_network = CreateStandaloneNetworkProperties(
+      kTetherName, NetworkType::kTether, ConnectionStateType::kConnected);
+  networks.push_back(std::move(tether_network));
+  UpdateNetworkList(networks);
+
+  CheckNetworkListOrdering(/*mobile_network_count=*/1u);
+  CheckNetworkListItem(/*index=*/1u, /*guid=*/kTetherName);
+}
+
+TEST_F(NetworkListViewControllerTest,
+       CellularStatusMessageAndToggleButtonState) {
+  EXPECT_EQ(nullptr, GetMobileStatusMessage());
+
+  AddEuicc();
+  SetupCellular();
+
+  // Update cellular device state to be Uninitialized.
+  network_state_helper()->manager_test()->SetTechnologyInitializing(
+      shill::kTypeCellular, /*initializing=*/true);
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_NE(nullptr, GetMobileStatusMessage());
+  EXPECT_FALSE(GetToggleButton()->GetVisible());
+  EXPECT_EQ(
+      l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_INITIALIZING_CELLULAR),
+      GetMobileStatusMessage()->label()->GetText());
+
+  network_state_helper()->manager_test()->SetTechnologyInitializing(
+      shill::kTypeCellular, /*initializing=*/false);
+  base::RunLoop().RunUntilIdle();
+
+  SetupCellular();
+  EXPECT_NE(nullptr, GetMobileStatusMessage());
+  EXPECT_NE(nullptr, GetMobileSubHeader());
+  EXPECT_EQ(l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_NO_MOBILE_NETWORKS),
+            GetMobileStatusMessage()->label()->GetText());
+  EXPECT_TRUE(GetMobileSubHeader()->is_toggle_enabled());
+  EXPECT_TRUE(GetMobileSubHeader()->is_toggle_on());
+  EXPECT_TRUE(GetToggleButton()->GetVisible());
+
+  // No message is shown when there are available networks.
+  std::vector<NetworkStatePropertiesPtr> networks;
+  networks.push_back(CreateStandaloneNetworkProperties(
+      kCellularName, NetworkType::kCellular, ConnectionStateType::kConnected));
+  UpdateNetworkList(networks);
+  EXPECT_EQ(nullptr, GetMobileStatusMessage());
+  EXPECT_TRUE(GetMobileSubHeader()->is_toggle_enabled());
+  EXPECT_TRUE(GetMobileSubHeader()->is_toggle_on());
+  EXPECT_TRUE(GetToggleButton()->GetVisible());
+
+  // Message shown again when list is empty.
+  UpdateNetworkList(empty_list_);
+  EXPECT_NE(nullptr, GetMobileStatusMessage());
+  EXPECT_EQ(l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_NO_MOBILE_NETWORKS),
+            GetMobileStatusMessage()->label()->GetText());
+  EXPECT_TRUE(GetToggleButton()->GetVisible());
+
+  // No message is shown when inhibited.
+  std::unique_ptr<CellularInhibitor::InhibitLock> inhibit_lock =
+      InhibitCellularScanning();
+  EXPECT_TRUE(inhibit_lock);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(nullptr, GetMobileStatusMessage());
+  EXPECT_FALSE(GetMobileSubHeader()->is_toggle_enabled());
+  EXPECT_TRUE(GetMobileSubHeader()->is_toggle_on());
+  EXPECT_TRUE(GetToggleButton()->GetVisible());
+
+  // Uninhibit the device.
+  inhibit_lock.reset();
+  base::RunLoop().RunUntilIdle();
+
+  // Message is shown when uninhibited.
+  EXPECT_NE(nullptr, GetMobileStatusMessage());
+  EXPECT_EQ(l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_NO_MOBILE_NETWORKS),
+            GetMobileStatusMessage()->label()->GetText());
+  EXPECT_TRUE(GetMobileSubHeader()->is_toggle_enabled());
+  EXPECT_TRUE(GetMobileSubHeader()->is_toggle_on());
+  EXPECT_TRUE(GetToggleButton()->GetVisible());
+
+  // When device is in disabling message is shown.
+  network_state_helper()->manager_test()->SetInteractiveDelay(
+      kInteractiveDelay);
+  network_state_handler()->SetTechnologyEnabled(
+      chromeos::NetworkTypePattern::Cellular(), /*enabled=*/false,
+      base::DoNothing());
+
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_NE(nullptr, GetMobileStatusMessage());
+  EXPECT_EQ(
+      l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_NETWORK_MOBILE_DISABLING),
+      GetMobileStatusMessage()->label()->GetText());
+  EXPECT_FALSE(GetMobileSubHeader()->is_toggle_enabled());
+  EXPECT_FALSE(GetMobileSubHeader()->is_toggle_on());
+  EXPECT_TRUE(GetToggleButton()->GetVisible());
+  task_environment()->FastForwardBy(kInteractiveDelay);
+
+  // Message is shown when device is disabled.
+  EXPECT_NE(nullptr, GetMobileStatusMessage());
+  EXPECT_EQ(
+      l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_NETWORK_MOBILE_DISABLED),
+      GetMobileStatusMessage()->label()->GetText());
+  EXPECT_TRUE(GetMobileSubHeader()->is_toggle_enabled());
+  EXPECT_FALSE(GetMobileSubHeader()->is_toggle_on());
+  EXPECT_TRUE(GetToggleButton()->GetVisible());
+}
+
+TEST_F(NetworkListViewControllerTest, HasCorrectTetherStatusMessage) {
+  // Mobile section is not shown if Tether network is unavailable.
+  EXPECT_EQ(nullptr, GetMobileStatusMessage());
+
+  // Tether is enabled but no devices are added.
+  network_state_handler()->SetTetherTechnologyState(
+      chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED);
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_NE(nullptr, GetMobileStatusMessage());
+  EXPECT_NE(nullptr, GetMobileSubHeader());
+  EXPECT_TRUE(GetMobileSubHeader()->is_toggle_enabled());
+  EXPECT_TRUE(GetMobileSubHeader()->is_toggle_on());
+  EXPECT_EQ(
+      l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_NO_MOBILE_DEVICES_FOUND),
+      GetMobileStatusMessage()->label()->GetText());
+
+  // Tether network is uninitialized and Bluetooth state enabling.
+  network_state_handler()->SetTetherTechnologyState(
+      chromeos::NetworkStateHandler::TechnologyState::TECHNOLOGY_UNINITIALIZED);
+  base::RunLoop().RunUntilIdle();
+
+  SetBluetoothAdapterState(BluetoothSystemState::kEnabling);
+  EXPECT_FALSE(GetMobileSubHeader()->is_toggle_enabled());
+  EXPECT_TRUE(GetMobileSubHeader()->is_toggle_on());
+  EXPECT_NE(nullptr, GetMobileStatusMessage());
+  EXPECT_EQ(
+      l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_INITIALIZING_CELLULAR),
+      GetMobileStatusMessage()->label()->GetText());
+
+  // Set Bluetooth device to disabling.
+  SetBluetoothAdapterState(BluetoothSystemState::kDisabling);
+  EXPECT_TRUE(GetMobileSubHeader()->is_toggle_enabled());
+  EXPECT_FALSE(GetMobileSubHeader()->is_toggle_on());
+  EXPECT_NE(nullptr, GetMobileStatusMessage());
+  EXPECT_EQ(l10n_util::GetStringUTF16(
+                IDS_ASH_STATUS_TRAY_ENABLING_MOBILE_ENABLES_BLUETOOTH),
+            GetMobileStatusMessage()->label()->GetText());
+
+  // Simulate login as secondary user and disable Bluetooth device.
+  LoginAsSecondaryUser();
+  SetBluetoothAdapterState(BluetoothSystemState::kDisabled);
+  EXPECT_FALSE(GetMobileSubHeader()->is_toggle_enabled());
+  EXPECT_FALSE(GetMobileSubHeader()->is_toggle_on());
+  EXPECT_NE(nullptr, GetMobileStatusMessage());
+  EXPECT_EQ(l10n_util::GetStringUTF16(
+                IDS_ASH_STATUS_TRAY_ENABLING_MOBILE_ENABLES_BLUETOOTH),
+            GetMobileStatusMessage()->label()->GetText());
+
+  // No message shown when Tether devices are added.
+  AddTetherNetworkState();
+  EXPECT_EQ(nullptr, GetMobileStatusMessage());
+}
+
+TEST_F(NetworkListViewControllerTest, HasConnectionWarning) {
+  EXPECT_EQ(nullptr, GetConnectionWarning());
+
+  AddVpnDevice();
+  std::vector<NetworkStatePropertiesPtr> networks;
+  networks.push_back(CreateStandaloneNetworkProperties(
+      kVpnName, NetworkType::kVPN, ConnectionStateType::kConnected));
+  UpdateNetworkList(networks);
+
+  EXPECT_NE(nullptr, GetConnectionWarning());
+  EXPECT_NE(nullptr, GetConnectionLabelView());
+  EXPECT_EQ(
+      l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_NETWORK_MONITORED_WARNING),
+      GetConnectionLabelView()->GetText());
+  EXPECT_EQ(1u, network_list()->children().size());
+  EXPECT_EQ(network_list()->children().at(0), GetConnectionWarning());
+
+  // Clear all devices and make sure warning is no longer being shown.
+  network_state_helper()->ClearDevices();
+  EXPECT_EQ(nullptr, GetConnectionWarning());
 }
 
 }  // namespace ash
diff --git a/ash/system/palette/palette_tool.cc b/ash/system/palette/palette_tool.cc
index 42240ab..117674d 100644
--- a/ash/system/palette/palette_tool.cc
+++ b/ash/system/palette/palette_tool.cc
@@ -23,10 +23,8 @@
 void PaletteTool::RegisterToolInstances(PaletteToolManager* tool_manager) {
   tool_manager->AddTool(std::make_unique<EnterCaptureMode>(tool_manager));
   tool_manager->AddTool(std::make_unique<CreateNoteAction>(tool_manager));
-  if (!ash::features::IsDeprecateAssistantStylusFeaturesEnabled() &&
-      assistant::util::IsGoogleDevice()) {
+  if (assistant::util::IsGoogleDevice())
     tool_manager->AddTool(std::make_unique<MetalayerMode>(tool_manager));
-  }
   tool_manager->AddTool(std::make_unique<LaserPointerMode>(tool_manager));
   tool_manager->AddTool(std::make_unique<MagnifierMode>(tool_manager));
 }
diff --git a/ash/system/palette/tools/metalayer_mode.cc b/ash/system/palette/tools/metalayer_mode.cc
index 86fe59e..3222b5372 100644
--- a/ash/system/palette/tools/metalayer_mode.cc
+++ b/ash/system/palette/tools/metalayer_mode.cc
@@ -8,6 +8,7 @@
 #include "ash/public/cpp/system/toast_catalog.h"
 #include "ash/public/cpp/system/toast_data.h"
 #include "ash/resources/vector_icons/vector_icons.h"
+#include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
 #include "ash/style/ash_color_provider.h"
@@ -18,6 +19,7 @@
 #include "ash/system/tray/tray_constants.h"
 #include "ash/system/tray/tray_popup_utils.h"
 #include "base/bind.h"
+#include "chromeos/services/assistant/public/cpp/assistant_prefs.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/events/event.h"
 #include "ui/gfx/paint_vector_icon.h"
@@ -32,10 +34,19 @@
 
 const char kToastId[] = "palette_metalayer_mode";
 
+// Toast ID for toast that shows for long press stylus actions when metalayer
+// mode is deprecated.
+const char kDeprecateAssistantStylusToastId[] = "deprecate_assistant_stylus";
+
 // If the last stroke happened within this amount of time,
 // assume writing/sketching usage.
 const int kMaxStrokeGapWhenWritingMs = 1000;
 
+// Returns the last active user pref service.
+PrefService* GetPrefs() {
+  return Shell::Get()->session_controller()->GetLastActiveUserPrefService();
+}
+
 }  // namespace
 
 MetalayerMode::MetalayerMode(Delegate* delegate) : CommonPaletteTool(delegate) {
@@ -93,6 +104,9 @@
 }
 
 views::View* MetalayerMode::CreateView() {
+  if (ash::features::IsDeprecateAssistantStylusFeaturesEnabled())
+    return nullptr;
+
   views::View* view = CreateDefaultView(std::u16string());
   UpdateView();
   return view;
@@ -134,6 +148,30 @@
   if (palette_utils::PaletteContainsPointInScreen(event->root_location()))
     return;
 
+  // Assistant stylus features are in the process of being deprecated.
+  // After deprecation, which is currently gated by a feature flag, long
+  // press stylus events will not trigger the metalayer mode.
+  if (ash::features::IsDeprecateAssistantStylusFeaturesEnabled()) {
+    // Only show the toast once when the metalayer is triggered for the first
+    // time.
+    if (!GetPrefs()->GetBoolean(
+            chromeos::assistant::prefs::kAssistantDeprecateStylusToast)) {
+      // Set the deprecate stylus toast assistant pref so that the toast doesn't
+      // repeatedly show.
+      GetPrefs()->SetBoolean(
+          chromeos::assistant::prefs::kAssistantDeprecateStylusToast, true);
+      Shell::Get()->toast_manager()->Show(
+          ToastData(kDeprecateAssistantStylusToastId,
+                    ToastCatalogName::kDeprecateAssistantStylus,
+                    l10n_util::GetStringUTF16(
+                        IDS_ASH_STYLUS_TOOLS_METALAYER_TOAST_DEPRECATE),
+                    ToastData::kDefaultToastDuration,
+                    /*visible_on_lock_screen=*/false,
+                    /*has_dismiss_button=*/true));
+    }
+    return;
+  }
+
   if (loading()) {
     // Repetitive presses will create toasts with the same id which will be
     // ignored.
diff --git a/ash/system/unified/unified_system_tray_controller.cc b/ash/system/unified/unified_system_tray_controller.cc
index 3b3099af..87d71d0 100644
--- a/ash/system/unified/unified_system_tray_controller.cc
+++ b/ash/system/unified/unified_system_tray_controller.cc
@@ -354,7 +354,8 @@
 
   base::RecordAction(base::UserMetricsAction("StatusArea_Network_Detailed"));
 
-  if (ash::features::IsQuickSettingsNetworkRevampEnabled()) {
+  if (ash::features::IsQuickSettingsNetworkRevampEnabled() &&
+      ash::features::IsBluetoothRevampEnabled()) {
     ShowDetailedView(std::make_unique<NetworkDetailedViewController>(this));
   } else {
     ShowDetailedView(
diff --git a/ash/webui/os_feedback_ui/os_feedback_ui.cc b/ash/webui/os_feedback_ui/os_feedback_ui.cc
index 3f1646b5..573a2b55 100644
--- a/ash/webui/os_feedback_ui/os_feedback_ui.cc
+++ b/ash/webui/os_feedback_ui/os_feedback_ui.cc
@@ -42,6 +42,7 @@
 void AddLocalizedStrings(content::WebUIDataSource* source) {
   static constexpr webui::LocalizedString kLocalizedStrings[] = {
       {"continueButtonLabel", IDS_FEEDBACK_TOOL_CONTINUE_BUTTON_LABEL},
+      {"descriptionHint", IDS_FEEDBACK_TOOL_DESCRIPTION_HINT},
       {"descriptionLabel", IDS_FEEDBACK_TOOL_DESCRIPTION_LABEL},
       {"descriptionRequired", IDS_FEEDBACK_TOOL_DESCRIPTION_REQUIRED},
       {"feedbackHelpLinkLabel", IDS_FEEDBACK_TOOL_FEEDBACK_HELP_LINK_LABEL},
diff --git a/ash/webui/os_feedback_ui/resources/search_page.html b/ash/webui/os_feedback_ui/resources/search_page.html
index 7e6821d..b8bf53ab 100644
--- a/ash/webui/os_feedback_ui/resources/search_page.html
+++ b/ash/webui/os_feedback_ui/resources/search_page.html
@@ -42,7 +42,7 @@
         </a>
       </div>
       <textarea id="descriptionText" aria-labelledby="descriptionTitle"
-          aria-required="true" on-input="handleInputChanged_">
+          aria-required="true" on-input="handleInputChanged_" placeholder="[[i18n('descriptionHint')]]">
       </textarea>
       <p id="descriptionEmptyError" aria-hidden="true" hidden>
         [[i18n('descriptionRequired')]]
diff --git a/ash/webui/personalization_app/resources/BUILD.gn b/ash/webui/personalization_app/resources/BUILD.gn
index 4c2ef63..156df15 100644
--- a/ash/webui/personalization_app/resources/BUILD.gn
+++ b/ash/webui/personalization_app/resources/BUILD.gn
@@ -6,7 +6,7 @@
 import("//chrome/browser/resources/tools/optimize_webui.gni")
 import("//third_party/closure_compiler/compile_js.gni")
 import("//tools/grit/preprocess_if_expr.gni")
-import("//tools/polymer/html_to_js.gni")
+import("//tools/polymer/css_to_wrapper.gni")
 import("//tools/polymer/html_to_wrapper.gni")
 import("//tools/typescript/ts_library.gni")
 import("//ui/webui/resources/tools/generate_grd.gni")
@@ -18,7 +18,7 @@
 
 # When adding a new file to personalization app, add it to one of the lists
 # below. `non_web_component_files` are plain ts files, `web_component_files` are
-# polymer based ts files, `css_wrapper_files` are css related ts files,
+# polymer based ts files, `css_wrapper_files` are Polymer css files,
 # `static_resource_files` are non-js files, e.g. image, html, css
 non_web_component_files = [
   "common/constants.ts",
@@ -122,22 +122,30 @@
   html_files += [ string_replace(f, ".ts", ".html") ]
 }
 
+icons_html_files = [ "common/icons.html" ]
+
 # Files that are generated by html_to_wrapper().
 html_wrapper_files = []
-foreach(f, html_files) {
+foreach(f, html_files + icons_html_files) {
   html_wrapper_files += [ f + ".ts" ]
 }
 
 ts_files = web_component_files + non_web_component_files
 
-css_wrapper_files = [
-  "common/icons.ts",
-  "common/styles.ts",
+# Files that are passed as input to css_to_wrapper().
+css_files = [
+  "common/common_style.css",
 
-  "trusted/cros_button_style.ts",
-  "trusted/wallpaper/styles.ts",
+  "trusted/cros_button_style.css",
+  "trusted/wallpaper/trusted_style.css",
 ]
 
+# Files that are generated by css_to_wrapper().
+css_wrapper_files = []
+foreach(f, css_files) {
+  css_wrapper_files += [ f + ".ts" ]
+}
+
 static_resource_files = [
   "hub_icon_64.png",
   "hub_icon_128.png",
@@ -159,12 +167,12 @@
   "trusted/index.html",
 ]
 
-html_to_js("css_wrapper_files") {
-  js_files = css_wrapper_files
+css_to_wrapper("css_wrapper_files") {
+  in_files = css_files
 }
 
 html_to_wrapper("html_to_wrapper_files") {
-  in_files = html_files
+  in_files = html_files + icons_html_files
 }
 
 preprocess_if_expr("preprocess") {
diff --git a/ash/webui/personalization_app/resources/common/common_style.css b/ash/webui/personalization_app/resources/common/common_style.css
new file mode 100644
index 0000000..4f922df
--- /dev/null
+++ b/ash/webui/personalization_app/resources/common/common_style.css
@@ -0,0 +1,355 @@
+/* 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. */
+
+/* #css_wrapper_metadata_start
+ * #type=style
+ * #import=chrome://resources/cr_elements/shared_vars_css.m.js
+ * #css_wrapper_metadata_end */
+
+[hidden] {
+  /* The |hidden| attribute does not hide an element with an explicitly
+   * specified |display| property. Handle this by forcing display to |none|
+   * when the |hidden| attribute is present. */
+  display: none !important;
+}
+
+:host {
+  --personalization-app-grid-item-background-color: var(--google-grey-100);
+  --personalization-app-grid-item-border-radius: 12px;
+  --personalization-app-grid-item-height: 120px;
+  --personalization-app-grid-item-spacing: 16px;
+
+  --personalization-app-text-shadow-elevation-1: 0 1px 3px
+      rgba(0, 0, 0, 15%), 0 1px 2px rgba(0, 0, 0, 30%);
+
+  /* copied from |AshColorProvider| |kSecondToneOpacity| constant. */
+  --personalization-app-second-tone-opacity: 0.3;
+
+  --personalization-app-label-font: 500 13px/20px
+      var(--cros-font-family-google-sans);
+}
+
+@media (prefers-color-scheme: dark) {
+  :host {
+    --personalization-app-grid-item-background-color:
+      rgba(var(--google-grey-700-rgb), 0.3);
+  }
+}
+
+iron-list {
+  height: 100%;
+}
+
+.photo-container {
+  box-sizing: border-box;
+  height: calc(
+    var(--personalization-app-grid-item-height) +
+    var(--personalization-app-grid-item-spacing));
+  overflow: hidden;
+  padding: calc(var(--personalization-app-grid-item-spacing) / 2);
+  /* Media queries in trusted and untrusted code will resize to 25% at
+   * correct widths.  Subtract 0.34px to fix subpixel rounding issues with
+   * iron-list. This makes sure all photo containers on a row add up to at
+   * least 1px smaller than the parent width.*/
+  width: calc(100% / 3 - 0.34px);
+}
+
+.photo-container:focus-visible {
+  outline: none;
+}
+
+/* This extra position: relative element corrects for absolutely positioned
+   elements ignoring parent interior padding. */
+.photo-inner-container {
+  align-items: center;
+  border-radius: var(--personalization-app-grid-item-border-radius);
+  cursor: pointer;
+  display: flex;
+  height: 100%;
+  justify-content: center;
+  overflow: hidden;
+  position: relative;
+  width: 100%;
+}
+
+@keyframes ripple {
+  /* 0 ms */
+  from {
+    opacity: 1;
+  }
+  /* 200 ms */
+  9% {
+    opacity: 0.15;
+  }
+  /* 350 ms */
+  15.8% {
+    opacity: 0.15;
+  }
+  /* 550 ms, hold for 83ms * 20 and then restart */
+  24.9% {
+    opacity: 1;
+  }
+  /* 2210 ms */
+  to {
+    opacity: 1;
+  }
+}
+
+.placeholder {
+  animation: 2210ms linear var(--animation-delay, 1s) infinite ripple;
+}
+
+.photo-inner-container:focus-visible,
+.photo-loading-placeholder:focus-visible {
+  border: 2px solid var(--cros-focus-ring-color);
+  border-radius: 14px;
+  outline: none;
+}
+
+.photo-images-container {
+  background-color: var(--personalization-app-grid-item-background-color);
+  border: 1px solid rgba(0, 0, 0, 0.08);
+  border-radius: 12px;
+  box-sizing: border-box;
+  display: flex;
+  flex-flow: row wrap;
+  height: 100%;
+  /* stop img and gradient-mask from ignoring above border-radius. */
+  overflow: hidden;
+  position: relative;
+  width: 100%;
+}
+
+.photo-images-container img {
+  flex: 1 1 0;
+  height: 100%;
+  min-width: 50%;
+  object-fit: cover;
+  width: 100%;
+}
+
+.photo-images-container img.left {
+  clip-path: inset(0 50% 0 0);
+  position: absolute;
+}
+
+.photo-images-container img.right {
+  clip-path: inset(0 0 0 50%);
+  position: absolute;
+}
+
+@keyframes scale-up {
+  from {
+    transform: scale(0);
+  }
+  to {
+    transform: scale(1);
+  }
+}
+
+.photo-container iron-icon[icon='personalization:checkmark'] {
+  --iron-icon-height: 20px;
+  --iron-icon-width: 20px;
+  animation-duration: 200ms;
+  animation-name: scale-up;
+  animation-timing-function: cubic-bezier(0.40, 0.00, 0.20, 1.00);
+  left: 4px;
+  position: absolute;
+  top: 4px;
+}
+
+.photo-inner-container:not([aria-selected='true'])
+iron-icon[icon='personalization:checkmark'] {
+  display: none;
+}
+
+.photo-inner-container[aria-selected='true'] {
+  background-color: rgba(var(--cros-icon-color-prominent-rgb),
+      var(--personalization-app-second-tone-opacity));
+  border-radius: 16px;
+}
+
+@keyframes resize {
+  100% {
+    height: calc(100% - 8px);
+    width: calc(100% - 8px);
+  }
+}
+
+.photo-inner-container[aria-selected='true'] .photo-images-container {
+  animation-duration: 200ms;
+  animation-fill-mode: forwards;
+  animation-name: resize;
+  animation-timing-function: cubic-bezier(0.40, 0.00, 0.20, 1.00);
+  border: 0;
+}
+
+.photo-inner-container:focus-visible:not([aria-selected='true'])
+.photo-images-container {
+  height: calc(100% - 4px);
+  width: calc(100% - 4px);
+}
+
+cr-button {
+  border-color: var(--cros-button-stroke-color-secondary);
+  border-radius: 16px;
+}
+
+cr-button[aria-pressed=true],
+cr-button[aria-selected=true] {
+  background-color: var(--cros-highlight-color);
+  border: 0;
+}
+
+cr-button + cr-button {
+  margin-inline-start: 8px;
+}
+
+.preview-container {
+  border: 1px solid var(--cros-separator-color);
+  border-radius: 16px;
+}
+
+.preview-text-container,
+.preview-text-placeholder {
+  align-items: flex-start;
+  display: flex;
+  flex-flow: column nowrap;
+  margin: 0;
+}
+
+.preview-text-container {
+  justify-content: flex-end;
+}
+
+.preview-text-placeholder {
+  justify-content: center;
+}
+
+.placeholder {
+  background-color: var(--personalization-app-grid-item-background-color);
+  border-radius: 12px;
+}
+
+.preview-image-container {
+  border: 1px solid rgba(0, 0, 0, 0.08);
+  border-radius: 12px;
+  box-sizing: border-box;
+  overflow: hidden;
+  position: relative;
+}
+
+.preview-image {
+  height: 100%;
+  object-fit: cover;
+  width: 100%;
+}
+
+.preview-text-container > *,
+.preview-text-placeholder > * {
+  margin: 0;
+  max-width: 100%;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.preview-text-container > * + * {
+  margin-top: 4px;
+}
+
+.preview-text-placeholder > * + * {
+  margin-top: 8px;
+}
+
+.preview-text-container > span:first-child {
+  color: var(--cros-text-color-secondary);
+  font: var(--cros-body-2-font);
+}
+
+.preview-text-placeholder > .placeholder:first-child {
+  /* Each row is 83 ms after the prior element. */
+  --animation-delay: calc(1s + 83ms);
+  width: 20%;
+  height: 20px;
+}
+
+.preview-text-container > span:nth-child(2) {
+  color: var(--cros-text-color-primary);
+  font: var(--cros-display-6-font);
+}
+
+.preview-text-placeholder > .placeholder:nth-child(2) {
+  --animation-delay: calc(1s + 83ms * 2);
+  width: 75%;
+  height: 24px;
+}
+
+.preview-text-container > span:nth-child(n+3) {
+  color: var(--cros-text-color-secondary);
+  font: var(--cros-body-1-font);
+}
+
+.preview-text-placeholder > .placeholder:nth-child(n+3) {
+  --animation-delay: calc(1s + 83ms * 3);
+  width: 33%;
+  height: 20px;
+}
+
+.ambient-subpage-element-title {
+  color: var(--cros-text-color-primary);
+  font: var(--personalization-app-label-font);
+  margin: 34px 8px 16px 8px;
+}
+
+.ambient-toggle-row-container {
+  border: 1px solid var(--cros-separator-color);
+  border-radius: 8px;
+  display: flex;
+  flex-flow: column nowrap;
+  height: 48px;
+  width: 100%;
+}
+
+.ambient-toggle-row {
+  align-items: center;
+  display: flex;
+  flex: 1;
+  flex-flow: row nowrap;
+  justify-content: space-between;
+  margin: 0 20px;
+}
+
+.ambient-toggle-row + .ambient-toggle-row {
+  border-top: 1px solid var(--cros-separator-color);
+}
+
+.ambient-toggle-row > p {
+  font: var(--cros-body-1-font);
+  height: 20px;
+  margin: 0;
+}
+
+.clickable {
+  cursor: pointer;
+}
+
+paper-tooltip::part(tooltip) {
+  align-items: flex-end;
+  display: flex;
+  flex-direction: row;
+  height: 18px;
+  padding: 3px 8px;
+}
+
+paper-tooltip {
+  --paper-tooltip-background: var(--cros-tooltip-background-color);
+  --paper-tooltip-text-color: var(--cros-tooltip-label-color);
+}
+
+paper-tooltip span {
+  align-items: center;
+  display: flex;
+  font: var(--cros-body-2-font);
+}
diff --git a/ash/webui/personalization_app/resources/common/icons.ts b/ash/webui/personalization_app/resources/common/icons.ts
deleted file mode 100644
index 72fade81..0000000
--- a/ash/webui/personalization_app/resources/common/icons.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2021 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.
-
-/**
- * @fileoverview Icons specific to personalization app.
- *
- * These icons should have transparent fill color to adapt to its container's
- * light/dark theme.
- *
- * Following the demo here:
- * @see https://github.com/PolymerElements/iron-iconset-svg/blob/v3.0.1/demo/svg-sample-icons.js
- */
-
-import '//resources/polymer/v3_0/iron-iconset-svg/iron-iconset-svg.js';
-
-const template = document.createElement('template');
-template.innerHTML = `{__html_template__}`;
-document.head.appendChild(template.content);
diff --git a/ash/webui/personalization_app/resources/common/styles.html b/ash/webui/personalization_app/resources/common/styles.html
deleted file mode 100644
index 8871d7a6..0000000
--- a/ash/webui/personalization_app/resources/common/styles.html
+++ /dev/null
@@ -1,326 +0,0 @@
-<template>
-  <style>
-    [hidden] {
-      /* The |hidden| attribute does not hide an element with an explicitly
-       * specified |display| property. Handle this by forcing display to |none|
-       * when the |hidden| attribute is present. */
-      display: none !important;
-    }
-    :host {
-      --personalization-app-grid-item-background-color: var(--google-grey-100);
-      --personalization-app-grid-item-border-radius: 12px;
-      --personalization-app-grid-item-height: 120px;
-      --personalization-app-grid-item-spacing: 16px;
-
-      --personalization-app-text-shadow-elevation-1: 0 1px 3px
-          rgba(0, 0, 0, 15%), 0 1px 2px rgba(0, 0, 0, 30%);
-
-      /* copied from |AshColorProvider| |kSecondToneOpacity| constant. */
-      --personalization-app-second-tone-opacity: 0.3;
-
-      --personalization-app-label-font: 500 13px/20px
-          var(--cros-font-family-google-sans);
-    }
-    @media (prefers-color-scheme: dark) {
-      :host {
-        --personalization-app-grid-item-background-color:
-          rgba(var(--google-grey-700-rgb), 0.3);
-      }
-    }
-    iron-list {
-      height: 100%;
-    }
-    .photo-container {
-      box-sizing: border-box;
-      height: calc(
-        var(--personalization-app-grid-item-height) +
-        var(--personalization-app-grid-item-spacing));
-      overflow: hidden;
-      padding: calc(var(--personalization-app-grid-item-spacing) / 2);
-      /* Media queries in trusted and untrusted code will resize to 25% at
-       * correct widths.  Subtract 0.34px to fix subpixel rounding issues with
-       * iron-list. This makes sure all photo containers on a row add up to at
-       * least 1px smaller than the parent width.*/
-      width: calc(100% / 3 - 0.34px);
-    }
-    .photo-container:focus-visible {
-      outline: none;
-    }
-    /* This extra position: relative element corrects for absolutely positioned
-       elements ignoring parent interior padding. */
-    .photo-inner-container {
-      align-items: center;
-      border-radius: var(--personalization-app-grid-item-border-radius);
-      cursor: pointer;
-      display: flex;
-      height: 100%;
-      justify-content: center;
-      overflow: hidden;
-      position: relative;
-      width: 100%;
-    }
-    @keyframes ripple {
-      /* 0 ms */
-      from {
-        opacity: 1;
-      }
-      /* 200 ms */
-      9% {
-        opacity: 0.15;
-      }
-      /* 350 ms */
-      15.8% {
-        opacity: 0.15;
-      }
-      /* 550 ms, hold for 83ms * 20 and then restart */
-      24.9% {
-        opacity: 1;
-      }
-      /* 2210 ms */
-      to {
-        opacity: 1;
-      }
-    }
-    .placeholder {
-      animation: 2210ms linear var(--animation-delay, 1s) infinite ripple;
-    }
-    .photo-inner-container:focus-visible,
-    .photo-loading-placeholder:focus-visible {
-      border: 2px solid var(--cros-focus-ring-color);
-      border-radius: 14px;
-      outline: none;
-    }
-    .photo-images-container {
-      background-color: var(--personalization-app-grid-item-background-color);
-      border: 1px solid rgba(0, 0, 0, 0.08);
-      border-radius: 12px;
-      box-sizing: border-box;
-      display: flex;
-      flex-flow: row wrap;
-      height: 100%;
-      /* stop img and gradient-mask from ignoring above border-radius. */
-      overflow: hidden;
-      position: relative;
-      width: 100%;
-    }
-    .photo-images-container img {
-      flex: 1 1 0;
-      height: 100%;
-      min-width: 50%;
-      object-fit: cover;
-      width: 100%;
-    }
-    .photo-images-container img.left {
-      clip-path: inset(0 50% 0 0);
-      position: absolute;
-    }
-    .photo-images-container img.right {
-      clip-path: inset(0 0 0 50%);
-      position: absolute;
-    }
-    @keyframes scale-up {
-      from {
-        transform: scale(0);
-      }
-      to {
-        transform: scale(1);
-      }
-    }
-    .photo-container iron-icon[icon='personalization:checkmark'] {
-      --iron-icon-height: 20px;
-      --iron-icon-width: 20px;
-      animation-duration: 200ms;
-      animation-name: scale-up;
-      animation-timing-function: cubic-bezier(0.40, 0.00, 0.20, 1.00);
-      left: 4px;
-      position: absolute;
-      top: 4px;
-    }
-    .photo-inner-container:not([aria-selected='true'])
-    iron-icon[icon='personalization:checkmark'] {
-      display: none;
-    }
-    .photo-inner-container[aria-selected='true'] {
-      background-color: rgba(var(--cros-icon-color-prominent-rgb),
-          var(--personalization-app-second-tone-opacity));
-      border-radius: 16px;
-    }
-    @keyframes resize {
-      100% {
-        height: calc(100% - 8px);
-        width: calc(100% - 8px);
-      }
-    }
-    .photo-inner-container[aria-selected='true'] .photo-images-container {
-      animation-duration: 200ms;
-      animation-fill-mode: forwards;
-      animation-name: resize;
-      animation-timing-function: cubic-bezier(0.40, 0.00, 0.20, 1.00);
-      border: 0;
-    }
-    .photo-inner-container:focus-visible:not([aria-selected='true'])
-    .photo-images-container {
-      height: calc(100% - 4px);
-      width: calc(100% - 4px);
-    }
-    cr-button {
-      border-color: var(--cros-button-stroke-color-secondary);
-      border-radius: 16px;
-    }
-    cr-button[aria-pressed=true],
-    cr-button[aria-selected=true] {
-      background-color: var(--cros-highlight-color);
-      border: 0;
-    }
-    cr-button + cr-button {
-      margin-inline-start: 8px;
-    }
-    .preview-container {
-      border: 1px solid var(--cros-separator-color);
-      border-radius: 16px;
-    }
-
-    .preview-text-container,
-    .preview-text-placeholder {
-      align-items: flex-start;
-      display: flex;
-      flex-flow: column nowrap;
-      margin: 0;
-    }
-
-    .preview-text-container {
-      justify-content: flex-end;
-    }
-
-    .preview-text-placeholder {
-      justify-content: center;
-    }
-
-    .placeholder {
-      background-color: var(--personalization-app-grid-item-background-color);
-      border-radius: 12px;
-    }
-
-    .preview-image-container {
-      border: 1px solid rgba(0, 0, 0, 0.08);
-      border-radius: 12px;
-      box-sizing: border-box;
-      overflow: hidden;
-      position: relative;
-    }
-
-    .preview-image {
-      height: 100%;
-      object-fit: cover;
-      width: 100%;
-    }
-
-    .preview-text-container > *,
-    .preview-text-placeholder > * {
-      margin: 0;
-      max-width: 100%;
-      overflow: hidden;
-      text-overflow: ellipsis;
-      white-space: nowrap;
-    }
-
-    .preview-text-container > * + * {
-      margin-top: 4px;
-    }
-
-    .preview-text-placeholder > * + * {
-      margin-top: 8px;
-    }
-
-    .preview-text-container > span:first-child {
-      color: var(--cros-text-color-secondary);
-      font: var(--cros-body-2-font);
-    }
-
-    .preview-text-placeholder > .placeholder:first-child {
-      /* Each row is 83 ms after the prior element. */
-      --animation-delay: calc(1s + 83ms);
-      width: 20%;
-      height: 20px;
-    }
-
-    .preview-text-container > span:nth-child(2) {
-      color: var(--cros-text-color-primary);
-      font: var(--cros-display-6-font);
-    }
-
-    .preview-text-placeholder > .placeholder:nth-child(2) {
-      --animation-delay: calc(1s + 83ms * 2);
-      width: 75%;
-      height: 24px;
-    }
-
-    .preview-text-container > span:nth-child(n+3) {
-      color: var(--cros-text-color-secondary);
-      font: var(--cros-body-1-font);
-    }
-
-    .preview-text-placeholder > .placeholder:nth-child(n+3) {
-      --animation-delay: calc(1s + 83ms * 3);
-      width: 33%;
-      height: 20px;
-    }
-
-    .ambient-subpage-element-title {
-      color: var(--cros-text-color-primary);
-      font: var(--personalization-app-label-font);
-      margin: 34px 8px 16px 8px;
-    }
-
-    .ambient-toggle-row-container {
-      border: 1px solid var(--cros-separator-color);
-      border-radius: 8px;
-      display: flex;
-      flex-flow: column nowrap;
-      height: 48px;
-      width: 100%;
-    }
-
-    .ambient-toggle-row {
-      align-items: center;
-      display: flex;
-      flex: 1;
-      flex-flow: row nowrap;
-      justify-content: space-between;
-      margin: 0 20px;
-    }
-
-    .ambient-toggle-row + .ambient-toggle-row {
-      border-top: 1px solid var(--cros-separator-color);
-    }
-
-    .ambient-toggle-row > p {
-      font: var(--cros-body-1-font);
-      height: 20px;
-      margin: 0;
-    }
-
-    .clickable {
-      cursor: pointer;
-    }
-
-    paper-tooltip::part(tooltip) {
-      align-items: flex-end;
-      display: flex;
-      flex-direction: row;
-      height: 18px;
-      padding: 3px 8px;
-    }
-
-    paper-tooltip {
-      --paper-tooltip-background: var(--cros-tooltip-background-color);
-      --paper-tooltip-text-color: var(--cros-tooltip-label-color);
-    }
-
-    paper-tooltip span {
-      align-items: center;
-      display: flex;
-      font: var(--cros-body-2-font);
-    }
-  </style>
-</template>
diff --git a/ash/webui/personalization_app/resources/common/styles.ts b/ash/webui/personalization_app/resources/common/styles.ts
deleted file mode 100644
index 9d40053..0000000
--- a/ash/webui/personalization_app/resources/common/styles.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright 2021 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.
-
-/**
- * @fileoverview Common styles for polymer components in both trusted and
- * untrusted code.
- */
-
-// Force tsc to consider this file a module.
-export {};
-
-const template = document.createElement('dom-module');
-template.innerHTML = `{__html_template__}`;
-template.register('common-style');
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/album_list_element.ts b/ash/webui/personalization_app/resources/trusted/ambient/album_list_element.ts
index 5782bfa7..9f975da1 100644
--- a/ash/webui/personalization_app/resources/trusted/ambient/album_list_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/ambient/album_list_element.ts
@@ -7,7 +7,7 @@
  */
 
 import 'chrome://resources/polymer/v3_0/iron-list/iron-list.js';
-import '../../common/styles.js';
+import '../../common/common_style.css.js';
 
 import {IronListElement} from 'chrome://resources/polymer/v3_0/iron-list/iron-list.js';
 import {afterNextRender} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
@@ -16,6 +16,7 @@
 import {AmbientModeAlbum, TopicSource} from '../personalization_app.mojom-webui.js';
 import {WithPersonalizationStore} from '../personalization_store.js';
 import {isRecentHighlightsAlbum} from '../utils.js';
+
 import {getTemplate} from './album_list_element.html.js';
 
 export interface AlbumList {
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/albums_subpage_element.ts b/ash/webui/personalization_app/resources/trusted/ambient/albums_subpage_element.ts
index ee5878e..2f251d3648 100644
--- a/ash/webui/personalization_app/resources/trusted/ambient/albums_subpage_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/ambient/albums_subpage_element.ts
@@ -12,7 +12,7 @@
 import 'chrome://resources/cr_elements/shared_vars_css.m.js';
 import './album_list_element.js';
 import './art_album_dialog_element.js';
-import '../../common/styles.js';
+import '../../common/common_style.css.js';
 
 import {assert} from 'chrome://resources/js/assert.m.js';
 
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/ambient_preview_element.ts b/ash/webui/personalization_app/resources/trusted/ambient/ambient_preview_element.ts
index 6150379f..33ef4cac 100644
--- a/ash/webui/personalization_app/resources/trusted/ambient/ambient_preview_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/ambient/ambient_preview_element.ts
@@ -10,11 +10,12 @@
 import 'chrome://resources/cr_elements/cr_auto_img/cr_auto_img.js';
 import 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 import 'chrome://resources/polymer/v3_0/paper-tooltip/paper-tooltip.js';
-import '../../common/styles.js';
-import '../cros_button_style.js';
+import '../../common/common_style.css.js';
+import '../cros_button_style.css.js';
 
 import {assert} from 'chrome://resources/js/assert_ts.js';
 import {Url} from 'chrome://resources/mojo/url/mojom/url.mojom-webui.js';
+
 import {isNonEmptyArray} from '../../common/utils.js';
 import {AmbientModeAlbum, TopicSource} from '../personalization_app.mojom-webui.js';
 import {logAmbientModeOptInUMA} from '../personalization_metrics_logger.js';
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/ambient_subpage_element.ts b/ash/webui/personalization_app/resources/trusted/ambient/ambient_subpage_element.ts
index ffe5421..9add348 100644
--- a/ash/webui/personalization_app/resources/trusted/ambient/ambient_subpage_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/ambient/ambient_subpage_element.ts
@@ -7,7 +7,7 @@
  * the ambient mode settings.
  */
 
-import '../../common/styles.js';
+import '../../common/common_style.css.js';
 import './albums_subpage_element.js';
 import './ambient_weather_element.js';
 import './ambient_preview_element.js';
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/ambient_weather_element.ts b/ash/webui/personalization_app/resources/trusted/ambient/ambient_weather_element.ts
index 1a5cd474..1665ceb 100644
--- a/ash/webui/personalization_app/resources/trusted/ambient/ambient_weather_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/ambient/ambient_weather_element.ts
@@ -7,7 +7,7 @@
  * behaviors similar to a radio button group, e.g. single selection.
  */
 
-import '../../common/styles.js';
+import '../../common/common_style.css.js';
 import 'chrome://resources/cr_elements/cr_radio_button/cr_radio_button.m.js';
 import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.m.js';
 import 'chrome://resources/cr_elements/shared_style_css.m.js';
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/animation_theme_item_element.ts b/ash/webui/personalization_app/resources/trusted/ambient/animation_theme_item_element.ts
index c7fff7d..91366c1 100644
--- a/ash/webui/personalization_app/resources/trusted/ambient/animation_theme_item_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/ambient/animation_theme_item_element.ts
@@ -6,7 +6,7 @@
  * @fileoverview The element for displaying an animation theme.
  */
 
-import '../../common/styles.js';
+import '../../common/common_style.css.js';
 import 'chrome://resources/cr_elements/cr_auto_img/cr_auto_img.js';
 import 'chrome://resources/cr_elements/shared_vars_css.m.js';
 
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/animation_theme_list_element.ts b/ash/webui/personalization_app/resources/trusted/ambient/animation_theme_list_element.ts
index ef3b0952..a78ab87 100644
--- a/ash/webui/personalization_app/resources/trusted/ambient/animation_theme_list_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/ambient/animation_theme_list_element.ts
@@ -6,7 +6,7 @@
  * @fileoverview The element for displaying a list of animation themes.
  */
 import './animation_theme_item_element.js';
-import '../../common/styles.js';
+import '../../common/common_style.css.js';
 
 import {AnimationTheme} from '../personalization_app.mojom-webui.js';
 import {WithPersonalizationStore} from '../personalization_store.js';
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/toggle_row_element.ts b/ash/webui/personalization_app/resources/trusted/ambient/toggle_row_element.ts
index 9ef47a4..5968268 100644
--- a/ash/webui/personalization_app/resources/trusted/ambient/toggle_row_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/ambient/toggle_row_element.ts
@@ -6,7 +6,7 @@
  * @fileoverview This component displays a description text and a toggle button.
  */
 
-import '../../common/styles.js';
+import '../../common/common_style.css.js';
 import 'chrome://resources/cr_elements/cr_toggle/cr_toggle.m.js';
 
 import {CrToggleElement} from 'chrome://resources/cr_elements/cr_toggle/cr_toggle.m.js';
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/topic_source_list_element.ts b/ash/webui/personalization_app/resources/trusted/ambient/topic_source_list_element.ts
index 67b9742..953e534 100644
--- a/ash/webui/personalization_app/resources/trusted/ambient/topic_source_list_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/ambient/topic_source_list_element.ts
@@ -7,7 +7,7 @@
  * behaviors similar to a radio button group, e.g. single selection.
  */
 
-import '../../common/styles.js';
+import '../../common/common_style.css.js';
 import './topic_source_item_element.js';
 import 'chrome://resources/polymer/v3_0/iron-list/iron-list.js';
 
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/zero_state_element.ts b/ash/webui/personalization_app/resources/trusted/ambient/zero_state_element.ts
index bd456cc1..345173e 100644
--- a/ash/webui/personalization_app/resources/trusted/ambient/zero_state_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/ambient/zero_state_element.ts
@@ -6,7 +6,7 @@
  * @fileoverview Polymer element that displays the Ambient zero state.
  */
 
-import '../../common/styles.js';
+import '../../common/common_style.css.js';
 import 'chrome://resources/polymer/v3_0/iron-media-query/iron-media-query.js';
 
 import {WithPersonalizationStore} from '../personalization_store.js';
diff --git a/ash/webui/personalization_app/resources/trusted/cros_button_style.css b/ash/webui/personalization_app/resources/trusted/cros_button_style.css
new file mode 100644
index 0000000..dd1bb48
--- /dev/null
+++ b/ash/webui/personalization_app/resources/trusted/cros_button_style.css
@@ -0,0 +1,74 @@
+/* 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. */
+
+/* #css_wrapper_metadata_start
+ * #type=style
+ * #css_wrapper_metadata_end */
+
+cr-button.primary {
+  background-color: var(--cros-button-background-color-primary);
+  --text-color: var(--cros-button-label-color-primary);
+  --ink-color: var(--cros-button-ripple-color-primary);
+  --hover-bg-color: var(--cros-button-background-color-primary-hover-preblended);
+  --disabled-bg: var(--cros-button-background-color-primary-disabled);
+  --disabled-text-color: var(--cros-button-label-color-primary-disabled);
+}
+
+cr-button.primary:hover {
+  background-color: var(--cros-button-background-color-primary-hover-preblended);
+}
+
+cr-button.secondary {
+  --text-color: var(--cros-button-label-color-secondary);
+  --border-color: var(--cros-button-stroke-color-secondary);
+  --ink-color: var(--cros-button-ripple-color-secondary);
+  --hover-border-color: var(--cros-button-stroke-color-secondary-hover);
+  --hover-bg-color: var(--cros-button-background-color-secondary-hover);
+  --disabled-text-color: var(--cros-button-label-color-secondary-disabled);
+  --disabled-border-color: var(--cros-button-stroke-color-secondary-disabled);
+}
+
+cr-button.secondary:hover {
+  background-color: var(--cros-button-background-color-secondary-hover);
+}
+
+cr-icon-button:focus-visible,
+cr-button:focus-visible {
+  box-shadow: none;
+  outline: 2px solid var(--cros-focus-ring-color);
+}
+
+cr-icon-button:hover,
+cr-button:hover {
+  background-color: var(--cros-ripple-color);
+  box-shadow: none;
+}
+
+cr-button[aria-pressed=true],
+cr-button[aria-selected=true] {
+  background-color: var(--cros-button-background-color-primary) !important;
+}
+
+cr-button[aria-pressed=true] .text,
+cr-button[aria-selected=true] .text {
+  color: var(--cros-button-label-color-primary) !important;
+}
+
+cr-button[aria-pressed=true] iron-icon,
+cr-button[aria-selected=true] iron-icon {
+  --iron-icon-fill-color: var(--cros-button-label-color-primary) !important;
+}
+
+cr-button:focus-visible {
+  outline: 2px solid var(--cros-focus-ring-color);
+}
+
+iron-icon {
+  --iron-icon-height: 20px;
+  --iron-icon-width: 20px;
+}
+
+cr-icon-button {
+  --cr-icon-button-fill-color: var(--cros-menu-icon-color);
+}
diff --git a/ash/webui/personalization_app/resources/trusted/cros_button_style.html b/ash/webui/personalization_app/resources/trusted/cros_button_style.html
deleted file mode 100644
index 287b4595..0000000
--- a/ash/webui/personalization_app/resources/trusted/cros_button_style.html
+++ /dev/null
@@ -1,70 +0,0 @@
-<template>
-  <style>
-    cr-button.primary {
-      background-color: var(--cros-button-background-color-primary);
-      --text-color: var(--cros-button-label-color-primary);
-      --ink-color: var(--cros-button-ripple-color-primary);
-      --hover-bg-color: var(--cros-button-background-color-primary-hover-preblended);
-      --disabled-bg: var(--cros-button-background-color-primary-disabled);
-      --disabled-text-color: var(--cros-button-label-color-primary-disabled);
-    }
-
-    cr-button.primary:hover {
-      background-color: var(--cros-button-background-color-primary-hover-preblended);
-    }
-
-    cr-button.secondary {
-      --text-color: var(--cros-button-label-color-secondary);
-      --border-color: var(--cros-button-stroke-color-secondary);
-      --ink-color: var(--cros-button-ripple-color-secondary);
-      --hover-border-color: var(--cros-button-stroke-color-secondary-hover);
-      --hover-bg-color: var(--cros-button-background-color-secondary-hover);
-      --disabled-text-color: var(--cros-button-label-color-secondary-disabled);
-      --disabled-border-color: var(--cros-button-stroke-color-secondary-disabled);
-    }
-
-    cr-button.secondary:hover {
-      background-color: var(--cros-button-background-color-secondary-hover);
-    }
-
-    cr-icon-button:focus-visible,
-    cr-button:focus-visible {
-      box-shadow: none;
-      outline: 2px solid var(--cros-focus-ring-color);
-    }
-
-    cr-icon-button:hover,
-    cr-button:hover {
-      background-color: var(--cros-ripple-color);
-      box-shadow: none;
-    }
-
-    cr-button[aria-pressed=true],
-    cr-button[aria-selected=true] {
-      background-color: var(--cros-button-background-color-primary) !important;
-    }
-
-    cr-button[aria-pressed=true] .text,
-    cr-button[aria-selected=true] .text {
-      color: var(--cros-button-label-color-primary) !important;
-    }
-
-    cr-button[aria-pressed=true] iron-icon,
-    cr-button[aria-selected=true] iron-icon {
-      --iron-icon-fill-color: var(--cros-button-label-color-primary) !important;
-    }
-
-    cr-button:focus-visible {
-      outline: 2px solid var(--cros-focus-ring-color);
-    }
-
-    iron-icon {
-      --iron-icon-height: 20px;
-      --iron-icon-width: 20px;
-    }
-
-    cr-icon-button {
-      --cr-icon-button-fill-color: var(--cros-menu-icon-color);
-    }
-  </style>
-</template>
diff --git a/ash/webui/personalization_app/resources/trusted/cros_button_style.ts b/ash/webui/personalization_app/resources/trusted/cros_button_style.ts
deleted file mode 100644
index 17aa3c25..0000000
--- a/ash/webui/personalization_app/resources/trusted/cros_button_style.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-// 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.
-
-/**
- * @fileoverview Common styles for adapting cr-button with cros colors.
- */
-
-// Force tsc to consider this file a module.
-export {};
-
-const template = document.createElement('dom-module');
-template.innerHTML = `{__html_template__}`;
-template.register('cros-button-style');
diff --git a/ash/webui/personalization_app/resources/trusted/keyboard_backlight/keyboard_backlight_element.ts b/ash/webui/personalization_app/resources/trusted/keyboard_backlight/keyboard_backlight_element.ts
index 9683ca0b..2d1b015 100644
--- a/ash/webui/personalization_app/resources/trusted/keyboard_backlight/keyboard_backlight_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/keyboard_backlight/keyboard_backlight_element.ts
@@ -6,8 +6,8 @@
 import 'chrome://resources/polymer/v3_0/iron-selector/iron-selector.js';
 import 'chrome://resources/polymer/v3_0/paper-ripple/paper-ripple.js';
 import 'chrome://resources/polymer/v3_0/paper-tooltip/paper-tooltip.js';
-import '../../common/styles.js';
-import '../cros_button_style.js';
+import '../../common/common_style.css.js';
+import '../cros_button_style.css.js';
 
 import {assert} from 'chrome://resources/js/assert_ts.js';
 import {SkColor} from 'chrome://resources/mojo/skia/public/mojom/skcolor.mojom-webui.js';
diff --git a/ash/webui/personalization_app/resources/trusted/personalization_breadcrumb_element.ts b/ash/webui/personalization_app/resources/trusted/personalization_breadcrumb_element.ts
index eb97158..67d43de1 100644
--- a/ash/webui/personalization_app/resources/trusted/personalization_breadcrumb_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/personalization_breadcrumb_element.ts
@@ -15,8 +15,8 @@
 import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
 import 'chrome://resources/polymer/v3_0/iron-a11y-keys/iron-a11y-keys.js';
 import 'chrome://resources/polymer/v3_0/iron-selector/iron-selector.js';
-import '../common/styles.js';
-import './cros_button_style.js';
+import '../common/common_style.css.js';
+import './cros_button_style.css.js';
 
 import {IronA11yKeysElement} from 'chrome://resources/polymer/v3_0/iron-a11y-keys/iron-a11y-keys.js';
 import {IronSelectorElement} from 'chrome://resources/polymer/v3_0/iron-selector/iron-selector.js';
diff --git a/ash/webui/personalization_app/resources/trusted/personalization_main_element.ts b/ash/webui/personalization_app/resources/trusted/personalization_main_element.ts
index 6eb752c..c126df5 100644
--- a/ash/webui/personalization_app/resources/trusted/personalization_main_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/personalization_main_element.ts
@@ -7,7 +7,7 @@
  * the personalization hub.
  */
 
-import './cros_button_style.js';
+import './cros_button_style.css.js';
 
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 
diff --git a/ash/webui/personalization_app/resources/trusted/personalization_theme_element.ts b/ash/webui/personalization_app/resources/trusted/personalization_theme_element.ts
index 550a775..01793a9 100644
--- a/ash/webui/personalization_app/resources/trusted/personalization_theme_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/personalization_theme_element.ts
@@ -11,13 +11,15 @@
 import 'chrome://resources/polymer/v3_0/iron-iconset-svg/iron-iconset-svg.js';
 import 'chrome://resources/polymer/v3_0/iron-a11y-keys/iron-a11y-keys.js';
 import 'chrome://resources/polymer/v3_0/iron-selector/iron-selector.js';
-import '../common/styles.js';
-import './cros_button_style.js';
+import '../common/common_style.css.js';
+import './cros_button_style.css.js';
 
 import {CrButtonElement} from 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
 import {IronA11yKeysElement} from 'chrome://resources/polymer/v3_0/iron-a11y-keys/iron-a11y-keys.js';
 import {IronSelectorElement} from 'chrome://resources/polymer/v3_0/iron-selector/iron-selector.js';
+
 import {isSelectionEvent} from '../common/utils.js';
+
 import {WithPersonalizationStore} from './personalization_store.js';
 import {getTemplate} from './personalization_theme_element.html.js';
 import {initializeData, setColorModeAutoSchedule, setColorModePref} from './theme/theme_controller.js';
diff --git a/ash/webui/personalization_app/resources/trusted/user/avatar_camera_element.ts b/ash/webui/personalization_app/resources/trusted/user/avatar_camera_element.ts
index 4b3d3c5..07d0af2 100644
--- a/ash/webui/personalization_app/resources/trusted/user/avatar_camera_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/user/avatar_camera_element.ts
@@ -10,7 +10,7 @@
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
 import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
 import 'chrome://resources/polymer/v3_0/paper-spinner/paper-spinner-lite.js';
-import '../cros_button_style.js';
+import '../cros_button_style.css.js';
 
 import {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
 import {assertInstanceof, assertNotReached} from 'chrome://resources/js/assert_ts.js';
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_albums_element.ts b/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_albums_element.ts
index a02deaef..1af300e 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_albums_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_albums_element.ts
@@ -8,8 +8,8 @@
 
 import 'chrome://resources/polymer/v3_0/iron-list/iron-list.js';
 import 'chrome://resources/polymer/v3_0/iron-scroll-threshold/iron-scroll-threshold.js';
-import './styles.js';
-import '../../common/styles.js';
+import './trusted_style.css.js';
+import '../../common/common_style.css.js';
 
 import {assert} from 'chrome://resources/js/assert_ts.js';
 import {IronListElement} from 'chrome://resources/polymer/v3_0/iron-list/iron-list.js';
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_collection_element.ts b/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_collection_element.ts
index fd5dd1b..320d516 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_collection_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_collection_element.ts
@@ -8,8 +8,8 @@
  */
 
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
-import './styles.js';
-import '../../common/styles.js';
+import './trusted_style.css.js';
+import '../../common/common_style.css.js';
 
 import {assertNotReached} from 'chrome://resources/js/assert_ts.js';
 
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_photos_by_album_id_element.ts b/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_photos_by_album_id_element.ts
index e3d4764a..449a6505 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_photos_by_album_id_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_photos_by_album_id_element.ts
@@ -9,8 +9,8 @@
 
 import 'chrome://resources/polymer/v3_0/iron-list/iron-list.js';
 import 'chrome://resources/polymer/v3_0/iron-scroll-threshold/iron-scroll-threshold.js';
-import './styles.js';
-import '../../common/styles.js';
+import './trusted_style.css.js';
+import '../../common/common_style.css.js';
 
 import {assert} from 'chrome://resources/js/assert_ts.js';
 import {IronListElement} from 'chrome://resources/polymer/v3_0/iron-list/iron-list.js';
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_photos_element.ts b/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_photos_element.ts
index 9f7f766..8329bbff 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_photos_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_photos_element.ts
@@ -8,8 +8,8 @@
 
 import 'chrome://resources/polymer/v3_0/iron-list/iron-list.js';
 import 'chrome://resources/polymer/v3_0/iron-scroll-threshold/iron-scroll-threshold.js';
-import './styles.js';
-import '../../common/styles.js';
+import './trusted_style.css.js';
+import '../../common/common_style.css.js';
 
 import {assert} from 'chrome://resources/js/assert_ts.js';
 import {IronListElement} from 'chrome://resources/polymer/v3_0/iron-list/iron-list.js';
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_zero_state_element.ts b/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_zero_state_element.ts
index b66d3b5..743dd5f 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_zero_state_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_zero_state_element.ts
@@ -6,8 +6,8 @@
  * @fileoverview Polymer element that displays the Google Photos zero state.
  */
 
-import './styles.js';
-import '../../common/styles.js';
+import './trusted_style.css.js';
+import '../../common/common_style.css.js';
 
 import {WithPersonalizationStore} from '../personalization_store.js';
 
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/index.ts b/ash/webui/personalization_app/resources/trusted/wallpaper/index.ts
index 451abd7..774b046 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/index.ts
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/index.ts
@@ -18,7 +18,7 @@
 import './wallpaper_subpage_element.js';
 import '../../untrusted/collections_grid.js';
 import '../../untrusted/images_grid.js';
-import './styles.js';
+import './trusted_style.css.js';
 
 import {WallpaperObserver} from './wallpaper_observer.js';
 
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/local_images_element.ts b/ash/webui/personalization_app/resources/trusted/wallpaper/local_images_element.ts
index fe09a558..ee25e29 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/local_images_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/local_images_element.ts
@@ -11,9 +11,9 @@
 
 import 'chrome://resources/polymer/v3_0/iron-list/iron-list.js';
 import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
-import './styles.js';
-import '../../common/icons.js';
-import '../../common/styles.js';
+import './trusted_style.css.js';
+import '../../common/icons.html.js';
+import '../../common/common_style.css.js';
 
 import {assert, assertNotReached} from 'chrome://resources/js/assert.m.js';
 import {FilePath} from 'chrome://resources/mojo/mojo/public/mojom/base/file_path.mojom-webui.js';
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/styles.html b/ash/webui/personalization_app/resources/trusted/wallpaper/styles.html
deleted file mode 100644
index a29c0ad..0000000
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/styles.html
+++ /dev/null
@@ -1,23 +0,0 @@
-<template>
-  <style>
-    /* Use !important to make sure there are no css ordering issues.
-     * Subtract 0.25px to fix subpixel rounding issues with iron-list. This
-     * makes sure all photo containers on a row add up to at least 1px smaller
-     * than the parent width.*/
-
-    @media (min-width: 720px) {
-      .photo-container {
-        width: calc(25% - 0.25px) !important;
-      }
-    }
-    main {
-      height: 100%;
-      width: 100%;
-    }
-    main:focus,
-    main:focus-visible,
-    main:focus-within {
-      outline: none;
-    }
-  </style>
-</template>
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/styles.ts b/ash/webui/personalization_app/resources/trusted/wallpaper/styles.ts
deleted file mode 100644
index 71f34935..0000000
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/styles.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright 2021 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.
-
-/**
- * @fileoverview styles for polymer components in trusted code.
- */
-
-import 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-import 'chrome://resources/cr_elements/chromeos/cros_color_overrides.m.js';
-import 'chrome://resources/cr_elements/shared_vars_css.m.js';
-import 'chrome://resources/polymer/v3_0/paper-styles/color.js';
-
-const template = document.createElement('dom-module');
-template.innerHTML = `{__html_template__}`;
-template.register('trusted-style');
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/trusted_style.css b/ash/webui/personalization_app/resources/trusted/wallpaper/trusted_style.css
new file mode 100644
index 0000000..f2bc1d28
--- /dev/null
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/trusted_style.css
@@ -0,0 +1,32 @@
+/* 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. */
+
+/* #css_wrapper_metadata_start
+ * #type=style
+ * import=chrome://resources/cr_elements/chromeos/cros_color_overrides.m.js
+ * import=chrome://resources/cr_elements/shared_vars_css.m.js
+ * import=chrome://resources/polymer/v3_0/paper-styles/color.js
+ * #css_wrapper_metadata_end */
+
+/* Use !important to make sure there are no css ordering issues.
+ * Subtract 0.25px to fix subpixel rounding issues with iron-list. This
+ * makes sure all photo containers on a row add up to at least 1px smaller
+ * than the parent width.*/
+
+@media (min-width: 720px) {
+  .photo-container {
+    width: calc(25% - 0.25px) !important;
+  }
+}
+
+main {
+  height: 100%;
+  width: 100%;
+}
+
+main:focus,
+main:focus-visible,
+main:focus-within {
+  outline: none;
+}
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_collections_element.ts b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_collections_element.ts
index 912d4b4..56a8112 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_collections_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_collections_element.ts
@@ -8,7 +8,7 @@
  * objects.
  */
 
-import './styles.js';
+import './trusted_style.css.js';
 
 import {FilePath} from 'chrome://resources/mojo/mojo/public/mojom/base/file_path.mojom-webui.js';
 
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_fullscreen_element.ts b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_fullscreen_element.ts
index ef10e41fc3..fc8d9690 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_fullscreen_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_fullscreen_element.ts
@@ -9,7 +9,7 @@
 
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
 import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
-import '../../common/icons.js';
+import '../../common/icons.html.js';
 
 import {assert} from 'chrome://resources/js/assert_ts.js';
 
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_grid_item_element.ts b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_grid_item_element.ts
index da9bcae..8a96f134 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_grid_item_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_grid_item_element.ts
@@ -7,10 +7,12 @@
  */
 
 import 'chrome://resources/cr_elements/cr_auto_img/cr_auto_img.js';
-import '../../common/styles.js';
+import '../../common/common_style.css.js';
 
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
 import {getLoadingPlaceholderAnimationDelay} from '../../common/utils.js';
+
 import {getTemplate} from './wallpaper_grid_item_element.html.js';
 
 export interface WallpaperGridItem {
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_images_element.ts b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_images_element.ts
index 09f579f..e3efd94 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_images_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_images_element.ts
@@ -10,7 +10,7 @@
  */
 
 import 'chrome://resources/polymer/v3_0/iron-media-query/iron-media-query.js';
-import './styles.js';
+import './trusted_style.css.js';
 
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_preview_element.ts b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_preview_element.ts
index 02d5803..16d1ea3 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_preview_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_preview_element.ts
@@ -9,10 +9,10 @@
 
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
 import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
-import '../../common/icons.js';
-import '../../common/styles.js';
-import './styles.js';
-import '../cros_button_style.js';
+import '../../common/icons.html.js';
+import '../../common/common_style.css.js';
+import './trusted_style.css.js';
+import '../cros_button_style.css.js';
 
 import {getLocalStorageAttribution, isNonEmptyArray} from '../../common/utils.js';
 import {CurrentWallpaper, WallpaperProviderInterface, WallpaperType} from '../personalization_app.mojom-webui.js';
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_selected_element.ts b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_selected_element.ts
index 7391297..0ec26b9f 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_selected_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_selected_element.ts
@@ -10,8 +10,8 @@
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
 import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
 import 'chrome://resources/polymer/v3_0/iron-iconset-svg/iron-iconset-svg.js';
-import '../../common/icons.js';
-import './styles.js';
+import '../../common/icons.html.js';
+import './trusted_style.css.js';
 
 import {assert} from 'chrome://resources/js/assert_ts.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
diff --git a/ash/webui/personalization_app/resources/untrusted/collections_grid.ts b/ash/webui/personalization_app/resources/untrusted/collections_grid.ts
index 20d7a70..e4a5b6f4 100644
--- a/ash/webui/personalization_app/resources/untrusted/collections_grid.ts
+++ b/ash/webui/personalization_app/resources/untrusted/collections_grid.ts
@@ -4,7 +4,7 @@
 
 import '//resources/polymer/v3_0/iron-list/iron-list.js';
 import './setup.js';
-import '../trusted/wallpaper/styles.js';
+import '../trusted/wallpaper/trusted_style.css.js';
 
 import {loadTimeData} from '//resources/js/load_time_data.m.js';
 import {afterNextRender, PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
diff --git a/ash/webui/personalization_app/resources/untrusted/images_grid.ts b/ash/webui/personalization_app/resources/untrusted/images_grid.ts
index 17ac993..cbdc3e2 100644
--- a/ash/webui/personalization_app/resources/untrusted/images_grid.ts
+++ b/ash/webui/personalization_app/resources/untrusted/images_grid.ts
@@ -5,7 +5,7 @@
 import '//resources/cr_elements/cr_auto_img/cr_auto_img.js';
 import '//resources/polymer/v3_0/iron-list/iron-list.js';
 import './setup.js';
-import '../trusted/wallpaper/styles.js';
+import '../trusted/wallpaper/trusted_style.css.js';
 
 import {assertNotReached} from '//resources/js/assert.m.js';
 import {afterNextRender, PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
diff --git a/ash/webui/personalization_app/resources/untrusted/setup.ts b/ash/webui/personalization_app/resources/untrusted/setup.ts
index 1d218df..4dc1fe8 100644
--- a/ash/webui/personalization_app/resources/untrusted/setup.ts
+++ b/ash/webui/personalization_app/resources/untrusted/setup.ts
@@ -11,6 +11,6 @@
 // guarantee that polymer and certain polymer elements are loaded first.
 import '//resources/polymer/v3_0/iron-icon/iron-icon.js';
 import '//resources/cr_elements/shared_vars_css.m.js';
-import '../common/icons.js';
-import '../common/styles.js';
+import '../common/icons.html.js';
+import '../common/common_style.css.js';
 import '/strings.m.js';
diff --git a/ash/wm/desks/desks_textfield.cc b/ash/wm/desks/desks_textfield.cc
index ce2cd11..60abe33 100644
--- a/ash/wm/desks/desks_textfield.cc
+++ b/ash/wm/desks/desks_textfield.cc
@@ -86,6 +86,10 @@
   return event.key_code() == ui::VKEY_TAB;
 }
 
+std::u16string DesksTextfield::GetTooltipText(const gfx::Point& p) const {
+  return GetPreferredSize().width() > width() ? GetText() : std::u16string();
+}
+
 void DesksTextfield::GetAccessibleNodeData(ui::AXNodeData* node_data) {
   Textfield::GetAccessibleNodeData(node_data);
   node_data->SetName(GetAccessibleName());
diff --git a/ash/wm/desks/desks_textfield.h b/ash/wm/desks/desks_textfield.h
index 1507427d..c57eaa7 100644
--- a/ash/wm/desks/desks_textfield.h
+++ b/ash/wm/desks/desks_textfield.h
@@ -38,6 +38,7 @@
   gfx::Size CalculatePreferredSize() const override;
   void SetBorder(std::unique_ptr<views::Border> b) override;
   bool SkipDefaultKeyEventProcessing(const ui::KeyEvent& event) override;
+  std::u16string GetTooltipText(const gfx::Point& p) const override;
   void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
   void OnMouseEntered(const ui::MouseEvent& event) override;
   void OnMouseExited(const ui::MouseEvent& event) override;
diff --git a/ash/wm/desks/desks_unittests.cc b/ash/wm/desks/desks_unittests.cc
index 9fdb8df..3a726122 100644
--- a/ash/wm/desks/desks_unittests.cc
+++ b/ash/wm/desks/desks_unittests.cc
@@ -501,6 +501,36 @@
   controller->RemoveObserver(&observer);
 }
 
+TEST_F(DesksTest, DesksTextfieldAddTooltipText) {
+  NewDesk();
+
+  auto* controller = DesksController::Get();
+
+  // Set the first desk with a name which is short enough to be fit in the desk
+  // name view.
+  controller->desks()[0]->SetName(u"test1", /*set_by_user=*/true);
+
+  // Set the second desk with a name which is long enough to be truncated in the
+  // desk name view.
+  std::u16string desk_name2(
+      u"test2 a very long desk name to test tooltip text");
+  controller->desks()[1]->SetName(desk_name2, /*set_by_user=*/true);
+
+  // Start overview.
+  auto* overview_controller = Shell::Get()->overview_controller();
+  EnterOverview();
+  EXPECT_TRUE(overview_controller->InOverviewSession());
+
+  // Expect there to be no tooltip when the desk name is short enough.
+  auto* desks_bar_view =
+      GetOverviewGridForRoot(Shell::GetPrimaryRootWindow())->desks_bar_view();
+  auto* desk_name_view1 = desks_bar_view->mini_views()[0]->desk_name_view();
+  EXPECT_TRUE(desk_name_view1->GetTooltipText(gfx::Point()).empty());
+
+  auto* desk_name_view2 = desks_bar_view->mini_views()[1]->desk_name_view();
+  EXPECT_EQ(desk_name2, desk_name_view2->GetTooltipText(gfx::Point()));
+}
+
 TEST_F(DesksTest, DesksBarViewDeskCreation) {
   auto* controller = DesksController::Get();
 
diff --git a/ash/wm/overview/overview_test_base.cc b/ash/wm/overview/overview_test_base.cc
index 61c3f00..ae05f19 100644
--- a/ash/wm/overview/overview_test_base.cc
+++ b/ash/wm/overview/overview_test_base.cc
@@ -177,8 +177,8 @@
   cache_ = std::make_unique<apps::AppRegistryCache>();
   desk_model_ = std::make_unique<desks_storage::LocalDeskDataManager>(
       user_data_temp_dir_.GetPath(), account_id_);
+  base::RunLoop().RunUntilIdle();
   desk_model_->SetExcludeSaveAndRecallDeskInMaxEntryCountForTesting(false);
-  desk_model_->EnsureCacheIsLoaded();
   desks_storage::desk_template_util::PopulateAppRegistryCache(account_id_,
                                                               cache_.get());
   static_cast<TestDesksTemplatesDelegate*>(
diff --git a/base/BUILD.gn b/base/BUILD.gn
index 58e6fcb7..5ad34ee 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -592,6 +592,8 @@
     "sampling_heap_profiler/poisson_allocation_sampler.h",
     "sampling_heap_profiler/sampling_heap_profiler.cc",
     "sampling_heap_profiler/sampling_heap_profiler.h",
+    "scoped_add_feature_flags.cc",
+    "scoped_add_feature_flags.h",
     "scoped_clear_last_error.h",
     "scoped_environment_variable_override.cc",
     "scoped_environment_variable_override.h",
@@ -3173,6 +3175,7 @@
     "run_loop_unittest.cc",
     "safe_numerics_unittest.cc",
     "sampling_heap_profiler/lock_free_address_hash_set_unittest.cc",
+    "scoped_add_feature_flags_unittest.cc",
     "scoped_clear_last_error_unittest.cc",
     "scoped_generic_unittest.cc",
     "scoped_multi_source_observation_unittest.cc",
diff --git a/base/scoped_add_feature_flags.cc b/base/scoped_add_feature_flags.cc
new file mode 100644
index 0000000..ce57399
--- /dev/null
+++ b/base/scoped_add_feature_flags.cc
@@ -0,0 +1,88 @@
+// Copyright 2019 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 "base/scoped_add_feature_flags.h"
+
+#include "base/base_switches.h"
+#include "base/command_line.h"
+#include "base/containers/contains.h"
+#include "base/strings/strcat.h"
+#include "base/strings/string_util.h"
+
+namespace base {
+
+ScopedAddFeatureFlags::ScopedAddFeatureFlags(CommandLine* command_line)
+    : command_line_(command_line) {
+  std::string enabled_features =
+      command_line->GetSwitchValueASCII(switches::kEnableFeatures);
+  std::string disabled_features =
+      command_line->GetSwitchValueASCII(switches::kDisableFeatures);
+  for (const StringPiece& feature :
+       FeatureList::SplitFeatureListString(enabled_features)) {
+    enabled_features_.emplace_back(feature);
+  }
+  for (const StringPiece& feature :
+       FeatureList::SplitFeatureListString(disabled_features)) {
+    disabled_features_.emplace_back(feature);
+  }
+}
+
+ScopedAddFeatureFlags::~ScopedAddFeatureFlags() {
+  command_line_->AppendSwitchASCII(switches::kEnableFeatures,
+                                   JoinString(enabled_features_, ","));
+  command_line_->AppendSwitchASCII(switches::kDisableFeatures,
+                                   JoinString(disabled_features_, ","));
+}
+
+void ScopedAddFeatureFlags::EnableIfNotSet(const Feature& feature) {
+  AddFeatureIfNotSet(feature, /*suffix=*/"", /*enable=*/true);
+}
+
+void ScopedAddFeatureFlags::EnableIfNotSetWithParameter(const Feature& feature,
+                                                        StringPiece name,
+                                                        StringPiece value) {
+  std::string suffix = StrCat({":", name, "/", value});
+  AddFeatureIfNotSet(feature, suffix, true /* enable */);
+}
+
+void ScopedAddFeatureFlags::DisableIfNotSet(const Feature& feature) {
+  AddFeatureIfNotSet(feature, /*suffix=*/"", /*enable=*/false);
+}
+
+bool ScopedAddFeatureFlags::IsEnabled(const Feature& feature) {
+  return IsEnabledWithParameter(feature, /*parameter_name=*/"",
+                                /*parameter_value=*/"");
+}
+
+bool ScopedAddFeatureFlags::IsEnabledWithParameter(
+    const Feature& feature,
+    StringPiece parameter_name,
+    StringPiece parameter_value) {
+  std::string feature_name = feature.name;
+  if (!parameter_name.empty()) {
+    StrAppend(&feature_name, {":", parameter_name, "/", parameter_value});
+  }
+  if (Contains(disabled_features_, feature_name))
+    return false;
+  if (Contains(enabled_features_, feature_name))
+    return true;
+  return feature.default_state == FEATURE_ENABLED_BY_DEFAULT;
+}
+
+void ScopedAddFeatureFlags::AddFeatureIfNotSet(const Feature& feature,
+                                               StringPiece suffix,
+                                               bool enable) {
+  std::string feature_name = StrCat({feature.name, suffix});
+  if (Contains(enabled_features_, feature_name) ||
+      Contains(disabled_features_, feature_name)) {
+    return;
+  }
+  if (enable) {
+    enabled_features_.emplace_back(feature_name);
+  } else {
+    disabled_features_.emplace_back(feature_name);
+  }
+}
+
+}  // namespace base
diff --git a/base/scoped_add_feature_flags.h b/base/scoped_add_feature_flags.h
new file mode 100644
index 0000000..e562ad46
--- /dev/null
+++ b/base/scoped_add_feature_flags.h
@@ -0,0 +1,62 @@
+// Copyright 2019 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 BASE_SCOPED_ADD_FEATURE_FLAGS_H_
+#define BASE_SCOPED_ADD_FEATURE_FLAGS_H_
+
+#include <string>
+#include <vector>
+
+#include "base/base_export.h"
+#include "base/feature_list.h"
+#include "base/memory/raw_ptr.h"
+
+namespace base {
+
+class CommandLine;
+
+// Helper class to enable and disable features if they are not already set in
+// the command line. It reads the command line on construction, allows user to
+// enable and disable features during its lifetime, and writes the modified
+// --enable-features=... and --disable-features=... flags back to the command
+// line on destruction.
+class BASE_EXPORT ScopedAddFeatureFlags {
+ public:
+  explicit ScopedAddFeatureFlags(CommandLine* command_line);
+
+  ScopedAddFeatureFlags(const ScopedAddFeatureFlags&) = delete;
+  ScopedAddFeatureFlags& operator=(const ScopedAddFeatureFlags&) = delete;
+
+  ~ScopedAddFeatureFlags();
+
+  // Any existing (user set) enable/disable takes precedence.
+  void EnableIfNotSet(const Feature& feature);
+  void DisableIfNotSet(const Feature& feature);
+  void EnableIfNotSetWithParameter(const Feature& feature,
+                                   StringPiece name,
+                                   StringPiece value);
+
+  // Check if the feature is enabled from command line or functions above
+  bool IsEnabled(const Feature& feature);
+
+  // Check if the feature with the given parameter name and value is enabled
+  // from command line or functions above. An empty parameter name means that we
+  // are checking if the feature is enabled without any parameter.
+  bool IsEnabledWithParameter(const Feature& feature,
+                              StringPiece parameter_name,
+                              StringPiece parameter_value);
+
+ private:
+  void AddFeatureIfNotSet(const Feature& feature,
+                          StringPiece suffix,
+                          bool enable);
+
+  const raw_ptr<CommandLine> command_line_;
+  std::vector<std::string> enabled_features_;
+  std::vector<std::string> disabled_features_;
+};
+
+}  // namespace base
+
+#endif  // BASE_SCOPED_ADD_FEATURE_FLAGS_H_
diff --git a/android_webview/browser/scoped_add_feature_flags_unittests.cc b/base/scoped_add_feature_flags_unittest.cc
similarity index 68%
rename from android_webview/browser/scoped_add_feature_flags_unittests.cc
rename to base/scoped_add_feature_flags_unittest.cc
index 4a05e11d..d105aa2 100644
--- a/android_webview/browser/scoped_add_feature_flags_unittests.cc
+++ b/base/scoped_add_feature_flags_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 "android_webview/browser/scoped_add_feature_flags.h"
+#include "base/scoped_add_feature_flags.h"
 
 #include <string>
 
@@ -11,9 +11,7 @@
 #include "base/feature_list.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-using base::CommandLine;
-
-namespace android_webview {
+namespace base {
 
 TEST(ScopedAddFeatureFlags, ConflictWithExistingFlags) {
   CommandLine command_line(CommandLine::NO_PROGRAM);
@@ -22,14 +20,12 @@
   command_line.AppendSwitchASCII(switches::kDisableFeatures,
                                  "ExistingDisabledFoo,ExistingDisabledBar");
 
-  const base::Feature kExistingEnabledFoo{"ExistingEnabledFoo",
-                                          base::FEATURE_DISABLED_BY_DEFAULT};
-  const base::Feature kExistingDisabledFoo{"ExistingDisabledFoo",
-                                           base::FEATURE_DISABLED_BY_DEFAULT};
-  const base::Feature kEnabledBaz{"EnabledBaz",
-                                  base::FEATURE_DISABLED_BY_DEFAULT};
-  const base::Feature kDisabledBaz{"DisabledBaz",
-                                   base::FEATURE_DISABLED_BY_DEFAULT};
+  const Feature kExistingEnabledFoo{"ExistingEnabledFoo",
+                                    FEATURE_DISABLED_BY_DEFAULT};
+  const Feature kExistingDisabledFoo{"ExistingDisabledFoo",
+                                     FEATURE_DISABLED_BY_DEFAULT};
+  const Feature kEnabledBaz{"EnabledBaz", FEATURE_DISABLED_BY_DEFAULT};
+  const Feature kDisabledBaz{"DisabledBaz", FEATURE_DISABLED_BY_DEFAULT};
   {
     ScopedAddFeatureFlags scoped_add(&command_line);
     scoped_add.EnableIfNotSet(kExistingEnabledFoo);
@@ -50,10 +46,10 @@
   CommandLine command_line(CommandLine::NO_PROGRAM);
   command_line.AppendSwitchASCII(switches::kEnableFeatures,
                                  "ExistingEnabledFoo");
-  const base::Feature kExistingEnabledFoo{"ExistingEnabledFoo",
-                                          base::FEATURE_DISABLED_BY_DEFAULT};
-  const base::Feature kFeatureWithParameter{"FeatureWithParam",
-                                            base::FEATURE_DISABLED_BY_DEFAULT};
+  const Feature kExistingEnabledFoo{"ExistingEnabledFoo",
+                                    FEATURE_DISABLED_BY_DEFAULT};
+  const Feature kFeatureWithParameter{"FeatureWithParam",
+                                      FEATURE_DISABLED_BY_DEFAULT};
 
   {
     ScopedAddFeatureFlags scoped_add(&command_line);
@@ -68,4 +64,4 @@
             command_line.GetSwitchValueASCII(switches::kEnableFeatures));
 }
 
-}  // namespace android_webview
+}  // namespace base
diff --git a/base/task/sequence_manager/thread_controller_with_message_pump_impl.cc b/base/task/sequence_manager/thread_controller_with_message_pump_impl.cc
index a03e0d5..86e99b3 100644
--- a/base/task/sequence_manager/thread_controller_with_message_pump_impl.cc
+++ b/base/task/sequence_manager/thread_controller_with_message_pump_impl.cc
@@ -40,7 +40,7 @@
 }
 
 // Feature to run tasks by batches before pumping out messages.
-const Feature kRunTasksByBatches = {"RunThreadControllerTasksByBatches",
+const Feature kRunTasksByBatches = {"RunTasksByBatches",
                                     base::FEATURE_DISABLED_BY_DEFAULT};
 
 std::atomic_bool g_align_wake_ups = false;
diff --git a/base/task/task_features.cc b/base/task/task_features.cc
index 3a07ae39..5a075212 100644
--- a/base/task/task_features.cc
+++ b/base/task/task_features.cc
@@ -63,6 +63,6 @@
     "ExplicitHighResolutionTimerWin", base::FEATURE_DISABLED_BY_DEFAULT};
 
 const BASE_EXPORT Feature kRunTasksByBatches = {
-    "RunThreadControllerTasksByBatches", base::FEATURE_DISABLED_BY_DEFAULT};
+    "RunTasksByBatches", base::FEATURE_DISABLED_BY_DEFAULT};
 
 }  // namespace base
diff --git a/build/args/headless.gn b/build/args/headless.gn
index 79297f2..fa52b43 100644
--- a/build/args/headless.gn
+++ b/build/args/headless.gn
@@ -17,9 +17,6 @@
 # Embed resource.pak into binary to simplify deployment.
 headless_use_embedded_resources = true
 
-# Expose headless bindings for freetype library bundled with Chromium.
-headless_fontconfig_utils = true
-
 # Don't use Prefs component, disabling access to Local State prefs.
 headless_use_prefs = false
 
diff --git a/build/fuchsia/cipd/BUILD.gn b/build/fuchsia/cipd/BUILD.gn
index 839ce6fd..6718cf35 100644
--- a/build/fuchsia/cipd/BUILD.gn
+++ b/build/fuchsia/cipd/BUILD.gn
@@ -207,8 +207,8 @@
   package_subdirectory = _web_engine_directory
   description = "Prebuilt WebRunner binaries for Fuchsia."
 
-  deps = [ "//fuchsia/runners:web_runner_pkg" ]
-  sources = [ "${root_gen_dir}/fuchsia/runners/web_runner/web_runner.far" ]
+  deps = [ "//fuchsia_web/runners:web_runner_pkg" ]
+  sources = [ "${root_gen_dir}/fuchsia_web/runners/web_runner/web_runner.far" ]
 }
 
 cipd_archive("web_engine") {
@@ -223,8 +223,9 @@
   package_subdirectory = _web_engine_directory
   description = "Prebuilt Cast application Runner binaries for Fuchsia."
 
-  deps = [ "//fuchsia/runners:cast_runner_pkg" ]
-  sources = [ "${root_gen_dir}/fuchsia/runners/cast_runner/cast_runner.far" ]
+  deps = [ "//fuchsia_web/runners:cast_runner_pkg" ]
+  sources =
+      [ "${root_gen_dir}/fuchsia_web/runners/cast_runner/cast_runner.far" ]
 }
 
 cipd_archive("web_engine_shell") {
@@ -278,8 +279,8 @@
   deps = [
     "//base:base_unittests_pkg",
     "//fuchsia/engine:web_engine_integration_tests_pkg",
-    "//fuchsia/runners:cast_runner_integration_tests_pkg",
-    "//fuchsia/runners:web_runner_integration_tests_pkg",
+    "//fuchsia_web/runners:cast_runner_integration_tests_pkg",
+    "//fuchsia_web/runners:web_runner_integration_tests_pkg",
     "//ipc:ipc_tests_pkg",
     "//media:media_unittests_pkg",
     "//mojo:mojo_unittests_pkg",
@@ -298,8 +299,8 @@
       ]
       cfv1_far_sources = [
         "${root_gen_dir}/fuchsia/engine/web_engine_integration_tests/web_engine_integration_tests.far",
-        "${root_gen_dir}/fuchsia/runners/cast_runner_integration_tests/cast_runner_integration_tests.far",
-        "${root_gen_dir}/fuchsia/runners/web_runner_integration_tests/web_runner_integration_tests.far",
+        "${root_gen_dir}/fuchsia_web/runners/cast_runner_integration_tests/cast_runner_integration_tests.far",
+        "${root_gen_dir}/fuchsia_web/runners/web_runner_integration_tests/web_runner_integration_tests.far",
         "${root_gen_dir}/media/media_unittests/media_unittests.far",
         "${root_gen_dir}/skia/skia_unittests/skia_unittests.far",
       ]
@@ -321,12 +322,12 @@
       manifest_path = "${target_gen_dir}/web_engine_tests_manifest.json"
       cfv1_far_sources = [
         "${root_gen_dir}/fuchsia/engine/web_engine_integration_tests/web_engine_integration_tests.far",
-        "${root_gen_dir}/fuchsia/runners/web_runner_integration_tests/web_runner_integration_tests.far",
+        "${root_gen_dir}/fuchsia_web/runners/web_runner_integration_tests/web_runner_integration_tests.far",
       ]
     },
     {
       manifest_path = "${target_gen_dir}/cast_runner_tests_manifest.json"
-      cfv1_far_sources = [ "${root_gen_dir}/fuchsia/runners/cast_runner_integration_tests/cast_runner_integration_tests.far" ]
+      cfv1_far_sources = [ "${root_gen_dir}/fuchsia_web/runners/cast_runner_integration_tests/cast_runner_integration_tests.far" ]
     },
   ]
 }
diff --git a/cc/base/math_util.h b/cc/base/math_util.h
index a29fc49..02aaebdc 100644
--- a/cc/base/math_util.h
+++ b/cc/base/math_util.h
@@ -5,22 +5,18 @@
 #ifndef CC_BASE_MATH_UTIL_H_
 #define CC_BASE_MATH_UTIL_H_
 
+#include <cmath>
 #include <limits>
-#include <memory>
-#include <vector>
 
 #include "base/check.h"
 #include "base/cxx17_backports.h"
 #include "build/build_config.h"
 #include "cc/base/base_export.h"
 #include "third_party/skia/include/core/SkM44.h"
+#include "third_party/skia/include/core/SkScalar.h"
 #include "ui/gfx/geometry/box_f.h"
 #include "ui/gfx/geometry/point3_f.h"
 #include "ui/gfx/geometry/point_f.h"
-#include "ui/gfx/geometry/rounded_corners_f.h"
-#include "ui/gfx/geometry/size.h"
-#include "ui/gfx/geometry/transform.h"
-#include "ui/gfx/geometry/vector2d_f.h"
 
 namespace base {
 class Value;
@@ -34,6 +30,7 @@
 class Rect;
 class RectF;
 class RRectF;
+class Size;
 class SizeF;
 class Transform;
 class Vector2dF;
diff --git a/cc/debug/debug_colors.cc b/cc/debug/debug_colors.cc
index a90c7d34..6b91453 100644
--- a/cc/debug/debug_colors.cc
+++ b/cc/debug/debug_colors.cc
@@ -16,48 +16,48 @@
 // ======= Layer border colors =======
 
 // Tiled content layers are orange.
-SkColor DebugColors::TiledContentLayerBorderColor() {
-  return SkColorSetARGB(128, 255, 128, 0);
+SkColor4f DebugColors::TiledContentLayerBorderColor() {
+  return {1.0f, 0.5f, 0.0f, 0.5f};
 }
 int DebugColors::TiledContentLayerBorderWidth(float device_scale_factor) {
   return Scale(2, device_scale_factor);
 }
 
 // Image layers are olive.
-SkColor DebugColors::ImageLayerBorderColor() {
-  return SkColorSetARGB(128, 128, 128, 0);
+SkColor4f DebugColors::ImageLayerBorderColor() {
+  return {0.5f, 0.5f, 0.0f, 0.5f};
 }
 int DebugColors::ImageLayerBorderWidth(float device_scale_factor) {
   return Scale(2, device_scale_factor);
 }
 
 // Non-tiled content layers area green.
-SkColor DebugColors::ContentLayerBorderColor() {
-  return SkColorSetARGB(128, 0, 128, 32);
+SkColor4f DebugColors::ContentLayerBorderColor() {
+  return {0.0f, 0.5f, 32.0f / 255.0f, 0.5f};
 }
 int DebugColors::ContentLayerBorderWidth(float device_scale_factor) {
   return Scale(2, device_scale_factor);
 }
 
 // Other container layers are yellow.
-SkColor DebugColors::ContainerLayerBorderColor() {
-  return SkColorSetARGB(192, 255, 255, 0);
+SkColor4f DebugColors::ContainerLayerBorderColor() {
+  return {1.0f, 1.0f, 0.0f, 0.75f};
 }
 int DebugColors::ContainerLayerBorderWidth(float device_scale_factor) {
   return Scale(2, device_scale_factor);
 }
 
 // Surface layers are a blue-ish green.
-SkColor DebugColors::SurfaceLayerBorderColor() {
-  return SkColorSetARGB(128, 0, 255, 136);
+SkColor4f DebugColors::SurfaceLayerBorderColor() {
+  return {0.0f, 1.0f, 136.0f / 255.0f, 0.5f};
 }
 int DebugColors::SurfaceLayerBorderWidth(float device_scale_factor) {
   return Scale(2, device_scale_factor);
 }
 
 // Render surfaces are blue.
-SkColor DebugColors::SurfaceBorderColor() {
-  return SkColorSetARGB(100, 0, 0, 255);
+SkColor4f DebugColors::SurfaceBorderColor() {
+  return {0.0f, 0.0f, 1.0f, 100.0f / 255.0f};
 }
 int DebugColors::SurfaceBorderWidth(float device_scale_factor) {
   return Scale(2, device_scale_factor);
@@ -66,64 +66,64 @@
 // ======= Tile colors =======
 
 // High-res tile borders are cyan.
-SkColor DebugColors::HighResTileBorderColor() {
-  return SkColorSetARGB(100, 80, 200, 200);
+SkColor4f DebugColors::HighResTileBorderColor() {
+  return {80.0f / 255.0f, 200.0f / 255.0f, 200.0f / 255.0f, 100.0f / 255.0f};
 }
 int DebugColors::HighResTileBorderWidth(float device_scale_factor) {
   return Scale(1, device_scale_factor);
 }
 
 // Low-res tile borders are purple.
-SkColor DebugColors::LowResTileBorderColor() {
-  return SkColorSetARGB(100, 212, 83, 192);
+SkColor4f DebugColors::LowResTileBorderColor() {
+  return {212.0f / 255.0f, 83.0f / 255.0f, 0.75f, 100.0f / 255.0f};
 }
 int DebugColors::LowResTileBorderWidth(float device_scale_factor) {
   return Scale(2, device_scale_factor);
 }
 
 // Other high-resolution tile borders are yellow.
-SkColor DebugColors::ExtraHighResTileBorderColor() {
-  return SkColorSetARGB(100, 239, 231, 20);
+SkColor4f DebugColors::ExtraHighResTileBorderColor() {
+  return {239.0f / 255.0f, 231.0f / 255.0f, 20.0f / 255.0f, 100.0f / 255.0f};
 }
 int DebugColors::ExtraHighResTileBorderWidth(float device_scale_factor) {
   return Scale(2, device_scale_factor);
 }
 
 // Other low-resolution tile borders are green.
-SkColor DebugColors::ExtraLowResTileBorderColor() {
-  return SkColorSetARGB(100, 93, 186, 18);
+SkColor4f DebugColors::ExtraLowResTileBorderColor() {
+  return {93.0f / 255.0f, 186.0f / 255.0f, 18.0f / 255.0f, 100.0f / 255.0f};
 }
 int DebugColors::ExtraLowResTileBorderWidth(float device_scale_factor) {
   return Scale(2, device_scale_factor);
 }
 
 // Missing tile borders are dark grey.
-SkColor DebugColors::MissingTileBorderColor() {
-  return SkColorSetARGB(64, 64, 64, 0);
+SkColor4f DebugColors::MissingTileBorderColor() {
+  return {0.25f, 0.25f, 0.0f, 0.25f};
 }
 int DebugColors::MissingTileBorderWidth(float device_scale_factor) {
   return Scale(1, device_scale_factor);
 }
 
 // Solid color tile borders are grey.
-SkColor DebugColors::SolidColorTileBorderColor() {
-  return SkColorSetARGB(128, 128, 128, 128);
+SkColor4f DebugColors::SolidColorTileBorderColor() {
+  return {0.5f, 0.5f, 0.5f, 0.5f};
 }
 int DebugColors::SolidColorTileBorderWidth(float device_scale_factor) {
   return Scale(1, device_scale_factor);
 }
 
 // OOM tile borders are red.
-SkColor DebugColors::OOMTileBorderColor() {
-  return SkColorSetARGB(100, 255, 0, 0);
+SkColor4f DebugColors::OOMTileBorderColor() {
+  return {1.0f, 0.0f, 0.0f, 100.0f / 255.0f};
 }
 int DebugColors::OOMTileBorderWidth(float device_scale_factor) {
   return Scale(1, device_scale_factor);
 }
 
 // Direct picture borders are chartreuse.
-SkColor DebugColors::DirectPictureBorderColor() {
-  return SkColorSetARGB(255, 127, 255, 0);
+SkColor4f DebugColors::DirectPictureBorderColor() {
+  return {127.0f / 255.0f, 1.0f, 0.0f, 1.0f};
 }
 int DebugColors::DirectPictureBorderWidth(float device_scale_factor) {
   return Scale(1, device_scale_factor);
@@ -146,8 +146,8 @@
 }
 
 // Compressed tile borders are blue.
-SkColor DebugColors::CompressedTileBorderColor() {
-  return SkColorSetARGB(100, 20, 20, 240);
+SkColor4f DebugColors::CompressedTileBorderColor() {
+  return {20.0f / 255.0f, 20.0f / 255.0f, 240.0f / 255.0f, 100.0f / 255.0f};
 }
 int DebugColors::CompressedTileBorderWidth(float device_scale_factor) {
   return Scale(2, device_scale_factor);
@@ -156,211 +156,214 @@
 // ======= Checkerboard colors =======
 
 // Non-debug checkerboards are grey.
-SkColor DebugColors::DefaultCheckerboardColor() {
-  return SkColorSetRGB(241, 241, 241);
+SkColor4f DebugColors::DefaultCheckerboardColor() {
+  return {241.0f / 255.0f, 241.0f / 255.0f, 241.0f / 255.0f, 1.0f};
 }
 
 // Invalidated tiles get sky blue checkerboards.
-SkColor DebugColors::InvalidatedTileCheckerboardColor() {
-  return SkColorSetRGB(128, 200, 245);
+SkColor4f DebugColors::InvalidatedTileCheckerboardColor() {
+  return {0.5f, 200.0f / 255.0f, 245.0f / 255.0f, 1.0f};
 }
 
 // Evicted tiles get pale red checkerboards.
-SkColor DebugColors::EvictedTileCheckerboardColor() {
-  return SkColorSetRGB(255, 200, 200);
+SkColor4f DebugColors::EvictedTileCheckerboardColor() {
+  return {1.0f, 200.0f / 255.0f, 200.0f / 255.0f, 1.0f};
 }
 
 // ======= Debug rect colors =======
 
-static SkColor FadedGreen(int initial_value, int step) {
+static SkColor4f FadedGreen(int initial_value, int step) {
   DCHECK_GE(step, 0);
   DCHECK_LE(step, DebugColors::kFadeSteps);
   int value = step * initial_value / DebugColors::kFadeSteps;
-  return SkColorSetARGB(value, 0, 195, 0);
+  return {0.0f, 195.0f / 255.0f, 0.0f, static_cast<float>(value) / 255.0f};
 }
 // Paint rects in green.
-SkColor DebugColors::PaintRectBorderColor(int step) {
+SkColor4f DebugColors::PaintRectBorderColor(int step) {
   return FadedGreen(255, step);
 }
 int DebugColors::PaintRectBorderWidth() { return 2; }
-SkColor DebugColors::PaintRectFillColor(int step) {
+SkColor4f DebugColors::PaintRectFillColor(int step) {
   return FadedGreen(60, step);
 }
 
-static SkColor FadedBlue(int initial_value, int step) {
+static SkColor4f FadedBlue(int initial_value, int step) {
   DCHECK_GE(step, 0);
   DCHECK_LE(step, DebugColors::kFadeSteps);
   int value = step * initial_value / DebugColors::kFadeSteps;
-  return SkColorSetARGB(value, 0, 0, 255);
+  return {0.0f, 0.0f, 1.0f, static_cast<float>(value) / 255.0f};
 }
 /// Layout Shift rects in blue.
-SkColor DebugColors::LayoutShiftRectBorderColor() {
-  return SkColorSetARGB(0, 0, 0, 255);
+SkColor4f DebugColors::LayoutShiftRectBorderColor() {
+  return {0.0f, 0.0f, 1.0f, 0.0f};
 }
 int DebugColors::LayoutShiftRectBorderWidth() {
   // We don't want any border showing for the layout shift debug rects so we set
   // the border width to be equal to 0.
   return 0;
 }
-SkColor DebugColors::LayoutShiftRectFillColor(int step) {
+SkColor4f DebugColors::LayoutShiftRectFillColor(int step) {
   return FadedBlue(60, step);
 }
 
 // Property-changed rects in blue.
-SkColor DebugColors::PropertyChangedRectBorderColor() {
-  return SkColorSetARGB(255, 0, 0, 255);
+SkColor4f DebugColors::PropertyChangedRectBorderColor() {
+  return {0.0f, 0.0f, 1.0f, 1.0f};
 }
 int DebugColors::PropertyChangedRectBorderWidth() { return 2; }
-SkColor DebugColors::PropertyChangedRectFillColor() {
-  return SkColorSetARGB(30, 0, 0, 255);
+SkColor4f DebugColors::PropertyChangedRectFillColor() {
+  return {0.0f, 0.0f, 1.0f, 30.0f / 255.0f};
 }
 
 // Surface damage rects in yellow-orange.
-SkColor DebugColors::SurfaceDamageRectBorderColor() {
-  return SkColorSetARGB(255, 200, 100, 0);
+SkColor4f DebugColors::SurfaceDamageRectBorderColor() {
+  return {200.0f / 255.0f, 100.0f / 255.0f, 0.0f, 1.0f};
 }
 int DebugColors::SurfaceDamageRectBorderWidth() { return 2; }
-SkColor DebugColors::SurfaceDamageRectFillColor() {
-  return SkColorSetARGB(30, 200, 100, 0);
+SkColor4f DebugColors::SurfaceDamageRectFillColor() {
+  return {200.0f / 255.0f, 100.0f / 255.0f, 0.0f, 30.0f / 255.0f};
 }
 
 // Surface screen space rects in yellow-green.
-SkColor DebugColors::ScreenSpaceLayerRectBorderColor() {
-  return SkColorSetARGB(255, 100, 200, 0);
+SkColor4f DebugColors::ScreenSpaceLayerRectBorderColor() {
+  return {100.0f / 255.0f, 200.0f / 255.0f, 0.0f, 1.0f};
 }
 int DebugColors::ScreenSpaceLayerRectBorderWidth() { return 2; }
-SkColor DebugColors::ScreenSpaceLayerRectFillColor() {
-  return SkColorSetARGB(30, 100, 200, 0);
+SkColor4f DebugColors::ScreenSpaceLayerRectFillColor() {
+  return {100.0f / 255.0f, 200.0f / 255.0f, 0.0f, 30.0f / 255.0f};
 }
 
 // Touch-event-handler rects in yellow.
-SkColor DebugColors::TouchEventHandlerRectBorderColor() {
-  return SkColorSetARGB(255, 239, 229, 60);
+SkColor4f DebugColors::TouchEventHandlerRectBorderColor() {
+  return {239.0f / 255.0f, 229.0f / 255.0f, 60.0f / 255.0f, 1.0f};
 }
 int DebugColors::TouchEventHandlerRectBorderWidth() { return 2; }
-SkColor DebugColors::TouchEventHandlerRectFillColor() {
-  return SkColorSetARGB(30, 239, 229, 60);
+SkColor4f DebugColors::TouchEventHandlerRectFillColor() {
+  return {239.0f / 255.0f, 229.0f / 255.0f, 60.0f / 255.0f, 30.0f / 255.0f};
 }
 
 // Wheel-event-handler rects in green.
-SkColor DebugColors::WheelEventHandlerRectBorderColor() {
-  return SkColorSetARGB(255, 189, 209, 57);
+SkColor4f DebugColors::WheelEventHandlerRectBorderColor() {
+  return {189.0f / 255.0f, 209.0f / 255.0f, 57.0f / 255.0f, 1.0f};
 }
 int DebugColors::WheelEventHandlerRectBorderWidth() { return 2; }
-SkColor DebugColors::WheelEventHandlerRectFillColor() {
-  return SkColorSetARGB(30, 189, 209, 57);
+SkColor4f DebugColors::WheelEventHandlerRectFillColor() {
+  return {189.0f / 255.0f, 209.0f / 255.0f, 57.0f / 255.0f, 30.0f / 255.0f};
 }
 
 // Scroll-event-handler rects in teal.
-SkColor DebugColors::ScrollEventHandlerRectBorderColor() {
-  return SkColorSetARGB(255, 24, 167, 181);
+SkColor4f DebugColors::ScrollEventHandlerRectBorderColor() {
+  return {24.0f / 255.0f, 167.0f / 255.0f, 181.0f / 255.0f, 1.0f};
 }
 int DebugColors::ScrollEventHandlerRectBorderWidth() { return 2; }
-SkColor DebugColors::ScrollEventHandlerRectFillColor() {
-  return SkColorSetARGB(30, 24, 167, 181);
+SkColor4f DebugColors::ScrollEventHandlerRectFillColor() {
+  return {24.0f / 255.0f, 167.0f / 255.0f, 181.0f / 255.0f, 30.0f / 255.0f};
 }
 
 // Non-fast-scrollable rects in orange.
-SkColor DebugColors::NonFastScrollableRectBorderColor() {
-  return SkColorSetARGB(255, 238, 163, 59);
+SkColor4f DebugColors::NonFastScrollableRectBorderColor() {
+  return {238.0f / 255.0f, 163.0f / 255.0f, 59.0f / 255.0f, 1.0f};
 }
 int DebugColors::NonFastScrollableRectBorderWidth() { return 2; }
-SkColor DebugColors::NonFastScrollableRectFillColor() {
-  return SkColorSetARGB(30, 238, 163, 59);
+SkColor4f DebugColors::NonFastScrollableRectFillColor() {
+  return {238.0f / 255.0f, 163.0f / 255.0f, 59.0f / 255.0f, 30.0f / 255.0f};
 }
 
 // Main-thread scrolling reason rects in yellow-orange.
-SkColor DebugColors::MainThreadScrollingReasonRectBorderColor() {
-  return SkColorSetARGB(255, 200, 100, 0);
+SkColor4f DebugColors::MainThreadScrollingReasonRectBorderColor() {
+  return {200.0f / 255.0f, 100.0f / 255.0f, 0.0f, 1.0f};
 }
 int DebugColors::MainThreadScrollingReasonRectBorderWidth() {
   return 2;
 }
-SkColor DebugColors::MainThreadScrollingReasonRectFillColor() {
-  return SkColorSetARGB(30, 200, 100, 0);
+SkColor4f DebugColors::MainThreadScrollingReasonRectFillColor() {
+  return {200.0f / 255.0f, 100.0f / 255.0f, 0.0f, 30.0f / 255.0f};
 }
 
 // Animation bounds are lime-green.
-SkColor DebugColors::LayerAnimationBoundsBorderColor() {
-  return SkColorSetARGB(255, 112, 229, 0);
+SkColor4f DebugColors::LayerAnimationBoundsBorderColor() {
+  return {112.0f / 255.0f, 229.0f / 255.0f, 0.0f, 1.0f};
 }
 int DebugColors::LayerAnimationBoundsBorderWidth() { return 2; }
-SkColor DebugColors::LayerAnimationBoundsFillColor() {
-  return SkColorSetARGB(30, 112, 229, 0);
+SkColor4f DebugColors::LayerAnimationBoundsFillColor() {
+  return {112.0f / 255.0f, 229.0f / 255.0f, 0.0f, 30.0f / 255.0f};
 }
 
 // Picture borders in transparent blue.
-SkColor DebugColors::PictureBorderColor() {
-  return SkColorSetARGB(100, 0, 0, 200);
+SkColor4f DebugColors::PictureBorderColor() {
+  return {0.0f, 0.0f, 200.0f / 255.0f, 100.0f / 255.0f};
 }
 
 // ======= HUD widget colors =======
 
-SkColor DebugColors::HUDBackgroundColor() {
-  return SkColorSetARGB(217, 0, 0, 0);
+SkColor4f DebugColors::HUDBackgroundColor() {
+  return {0.0f, 0.0f, 0.0f, 217.0f / 255.0f};
 }
-SkColor DebugColors::HUDSeparatorLineColor() {
-  return SkColorSetARGB(64, 0, 255, 0);
+SkColor4f DebugColors::HUDSeparatorLineColor() {
+  return {0.0f, 1.0f, 0.0f, 0.25f};
 }
-SkColor DebugColors::HUDIndicatorLineColor() {
-  return SK_ColorYELLOW;
+SkColor4f DebugColors::HUDIndicatorLineColor() {
+  return SkColors::kYellow;
 }
-SkColor DebugColors::HUDTitleColor() {
-  return SkColorSetARGB(255, 232, 232, 232);
+SkColor4f DebugColors::HUDTitleColor() {
+  return {232.0f / 255.0f, 232.0f / 255.0f, 232.0f / 255.0f, 1.0f};
 }
 
-SkColor DebugColors::PlatformLayerTreeTextColor() { return SK_ColorRED; }
-SkColor DebugColors::FPSDisplayTextAndGraphColor() {
-  return SK_ColorGREEN;
+SkColor4f DebugColors::PlatformLayerTreeTextColor() {
+  return SkColors::kRed;
+}
+SkColor4f DebugColors::FPSDisplayTextAndGraphColor() {
+  return SkColors::kGreen;
 }
 
 // Color used to represent dropped compositor frames.
-SkColor DebugColors::FPSDisplayDroppedFrame() {
-  return SkColorSetRGB(202, 91, 29);
+SkColor4f DebugColors::FPSDisplayDroppedFrame() {
+  return {202.0f / 255.0f, 91.0f / 255.0f, 29.0f / 255.0f, 1.0f};
 }
 // Color used to represent a "partial" frame, i.e. a frame that missed
 // its commit deadline.
-SkColor DebugColors::FPSDisplayMissedFrame() {
-  return SkColorSetRGB(255, 245, 0);
+SkColor4f DebugColors::FPSDisplayMissedFrame() {
+  return {1.0f, 245.0f / 255.0f, 0.0f, 1.0f};
 }
 // Color used to represent a frame that successfully rendered.
-SkColor DebugColors::FPSDisplaySuccessfulFrame() {
-  return SkColorSetARGB(191, 174, 221, 255);
+SkColor4f DebugColors::FPSDisplaySuccessfulFrame() {
+  return {174.0f / 255.0f, 221.0f / 255.0f, 1.0f, 191.0f / 255.0f};
 }
 
-SkColor DebugColors::MemoryDisplayTextColor() {
-  return SK_ColorCYAN;
+SkColor4f DebugColors::MemoryDisplayTextColor() {
+  return SkColors::kCyan;
 }
 
 // Paint time display in green (similar to paint times in the WebInspector)
-SkColor DebugColors::PaintTimeDisplayTextAndGraphColor() {
-  return SkColorSetRGB(75, 155, 55);
+SkColor4f DebugColors::PaintTimeDisplayTextAndGraphColor() {
+  return {75.0f / 255.0f, 155.0f / 255.0f, 55.0f / 255.0f, 1.0f};
 }
 
-SkColor DebugColors::NonLCDTextHighlightColor(LCDTextDisallowedReason reason) {
+SkColor4f DebugColors::NonLCDTextHighlightColor(
+    LCDTextDisallowedReason reason) {
   switch (reason) {
     case LCDTextDisallowedReason::kNone:
     case LCDTextDisallowedReason::kNoText:
-      return SK_ColorTRANSPARENT;
+      return SkColors::kTransparent;
     case LCDTextDisallowedReason::kSetting:
-      return SkColorSetARGB(96, 128, 255, 0);
+      return {0.5f, 1.0f, 0.0f, 96.0f / 255.0f};
     case LCDTextDisallowedReason::kBackgroundColorNotOpaque:
-      return SkColorSetARGB(96, 128, 128, 0);
+      return {0.5f, 0.5f, 0.0f, 96.0f / 255.0f};
     case LCDTextDisallowedReason::kContentsNotOpaque:
-      return SkColorSetARGB(96, 255, 0, 0);
+      return {1.0f, 0.0f, 0.0f, 96.0f / 255.0f};
     case LCDTextDisallowedReason::kNonIntegralTranslation:
-      return SkColorSetARGB(96, 255, 128, 0);
+      return {1.0f, 0.5f, 0.0f, 96.0f / 255.0f};
     case LCDTextDisallowedReason::kNonIntegralXOffset:
     case LCDTextDisallowedReason::kNonIntegralYOffset:
-      return SkColorSetARGB(96, 255, 0, 128);
+      return {1.0f, 0.0f, 0.5f, 96.0f / 255.0f};
     case LCDTextDisallowedReason::kWillChangeTransform:
     case LCDTextDisallowedReason::kTransformAnimation:
-      return SkColorSetARGB(96, 128, 0, 255);
+      return {0.5f, 0.0f, 1.0f, 96.0f / 255.0f};
     case LCDTextDisallowedReason::kPixelOrColorEffect:
-      return SkColorSetARGB(96, 0, 128, 0);
+      return {0.0f, 0.5f, 0.0f, 96.0f / 255.0f};
   }
   NOTREACHED();
-  return SK_ColorTRANSPARENT;
+  return SkColors::kTransparent;
 }
 
 }  // namespace cc
diff --git a/cc/debug/debug_colors.h b/cc/debug/debug_colors.h
index 109bb13..49ad7f3 100644
--- a/cc/debug/debug_colors.h
+++ b/cc/debug/debug_colors.h
@@ -16,121 +16,121 @@
  public:
   DebugColors() = delete;
 
-  static SkColor TiledContentLayerBorderColor();
+  static SkColor4f TiledContentLayerBorderColor();
   static int TiledContentLayerBorderWidth(float device_scale_factor);
 
-  static SkColor ImageLayerBorderColor();
+  static SkColor4f ImageLayerBorderColor();
   static int ImageLayerBorderWidth(float device_scale_factor);
 
-  static SkColor ContentLayerBorderColor();
+  static SkColor4f ContentLayerBorderColor();
   static int ContentLayerBorderWidth(float device_scale_factor);
 
-  static SkColor ContainerLayerBorderColor();
+  static SkColor4f ContainerLayerBorderColor();
   static int ContainerLayerBorderWidth(float device_scale_factor);
 
-  static SkColor SurfaceLayerBorderColor();
+  static SkColor4f SurfaceLayerBorderColor();
   static int SurfaceLayerBorderWidth(float device_scale_factor);
 
-  static SkColor SurfaceBorderColor();
+  static SkColor4f SurfaceBorderColor();
   static int SurfaceBorderWidth(float device_scale_factor);
 
-  static SkColor HighResTileBorderColor();
+  static SkColor4f HighResTileBorderColor();
   static int HighResTileBorderWidth(float device_scale_factor);
 
-  static SkColor LowResTileBorderColor();
+  static SkColor4f LowResTileBorderColor();
   static int LowResTileBorderWidth(float device_scale_factor);
 
-  static SkColor ExtraHighResTileBorderColor();
+  static SkColor4f ExtraHighResTileBorderColor();
   static int ExtraHighResTileBorderWidth(float device_scale_factor);
 
-  static SkColor ExtraLowResTileBorderColor();
+  static SkColor4f ExtraLowResTileBorderColor();
   static int ExtraLowResTileBorderWidth(float device_scale_factor);
 
-  static SkColor MissingTileBorderColor();
+  static SkColor4f MissingTileBorderColor();
   static int MissingTileBorderWidth(float device_scale_factor);
 
-  static SkColor SolidColorTileBorderColor();
+  static SkColor4f SolidColorTileBorderColor();
   static int SolidColorTileBorderWidth(float device_scale_factor);
 
-  static SkColor OOMTileBorderColor();
+  static SkColor4f OOMTileBorderColor();
   static int OOMTileBorderWidth(float device_scale_factor);
 
-  static SkColor DirectPictureBorderColor();
+  static SkColor4f DirectPictureBorderColor();
   static int DirectPictureBorderWidth(float device_scale_factor);
 
-  static SkColor CompressedTileBorderColor();
+  static SkColor4f CompressedTileBorderColor();
   static int CompressedTileBorderWidth(float device_scale_factor);
 
-  static SkColor DefaultCheckerboardColor();
-  static SkColor EvictedTileCheckerboardColor();
-  static SkColor InvalidatedTileCheckerboardColor();
+  static SkColor4f DefaultCheckerboardColor();
+  static SkColor4f EvictedTileCheckerboardColor();
+  static SkColor4f InvalidatedTileCheckerboardColor();
 
   static const int kFadeSteps = 50;
-  static SkColor PaintRectBorderColor(int step);
+  static SkColor4f PaintRectBorderColor(int step);
   static int PaintRectBorderWidth();
-  static SkColor PaintRectFillColor(int step);
+  static SkColor4f PaintRectFillColor(int step);
 
-  static SkColor LayoutShiftRectBorderColor();
+  static SkColor4f LayoutShiftRectBorderColor();
   static int LayoutShiftRectBorderWidth();
-  static SkColor LayoutShiftRectFillColor(int step);
+  static SkColor4f LayoutShiftRectFillColor(int step);
 
-  static SkColor PropertyChangedRectBorderColor();
+  static SkColor4f PropertyChangedRectBorderColor();
   static int PropertyChangedRectBorderWidth();
-  static SkColor PropertyChangedRectFillColor();
+  static SkColor4f PropertyChangedRectFillColor();
 
-  static SkColor SurfaceDamageRectBorderColor();
+  static SkColor4f SurfaceDamageRectBorderColor();
   static int SurfaceDamageRectBorderWidth();
-  static SkColor SurfaceDamageRectFillColor();
+  static SkColor4f SurfaceDamageRectFillColor();
 
-  static SkColor ScreenSpaceLayerRectBorderColor();
+  static SkColor4f ScreenSpaceLayerRectBorderColor();
   static int ScreenSpaceLayerRectBorderWidth();
-  static SkColor ScreenSpaceLayerRectFillColor();
+  static SkColor4f ScreenSpaceLayerRectFillColor();
 
-  static SkColor TouchEventHandlerRectBorderColor();
+  static SkColor4f TouchEventHandlerRectBorderColor();
   static int TouchEventHandlerRectBorderWidth();
-  static SkColor TouchEventHandlerRectFillColor();
+  static SkColor4f TouchEventHandlerRectFillColor();
 
-  static SkColor WheelEventHandlerRectBorderColor();
+  static SkColor4f WheelEventHandlerRectBorderColor();
   static int WheelEventHandlerRectBorderWidth();
-  static SkColor WheelEventHandlerRectFillColor();
+  static SkColor4f WheelEventHandlerRectFillColor();
 
-  static SkColor ScrollEventHandlerRectBorderColor();
+  static SkColor4f ScrollEventHandlerRectBorderColor();
   static int ScrollEventHandlerRectBorderWidth();
-  static SkColor ScrollEventHandlerRectFillColor();
+  static SkColor4f ScrollEventHandlerRectFillColor();
 
-  static SkColor NonFastScrollableRectBorderColor();
+  static SkColor4f NonFastScrollableRectBorderColor();
   static int NonFastScrollableRectBorderWidth();
-  static SkColor NonFastScrollableRectFillColor();
+  static SkColor4f NonFastScrollableRectFillColor();
 
-  static SkColor MainThreadScrollingReasonRectBorderColor();
+  static SkColor4f MainThreadScrollingReasonRectBorderColor();
   static int MainThreadScrollingReasonRectBorderWidth();
-  static SkColor MainThreadScrollingReasonRectFillColor();
+  static SkColor4f MainThreadScrollingReasonRectFillColor();
 
-  static SkColor LayerAnimationBoundsBorderColor();
+  static SkColor4f LayerAnimationBoundsBorderColor();
   static int LayerAnimationBoundsBorderWidth();
-  static SkColor LayerAnimationBoundsFillColor();
+  static SkColor4f LayerAnimationBoundsFillColor();
 
-  static SkColor NonPaintedFillColor();
-  static SkColor MissingPictureFillColor();
-  static SkColor MissingResizeInvalidations();
-  static SkColor PictureBorderColor();
+  static SkColor4f NonPaintedFillColor();
+  static SkColor4f MissingPictureFillColor();
+  static SkColor4f MissingResizeInvalidations();
+  static SkColor4f PictureBorderColor();
 
   static base::span<const float> TintCompositedContentColorTransformMatrix();
 
-  static SkColor HUDBackgroundColor();
-  static SkColor HUDSeparatorLineColor();
-  static SkColor HUDIndicatorLineColor();
-  static SkColor HUDTitleColor();
+  static SkColor4f HUDBackgroundColor();
+  static SkColor4f HUDSeparatorLineColor();
+  static SkColor4f HUDIndicatorLineColor();
+  static SkColor4f HUDTitleColor();
 
-  static SkColor PlatformLayerTreeTextColor();
-  static SkColor FPSDisplayTextAndGraphColor();
-  static SkColor FPSDisplayDroppedFrame();
-  static SkColor FPSDisplayMissedFrame();
-  static SkColor FPSDisplaySuccessfulFrame();
-  static SkColor MemoryDisplayTextColor();
-  static SkColor PaintTimeDisplayTextAndGraphColor();
+  static SkColor4f PlatformLayerTreeTextColor();
+  static SkColor4f FPSDisplayTextAndGraphColor();
+  static SkColor4f FPSDisplayDroppedFrame();
+  static SkColor4f FPSDisplayMissedFrame();
+  static SkColor4f FPSDisplaySuccessfulFrame();
+  static SkColor4f MemoryDisplayTextColor();
+  static SkColor4f PaintTimeDisplayTextAndGraphColor();
 
-  static SkColor NonLCDTextHighlightColor(LCDTextDisallowedReason);
+  static SkColor4f NonLCDTextHighlightColor(LCDTextDisallowedReason);
 };
 
 }  // namespace cc
diff --git a/cc/layers/heads_up_display_layer_impl.cc b/cc/layers/heads_up_display_layer_impl.cc
index 96b098f..bf883bd1 100644
--- a/cc/layers/heads_up_display_layer_impl.cc
+++ b/cc/layers/heads_up_display_layer_impl.cc
@@ -685,7 +685,8 @@
 void HeadsUpDisplayLayerImpl::DrawGraphBackground(PaintCanvas* canvas,
                                                   PaintFlags* flags,
                                                   const SkRect& bounds) const {
-  flags->setColor(DebugColors::HUDBackgroundColor());
+  // TODO(crbug/1308932): Remove toSkColor and make all SkColor4f.
+  flags->setColor(DebugColors::HUDBackgroundColor().toSkColor());
   canvas->drawRect(bounds, *flags);
 }
 
@@ -693,7 +694,8 @@
                                              PaintFlags* flags,
                                              const SkRect& bounds) const {
   // Draw top and bottom line.
-  flags->setColor(DebugColors::HUDSeparatorLineColor());
+  // TODO(crbug/1308932): Remove toSkColor and make all SkColor4f.
+  flags->setColor(DebugColors::HUDSeparatorLineColor().toSkColor());
   canvas->drawLine(bounds.left(), bounds.top() - 1, bounds.right(),
                    bounds.top() - 1, *flags);
   canvas->drawLine(bounds.left(), bounds.bottom(), bounds.right(),
@@ -755,11 +757,13 @@
   }
   VLOG(1) << value_text;
 
-  flags.setColor(DebugColors::HUDTitleColor());
+  // TODO(crbug/1308932): Remove toSkColor and make all SkColor4f.
+  flags.setColor(DebugColors::HUDTitleColor().toSkColor());
   DrawText(canvas, flags, title, TextAlign::kLeft, kTitleFontHeight,
            title_bounds.left(), title_bounds.bottom());
 
-  flags.setColor(DebugColors::FPSDisplayTextAndGraphColor());
+  // TODO(crbug/1308932): Remove toSkColor and make all SkColor4f.
+  flags.setColor(DebugColors::FPSDisplayTextAndGraphColor().toSkColor());
   DrawText(canvas, flags, value_text, TextAlign::kRight, kFontHeight,
            text_bounds.right(), text_bounds.bottom());
 
@@ -786,13 +790,15 @@
   flags.setStyle(PaintFlags::kStroke_Style);
   flags.setStrokeWidth(1);
 
-  flags.setColor(DebugColors::FPSDisplaySuccessfulFrame());
+  // TODO(crbug/1308932): Remove all instances of toSkColor below and make all
+  // SkColor4f.
+  flags.setColor(DebugColors::FPSDisplaySuccessfulFrame().toSkColor());
   canvas->drawPath(good_path, flags);
 
-  flags.setColor(DebugColors::FPSDisplayDroppedFrame());
+  flags.setColor(DebugColors::FPSDisplayDroppedFrame().toSkColor());
   canvas->drawPath(dropped_path, flags);
 
-  flags.setColor(DebugColors::FPSDisplayMissedFrame());
+  flags.setColor(DebugColors::FPSDisplayMissedFrame().toSkColor());
   canvas->drawPath(partial_path, flags);
 
   return area;
@@ -822,11 +828,13 @@
   SkPoint stat2_pos = SkPoint::Make(left + width - kPadding - 1,
                                     top + 2 * kPadding + 3 * kFontHeight);
 
-  flags.setColor(DebugColors::HUDTitleColor());
+  // TODO(crbug/1308932): Remove toSkColor and make all SkColor4f.
+  flags.setColor(DebugColors::HUDTitleColor().toSkColor());
   DrawText(canvas, flags, "GPU memory", TextAlign::kLeft, kTitleFontHeight,
            title_pos);
 
-  flags.setColor(DebugColors::MemoryDisplayTextColor());
+  // TODO(crbug/1308932): Remove toSkColor and make all SkColor4f.
+  flags.setColor(DebugColors::MemoryDisplayTextColor().toSkColor());
   std::string text = base::StringPrintf(
       "%6.1f MB used", memory_entry_.total_bytes_used / kMegabyte);
   DrawText(canvas, flags, text, TextAlign::kRight, kFontHeight, stat1_pos);
@@ -916,7 +924,8 @@
 
   SkPoint gpu_status_pos = SkPoint::Make(left + width - kPadding,
                                          top + 2 * kFontHeight + 2 * kPadding);
-  flags.setColor(DebugColors::HUDTitleColor());
+  // TODO(crbug/1308932): Remove toSkColor and make all SkColor4f.
+  flags.setColor(DebugColors::HUDTitleColor().toSkColor());
   DrawText(canvas, flags, "GPU raster", TextAlign::kLeft, kTitleFontHeight,
            left + kPadding, top + kFontHeight + kPadding);
   flags.setColor(color);
@@ -995,6 +1004,8 @@
     std::string label_text;
 
     switch (debug_rects[i].type) {
+        // TODO(crbug/1308932): Remove all instances of toSkColor below and make
+        // all SkColor4f.
       case LAYOUT_SHIFT_RECT_TYPE:
         new_layout_shift_rects.push_back(debug_rects[i]);
         continue;
@@ -1002,56 +1013,65 @@
         new_paint_rects.push_back(debug_rects[i]);
         continue;
       case PROPERTY_CHANGED_RECT_TYPE:
-        stroke_color = DebugColors::PropertyChangedRectBorderColor();
-        fill_color = DebugColors::PropertyChangedRectFillColor();
+        stroke_color =
+            DebugColors::PropertyChangedRectBorderColor().toSkColor();
+        fill_color = DebugColors::PropertyChangedRectFillColor().toSkColor();
         stroke_width = DebugColors::PropertyChangedRectBorderWidth();
         break;
       case SURFACE_DAMAGE_RECT_TYPE:
-        stroke_color = DebugColors::SurfaceDamageRectBorderColor();
-        fill_color = DebugColors::SurfaceDamageRectFillColor();
+        stroke_color = DebugColors::SurfaceDamageRectBorderColor().toSkColor();
+        fill_color = DebugColors::SurfaceDamageRectFillColor().toSkColor();
         stroke_width = DebugColors::SurfaceDamageRectBorderWidth();
         break;
       case SCREEN_SPACE_RECT_TYPE:
-        stroke_color = DebugColors::ScreenSpaceLayerRectBorderColor();
-        fill_color = DebugColors::ScreenSpaceLayerRectFillColor();
+        stroke_color =
+            DebugColors::ScreenSpaceLayerRectBorderColor().toSkColor();
+        fill_color = DebugColors::ScreenSpaceLayerRectFillColor().toSkColor();
         stroke_width = DebugColors::ScreenSpaceLayerRectBorderWidth();
         break;
       case TOUCH_EVENT_HANDLER_RECT_TYPE:
-        stroke_color = DebugColors::TouchEventHandlerRectBorderColor();
-        fill_color = DebugColors::TouchEventHandlerRectFillColor();
+        stroke_color =
+            DebugColors::TouchEventHandlerRectBorderColor().toSkColor();
+        fill_color = DebugColors::TouchEventHandlerRectFillColor().toSkColor();
         stroke_width = DebugColors::TouchEventHandlerRectBorderWidth();
         label_text = "touch event listener: ";
         label_text.append(TouchActionToString(debug_rects[i].touch_action));
         break;
       case WHEEL_EVENT_HANDLER_RECT_TYPE:
-        stroke_color = DebugColors::WheelEventHandlerRectBorderColor();
-        fill_color = DebugColors::WheelEventHandlerRectFillColor();
+        stroke_color =
+            DebugColors::WheelEventHandlerRectBorderColor().toSkColor();
+        fill_color = DebugColors::WheelEventHandlerRectFillColor().toSkColor();
         stroke_width = DebugColors::WheelEventHandlerRectBorderWidth();
         label_text = "mousewheel event listener";
         break;
       case SCROLL_EVENT_HANDLER_RECT_TYPE:
-        stroke_color = DebugColors::ScrollEventHandlerRectBorderColor();
-        fill_color = DebugColors::ScrollEventHandlerRectFillColor();
+        stroke_color =
+            DebugColors::ScrollEventHandlerRectBorderColor().toSkColor();
+        fill_color = DebugColors::ScrollEventHandlerRectFillColor().toSkColor();
         stroke_width = DebugColors::ScrollEventHandlerRectBorderWidth();
         label_text = "scroll event listener";
         break;
       case NON_FAST_SCROLLABLE_RECT_TYPE:
-        stroke_color = DebugColors::NonFastScrollableRectBorderColor();
-        fill_color = DebugColors::NonFastScrollableRectFillColor();
+        stroke_color =
+            DebugColors::NonFastScrollableRectBorderColor().toSkColor();
+        fill_color = DebugColors::NonFastScrollableRectFillColor().toSkColor();
         stroke_width = DebugColors::NonFastScrollableRectBorderWidth();
         label_text = "repaints on scroll";
         break;
       case MAIN_THREAD_SCROLLING_REASON_RECT_TYPE:
-        stroke_color = DebugColors::MainThreadScrollingReasonRectBorderColor();
-        fill_color = DebugColors::MainThreadScrollingReasonRectFillColor();
+        stroke_color =
+            DebugColors::MainThreadScrollingReasonRectBorderColor().toSkColor();
+        fill_color =
+            DebugColors::MainThreadScrollingReasonRectFillColor().toSkColor();
         stroke_width = DebugColors::MainThreadScrollingReasonRectBorderWidth();
         label_text = "main thread scrolling: ";
         label_text.append(base::ToLowerASCII(MainThreadScrollingReason::AsText(
             debug_rects[i].main_thread_scrolling_reasons)));
         break;
       case ANIMATION_BOUNDS_RECT_TYPE:
-        stroke_color = DebugColors::LayerAnimationBoundsBorderColor();
-        fill_color = DebugColors::LayerAnimationBoundsFillColor();
+        stroke_color =
+            DebugColors::LayerAnimationBoundsBorderColor().toSkColor();
+        fill_color = DebugColors::LayerAnimationBoundsFillColor().toSkColor();
         stroke_width = DebugColors::LayerAnimationBoundsBorderWidth();
         label_text = "animation bounds";
         break;
@@ -1067,11 +1087,14 @@
   }
   if (paint_rects_fade_step_ > 0) {
     paint_rects_fade_step_--;
-    for (size_t i = 0; i < paint_rects_.size(); ++i) {
-      DrawDebugRect(canvas, &flags, paint_rects_[i],
-                    DebugColors::PaintRectBorderColor(paint_rects_fade_step_),
-                    DebugColors::PaintRectFillColor(paint_rects_fade_step_),
-                    DebugColors::PaintRectBorderWidth(), "");
+    for (auto& paint_rect : paint_rects_) {
+      // TODO(crbug/1308932): Remove all instances of toSkColor below and make
+      // all SkColor4f.
+      DrawDebugRect(
+          canvas, &flags, paint_rect,
+          DebugColors::PaintRectBorderColor(paint_rects_fade_step_).toSkColor(),
+          DebugColors::PaintRectFillColor(paint_rects_fade_step_).toSkColor(),
+          DebugColors::PaintRectBorderWidth(), "");
     }
   }
   if (new_layout_shift_rects.size()) {
@@ -1080,11 +1103,14 @@
   }
   if (layout_shift_rects_fade_step_ > 0) {
     layout_shift_rects_fade_step_--;
-    for (size_t i = 0; i < layout_shift_debug_rects_.size(); ++i) {
+    for (auto& layout_shift_debug_rect : layout_shift_debug_rects_) {
+      // TODO(crbug/1308932): Remove all instances of toSkColor below and make
+      // all SkColor4f.
       DrawDebugRect(
-          canvas, &flags, layout_shift_debug_rects_[i],
-          DebugColors::LayoutShiftRectBorderColor(),
-          DebugColors::LayoutShiftRectFillColor(layout_shift_rects_fade_step_),
+          canvas, &flags, layout_shift_debug_rect,
+          DebugColors::LayoutShiftRectBorderColor().toSkColor(),
+          DebugColors::LayoutShiftRectFillColor(layout_shift_rects_fade_step_)
+              .toSkColor(),
           DebugColors::LayoutShiftRectBorderWidth(), "");
     }
   }
@@ -1100,7 +1126,8 @@
     bool has_value,
     double value) const {
   std::string value_str = "-";
-  SkColor metrics_color = DebugColors::HUDTitleColor();
+  // TODO(crbug/1308932): Remove toSkColor and make all SkColor4f.
+  SkColor metrics_color = DebugColors::HUDTitleColor().toSkColor();
   SkColor badge_color = SK_ColorGREEN;
   if (has_value) {
     value_str = ToStringTwoDecimalPrecision(value) + info.UnitToString();
@@ -1148,7 +1175,8 @@
 
   // Draw the label and values of the metric.
   PaintFlags flags;
-  flags.setColor(DebugColors::HUDTitleColor());
+  // TODO(crbug/1308932): Remove toSkColor and make all SkColor4f.
+  flags.setColor(DebugColors::HUDTitleColor().toSkColor());
   DrawText(canvas, flags, name, TextAlign::kLeft, metrics_sizes.kFontHeight,
            left + metrics_sizes.kSidePadding + metrics_sizes.kBadgeWidth, top);
   flags.setColor(metrics_color);
@@ -1201,11 +1229,13 @@
                                                         std::string name,
                                                         double value) const {
   std::string value_str = "-";
-  SkColor metrics_color = DebugColors::HUDTitleColor();
+  // TODO(crbug/1308932): Remove toSkColor and make all SkColor4f.
+  SkColor metrics_color = DebugColors::HUDTitleColor().toSkColor();
   value_str = ToStringTwoDecimalPrecision(value) + "%";
 
   PaintFlags flags;
-  flags.setColor(DebugColors::HUDTitleColor());
+  // TODO(crbug/1308932): Remove toSkColor and make all SkColor4f.
+  flags.setColor(DebugColors::HUDTitleColor().toSkColor());
   DrawText(canvas, flags, name, TextAlign::kLeft, metrics_sizes.kFontHeight,
            left + metrics_sizes.kSidePadding + metrics_sizes.kBadgeWidth, top);
   flags.setColor(metrics_color);
diff --git a/cc/layers/layer.cc b/cc/layers/layer.cc
index aee80650..516dc9d 100644
--- a/cc/layers/layer.cc
+++ b/cc/layers/layer.cc
@@ -1204,6 +1204,7 @@
   EnsureRareInputs().capture_bounds = std::move(bounds);
   SetPropertyTreesNeedRebuild();
   SetNeedsCommit();
+  SetSubtreePropertyChanged();
 }
 
 void Layer::SetWheelEventRegion(Region wheel_event_region) {
diff --git a/cc/layers/layer_impl.cc b/cc/layers/layer_impl.cc
index f657d64..0612288 100644
--- a/cc/layers/layer_impl.cc
+++ b/cc/layers/layer_impl.cc
@@ -233,12 +233,14 @@
       layer_tree_impl() ? layer_tree_impl()->device_scale_factor() : 1;
 
   if (draws_content_) {
-    *color = DebugColors::ContentLayerBorderColor();
+    // TODO(crbug/1308932): Remove toSkColor and make all SkColor4f.
+    *color = DebugColors::ContentLayerBorderColor().toSkColor();
     *width = DebugColors::ContentLayerBorderWidth(device_scale_factor);
     return;
   }
 
-  *color = DebugColors::ContainerLayerBorderColor();
+  // TODO(crbug/1308932): Remove toSkColor and make all SkColor4f.
+  *color = DebugColors::ContainerLayerBorderColor().toSkColor();
   *width = DebugColors::ContainerLayerBorderWidth(device_scale_factor);
 }
 
diff --git a/cc/layers/layer_unittest.cc b/cc/layers/layer_unittest.cc
index 112d869b..e822609 100644
--- a/cc/layers/layer_unittest.cc
+++ b/cc/layers/layer_unittest.cc
@@ -87,6 +87,21 @@
   EXPECT_FALSE(child->subtree_property_changed());             \
   EXPECT_FALSE(grand_child->subtree_property_changed());
 
+// TODO(https://crbug.com/1330728): tests should be cleaned up to eliminate
+// mixing of EXPECT_CALL with calls to the mock functions. This method
+// should be deduped with EXPECT_SET_NEEDS_COMMIT as part of this cleanup.
+#define EXPECT_SET_NEEDS_COMMIT_WAS_CALLED(code_to_test)     \
+  do {                                                       \
+    code_to_test;                                            \
+    EXPECT_TRUE(layer_tree_host_->GetNeedsCommitAndReset()); \
+  } while (false)
+
+#define EXPECT_SET_NEEDS_COMMIT_WAS_NOT_CALLED(code_to_test)  \
+  do {                                                        \
+    code_to_test;                                             \
+    EXPECT_FALSE(layer_tree_host_->GetNeedsCommitAndReset()); \
+  } while (false)
+
 namespace cc {
 
 namespace {
@@ -110,9 +125,18 @@
     return thread_unsafe_commit_state();
   }
 
-  MOCK_METHOD0(SetNeedsCommit, void());
-  MOCK_METHOD0(SetNeedsUpdateLayers, void());
-  MOCK_METHOD0(SetNeedsFullTreeSync, void());
+  MOCK_METHOD(void, SetNeedsUpdateLayers, (), (override));
+  MOCK_METHOD(void, SetNeedsFullTreeSync, (), (override));
+
+  void SetNeedsCommit() override { needs_commit_ = true; }
+  bool GetNeedsCommitAndReset() {
+    const bool out = needs_commit_;
+    needs_commit_ = false;
+    return out;
+  }
+
+ private:
+  bool needs_commit_ = false;
 };
 
 bool LayerNeedsDisplay(Layer* layer) {
@@ -259,11 +283,7 @@
   scoped_refptr<Layer> test_layer = Layer::Create();
   ASSERT_TRUE(test_layer.get());
 
-  EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(0);
   test_layer->SetLayerTreeHost(layer_tree_host_.get());
-  Mock::VerifyAndClearExpectations(layer_tree_host_.get());
-
-  EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(0);
   test_layer->SetLayerTreeHost(nullptr);
 }
 
@@ -283,21 +303,22 @@
   top->AddChild(child);
   top->AddChild(child2);
   child->AddChild(grand_child);
-  EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(AtLeast(1));
+
   // To force a transform node for |top|.
   gfx::Transform top_transform;
   top_transform.Scale3d(1, 2, 3);
   top->SetTransform(top_transform);
   child->SetForceRenderSurfaceForTesting(true);
+  EXPECT_TRUE(layer_tree_host_->GetNeedsCommitAndReset());
 
   // Resizing without a mask layer or masks_to_bounds, should only require a
   // regular commit. Note that a layer and its mask should match sizes, but
   // the mask isn't in the tree yet, so won't need its own commit.
   gfx::Size arbitrary_size = gfx::Size(1, 2);
-  EXPECT_SET_NEEDS_COMMIT(1, top->SetBounds(arbitrary_size));
-  EXPECT_SET_NEEDS_COMMIT(0, mask_layer1->SetBounds(arbitrary_size));
-  EXPECT_CALL(*layer_tree_host_, SetNeedsFullTreeSync()).Times(1);
-  EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(AtLeast(1));
+  EXPECT_SET_NEEDS_COMMIT_WAS_CALLED(top->SetBounds(arbitrary_size));
+  EXPECT_SET_NEEDS_COMMIT_WAS_NOT_CALLED(
+      mask_layer1->SetBounds(arbitrary_size));
+  EXPECT_CALL(*layer_tree_host_, SetNeedsFullTreeSync());
   layer_tree_host_->WillCommit(/*completion=*/nullptr,
                                /*has_updates=*/true);
   EXECUTE_AND_VERIFY_SUBTREE_CHANGED(top->SetMaskLayer(mask_layer1));
@@ -335,11 +356,11 @@
 
   // Once there is a mask layer, resizes require subtree properties to update.
   arbitrary_size = gfx::Size(11, 22);
-  EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(2);
   EXECUTE_AND_VERIFY_SUBTREE_CHANGED(top->SetBounds(arbitrary_size));
+  EXPECT_TRUE(layer_tree_host_->GetNeedsCommitAndReset());
   EXECUTE_AND_VERIFY_SUBTREE_CHANGED(mask_layer1->SetBounds(arbitrary_size));
+  EXPECT_TRUE(layer_tree_host_->GetNeedsCommitAndReset());
 
-  EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(1);
   EXECUTE_AND_VERIFY_SUBTREE_CHANGED(top->SetMasksToBounds(true));
 
   commit_state = layer_tree_host_->WillCommit(/*completion=*/nullptr,
@@ -351,9 +372,10 @@
       grand_child->PushPropertiesTo(grand_child_impl.get(), *commit_state,
                                     unsafe_state));
   layer_tree_host_->CommitComplete({base::TimeTicks(), base::TimeTicks::Now()});
+  EXPECT_TRUE(layer_tree_host_->GetNeedsCommitAndReset());
 
-  EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(1);
   EXECUTE_AND_VERIFY_SUBTREE_CHANGED(top->SetContentsOpaque(true));
+  EXPECT_TRUE(layer_tree_host_->GetNeedsCommitAndReset());
 
   commit_state = layer_tree_host_->WillCommit(/*completion=*/nullptr,
                                               /*has_updates=*/true);
@@ -365,8 +387,8 @@
                                     unsafe_state));
   layer_tree_host_->CommitComplete({base::TimeTicks(), base::TimeTicks::Now()});
 
-  EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(1);
   EXECUTE_AND_VERIFY_SUBTREE_CHANGED(top->SetTrilinearFiltering(true));
+  EXPECT_TRUE(layer_tree_host_->GetNeedsCommitAndReset());
 
   commit_state = layer_tree_host_->WillCommit(/*completion=*/nullptr,
                                               /*has_updates=*/true);
@@ -378,8 +400,8 @@
                                     unsafe_state));
   layer_tree_host_->CommitComplete({base::TimeTicks(), base::TimeTicks::Now()});
 
-  EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(1);
   EXECUTE_AND_VERIFY_SUBTREE_CHANGED(top->SetTrilinearFiltering(false));
+  EXPECT_TRUE(layer_tree_host_->GetNeedsCommitAndReset());
 
   commit_state = layer_tree_host_->WillCommit(/*completion=*/nullptr,
                                               /*has_updates=*/true);
@@ -391,9 +413,10 @@
                                     unsafe_state));
   layer_tree_host_->CommitComplete({base::TimeTicks(), base::TimeTicks::Now()});
 
-  EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(2);
   top->SetRoundedCorner({1, 2, 3, 4});
+  EXPECT_TRUE(layer_tree_host_->GetNeedsCommitAndReset());
   EXECUTE_AND_VERIFY_SUBTREE_CHANGED(top->SetIsFastRoundedCorner(true));
+  EXPECT_TRUE(layer_tree_host_->GetNeedsCommitAndReset());
 
   commit_state = layer_tree_host_->WillCommit(/*completion=*/nullptr,
                                               /*has_updates=*/true);
@@ -405,8 +428,8 @@
                                     unsafe_state));
   layer_tree_host_->CommitComplete({base::TimeTicks(), base::TimeTicks::Now()});
 
-  EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(1);
   EXECUTE_AND_VERIFY_SUBTREE_CHANGED(top->SetHideLayerAndSubtree(true));
+  EXPECT_TRUE(layer_tree_host_->GetNeedsCommitAndReset());
 
   commit_state = layer_tree_host_->WillCommit(/*completion=*/nullptr,
                                               /*has_updates=*/true);
@@ -418,8 +441,8 @@
                                     unsafe_state));
   layer_tree_host_->CommitComplete({base::TimeTicks(), base::TimeTicks::Now()});
 
-  EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(1);
   EXECUTE_AND_VERIFY_SUBTREE_CHANGED(top->SetBlendMode(arbitrary_blend_mode));
+  EXPECT_TRUE(layer_tree_host_->GetNeedsCommitAndReset());
 
   commit_state = layer_tree_host_->WillCommit(/*completion=*/nullptr,
                                               /*has_updates=*/true);
@@ -434,9 +457,10 @@
   // Should be a different size than previous call, to ensure it marks tree
   // changed.
   arbitrary_size = gfx::Size(111, 222);
-  EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(2);
   EXECUTE_AND_VERIFY_SUBTREE_CHANGED(top->SetBounds(arbitrary_size));
+  EXPECT_TRUE(layer_tree_host_->GetNeedsCommitAndReset());
   EXECUTE_AND_VERIFY_SUBTREE_CHANGED(mask_layer1->SetBounds(arbitrary_size));
+  EXPECT_TRUE(layer_tree_host_->GetNeedsCommitAndReset());
 
   commit_state = layer_tree_host_->WillCommit(/*completion=*/nullptr,
                                               /*has_updates=*/true);
@@ -450,8 +474,8 @@
 
   FilterOperations arbitrary_filters;
   arbitrary_filters.Append(FilterOperation::CreateOpacityFilter(0.5f));
-  EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(1);
   EXECUTE_AND_VERIFY_SUBTREE_CHANGED(top->SetFilters(arbitrary_filters));
+  EXPECT_TRUE(layer_tree_host_->GetNeedsCommitAndReset());
 
   commit_state = layer_tree_host_->WillCommit(/*completion=*/nullptr,
                                               /*has_updates=*/true);
@@ -463,7 +487,6 @@
                                     unsafe_state));
   layer_tree_host_->CommitComplete({base::TimeTicks(), base::TimeTicks::Now()});
 
-  EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(2);
   EXECUTE_AND_VERIFY_SUBTREE_CHANGED(
       top->SetBackdropFilters(arbitrary_filters));
 
@@ -476,9 +499,9 @@
       grand_child->PushPropertiesTo(grand_child_impl.get(), *commit_state,
                                     unsafe_state));
   layer_tree_host_->CommitComplete({base::TimeTicks(), base::TimeTicks::Now()});
+  EXPECT_TRUE(layer_tree_host_->GetNeedsCommitAndReset());
 
   gfx::PointF arbitrary_point_f = gfx::PointF(0.125f, 0.25f);
-  EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(1);
   top->SetPosition(arbitrary_point_f);
   TransformNode* node =
       layer_tree_host_->property_trees()->transform_tree_mutable().Node(
@@ -496,12 +519,13 @@
       layer_tree_host_->property_trees()->ResetAllChangeTracking());
   layer_tree_host_->CommitComplete({base::TimeTicks(), base::TimeTicks::Now()});
   EXPECT_FALSE(node->transform_changed);
+  EXPECT_TRUE(layer_tree_host_->GetNeedsCommitAndReset());
 
-  EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(1);
   child->SetPosition(arbitrary_point_f);
   node = layer_tree_host_->property_trees()->transform_tree_mutable().Node(
       child->transform_tree_index());
   EXPECT_TRUE(node->transform_changed);
+  EXPECT_TRUE(layer_tree_host_->GetNeedsCommitAndReset());
 
   commit_state = layer_tree_host_->WillCommit(/*completion=*/nullptr,
                                               /*has_updates=*/true);
@@ -516,11 +540,11 @@
   EXPECT_FALSE(node->transform_changed);
 
   gfx::Point3F arbitrary_point_3f = gfx::Point3F(0.125f, 0.25f, 0.f);
-  EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(1);
   top->SetTransformOrigin(arbitrary_point_3f);
   node = layer_tree_host_->property_trees()->transform_tree_mutable().Node(
       top->transform_tree_index());
   EXPECT_TRUE(node->transform_changed);
+  EXPECT_TRUE(layer_tree_host_->GetNeedsCommitAndReset());
 
   commit_state = layer_tree_host_->WillCommit(/*completion=*/nullptr,
                                               /*has_updates=*/true);
@@ -535,11 +559,11 @@
 
   gfx::Transform arbitrary_transform;
   arbitrary_transform.Scale3d(0.1f, 0.2f, 0.3f);
-  EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(1);
   top->SetTransform(arbitrary_transform);
   node = layer_tree_host_->property_trees()->transform_tree_mutable().Node(
       top->transform_tree_index());
   EXPECT_TRUE(node->transform_changed);
+  EXPECT_TRUE(layer_tree_host_->GetNeedsCommitAndReset());
 }
 
 TEST_F(LayerTest, AddAndRemoveChild) {
@@ -787,8 +811,8 @@
 
   EXPECT_FALSE(child4->parent());
 
-  EXPECT_SET_NEEDS_FULL_TREE_SYNC(
-      AtLeast(1), parent_->ReplaceChild(child2_.get(), child4));
+  EXPECT_SET_NEEDS_FULL_TREE_SYNC(AtLeast(1),
+                                  parent_->ReplaceChild(child2_.get(), child4));
   EXPECT_FALSE(LayerNeedsDisplay(parent_.get()));
   EXPECT_FALSE(LayerNeedsDisplay(child1_.get()));
   EXPECT_FALSE(LayerNeedsDisplay(child2_.get()));
@@ -815,8 +839,8 @@
   EXPECT_EQ(child4, test_layer->children()[0]);
   EXPECT_EQ(test_layer.get(), child4->parent());
 
-  EXPECT_SET_NEEDS_FULL_TREE_SYNC(
-      AtLeast(1), parent_->ReplaceChild(child2_.get(), child4));
+  EXPECT_SET_NEEDS_FULL_TREE_SYNC(AtLeast(1),
+                                  parent_->ReplaceChild(child2_.get(), child4));
 
   ASSERT_EQ(3U, parent_->children().size());
   EXPECT_EQ(child1_, parent_->children()[0]);
@@ -833,10 +857,8 @@
 TEST_F(LayerTest, ReplaceChildWithSameChild) {
   CreateSimpleTestTree();
 
-  // SetNeedsFullTreeSync / SetNeedsCommit should not be called because its the
-  // same child.
-  EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(0);
-  EXPECT_CALL(*layer_tree_host_, SetNeedsFullTreeSync()).Times(0);
+  // SetNeedsFullTreeSync / SetNeedsCommit should not be called because its
+  // the same child.
   parent_->ReplaceChild(child2_.get(), child2_);
 
   VerifyTestTreeInitialState();
@@ -886,7 +908,7 @@
   EXPECT_EQ(parent_.get(), child1_->RootLayer());
   EXPECT_EQ(parent_.get(), child2_->RootLayer());
   EXPECT_EQ(parent_.get(), child3_->RootLayer());
-  EXPECT_EQ(child4.get(),   child4->RootLayer());
+  EXPECT_EQ(child4.get(), child4->RootLayer());
   EXPECT_EQ(parent_.get(), grand_child1_->RootLayer());
   EXPECT_EQ(parent_.get(), grand_child2_->RootLayer());
   EXPECT_EQ(parent_.get(), grand_child3_->RootLayer());
@@ -917,8 +939,8 @@
 
   child2_->ReplaceChild(grand_child3_.get(), child1_);
 
-  // |grand_child3| gets orphaned and the child1 subtree gets planted back into
-  // the tree under child2.
+  // |grand_child3| gets orphaned and the child1 subtree gets planted back
+  // into the tree under child2.
   EXPECT_EQ(parent_.get(), parent_->RootLayer());
   EXPECT_EQ(parent_.get(), child1_->RootLayer());
   EXPECT_EQ(parent_.get(), child2_->RootLayer());
@@ -938,7 +960,7 @@
   scoped_refptr<Layer> test_layer = Layer::Create();
   EXPECT_SET_NEEDS_FULL_TREE_SYNC(1,
                                   layer_tree_host_->SetRootLayer(test_layer));
-  EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetIsDrawable(true));
+  EXPECT_SET_NEEDS_COMMIT_WAS_CALLED(test_layer->SetIsDrawable(true));
 
   gfx::Size test_bounds = gfx::Size(501, 508);
 
@@ -948,9 +970,9 @@
   // Before anything, test_layer should not be dirty.
   EXPECT_FALSE(LayerNeedsDisplay(test_layer.get()));
 
-  // This is just initialization, but SetNeedsCommit behavior is verified anyway
-  // to avoid warnings.
-  EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetBounds(test_bounds));
+  // This is just initialization, but SetNeedsCommit behavior is verified
+  // anyway to avoid warnings.
+  EXPECT_SET_NEEDS_COMMIT_WAS_CALLED(test_layer->SetBounds(test_bounds));
   EXPECT_FALSE(LayerNeedsDisplay(test_layer.get()));
 
   // The real test begins here.
@@ -972,12 +994,13 @@
 
   // Case 3: SetNeedsDisplay() with an empty rect.
   EXPECT_FALSE(LayerNeedsDisplay(test_layer.get()));
-  EXPECT_SET_NEEDS_COMMIT(0, test_layer->SetNeedsDisplayRect(gfx::Rect()));
+  EXPECT_SET_NEEDS_COMMIT_WAS_NOT_CALLED(
+      test_layer->SetNeedsDisplayRect(gfx::Rect()));
   EXPECT_FALSE(LayerNeedsDisplay(test_layer.get()));
   SimulateCommitForLayer(test_layer.get());
 
   // Case 4: SetNeedsDisplay() with a non-drawable layer
-  EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetIsDrawable(false));
+  EXPECT_SET_NEEDS_COMMIT_WAS_CALLED(test_layer->SetIsDrawable(false));
   SimulateCommitForLayer(test_layer.get());
   EXPECT_FALSE(LayerNeedsDisplay(test_layer.get()));
   EXPECT_SET_NEEDS_UPDATE(0, test_layer->SetNeedsDisplayRect(dirty_rect));
@@ -988,7 +1011,7 @@
   scoped_refptr<Layer> test_layer = Layer::Create();
   EXPECT_SET_NEEDS_FULL_TREE_SYNC(1,
                                   layer_tree_host_->SetRootLayer(test_layer));
-  EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetIsDrawable(true));
+  EXPECT_SET_NEEDS_COMMIT_WAS_CALLED(test_layer->SetIsDrawable(true));
 
   FakeContentLayerClient client;
   scoped_refptr<PictureLayer> mask_layer1 = PictureLayer::Create(&client);
@@ -999,37 +1022,50 @@
   // Next, test properties that should call SetNeedsCommit (but not
   // SetNeedsDisplay). All properties need to be set to new values in order for
   // SetNeedsCommit to be called.
-  EXPECT_SET_NEEDS_COMMIT(
-      1, test_layer->SetTransformOrigin(gfx::Point3F(1.23f, 4.56f, 0.f)));
-  EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetBackgroundColor(SkColors::kLtGray));
-  EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetMasksToBounds(true));
-  EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetClipRect(gfx::Rect(1, 2, 3, 4)));
-  EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetRoundedCorner({1, 2, 3, 4}));
-  EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetIsFastRoundedCorner(true));
-  EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetOpacity(0.5f));
-  EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetBlendMode(SkBlendMode::kHue));
-  EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetContentsOpaque(true));
-  EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetPosition(gfx::PointF(4.f, 9.f)));
+  EXPECT_SET_NEEDS_COMMIT_WAS_CALLED(
+      test_layer->SetTransformOrigin(gfx::Point3F(1.23f, 4.56f, 0.f)));
+  EXPECT_SET_NEEDS_COMMIT_WAS_CALLED(
+      test_layer->SetBackgroundColor(SkColors::kLtGray));
+  EXPECT_SET_NEEDS_COMMIT_WAS_CALLED(test_layer->SetMasksToBounds(true));
+  EXPECT_SET_NEEDS_COMMIT_WAS_CALLED(
+      test_layer->SetClipRect(gfx::Rect(1, 2, 3, 4)));
+  EXPECT_SET_NEEDS_COMMIT_WAS_CALLED(
+      test_layer->SetRoundedCorner({1, 2, 3, 4}));
+  EXPECT_SET_NEEDS_COMMIT_WAS_CALLED(test_layer->SetIsFastRoundedCorner(true));
+  EXPECT_SET_NEEDS_COMMIT_WAS_CALLED(test_layer->SetOpacity(0.5f));
+  EXPECT_SET_NEEDS_COMMIT_WAS_CALLED(
+      test_layer->SetBlendMode(SkBlendMode::kHue));
+  EXPECT_SET_NEEDS_COMMIT_WAS_CALLED(test_layer->SetContentsOpaque(true));
+  EXPECT_SET_NEEDS_COMMIT_WAS_CALLED(
+      test_layer->SetPosition(gfx::PointF(4.f, 9.f)));
   // We can use any layer pointer here since we aren't syncing for real.
-  EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetScrollable(gfx::Size(1, 1)));
-  EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetUserScrollable(true, false));
-  EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetScrollOffset(gfx::PointF(10, 10)));
-  EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetNonFastScrollableRegion(
-      Region(gfx::Rect(1, 1, 2, 2))));
-  EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetTransform(
-      gfx::Transform(0.0, 0.0, 0.0, 0.0, 0.0, 0.0)));
+  EXPECT_SET_NEEDS_COMMIT_WAS_CALLED(
+      test_layer->SetScrollable(gfx::Size(1, 1)));
+  EXPECT_SET_NEEDS_COMMIT_WAS_CALLED(
+      test_layer->SetUserScrollable(true, false));
+  EXPECT_SET_NEEDS_COMMIT_WAS_CALLED(
+      test_layer->SetScrollOffset(gfx::PointF(10, 10)));
+  EXPECT_SET_NEEDS_COMMIT_WAS_CALLED(
+      test_layer->SetNonFastScrollableRegion(Region(gfx::Rect(1, 1, 2, 2))));
+  EXPECT_SET_NEEDS_COMMIT_WAS_CALLED(
+      test_layer->SetTransform(gfx::Transform(0.0, 0.0, 0.0, 0.0, 0.0, 0.0)));
   TouchActionRegion touch_action_region;
   touch_action_region.Union(TouchAction::kNone, gfx::Rect(10, 10));
-  EXPECT_SET_NEEDS_COMMIT(
-      1, test_layer->SetTouchActionRegion(std::move(touch_action_region)));
-  EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetForceRenderSurfaceForTesting(true));
-  EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetHideLayerAndSubtree(true));
-  EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetElementId(ElementId(2)));
-
-  EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(1);
+  EXPECT_SET_NEEDS_COMMIT_WAS_CALLED(
+      test_layer->SetTouchActionRegion(std::move(touch_action_region)));
+  EXPECT_SET_NEEDS_COMMIT_WAS_CALLED(
+      test_layer->SetForceRenderSurfaceForTesting(true));
+  EXPECT_SET_NEEDS_COMMIT_WAS_CALLED(test_layer->SetHideLayerAndSubtree(true));
+  EXPECT_SET_NEEDS_COMMIT_WAS_CALLED(test_layer->SetElementId(ElementId(2)));
+  EXPECT_SET_NEEDS_COMMIT_WAS_CALLED(
+      test_layer->SetCaptureBounds(viz::RegionCaptureBounds(
+          base::flat_map<viz::RegionCaptureCropId, gfx::Rect>{
+              {viz::RegionCaptureCropId(123u, 456u),
+               gfx::Rect(0, 0, 640, 480)}})));
   EXPECT_SET_NEEDS_FULL_TREE_SYNC(1, test_layer->SetMaskLayer(mask_layer1));
-
-  // The above tests should not have caused a change to the needs_display flag.
+  EXPECT_TRUE(layer_tree_host_->GetNeedsCommitAndReset());
+  // The above tests should not have caused a change to the needs_display
+  // flag.
   EXPECT_FALSE(LayerNeedsDisplay(test_layer.get()));
 
   // As layers are removed from the tree, they will cause a tree sync.
@@ -1050,8 +1086,8 @@
   CommitAndPushProperties(test_layer.get(), impl_layer_ptr);
   EXPECT_EQ(gfx::Rect(0, 0, 5, 5), impl_layer_ptr->update_rect());
 
-  // The LayerImpl's update_rect() should be accumulated here, since we did not
-  // do anything to clear it.
+  // The LayerImpl's update_rect() should be accumulated here, since we did
+  // not do anything to clear it.
   test_layer->SetNeedsDisplayRect(gfx::Rect(10, 10, 5, 5));
   CommitAndPushProperties(test_layer.get(), impl_layer_ptr);
   EXPECT_EQ(gfx::Rect(0, 0, 15, 15), impl_layer_ptr->update_rect());
@@ -1074,7 +1110,7 @@
 
   gfx::Transform transform;
   transform.Rotate(45.0);
-  EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetTransform(transform));
+  EXPECT_SET_NEEDS_COMMIT_WAS_CALLED(test_layer->SetTransform(transform));
 
   EXPECT_FALSE(impl_layer->LayerPropertyChanged());
 
@@ -1094,7 +1130,8 @@
   EXPECT_SET_NEEDS_FULL_TREE_SYNC(1,
                                   layer_tree_host_->SetRootLayer(test_layer));
 
-  EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetRoundedCorner({1, 2, 3, 4}));
+  EXPECT_SET_NEEDS_COMMIT_WAS_CALLED(
+      test_layer->SetRoundedCorner({1, 2, 3, 4}));
 
   EXPECT_FALSE(impl_layer->LayerPropertyChanged());
 
@@ -1113,7 +1150,7 @@
   EXPECT_SET_NEEDS_FULL_TREE_SYNC(1,
                                   layer_tree_host_->SetRootLayer(test_layer));
 
-  EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetOpacity(0.5f));
+  EXPECT_SET_NEEDS_COMMIT_WAS_CALLED(test_layer->SetOpacity(0.5f));
 
   EXPECT_FALSE(impl_layer->LayerPropertyChanged());
 
@@ -1182,8 +1219,8 @@
   FakeContentLayerClient client;
   scoped_refptr<PictureLayer> mask = PictureLayer::Create(&client);
 
-  // Set up a detached tree of layers. The host pointer should be nil for these
-  // layers.
+  // Set up a detached tree of layers. The host pointer should be nil for
+  // these layers.
   parent->AddChild(child);
   child->SetMaskLayer(mask);
 
@@ -1255,8 +1292,9 @@
   AssertLayerTreeHostMatchesForSubtree(parent.get(),
                                        first_layer_tree_host.get());
 
-  // Now re-root the tree to a new host (simulating what we do on a context lost
-  // event). This should update the host pointers for all layers in the tree.
+  // Now re-root the tree to a new host (simulating what we do on a context
+  // lost event). This should update the host pointers for all layers in the
+  // tree.
   auto animation_host2 = AnimationHost::CreateForTesting(ThreadInstance::MAIN);
   std::unique_ptr<LayerTreeHost> second_layer_tree_host =
       factory.Create(animation_host2.get());
@@ -1289,8 +1327,8 @@
   AssertLayerTreeHostMatchesForSubtree(first_parent.get(),
                                        first_layer_tree_host.get());
 
-  // Now reparent the subtree starting at second_child to a layer in a different
-  // tree.
+  // Now reparent the subtree starting at second_child to a layer in a
+  // different tree.
   auto animation_host2 = AnimationHost::CreateForTesting(ThreadInstance::MAIN);
   std::unique_ptr<LayerTreeHost> second_layer_tree_host =
       factory.Create(animation_host2.get());
@@ -1327,7 +1365,8 @@
 
   AssertLayerTreeHostMatchesForSubtree(parent.get(), layer_tree_host.get());
 
-  // Replacing the mask should clear out the old mask's subtree's host pointers.
+  // Replacing the mask should clear out the old mask's subtree's host
+  // pointers.
   parent->SetMaskLayer(mask_replacement);
   EXPECT_EQ(nullptr, mask->layer_tree_host());
   EXPECT_EQ(nullptr, mask_child->layer_tree_host());
@@ -1427,7 +1466,6 @@
       LayerImpl::Create(host_impl_.active_tree(), 1);
   EXPECT_SET_NEEDS_FULL_TREE_SYNC(1,
                                   layer_tree_host_->SetRootLayer(root_layer));
-  EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(5);
 
   // A layer that draws content should be hit testable.
   root_layer->SetIsDrawable(true);
@@ -1435,6 +1473,7 @@
   CommitAndPushProperties(root_layer.get(), impl_layer.get());
   EXPECT_TRUE(impl_layer->draws_content());
   EXPECT_TRUE(impl_layer->HitTestable());
+  EXPECT_TRUE(layer_tree_host_->GetNeedsCommitAndReset());
 
   // A layer that does not draw content and does not hit test without drawing
   // content should not be hit testable.
@@ -1443,6 +1482,7 @@
   CommitAndPushProperties(root_layer.get(), impl_layer.get());
   EXPECT_FALSE(impl_layer->draws_content());
   EXPECT_FALSE(impl_layer->HitTestable());
+  EXPECT_TRUE(layer_tree_host_->GetNeedsCommitAndReset());
 
   // |SetHitTestableWithoutDrawsContent| should cause a layer to become hit
   // testable even though it does not draw content.
@@ -1450,6 +1490,7 @@
   CommitAndPushProperties(root_layer.get(), impl_layer.get());
   EXPECT_FALSE(impl_layer->draws_content());
   EXPECT_TRUE(impl_layer->HitTestable());
+  EXPECT_TRUE(layer_tree_host_->GetNeedsCommitAndReset());
 }
 
 void ReceiveCopyOutputResult(int* result_count,
@@ -1575,12 +1616,12 @@
   EXPECT_SET_NEEDS_FULL_TREE_SYNC(1, layer_tree_host_->SetRootLayer(layer));
   auto element_id = layer->element_id();
 
-  EXPECT_CALL(*layer_tree_host_, SetNeedsUpdateLayers()).Times(1);
+  EXPECT_CALL(*layer_tree_host_, SetNeedsUpdateLayers());
   layer_tree_host_->SetElementOpacityMutated(element_id,
                                              ElementListType::ACTIVE, 0.5f);
   Mock::VerifyAndClearExpectations(layer_tree_host_.get());
 
-  EXPECT_CALL(*layer_tree_host_, SetNeedsUpdateLayers()).Times(1);
+  EXPECT_CALL(*layer_tree_host_, SetNeedsUpdateLayers());
   gfx::Transform transform;
   transform.Rotate(45.0);
   layer_tree_host_->SetElementTransformMutated(
@@ -1603,15 +1644,12 @@
   EXPECT_SET_NEEDS_FULL_TREE_SYNC(1,
                                   layer_tree_host_->SetRootLayer(test_layer));
 
-  EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(1);
-
   test_layer->SetElementId(ElementId(2));
-
   EXPECT_FALSE(impl_layer->element_id());
 
   CommitAndPushProperties(test_layer.get(), impl_layer.get());
-
   EXPECT_EQ(ElementId(2), impl_layer->element_id());
+  EXPECT_TRUE(layer_tree_host_->GetNeedsCommitAndReset());
 }
 
 TEST_F(LayerTest, SetLayerTreeHostNotUsingLayerListsManagesElementId) {
@@ -1621,7 +1659,6 @@
 
   // Expect additional calls due to has-animation check and initialization
   // of keyframes.
-  EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(3);
   scoped_refptr<AnimationTimeline> timeline =
       AnimationTimeline::Create(AnimationIdProvider::NextTimelineId());
   animation_host_->AddAnimationTimeline(timeline);
@@ -1629,11 +1666,13 @@
   AddOpacityTransitionToElementWithAnimation(element_id, timeline, 10.0, 1.f,
                                              0.f, false);
   EXPECT_TRUE(animation_host_->IsElementAnimating(element_id));
+  EXPECT_TRUE(layer_tree_host_->GetNeedsCommitAndReset());
 
   EXPECT_EQ(nullptr, layer_tree_host_->LayerByElementId(element_id));
   test_layer->SetLayerTreeHost(layer_tree_host_.get());
   // Layer should now be registered by element id.
   EXPECT_EQ(test_layer, layer_tree_host_->LayerByElementId(element_id));
+  EXPECT_TRUE(layer_tree_host_->GetNeedsCommitAndReset());
 
   // We're expected to remove the animations before calling
   // SetLayerTreeHost(nullptr).
@@ -1648,7 +1687,6 @@
 // compositor is expensive and updated counts can wait until the next
 // commit to be pushed. See https://crbug.com/1083244.
 TEST_F(LayerTest, PushAnimationCountsLazily) {
-  EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(0);
   animation_host_->SetAnimationCounts(0);
   animation_host_->SetCurrentFrameHadRaf(true);
   animation_host_->SetNextFrameHasPendingRaf(true);
@@ -1660,19 +1698,20 @@
                                     *layer_tree_host_->property_trees());
   EXPECT_TRUE(host_impl_.animation_host()->CurrentFrameHadRAF());
   EXPECT_TRUE(host_impl_.animation_host()->HasSmilAnimation());
+  EXPECT_FALSE(layer_tree_host_->GetNeedsCommitAndReset());
 }
 
 TEST_F(LayerTest, SetElementIdNotUsingLayerLists) {
   scoped_refptr<Layer> test_layer = Layer::Create();
   test_layer->SetLayerTreeHost(layer_tree_host_.get());
 
-  EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(2);
   ElementId element_id = ElementId(2);
   EXPECT_EQ(nullptr, layer_tree_host_->LayerByElementId(element_id));
 
   test_layer->SetElementId(element_id);
   // Layer should now be registered by element id.
   EXPECT_EQ(test_layer, layer_tree_host_->LayerByElementId(element_id));
+  EXPECT_TRUE(layer_tree_host_->GetNeedsCommitAndReset());
 
   ElementId other_element_id = ElementId(3);
   test_layer->SetElementId(other_element_id);
@@ -1682,6 +1721,7 @@
   EXPECT_EQ(test_layer, layer_tree_host_->LayerByElementId(other_element_id));
 
   test_layer->SetLayerTreeHost(nullptr);
+  EXPECT_TRUE(layer_tree_host_->GetNeedsCommitAndReset());
 }
 
 // Verifies that when mirror count of the layer is incremented or decremented,
@@ -1701,7 +1741,8 @@
   EXPECT_EQ(0u, layer_tree_host_->GetPendingCommitState()
                     ->layers_that_should_push_properties.size());
 
-  // Incrementing mirror count from zero should trigger property trees rebuild.
+  // Incrementing mirror count from zero should trigger property trees
+  // rebuild.
   test_layer->IncrementMirrorCount();
   EXPECT_EQ(1, test_layer->mirror_count());
   EXPECT_TRUE(layer_tree_host_->property_trees()->needs_rebuild());
@@ -1745,6 +1786,64 @@
   test_layer->SetLayerTreeHost(nullptr);
 }
 
+TEST_F(LayerTest, UpdatingCaptureBounds) {
+  static const viz::RegionCaptureBounds kEmptyBounds;
+  static const viz::RegionCaptureBounds kPopulatedBounds(
+      base::flat_map<viz::RegionCaptureCropId, gfx::Rect>{
+          {viz::RegionCaptureCropId(123u, 456u), gfx::Rect(0, 0, 640, 480)}});
+  static const viz::RegionCaptureBounds kUpdatedBounds(
+      base::flat_map<viz::RegionCaptureCropId, gfx::Rect>{
+          {viz::RegionCaptureCropId(123u, 456u), gfx::Rect(0, 0, 1280, 720)}});
+
+  // We don't track full tree syncs in this test.
+  EXPECT_CALL(*layer_tree_host_, SetNeedsFullTreeSync()).Times(AtLeast(1));
+
+  scoped_refptr<Layer> layer = Layer::Create();
+  layer_tree_host_->SetRootLayer(layer);
+
+  // Clear the updates caused by setting a new root layer.
+  layer->ClearSubtreePropertyChangedForTesting();
+  layer_tree_host_->property_trees()->set_needs_rebuild(false);
+
+  // An empty bounds when none is currently set should not cause an update.
+  layer->SetCaptureBounds(kEmptyBounds);
+  EXPECT_FALSE(layer_tree_host_->property_trees()->needs_rebuild());
+  EXPECT_FALSE(layer->subtree_property_changed());
+  EXPECT_FALSE(layer_tree_host_->GetNeedsCommitAndReset());
+
+  // Setting to a new bounds should cause an update.
+  layer->SetCaptureBounds(kPopulatedBounds);
+  EXPECT_TRUE(layer_tree_host_->property_trees()->needs_rebuild());
+  EXPECT_TRUE(layer->subtree_property_changed());
+  EXPECT_TRUE(layer_tree_host_->GetNeedsCommitAndReset());
+
+  // Reset properties.
+  layer->ClearSubtreePropertyChangedForTesting();
+  layer_tree_host_->property_trees()->set_needs_rebuild(false);
+
+  // Setting to the same bounds should not, however.
+  layer->SetCaptureBounds(kPopulatedBounds);
+  EXPECT_FALSE(layer_tree_host_->property_trees()->needs_rebuild());
+  EXPECT_FALSE(layer->subtree_property_changed());
+  EXPECT_FALSE(layer_tree_host_->GetNeedsCommitAndReset());
+
+  // Switching to a differently valued bounds should cause an update.
+  layer->SetCaptureBounds(kUpdatedBounds);
+  EXPECT_TRUE(layer_tree_host_->property_trees()->needs_rebuild());
+  EXPECT_TRUE(layer->subtree_property_changed());
+  EXPECT_TRUE(layer_tree_host_->GetNeedsCommitAndReset());
+
+  // Reset properties.
+  layer->ClearSubtreePropertyChangedForTesting();
+  layer_tree_host_->property_trees()->set_needs_rebuild(false);
+
+  // Finally, setting to empty should cause an update.
+  layer->SetCaptureBounds(kEmptyBounds);
+  EXPECT_TRUE(layer_tree_host_->property_trees()->needs_rebuild());
+  EXPECT_TRUE(layer->subtree_property_changed());
+  EXPECT_TRUE(layer_tree_host_->GetNeedsCommitAndReset());
+}
+
 TEST_F(LayerTest, UpdatingClipRect) {
   const gfx::Size kRootSize(200, 200);
   const gfx::Vector2dF kParentOffset(10.f, 20.f);
@@ -1763,7 +1862,6 @@
   scoped_refptr<Layer> clipped_4 = Layer::Create();
 
   EXPECT_CALL(*layer_tree_host_, SetNeedsFullTreeSync()).Times(AtLeast(1));
-  EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(AtLeast(1));
   layer_tree_host_->SetRootLayer(root);
   root->AddChild(parent);
   parent->AddChild(clipped_1);
@@ -1805,9 +1903,9 @@
   EXPECT_EQ(gfx::RectF(kClipRect) + kParentOffset, node_3->clip);
   EXPECT_EQ(gfx::RectF(kClipRect) + kParentOffset, node_4->clip);
 
-  // The following layer properties should result in the layer being clipped to
-  // its bounds along with being clipped by the clip rect. Check if the final
-  // rect on the clip node is set correctly.
+  // The following layer properties should result in the layer being clipped
+  // to its bounds along with being clipped by the clip rect. Check if the
+  // final rect on the clip node is set correctly.
 
   // Setting clip to layer bounds.
   clipped_1->SetMasksToBounds(true);
@@ -1869,7 +1967,6 @@
   scoped_refptr<Layer> layer_5 = Layer::Create();
 
   EXPECT_CALL(*layer_tree_host_, SetNeedsFullTreeSync()).Times(AtLeast(1));
-  EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(AtLeast(1));
   layer_tree_host_->SetRootLayer(root);
   root->AddChild(layer_1);
   root->AddChild(layer_2);
diff --git a/cc/layers/picture_layer_impl.cc b/cc/layers/picture_layer_impl.cc
index 438dabcd..8993cc58 100644
--- a/cc/layers/picture_layer_impl.cc
+++ b/cc/layers/picture_layer_impl.cc
@@ -295,9 +295,10 @@
 
   if (current_draw_mode_ == DRAW_MODE_RESOURCELESS_SOFTWARE) {
     DCHECK(shared_quad_state->quad_layer_rect.origin() == gfx::Point(0, 0));
+    // TODO(crbug/1308932): Remove toSkColor and make all SkColor4f.
     AppendDebugBorderQuad(
         render_pass, shared_quad_state->quad_layer_rect, shared_quad_state,
-        append_quads_data, DebugColors::DirectPictureBorderColor(),
+        append_quads_data, DebugColors::DirectPictureBorderColor().toSkColor(),
         DebugColors::DirectPictureBorderWidth(device_scale_factor));
 
     gfx::Rect geometry_rect = shared_quad_state->visible_quad_layer_rect;
@@ -369,28 +370,30 @@
       SkColor color;
       float width;
       if (*iter && iter->draw_info().IsReadyToDraw()) {
+        // TODO(crbug/1308932): Remove all instances of toSkColor below and make
+        // all SkColor4f.
         TileDrawInfo::Mode mode = iter->draw_info().mode();
         if (mode == TileDrawInfo::SOLID_COLOR_MODE) {
-          color = DebugColors::SolidColorTileBorderColor();
+          color = DebugColors::SolidColorTileBorderColor().toSkColor();
           width = DebugColors::SolidColorTileBorderWidth(device_scale_factor);
         } else if (mode == TileDrawInfo::OOM_MODE) {
-          color = DebugColors::OOMTileBorderColor();
+          color = DebugColors::OOMTileBorderColor().toSkColor();
           width = DebugColors::OOMTileBorderWidth(device_scale_factor);
         } else if (iter.resolution() == HIGH_RESOLUTION) {
-          color = DebugColors::HighResTileBorderColor();
+          color = DebugColors::HighResTileBorderColor().toSkColor();
           width = DebugColors::HighResTileBorderWidth(device_scale_factor);
         } else if (iter.resolution() == LOW_RESOLUTION) {
-          color = DebugColors::LowResTileBorderColor();
+          color = DebugColors::LowResTileBorderColor().toSkColor();
           width = DebugColors::LowResTileBorderWidth(device_scale_factor);
         } else if (iter->contents_scale_key() > max_contents_scale) {
-          color = DebugColors::ExtraHighResTileBorderColor();
+          color = DebugColors::ExtraHighResTileBorderColor().toSkColor();
           width = DebugColors::ExtraHighResTileBorderWidth(device_scale_factor);
         } else {
-          color = DebugColors::ExtraLowResTileBorderColor();
+          color = DebugColors::ExtraLowResTileBorderColor().toSkColor();
           width = DebugColors::ExtraLowResTileBorderWidth(device_scale_factor);
         }
       } else {
-        color = DebugColors::MissingTileBorderColor();
+        color = DebugColors::MissingTileBorderColor().toSkColor();
         width = DebugColors::MissingTileBorderWidth(device_scale_factor);
       }
 
@@ -408,14 +411,16 @@
   }
 
   if (layer_tree_impl()->debug_state().highlight_non_lcd_text_layers) {
-    SkColor color =
+    // TODO(crbug/1308932): Remove all instances of toSkColor below and make all
+    // SkColor4f.
+    SkColor4f color =
         DebugColors::NonLCDTextHighlightColor(lcd_text_disallowed_reason());
-    if (color != SK_ColorTRANSPARENT &&
+    if (color != SkColors::kTransparent &&
         GetRasterSource()->GetDisplayItemList()->AreaOfDrawText(
             gfx::Rect(bounds()))) {
       render_pass->CreateAndAppendDrawQuad<viz::SolidColorDrawQuad>()->SetNew(
-          shared_quad_state, debug_border_rect, debug_border_rect, color,
-          append_quads_data);
+          shared_quad_state, debug_border_rect, debug_border_rect,
+          color.toSkColor(), append_quads_data);
     }
   }
 
@@ -516,7 +521,8 @@
       SkColor color = safe_opaque_background_color();
       if (ShowDebugBorders(DebugBorderType::LAYER)) {
         // Fill the whole tile with the missing tile color.
-        color = DebugColors::DefaultCheckerboardColor();
+        // TODO(crbug/1308932): Remove toSkColor and make all SkColor4f.
+        color = DebugColors::DefaultCheckerboardColor().toSkColor();
       }
       auto* quad =
           render_pass->CreateAndAppendDrawQuad<viz::SolidColorDrawQuad>();
@@ -1868,10 +1874,12 @@
       layer_tree_impl() ? layer_tree_impl()->device_scale_factor() : 1;
 
   if (IsDirectlyCompositedImage()) {
-    *color = DebugColors::ImageLayerBorderColor();
+    // TODO(crbug/1308932): Remove toSkColor and make all SkColor4f.
+    *color = DebugColors::ImageLayerBorderColor().toSkColor();
     *width = DebugColors::ImageLayerBorderWidth(device_scale_factor);
   } else {
-    *color = DebugColors::TiledContentLayerBorderColor();
+    // TODO(crbug/1308932): Remove toSkColor and make all SkColor4f.
+    *color = DebugColors::TiledContentLayerBorderColor().toSkColor();
     *width = DebugColors::TiledContentLayerBorderWidth(device_scale_factor);
   }
 }
diff --git a/cc/layers/render_surface_impl.cc b/cc/layers/render_surface_impl.cc
index 6636586..d274baf 100644
--- a/cc/layers/render_surface_impl.cc
+++ b/cc/layers/render_surface_impl.cc
@@ -111,7 +111,8 @@
 }
 
 SkColor RenderSurfaceImpl::GetDebugBorderColor() const {
-  return DebugColors::SurfaceBorderColor();
+  // TODO(crbug/1308932): Remove toSkColor and make all SkColor4f.
+  return DebugColors::SurfaceBorderColor().toSkColor();
 }
 
 float RenderSurfaceImpl::GetDebugBorderWidth() const {
diff --git a/cc/layers/surface_layer_impl.cc b/cc/layers/surface_layer_impl.cc
index b58be06..4739957 100644
--- a/cc/layers/surface_layer_impl.cc
+++ b/cc/layers/surface_layer_impl.cc
@@ -219,7 +219,8 @@
 
 void SurfaceLayerImpl::GetDebugBorderProperties(SkColor* color,
                                                 float* width) const {
-  *color = DebugColors::SurfaceLayerBorderColor();
+  // TODO(crbug/1308932): Remove toSkColor and make all SkColor4f.
+  *color = DebugColors::SurfaceLayerBorderColor().toSkColor();
   *width = DebugColors::SurfaceLayerBorderWidth(
       layer_tree_impl() ? layer_tree_impl()->device_scale_factor() : 1);
 }
diff --git a/cc/paint/display_item_list_unittest.cc b/cc/paint/display_item_list_unittest.cc
index 3246ef6..90608ac 100644
--- a/cc/paint/display_item_list_unittest.cc
+++ b/cc/paint/display_item_list_unittest.cc
@@ -31,6 +31,7 @@
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/geometry/rect_conversions.h"
 #include "ui/gfx/geometry/skia_conversions.h"
+#include "ui/gfx/geometry/transform.h"
 
 namespace cc {
 
diff --git a/cc/test/pixel_test_output_surface.cc b/cc/test/pixel_test_output_surface.cc
index d0c52621..fb3c64d 100644
--- a/cc/test/pixel_test_output_surface.cc
+++ b/cc/test/pixel_test_output_surface.cc
@@ -21,9 +21,7 @@
 
 PixelTestOutputSurface::PixelTestOutputSurface(
     std::unique_ptr<viz::SoftwareOutputDevice> software_device)
-    : OutputSurface(std::move(software_device)) {
-  capabilities_.supports_stencil = true;
-}
+    : OutputSurface(std::move(software_device)) {}
 
 PixelTestOutputSurface::~PixelTestOutputSurface() = default;
 
@@ -35,18 +33,10 @@
 
 void PixelTestOutputSurface::DiscardBackbuffer() {}
 
-void PixelTestOutputSurface::BindFramebuffer() {}
-
 void PixelTestOutputSurface::Reshape(const ReshapeParams& params) {
   software_device()->Resize(params.size, params.device_scale_factor);
 }
 
-bool PixelTestOutputSurface::HasExternalStencilTest() const {
-  return false;
-}
-
-void PixelTestOutputSurface::ApplyExternalStencil() {}
-
 void PixelTestOutputSurface::SwapBuffers(viz::OutputSurfaceFrame frame) {
   base::ThreadTaskRunnerHandle::Get()->PostTask(
       FROM_HERE, base::BindOnce(&PixelTestOutputSurface::SwapBuffersCallback,
@@ -66,21 +56,6 @@
   return false;
 }
 
-unsigned PixelTestOutputSurface::GetOverlayTextureId() const {
-  return 0;
-}
-
-uint32_t PixelTestOutputSurface::GetFramebufferCopyTextureFormat() {
-  // This format will work if the |context_provider| has an RGB or RGBA
-  // framebuffer. For now assume tests do not want/care about alpha in
-  // the root render pass.
-  return GL_RGB;
-}
-
-unsigned PixelTestOutputSurface::UpdateGpuFence() {
-  return 0;
-}
-
 void PixelTestOutputSurface::SetUpdateVSyncParametersCallback(
     viz::UpdateVSyncParametersCallback callback) {}
 
diff --git a/cc/test/pixel_test_output_surface.h b/cc/test/pixel_test_output_surface.h
index 95dc609..7dc4eedf 100644
--- a/cc/test/pixel_test_output_surface.h
+++ b/cc/test/pixel_test_output_surface.h
@@ -24,15 +24,9 @@
   void BindToClient(viz::OutputSurfaceClient* client) override;
   void EnsureBackbuffer() override;
   void DiscardBackbuffer() override;
-  void BindFramebuffer() override;
   void Reshape(const ReshapeParams& params) override;
-  bool HasExternalStencilTest() const override;
-  void ApplyExternalStencil() override;
   void SwapBuffers(viz::OutputSurfaceFrame frame) override;
   bool IsDisplayedAsOverlayPlane() const override;
-  unsigned GetOverlayTextureId() const override;
-  uint32_t GetFramebufferCopyTextureFormat() override;
-  unsigned UpdateGpuFence() override;
   void SetUpdateVSyncParametersCallback(
       viz::UpdateVSyncParametersCallback callback) override;
   void SetDisplayTransformHint(gfx::OverlayTransform transform) override {}
diff --git a/chrome/BUILD.gn b/chrome/BUILD.gn
index 6c60156..abbbfc5db 100644
--- a/chrome/BUILD.gn
+++ b/chrome/BUILD.gn
@@ -1594,12 +1594,6 @@
 }
 
 if (is_android) {
-  java_cpp_enum("assist_ranker_prediction_enum_javagen") {
-    sources = [
-      "browser/android/contextualsearch/contextual_search_ranker_logger_impl.h",
-    ]
-  }
-
   java_cpp_enum("partner_bookmarks_javagen") {
     sources = [ "browser/android/bookmarks/partner_bookmarks_reader.h" ]
   }
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index fb017b9..2f9b30d0 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -666,7 +666,6 @@
     ":chrome_strict_mode_switch",
     ":resource_id_javagen",
     ":vr_build_config",
-    "//chrome:assist_ranker_prediction_enum_javagen",
     "//chrome:instant_apps_reasons_enum_javagen",
     "//chrome:offline_pages_enum_javagen",
     "//chrome:quick_action_category_enum_javagen",
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanelMetrics.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanelMetrics.java
index 0f475fbe..f4cddb1 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanelMetrics.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanelMetrics.java
@@ -344,8 +344,6 @@
             mInteractionRecorder.logOutcome(
                     ContextualSearchInteractionRecorder.Feature.OUTCOME_WAS_PANEL_OPENED,
                     mWasSearchContentViewSeen);
-            ContextualSearchUma.logRankerInference(mWasSearchContentViewSeen,
-                    mInteractionRecorder.getPredictionForTapSuppression());
             mInteractionRecorder.logOutcome(
                     ContextualSearchInteractionRecorder.Feature.OUTCOME_WAS_CARDS_DATA_SHOWN,
                     mWasContextualCardsDataShown);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchHeuristic.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchHeuristic.java
index 0388246a2..72e5b76 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchHeuristic.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchHeuristic.java
@@ -52,14 +52,6 @@
     }
 
     /**
-     * Logs the heuristic to UMA and UKM through Ranker logging for the purpose of Tap Suppression.
-     * @param recorder A logger to log to.
-     */
-    protected void logRankerTapSuppression(ContextualSearchInteractionRecorder recorder) {
-        // Default is to not log.
-    }
-
-    /**
      * Logs a Ranker outcome using the heuristic for the purpose of Ranker Tap Suppression.
      * @param recorder A logger to log to.
      */
@@ -68,13 +60,6 @@
     }
 
     /**
-     * Allows a heuristic to override a machine-learning model's Tap Suppression.
-     */
-    protected boolean shouldOverrideMlTapSuppression() {
-        return false;
-    }
-
-    /**
      * Clamps an input value into a range of 1-10 inclusive.
      * @param value The value to limit.
      * @return A value that's at least 1 and at most 10.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchHeuristics.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchHeuristics.java
index 4161dce..8832cc5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchHeuristics.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchHeuristics.java
@@ -64,16 +64,6 @@
     }
 
     /**
-     * Logs all the heuristics that want to provide a Ranker "feature" to the given recorder.
-     * @param recorder The recorder to log to.
-     */
-    public void logRankerTapSuppression(ContextualSearchInteractionRecorder recorder) {
-        for (ContextualSearchHeuristic heuristic : mHeuristics) {
-            heuristic.logRankerTapSuppression(recorder);
-        }
-    }
-
-    /**
      * Logs all the heuristics that want to provide outcomes to Ranker to the given recorder.
      * @param recorder The logger to log to.
      */
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchInteractionRecorder.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchInteractionRecorder.java
index 5a9dd62..74d84e4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchInteractionRecorder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchInteractionRecorder.java
@@ -97,26 +97,6 @@
      */
     void logOutcome(@Feature int feature, Object value);
 
-    /**
-     * Tries to run the machine intelligence model for tap suppression and returns an int that
-     * describes whether the prediction was obtainable and what it was.
-     * See chrome/browser/android/contextualsearch/contextual_search_ranker_logger_impl.h for
-     * details on the {@link AssistRankerPrediction} possibilities.
-     * @return An integer that encodes the prediction result.
-     */
-    @AssistRankerPrediction
-    int runPredictionForTapSuppression();
-
-    /**
-     * Gets the previous result from trying to run the machine intelligence model for tap
-     * suppression. A previous call to {@link #runPredictionForTapSuppression} is required.
-     * See chrome/browser/android/contextualsearch/contextual_search_ranker_logger_impl.h for
-     * details on the {@link AssistRankerPrediction} possibilities.
-     * @return An integer that encodes the prediction.
-     */
-    @AssistRankerPrediction
-    int getPredictionForTapSuppression();
-
     /** Stores an Event ID from the server that we should persist along with user interactions. */
     void persistInteraction(long eventId);
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchRankerLoggerImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchRankerLoggerImpl.java
index 8eed3c8..2c49605 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchRankerLoggerImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchRankerLoggerImpl.java
@@ -27,14 +27,6 @@
     // The WebContents of the base page that the log data is associated with.
     private WebContents mBasePageWebContents;
 
-    // Whether inference has already occurred for this interaction (and calling #logFeature is no
-    // longer allowed).
-    private boolean mHasInferenceOccurred;
-
-    // What kind of ML prediction we were able to get.
-    private @AssistRankerPrediction int mAssistRankerPrediction =
-            AssistRankerPrediction.UNDETERMINED;
-
     // Map that accumulates all of the Features to log for a specific user-interaction.
     private Map<Integer /* @Feature */, Object> mFeaturesToLog;
 
@@ -111,7 +103,6 @@
     public void setupLoggingForPage(@Nullable WebContents basePageWebContents) {
         mIsLoggingReadyForPage = true;
         mBasePageWebContents = basePageWebContents;
-        mHasInferenceOccurred = false;
         ContextualSearchRankerLoggerImplJni.get().setupLoggingAndRanker(
                 mNativePointer, ContextualSearchRankerLoggerImpl.this, basePageWebContents);
     }
@@ -125,37 +116,16 @@
     @Override
     public void logOutcome(@Feature int feature, Object value) {
         assert mIsLoggingReadyForPage;
-        assert mHasInferenceOccurred;
         if (!isEnabled()) return;
 
         logInternal(feature, value);
     }
 
     @Override
-    public @AssistRankerPrediction int runPredictionForTapSuppression() {
-        assert mIsLoggingReadyForPage;
-        assert !mHasInferenceOccurred;
-        mHasInferenceOccurred = true;
-        if (isEnabled() && mBasePageWebContents != null) {
-            mAssistRankerPrediction = ContextualSearchRankerLoggerImplJni.get().runInference(
-                    mNativePointer, ContextualSearchRankerLoggerImpl.this);
-            ContextualSearchUma.logRecordedFeaturesToRanker();
-        }
-        return mAssistRankerPrediction;
-    }
-
-    @Override
-    public @AssistRankerPrediction int getPredictionForTapSuppression() {
-        return mAssistRankerPrediction;
-    }
-
-    @Override
     public void reset() {
         mIsLoggingReadyForPage = false;
-        mHasInferenceOccurred = false;
         mFeaturesToLog = null;
         mBasePageWebContents = null;
-        mAssistRankerPrediction = AssistRankerPrediction.UNDETERMINED;
     }
 
     @Override
@@ -164,7 +134,6 @@
             if (mBasePageWebContents != null && mFeaturesToLog != null
                     && !mFeaturesToLog.isEmpty()) {
                 assert mIsLoggingReadyForPage;
-                assert mHasInferenceOccurred;
 
                 mOutcomesLoggedForTesting = mFeaturesToLog;
                 ContextualSearchUma.logRecordedOutcomesToRanker();
@@ -230,9 +199,6 @@
                 ContextualSearchRankerLoggerImpl caller);
         void setupLoggingAndRanker(long nativeContextualSearchRankerLoggerImpl,
                 ContextualSearchRankerLoggerImpl caller, WebContents basePageWebContents);
-        // Returns an AssistRankerPrediction integer value.
-        int runInference(long nativeContextualSearchRankerLoggerImpl,
-                ContextualSearchRankerLoggerImpl caller);
 
         void writeLogAndReset(long nativeContextualSearchRankerLoggerImpl,
                 ContextualSearchRankerLoggerImpl caller);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchSelectionController.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchSelectionController.java
index 1687e76..4dd881b4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchSelectionController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchSelectionController.java
@@ -467,31 +467,10 @@
         mHandler.handleMetricsForWouldSuppressTap(tapHeuristics);
 
         boolean shouldSuppressTapBasedOnHeuristics = tapHeuristics.shouldSuppressTap();
-        boolean shouldOverrideMlTapSuppression = tapHeuristics.shouldOverrideMlTapSuppression();
-
-        // Make sure Tap Suppression features are consistent.
-        assert !ContextualSearchFieldTrial.getSwitch(
-                ContextualSearchSwitch.IS_CONTEXTUAL_SEARCH_ML_TAP_SUPPRESSION_ENABLED)
-                || interactionRecorder.isQueryEnabled()
-            : "Tap Suppression requires the Ranker Query feature to be enabled!";
-
-        // If we're suppressing based on heuristics then Ranker doesn't need to know about it.
-        @AssistRankerPrediction
-        int tapPrediction = AssistRankerPrediction.UNDETERMINED;
-        if (!shouldSuppressTapBasedOnHeuristics) {
-            tapHeuristics.logRankerTapSuppression(interactionRecorder);
-            tapPrediction = interactionRecorder.runPredictionForTapSuppression();
-            ContextualSearchUma.logRankerPrediction(tapPrediction);
-        }
 
         // Make the suppression decision and act upon it.
-        boolean shouldSuppressTapBasedOnRanker = (tapPrediction == AssistRankerPrediction.SUPPRESS)
-                && ContextualSearchFieldTrial.getSwitch(
-                        ContextualSearchSwitch.IS_CONTEXTUAL_SEARCH_ML_TAP_SUPPRESSION_ENABLED)
-                && !shouldOverrideMlTapSuppression;
-        if (shouldSuppressTapBasedOnHeuristics || shouldSuppressTapBasedOnRanker) {
-            Log.i(TAG, "Tap suppressed due to Ranker: %s, heuristics: %s",
-                    shouldSuppressTapBasedOnRanker, shouldSuppressTapBasedOnHeuristics);
+        if (shouldSuppressTapBasedOnHeuristics) {
+            Log.i(TAG, "Tap suppressed due to heuristics: %s", shouldSuppressTapBasedOnHeuristics);
             mHandler.handleSuppressedTap();
         } else {
             mHandler.handleNonSuppressedTap(mTapTimeNanoseconds);
@@ -499,8 +478,7 @@
 
         if (mTapTimeNanoseconds != 0) {
             // Remember the tap state for subsequent tap evaluation.
-            mLastTapState = new ContextualSearchTapState(
-                    x, y, mTapTimeNanoseconds, shouldSuppressTapBasedOnRanker);
+            mLastTapState = new ContextualSearchTapState(x, y, mTapTimeNanoseconds);
         } else {
             mLastTapState = null;
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTapState.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTapState.java
index 43d8bb2..491a69b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTapState.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTapState.java
@@ -5,27 +5,24 @@
 package org.chromium.chrome.browser.contextualsearch;
 
 /**
- * Encapsulates the state of a recent Tap gesture; x, y position and if ML-suppressed.
+ * Encapsulates the state of a recent Tap gesture; x, y position and the timestamp.
  * Instances of this class are immutable.
  */
 class ContextualSearchTapState {
     private final float mX;
     private final float mY;
     private final long mTapTimeNanoseconds;
-    private final boolean mWasMlSuppressed;
 
     /**
      * Constructs a Tap at the given x,y position and indicates if the tap was suppressed or not.
      * @param x The x coordinate of the current tap.
      * @param y The y coordinate of the current tap.
      * @param tapTimeNanoseconds The timestamp when the Tap occurred.
-     * @param wasMlSuppressed Whether this tap was suppressed by the ML model.
      */
-    ContextualSearchTapState(float x, float y, long tapTimeNanoseconds, boolean wasMlSuppressed) {
+    ContextualSearchTapState(float x, float y, long tapTimeNanoseconds) {
         mX = x;
         mY = y;
         mTapTimeNanoseconds = tapTimeNanoseconds;
-        mWasMlSuppressed = wasMlSuppressed;
     }
 
     /**
@@ -48,11 +45,4 @@
     long tapTimeNanoseconds() {
         return mTapTimeNanoseconds;
     }
-
-    /**
-     * @return Whether this Tap was suppressed by an ML model.
-     */
-    boolean wasMlSuppressed() {
-        return mWasMlSuppressed;
-    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchUma.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchUma.java
index 711f551..edfccf3 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchUma.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchUma.java
@@ -769,43 +769,6 @@
                 "Search.ContextualSearch.CardTag", cardTagEnum, CardTag.NUM_ENTRIES);
     }
 
-    /**
-     * Logs results-seen when we have a useful Ranker prediction inference.
-     * @param wasPanelSeen Whether the panel was seen.
-     * @param predictionKind An integer reflecting the Ranker prediction, e.g. that this is a good
-     *        time to suppress triggering because the likelihood of opening the panel is relatively
-     *        low.
-     */
-    public static void logRankerInference(
-            boolean wasPanelSeen, @AssistRankerPrediction int predictionKind) {
-        if (predictionKind == AssistRankerPrediction.SHOW) {
-            RecordHistogram.recordEnumeratedHistogram(
-                    "Search.ContextualSearch.Ranker.NotSuppressed.ResultsSeen",
-                    wasPanelSeen ? Results.SEEN : Results.NOT_SEEN, Results.NUM_ENTRIES);
-        } else if (predictionKind == AssistRankerPrediction.SUPPRESS) {
-            RecordHistogram.recordEnumeratedHistogram(
-                    "Search.ContextualSearch.Ranker.WouldSuppress.ResultsSeen",
-                    wasPanelSeen ? Results.SEEN : Results.NOT_SEEN, Results.NUM_ENTRIES);
-        }
-    }
-
-    /**
-     * Logs Ranker's prediction of whether or not to suppress.
-     * @param predictionKind An integer reflecting the Ranker prediction, e.g. that this is a good
-     *        time to suppress triggering because the likelihood of opening the panel is relatively
-     *        low.
-     */
-    public static void logRankerPrediction(@AssistRankerPrediction int predictionKind) {
-        // For now we just log whether or not suppression is predicted.
-        RecordHistogram.recordBooleanHistogram("Search.ContextualSearch.Ranker.Suppressed",
-                predictionKind == AssistRankerPrediction.SUPPRESS);
-    }
-
-    /** Logs that Ranker recorded a set of features for training or inference. */
-    public static void logRecordedFeaturesToRanker() {
-        logRecordedToRanker(false);
-    }
-
     /** Logs that Ranker recorded a set of outcomes for training or inference. */
     public static void logRecordedOutcomesToRanker() {
         logRecordedToRanker(true);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/TapSuppressionHeuristics.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/TapSuppressionHeuristics.java
index 0f3aafdd..1ff21851 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/TapSuppressionHeuristics.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/TapSuppressionHeuristics.java
@@ -54,14 +54,4 @@
         }
         return false;
     }
-
-    /**
-     * @return Whether the Tap should override an ML suppression.
-     */
-    boolean shouldOverrideMlTapSuppression() {
-        for (ContextualSearchHeuristic heuristic : mHeuristics) {
-            if (heuristic.shouldOverrideMlTapSuppression()) return true;
-        }
-        return false;
-    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabObserver.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabObserver.java
index ef2ec96f..8a58f6a5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabObserver.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabObserver.java
@@ -187,7 +187,7 @@
         boolean firstNavigation = mFirstCommitTimestamp == 0;
         boolean isFirstMainFrameCommit = firstNavigation && navigation.hasCommitted()
                 && !navigation.isErrorPage() && navigation.isInPrimaryMainFrame()
-                && !navigation.isSameDocument() && !navigation.isFragmentNavigation();
+                && !navigation.isSameDocument();
         if (isFirstMainFrameCommit) mFirstCommitTimestamp = SystemClock.elapsedRealtime();
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/metrics/ActivityTabStartupMetricsTracker.java b/chrome/android/java/src/org/chromium/chrome/browser/metrics/ActivityTabStartupMetricsTracker.java
index e967362..6f1ea7f7 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/metrics/ActivityTabStartupMetricsTracker.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/metrics/ActivityTabStartupMetricsTracker.java
@@ -181,7 +181,6 @@
                         boolean isTrackedPage = navigation.hasCommitted()
                                 && navigation.isInPrimaryMainFrame() && !navigation.isErrorPage()
                                 && !navigation.isSameDocument()
-                                && !navigation.isFragmentNavigation()
                                 && UrlUtilities.isHttpOrHttps(navigation.getUrl());
                         registerFinishNavigation(isTrackedPage);
                     }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/signin/AccountsReloadingTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/signin/AccountsReloadingTest.java
index 5847eda..bf60bcf 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/signin/AccountsReloadingTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/signin/AccountsReloadingTest.java
@@ -4,8 +4,6 @@
 
 package org.chromium.chrome.browser.signin;
 
-import android.os.Build;
-
 import androidx.test.filters.MediumTest;
 
 import org.junit.Assert;
@@ -19,7 +17,6 @@
 import org.chromium.base.Callback;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.CriteriaHelper;
-import org.chromium.base.test.util.DisableIf;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.signin.services.IdentityServicesProvider;
@@ -197,7 +194,6 @@
 
     @Test
     @MediumTest
-    @DisableIf.Build(sdk_is_less_than = Build.VERSION_CODES.O, message = "crbug/1254427")
     public void testRefreshTokenUpdateWhenSignedInAndSyncUserAddsNewAccount() {
         final CoreAccountInfo account1 =
                 mAccountManagerTestRule.addTestAccountThenSigninAndEnableSync();
diff --git a/chrome/app/chrome_main_delegate.cc b/chrome/app/chrome_main_delegate.cc
index b5a7c2f..c2c7039 100644
--- a/chrome/app/chrome_main_delegate.cc
+++ b/chrome/app/chrome_main_delegate.cc
@@ -24,6 +24,7 @@
 #include "base/process/memory.h"
 #include "base/process/process.h"
 #include "base/process/process_handle.h"
+#include "base/scoped_add_feature_flags.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/task/sequence_manager/sequence_manager_impl.h"
@@ -80,6 +81,7 @@
 #include "ppapi/buildflags/buildflags.h"
 #include "printing/buildflags/buildflags.h"
 #include "services/tracing/public/cpp/stack_sampling/tracing_sampler_profiler.h"
+#include "third_party/blink/public/common/features.h"
 #include "ui/base/resource/resource_bundle.h"
 #include "ui/base/ui_base_switches.h"
 
@@ -911,6 +913,16 @@
 
 #endif  // BUILDFLAG(IS_WIN)
 
+  {
+    base::ScopedAddFeatureFlags features(
+        base::CommandLine::ForCurrentProcess());
+
+    // Disable Event.path on Canary and Dev to help the deprecation and removal.
+    // See crbug.com/1277431 for more details.
+    if (chrome::GetChannel() < version_info::Channel::BETA)
+      features.DisableIfNotSet(::blink::features::kEventPath);
+  }
+
   chrome::RegisterPathProvider();
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   ash::RegisterPathProvider();
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 2b3b6e6..9040867 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -5302,55 +5302,54 @@
       <!-- End Extension Settings Overridden Dialog strings. -->
       <!-- Force Installed Deprecated Apps Deletion Dialog strings. -->
       <message name="IDS_FORCE_INSTALLED_DEPRECATED_APPS_CONTENT" desc="Content of the force installed deprecated app dialog">
-        Your administrator installed "<ph name="EXTENSION_NAME">$1<ex>Google Docs</ex></ph>" but this Chrome App is no longer supported. Contact your administrator to remove it.
-      </message>
-      <message name="IDS_FORCE_INSTALLED_PREINSTALLED_DEPRECATED_APPS_TITLE" desc="Title of the force installed and pre-installed deprecated app dialog">
-        <ph name="EXTENSION_NAME">$1<ex>Google Docs</ex></ph> needs to be updated
-      </message>
-      <message name="IDS_FORCE_INSTALLED_PREINSTALLED_DEPRECATED_APPS_CONTENT" desc="Content of the force installed and pre-installed deprecated app dialog">
-        Contact your administrator for the newest version.
-      </message>
-      <message name="IDS_FORCE_INSTALLED_DEPRECATED_APPS_LEARN_MORE_AX_LABEL" desc="Accessibility label text for IDS_DEPRECATED_APPS_LEARN_MORE link">
-        Learn more about unsupported Chrome Apps
+        Old versions of Chrome Apps won't open after December 2022. Contact your administrator to update to a new version or remove this app.
       </message>
       <!-- End Force Installed Deprecated Apps Deletion Dialog strings. -->
       <!-- Deprecated Apps Deletion Dialog strings. -->
-      <message name="IDS_DEPRECATED_APPS_RENDERER_TITLE" desc="The title of the deprecated app dialog">
-        {NUM_APPS, plural,
-        =1 {Unsupported App}
-        other {Unsupported Apps}}
+      <message name="IDS_DEPRECATED_APPS_RENDERER_TITLE_PLURAL" desc="The title of the deprecated app dialog">
+        <ph name="APPS">$1<ex>2</ex></ph> apps are no longer supported
       </message>
-      <message name="IDS_DEPRECATED_APPS_MONITOR_RENDERER" desc="States how many deprecated apps are present, with a link to a help article">
-        {NUM_APPS, plural,
-        =1 {1 of your apps is no longer supported.}
-        other {# of your apps are no longer supported.}}
+      <message name="IDS_DEPRECATED_APPS_RENDERER_TITLE_WITH_APP_NAME" desc="The title of the deprecated app dialog with the app name">
+        "<ph name="EXTENSION_NAME">$1<ex>Google Docs</ex></ph>" is no longer supported
+      </message>
+      <message name="IDS_DEPRECATED_APPS_MONITOR_RENDERER" desc="Dialog content that educates users that Chrome Apps will soon no longer launch.">
+        Old versions of Chrome apps won't open after December 2022. You can check if there's a new version available.
       </message>
       <message name="IDS_DEPRECATED_APPS_LEARN_MORE" desc="Redirects to a link with more information on chrome apps deprecation">
         Learn more
       </message>
+      <message name="IDS_DEPRECATED_APPS_LEARN_MORE_AX_LABEL" desc="Accessibility label text for IDS_DEPRECATED_APPS_LEARN_MORE link">
+        Learn more about unsupported Chrome apps
+      </message>
       <message name="IDS_DEPRECATED_APPS_DELETION_LINK" desc="Contains link to trigger the deprecated apps deletion dialog from chrome://apps">
         {NUM_APPS, plural,
-        =1 {Delete 1 unsupported app}
-        other {Delete # unsupported apps}}
+        =1 {Remove 1 unsupported app}
+        other {Remove # unsupported apps}}
       </message>
       <if expr="use_titlecase">
-        <message name="IDS_DEPRECATED_APPS_OK_LABEL" desc="Label for OK button on deprecated apps dialog">
+        <message name="IDS_DEPRECATED_APPS_OK_LABEL" desc="Label for OK button to delete deprecated apps on deprecated apps dialog">
           {NUM_APPS, plural,
-          =1 {Delete App Now}
-          other {Delete Apps Now}}
+          =1 {Remove App}
+          other {Remove Apps}}
         </message>
         <message name="IDS_DEPRECATED_APPS_CANCEL_LABEL" desc="Label for Cancel button on deprecated apps dialog">
-          Ask Next Time
+          Cancel
+        </message>
+        <message name="IDS_DEPRECATED_APPS_LAUNCH_ANYWAY_LABEL" desc="Label for button cancel button to launch the app anyways in the deprecated apps dialog">
+          Open Anyway
         </message>
       </if>
         <if expr="not use_titlecase">
-        <message name="IDS_DEPRECATED_APPS_OK_LABEL" desc="Label for OK button on deprecated apps dialog">
+        <message name="IDS_DEPRECATED_APPS_OK_LABEL" desc="Label for OK button to delete deprecated apps on deprecated apps dialog">
           {NUM_APPS, plural,
-          =1 {Delete app now}
-          other {Delete apps now}}
+          =1 {Remove app}
+          other {Remove apps}}
         </message>
         <message name="IDS_DEPRECATED_APPS_CANCEL_LABEL" desc="Label for Cancel button on deprecated apps dialog">
-          Ask next time
+          Cancel
+        </message>
+        <message name="IDS_DEPRECATED_APPS_LAUNCH_ANYWAY_LABEL" desc="Label for button cancel button to launch the app anyways in the deprecated apps dialog">
+          Open anyway
         </message>
       </if>
       <!-- End Deprecated Apps Deletion Dialog strings. -->
@@ -13040,6 +13039,12 @@
 
     <!-- Input overlay strings -->
     <if expr="chromeos_ash">
+      <message name="IDS_INPUT_OVERLAY_GAME_CONTROLS_ALPHA" desc="Generic feature name during alpha phase, to be displayed in educational and settigns UI.">
+        Game controls
+      </message>
+      <message name="IDS_INPUT_OVERLAY_ACCESSIBILITY_ALPHA" desc="Text tied to the close button on Settings menu, readable via Chromevox.">
+        Close game controls
+      </message>
       <message name="IDS_INPUT_OVERLAY_MENU_ENTRY_BUTTON" desc="Button to access Input Overlay's settings menu.">
         Game Control
       </message>
diff --git a/chrome/app/generated_resources_grd/IDS_DEPRECATED_APPS_CANCEL_LABEL.png.sha1 b/chrome/app/generated_resources_grd/IDS_DEPRECATED_APPS_CANCEL_LABEL.png.sha1
index e20a484..26ea844 100644
--- a/chrome/app/generated_resources_grd/IDS_DEPRECATED_APPS_CANCEL_LABEL.png.sha1
+++ b/chrome/app/generated_resources_grd/IDS_DEPRECATED_APPS_CANCEL_LABEL.png.sha1
@@ -1 +1 @@
-d9b944ef5ef27e0f1295241c9f406b9c8cb72aee
\ No newline at end of file
+1e123c34e823e2462febf9190afc628a2ce8e57c
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_DEPRECATED_APPS_DELETION_LINK.png.sha1 b/chrome/app/generated_resources_grd/IDS_DEPRECATED_APPS_DELETION_LINK.png.sha1
index b9455dd3..af82f71 100644
--- a/chrome/app/generated_resources_grd/IDS_DEPRECATED_APPS_DELETION_LINK.png.sha1
+++ b/chrome/app/generated_resources_grd/IDS_DEPRECATED_APPS_DELETION_LINK.png.sha1
@@ -1 +1 @@
-6ff2631811c45b9f3b84abc7c67b5c5afcd547e5
\ No newline at end of file
+7a07c53c45948b89a41926c6db34d2789d145eee
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_DEPRECATED_APPS_LAUNCH_ANYWAY_LABEL.png.sha1 b/chrome/app/generated_resources_grd/IDS_DEPRECATED_APPS_LAUNCH_ANYWAY_LABEL.png.sha1
new file mode 100644
index 0000000..ff28de0
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_DEPRECATED_APPS_LAUNCH_ANYWAY_LABEL.png.sha1
@@ -0,0 +1 @@
+cba71d69dd28b563b84d8f711f75b512dd087540
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_DEPRECATED_APPS_LEARN_MORE_AX_LABEL.png.sha1 b/chrome/app/generated_resources_grd/IDS_DEPRECATED_APPS_LEARN_MORE_AX_LABEL.png.sha1
new file mode 100644
index 0000000..18ae197
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_DEPRECATED_APPS_LEARN_MORE_AX_LABEL.png.sha1
@@ -0,0 +1 @@
+b76eea7f0c5ea4eb7e989849d710a4ebd29730f3
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_DEPRECATED_APPS_MONITOR_RENDERER.png.sha1 b/chrome/app/generated_resources_grd/IDS_DEPRECATED_APPS_MONITOR_RENDERER.png.sha1
index 1b2ad4ec..ae4936a95 100644
--- a/chrome/app/generated_resources_grd/IDS_DEPRECATED_APPS_MONITOR_RENDERER.png.sha1
+++ b/chrome/app/generated_resources_grd/IDS_DEPRECATED_APPS_MONITOR_RENDERER.png.sha1
@@ -1 +1 @@
-9e28ad20100395c69d134fa5af69a1b20a557d8d
\ No newline at end of file
+d5ff0b22ce14fcea1fc49a2c7de9b15f730c131a
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_DEPRECATED_APPS_OK_LABEL.png.sha1 b/chrome/app/generated_resources_grd/IDS_DEPRECATED_APPS_OK_LABEL.png.sha1
index e20a484..bd48885 100644
--- a/chrome/app/generated_resources_grd/IDS_DEPRECATED_APPS_OK_LABEL.png.sha1
+++ b/chrome/app/generated_resources_grd/IDS_DEPRECATED_APPS_OK_LABEL.png.sha1
@@ -1 +1 @@
-d9b944ef5ef27e0f1295241c9f406b9c8cb72aee
\ No newline at end of file
+5237e4a51724524f17f62063829e5f73555821f3
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_DEPRECATED_APPS_RENDERER_TITLE.png.sha1 b/chrome/app/generated_resources_grd/IDS_DEPRECATED_APPS_RENDERER_TITLE.png.sha1
deleted file mode 100644
index e20a484..0000000
--- a/chrome/app/generated_resources_grd/IDS_DEPRECATED_APPS_RENDERER_TITLE.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-d9b944ef5ef27e0f1295241c9f406b9c8cb72aee
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_DEPRECATED_APPS_RENDERER_TITLE_PLURAL.png.sha1 b/chrome/app/generated_resources_grd/IDS_DEPRECATED_APPS_RENDERER_TITLE_PLURAL.png.sha1
new file mode 100644
index 0000000..c48a3de
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_DEPRECATED_APPS_RENDERER_TITLE_PLURAL.png.sha1
@@ -0,0 +1 @@
+0489408824bfd6e642d234e9d96445ad9d4a2299
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_DEPRECATED_APPS_RENDERER_TITLE_WITH_APP_NAME.png.sha1 b/chrome/app/generated_resources_grd/IDS_DEPRECATED_APPS_RENDERER_TITLE_WITH_APP_NAME.png.sha1
new file mode 100644
index 0000000..227f05d5
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_DEPRECATED_APPS_RENDERER_TITLE_WITH_APP_NAME.png.sha1
@@ -0,0 +1 @@
+8a964ff06f8f3ce67a0e5e13029a4316e17731a8
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_FORCE_INSTALLED_DEPRECATED_APPS_CONTENT.png.sha1 b/chrome/app/generated_resources_grd/IDS_FORCE_INSTALLED_DEPRECATED_APPS_CONTENT.png.sha1
index 204e0f04..10790c7b 100644
--- a/chrome/app/generated_resources_grd/IDS_FORCE_INSTALLED_DEPRECATED_APPS_CONTENT.png.sha1
+++ b/chrome/app/generated_resources_grd/IDS_FORCE_INSTALLED_DEPRECATED_APPS_CONTENT.png.sha1
@@ -1 +1 @@
-69621246a4875aff9ab7a1d0d9528316415fe560
\ No newline at end of file
+0beafb213eafd8a58fd41a4bbd830a216cc85cbe
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_FORCE_INSTALLED_DEPRECATED_APPS_LEARN_MORE_AX_LABEL.png.sha1 b/chrome/app/generated_resources_grd/IDS_FORCE_INSTALLED_DEPRECATED_APPS_LEARN_MORE_AX_LABEL.png.sha1
deleted file mode 100644
index 8ec600b..0000000
--- a/chrome/app/generated_resources_grd/IDS_FORCE_INSTALLED_DEPRECATED_APPS_LEARN_MORE_AX_LABEL.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-767a804ffadd4122174e17e900980fc7eb77f42b
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_FORCE_INSTALLED_PREINSTALLED_DEPRECATED_APPS_CONTENT.png.sha1 b/chrome/app/generated_resources_grd/IDS_FORCE_INSTALLED_PREINSTALLED_DEPRECATED_APPS_CONTENT.png.sha1
deleted file mode 100644
index 34a2322..0000000
--- a/chrome/app/generated_resources_grd/IDS_FORCE_INSTALLED_PREINSTALLED_DEPRECATED_APPS_CONTENT.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-cc0483851ef3f43bde9de8ffaae445e13f21402d
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_FORCE_INSTALLED_PREINSTALLED_DEPRECATED_APPS_TITLE.png.sha1 b/chrome/app/generated_resources_grd/IDS_FORCE_INSTALLED_PREINSTALLED_DEPRECATED_APPS_TITLE.png.sha1
deleted file mode 100644
index 34a2322..0000000
--- a/chrome/app/generated_resources_grd/IDS_FORCE_INSTALLED_PREINSTALLED_DEPRECATED_APPS_TITLE.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-cc0483851ef3f43bde9de8ffaae445e13f21402d
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_INPUT_OVERLAY_ACCESSIBILITY_ALPHA.png.sha1 b/chrome/app/generated_resources_grd/IDS_INPUT_OVERLAY_ACCESSIBILITY_ALPHA.png.sha1
new file mode 100644
index 0000000..0189dad
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_INPUT_OVERLAY_ACCESSIBILITY_ALPHA.png.sha1
@@ -0,0 +1 @@
+cf5cd1210f6c174c79c88237c31e533f9d0ab17b
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_INPUT_OVERLAY_GAME_CONTROLS_ALPHA.png.sha1 b/chrome/app/generated_resources_grd/IDS_INPUT_OVERLAY_GAME_CONTROLS_ALPHA.png.sha1
new file mode 100644
index 0000000..0189dad
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_INPUT_OVERLAY_GAME_CONTROLS_ALPHA.png.sha1
@@ -0,0 +1 @@
+cf5cd1210f6c174c79c88237c31e533f9d0ab17b
\ No newline at end of file
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 55f74f0..31ab001 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -4686,8 +4686,8 @@
       "apps/digital_goods/digital_goods_impl.h",
       "apps/digital_goods/util.cc",
       "apps/digital_goods/util.h",
-      "browser_process_platform_part_chromeos.cc",
-      "browser_process_platform_part_chromeos.h",
+      "browser_process_platform_part_ash.cc",
+      "browser_process_platform_part_ash.h",
       "component_updater/app_provisioning_component_installer.cc",
       "component_updater/app_provisioning_component_installer.h",
       "component_updater/cros_component_installer_chromeos.cc",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index fcc94b734..b599285 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -1021,17 +1021,17 @@
         {"omnibox_action_with_pedals", "true"},
 };
 const FeatureEntry::FeatureParam
-    kJourneysOmniboxActionOnNonNavigationIntentsParams[] = {
+    kJourneysOmniboxActionOnNavigationIntentsParams[] = {
         {"omnibox_action_on_urls", "false"},
         {"omnibox_action_on_noisy_urls", "false"},
-        {"omnibox_action_on_navigation_intents", "false"},
-        {"omnibox_action_with_pedals", "true"},
+        {"omnibox_action_on_navigation_intents", "true"},
+        {"omnibox_action_with_pedals", "false"},
 };
 const FeatureEntry::FeatureParam kJourneysOmniboxActionWithPedalsParams[] = {
     {"omnibox_action_on_urls", "false"},
     {"omnibox_action_on_noisy_urls", "false"},
-    {"omnibox_action_on_navigation_intents", "true"},
-    {"omnibox_action_with_pedals", "false"},
+    {"omnibox_action_on_navigation_intents", "false"},
+    {"omnibox_action_with_pedals", "true"},
 };
 const FeatureEntry::FeatureVariation kJourneysOmniboxActionVariations[] = {
     {"Action Chips on All URLs", kJourneysOmniboxActionOnAllURLsParams,
@@ -1039,11 +1039,10 @@
     {"Action Chips on Non-Noisy URLs",
      kJourneysOmniboxActionOnNonNoisyURLsParams,
      std::size(kJourneysOmniboxActionOnNonNoisyURLsParams), nullptr},
-    {"Action Chips Disabled on Navigation Intents",
-     kJourneysOmniboxActionOnNonNavigationIntentsParams,
-     std::size(kJourneysOmniboxActionOnNonNavigationIntentsParams), nullptr},
-    {"Action Chips Disabled with Pedals",
-     kJourneysOmniboxActionWithPedalsParams,
+    {"Action Chips Enabled on Navigation Intents",
+     kJourneysOmniboxActionOnNavigationIntentsParams,
+     std::size(kJourneysOmniboxActionOnNavigationIntentsParams), nullptr},
+    {"Action Chips Enabled with Pedals", kJourneysOmniboxActionWithPedalsParams,
      std::size(kJourneysOmniboxActionWithPedalsParams), nullptr},
 };
 const FeatureEntry::FeatureParam kJourneysLabelsWithEntitiesParams[] = {
@@ -6303,6 +6302,10 @@
     {"tangible-sync", flag_descriptions::kTangibleSyncName,
      flag_descriptions::kTangibleSyncDescription, kOsAndroid,
      FEATURE_VALUE_TYPE(switches::kTangibleSync)},
+
+    {"enable-cbd-sign-out", flag_descriptions::kEnableCbdSignOutName,
+     flag_descriptions::kEnableCbdSignOutDescription, kOsAndroid,
+     FEATURE_VALUE_TYPE(switches::kEnableCbdSignOut)},
 #endif  // BUILDFLAG(IS_ANDROID)
 
     {"autofill-use-improved-label-disambiguation",
diff --git a/chrome/browser/android/contextualsearch/contextual_search_ranker_logger_impl.cc b/chrome/browser/android/contextualsearch/contextual_search_ranker_logger_impl.cc
index 01beecb..1a268d3 100644
--- a/chrome/browser/android/contextualsearch/contextual_search_ranker_logger_impl.cc
+++ b/chrome/browser/android/contextualsearch/contextual_search_ranker_logger_impl.cc
@@ -69,33 +69,6 @@
   }
 }
 
-AssistRankerPrediction ContextualSearchRankerLoggerImpl::RunInference(
-    JNIEnv* env,
-    jobject obj) {
-  has_predicted_decision_ = true;
-  bool prediction = false;
-  bool was_able_to_predict = false;
-  if (IsQueryEnabledInternal()) {
-    was_able_to_predict = predictor_->Predict(*ranker_example_, &prediction);
-    // Log to UMA whether we were able to predict or not.
-    base::UmaHistogramBoolean("Search.ContextualSearch.Ranker.WasAbleToPredict",
-                              was_able_to_predict);
-  }
-  AssistRankerPrediction prediction_enum;
-  if (was_able_to_predict) {
-    if (prediction) {
-      prediction_enum = ASSIST_RANKER_PREDICTION_SHOW;
-    } else {
-      prediction_enum = ASSIST_RANKER_PREDICTION_SUPPRESS;
-    }
-  } else {
-    prediction_enum = ASSIST_RANKER_PREDICTION_UNAVAILABLE;
-  }
-  // TODO(donnd): remove when behind-the-flag bug fixed (crbug.com/786589).
-  DVLOG(0) << "prediction: " << prediction_enum;
-  return prediction_enum;
-}
-
 void ContextualSearchRankerLoggerImpl::WriteLogAndReset(JNIEnv* env,
                                                         jobject obj) {
   if (predictor_ && ranker_example_ && source_id_ != ukm::kInvalidSourceId) {
diff --git a/chrome/browser/android/contextualsearch/contextual_search_ranker_logger_impl.h b/chrome/browser/android/contextualsearch/contextual_search_ranker_logger_impl.h
index d5dab2b4..68351f03 100644
--- a/chrome/browser/android/contextualsearch/contextual_search_ranker_logger_impl.h
+++ b/chrome/browser/android/contextualsearch/contextual_search_ranker_logger_impl.h
@@ -20,15 +20,6 @@
 class RankerExample;
 }  // namespace assist_ranker
 
-// A Java counterpart will be generated for this enum.
-// GENERATED_JAVA_ENUM_PACKAGE: org.chromium.chrome.browser.contextualsearch
-enum AssistRankerPrediction {
-  ASSIST_RANKER_PREDICTION_UNDETERMINED,
-  ASSIST_RANKER_PREDICTION_UNAVAILABLE,
-  ASSIST_RANKER_PREDICTION_SUPPRESS,
-  ASSIST_RANKER_PREDICTION_SHOW,
-};
-
 // Provides the native portion of the Java class by the same name.
 // Runs Ranker inference and logging through UKM for Ranker model development.
 // This is used to prediction whether a tap gesture will be useful to the user
@@ -57,10 +48,6 @@
       jobject obj,
       const base::android::JavaParamRef<jobject>& java_web_contents);
 
-  // Runs the model and returns the inference result as an
-  // AssistRankerPrediction enum.
-  AssistRankerPrediction RunInference(JNIEnv* env, jobject obj);
-
   // Writes the currently logged data and resets the current builder to be
   // ready to start logging the next set of data.
   void WriteLogAndReset(JNIEnv* env, jobject obj);
diff --git a/chrome/browser/apps/icon_standardizer.cc b/chrome/browser/apps/icon_standardizer.cc
index 15fde45..7bd2c98 100644
--- a/chrome/browser/apps/icon_standardizer.cc
+++ b/chrome/browser/apps/icon_standardizer.cc
@@ -16,7 +16,7 @@
 
 constexpr int kMinimumVisibleAlpha = 40;
 
-constexpr float kCircleShapePixelDifferenceThreshold = 0.01f;
+constexpr float kCircleShapePixelDifferenceThreshold = 0.02f;
 
 constexpr float kIconScaleToFit = 0.85f;
 
diff --git a/chrome/browser/apps/platform_apps/platform_app_launch.cc b/chrome/browser/apps/platform_apps/platform_app_launch.cc
index c132936..10f4cd2 100644
--- a/chrome/browser/apps/platform_apps/platform_app_launch.cc
+++ b/chrome/browser/apps/platform_apps/platform_app_launch.cc
@@ -138,7 +138,7 @@
     url = GURL(chrome::kChromeUIAppsWithForceInstalledDeprecationDialogURL +
                app_id);
   } else {
-    url = GURL(chrome::kChromeUIAppsWithDeprecationDialogURL);
+    url = GURL(chrome::kChromeUIAppsWithDeprecationDialogURL + app_id);
   }
 
   NavigateParams params(browser, url, ui::PAGE_TRANSITION_AUTO_TOPLEVEL);
diff --git a/chrome/browser/ash/account_manager/account_manager_facade_factory_ash.cc b/chrome/browser/ash/account_manager/account_manager_facade_factory_ash.cc
index 98d1169..76a650d 100644
--- a/chrome/browser/ash/account_manager/account_manager_facade_factory_ash.cc
+++ b/chrome/browser/ash/account_manager/account_manager_facade_factory_ash.cc
@@ -12,7 +12,7 @@
 #include "ash/components/account_manager/account_manager_factory.h"
 #include "base/no_destructor.h"
 #include "chrome/browser/browser_process.h"
-#include "chrome/browser/browser_process_platform_part_chromeos.h"
+#include "chrome/browser/browser_process_platform_part_ash.h"
 #include "components/account_manager_core/account_manager_facade_impl.h"
 #include "components/account_manager_core/chromeos/account_manager.h"
 #include "components/account_manager_core/chromeos/account_manager_mojo_service.h"
diff --git a/chrome/browser/ash/app_mode/kiosk_app_update_service.cc b/chrome/browser/ash/app_mode/kiosk_app_update_service.cc
index 919656b..6b77ad1b 100644
--- a/chrome/browser/ash/app_mode/kiosk_app_update_service.cc
+++ b/chrome/browser/ash/app_mode/kiosk_app_update_service.cc
@@ -9,7 +9,7 @@
 #include "chrome/browser/ash/app_mode/kiosk_app_manager.h"
 #include "chrome/browser/ash/system/automatic_reboot_manager.h"
 #include "chrome/browser/browser_process.h"
-#include "chrome/browser/browser_process_platform_part_chromeos.h"
+#include "chrome/browser/browser_process_platform_part_ash.h"
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/lifetime/application_lifetime.h"
 #include "chrome/browser/profiles/profile.h"
diff --git a/chrome/browser/ash/arc/input_overlay/display_overlay_controller.cc b/chrome/browser/ash/arc/input_overlay/display_overlay_controller.cc
index 44c5609..172bb3b7 100644
--- a/chrome/browser/ash/arc/input_overlay/display_overlay_controller.cc
+++ b/chrome/browser/ash/arc/input_overlay/display_overlay_controller.cc
@@ -170,10 +170,8 @@
   // TODO(djacobo): Set proper positioning based on specs and responding to
   // resize.
   menu_entry->SetPosition(CalculateMenuEntryPosition());
-  // TODO(djacobo): come up with a new resource for this so it can be
-  // translated, or just keep reusing the one I set here.
   menu_entry->SetAccessibleName(
-      l10n_util::GetStringUTF16(IDS_INPUT_OVERLAY_MENU_ENTRY_BUTTON));
+      l10n_util::GetStringUTF16(IDS_INPUT_OVERLAY_GAME_CONTROLS_ALPHA));
 
   auto* parent_view = overlay_widget->GetContentsView();
   DCHECK(parent_view);
diff --git a/chrome/browser/ash/arc/input_overlay/ui/educational_view.cc b/chrome/browser/ash/arc/input_overlay/ui/educational_view.cc
index 75c1292d..a8c7291b 100644
--- a/chrome/browser/ash/arc/input_overlay/ui/educational_view.cc
+++ b/chrome/browser/ash/arc/input_overlay/ui/educational_view.cc
@@ -98,13 +98,13 @@
     AddChildView(std::move(banner));
   }
   {
-    // |Game Control [Alpha]| title tag.
+    // |Game controls [Alpha]| title tag.
     auto container_view = std::make_unique<views::View>();
     container_view->SetLayoutManager(std::make_unique<views::FlexLayout>())
         ->SetOrientation(views::LayoutOrientation::kHorizontal)
         .SetMainAxisAlignment(views::LayoutAlignment::kCenter);
     auto* game_control = ash::login_views_utils::CreateBubbleLabel(
-        l10n_util::GetStringUTF16(IDS_INPUT_OVERLAY_EDUCATIONAL_TITLE),
+        l10n_util::GetStringUTF16(IDS_INPUT_OVERLAY_GAME_CONTROLS_ALPHA),
         /*view_defining_max_width=*/nullptr,
         /*color=*/
         GetContentLayerColor(
diff --git a/chrome/browser/ash/arc/input_overlay/ui/input_menu_view.cc b/chrome/browser/ash/arc/input_overlay/ui/input_menu_view.cc
index 096048f..fc1bab6 100644
--- a/chrome/browser/ash/arc/input_overlay/ui/input_menu_view.cc
+++ b/chrome/browser/ash/arc/input_overlay/ui/input_menu_view.cc
@@ -166,7 +166,7 @@
         .SetCrossAxisAlignment(views::LayoutAlignment::kStretch);
 
     auto* menu_title = ash::login_views_utils::CreateBubbleLabel(
-        l10n_util::GetStringUTF16(IDS_INPUT_OVERLAY_MENU_GAME_CONTROL),
+        l10n_util::GetStringUTF16(IDS_INPUT_OVERLAY_GAME_CONTROLS_ALPHA),
         /*view_defining_max_width=*/nullptr, color,
         /*font_list=*/
         gfx::FontList({kGoogleSansFont}, gfx::Font::FontStyle::NORMAL,
@@ -179,7 +179,7 @@
             base::BindRepeating(&InputMenuView::OnToggleGameControlPressed,
                                 base::Unretained(this))));
     game_control_toggle_->SetAccessibleName(
-        l10n_util::GetStringUTF16(IDS_INPUT_OVERLAY_MENU_GAME_CONTROL));
+        l10n_util::GetStringUTF16(IDS_INPUT_OVERLAY_GAME_CONTROLS_ALPHA));
     game_control_toggle_->SetIsOn(
         display_overlay_controller_->GetTouchInjectorEnable());
 
@@ -196,7 +196,7 @@
     close_button->SetImageHorizontalAlignment(views::ImageButton::ALIGN_CENTER);
     close_button->SetImageVerticalAlignment(views::ImageButton::ALIGN_MIDDLE);
     close_button->SetAccessibleName(
-        l10n_util::GetStringUTF16(IDS_INPUT_OVERLAY_CLOSE_MENU));
+        l10n_util::GetStringUTF16(IDS_INPUT_OVERLAY_ACCESSIBILITY_ALPHA));
     close_button_ = header_view->AddChildView(std::move(close_button));
     menu_title->SetBorder(views::CreateEmptyBorder(CalculateInsets(
         header_view.get(), /*left=*/20, /*right=*/8, /*other_spacing=*/16)));
@@ -299,7 +299,7 @@
     return;
   const bool enabled = game_control_toggle_->GetIsOn();
   display_overlay_controller_->SetTouchInjectorEnable(enabled);
-  // Adjust |enabled_| and |visible_| properties to match |Game Control|.
+  // Adjust |enabled_| and |visible_| properties to match |Game controls|.
   show_hint_toggle_->SetIsOn(enabled);
   display_overlay_controller_->SetInputMappingVisible(enabled);
   show_hint_toggle_->SetEnabled(enabled);
diff --git a/chrome/browser/ash/arc/input_overlay/ui/input_menu_view.h b/chrome/browser/ash/arc/input_overlay/ui/input_menu_view.h
index 6aeba958..e408708 100644
--- a/chrome/browser/ash/arc/input_overlay/ui/input_menu_view.h
+++ b/chrome/browser/ash/arc/input_overlay/ui/input_menu_view.h
@@ -24,7 +24,7 @@
 //
 // The class reports back to DisplayOverlayController, who owns this.
 //   +---------------------------------+
-//   | Game Control       [ o]    [x]  |
+//   | Game controls      [ o]    [x]  |
 //   |                                 |
 //   | Key mapping        [Customize]  |
 //   |                                 |
diff --git a/chrome/browser/ash/attestation/tpm_challenge_key_subtle.cc b/chrome/browser/ash/attestation/tpm_challenge_key_subtle.cc
index 0ef696e..d7c2eea1 100644
--- a/chrome/browser/ash/attestation/tpm_challenge_key_subtle.cc
+++ b/chrome/browser/ash/attestation/tpm_challenge_key_subtle.cc
@@ -23,7 +23,7 @@
 #include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/ash/settings/cros_settings.h"
 #include "chrome/browser/browser_process.h"
-#include "chrome/browser/browser_process_platform_part_chromeos.h"
+#include "chrome/browser/browser_process_platform_part_ash.h"
 #include "chrome/browser/extensions/chrome_extension_function_details.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/common/pref_names.h"
diff --git a/chrome/browser/ash/chrome_browser_main_parts_ash.cc b/chrome/browser/ash/chrome_browser_main_parts_ash.cc
index b987c19..e67c603 100644
--- a/chrome/browser/ash/chrome_browser_main_parts_ash.cc
+++ b/chrome/browser/ash/chrome_browser_main_parts_ash.cc
@@ -166,7 +166,7 @@
 #include "chrome/browser/ash/usb/cros_usb_detector.h"
 #include "chrome/browser/ash/wilco_dtc_supportd/wilco_dtc_supportd_manager.h"
 #include "chrome/browser/browser_process.h"
-#include "chrome/browser/browser_process_platform_part_chromeos.h"
+#include "chrome/browser/browser_process_platform_part_ash.h"
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/chromeos/extensions/default_app_order.h"
 #include "chrome/browser/chromeos/extensions/login_screen/login_screen_ui/ui_handler.h"
diff --git a/chrome/browser/ash/crosapi/browser_manager.cc b/chrome/browser/ash/crosapi/browser_manager.cc
index d8d96cb..619b733 100644
--- a/chrome/browser/ash/crosapi/browser_manager.cc
+++ b/chrome/browser/ash/crosapi/browser_manager.cc
@@ -61,7 +61,7 @@
 #include "chrome/browser/ash/policy/core/user_cloud_policy_manager_ash.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/browser_process.h"
-#include "chrome/browser/browser_process_platform_part_chromeos.h"
+#include "chrome/browser/browser_process_platform_part_ash.h"
 #include "chrome/browser/component_updater/cros_component_manager.h"
 #include "chrome/browser/notifications/system_notification_helper.h"
 #include "chrome/browser/prefs/incognito_mode_prefs.h"
diff --git a/chrome/browser/ash/crosapi/browser_manager_unittest.cc b/chrome/browser/ash/crosapi/browser_manager_unittest.cc
index b03fd9c..95160c4 100644
--- a/chrome/browser/ash/crosapi/browser_manager_unittest.cc
+++ b/chrome/browser/ash/crosapi/browser_manager_unittest.cc
@@ -113,6 +113,7 @@
     fake_user_manager_->UserLoggedIn(account_id, user->username_hash(),
                                      /*browser_restart=*/false,
                                      /*is_child=*/false);
+    fake_user_manager_->SimulateUserProfileLoad(account_id);
     ash::ProfileHelper::Get()->SetUserToProfileMappingForTesting(
         user, &testing_profile_);
   }
diff --git a/chrome/browser/ash/crosapi/device_settings_ash.cc b/chrome/browser/ash/crosapi/device_settings_ash.cc
index 593f2333..0dcbd85 100644
--- a/chrome/browser/ash/crosapi/device_settings_ash.cc
+++ b/chrome/browser/ash/crosapi/device_settings_ash.cc
@@ -13,6 +13,7 @@
 #include "chrome/browser/browser_process_platform_part.h"
 #include "chrome/browser/policy/chrome_policy_conversions_client.h"
 #include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/ui/webui/policy/status_provider/device_cloud_policy_status_provider_chromeos.h"
 #include "mojo/public/cpp/bindings/remote.h"
 
 namespace crosapi {
@@ -44,9 +45,9 @@
 }
 
 void DeviceSettingsAsh::GetDevicePolicy(GetDevicePolicyCallback callback) {
-  if (!g_browser_process->platform_part()
-           ->browser_policy_connector_ash()
-           ->IsDeviceEnterpriseManaged()) {
+  const policy::BrowserPolicyConnectorAsh* connector =
+      g_browser_process->platform_part()->browser_policy_connector_ash();
+  if (!connector->IsDeviceEnterpriseManaged()) {
     std::move(callback).Run(base::Value(), base::Value());
     return;
   }
@@ -54,11 +55,11 @@
   auto client = std::make_unique<policy::ChromePolicyConversionsClient>(
       ProfileManager::GetActiveUserProfile());
   client->EnableUserPolicies(false);
-
-  // TODO(crbug.com/1243869): Get device_status from |client| and pass as the
-  // second parameter.
+  DeviceCloudPolicyStatusProviderChromeOS provider(connector);
+  base::DictionaryValue status;
+  provider.GetStatus(&status);
   std::move(callback).Run(base::Value(client->GetChromePolicies()),
-                          base::Value());
+                          std::move(status));
 }
 
 }  // namespace crosapi
diff --git a/chrome/browser/ash/crosapi/document_scan_ash_unittest.cc b/chrome/browser/ash/crosapi/document_scan_ash_unittest.cc
index 92d7709..81305a7 100644
--- a/chrome/browser/ash/crosapi/document_scan_ash_unittest.cc
+++ b/chrome/browser/ash/crosapi/document_scan_ash_unittest.cc
@@ -54,6 +54,7 @@
     fake_user_manager->UserLoggedIn(account_id, user->username_hash(),
                                     /*browser_restart=*/false,
                                     /*is_child=*/false);
+    fake_user_manager->SimulateUserProfileLoad(account_id);
     return fake_user_manager;
   }
 
diff --git a/chrome/browser/ash/crosapi/networking_attributes_ash_unittest.cc b/chrome/browser/ash/crosapi/networking_attributes_ash_unittest.cc
index 0ea9467..fa80c79 100644
--- a/chrome/browser/ash/crosapi/networking_attributes_ash_unittest.cc
+++ b/chrome/browser/ash/crosapi/networking_attributes_ash_unittest.cc
@@ -107,6 +107,7 @@
         user_manager->AddUserWithAffiliation(account_id, is_affiliated);
     user_manager->UserLoggedIn(account_id, user->username_hash(),
                                /*browser_restart=*/false, /*is_child=*/false);
+    user_manager->SimulateUserProfileLoad(account_id);
     ash::ProfileHelper::Get()->SetUserToProfileMappingForTesting(user,
                                                                  &profile_);
   }
diff --git a/chrome/browser/ash/crostini/termina_installer.cc b/chrome/browser/ash/crostini/termina_installer.cc
index 2ee9c4e..18400ee 100644
--- a/chrome/browser/ash/crostini/termina_installer.cc
+++ b/chrome/browser/ash/crostini/termina_installer.cc
@@ -18,7 +18,7 @@
 #include "base/threading/sequenced_task_runner_handle.h"
 #include "chrome/browser/ash/crostini/crostini_util.h"
 #include "chrome/browser/browser_process.h"
-#include "chrome/browser/browser_process_platform_part_chromeos.h"
+#include "chrome/browser/browser_process_platform_part_ash.h"
 #include "chromeos/dbus/dlcservice/dlcservice.pb.h"
 #include "content/public/browser/network_service_instance.h"
 #include "services/network/public/cpp/network_connection_tracker.h"
diff --git a/chrome/browser/ash/dbus/dlp_files_policy_service_provider_unittest.cc b/chrome/browser/ash/dbus/dlp_files_policy_service_provider_unittest.cc
index 614eafb4..427c2fd 100644
--- a/chrome/browser/ash/dbus/dlp_files_policy_service_provider_unittest.cc
+++ b/chrome/browser/ash/dbus/dlp_files_policy_service_provider_unittest.cc
@@ -56,6 +56,7 @@
     user_manager_->UserLoggedIn(account_id, user->username_hash(),
                                 /*browser_restart=*/false,
                                 /*is_child=*/false);
+    user_manager_->SimulateUserProfileLoad(account_id);
 
     policy::DlpRulesManagerFactory::GetInstance()->SetTestingFactory(
         profile_.get(),
diff --git a/chrome/browser/ash/input_method/input_method_manager_impl.cc b/chrome/browser/ash/input_method/input_method_manager_impl.cc
index bf6623a..d4fde8e 100644
--- a/chrome/browser/ash/input_method/input_method_manager_impl.cc
+++ b/chrome/browser/ash/input_method/input_method_manager_impl.cc
@@ -36,7 +36,7 @@
 #include "chrome/browser/ash/language_preferences.h"
 #include "chrome/browser/ash/login/session/user_session_manager.h"
 #include "chrome/browser/browser_process.h"
-#include "chrome/browser/browser_process_platform_part_chromeos.h"
+#include "chrome/browser/browser_process_platform_part_ash.h"
 #include "chrome/browser/lifetime/termination_notification.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/ui/ash/keyboard/chrome_keyboard_controller_client.h"
diff --git a/chrome/browser/ash/login/oobe_quick_start/connectivity/DIR_METADATA b/chrome/browser/ash/login/oobe_quick_start/connectivity/DIR_METADATA
new file mode 100644
index 0000000..771cf65
--- /dev/null
+++ b/chrome/browser/ash/login/oobe_quick_start/connectivity/DIR_METADATA
@@ -0,0 +1,4 @@
+team_email: "chromeos-cross-device-eng@google.com"
+buganizer {
+  component_id: 1155263
+}
diff --git a/chrome/browser/ash/login/oobe_quick_start/connectivity/OWNERS b/chrome/browser/ash/login/oobe_quick_start/connectivity/OWNERS
new file mode 100644
index 0000000..4eaeef4
--- /dev/null
+++ b/chrome/browser/ash/login/oobe_quick_start/connectivity/OWNERS
@@ -0,0 +1,4 @@
+hansenmichael@google.com
+cclem@google.com
+bhartmire@google.com
+hansberry@chromium.org
diff --git a/chrome/browser/ash/login/oobe_quick_start/connectivity/README.md b/chrome/browser/ash/login/oobe_quick_start/connectivity/README.md
new file mode 100644
index 0000000..b035fcb
--- /dev/null
+++ b/chrome/browser/ash/login/oobe_quick_start/connectivity/README.md
@@ -0,0 +1,6 @@
+This directory contains the functionality needed to connect to a phone over
+Bluetooth and drive the device-to-device communication for Quick Start, and is
+owned by the Cross Device team.
+
+TargetDeviceBootstrapControllerImpl is the main entry point. Calling code is
+expected to instantiate and own a member of this class.
diff --git a/chrome/browser/ash/login/oobe_quick_start/connectivity/target_device_bootstrap_controller.cc b/chrome/browser/ash/login/oobe_quick_start/connectivity/target_device_bootstrap_controller.cc
new file mode 100644
index 0000000..3e90bba
--- /dev/null
+++ b/chrome/browser/ash/login/oobe_quick_start/connectivity/target_device_bootstrap_controller.cc
@@ -0,0 +1,9 @@
+// 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.
+
+namespace ash::quick_start {
+
+// TODO impl
+
+}  // namespace ash::quick_start
diff --git a/chrome/browser/ash/login/oobe_quick_start/connectivity/target_device_bootstrap_controller.h b/chrome/browser/ash/login/oobe_quick_start/connectivity/target_device_bootstrap_controller.h
new file mode 100644
index 0000000..0329807
--- /dev/null
+++ b/chrome/browser/ash/login/oobe_quick_start/connectivity/target_device_bootstrap_controller.h
@@ -0,0 +1,22 @@
+// 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 CHROME_BROWSER_ASH_LOGIN_OOBE_QUICK_START_CONNECTIVITY_TARGET_DEVICE_BOOTSTRAP_CONTROLLER_H_
+#define CHROME_BROWSER_ASH_LOGIN_OOBE_QUICK_START_CONNECTIVITY_TARGET_DEVICE_BOOTSTRAP_CONTROLLER_H_
+
+namespace ash::quick_start {
+
+class TargetDeviceBootstrapController {
+ public:
+  TargetDeviceBootstrapController() = default;
+  virtual ~TargetDeviceBootstrapController() = default;
+
+  // Checks to see whether the feature can be supported on the device's
+  // hardware. Returns true if Bluetooth is supported and an adapter is present.
+  virtual bool IsFeatureSupported() = 0;
+}
+
+}  // namespace ash::quick_start
+
+#endif  // CHROME_BROWSER_ASH_LOGIN_OOBE_QUICK_START_CONNECTIVITY_TARGET_DEVICE_BOOTSTRAP_CONTROLLER_H_
diff --git a/chrome/browser/ash/login/saml/in_session_password_change_manager.cc b/chrome/browser/ash/login/saml/in_session_password_change_manager.cc
index fae5440..ff1bf73 100644
--- a/chrome/browser/ash/login/saml/in_session_password_change_manager.cc
+++ b/chrome/browser/ash/login/saml/in_session_password_change_manager.cc
@@ -16,7 +16,7 @@
 #include "chrome/browser/ash/login/saml/password_expiry_notification.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/browser_process.h"
-#include "chrome/browser/browser_process_platform_part_chromeos.h"
+#include "chrome/browser/browser_process_platform_part_ash.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/webui/chromeos/in_session_password_change/password_change_dialogs.h"
 #include "chrome/common/chrome_features.h"
diff --git a/chrome/browser/ash/login/screens/packaged_license_screen.cc b/chrome/browser/ash/login/screens/packaged_license_screen.cc
index 3920f1f..5bf4880b8 100644
--- a/chrome/browser/ash/login/screens/packaged_license_screen.cc
+++ b/chrome/browser/ash/login/screens/packaged_license_screen.cc
@@ -8,7 +8,7 @@
 #include "chrome/browser/ash/policy/core/browser_policy_connector_ash.h"
 #include "chrome/browser/ash/policy/enrollment/enrollment_config.h"
 #include "chrome/browser/browser_process.h"
-#include "chrome/browser/browser_process_platform_part_chromeos.h"
+#include "chrome/browser/browser_process_platform_part_ash.h"
 #include "chrome/browser/ui/webui/chromeos/login/packaged_license_screen_handler.h"
 
 namespace ash {
diff --git a/chrome/browser/ash/login/screens/reset_screen.cc b/chrome/browser/ash/login/screens/reset_screen.cc
index 37615cd9..8ca66056 100644
--- a/chrome/browser/ash/login/screens/reset_screen.cc
+++ b/chrome/browser/ash/login/screens/reset_screen.cc
@@ -25,7 +25,7 @@
 #include "chrome/browser/ash/tpm_firmware_update.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/browser_process_platform_part.h"
-#include "chrome/browser/browser_process_platform_part_chromeos.h"
+#include "chrome/browser/browser_process_platform_part_ash.h"
 #include "chrome/browser/ui/webui/chromeos/login/reset_screen_handler.h"
 #include "chrome/common/pref_names.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
diff --git a/chrome/browser/ash/login/session/chrome_session_manager.cc b/chrome/browser/ash/login/session/chrome_session_manager.cc
index e3beaa7..3d13b170 100644
--- a/chrome/browser/ash/login/session/chrome_session_manager.cc
+++ b/chrome/browser/ash/login/session/chrome_session_manager.cc
@@ -41,7 +41,7 @@
 #include "chrome/browser/ash/tpm_firmware_update_notification.h"
 #include "chrome/browser/ash/u2f_notification.h"
 #include "chrome/browser/browser_process.h"
-#include "chrome/browser/browser_process_platform_part_chromeos.h"
+#include "chrome/browser/browser_process_platform_part_ash.h"
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/policy/profile_policy_connector.h"
 #include "chrome/browser/profiles/profile.h"
diff --git a/chrome/browser/ash/login/session/user_session_initializer.cc b/chrome/browser/ash/login/session/user_session_initializer.cc
index 07aad93d..0186ffee 100644
--- a/chrome/browser/ash/login/session/user_session_initializer.cc
+++ b/chrome/browser/ash/login/session/user_session_initializer.cc
@@ -33,7 +33,7 @@
 #include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/ash/settings/cros_settings.h"
 #include "chrome/browser/browser_process.h"
-#include "chrome/browser/browser_process_platform_part_chromeos.h"
+#include "chrome/browser/browser_process_platform_part_ash.h"
 #include "chrome/browser/component_updater/crl_set_component_installer.h"
 #include "chrome/browser/google/google_brand_chromeos.h"
 #include "chrome/browser/net/nss_service.h"
diff --git a/chrome/browser/ash/login/session/user_session_manager.cc b/chrome/browser/ash/login/session/user_session_manager.cc
index 2adb025..a31d2c19 100644
--- a/chrome/browser/ash/login/session/user_session_manager.cc
+++ b/chrome/browser/ash/login/session/user_session_manager.cc
@@ -104,7 +104,7 @@
 #include "chrome/browser/ash/u2f_notification.h"
 #include "chrome/browser/ash/web_applications/help_app/help_app_notification_controller.h"
 #include "chrome/browser/browser_process.h"
-#include "chrome/browser/browser_process_platform_part_chromeos.h"
+#include "chrome/browser/browser_process_platform_part_ash.h"
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/first_run/first_run.h"
 #include "chrome/browser/lifetime/application_lifetime.h"
diff --git a/chrome/browser/ash/platform_keys/platform_keys_service_nss.cc b/chrome/browser/ash/platform_keys/platform_keys_service_nss.cc
index 1f400b3..1707037c 100644
--- a/chrome/browser/ash/platform_keys/platform_keys_service_nss.cc
+++ b/chrome/browser/ash/platform_keys/platform_keys_service_nss.cc
@@ -31,7 +31,7 @@
 #include "chrome/browser/ash/net/client_cert_store_ash.h"
 #include "chrome/browser/ash/platform_keys/chaps_util.h"
 #include "chrome/browser/browser_process.h"
-#include "chrome/browser/browser_process_platform_part_chromeos.h"
+#include "chrome/browser/browser_process_platform_part_ash.h"
 #include "chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api.h"
 #include "chrome/browser/platform_keys/platform_keys.h"
 #include "components/policy/core/common/cloud/cloud_policy_constants.h"
diff --git a/chrome/browser/ash/policy/core/browser_policy_connector_ash_browsertest.cc b/chrome/browser/ash/policy/core/browser_policy_connector_ash_browsertest.cc
index f99b0f02..40b22ad 100644
--- a/chrome/browser/ash/policy/core/browser_policy_connector_ash_browsertest.cc
+++ b/chrome/browser/ash/policy/core/browser_policy_connector_ash_browsertest.cc
@@ -8,7 +8,7 @@
 #include "chrome/browser/ash/policy/core/device_cloud_policy_store_ash.h"
 #include "chrome/browser/ash/policy/core/device_policy_cros_browser_test.h"
 #include "chrome/browser/browser_process.h"
-#include "chrome/browser/browser_process_platform_part_chromeos.h"
+#include "chrome/browser/browser_process_platform_part_ash.h"
 #include "components/policy/core/common/cloud/mock_cloud_policy_store.h"
 #include "components/policy/core/common/cloud/test/policy_builder.h"
 #include "components/policy/proto/device_management_backend.pb.h"
diff --git a/chrome/browser/ash/policy/core/device_attributes_browsertest.cc b/chrome/browser/ash/policy/core/device_attributes_browsertest.cc
index b1151cc2..5c2caf0 100644
--- a/chrome/browser/ash/policy/core/device_attributes_browsertest.cc
+++ b/chrome/browser/ash/policy/core/device_attributes_browsertest.cc
@@ -12,7 +12,7 @@
 #include "chrome/browser/ash/policy/core/device_cloud_policy_store_ash.h"
 #include "chrome/browser/ash/policy/core/device_policy_cros_browser_test.h"
 #include "chrome/browser/browser_process.h"
-#include "chrome/browser/browser_process_platform_part_chromeos.h"
+#include "chrome/browser/browser_process_platform_part_ash.h"
 #include "components/policy/core/common/cloud/cloud_policy_constants.h"
 #include "components/policy/core/common/cloud/mock_cloud_policy_store.h"
 #include "components/policy/core/common/cloud/test/policy_builder.h"
diff --git a/chrome/browser/ash/policy/core/device_attributes_impl.cc b/chrome/browser/ash/policy/core/device_attributes_impl.cc
index 35836b3..bc43afa 100644
--- a/chrome/browser/ash/policy/core/device_attributes_impl.cc
+++ b/chrome/browser/ash/policy/core/device_attributes_impl.cc
@@ -7,7 +7,7 @@
 #include "chrome/browser/ash/policy/core/browser_policy_connector_ash.h"
 #include "chrome/browser/ash/policy/core/device_attributes_impl.h"
 #include "chrome/browser/browser_process.h"
-#include "chrome/browser/browser_process_platform_part_chromeos.h"
+#include "chrome/browser/browser_process_platform_part_ash.h"
 #include "components/policy/core/common/cloud/cloud_policy_constants.h"
 
 namespace policy {
diff --git a/chrome/browser/ash/policy/core/device_policy_cros_browser_test.cc b/chrome/browser/ash/policy/core/device_policy_cros_browser_test.cc
index 208cdf8..2d586a6 100644
--- a/chrome/browser/ash/policy/core/device_policy_cros_browser_test.cc
+++ b/chrome/browser/ash/policy/core/device_policy_cros_browser_test.cc
@@ -21,7 +21,7 @@
 #include "chrome/browser/ash/policy/core/device_policy_builder.h"
 #include "chrome/browser/ash/settings/cros_settings.h"
 #include "chrome/browser/browser_process.h"
-#include "chrome/browser/browser_process_platform_part_chromeos.h"
+#include "chrome/browser/browser_process_platform_part_ash.h"
 #include "chrome/common/chrome_paths.h"
 #include "chromeos/dbus/constants/dbus_paths.h"
 #include "chromeos/dbus/session_manager/fake_session_manager_client.h"
diff --git a/chrome/browser/ash/policy/dlp/dlp_files_controller_unittest.cc b/chrome/browser/ash/policy/dlp/dlp_files_controller_unittest.cc
index 0c6fd1c..5563cfe 100644
--- a/chrome/browser/ash/policy/dlp/dlp_files_controller_unittest.cc
+++ b/chrome/browser/ash/policy/dlp/dlp_files_controller_unittest.cc
@@ -87,6 +87,7 @@
     user_manager_->UserLoggedIn(account_id, user->username_hash(),
                                 /*browser_restart=*/false,
                                 /*is_child=*/false);
+    user_manager_->SimulateUserProfileLoad(account_id);
 
     policy::DlpRulesManagerFactory::GetInstance()->SetTestingFactory(
         profile_.get(),
diff --git a/chrome/browser/ash/policy/external_data/handlers/device_wilco_dtc_configuration_external_data_handler_browsertest.cc b/chrome/browser/ash/policy/external_data/handlers/device_wilco_dtc_configuration_external_data_handler_browsertest.cc
index dae5d8673..2666ebc6 100644
--- a/chrome/browser/ash/policy/external_data/handlers/device_wilco_dtc_configuration_external_data_handler_browsertest.cc
+++ b/chrome/browser/ash/policy/external_data/handlers/device_wilco_dtc_configuration_external_data_handler_browsertest.cc
@@ -16,7 +16,7 @@
 #include "chrome/browser/ash/policy/external_data/cloud_external_data_manager_base_test_util.h"
 #include "chrome/browser/ash/wilco_dtc_supportd/wilco_dtc_supportd_manager.h"
 #include "chrome/browser/browser_process.h"
-#include "chrome/browser/browser_process_platform_part_chromeos.h"
+#include "chrome/browser/browser_process_platform_part_ash.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "components/policy/core/common/external_data_fetcher.h"
diff --git a/chrome/browser/ash/policy/reporting/user_added_removed/user_added_removed_reporter.cc b/chrome/browser/ash/policy/reporting/user_added_removed/user_added_removed_reporter.cc
index c41fb980..4c43e28c0 100644
--- a/chrome/browser/ash/policy/reporting/user_added_removed/user_added_removed_reporter.cc
+++ b/chrome/browser/ash/policy/reporting/user_added_removed/user_added_removed_reporter.cc
@@ -11,7 +11,7 @@
 #include "chrome/browser/ash/policy/core/browser_policy_connector_ash.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/browser_process.h"
-#include "chrome/browser/browser_process_platform_part_chromeos.h"
+#include "chrome/browser/browser_process_platform_part_ash.h"
 #include "chrome/browser/policy/messaging_layer/proto/synced/add_remove_user_event.pb.h"
 #include "components/reporting/proto/synced/record_constants.pb.h"
 #include "components/user_manager/user.h"
diff --git a/chrome/browser/ash/profiles/profile_helper.cc b/chrome/browser/ash/profiles/profile_helper.cc
index 8953453..ff0aae0f 100644
--- a/chrome/browser/ash/profiles/profile_helper.cc
+++ b/chrome/browser/ash/profiles/profile_helper.cc
@@ -118,8 +118,6 @@
   Profile* GetProfileByAccountId(const AccountId& account_id) override;
   Profile* GetProfileByUser(const user_manager::User* user) override;
 
-  Profile* GetProfileByUserUnsafe(const user_manager::User* user) override;
-
   const user_manager::User* GetUserByProfile(
       const Profile* profile) const override;
   user_manager::User* GetUserByProfile(Profile* profile) const override;
@@ -403,34 +401,6 @@
   return profile;
 }
 
-Profile* ProfileHelperImpl::GetProfileByUserUnsafe(
-    const user_manager::User* user) {
-  // This map is non-empty only in tests.
-  if (!user_to_profile_for_testing_.empty()) {
-    std::map<const user_manager::User*, Profile*>::const_iterator it =
-        user_to_profile_for_testing_.find(user);
-    if (it != user_to_profile_for_testing_.end())
-      return it->second;
-  }
-
-  Profile* profile = NULL;
-  if (user->is_profile_created()) {
-    profile = GetProfileByUserIdHash(user->username_hash());
-  } else {
-    LOG(ERROR) << "ProfileHelper::GetProfileByUserUnsafe is called when "
-                  "|user|'s profile is not created. It probably means that "
-                  "something is wrong with a calling code. Please report in "
-                  "http://crbug.com/361528 if you see this message.";
-    profile = ProfileManager::GetActiveUserProfile();
-  }
-
-  // GetActiveUserProfile() or GetProfileByUserIdHash() returns a new instance
-  // of ProfileImpl(), but actually its off-the-record profile should be used.
-  if (profile && user_manager::UserManager::Get()->IsLoggedInAsGuest())
-    profile = profile->GetPrimaryOTRProfile(/*create_if_needed=*/true);
-  return profile;
-}
-
 const user_manager::User* ProfileHelperImpl::GetUserByProfile(
     const Profile* profile) const {
   if (!ProfileHelper::IsRegularProfile(profile)) {
diff --git a/chrome/browser/ash/profiles/profile_helper.h b/chrome/browser/ash/profiles/profile_helper.h
index 115d27c..81c39cc 100644
--- a/chrome/browser/ash/profiles/profile_helper.h
+++ b/chrome/browser/ash/profiles/profile_helper.h
@@ -137,16 +137,6 @@
   // Otherwise, returns NULL.
   virtual Profile* GetProfileByUser(const user_manager::User* user) = 0;
 
-  // DEPRECATED
-  // Returns profile of the |user| if user's profile is created and fully
-  // initialized. Otherwise, if some user is active, returns their profile.
-  // Otherwise, returns signin profile.
-  // Behaviour of this function does not correspond to its name and can be
-  // very surprising, that's why it should not be used anymore.
-  // Use |GetProfileByUser| instead.
-  // TODO(dzhioev): remove this method. http://crbug.com/361528
-  virtual Profile* GetProfileByUserUnsafe(const user_manager::User* user) = 0;
-
   // Returns NULL if User is not created.
   virtual const user_manager::User* GetUserByProfile(
       const Profile* profile) const = 0;
diff --git a/chrome/browser/ash/system/input_device_settings_impl_ozone.cc b/chrome/browser/ash/system/input_device_settings_impl_ozone.cc
index 7ee6135..f7367b46 100644
--- a/chrome/browser/ash/system/input_device_settings_impl_ozone.cc
+++ b/chrome/browser/ash/system/input_device_settings_impl_ozone.cc
@@ -7,7 +7,7 @@
 #include "base/bind.h"
 #include "chrome/browser/ash/system/fake_input_device_settings.h"
 #include "chrome/browser/browser_process.h"
-#include "chrome/browser/browser_process_platform_part_chromeos.h"
+#include "chrome/browser/browser_process_platform_part_ash.h"
 #include "chromeos/system/devicemode.h"
 #include "content/public/browser/browser_thread.h"
 #include "ui/ozone/public/input_controller.h"
diff --git a/chrome/browser/ash/wallpaper_handlers/wallpaper_handlers.cc b/chrome/browser/ash/wallpaper_handlers/wallpaper_handlers.cc
index 67f9502..ea20413 100644
--- a/chrome/browser/ash/wallpaper_handlers/wallpaper_handlers.cc
+++ b/chrome/browser/ash/wallpaper_handlers/wallpaper_handlers.cc
@@ -632,7 +632,16 @@
 
   auto resource_request = std::make_unique<network::ResourceRequest>();
   resource_request->method = "GET";
-  resource_request->url = service_url;
+
+  // By default, the server will not serialize repeated proto fields if they are
+  // empty. This makes it impossible for us to tell if the server has broken
+  // compatibility with the client or just has nothing to return. Append a
+  // special parameter to request that the server always serializes repeated
+  // proto fields and any other fields which might otherwise be omitted by
+  // default (https://cloud.google.com/apis/docs/system-parameters#definitions).
+  resource_request->url = net::AppendOrReplaceQueryParameter(
+      service_url, "$outputDefaults", "true");
+
   // Cookies should not be allowed.
   resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
   resource_request->load_flags = net::LOAD_DISABLE_CACHE;
diff --git a/chrome/browser/browser_process_platform_part.h b/chrome/browser/browser_process_platform_part.h
index 3bed4989..7e8f139 100644
--- a/chrome/browser/browser_process_platform_part.h
+++ b/chrome/browser/browser_process_platform_part.h
@@ -12,7 +12,7 @@
 #if BUILDFLAG(IS_ANDROID)
 #include "chrome/browser/browser_process_platform_part_android.h"
 #elif BUILDFLAG(IS_CHROMEOS_ASH)
-#include "chrome/browser/browser_process_platform_part_chromeos.h"
+#include "chrome/browser/browser_process_platform_part_ash.h"
 #elif BUILDFLAG(IS_MAC)
 #include "chrome/browser/browser_process_platform_part_mac.h"
 #elif BUILDFLAG(IS_WIN)
diff --git a/chrome/browser/browser_process_platform_part_chromeos.cc b/chrome/browser/browser_process_platform_part_ash.cc
similarity index 99%
rename from chrome/browser/browser_process_platform_part_chromeos.cc
rename to chrome/browser/browser_process_platform_part_ash.cc
index a412eb9..40ff594 100644
--- a/chrome/browser/browser_process_platform_part_chromeos.cc
+++ b/chrome/browser/browser_process_platform_part_ash.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/browser_process_platform_part_chromeos.h"
+#include "chrome/browser/browser_process_platform_part_ash.h"
 
 #include <memory>
 #include <utility>
diff --git a/chrome/browser/browser_process_platform_part_chromeos.h b/chrome/browser/browser_process_platform_part_ash.h
similarity index 96%
rename from chrome/browser/browser_process_platform_part_chromeos.h
rename to chrome/browser/browser_process_platform_part_ash.h
index 1159890..fec9577 100644
--- a/chrome/browser/browser_process_platform_part_chromeos.h
+++ b/chrome/browser/browser_process_platform_part_ash.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_BROWSER_PROCESS_PLATFORM_PART_CHROMEOS_H_
-#define CHROME_BROWSER_BROWSER_PROCESS_PLATFORM_PART_CHROMEOS_H_
+#ifndef CHROME_BROWSER_BROWSER_PROCESS_PLATFORM_PART_ASH_H_
+#define CHROME_BROWSER_BROWSER_PROCESS_PLATFORM_PART_ASH_H_
 
 #include <memory>
 
@@ -211,4 +211,4 @@
   SEQUENCE_CHECKER(sequence_checker_);
 };
 
-#endif  // CHROME_BROWSER_BROWSER_PROCESS_PLATFORM_PART_CHROMEOS_H_
+#endif  // CHROME_BROWSER_BROWSER_PROCESS_PLATFORM_PART_ASH_H_
diff --git a/chrome/browser/chrome_browser_main.cc b/chrome/browser/chrome_browser_main.cc
index 68042f64..18b522ab 100644
--- a/chrome/browser/chrome_browser_main.cc
+++ b/chrome/browser/chrome_browser_main.cc
@@ -275,8 +275,8 @@
 
 // TODO(crbug.com/1052397): Revisit the macro expression once build flag switch
 // of lacros-chrome is complete.
-#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || \
-    (BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS))
+#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || \
+    BUILDFLAG(IS_CHROMEOS_LACROS) || BUILDFLAG(IS_FUCHSIA)
 #include "chrome/browser/metrics/desktop_session_duration/desktop_session_duration_tracker.h"
 #include "chrome/browser/metrics/desktop_session_duration/touch_mode_stats_tracker.h"
 #include "chrome/browser/profiles/profile_activity_metrics_recorder.h"
@@ -1058,8 +1058,8 @@
 
 // TODO(crbug.com/1052397): Revisit the macro expression once build flag switch
 // of lacros-chrome is complete.
-#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || \
-    (BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS))
+#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || \
+    BUILDFLAG(IS_CHROMEOS_LACROS) || BUILDFLAG(IS_FUCHSIA)
   metrics::DesktopSessionDurationTracker::Initialize();
   ProfileActivityMetricsRecorder::Initialize();
   TouchModeStatsTracker::Initialize(
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index 1194c79..4506640 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -1366,7 +1366,8 @@
       enterprise::content::kCopyPreventionSettings);
   registry->RegisterIntegerPref(
       prefs::kUserAgentReduction,
-      UserAgentReductionEnterprisePolicyState::kDefault);
+      static_cast<int>(
+          embedder_support::UserAgentReductionEnterprisePolicyState::kDefault));
   registry->RegisterBooleanPref(prefs::kOriginAgentClusterDefaultEnabled, true);
   registry->RegisterIntegerPref(
       prefs::kForceMajorVersionToMinorPositionInUserAgent,
@@ -5897,18 +5898,26 @@
 
 std::string ChromeContentBrowserClient::GetUserAgentBasedOnPolicy(
     content::BrowserContext* context) {
+  const PrefService* prefs = Profile::FromBrowserContext(context)->GetPrefs();
   embedder_support::ForceMajorVersionToMinorPosition
-      force_major_version_to_minor = embedder_support::GetMajorToMinorFromPrefs(
-          Profile::FromBrowserContext(context)->GetPrefs());
-  switch (GetUserAgentReductionEnterprisePolicyState(context)) {
-    case UserAgentReductionEnterprisePolicyState::kForceDisabled:
-      return embedder_support::GetFullUserAgent(force_major_version_to_minor);
-    case UserAgentReductionEnterprisePolicyState::kForceEnabled:
+      force_major_version_to_minor =
+          embedder_support::GetMajorToMinorFromPrefs(prefs);
+  embedder_support::UserAgentReductionEnterprisePolicyState
+      user_agent_reduction =
+          embedder_support::GetUserAgentReductionFromPrefs(prefs);
+  switch (user_agent_reduction) {
+    case embedder_support::UserAgentReductionEnterprisePolicyState::
+        kForceDisabled:
+      return embedder_support::GetFullUserAgent(force_major_version_to_minor,
+                                                user_agent_reduction);
+    case embedder_support::UserAgentReductionEnterprisePolicyState::
+        kForceEnabled:
       return embedder_support::GetReducedUserAgent(
           force_major_version_to_minor);
-    case UserAgentReductionEnterprisePolicyState::kDefault:
+    case embedder_support::UserAgentReductionEnterprisePolicyState::kDefault:
     default:
-      return embedder_support::GetUserAgent(force_major_version_to_minor);
+      return embedder_support::GetUserAgent(force_major_version_to_minor,
+                                            user_agent_reduction);
   }
 }
 
@@ -6476,23 +6485,6 @@
       *Profile::FromBrowserContext(browser_context)->GetPrefs());
 }
 
-ChromeContentBrowserClient::UserAgentReductionEnterprisePolicyState
-ChromeContentBrowserClient::GetUserAgentReductionEnterprisePolicyState(
-    content::BrowserContext* context) {
-  int policy = Profile::FromBrowserContext(context)->GetPrefs()->GetInteger(
-      prefs::kUserAgentReduction);
-  switch (policy) {
-    case 0:
-      return UserAgentReductionEnterprisePolicyState::kDefault;
-    case 1:
-      return UserAgentReductionEnterprisePolicyState::kForceDisabled;
-    case 2:
-      return UserAgentReductionEnterprisePolicyState::kForceEnabled;
-  }
-
-  return UserAgentReductionEnterprisePolicyState::kDefault;
-}
-
 bool ChromeContentBrowserClient::ShouldDisableOriginAgentClusterDefault(
     content::BrowserContext* browser_context) {
   // The enterprise policy for kOriginAgentClusterDefaultEnabled defaults to
diff --git a/chrome/browser/chrome_content_browser_client.h b/chrome/browser/chrome_content_browser_client.h
index 0ca7726..d6ddbb3 100644
--- a/chrome/browser/chrome_content_browser_client.h
+++ b/chrome/browser/chrome_content_browser_client.h
@@ -785,12 +785,6 @@
   bool ShouldPreconnectNavigation(
       content::BrowserContext* browser_context) override;
 
-  enum UserAgentReductionEnterprisePolicyState {
-    kDefault = 0,
-    kForceDisabled = 1,
-    kForceEnabled = 2,
-  };
-
   bool ShouldDisableOriginAgentClusterDefault(
       content::BrowserContext* browser_context) override;
 
@@ -887,9 +881,6 @@
       std::unique_ptr<ScopedKeepAlive> keep_alive_handle);
 #endif
 
-  UserAgentReductionEnterprisePolicyState
-  GetUserAgentReductionEnterprisePolicyState(content::BrowserContext* context);
-
   // Vector of additional ChromeContentBrowserClientParts.
   // Parts are deleted in the reverse order they are added.
   std::vector<ChromeContentBrowserClientParts*> extra_parts_;
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 153789f..344c634 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -1164,6 +1164,8 @@
     "../ash/login/onboarding_user_activity_counter.h",
     "../ash/login/oobe_configuration.cc",
     "../ash/login/oobe_configuration.h",
+    "../ash/login/oobe_quick_start/connectivity/target_device_bootstrap_controller.cc",
+    "../ash/login/oobe_quick_start/connectivity/target_device_bootstrap_controller.h",
     "../ash/login/oobe_quick_start/verification_shapes.cc",
     "../ash/login/oobe_quick_start/verification_shapes.h",
     "../ash/login/oobe_screen.cc",
diff --git a/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustSignalsMediator.java b/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustSignalsMediator.java
index e1173bb..637bbca 100644
--- a/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustSignalsMediator.java
+++ b/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustSignalsMediator.java
@@ -36,8 +36,8 @@
             public void onDidFinishNavigation(Tab tab, NavigationHandle navigation) {
                 if ((tab.isIncognito()) || (!navigation.hasCommitted())
                         || (!navigation.isInPrimaryMainFrame())
-                        || (navigation.isFragmentNavigation()) || (navigation.isErrorPage())
-                        || (navigation.getUrl() == null)
+                        || (navigation.isPrimaryMainFrameFragmentNavigation())
+                        || (navigation.isErrorPage()) || (navigation.getUrl() == null)
                         || (TextUtils.isEmpty(navigation.getUrl().getHost()))) {
                     return;
                 }
diff --git a/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustSignalsMediatorTest.java b/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustSignalsMediatorTest.java
index c73451b..ec38c78f 100644
--- a/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustSignalsMediatorTest.java
+++ b/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustSignalsMediatorTest.java
@@ -79,7 +79,7 @@
         doReturn(false).when(mMockTab).isIncognito();
         doReturn(true).when(mMockNavigationHandle).hasCommitted();
         doReturn(true).when(mMockNavigationHandle).isInPrimaryMainFrame();
-        doReturn(false).when(mMockNavigationHandle).isFragmentNavigation();
+        doReturn(false).when(mMockNavigationHandle).isPrimaryMainFrameFragmentNavigation();
         doReturn(false).when(mMockNavigationHandle).isErrorPage();
         doReturn(mMockUrl).when(mMockNavigationHandle).getUrl();
         doReturn("fake_host").when(mMockUrl).getHost();
@@ -141,8 +141,8 @@
     }
 
     @Test
-    public void testTabObserverOnDidFinishNavigation_FragmentNavigation() {
-        doReturn(true).when(mMockNavigationHandle).isFragmentNavigation();
+    public void testTabObserverOnDidFinishNavigation_PrimaryMainFrameFragmentNavigation() {
+        doReturn(true).when(mMockNavigationHandle).isPrimaryMainFrameFragmentNavigation();
 
         mTabObserverCaptor.getValue().onDidFinishNavigation(mMockTab, mMockNavigationHandle);
         verify(mMockDelegate, never())
diff --git a/chrome/browser/component_updater/cros_component_installer_chromeos_unittest.cc b/chrome/browser/component_updater/cros_component_installer_chromeos_unittest.cc
index 33415e2..2151ed0 100644
--- a/chrome/browser/component_updater/cros_component_installer_chromeos_unittest.cc
+++ b/chrome/browser/component_updater/cros_component_installer_chromeos_unittest.cc
@@ -20,7 +20,7 @@
 #include "base/test/test_simple_task_runner.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
-#include "chrome/browser/browser_process_platform_part_chromeos.h"
+#include "chrome/browser/browser_process_platform_part_ash.h"
 #include "chrome/browser/component_updater/cros_component_installer_chromeos.h"
 #include "chrome/browser/component_updater/metadata_table_chromeos.h"
 #include "chrome/common/chrome_paths.h"
diff --git a/chrome/browser/extensions/api/quick_unlock_private/quick_unlock_private_api_unittest.cc b/chrome/browser/extensions/api/quick_unlock_private/quick_unlock_private_api_unittest.cc
index 43316ad..267607d 100644
--- a/chrome/browser/extensions/api/quick_unlock_private/quick_unlock_private_api_unittest.cc
+++ b/chrome/browser/extensions/api/quick_unlock_private/quick_unlock_private_api_unittest.cc
@@ -225,6 +225,7 @@
     fake_user_manager_->AddUser(test_account);
     fake_user_manager_->UserLoggedIn(test_account, kTestUserEmailHash, false,
                                      false);
+    fake_user_manager_->SimulateUserProfileLoad(test_account);
     ash::ProfileHelper::Get()->SetUserToProfileMappingForTesting(
         fake_user_manager_->GetPrimaryUser(), profile);
 
diff --git a/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router.cc b/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router.cc
index cebdf400..8ee1990 100644
--- a/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router.cc
+++ b/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router.cc
@@ -52,7 +52,7 @@
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "chrome/browser/ash/policy/core/user_cloud_policy_manager_ash.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
-#include "chrome/browser/browser_process_platform_part_chromeos.h"
+#include "chrome/browser/browser_process_platform_part_ash.h"
 #include "components/user_manager/user.h"
 #include "components/user_manager/user_manager.h"
 #else
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index f7941da5..c292ebf5 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -927,14 +927,6 @@
     "owners": [ "yusuyoutube@google.com", "benwgold@google.com", "lens-chrome@google.com" ],
     "expiry_milestone": 103
   },
-  { "name":"context-menu-phase2",
-      "owners":["gambard", "bling-flags@google.com"],
-      "expiry_milestone":100
-  },
-  { "name":"context-menu-phase2-screenshot",
-      "owners":["christianxu", "gambard", "bling-flags@google.com"],
-      "expiry_milestone":103
-  },
   {
     "name": "context-menu-popup-style",
     "owners": [ "wenyufu", "clank-app-team@google.com" ],
@@ -1620,6 +1612,11 @@
     "expiry_milestone": 105
   },
   {
+    "name": "enable-cbd-sign-out",
+    "owners": [ "triploblastic", "chrome-signin-team" ],
+    "expiry_milestone":120
+  },
+  {
     "name": "enable-chrome-management-page-android",
     "owners": [ "ogastorga", "ftirelo" ],
     "expiry_milestone": 107
@@ -5422,27 +5419,27 @@
   {
     "name": "shared-highlighting-refined-blocklist",
     "owners": ["jeffreycohen", "chrome-with-friends-robots@google.com" ],
-    "expiry_milestone": 105
+    "expiry_milestone": 108
   },
   {
     "name": "shared-highlighting-refined-maxcontextwords",
     "owners": ["wissemgamra", "jeffreycohen", "chrome-with-friends-robots@google.com" ],
-    "expiry_milestone": 107
+    "expiry_milestone": 108
   },
   {
     "name": "shared-highlighting-v2",
     "owners": ["jeffreycohen", "kristipark", "cheickcisse@google.com", "chrome-with-friends-robots@google.com"],
-    "expiry_milestone": 99
+    "expiry_milestone": 108
   },
   {
     "name": "sharing-desktop-screenshots",
     "owners": ["skare", "jeffreycohen", "chrome-with-friends-robots@google.com" ],
-    "expiry_milestone": 104
+    "expiry_milestone": 108
   },
   {
     "name": "sharing-desktop-screenshots-edit",
     "owners": ["skare", "jeffreycohen", "chrome-with-friends-robots@google.com" ],
-    "expiry_milestone": 104
+    "expiry_milestone": 108
   },
   {
     "name": "sharing-desktop-share-preview",
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 4144dde..58ddf13 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -3138,6 +3138,12 @@
     "In multi-window mode, launches share hub actions in an adjacent window. "
     "For internal debugging.";
 
+const char kEnableCbdSignOutName[] =
+    "Decouple Sign out from clearing browsing data";
+const char kEnableCbdSignOutDescription[] =
+    "Enable additional affordance to sign out when clearing browsing data and "
+    "ensure consistent behavior for all signed-in users.";
+
 const char kCloseTabSuggestionsName[] = "Suggest to close Tabs";
 const char kCloseTabSuggestionsDescription[] =
     "Suggests to the user to close Tabs that haven't been used beyond a "
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 721e1624..92c1142 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -1769,6 +1769,9 @@
 extern const char kChromeSharingHubLaunchAdjacentName[];
 extern const char kChromeSharingHubLaunchAdjacentDescription[];
 
+extern const char kEnableCbdSignOutName[];
+extern const char kEnableCbdSignOutDescription[];
+
 extern const char kCloseTabSuggestionsName[];
 extern const char kCloseTabSuggestionsDescription[];
 
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc
index 7d76673..03da2db70 100644
--- a/chrome/browser/flags/android/chrome_feature_list.cc
+++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -370,6 +370,7 @@
     &share::kPersistShareHubOnAppSwitch,
     &share::kUpcomingSharingFeatures,
     &switches::kAllowSyncOffForChildAccounts,
+    &switches::kEnableCbdSignOut,
     &switches::kForceStartupSigninPromo,
     &switches::kForceDisableExtendedSyncPromos,
     &switches::kTangibleSync,
diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
index a0bb287..67629accb 100644
--- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
+++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
@@ -267,6 +267,7 @@
     public static final String CHROME_SHARING_HUB_LAUNCH_ADJACENT =
             "ChromeSharingHubLaunchAdjacent";
     public static final String CHROME_SURVEY_NEXT_ANDROID = "ChromeSurveyNextAndroid";
+    public static final String ENABLE_CBD_SIGN_OUT = "EnableCbdSignOut";
     public static final String COMMAND_LINE_ON_NON_ROOTED = "CommandLineOnNonRooted";
     public static final String COMMERCE_MERCHANT_VIEWER = "CommerceMerchantViewer";
     public static final String COMMERCE_PRICE_TRACKING = "CommercePriceTracking";
diff --git a/chrome/browser/metrics/metrics_reporting_state.cc b/chrome/browser/metrics/metrics_reporting_state.cc
index ee1cadad..eb7cf85 100644
--- a/chrome/browser/metrics/metrics_reporting_state.cc
+++ b/chrome/browser/metrics/metrics_reporting_state.cc
@@ -169,6 +169,7 @@
   // Clear the client id and low entropy sources pref when opting out.
   // Note: This will not affect the running state (e.g. field trial
   // randomization), as the pref is only read on startup.
+
   UMA_HISTOGRAM_BOOLEAN("UMA.ClientIdCleared", true);
 
   PrefService* local_state = g_browser_process->local_state();
@@ -180,6 +181,7 @@
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
   local_state->ClearPref(metrics::prefs::kMetricsClientID);
+  local_state->ClearPref(metrics::prefs::kMetricsProvisionalClientID);
   metrics::EntropyState::ClearPrefs(local_state);
   metrics::ClonedInstallDetector::ClearClonedInstallInfo(local_state);
   local_state->ClearPref(metrics::prefs::kMetricsReportingEnabledTimestamp);
diff --git a/chrome/browser/metrics/usertype_by_devicetype_metrics_provider_browsertest.cc b/chrome/browser/metrics/usertype_by_devicetype_metrics_provider_browsertest.cc
index 81d7b07..5cce39d6 100644
--- a/chrome/browser/metrics/usertype_by_devicetype_metrics_provider_browsertest.cc
+++ b/chrome/browser/metrics/usertype_by_devicetype_metrics_provider_browsertest.cc
@@ -24,7 +24,7 @@
 #include "chrome/browser/ash/policy/core/device_local_account.h"
 #include "chrome/browser/ash/policy/core/device_policy_cros_browser_test.h"
 #include "chrome/browser/browser_process.h"
-#include "chrome/browser/browser_process_platform_part_chromeos.h"
+#include "chrome/browser/browser_process_platform_part_ash.h"
 #include "chrome/common/chrome_features.h"
 #include "chromeos/dbus/session_manager/fake_session_manager_client.h"
 #include "components/metrics/metrics_service.h"
diff --git a/chrome/browser/nearby_sharing/sharesheet/nearby_share_action.cc b/chrome/browser/nearby_sharing/sharesheet/nearby_share_action.cc
index 7a3b75f..6abf34a 100644
--- a/chrome/browser/nearby_sharing/sharesheet/nearby_share_action.cc
+++ b/chrome/browser/nearby_sharing/sharesheet/nearby_share_action.cc
@@ -27,6 +27,7 @@
 #include "storage/browser/file_system/file_system_url.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/gfx/geometry/rounded_corners_f.h"
 #include "ui/gfx/geometry/size.h"
 #include "ui/views/controls/webview/webview.h"
 #include "url/gurl.h"
diff --git a/chrome/browser/performance_manager/chrome_browser_main_extra_parts_performance_manager.cc b/chrome/browser/performance_manager/chrome_browser_main_extra_parts_performance_manager.cc
index 72fd6e1f..0a0d7e84d 100644
--- a/chrome/browser/performance_manager/chrome_browser_main_extra_parts_performance_manager.cc
+++ b/chrome/browser/performance_manager/chrome_browser_main_extra_parts_performance_manager.cc
@@ -202,10 +202,16 @@
   extension_watcher_ =
       std::make_unique<performance_manager::ExtensionWatcher>();
 #endif
+}
 
+void ChromeBrowserMainExtraPartsPerformanceManager::PreMainMessageLoopRun() {
 #if !BUILDFLAG(IS_ANDROID)
+  // This object requires the host frame sink manager to exist, which is created
+  // after all the extra parts have run their PostCreateThreads.
   if (base::FeatureList::IsEnabled(
-          performance_manager::features::kHighEfficiencyModeAvailable)) {
+          performance_manager::features::kHighEfficiencyModeAvailable) ||
+      base::FeatureList::IsEnabled(
+          performance_manager::features::kBatterySaverModeAvailable)) {
     high_efficiency_mode_policy_helper_ = std::make_unique<
         performance_manager::policies::HighEfficiencyModePolicyHelper>(
         g_browser_process->local_state());
diff --git a/chrome/browser/performance_manager/chrome_browser_main_extra_parts_performance_manager.h b/chrome/browser/performance_manager/chrome_browser_main_extra_parts_performance_manager.h
index 08e1ef0..189b15e 100644
--- a/chrome/browser/performance_manager/chrome_browser_main_extra_parts_performance_manager.h
+++ b/chrome/browser/performance_manager/chrome_browser_main_extra_parts_performance_manager.h
@@ -69,6 +69,7 @@
 
   // ChromeBrowserMainExtraParts overrides.
   void PostCreateThreads() override;
+  void PreMainMessageLoopRun() override;
   void PostMainMessageLoopRun() override;
 
   // ProfileManagerObserver:
diff --git a/chrome/browser/performance_manager/policies/high_efficiency_mode_policy_helper.cc b/chrome/browser/performance_manager/policies/high_efficiency_mode_policy_helper.cc
index a235102..fee70e4 100644
--- a/chrome/browser/performance_manager/policies/high_efficiency_mode_policy_helper.cc
+++ b/chrome/browser/performance_manager/policies/high_efficiency_mode_policy_helper.cc
@@ -4,24 +4,40 @@
 
 #include "chrome/browser/performance_manager/policies/high_efficiency_mode_policy_helper.h"
 
+#include "base/feature_list.h"
 #include "chrome/browser/performance_manager/policies/high_efficiency_mode_policy.h"
+#include "components/performance_manager/public/features.h"
 #include "components/performance_manager/public/performance_manager.h"
 #include "components/performance_manager/public/user_tuning/prefs.h"
 #include "components/prefs/pref_service.h"
+#include "content/public/browser/frame_rate_throttling.h"
 
 namespace performance_manager::policies {
 
 HighEfficiencyModePolicyHelper::HighEfficiencyModePolicyHelper(
     PrefService* local_state) {
   pref_change_registrar_.Init(local_state);
-  pref_change_registrar_.Add(
-      performance_manager::user_tuning::prefs::kHighEfficiencyModeEnabled,
-      base::BindRepeating(
-          &HighEfficiencyModePolicyHelper::OnHighEfficiencyModeChanged,
-          base::Unretained(this)));
 
-  // Make sure the initial state of the pref is passed on to the policy.
-  OnHighEfficiencyModeChanged();
+  if (base::FeatureList::IsEnabled(
+          performance_manager::features::kHighEfficiencyModeAvailable)) {
+    pref_change_registrar_.Add(
+        performance_manager::user_tuning::prefs::kHighEfficiencyModeEnabled,
+        base::BindRepeating(
+            &HighEfficiencyModePolicyHelper::OnHighEfficiencyModeChanged,
+            base::Unretained(this)));
+    // Make sure the initial state of the pref is passed on to the policy.
+    OnHighEfficiencyModeChanged();
+  }
+
+  if (base::FeatureList::IsEnabled(
+          performance_manager::features::kBatterySaverModeAvailable)) {
+    pref_change_registrar_.Add(
+        performance_manager::user_tuning::prefs::kBatterySaverModeEnabled,
+        base::BindRepeating(
+            &HighEfficiencyModePolicyHelper::OnBatterySaverModeChanged,
+            base::Unretained(this)));
+    OnBatterySaverModeChanged();
+  }
 }
 
 void HighEfficiencyModePolicyHelper::OnHighEfficiencyModeChanged() {
@@ -36,4 +52,15 @@
                      enabled));
 }
 
+void HighEfficiencyModePolicyHelper::OnBatterySaverModeChanged() {
+  bool enabled = pref_change_registrar_.prefs()->GetBoolean(
+      performance_manager::user_tuning::prefs::kBatterySaverModeEnabled);
+
+  if (enabled) {
+    content::StartThrottlingAllFrameSinks(base::Hertz(30));
+  } else {
+    content::StopThrottlingAllFrameSinks();
+  }
+}
+
 }  // namespace performance_manager::policies
diff --git a/chrome/browser/performance_manager/policies/high_efficiency_mode_policy_helper.h b/chrome/browser/performance_manager/policies/high_efficiency_mode_policy_helper.h
index c947587..5c7220d 100644
--- a/chrome/browser/performance_manager/policies/high_efficiency_mode_policy_helper.h
+++ b/chrome/browser/performance_manager/policies/high_efficiency_mode_policy_helper.h
@@ -17,6 +17,7 @@
 
  private:
   void OnHighEfficiencyModeChanged();
+  void OnBatterySaverModeChanged();
 
   PrefChangeRegistrar pref_change_registrar_;
 };
diff --git a/chrome/browser/policy/schema_registry_service_builder.cc b/chrome/browser/policy/schema_registry_service_builder.cc
index 00e49cd..8098c330 100644
--- a/chrome/browser/policy/schema_registry_service_builder.cc
+++ b/chrome/browser/policy/schema_registry_service_builder.cc
@@ -22,7 +22,7 @@
 #include "chrome/browser/ash/policy/core/device_local_account_policy_service.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/browser_process.h"
-#include "chrome/browser/browser_process_platform_part_chromeos.h"
+#include "chrome/browser/browser_process_platform_part_ash.h"
 #include "chrome/browser/profiles/profile.h"
 #include "components/user_manager/user.h"
 #include "components/user_manager/user_manager.h"
diff --git a/chrome/browser/profiles/profile_attributes_entry.cc b/chrome/browser/profiles/profile_attributes_entry.cc
index 0760f6af..860bd55 100644
--- a/chrome/browser/profiles/profile_attributes_entry.cc
+++ b/chrome/browser/profiles/profile_attributes_entry.cc
@@ -655,9 +655,9 @@
 }
 
 void ProfileAttributesEntry::SetIsUsingGAIAPicture(bool value) {
-  SetBool(kUseGAIAPictureKey, value);
-  // TODO(alexilin): send notification only if the value has changed.
-  profile_attributes_storage_->NotifyOnProfileAvatarChanged(profile_path_);
+  if (SetBool(kUseGAIAPictureKey, value)) {
+    profile_attributes_storage_->NotifyOnProfileAvatarChanged(profile_path_);
+  }
 }
 
 void ProfileAttributesEntry::SetLastDownloadedGAIAPictureUrlWithSize(
@@ -666,17 +666,14 @@
 }
 
 void ProfileAttributesEntry::SetSignedInWithCredentialProvider(bool value) {
-  if (value != GetBool(prefs::kSignedInWithCredentialProvider)) {
-    SetBool(prefs::kSignedInWithCredentialProvider, value);
-  }
+  SetBool(prefs::kSignedInWithCredentialProvider, value);
 }
 
 void ProfileAttributesEntry::LockForceSigninProfile(bool is_lock) {
   DCHECK(signin_util::IsForceSigninEnabled());
-  if (GetBool(kForceSigninProfileLockedKey) == is_lock)
-    return;
-  SetBool(kForceSigninProfileLockedKey, is_lock);
-  profile_attributes_storage_->NotifyIsSigninRequiredChanged(GetPath());
+  if (SetBool(kForceSigninProfileLockedKey, is_lock)) {
+    profile_attributes_storage_->NotifyIsSigninRequiredChanged(GetPath());
+  }
 }
 
 void ProfileAttributesEntry::RecordAccountMetrics() const {
@@ -710,24 +707,20 @@
 void ProfileAttributesEntry::SetAvatarIconIndex(size_t icon_index) {
   std::string default_avatar_icon_url =
       profiles::GetDefaultAvatarIconUrl(icon_index);
-  if (default_avatar_icon_url == GetString(kAvatarIconKey)) {
+  if (SetString(kAvatarIconKey, default_avatar_icon_url)) {
     // On Windows, Taskbar and Desktop icons are refreshed every time
     // |OnProfileAvatarChanged| notification is fired.
     // As the current avatar icon is already set to |default_avatar_icon_url|,
     // it is important to avoid firing |OnProfileAvatarChanged| in this case.
     // See http://crbug.com/900374
-    return;
+    base::FilePath profile_path = GetPath();
+    if (!profile_attributes_storage_->GetDisableAvatarDownloadForTesting()) {
+      profile_attributes_storage_->DownloadHighResAvatarIfNeeded(icon_index,
+                                                                 profile_path);
+    }
+
+    profile_attributes_storage_->NotifyOnProfileAvatarChanged(profile_path);
   }
-
-  SetString(kAvatarIconKey, default_avatar_icon_url);
-
-  base::FilePath profile_path = GetPath();
-  if (!profile_attributes_storage_->GetDisableAvatarDownloadForTesting()) {
-    profile_attributes_storage_->DownloadHighResAvatarIfNeeded(icon_index,
-                                                               profile_path);
-  }
-
-  profile_attributes_storage_->NotifyOnProfileAvatarChanged(profile_path);
 }
 
 void ProfileAttributesEntry::SetProfileThemeColors(
@@ -956,24 +949,29 @@
 // Internal setters using keys;
 bool ProfileAttributesEntry::SetString(const char* key,
                                        const std::string& value) {
-  return SetValue(key, base::Value(value));
+  std::string old_value = GetString(key);
+  return SetValue(key, base::Value(value)) && old_value != value;
 }
 
 bool ProfileAttributesEntry::SetString16(const char* key,
                                          const std::u16string& value) {
-  return SetValue(key, base::Value(value));
+  std::u16string old_value = GetString16(key);
+  return SetValue(key, base::Value(value)) && old_value != value;
 }
 
 bool ProfileAttributesEntry::SetDouble(const char* key, double value) {
-  return SetValue(key, base::Value(value));
+  double old_value = GetDouble(key);
+  return SetValue(key, base::Value(value)) && old_value != value;
 }
 
 bool ProfileAttributesEntry::SetBool(const char* key, bool value) {
-  return SetValue(key, base::Value(value));
+  bool old_value = GetBool(key);
+  return SetValue(key, base::Value(value)) && old_value != value;
 }
 
 bool ProfileAttributesEntry::SetInteger(const char* key, int value) {
-  return SetValue(key, base::Value(value));
+  int old_value = GetInteger(key);
+  return SetValue(key, base::Value(value)) && old_value != value;
 }
 
 bool ProfileAttributesEntry::SetValue(const char* key, base::Value value) {
diff --git a/chrome/browser/profiles/profile_attributes_entry.h b/chrome/browser/profiles/profile_attributes_entry.h
index 632bf6bb..33cd025 100644
--- a/chrome/browser/profiles/profile_attributes_entry.h
+++ b/chrome/browser/profiles/profile_attributes_entry.h
@@ -310,6 +310,9 @@
 
   // Internal setters that accept basic data types. Return if the original data
   // is different from the new data, i.e. whether actual update is done.
+  // If the data was missing or was from a different type and `value` is the
+  // default value (e.g. false, 0, empty string...), the value is explicitly
+  // written but these return false.
   bool SetString(const char* key, const std::string& value);
   bool SetString16(const char* key, const std::u16string& value);
   bool SetDouble(const char* key, double value);
diff --git a/chrome/browser/profiles/profile_attributes_storage_unittest.cc b/chrome/browser/profiles/profile_attributes_storage_unittest.cc
index 2670136..c1ec0c8c 100644
--- a/chrome/browser/profiles/profile_attributes_storage_unittest.cc
+++ b/chrome/browser/profiles/profile_attributes_storage_unittest.cc
@@ -831,19 +831,19 @@
 
   EXPECT_TRUE(entry->SetString16(key, u"efgh"));
 
-  // If previous data is not there, setters should returns true even if the
+  // If previous data is not there, setters should returns false even if the
   // defaults (empty string, 0.0, or false) are written.
-  EXPECT_TRUE(entry->SetString("test1", std::string()));
-  EXPECT_TRUE(entry->SetString16("test2", std::u16string()));
-  EXPECT_TRUE(entry->SetDouble("test3", 0.0));
-  EXPECT_TRUE(entry->SetBool("test4", false));
+  EXPECT_FALSE(entry->SetString("test1", std::string()));
+  EXPECT_FALSE(entry->SetString16("test2", std::u16string()));
+  EXPECT_FALSE(entry->SetDouble("test3", 0.0));
+  EXPECT_FALSE(entry->SetBool("test4", false));
 
-  // If previous data is in a wrong type, setters should returns true even if
+  // If previous data is in a wrong type, setters should returns false even if
   // the defaults (empty string, 0.0, or false) are written.
-  EXPECT_TRUE(entry->SetString("test3", std::string()));
-  EXPECT_TRUE(entry->SetString16("test4", std::u16string()));
-  EXPECT_TRUE(entry->SetDouble("test1", 0.0));
-  EXPECT_TRUE(entry->SetBool("test2", false));
+  EXPECT_FALSE(entry->SetString("test3", std::string()));
+  EXPECT_FALSE(entry->SetString16("test4", std::u16string()));
+  EXPECT_FALSE(entry->SetDouble("test1", 0.0));
+  EXPECT_FALSE(entry->SetBool("test2", false));
 }
 
 TEST_F(ProfileAttributesStorageTest, ProfileActiveTime) {
diff --git a/chrome/browser/profiles/profile_manager.cc b/chrome/browser/profiles/profile_manager.cc
index 5f98cf11..fa40d882 100644
--- a/chrome/browser/profiles/profile_manager.cc
+++ b/chrome/browser/profiles/profile_manager.cc
@@ -149,7 +149,7 @@
 #include "chrome/browser/ash/account_manager/child_account_type_changed_user_data.h"
 #include "chrome/browser/ash/arc/policy/arc_policy_util.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
-#include "chrome/browser/browser_process_platform_part_chromeos.h"
+#include "chrome/browser/browser_process_platform_part_ash.h"
 #include "components/user_manager/user.h"
 #include "components/user_manager/user_manager.h"
 #include "components/user_manager/user_type.h"
@@ -712,8 +712,20 @@
     if (!user)  // Can be null in unit tests.
       return nullptr;
 
-    // Note: The ProfileHelper will take care of guest profiles.
-    return ash::ProfileHelper::Get()->GetProfileByUserUnsafe(user);
+    if (user->is_profile_created()) {
+      // Note: The ProfileHelper will take care of guest profiles.
+      return ash::ProfileHelper::Get()->GetProfileByUser(user);
+    }
+
+    LOG(ERROR) << "ProfileManager::GetPrimaryUserProfile is called when "
+                  "|user| is created but |user|'s profile is not yet created. "
+                  "It probably means that something is wrong with a calling "
+                  "code. Please report in http://crbug.com/361528 if you see "
+                  "this message.";
+    Profile* profile = ProfileManager::GetActiveUserProfile();
+    if (profile && manager->IsLoggedInAsGuest())
+      profile = profile->GetPrimaryOTRProfile(/*create_if_needed=*/true);
+    return profile;
   }
 #endif
 
@@ -739,7 +751,7 @@
     // yet created we load the profile using the profile directly.
     // TODO: This should be cleaned up with the new profile manager.
     if (user && user->is_profile_created())
-      return ash::ProfileHelper::Get()->GetProfileByUserUnsafe(user);
+      return ash::ProfileHelper::Get()->GetProfileByUser(user);
   }
 #endif
   Profile* profile = profile_manager->GetActiveUserOrOffTheRecordProfile();
diff --git a/chrome/browser/resources/access_code_cast/BUILD.gn b/chrome/browser/resources/access_code_cast/BUILD.gn
index 4e7ceea8..f4d69ad 100644
--- a/chrome/browser/resources/access_code_cast/BUILD.gn
+++ b/chrome/browser/resources/access_code_cast/BUILD.gn
@@ -3,39 +3,36 @@
 # found in the LICENSE file.
 
 import("//tools/grit/grit_rule.gni")
-import("//tools/polymer/html_to_js.gni")
+import("//tools/grit/preprocess_if_expr.gni")
+import("//tools/polymer/html_to_wrapper.gni")
 import("//tools/typescript/ts_library.gni")
 import("//ui/webui/resources/tools/generate_grd.gni")
+import("./access_code_cast.gni")
 
 assert(!is_android, "access_code_cast is not for android.")
 
 resources_grd_file = "$target_gen_dir/resources.grd"
 
-html_to_js("web_components") {
-  js_files = [
-    "access_code_cast.ts",
-    "error_message/error_message.ts",
-    "passcode_input/passcode_input.ts",
-  ]
+html_to_wrapper("html_wrapper_files") {
+  in_files = html_files
 }
 
-copy("copy_browser_proxy") {
-  sources = [ "browser_proxy.ts" ]
-  outputs = [ "$target_gen_dir/{{source_file_part}}" ]
+preprocess_if_expr("preprocess_src") {
+  in_folder = "."
+  out_folder = target_gen_dir
+  in_files = ts_files
 }
 
 copy("copy_mojo") {
-  deps = [ "//chrome/browser/ui/webui/access_code_cast:mojo_bindings_webui_js" ]
-  mojom_folder =
-      "$root_gen_dir/mojom-webui/chrome/browser/ui/webui/access_code_cast/"
-  sources = [ "$mojom_folder/access_code_cast.mojom-webui.js" ]
-  outputs = [ "$target_gen_dir/{{source_file_part}}" ]
-}
+  deps = [
+    "//chrome/browser/ui/webui/access_code_cast:mojo_bindings_webui_js",
+    "//components/media_router/common/mojom:route_request_result_code_webui_js",
+  ]
 
-copy("copy_mojo_dep") {
-  deps = [ "//components/media_router/common/mojom:route_request_result_code_webui_js" ]
-  mojom_folder = "$root_gen_dir/mojom-webui/components/media_router/common/mojom/"
-  sources = [ "$mojom_folder/route_request_result_code.mojom-webui.js" ]
+  sources = [
+    "$root_gen_dir/mojom-webui/chrome/browser/ui/webui/access_code_cast/access_code_cast.mojom-webui.js",
+    "$root_gen_dir/mojom-webui/components/media_router/common/mojom/route_request_result_code.mojom-webui.js",
+  ]
   outputs = [ "$target_gen_dir/{{source_file_part}}" ]
 }
 
@@ -45,23 +42,18 @@
     "//ui/webui/resources:library",
   ]
   extra_deps = [
-    ":copy_browser_proxy",
     ":copy_mojo",
-    ":copy_mojo_dep",
-    ":web_components",
+    ":html_wrapper_files",
+    ":preprocess_src",
   ]
-  root_dir = "$target_gen_dir"
+  root_dir = target_gen_dir
   out_dir = "$target_gen_dir/tsc"
   composite = true
   tsconfig_base = "tsconfig_base.json"
-  in_files = [
-    "access_code_cast.mojom-webui.js",
-    "access_code_cast.ts",
-    "browser_proxy.ts",
-    "error_message/error_message.ts",
-    "passcode_input/passcode_input.ts",
-    "route_request_result_code.mojom-webui.js",
-  ]
+  in_files = ts_files + html_wrapper_files + [
+               "access_code_cast.mojom-webui.js",
+               "route_request_result_code.mojom-webui.js",
+             ]
 }
 
 generate_grd("build_grd") {
diff --git a/chrome/browser/resources/access_code_cast/access_code_cast.gni b/chrome/browser/resources/access_code_cast/access_code_cast.gni
new file mode 100644
index 0000000..4c7ac89
--- /dev/null
+++ b/chrome/browser/resources/access_code_cast/access_code_cast.gni
@@ -0,0 +1,26 @@
+# 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.
+
+_non_web_component_files = [ "browser_proxy.ts" ]
+
+# Files holding a custom element definition and have an equivalent .html file.
+_web_component_files = [
+  "access_code_cast.ts",
+  "error_message/error_message.ts",
+  "passcode_input/passcode_input.ts",
+]
+
+# Files that are passed as input to html_to_wrapper().
+html_files = []
+foreach(f, _web_component_files) {
+  html_files += [ string_replace(f, ".ts", ".html") ]
+}
+
+# Files that are generated by html_to_wrapper().
+html_wrapper_files = []
+foreach(f, html_files) {
+  html_wrapper_files += [ f + ".ts" ]
+}
+
+ts_files = _web_component_files + _non_web_component_files
diff --git a/chrome/browser/resources/access_code_cast/access_code_cast.ts b/chrome/browser/resources/access_code_cast/access_code_cast.ts
index df906a5..4efd6bf 100644
--- a/chrome/browser/resources/access_code_cast/access_code_cast.ts
+++ b/chrome/browser/resources/access_code_cast/access_code_cast.ts
@@ -4,7 +4,6 @@
 
 import './passcode_input/passcode_input.js';
 import './error_message/error_message.js';
-
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
 import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
 import 'chrome://resources/cr_elements/icons.m.js';
@@ -18,12 +17,13 @@
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 import {PluralStringProxyImpl} from 'chrome://resources/js/plural_string_proxy.js';
 import {WebUIListenerMixin} from 'chrome://resources/js/web_ui_listener_mixin.js';
-import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
+import {getTemplate} from './access_code_cast.html.js';
 import {AddSinkResultCode, CastDiscoveryMethod, PageCallbackRouter} from './access_code_cast.mojom-webui.js';
 import {BrowserProxy, DialogCloseReason} from './browser_proxy.js';
-import {PasscodeInputElement} from './passcode_input/passcode_input.js';
 import {ErrorMessageElement} from './error_message/error_message.js';
+import {PasscodeInputElement} from './passcode_input/passcode_input.js';
 import {RouteRequestResultCode} from './route_request_result_code.mojom-webui.js';
 
 enum PageState {
@@ -58,7 +58,7 @@
   }
 
   static get template() {
-    return html`{__html_template__}`;
+    return getTemplate();
   }
 
   static get properties() {
diff --git a/chrome/browser/resources/access_code_cast/error_message/error_message.ts b/chrome/browser/resources/access_code_cast/error_message/error_message.ts
index 39a4fb16..814399a 100644
--- a/chrome/browser/resources/access_code_cast/error_message/error_message.ts
+++ b/chrome/browser/resources/access_code_cast/error_message/error_message.ts
@@ -5,11 +5,13 @@
 import 'chrome://resources/cr_elements/icons.m.js';
 import 'chrome://resources/cr_elements/shared_vars_css.m.js';
 
-import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {AddSinkResultCode} from '../access_code_cast.mojom-webui.js';
 import {RouteRequestResultCode} from '../route_request_result_code.mojom-webui.js';
 
+import {getTemplate} from './error_message.html.js';
+
 enum ErrorMessage {
   NO_ERROR,
   GENERIC,
@@ -95,7 +97,7 @@
   }
 
   static get template() {
-    return html`{__html_template__}`;
+    return getTemplate();
   }
 
   private messageCode = ErrorMessage.NO_ERROR;
diff --git a/chrome/browser/resources/access_code_cast/passcode_input/passcode_input.ts b/chrome/browser/resources/access_code_cast/passcode_input/passcode_input.ts
index 6fce10c1..485fc3d 100644
--- a/chrome/browser/resources/access_code_cast/passcode_input/passcode_input.ts
+++ b/chrome/browser/resources/access_code_cast/passcode_input/passcode_input.ts
@@ -2,7 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {afterNextRender, html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {afterNextRender, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {getTemplate} from './passcode_input.html.js';
 
 type ForEachCallback = (el: HTMLParagraphElement|HTMLDivElement, index: number) => void;
 
@@ -19,7 +21,7 @@
   }
 
   static get template() {
-    return html`{__html_template__}`;
+    return getTemplate();
   }
 
   static get properties() {
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn b/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn
index de7b6eb..5bbe48b 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn
@@ -37,7 +37,6 @@
   "background/event_source.js",
   "background/focus_bounds.js",
   "background/logging/log_store.js",
-  "background/output/output.js",
   "background/output/output_ancestry_info.js",
   "background/output/output_format_parser.js",
   "background/output/output_format_tree.js",
@@ -115,6 +114,7 @@
   "background/logging/log_url_watcher.js",
   "background/math_handler.js",
   "background/media_automation_handler.js",
+  "background/output/output.js",
   "background/page_load_sound_handler.js",
   "background/panel/i_search.js",
   "background/panel/i_search_handler.js",
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/background.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/background.js
index 23d9851..983e9731 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/background.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/background.js
@@ -18,6 +18,7 @@
 import {LiveRegions} from '/chromevox/background/live_regions.js';
 import {MathHandler} from '/chromevox/background/math_handler.js';
 import {MediaAutomationHandler} from '/chromevox/background/media_automation_handler.js';
+import {Output} from '/chromevox/background/output/output.js';
 import {PageLoadSoundHandler} from '/chromevox/background/page_load_sound_handler.js';
 import {PanelBackground} from '/chromevox/background/panel/panel_background.js';
 import {ChromeVoxPrefs} from '/chromevox/background/prefs.js';
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/background_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/background_test.js
index 6e4ef0fd..4be5703 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/background_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/background_test.js
@@ -44,6 +44,7 @@
     await importModule(
         'GestureCommandHandler',
         '/chromevox/background/gesture_command_handler.js');
+    await importModule('Output', '/chromevox/background/output/output.js');
     await importModule(
         'PageLoadSoundHandler',
         '/chromevox/background/page_load_sound_handler.js');
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/base_automation_handler.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/base_automation_handler.js
index 9cf6e47e..73795fbf 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/base_automation_handler.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/base_automation_handler.js
@@ -7,6 +7,7 @@
  * node.
  */
 import {ChromeVoxState} from '/chromevox/background/chromevox_state.js';
+import {Output} from '/chromevox/background/output/output.js';
 import {ChromeVoxEvent} from '/chromevox/common/custom_automation_event.js';
 
 const ActionType = chrome.automation.ActionType;
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/braille/braille_command_handler.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/braille/braille_command_handler.js
index 8dff763..417257c 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/braille/braille_command_handler.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/braille/braille_command_handler.js
@@ -7,6 +7,7 @@
  */
 import {ChromeVoxState} from '/chromevox/background/chromevox_state.js';
 import {DesktopAutomationInterface} from '/chromevox/background/desktop_automation_interface.js';
+import {Output} from '/chromevox/background/output/output.js';
 import {BrailleCommandData} from '/chromevox/common/braille/braille_command_data.js';
 import {EventGenerator} from '/common/event_generator.js';
 
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/braille/braille_key_event_rewriter.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/braille/braille_key_event_rewriter.js
index 4a9e456a..7baf4ee 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/braille/braille_key_event_rewriter.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/braille/braille_key_event_rewriter.js
@@ -5,6 +5,7 @@
 /**
  * @fileoverview Rewrites a braille key event.
  */
+import {Output} from '/chromevox/background/output/output.js';
 
 /**
  * A class that transforms a sequence of braille key events into a standard key
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/classic_background.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/classic_background.js
index dff6b5ee..157ff8b9 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/classic_background.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/classic_background.js
@@ -11,6 +11,7 @@
 import {ConsoleTts} from '/chromevox/background/console_tts.js';
 import {ChromeVoxEditableTextBase, TypingEcho} from '/chromevox/background/editing/editable_text_base.js';
 import {InjectedScriptLoader} from '/chromevox/background/injected_script_loader.js';
+import {Output} from '/chromevox/background/output/output.js';
 import {ChromeVoxPrefs} from '/chromevox/background/prefs.js';
 import {TtsBackground} from '/chromevox/background/tts_background.js';
 import {AbstractTts} from '/chromevox/common/abstract_tts.js';
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 3caca65..8e7b7de 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/command_handler.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/command_handler.js
@@ -14,6 +14,7 @@
 import {DesktopAutomationInterface} from '/chromevox/background/desktop_automation_interface.js';
 import {TypingEcho} from '/chromevox/background/editing/editable_text_base.js';
 import {GestureInterface} from '/chromevox/background/gesture_interface.js';
+import {Output} from '/chromevox/background/output/output.js';
 import {ChromeVoxPrefs} from '/chromevox/background/prefs.js';
 import {SmartStickyMode} from '/chromevox/background/smart_sticky_mode.js';
 import {AbstractTts} from '/chromevox/common/abstract_tts.js';
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/desktop_automation_handler.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/desktop_automation_handler.js
index 48991631..40498fe 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/desktop_automation_handler.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/desktop_automation_handler.js
@@ -10,6 +10,7 @@
 import {ChromeVoxState} from '/chromevox/background/chromevox_state.js';
 import {DesktopAutomationInterface} from '/chromevox/background/desktop_automation_interface.js';
 import {TextEditHandler} from '/chromevox/background/editing/editing.js';
+import {Output} from '/chromevox/background/output/output.js';
 import {ChromeVoxEvent, CustomAutomationEvent} from '/chromevox/common/custom_automation_event.js';
 
 const ActionType = chrome.automation.ActionType;
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/download_handler.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/download_handler.js
index 543364f..9747dff 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/download_handler.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/download_handler.js
@@ -6,6 +6,7 @@
  * @fileoverview Listens for download events and provides corresponding
  * notifications in ChromeVox.
  */
+import {Output} from '/chromevox/background/output/output.js';
 
 export class DownloadHandler {}
 
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/editable_line.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/editable_line.js
index dbb785f..e2c80f76 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/editable_line.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/editable_line.js
@@ -8,6 +8,7 @@
  * (e.g. start/end offsets) get saved. Line: nodes/offsets at the beginning/end
  * of a line get saved.
  */
+import {Output} from '/chromevox/background/output/output.js';
 
 const AutomationEvent = chrome.automation.AutomationEvent;
 const AutomationNode = chrome.automation.AutomationNode;
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/editing.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/editing.js
index e046691..ce0e2aa 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/editing.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/editing.js
@@ -12,6 +12,7 @@
 import {EditableLine} from '/chromevox/background/editing/editable_line.js';
 import {ChromeVoxEditableTextBase, TextChangeEvent} from '/chromevox/background/editing/editable_text_base.js';
 import {IntentHandler} from '/chromevox/background/editing/intent_handler.js';
+import {Output} from '/chromevox/background/output/output.js';
 import {AbstractTts} from '/chromevox/common/abstract_tts.js';
 import {ChromeVoxEvent} from '/chromevox/common/custom_automation_event.js';
 
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/intent_handler.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/intent_handler.js
index 890ee1729..38db752 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/intent_handler.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/intent_handler.js
@@ -7,6 +7,7 @@
  * Braille is *not* handled in this module.
  */
 import {EditableLine} from '/chromevox/background/editing/editable_line.js';
+import {Output} from '/chromevox/background/output/output.js';
 
 const AutomationIntent = chrome.automation.AutomationIntent;
 const Cursor = cursors.Cursor;
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/intent_handler_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/intent_handler_test.js
index 3a2c4af2..46900d5 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/intent_handler_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/intent_handler_test.js
@@ -19,6 +19,7 @@
     await super.setUpDeferred();
     await importModule(
         'IntentHandler', '/chromevox/background/editing/intent_handler.js');
+    await importModule('Output', '/chromevox/background/output/output.js');
 
     window.Dir = constants.Dir;
     window.IntentTextBoundaryType = chrome.automation.IntentTextBoundaryType;
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/find_handler.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/find_handler.js
index a33abd0..c3c8c13 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/find_handler.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/find_handler.js
@@ -6,6 +6,7 @@
  * @fileoverview Handles output for Chrome's built-in find.
  */
 import {ChromeVoxState} from '/chromevox/background/chromevox_state.js';
+import {Output} from '/chromevox/background/output/output.js';
 
 const TreeChangeObserverFilter = chrome.automation.TreeChangeObserverFilter;
 
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/focus_automation_handler.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/focus_automation_handler.js
index f7c6105..705b9f1 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/focus_automation_handler.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/focus_automation_handler.js
@@ -7,6 +7,7 @@
  */
 import {BaseAutomationHandler} from '/chromevox/background/base_automation_handler.js';
 import {ChromeVoxState} from '/chromevox/background/chromevox_state.js';
+import {Output} from '/chromevox/background/output/output.js';
 import {ChromeVoxEvent} from '/chromevox/common/custom_automation_event.js';
 
 const AutomationEvent = chrome.automation.AutomationEvent;
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/gesture_command_handler.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/gesture_command_handler.js
index bbd9d03..049ab7d 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/gesture_command_handler.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/gesture_command_handler.js
@@ -7,6 +7,7 @@
  */
 import {ChromeVoxState} from '/chromevox/background/chromevox_state.js';
 import {GestureInterface} from '/chromevox/background/gesture_interface.js';
+import {Output} from '/chromevox/background/output/output.js';
 import {PointerHandler} from '/chromevox/background/pointer_handler.js';
 import {UserActionMonitor} from '/chromevox/background/user_action_monitor.js';
 import {GestureCommandData, GestureGranularity} from '/chromevox/common/gesture_command_data.js';
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/keyboard_handler.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/keyboard_handler.js
index 06718706..025418c 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/keyboard_handler.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/keyboard_handler.js
@@ -7,6 +7,7 @@
  */
 import {ChromeVoxState} from '/chromevox/background/chromevox_state.js';
 import {MathHandler} from '/chromevox/background/math_handler.js';
+import {Output} from '/chromevox/background/output/output.js';
 import {ChromeVoxKbHandler} from '/chromevox/common/keyboard_handler.js';
 
 /**
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/live_regions.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/live_regions.js
index 96b409f7b..7498100 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/live_regions.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/live_regions.js
@@ -6,6 +6,7 @@
  * @fileoverview Implements support for live regions in ChromeVox Next.
  */
 import {ChromeVoxState} from '/chromevox/background/chromevox_state.js';
+import {Output} from '/chromevox/background/output/output.js';
 
 const AutomationNode = chrome.automation.AutomationNode;
 const RoleType = chrome.automation.RoleType;
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/live_regions_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/live_regions_test.js
index 85b8ffd..da7d4293 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/live_regions_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/live_regions_test.js
@@ -16,6 +16,7 @@
     await importModule(
         'ChromeVoxState', '/chromevox/background/chromevox_state.js');
     await importModule('LiveRegions', '/chromevox/background/live_regions.js');
+    await importModule('Output', '/chromevox/background/output/output.js');
 
     window.TreeChangeType = chrome.automation.TreeChangeType;
   }
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/loader.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/loader.js
index 01f09de..754d229 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/loader.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/loader.js
@@ -29,8 +29,18 @@
 goog.require('LogType');
 goog.require('Msgs');
 goog.require('NavBraille');
-goog.require('Output');
+goog.require('OutputAction');
+goog.require('OutputAncestryInfo');
+goog.require('OutputContextOrder');
+goog.require('OutputEarconAction');
 goog.require('OutputEventType');
+goog.require('OutputFormatParser');
+goog.require('OutputFormatTree');
+goog.require('OutputNodeSpan');
+goog.require('OutputRoleInfo');
+goog.require('OutputRulesStr');
+goog.require('OutputSelectionSpan');
+goog.require('OutputSpeechProperties');
 goog.require('PanelBridge');
 goog.require('PanelCommand');
 goog.require('PanelCommandType');
@@ -41,8 +51,10 @@
 goog.require('Spannable');
 goog.require('SpeechLog');
 goog.require('StringUtil');
+goog.require('TextLog');
 goog.require('TreeDumper');
 goog.require('TreePathRecoveryStrategy');
+goog.require('TtsCategory');
 goog.require('TtsInterface');
 goog.require('ValueSelectionSpan');
 goog.require('ValueSpan');
@@ -50,5 +62,7 @@
 goog.require('constants');
 goog.require('cursors.Cursor');
 goog.require('cursors.Range');
+goog.require('cursors.Unit');
+goog.require('goog.i18n.MessageFormat');
 
 goog.require('ALL_NODE_MENU_DATA');
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 c021b331..1289c0d 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output.js
@@ -6,41 +6,6 @@
  * @fileoverview Provides output services for ChromeVox.
  */
 
-goog.provide('Output');
-
-goog.require('AbstractEarcons');
-goog.require('AutomationTreeWalker');
-goog.require('ChromeVox');
-goog.require('EventSourceState');
-goog.require('FocusBounds');
-goog.require('LocaleOutputHelper');
-goog.require('LogStore');
-goog.require('NavBraille');
-goog.require('OutputAction');
-goog.require('OutputAncestryInfo');
-goog.require('OutputContextOrder');
-goog.require('OutputEarconAction');
-goog.require('OutputEventType');
-goog.require('OutputFormatParser');
-goog.require('OutputFormatTree');
-goog.require('OutputNodeSpan');
-goog.require('OutputRoleInfo');
-goog.require('OutputRulesStr');
-goog.require('OutputSelectionSpan');
-goog.require('OutputSpeechProperties');
-goog.require('PhoneticData');
-goog.require('Spannable');
-goog.require('TextLog');
-goog.require('TtsCategory');
-goog.require('ValueSelectionSpan');
-goog.require('ValueSpan');
-goog.require('constants');
-goog.require('cursors.Cursor');
-goog.require('cursors.Range');
-goog.require('cursors.Unit');
-goog.require('goog.i18n.MessageFormat');
-
-goog.scope(function() {
 const AriaCurrentState = chrome.automation.AriaCurrentState;
 const AutomationNode = chrome.automation.AutomationNode;
 const DescriptionFromType = chrome.automation.DescriptionFromType;
@@ -78,7 +43,7 @@
  *     For example, $name= would insert the name attribute only if no name
  * attribute had been inserted previously.
  */
-Output = class {
+export class Output {
   constructor() {
     // TODO(dtseng): Include braille specific rules.
     /** @type {!Array<!Spannable>} @private */
@@ -2564,7 +2529,7 @@
       buff[buff.length - 1].setSpan(speechProps, 0, 0);
     }
   }
-};
+}
 
 /**
  * Delimiter to use between output values.
@@ -2932,4 +2897,3 @@
  * @private
  */
 Output.forceModeForNextSpeechUtterance_;
-});  // goog.scope
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output_test.js
index 0c3e945..96d5389 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output_test.js
@@ -103,6 +103,12 @@
     window.Dir = AutomationUtil.Dir;
     this.forceContextualLastOutput();
   }
+
+  /** @override */
+  async setUpDeferred() {
+    await super.setUpDeferred();
+    await importModule('Output', '/chromevox/background/output/output.js');
+  }
 };
 
 
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/panel/i_search.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/panel/i_search.js
index 907100d6..f26fe88 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/panel/i_search.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/panel/i_search.js
@@ -5,6 +5,7 @@
 /**
  * @fileoverview The logic behind incremental search.
  */
+import {Output} from '/chromevox/background/output/output.js';
 import {ISearchHandler} from '/chromevox/background/panel/i_search_handler.js';
 
 const Dir = constants.Dir;
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/pointer_handler.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/pointer_handler.js
index 7a15b57..ec85cf6 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/pointer_handler.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/pointer_handler.js
@@ -9,6 +9,7 @@
 import {BaseAutomationHandler} from '/chromevox/background/base_automation_handler.js';
 import {ChromeVoxState} from '/chromevox/background/chromevox_state.js';
 import {DesktopAutomationInterface} from '/chromevox/background/desktop_automation_interface.js';
+import {Output} from '/chromevox/background/output/output.js';
 import {CustomAutomationEvent} from '/chromevox/common/custom_automation_event.js';
 import {EventGenerator} from '/common/event_generator.js';
 
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/range_automation_handler.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/range_automation_handler.js
index 6b502dd..8e0d95d 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/range_automation_handler.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/range_automation_handler.js
@@ -8,6 +8,7 @@
 import {BaseAutomationHandler} from '/chromevox/background/base_automation_handler.js';
 import {ChromeVoxState, ChromeVoxStateObserver} from '/chromevox/background/chromevox_state.js';
 import {DesktopAutomationHandler} from '/chromevox/background/desktop_automation_handler.js';
+import {Output} from '/chromevox/background/output/output.js';
 import {ChromeVoxEvent, CustomAutomationEvent} from '/chromevox/common/custom_automation_event.js';
 
 const AutomationEvent = chrome.automation.AutomationEvent;
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/user_action_monitor.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/user_action_monitor.js
index 292dbdb3..8c32ef3 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/user_action_monitor.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/user_action_monitor.js
@@ -5,6 +5,7 @@
 /**
  * @fileoverview Monitors user actions.
  */
+import {Output} from '/chromevox/background/output/output.js';
 
 /**
  * The types of actions we want to monitor.
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel_loader.js b/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel_loader.js
index ff4fc54..5fce03c 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel_loader.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel_loader.js
@@ -11,18 +11,42 @@
 goog.require('BackgroundBridge');
 goog.require('BridgeHelper');
 goog.require('EarconDescription');
+goog.require('EventSourceState');
 goog.require('EventSourceType');
+goog.require('FocusBounds');
 goog.require('KeyCode');
 goog.require('KeySequence');
 goog.require('LocaleOutputHelper');
+goog.require('LogStore');
 goog.require('Msgs');
-goog.require('Output');
-goog.require('Output');
+goog.require('NavBraille');
+goog.require('OutputAction');
+goog.require('OutputAncestryInfo');
+goog.require('OutputContextOrder');
+goog.require('OutputEarconAction');
+goog.require('OutputEventType');
+goog.require('OutputFormatParser');
+goog.require('OutputFormatTree');
+goog.require('OutputNodeSpan');
+goog.require('OutputRoleInfo');
+goog.require('OutputRulesStr');
+goog.require('OutputSelectionSpan');
+goog.require('OutputSpeechProperties');
 goog.require('PanelCommand');
+goog.require('PanelCommandType');
 goog.require('PanelNodeMenuData');
+goog.require('PanelNodeMenuItemData');
 goog.require('QueueMode');
+goog.require('Spannable');
+goog.require('TextLog');
+goog.require('TtsCategory');
+goog.require('ValueSelectionSpan');
+goog.require('ValueSpan');
 
 goog.require('constants');
 goog.require('cursors.Cursor');
 goog.require('cursors.Range');
+goog.require('cursors.Unit');
+goog.require('goog.i18n.MessageFormat');
+
 goog.require('ALL_NODE_MENU_DATA');
diff --git a/chrome/browser/resources/support_tool/BUILD.gn b/chrome/browser/resources/support_tool/BUILD.gn
index d5c8f7bb..a73e9920 100644
--- a/chrome/browser/resources/support_tool/BUILD.gn
+++ b/chrome/browser/resources/support_tool/BUILD.gn
@@ -3,7 +3,7 @@
 # found in the LICENSE file.
 
 import("//tools/grit/grit_rule.gni")
-import("//tools/polymer/html_to_js.gni")
+import("//tools/polymer/css_to_wrapper.gni")
 import("//tools/polymer/html_to_wrapper.gni")
 import("//tools/typescript/ts_library.gni")
 import("//ui/webui/resources/tools/generate_grd.gni")
@@ -43,8 +43,8 @@
   outputs = [ "$target_gen_dir/{{source_file_part}}" ]
 }
 
-html_to_js("css_wrapper_files") {
-  js_files = css_wrapper_files
+css_to_wrapper("css_wrapper_files") {
+  in_files = css_files
 }
 
 html_to_wrapper("html_wrapper_files") {
diff --git a/chrome/browser/resources/support_tool/data_collectors.ts b/chrome/browser/resources/support_tool/data_collectors.ts
index 8c4c70b..1b89f1a 100644
--- a/chrome/browser/resources/support_tool/data_collectors.ts
+++ b/chrome/browser/resources/support_tool/data_collectors.ts
@@ -2,11 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import './support_tool_shared_css.js';
+import './support_tool_shared.css.js';
 import 'chrome://resources/polymer/v3_0/iron-list/iron-list.js';
 import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
 
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
 import {BrowserProxy, BrowserProxyImpl, DataCollectorItem} from './browser_proxy.js';
 import {getTemplate} from './data_collectors.html.js';
 
@@ -51,4 +52,4 @@
   }
 }
 
-customElements.define(DataCollectorsElement.is, DataCollectorsElement);
\ No newline at end of file
+customElements.define(DataCollectorsElement.is, DataCollectorsElement);
diff --git a/chrome/browser/resources/support_tool/data_export_done.ts b/chrome/browser/resources/support_tool/data_export_done.ts
index d57f30a..93f0d6e4 100644
--- a/chrome/browser/resources/support_tool/data_export_done.ts
+++ b/chrome/browser/resources/support_tool/data_export_done.ts
@@ -2,13 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import './support_tool_shared_css.js';
+import './support_tool_shared.css.js';
 import 'chrome://resources/cr_elements/icons.m.js';
 import 'chrome://resources/cr_elements/shared_vars_css.m.js';
 import 'chrome://resources/cr_elements/action_link_css.m.js';
 import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
 
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
 import {BrowserProxy, BrowserProxyImpl} from './browser_proxy.js';
 import {getTemplate} from './data_export_done.html.js';
 
@@ -48,4 +49,4 @@
   }
 }
 
-customElements.define(DataExportDoneElement.is, DataExportDoneElement);
\ No newline at end of file
+customElements.define(DataExportDoneElement.is, DataExportDoneElement);
diff --git a/chrome/browser/resources/support_tool/issue_details.ts b/chrome/browser/resources/support_tool/issue_details.ts
index fc4a126..1c42030 100644
--- a/chrome/browser/resources/support_tool/issue_details.ts
+++ b/chrome/browser/resources/support_tool/issue_details.ts
@@ -6,10 +6,11 @@
 import 'chrome://resources/cr_elements/shared_vars_css.m.js';
 import 'chrome://resources/cr_elements/cr_input/cr_input.m.js';
 import 'chrome://resources/cr_elements/md_select_css.m.js';
-import './support_tool_shared_css.js';
+import './support_tool_shared.css.js';
 
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
 import {BrowserProxy, BrowserProxyImpl, IssueDetails} from './browser_proxy.js';
 import {getTemplate} from './issue_details.html.js';
 
@@ -81,4 +82,4 @@
   }
 }
 
-customElements.define(IssueDetailsElement.is, IssueDetailsElement);
\ No newline at end of file
+customElements.define(IssueDetailsElement.is, IssueDetailsElement);
diff --git a/chrome/browser/resources/support_tool/pii_selection.ts b/chrome/browser/resources/support_tool/pii_selection.ts
index b21a52e5..1a76ce3 100644
--- a/chrome/browser/resources/support_tool/pii_selection.ts
+++ b/chrome/browser/resources/support_tool/pii_selection.ts
@@ -8,7 +8,7 @@
 import 'chrome://resources/cr_elements/cr_radio_button/cr_radio_button.m.js';
 import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.m.js';
 import 'chrome://resources/cr_elements/shared_vars_css.m.js';
-import './support_tool_shared_css.js';
+import './support_tool_shared.css.js';
 
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
@@ -111,4 +111,4 @@
   }
 }
 
-customElements.define(PIISelectionElement.is, PIISelectionElement);
\ No newline at end of file
+customElements.define(PIISelectionElement.is, PIISelectionElement);
diff --git a/chrome/browser/resources/support_tool/spinner_page.ts b/chrome/browser/resources/support_tool/spinner_page.ts
index 6fe18bea..6834366 100644
--- a/chrome/browser/resources/support_tool/spinner_page.ts
+++ b/chrome/browser/resources/support_tool/spinner_page.ts
@@ -2,11 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import './support_tool_shared_css.js';
+import './support_tool_shared.css.js';
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
 import 'chrome://resources/polymer/v3_0/paper-spinner/paper-spinner-lite.js';
 
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
 import {BrowserProxy, BrowserProxyImpl} from './browser_proxy.js';
 import {getTemplate} from './spinner_page.html.js';
 
@@ -42,4 +43,4 @@
   }
 }
 
-customElements.define(SpinnerPageElement.is, SpinnerPageElement);
\ No newline at end of file
+customElements.define(SpinnerPageElement.is, SpinnerPageElement);
diff --git a/chrome/browser/resources/support_tool/support_tool.gni b/chrome/browser/resources/support_tool/support_tool.gni
index 7809bfee..1044b44 100644
--- a/chrome/browser/resources/support_tool/support_tool.gni
+++ b/chrome/browser/resources/support_tool/support_tool.gni
@@ -3,19 +3,19 @@
 # found in the LICENSE file.
 
 # Files holding a Polymer element definition and have an equivalent .html file.
-web_component_files = [
-  "support_tool.ts",
-  "issue_details.ts",
+_web_component_files = [
   "data_collectors.ts",
-  "spinner_page.ts",
-  "pii_selection.ts",
   "data_export_done.ts",
+  "issue_details.ts",
+  "pii_selection.ts",
+  "spinner_page.ts",
+  "support_tool.ts",
   "url_generator.ts",
 ]
 
 # Files that are passed as input to html_to_wrapper().
 html_files = []
-foreach(f, web_component_files) {
+foreach(f, _web_component_files) {
   html_files += [ string_replace(f, ".ts", ".html") ]
 }
 
@@ -25,6 +25,13 @@
   html_wrapper_files += [ f + ".ts" ]
 }
 
-ts_files = web_component_files + [ "browser_proxy.ts" ]
+# Files that are passed as input to css_to_wrapper().
+css_files = [ "support_tool_shared.css" ]
 
-css_wrapper_files = [ "support_tool_shared_css.ts" ]
+# Files that are generated by css_to_wrapper().
+css_wrapper_files = []
+foreach(f, css_files) {
+  css_wrapper_files += [ f + ".ts" ]
+}
+
+ts_files = _web_component_files + [ "browser_proxy.ts" ]
diff --git a/chrome/browser/resources/support_tool/support_tool.ts b/chrome/browser/resources/support_tool/support_tool.ts
index 662d816..51f4723 100644
--- a/chrome/browser/resources/support_tool/support_tool.ts
+++ b/chrome/browser/resources/support_tool/support_tool.ts
@@ -12,11 +12,12 @@
 import './spinner_page.js';
 import './pii_selection.js';
 import './data_export_done.js';
-import './support_tool_shared_css.js';
+import './support_tool_shared.css.js';
 
 import {CrToastElement} from 'chrome://resources/cr_elements/cr_toast/cr_toast.js';
 import {WebUIListenerMixin} from 'chrome://resources/js/web_ui_listener_mixin.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
 import {BrowserProxy, BrowserProxyImpl, PIIDataItem, StartDataCollectionResult} from './browser_proxy.js';
 import {DataCollectorsElement} from './data_collectors.js';
 import {DataExportDoneElement} from './data_export_done.js';
@@ -181,4 +182,4 @@
   }
 }
 
-customElements.define(SupportToolElement.is, SupportToolElement);
\ No newline at end of file
+customElements.define(SupportToolElement.is, SupportToolElement);
diff --git a/chrome/browser/resources/support_tool/support_tool_shared.css b/chrome/browser/resources/support_tool/support_tool_shared.css
new file mode 100644
index 0000000..543981dc
--- /dev/null
+++ b/chrome/browser/resources/support_tool/support_tool_shared.css
@@ -0,0 +1,48 @@
+/* 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. */
+
+/* #css_wrapper_metadata_start
+ * #type=style
+ * #import=chrome://resources/cr_elements/shared_vars_css.m.js
+ * #css_wrapper_metadata_end */
+
+/* Common styles for Support Tool components. */
+h2 {
+  color: var(--cr-text-color-primary);
+  font-size: 20px;
+  font-weight: normal;
+  margin-bottom: 18px;
+}
+
+.support-tool-title {
+  color: var(--cr-title-text-color);
+  font-size: 14px;
+  line-height: 20px;
+  margin-bottom: 10px;
+  margin-top: 10px;
+}
+
+.navigation-buttons {
+  float: right;
+  margin-bottom: 30px;
+  margin-top: 30px;
+  position: relative;
+  right: 0;
+}
+
+.support-case-id {
+  height: 32px;
+  width: 248px;
+}
+
+.data-collector-checkbox {
+  padding-bottom: 8px;
+  padding-top: 8px;
+}
+
+.data-collector-list {
+  border-bottom: var(--cr-separator-line);
+  border-top: var(--cr-separator-line);
+  width: 520px;
+}
diff --git a/chrome/browser/resources/support_tool/support_tool_shared_css.html b/chrome/browser/resources/support_tool/support_tool_shared_css.html
deleted file mode 100644
index a3f94cc..0000000
--- a/chrome/browser/resources/support_tool/support_tool_shared_css.html
+++ /dev/null
@@ -1,47 +0,0 @@
-<!-- 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. -->
-
-<!-- Common styles for Support Tool components. -->
-<template>
-  <style>
-    h2 {
-      color: var(--cr-text-color-primary);
-      font-size: 20px;
-      font-weight: normal;
-      margin-bottom: 18px;
-    }
-
-    .support-tool-title {
-      color: var(--cr-title-text-color);
-      font-size: 14px;
-      line-height: 20px;
-      margin-bottom: 10px;
-      margin-top: 10px;
-    }
-
-    .navigation-buttons {
-      float: right;
-      margin-bottom: 30px;
-      margin-top: 30px;
-      position: relative;
-      right: 0;
-    }
-
-    .support-case-id {
-      height: 32px;
-      width: 248px;
-    }
-
-    .data-collector-checkbox {
-      padding-bottom: 8px;
-      padding-top: 8px;
-    }
-
-    .data-collector-list {
-      border-bottom: var(--cr-separator-line);
-      border-top: var(--cr-separator-line);
-      width: 520px;
-    }
-  </style>
-</template>
diff --git a/chrome/browser/resources/support_tool/support_tool_shared_css.ts b/chrome/browser/resources/support_tool/support_tool_shared_css.ts
deleted file mode 100644
index 4b97a110..0000000
--- a/chrome/browser/resources/support_tool/support_tool_shared_css.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-// 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.
-
-import 'chrome://resources/cr_elements/shared_vars_css.m.js';
-
-const styleMod = document.createElement('dom-module');
-styleMod.innerHTML = `{__html_template__}`;
-styleMod.register('support-tool-shared');
diff --git a/chrome/browser/resources/support_tool/url_generator.ts b/chrome/browser/resources/support_tool/url_generator.ts
index 408f46b2..d1806a70 100644
--- a/chrome/browser/resources/support_tool/url_generator.ts
+++ b/chrome/browser/resources/support_tool/url_generator.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 './support_tool_shared_css.js';
+import './support_tool_shared.css.js';
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
 import 'chrome://resources/cr_elements/shared_vars_css.m.js';
 import 'chrome://resources/cr_elements/cr_input/cr_input.m.js';
@@ -116,4 +116,4 @@
   }
 }
 
-customElements.define(UrlGeneratorElement.is, UrlGeneratorElement);
\ No newline at end of file
+customElements.define(UrlGeneratorElement.is, UrlGeneratorElement);
diff --git a/chrome/browser/resources/tab_strip/BUILD.gn b/chrome/browser/resources/tab_strip/BUILD.gn
index a89327b..1d49aaba 100644
--- a/chrome/browser/resources/tab_strip/BUILD.gn
+++ b/chrome/browser/resources/tab_strip/BUILD.gn
@@ -5,10 +5,11 @@
 import("//chrome/common/features.gni")
 import("//tools/grit/grit_rule.gni")
 import("//tools/grit/preprocess_if_expr.gni")
-import("//tools/polymer/html_to_js.gni")
+import("//tools/polymer/html_to_wrapper.gni")
 import("//tools/typescript/ts_library.gni")
 import("//ui/webui/resources/tools/generate_grd.gni")
 import("//ui/webui/webui_features.gni")
+import("./tab_strip.gni")
 
 assert(enable_webui_tab_strip)
 
@@ -36,41 +37,29 @@
   manifest_files = [ "$target_gen_dir/tsconfig.manifest" ]
 }
 
-preprocess_if_expr("preprocess_mojo") {
-  deps = [ "//chrome/browser/ui/webui/tab_strip:mojo_bindings_webui_js" ]
-  in_folder = "$root_gen_dir/mojom-webui/chrome/browser/ui/webui/tab_strip/"
-  out_folder = "$target_gen_dir/$preprocess_folder"
-  in_files = [ "tab_strip.mojom-webui.js" ]
-}
-
-preprocess_if_expr("preprocess_tabs") {
-  deps = [ "//chrome/browser/ui/webui/tabs:mojo_bindings_webui_js" ]
-  in_folder = "$root_gen_dir/mojom-webui/chrome/browser/ui/webui/tabs/"
-  out_folder = "$target_gen_dir/$preprocess_folder"
-  in_files = [ "tabs.mojom-webui.js" ]
-}
-
-preprocess_if_expr("preprocess") {
-  in_folder = "./"
-  out_folder = "$target_gen_dir/$preprocess_folder"
-  in_files = [
-    "drag_manager.ts",
-    "tabs_api_proxy.ts",
-    "tab_swiper.ts",
+copy("copy_mojo") {
+  deps = [
+    "//chrome/browser/ui/webui/tab_strip:mojo_bindings_webui_js",
+    "//chrome/browser/ui/webui/tabs:mojo_bindings_webui_js",
   ]
+  sources = [
+    "$root_gen_dir/mojom-webui/chrome/browser/ui/webui/tab_strip/tab_strip.mojom-webui.js",
+    "$root_gen_dir/mojom-webui/chrome/browser/ui/webui/tabs/tabs.mojom-webui.js",
+  ]
+  outputs = [ "$target_gen_dir/$preprocess_folder/{{source_file_part}}" ]
 }
 
-preprocess_if_expr("preprocess_generated") {
-  deps = [ ":web_components" ]
+preprocess_if_expr("preprocess_src") {
+  in_folder = "."
+  out_folder = "$target_gen_dir/$preprocess_folder"
+  in_files = ts_files
+}
+
+preprocess_if_expr("preprocess_gen") {
+  deps = [ ":html_wrapper_files" ]
   in_folder = target_gen_dir
   out_folder = "$target_gen_dir/$preprocess_folder"
-  in_files = [
-    "alert_indicator.ts",
-    "alert_indicators.ts",
-    "tab_group.ts",
-    "tab_list.ts",
-    "tab.ts",
-  ]
+  in_files = html_wrapper_files
 }
 
 grit("resources") {
@@ -90,14 +79,9 @@
   output_dir = "$root_gen_dir/chrome"
 }
 
-html_to_js("web_components") {
-  js_files = [
-    "alert_indicator.ts",
-    "alert_indicators.ts",
-    "tab_group.ts",
-    "tab_list.ts",
-    "tab.ts",
-  ]
+html_to_wrapper("html_wrapper_files") {
+  in_files = html_files
+  template = "native"
 }
 
 ts_library("build_ts") {
@@ -105,27 +89,18 @@
   out_dir = "$target_gen_dir/tsc"
   composite = true
   tsconfig_base = "tsconfig_base.json"
-  in_files = [
-    "alert_indicator.ts",
-    "alert_indicators.ts",
-    "drag_manager.ts",
-    "tab_group.ts",
-    "tab.ts",
-    "tab_list.ts",
-    "tabs_api_proxy.ts",
-    "tabs.mojom-webui.js",
-    "tab_strip.mojom-webui.js",
-    "tab_swiper.ts",
-  ]
+  in_files = ts_files + html_wrapper_files + [
+               "tabs.mojom-webui.js",
+               "tab_strip.mojom-webui.js",
+             ]
   deps = [
     "//third_party/polymer/v3_0:library",
     "//ui/webui/resources:library",
   ]
   definitions = [ "//tools/typescript/definitions/metrics_private.d.ts" ]
   extra_deps = [
-    ":preprocess",
-    ":preprocess_generated",
-    ":preprocess_mojo",
-    ":preprocess_tabs",
+    ":copy_mojo",
+    ":preprocess_gen",
+    ":preprocess_src",
   ]
 }
diff --git a/chrome/browser/resources/tab_strip/alert_indicator.ts b/chrome/browser/resources/tab_strip/alert_indicator.ts
index aaf536a7..65b88e6b 100644
--- a/chrome/browser/resources/tab_strip/alert_indicator.ts
+++ b/chrome/browser/resources/tab_strip/alert_indicator.ts
@@ -6,8 +6,8 @@
 
 import {CustomElement} from 'chrome://resources/js/custom_element.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
-import {getTrustedHTML} from 'chrome://resources/js/static_types.js';
 
+import {getTemplate} from './alert_indicator.html.js';
 import {TabAlertState} from './tabs.mojom-webui.js';
 
 const MAX_WIDTH: string = '16px';
@@ -70,7 +70,7 @@
 
 export class AlertIndicatorElement extends CustomElement {
   static override get template() {
-    return getTrustedHTML`{__html_template__}`;
+    return getTemplate();
   }
 
   private alertState_: TabAlertState;
diff --git a/chrome/browser/resources/tab_strip/alert_indicators.ts b/chrome/browser/resources/tab_strip/alert_indicators.ts
index 397f8247..a7ea093 100644
--- a/chrome/browser/resources/tab_strip/alert_indicators.ts
+++ b/chrome/browser/resources/tab_strip/alert_indicators.ts
@@ -5,14 +5,14 @@
 import './alert_indicator.js';
 
 import {CustomElement} from 'chrome://resources/js/custom_element.js';
-import {getTrustedHTML} from 'chrome://resources/js/static_types.js';
 
 import {AlertIndicatorElement} from './alert_indicator.js';
+import {getTemplate} from './alert_indicators.html.js';
 import {TabAlertState} from './tabs.mojom-webui.js';
 
 export class AlertIndicatorsElement extends CustomElement {
   static override get template() {
-    return getTrustedHTML`{__html_template__}`;
+    return getTemplate();
   }
 
   private containerEl_: HTMLElement;
diff --git a/chrome/browser/resources/tab_strip/tab.ts b/chrome/browser/resources/tab_strip/tab.ts
index a529c6c..dfb1a2a 100644
--- a/chrome/browser/resources/tab_strip/tab.ts
+++ b/chrome/browser/resources/tab_strip/tab.ts
@@ -9,10 +9,10 @@
 import {CustomElement} from 'chrome://resources/js/custom_element.js';
 import {getFavicon} from 'chrome://resources/js/icon.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
-import {getTrustedHTML} from 'chrome://resources/js/static_types.js';
 import {isRTL} from 'chrome://resources/js/util.m.js';
 
 import {AlertIndicatorsElement} from './alert_indicators.js';
+import {getTemplate} from './tab.html.js';
 import {Tab, TabNetworkState} from './tab_strip.mojom-webui.js';
 import {TabSwiper} from './tab_swiper.js';
 import {CloseTabAction, TabsApiProxy, TabsApiProxyImpl} from './tabs_api_proxy.js';
@@ -40,7 +40,7 @@
 
 export class TabElement extends CustomElement {
   static override get template() {
-    return getTrustedHTML`{__html_template__}`;
+    return getTemplate();
   }
 
   private alertIndicatorsEl_: AlertIndicatorsElement;
diff --git a/chrome/browser/resources/tab_strip/tab_group.ts b/chrome/browser/resources/tab_strip/tab_group.ts
index 219754da..b79a731 100644
--- a/chrome/browser/resources/tab_strip/tab_group.ts
+++ b/chrome/browser/resources/tab_strip/tab_group.ts
@@ -4,14 +4,14 @@
 
 import {CustomElement} from 'chrome://resources/js/custom_element.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
-import {getTrustedHTML} from 'chrome://resources/js/static_types.js';
 
+import {getTemplate} from './tab_group.html.js';
 import {TabGroupVisualData} from './tab_strip.mojom-webui.js';
 import {TabsApiProxy, TabsApiProxyImpl} from './tabs_api_proxy.js';
 
 export class TabGroupElement extends CustomElement {
   static override get template() {
-    return getTrustedHTML`{__html_template__}`;
+    return getTemplate();
   }
 
   private tabsApi_: TabsApiProxy;
diff --git a/chrome/browser/resources/tab_strip/tab_list.ts b/chrome/browser/resources/tab_strip/tab_list.ts
index 24241622..34c38b46 100644
--- a/chrome/browser/resources/tab_strip/tab_list.ts
+++ b/chrome/browser/resources/tab_strip/tab_list.ts
@@ -11,12 +11,12 @@
 import {FocusOutlineManager} from 'chrome://resources/js/cr/ui/focus_outline_manager.m.js';
 import {CustomElement} from 'chrome://resources/js/custom_element.js';
 import {EventTracker} from 'chrome://resources/js/event_tracker.m.js';
-import {getTrustedHTML} from 'chrome://resources/js/static_types.js';
 import {isRTL} from 'chrome://resources/js/util.m.js';
 
 import {DragManager, DragManagerDelegate} from './drag_manager.js';
 import {isTabElement, TabElement} from './tab.js';
 import {isDragHandle, isTabGroupElement, TabGroupElement} from './tab_group.js';
+import {getTemplate} from './tab_list.html.js';
 import {Tab, TabGroupVisualData} from './tab_strip.mojom-webui.js';
 import {TabsApiProxy, TabsApiProxyImpl} from './tabs_api_proxy.js';
 
@@ -149,7 +149,7 @@
   private scrollListener_: (e: Event) => void;
 
   static override get template() {
-    return getTrustedHTML`{__html_template__}`;
+    return getTemplate();
   }
 
   constructor() {
diff --git a/chrome/browser/resources/tab_strip/tab_strip.gni b/chrome/browser/resources/tab_strip/tab_strip.gni
new file mode 100644
index 0000000..c5fe6b5
--- /dev/null
+++ b/chrome/browser/resources/tab_strip/tab_strip.gni
@@ -0,0 +1,32 @@
+# 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.
+
+_non_web_component_files = [
+  "drag_manager.ts",
+  "tabs_api_proxy.ts",
+  "tab_swiper.ts",
+]
+
+# Files holding a custom element definition and have an equivalent .html file.
+_web_component_files = [
+  "alert_indicator.ts",
+  "alert_indicators.ts",
+  "tab_group.ts",
+  "tab_list.ts",
+  "tab.ts",
+]
+
+# Files that are passed as input to html_to_wrapper().
+html_files = []
+foreach(f, _web_component_files) {
+  html_files += [ string_replace(f, ".ts", ".html") ]
+}
+
+# Files that are generated by html_to_wrapper().
+html_wrapper_files = []
+foreach(f, html_files) {
+  html_wrapper_files += [ f + ".ts" ]
+}
+
+ts_files = _web_component_files + _non_web_component_files
diff --git a/chrome/browser/safe_browsing/tailored_security/chrome_tailored_security_service.cc b/chrome/browser/safe_browsing/tailored_security/chrome_tailored_security_service.cc
index 4115d2b..048a059 100644
--- a/chrome/browser/safe_browsing/tailored_security/chrome_tailored_security_service.cc
+++ b/chrome/browser/safe_browsing/tailored_security/chrome_tailored_security_service.cc
@@ -86,14 +86,6 @@
     return;
   }
 
-  if (is_enabled) {
-    // TODO(crbug.com/1330723): Remove this metric. This case is being replaced
-    // by `kEnhancedProtectionAlreadyEnabled`.
-    base::UmaHistogramBoolean(
-        "SafeBrowsing.TailoredSecurity.SyncPromptSkippedAlreadyEnabled",
-        IsEnhancedProtectionEnabled(*prefs()));
-  }
-
   if (is_enabled && IsEnhancedProtectionEnabled(*prefs())) {
     RecordEnabledNotificationResult(
         TailoredSecurityNotificationResult::kEnhancedProtectionAlreadyEnabled);
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 620c871..4540e1d 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -3300,6 +3300,8 @@
       "views/frame/immersive_mode_controller_chromeos.h",
       "views/profiles/lacros_first_run_signed_in_flow_controller.cc",
       "views/profiles/lacros_first_run_signed_in_flow_controller.h",
+      "webui/policy/status_provider/device_policy_status_provider_lacros.cc",
+      "webui/policy/status_provider/device_policy_status_provider_lacros.h",
       "webui/signin/profile_picker_lacros_sign_in_provider.cc",
       "webui/signin/profile_picker_lacros_sign_in_provider.h",
       "window_sizer/window_sizer_chromeos.cc",
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/geo/PlatformNetworksManager.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/geo/PlatformNetworksManager.java
index cce068b..432bd11 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/geo/PlatformNetworksManager.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/geo/PlatformNetworksManager.java
@@ -169,7 +169,7 @@
 
     static void getAllVisibleCells(Context context, TelephonyManager telephonyManager,
             Callback<Set<VisibleCell>> callback) {
-        if (!hasLocationPermission(context)) {
+        if (!hasLocationPermission(context) || telephonyManager == null) {
             callback.onResult(Collections.emptySet());
             return;
         }
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/geo/PlatformNetworksManagerTest.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/geo/PlatformNetworksManagerTest.java
index a439d2f..fedc94d 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/geo/PlatformNetworksManagerTest.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/geo/PlatformNetworksManagerTest.java
@@ -296,6 +296,14 @@
     }
 
     @Test
+    public void testGetAllVisibleCells_telephonyManagerUnavailable() {
+        PlatformNetworksManager.getAllVisibleCells(mContext, null, mVisibleCellCallback);
+        verify(mVisibleCellCallback).onResult(mVisibleCellsArgument.capture());
+        // Empty set expected
+        assertEquals(0, mVisibleCellsArgument.getValue().size());
+    }
+
+    @Test
     public void testGetConnectedWifi_BeforeS() {
         VisibleWifi visibleWifi = PlatformNetworksManager.getConnectedWifi(mContext);
         assertEquals(CONNECTED_WIFI, visibleWifi);
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarSnapshotState.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarSnapshotState.java
index a7b2f5c..44d90b2 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarSnapshotState.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarSnapshotState.java
@@ -61,7 +61,6 @@
     private final ColorStateList mColorStateList;
     private final boolean mIsShowingUpdateBadgeDuringLastCapture;
     private final boolean mIsPaintPreview;
-    private final float mProgress;
     private final int mUnfocusedLocationBarLayoutWidth;
 
     public ToolbarSnapshotState(@ColorInt int tint, int tabCount, ButtonData optionalButtonData,
@@ -77,7 +76,8 @@
         mColorStateList = colorStateList;
         mIsShowingUpdateBadgeDuringLastCapture = isShowingUpdateBadgeDuringLastCapture;
         mIsPaintPreview = isPaintPreview;
-        mProgress = progress;
+        // Progress is not currently used for comparing snapshot states. It isn't part of the bitmap
+        // capture anyway.
         mUnfocusedLocationBarLayoutWidth = unfocusedLocationBarLayoutWidth;
     }
 
@@ -105,8 +105,6 @@
             return ToolbarSnapshotDifference.SHOWING_UPDATE_BADGE;
         } else if (mIsPaintPreview != that.mIsPaintPreview) {
             return ToolbarSnapshotDifference.PAINT_PREVIEW;
-        } else if (Float.compare(mProgress, that.mProgress) != 0) {
-            return ToolbarSnapshotDifference.PROGRESS;
         } else if (mUnfocusedLocationBarLayoutWidth != that.mUnfocusedLocationBarLayoutWidth) {
             return ToolbarSnapshotDifference.LOCATION_BAR_WIDTH;
         } else if (!Objects.equals(mUrlText, that.mUrlText)) {
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarSnapshotStateTest.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarSnapshotStateTest.java
index 0345bcd..e54dd22 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarSnapshotStateTest.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarSnapshotStateTest.java
@@ -173,7 +173,7 @@
                 DEFAULT_SECURITY_ICON, mDefaultColorStateList,
                 DEFAULT_IS_SHOWING_UPDATE_BADGE_DURING_LAST_CAPTURE, DEFAULT_IS_PAINT_PREVIEW, 0.2f,
                 DEFAULT_UNFOCUSED_LOCATION_BAR_LAYOUT_WIDTH);
-        Assert.assertEquals(ToolbarSnapshotDifference.PROGRESS,
+        Assert.assertEquals(ToolbarSnapshotDifference.NONE,
                 otherToolbarSnapshotState.getAnyDifference(mDefaultToolbarSnapshotState));
     }
     @Test
diff --git a/chrome/browser/ui/ash/chrome_shell_delegate.cc b/chrome/browser/ui/ash/chrome_shell_delegate.cc
index 35c1244..3bb0057 100644
--- a/chrome/browser/ui/ash/chrome_shell_delegate.cc
+++ b/chrome/browser/ui/ash/chrome_shell_delegate.cc
@@ -25,7 +25,7 @@
 #include "chrome/browser/ash/multidevice_setup/multidevice_setup_service_factory.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/browser_process.h"
-#include "chrome/browser/browser_process_platform_part_chromeos.h"
+#include "chrome/browser/browser_process_platform_part_ash.h"
 #include "chrome/browser/nearby_sharing/nearby_share_delegate_impl.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_manager.h"
diff --git a/chrome/browser/ui/startup/startup_browser_creator_browsertest.cc b/chrome/browser/ui/startup/startup_browser_creator_browsertest.cc
index b124dec..24db640 100644
--- a/chrome/browser/ui/startup/startup_browser_creator_browsertest.cc
+++ b/chrome/browser/ui/startup/startup_browser_creator_browsertest.cc
@@ -665,16 +665,16 @@
     }
   }
 
-  void ExpectBlockLaunch(
-      const std::string& force_installed_app_id = std::string()) {
+  void ExpectBlockLaunchAndLaunchAnyways(const std::string& app_id,
+                                         bool force_install_dialog,
+                                         bool tab_launch_expected) {
     ASSERT_EQ(2u, chrome::GetBrowserCount(browser()->profile()));
 #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || \
     BUILDFLAG(IS_FUCHSIA)
     auto waiter = views::NamedWidgetShownWaiter(
         views::test::AnyWidgetTestPasskey{},
-        force_installed_app_id.empty()
-            ? "DeprecatedAppsDialogView"
-            : "ForceInstalledDeprecatedAppsDialogView");
+        force_install_dialog ? "ForceInstalledDeprecatedAppsDialogView"
+                             : "DeprecatedAppsDialogView");
 #endif
     // Should have opened the requested homepage about:blank in 1st window.
     TabStripModel* tab_strip = browser()->tab_strip_model();
@@ -694,15 +694,48 @@
     BUILDFLAG(IS_FUCHSIA)
 
     GURL expected_url =
-        force_installed_app_id.empty()
-            ? GURL(chrome::kChromeUIAppsWithDeprecationDialogURL)
-            : GURL(chrome::kChromeUIAppsWithForceInstalledDeprecationDialogURL +
-                   force_installed_app_id);
+        force_install_dialog
+            ? GURL(chrome::kChromeUIAppsWithForceInstalledDeprecationDialogURL +
+                   app_id)
+            : GURL(chrome::kChromeUIAppsWithDeprecationDialogURL + app_id);
     EXPECT_EQ(expected_url,
               other_tab_strip->GetWebContentsAt(0)->GetVisibleURL());
 
+    std::set<Browser*> initial_browsers;
+    for (auto* initial_browser : *BrowserList::GetInstance())
+      initial_browsers.insert(initial_browser);
+
+    content::TestNavigationObserver same_tab_observer(
+        other_tab_strip->GetActiveWebContents(), 1,
+        content::MessageLoopRunner::QuitMode::DEFERRED,
+        /*ignore_uncommitted_navigations=*/false);
+
     // Verify that the Deprecated Apps Dialog View also shows up.
-    EXPECT_TRUE(waiter.WaitIfNeededAndGet() != nullptr);
+    auto* dialog = waiter.WaitIfNeededAndGet();
+    EXPECT_TRUE(dialog != nullptr);
+    if (force_install_dialog) {
+      // The 'accept' option in the force-install dialog is "launch anyways".
+      dialog->widget_delegate()->AsDialogDelegate()->Accept();
+    } else {
+      // The 'cancel' option in the deprecation dialog is "launch anyways".
+      dialog->widget_delegate()->AsDialogDelegate()->Cancel();
+    }
+    if (tab_launch_expected) {
+      same_tab_observer.Wait();
+    } else {
+      Browser* app_browser =
+          ui_test_utils::GetBrowserNotInSet(initial_browsers);
+      if (!app_browser) {
+        app_browser = ui_test_utils::WaitForBrowserToOpen();
+        // The new browser should never be in |excluded_browsers|.
+        DCHECK(!base::Contains(initial_browsers, app_browser));
+      }
+      ASSERT_TRUE(app_browser);
+      TabStripModel* app_tab_strip = app_browser->tab_strip_model();
+      EXPECT_EQ(1, app_tab_strip->count());
+      EXPECT_TRUE(app_browser->is_type_app());
+      EXPECT_FALSE(app_browser->is_type_normal());
+    }
 #endif
   }
 
@@ -750,7 +783,19 @@
       command_line, base::FilePath(), chrome::startup::IsProcessStartup::kNo,
       {browser()->profile(), StartupProfileMode::kBrowserWindow}, {}));
 
-  if (IsExpectedToAllowLaunch()) {
+  Browser* expected_launch_browser = browser();
+
+  if (!IsExpectedToAllowLaunch()) {
+    ExpectBlockLaunchAndLaunchAnyways(extension_app->id(),
+                                      /*force_install_dialog=*/false,
+                                      /*tab_launch_expected=*/true);
+    // When we block the launch, we always create a new browser window to
+    // display chrome://apps and the dialog.
+    ASSERT_EQ(2u, chrome::GetBrowserCount(browser()->profile()));
+    Browser* expected_launch_browser = FindOneOtherBrowser(browser());
+    tab_strip = expected_launch_browser->tab_strip_model();
+    EXPECT_EQ(1, tab_strip->count());
+  } else {
     // No pref was set, so the app should have opened in a tab in the existing
     // window.
     tab_waiter.Wait();
@@ -758,20 +803,18 @@
     EXPECT_EQ(2, tab_strip->count());
     EXPECT_EQ(tab_strip->GetActiveWebContents(),
               tab_strip->GetWebContentsAt(1));
-
-    // It should be a standard tabbed window, not an app window.
-    EXPECT_FALSE(browser()->is_type_app());
-    EXPECT_TRUE(browser()->is_type_normal());
-
-    // It should have loaded the requested app.
-    const std::u16string expected_title(
-        u"app_with_tab_container/empty.html title");
-    content::TitleWatcher title_watcher(tab_strip->GetActiveWebContents(),
-                                        expected_title);
-    EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle());
-  } else {
-    ExpectBlockLaunch();
   }
+
+  // It should be a standard tabbed window, not an app window.
+  EXPECT_FALSE(expected_launch_browser->is_type_app());
+  EXPECT_TRUE(expected_launch_browser->is_type_normal());
+
+  // It should have loaded the requested app.
+  const std::u16string expected_title(
+      u"app_with_tab_container/empty.html title");
+  content::TitleWatcher title_watcher(tab_strip->GetActiveWebContents(),
+                                      expected_title);
+  EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle());
 }
 
 IN_PROC_BROWSER_TEST_P(StartupBrowserCreatorChromeAppShortcutTest,
@@ -791,23 +834,27 @@
       command_line, base::FilePath(), chrome::startup::IsProcessStartup::kNo,
       {browser()->profile(), StartupProfileMode::kBrowserWindow}, {}));
 
-  if (IsExpectedToAllowLaunch()) {
-    // Pref was set to open in a window, so the app should have opened in a
-    // window.  The launch should have created a new browser. Find the new
-    // browser.
-    Browser* new_browser = browser_waiter.Wait();
-    ASSERT_TRUE(new_browser);
-
-    // Expect an app window.
-    EXPECT_TRUE(new_browser->is_type_app());
-
-    // The browser's app_name should include the app's ID.
-    EXPECT_NE(new_browser->app_name().find(extension_app->id()),
-              std::string::npos)
-        << new_browser->app_name();
-  } else {
-    ExpectBlockLaunch();
+  if (!IsExpectedToAllowLaunch()) {
+    ExpectBlockLaunchAndLaunchAnyways(extension_app->id(),
+                                      /*force_install_dialog=*/false,
+                                      /*tab_launch_expected=*/false);
+    // When we block the launch, we always create a new browser window to
+    // display chrome://apps and the dialog, and then another to launch the app.
+    ASSERT_EQ(3u, chrome::GetBrowserCount(browser()->profile()));
   }
+  // Pref was set to open in a window, so the app should have opened in a
+  // window.  The launch should have created a new browser. Find the new
+  // browser.
+  Browser* new_browser = browser_waiter.Wait();
+  ASSERT_TRUE(new_browser);
+
+  // Expect an app window.
+  EXPECT_TRUE(new_browser->is_type_app());
+
+  // The browser's app_name should include the app's ID.
+  EXPECT_NE(new_browser->app_name().find(extension_app->id()),
+            std::string::npos)
+      << new_browser->app_name();
 }
 
 IN_PROC_BROWSER_TEST_P(StartupBrowserCreatorChromeAppShortcutTest,
@@ -830,7 +877,19 @@
       command_line, base::FilePath(), chrome::startup::IsProcessStartup::kNo,
       {browser()->profile(), StartupProfileMode::kBrowserWindow}, {}));
 
-  if (IsExpectedToAllowLaunch()) {
+  Browser* expected_launch_browser = browser();
+
+  if (!IsExpectedToAllowLaunch()) {
+    ExpectBlockLaunchAndLaunchAnyways(extension_app->id(),
+                                      /*force_install_dialog=*/false,
+                                      /*tab_launch_expected=*/true);
+    // When we block the launch, we always create a new browser window to
+    // display chrome://apps and the dialog.
+    ASSERT_EQ(2u, chrome::GetBrowserCount(browser()->profile()));
+    Browser* expected_launch_browser = FindOneOtherBrowser(browser());
+    tab_strip = expected_launch_browser->tab_strip_model();
+    EXPECT_EQ(1, tab_strip->count());
+  } else {
     // When an app shortcut is open and the pref indicates a tab should open,
     // the tab is open in the existing browser window.
     tab_waiter.Wait();
@@ -838,22 +897,20 @@
     EXPECT_EQ(2, tab_strip->count());
     EXPECT_EQ(tab_strip->GetActiveWebContents(),
               tab_strip->GetWebContentsAt(1));
-
-    // The browser's app_name should not include the app's ID: it is in a normal
-    // tabbed browser.
-    EXPECT_EQ(browser()->app_name().find(extension_app->id()),
-              std::string::npos)
-        << browser()->app_name();
-
-    // It should have loaded the requested app.
-    const std::u16string expected_title(
-        u"app_with_tab_container/empty.html title");
-    content::TitleWatcher title_watcher(tab_strip->GetActiveWebContents(),
-                                        expected_title);
-    EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle());
-  } else {
-    ExpectBlockLaunch();
   }
+
+  // The browser's app_name should not include the app's ID: it is in a normal
+  // tabbed browser.
+  EXPECT_EQ(expected_launch_browser->app_name().find(extension_app->id()),
+            std::string::npos)
+      << browser()->app_name();
+
+  // It should have loaded the requested app.
+  const std::u16string expected_title(
+      u"app_with_tab_container/empty.html title");
+  content::TitleWatcher title_watcher(tab_strip->GetActiveWebContents(),
+                                      expected_title);
+  EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle());
 }
 
 IN_PROC_BROWSER_TEST_P(StartupBrowserCreatorChromeAppShortcutTest,
@@ -882,32 +939,36 @@
       command_line, base::FilePath(), chrome::startup::IsProcessStartup::kNo,
       {browser()->profile(), StartupProfileMode::kBrowserWindow}, {}));
 
-  if (IsExpectedToAllowLaunch()) {
-    tab_waiter.Wait();
+  Browser* expected_launch_browser = browser();
 
-    // Policy force-installed app should be allowed regardless of Chrome App
-    // Deprecation status.
-    //
+  if (!IsExpectedToAllowLaunch()) {
+    ExpectBlockLaunchAndLaunchAnyways(extension_app->id(), true, true);
+    // When we block the launch, we always create a new browser window to
+    // display chrome://apps and the dialog.
+    ASSERT_EQ(2u, chrome::GetBrowserCount(browser()->profile()));
+    Browser* expected_launch_browser = FindOneOtherBrowser(browser());
+    tab_strip = expected_launch_browser->tab_strip_model();
+    EXPECT_EQ(1, tab_strip->count());
+  } else {
+    tab_waiter.Wait();
+    ASSERT_EQ(1u, chrome::GetBrowserCount(browser()->profile()));
     // No app launch pref was set, so the app should have opened in a tab in the
     // existing window.
-    ASSERT_EQ(1u, chrome::GetBrowserCount(browser()->profile()));
     EXPECT_EQ(2, tab_strip->count());
     EXPECT_EQ(tab_strip->GetActiveWebContents(),
               tab_strip->GetWebContentsAt(1));
-
-    // It should be a standard tabbed window, not an app window.
-    EXPECT_FALSE(browser()->is_type_app());
-    EXPECT_TRUE(browser()->is_type_normal());
-
-    // It should have loaded the requested app.
-    const std::u16string expected_title(
-        u"app_with_tab_container/empty.html title");
-    content::TitleWatcher title_watcher(tab_strip->GetActiveWebContents(),
-                                        expected_title);
-    EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle());
-  } else {
-    ExpectBlockLaunch(extension_app->id());
   }
+
+  // It should be a standard tabbed window, not an app window.
+  EXPECT_FALSE(expected_launch_browser->is_type_app());
+  EXPECT_TRUE(expected_launch_browser->is_type_normal());
+
+  // It should have loaded the requested app.
+  const std::u16string expected_title(
+      u"app_with_tab_container/empty.html title");
+  content::TitleWatcher title_watcher(tab_strip->GetActiveWebContents(),
+                                      expected_title);
+  EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle());
 }
 
 INSTANTIATE_TEST_SUITE_P(
diff --git a/chrome/browser/ui/tab_dialogs.h b/chrome/browser/ui/tab_dialogs.h
index 3b7f8ec..2b3732e 100644
--- a/chrome/browser/ui/tab_dialogs.h
+++ b/chrome/browser/ui/tab_dialogs.h
@@ -52,13 +52,16 @@
 
   // Shows the deprecated app dialog.
   virtual void ShowDeprecatedAppsDialog(
+      const extensions::ExtensionId& optional_launched_extension_id,
       const std::set<extensions::ExtensionId>& deprecated_app_ids,
-      content::WebContents* web_contents) = 0;
+      content::WebContents* web_contents,
+      base::OnceClosure launch_anyways) = 0;
 
   // Shows the force installed and deprecated app dialog.
   virtual void ShowForceInstalledDeprecatedAppsDialog(
       const extensions::ExtensionId& app_id,
-      content::WebContents* web_contents) = 0;
+      content::WebContents* web_contents,
+      base::OnceClosure launch_anyways) = 0;
 
   // Shows or hides the ManagePasswords bubble.
   // Pass true for |user_action| if this is a user initiated action.
diff --git a/chrome/browser/ui/views/javascript_tab_modal_dialog_view_views.cc b/chrome/browser/ui/views/javascript_tab_modal_dialog_view_views.cc
index e6ec04e..684a1b2e 100644
--- a/chrome/browser/ui/views/javascript_tab_modal_dialog_view_views.cc
+++ b/chrome/browser/ui/views/javascript_tab_modal_dialog_view_views.cc
@@ -10,6 +10,7 @@
 #include "components/constrained_window/constrained_window_views.h"
 #include "content/public/browser/javascript_dialog_manager.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
+#include "ui/views/accessibility/view_accessibility.h"
 #include "ui/views/bubble/bubble_frame_view.h"
 #include "ui/views/controls/label.h"
 #include "ui/views/controls/message_box_view.h"
@@ -46,6 +47,8 @@
   auto* bubble_frame_view = static_cast<views::BubbleFrameView*>(
       GetWidget()->non_client_view()->frame_view());
   bubble_frame_view->SetTitleView(CreateTitleOriginLabel(GetWindowTitle()));
+  GetWidget()->GetRootView()->GetViewAccessibility().OverrideDescription(
+      message_text_);
 }
 
 JavaScriptTabModalDialogViewViews::JavaScriptTabModalDialogViewViews(
@@ -110,6 +113,19 @@
   constrained_window::ShowWebModalDialogViews(this, parent_web_contents);
 }
 
+// static
+JavaScriptTabModalDialogViewViews*
+JavaScriptTabModalDialogViewViews::CreateAlertDialogForTesting(
+    Browser* browser,
+    std::u16string title,
+    std::u16string message) {
+  return new JavaScriptTabModalDialogViewViews(
+      browser->tab_strip_model()->GetActiveWebContents(),
+      browser->tab_strip_model()->GetActiveWebContents(), title,
+      content::JAVASCRIPT_DIALOG_TYPE_ALERT, message, std::u16string(),
+      base::NullCallback(), base::NullCallback());
+}
+
 BEGIN_METADATA(JavaScriptTabModalDialogViewViews, views::DialogDelegateView)
 END_METADATA
 
diff --git a/chrome/browser/ui/views/javascript_tab_modal_dialog_view_views.h b/chrome/browser/ui/views/javascript_tab_modal_dialog_view_views.h
index b472832b..b651b20 100644
--- a/chrome/browser/ui/views/javascript_tab_modal_dialog_view_views.h
+++ b/chrome/browser/ui/views/javascript_tab_modal_dialog_view_views.h
@@ -8,6 +8,7 @@
 #include <memory>
 
 #include "base/memory/raw_ptr.h"
+#include "chrome/browser/ui/browser.h"
 #include "components/javascript_dialogs/tab_modal_dialog_view.h"
 #include "content/public/browser/javascript_dialog_manager.h"
 #include "ui/base/metadata/metadata_header_macros.h"
@@ -45,6 +46,13 @@
   // views::View:
   void AddedToWidget() override;
 
+  // TODO(crbug.com/1330353): We cannot use unique_ptr because ownership of
+  // this object gets passed to Views.
+  static JavaScriptTabModalDialogViewViews* CreateAlertDialogForTesting(
+      Browser* browser,
+      std::u16string title,
+      std::u16string message);
+
  private:
   friend class JavaScriptDialog;
   friend class JavaScriptTabModalDialogManagerDelegateDesktop;
diff --git a/chrome/browser/ui/views/javascript_tab_modal_dialog_view_views_browsertest.cc b/chrome/browser/ui/views/javascript_tab_modal_dialog_view_views_browsertest.cc
new file mode 100644
index 0000000..db23024
--- /dev/null
+++ b/chrome/browser/ui/views/javascript_tab_modal_dialog_view_views_browsertest.cc
@@ -0,0 +1,59 @@
+// 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 "base/bind.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/views/javascript_tab_modal_dialog_view_views.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "content/public/test/browser_test.h"
+#include "ui/views/accessibility/view_accessibility.h"
+#include "ui/views/bubble/bubble_frame_view.h"
+
+using JavaScriptTabModalDialogViewViewsBrowserTest = InProcessBrowserTest;
+
+IN_PROC_BROWSER_TEST_F(JavaScriptTabModalDialogViewViewsBrowserTest,
+                       AlertDialogAccessibleNameDescriptionAndRole) {
+  std::u16string title = u"Title";
+  std::u16string message = u"The message";
+  auto* dialog_views =
+      JavaScriptTabModalDialogViewViews::CreateAlertDialogForTesting(
+          browser(), title, message);
+
+  // The JavaScriptTabModalDialogViewViews should set the RootView's accessible
+  // name to the alert's title and the accessible description to the alert's
+  // message text. The role of the RootView should be dialog.
+  ui::AXNodeData data;
+  dialog_views->GetWidget()
+      ->GetRootView()
+      ->GetViewAccessibility()
+      .GetAccessibleNodeData(&data);
+  EXPECT_EQ(data.GetString16Attribute(ax::mojom::StringAttribute::kName),
+            title);
+  EXPECT_EQ(data.GetString16Attribute(ax::mojom::StringAttribute::kDescription),
+            message);
+  EXPECT_EQ(data.role, ax::mojom::Role::kDialog);
+
+  // TODO(crbug.com/1325879): Nothing sets the description-from attribute
+  // when Views override the description. If we fix that in OverrideDescription,
+  // the value will still not be carried over to the AXNodeData for the reason
+  // described in the issue.
+  EXPECT_EQ(data.GetIntAttribute(ax::mojom::IntAttribute::kDescriptionFrom),
+            static_cast<int32_t>(ax::mojom::DescriptionFrom::kNone));
+}
+
+IN_PROC_BROWSER_TEST_F(JavaScriptTabModalDialogViewViewsBrowserTest,
+                       AlertDialogCloseButtonAccessibilityIgnored) {
+  std::u16string title = u"Title";
+  std::u16string message = u"The message";
+  auto* dialog_views =
+      JavaScriptTabModalDialogViewViews::CreateAlertDialogForTesting(
+          browser(), title, message);
+
+  // In an alert, the Close button is not used. It should be removed from the
+  // accessibility tree ("ignored").
+  auto* bubble_frame_view = static_cast<views::BubbleFrameView*>(
+      dialog_views->GetWidget()->non_client_view()->frame_view());
+  if (auto* close_button = bubble_frame_view->GetCloseButtonForTesting())
+    EXPECT_TRUE(close_button->GetViewAccessibility().IsIgnored());
+}
diff --git a/chrome/browser/ui/views/profiles/profile_picker_view_browsertest.cc b/chrome/browser/ui/views/profiles/profile_picker_view_browsertest.cc
index ed46393..5d31ad4 100644
--- a/chrome/browser/ui/views/profiles/profile_picker_view_browsertest.cc
+++ b/chrome/browser/ui/views/profiles/profile_picker_view_browsertest.cc
@@ -281,11 +281,14 @@
   void ShowManagePasswordsBubble(bool user_action) override {}
   void HideManagePasswordsBubble() override {}
   void ShowDeprecatedAppsDialog(
+      const extensions::ExtensionId& optional_launched_extension_id,
       const std::set<extensions::ExtensionId>& deprecated_app_ids,
-      content::WebContents* web_contents) override {}
+      content::WebContents* web_contents,
+      base::OnceClosure launch_anyways) override {}
   void ShowForceInstalledDeprecatedAppsDialog(
       const extensions::ExtensionId& app_id,
-      content::WebContents* web_contents) override {}
+      content::WebContents* web_contents,
+      base::OnceClosure launch_anyways) override {}
 
  private:
   raw_ptr<content::WebContents> contents_;
diff --git a/chrome/browser/ui/views/tab_dialogs_views.cc b/chrome/browser/ui/views/tab_dialogs_views.cc
index 26a47d3..6f520cc 100644
--- a/chrome/browser/ui/views/tab_dialogs_views.cc
+++ b/chrome/browser/ui/views/tab_dialogs_views.cc
@@ -80,19 +80,23 @@
 }
 
 void TabDialogsViews::ShowDeprecatedAppsDialog(
+    const extensions::ExtensionId& optional_launched_extension_id,
     const std::set<extensions::ExtensionId>& deprecated_app_ids,
-    content::WebContents* web_contents) {
+    content::WebContents* web_contents,
+    base::OnceClosure launch_anyways) {
 #if defined(TOOLKIT_VIEWS) && !BUILDFLAG(IS_CHROMEOS)
-  DeprecatedAppsDialogView::CreateAndShowDialog(deprecated_app_ids,
-                                                web_contents);
+  DeprecatedAppsDialogView::CreateAndShowDialog(
+      optional_launched_extension_id, deprecated_app_ids, web_contents,
+      std::move(launch_anyways));
 #endif
 }
 
 void TabDialogsViews::ShowForceInstalledDeprecatedAppsDialog(
     const extensions::ExtensionId& app_id,
-    content::WebContents* web_contents) {
+    content::WebContents* web_contents,
+    base::OnceClosure launch_anyways) {
 #if defined(TOOLKIT_VIEWS) && !BUILDFLAG(IS_CHROMEOS)
-  ForceInstalledDeprecatedAppsDialogView::CreateAndShowDialog(app_id,
-                                                              web_contents);
+  ForceInstalledDeprecatedAppsDialogView::CreateAndShowDialog(
+      app_id, web_contents, std::move(launch_anyways));
 #endif
 }
diff --git a/chrome/browser/ui/views/tab_dialogs_views.h b/chrome/browser/ui/views/tab_dialogs_views.h
index 3dc1d0d..de2c205 100644
--- a/chrome/browser/ui/views/tab_dialogs_views.h
+++ b/chrome/browser/ui/views/tab_dialogs_views.h
@@ -30,11 +30,14 @@
   void ShowManagePasswordsBubble(bool user_action) override;
   void HideManagePasswordsBubble() override;
   void ShowDeprecatedAppsDialog(
+      const extensions::ExtensionId& optional_launched_extension_id,
       const std::set<extensions::ExtensionId>& deprecated_app_ids,
-      content::WebContents* web_contents) override;
+      content::WebContents* web_contents,
+      base::OnceClosure launch_anyways) override;
   void ShowForceInstalledDeprecatedAppsDialog(
       const extensions::ExtensionId& app_id,
-      content::WebContents* web_contents) override;
+      content::WebContents* web_contents,
+      base::OnceClosure launch_anyways) override;
 
  private:
   raw_ptr<content::WebContents> web_contents_;  // Weak. Owns this.
diff --git a/chrome/browser/ui/views/web_apps/deprecated_apps_dialog_view.cc b/chrome/browser/ui/views/web_apps/deprecated_apps_dialog_view.cc
index cafc58b..d0866a9 100644
--- a/chrome/browser/ui/views/web_apps/deprecated_apps_dialog_view.cc
+++ b/chrome/browser/ui/views/web_apps/deprecated_apps_dialog_view.cc
@@ -111,10 +111,13 @@
 
 // static
 DeprecatedAppsDialogView* DeprecatedAppsDialogView::CreateAndShowDialog(
+    const extensions::ExtensionId& optional_launched_extension_id,
     const std::set<extensions::ExtensionId>& deprecated_app_ids,
-    content::WebContents* web_contents) {
-  DeprecatedAppsDialogView* view =
-      new DeprecatedAppsDialogView(deprecated_app_ids, web_contents);
+    content::WebContents* web_contents,
+    base::OnceClosure launch_anyways) {
+  DeprecatedAppsDialogView* view = new DeprecatedAppsDialogView(
+      optional_launched_extension_id, deprecated_app_ids, web_contents,
+      std::move(launch_anyways));
   view->InitDialog();
   constrained_window::ShowWebModalDialogViews(view, web_contents);
   return view;
@@ -125,15 +128,42 @@
 }
 
 std::u16string DeprecatedAppsDialogView::GetWindowTitle() const {
-  return l10n_util::GetPluralStringFUTF16(
-      IDS_DEPRECATED_APPS_RENDERER_TITLE,
+  if (launched_extension_name_) {
+    return l10n_util::GetStringFUTF16(
+        IDS_DEPRECATED_APPS_RENDERER_TITLE_WITH_APP_NAME,
+        launched_extension_name_.value());
+  }
+  if (single_app_name_) {
+    return l10n_util::GetStringFUTF16(
+        IDS_DEPRECATED_APPS_RENDERER_TITLE_WITH_APP_NAME,
+        single_app_name_.value());
+  }
+  return l10n_util::GetStringFUTF16Int(
+      IDS_DEPRECATED_APPS_RENDERER_TITLE_PLURAL,
       deprecated_apps_table_model_->RowCount());
 }
 
 DeprecatedAppsDialogView::DeprecatedAppsDialogView(
+    const extensions::ExtensionId& optional_launched_extension_id,
     const std::set<extensions::ExtensionId>& deprecated_app_ids,
-    content::WebContents* web_contents)
-    : deprecated_app_ids_(deprecated_app_ids), web_contents_(web_contents) {
+    content::WebContents* web_contents,
+    base::OnceClosure launch_anyways)
+    : deprecated_app_ids_(deprecated_app_ids),
+      launch_anyways_(std::move(launch_anyways)),
+      web_contents_(web_contents) {
+  if (!optional_launched_extension_id.empty()) {
+    const extensions::Extension* extension =
+        extensions::ExtensionRegistry::Get(web_contents_->GetBrowserContext())
+            ->GetInstalledExtension(optional_launched_extension_id);
+    launched_extension_name_ = base::UTF8ToUTF16(extension->name());
+  }
+  if (deprecated_app_ids_.size() == 1) {
+    const extensions::Extension* extension =
+        extensions::ExtensionRegistry::Get(web_contents_->GetBrowserContext())
+            ->GetInstalledExtension(*deprecated_app_ids_.begin());
+    DCHECK(extension);
+    single_app_name_ = base::UTF8ToUTF16(extension->name());
+  }
   deprecated_apps_table_model_ = std::make_unique<DeprecatedAppsTableModel>(
       deprecated_app_ids, web_contents,
       base::BindRepeating(&DeprecatedAppsDialogView::OnIconsLoadedForTable,
@@ -153,24 +183,29 @@
           views::DISTANCE_UNRELATED_CONTROL_VERTICAL)));
   set_fixed_width(views::LayoutProvider::Get()->GetDistanceMetric(
       views::DISTANCE_MODAL_DIALOG_PREFERRED_WIDTH));
-
   // Set up buttons.
   SetButtonLabel(ui::DIALOG_BUTTON_OK,
                  l10n_util::GetPluralStringFUTF16(
                      IDS_DEPRECATED_APPS_OK_LABEL,
                      deprecated_apps_table_model_->RowCount()));
-  SetButtonLabel(ui::DIALOG_BUTTON_CANCEL,
-                 l10n_util::GetStringUTF16(IDS_DEPRECATED_APPS_CANCEL_LABEL));
-  SetDefaultButton(ui::DIALOG_BUTTON_NONE);
-  SetCancelCallback(base::BindOnce(&DeprecatedAppsDialogView::CloseDialog,
+  SetAcceptCallback(base::BindOnce(&DeprecatedAppsDialogView::OnAccept,
                                    base::Unretained(this)));
-  SetAcceptCallback(base::BindOnce(
-      &DeprecatedAppsDialogView::UninstallExtensions, base::Unretained(this)));
 
-  info_label_ = AddChildView(
-      std::make_unique<views::Label>(l10n_util::GetPluralStringFUTF16(
-          IDS_DEPRECATED_APPS_MONITOR_RENDERER,
-          deprecated_apps_table_model_->RowCount())));
+  if (launched_extension_name_) {
+    SetButtonLabel(
+        ui::DIALOG_BUTTON_CANCEL,
+        l10n_util::GetStringUTF16(IDS_DEPRECATED_APPS_LAUNCH_ANYWAY_LABEL));
+  } else {
+    SetButtonLabel(ui::DIALOG_BUTTON_CANCEL,
+                   l10n_util::GetStringUTF16(IDS_DEPRECATED_APPS_CANCEL_LABEL));
+  }
+  SetCancelCallback(base::BindOnce(&DeprecatedAppsDialogView::OnCancel,
+                                   base::Unretained(this)));
+
+  SetDefaultButton(ui::DIALOG_BUTTON_OK);
+
+  info_label_ = AddChildView(std::make_unique<views::Label>(
+      l10n_util::GetStringUTF16(IDS_DEPRECATED_APPS_MONITOR_RENDERER)));
   info_label_->SetMultiLine(true);
   info_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
 
@@ -186,8 +221,8 @@
             ui::PAGE_TRANSITION_LINK, /*is_renderer_initiated=*/false));
       },
       web_contents_));
-  learn_more->SetAccessibleName(l10n_util::GetStringUTF16(
-      IDS_FORCE_INSTALLED_DEPRECATED_APPS_LEARN_MORE_AX_LABEL));
+  learn_more->SetAccessibleName(
+      l10n_util::GetStringUTF16(IDS_DEPRECATED_APPS_LEARN_MORE_AX_LABEL));
   learn_more->SetHorizontalAlignment(gfx::ALIGN_LEFT);
 
   // Set up the table view.
@@ -218,7 +253,7 @@
   deprecated_apps_table_view_->SchedulePaint();
 }
 
-void DeprecatedAppsDialogView::UninstallExtensions() {
+void DeprecatedAppsDialogView::OnAccept() {
   for (extensions::ExtensionId id : deprecated_app_ids_) {
     extensions::ExtensionSystem::Get(web_contents_->GetBrowserContext())
         ->extension_service()
@@ -228,5 +263,10 @@
   CloseDialog();
 }
 
+void DeprecatedAppsDialogView::OnCancel() {
+  std::move(launch_anyways_).Run();
+  CloseDialog();
+}
+
 BEGIN_METADATA(DeprecatedAppsDialogView, views::DialogDelegateView)
 END_METADATA
diff --git a/chrome/browser/ui/views/web_apps/deprecated_apps_dialog_view.h b/chrome/browser/ui/views/web_apps/deprecated_apps_dialog_view.h
index 9cc73c9..4fce17c 100644
--- a/chrome/browser/ui/views/web_apps/deprecated_apps_dialog_view.h
+++ b/chrome/browser/ui/views/web_apps/deprecated_apps_dialog_view.h
@@ -7,11 +7,13 @@
 
 #include <memory>
 #include <set>
+#include <string>
 #include <vector>
 
 #include "base/callback_forward.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
+#include "chrome/browser/ui/tab_dialogs.h"
 #include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_system.h"
 #include "extensions/common/extension_id.h"
@@ -40,10 +42,20 @@
   DeprecatedAppsDialogView& operator=(const DeprecatedAppsDialogView&) = delete;
   ~DeprecatedAppsDialogView() override;
 
-  // Create the dialog metadata and show it.
+  // Create the dialog metadata and show it. Some behavior specializations:
+  // * If the `optional_launched_extension_id` is passed, then the dialog will
+  //   show the name of that chrome app in the title.
+  // * If `optional_launched_extension_id` is empty and `deprecated_app_ids`
+  //   only has one entry, then the dialog will display the name of the one
+  //   deprecated chrome app.
+  // * If `optional_launched_extension_id` is empty and `deprecated_app_ids` has
+  //   more than one entry, then the title will just contain the number of
+  //   deprecated chrome apps.
   static DeprecatedAppsDialogView* CreateAndShowDialog(
+      const extensions::ExtensionId& optional_launched_extension_id,
       const std::set<extensions::ExtensionId>& deprecated_app_ids,
-      content::WebContents* web_contents);
+      content::WebContents* web_contents,
+      base::OnceClosure launch_anyways);
 
   base::WeakPtr<DeprecatedAppsDialogView> AsWeakPtr();
 
@@ -55,8 +67,10 @@
  private:
   class DeprecatedAppsTableModel;
   DeprecatedAppsDialogView(
+      const extensions::ExtensionId& optional_launched_extension_id,
       const std::set<extensions::ExtensionId>& deprecated_app_ids,
-      content::WebContents* web_contents);
+      content::WebContents* web_contents,
+      base::OnceClosure launch_anyways);
 
   // Initialize the dialog when the object is instantiated.
   void InitDialog();
@@ -69,7 +83,8 @@
 
   // Callback that runs when accept button is clicked to
   // uninstall all extensions.
-  void UninstallExtensions();
+  void OnAccept();
+  void OnCancel();
 
   // Controls the table view within the dialog box.
   raw_ptr<views::TableView> deprecated_apps_table_view_;
@@ -79,7 +94,10 @@
 
   raw_ptr<views::Label> info_label_;
 
+  absl::optional<std::u16string> launched_extension_name_;
   std::set<extensions::ExtensionId> deprecated_app_ids_;
+  absl::optional<std::u16string> single_app_name_;
+  base::OnceClosure launch_anyways_;
 
   raw_ptr<content::WebContents> web_contents_;
 
diff --git a/chrome/browser/ui/views/web_apps/deprecated_apps_dialog_view_browsertest.cc b/chrome/browser/ui/views/web_apps/deprecated_apps_dialog_view_browsertest.cc
index 7e90e04..207bc1d 100644
--- a/chrome/browser/ui/views/web_apps/deprecated_apps_dialog_view_browsertest.cc
+++ b/chrome/browser/ui/views/web_apps/deprecated_apps_dialog_view_browsertest.cc
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "base/callback_helpers.h"
 #include "chrome/browser/ui/views/web_apps/deprecated_apps_dialog_view.h"
 
 #include <set>
@@ -9,6 +10,7 @@
 #include "base/feature_list.h"
 #include "base/run_loop.h"
 #include "base/test/bind.h"
+#include "base/test/mock_callback.h"
 #include "chrome/browser/extensions/extension_browsertest.h"
 #include "chrome/browser/platform_util.h"
 #include "chrome/browser/ui/browser.h"
@@ -150,7 +152,8 @@
 
   InstallExtensionForTesting(mock_app_manifest1, mock_url1);
   test_dialog_view_ = DeprecatedAppsDialogView::CreateAndShowDialog(
-                          deprecated_app_ids_for_testing_, web_contents)
+                          std::string(), deprecated_app_ids_for_testing_,
+                          web_contents, base::DoNothing())
                           ->AsWeakPtr();
 
   EXPECT_TRUE(IsDialogShown());
@@ -165,7 +168,8 @@
   InstallExtensionForTesting(mock_app_manifest1, mock_url1);
   InstallExtensionForTesting(mock_app_manifest2, mock_url2);
   test_dialog_view_ = DeprecatedAppsDialogView::CreateAndShowDialog(
-                          deprecated_app_ids_for_testing_, web_contents)
+                          std::string(), deprecated_app_ids_for_testing_,
+                          web_contents, base::DoNothing())
                           ->AsWeakPtr();
 
   EXPECT_TRUE(IsDialogShown());
@@ -177,11 +181,14 @@
                        AcceptDialogAndVerify) {
   auto* web_contents = browser()->tab_strip_model()->GetActiveWebContents();
 
+  base::MockCallback<base::OnceClosure> mock_callback;
   extensions::ExtensionId test_id(
       InstallExtensionForTesting(mock_app_manifest1, mock_url1));
   test_dialog_view_ = DeprecatedAppsDialogView::CreateAndShowDialog(
-                          deprecated_app_ids_for_testing_, web_contents)
+                          std::string(), deprecated_app_ids_for_testing_,
+                          web_contents, mock_callback.Get())
                           ->AsWeakPtr();
+  EXPECT_CALL(mock_callback, Run()).Times(0);
 
   // Verify dialog is shown.
   ASSERT_TRUE(IsDialogShown());
@@ -203,9 +210,11 @@
                        CloseDialogAndVerify) {
   auto* web_contents = browser()->tab_strip_model()->GetActiveWebContents();
 
+  base::MockCallback<base::OnceClosure> mock_callback;
   InstallExtensionForTesting(mock_app_manifest1, mock_url1);
   test_dialog_view_ = DeprecatedAppsDialogView::CreateAndShowDialog(
-                          deprecated_app_ids_for_testing_, web_contents)
+                          std::string(), deprecated_app_ids_for_testing_,
+                          web_contents, mock_callback.Get())
                           ->AsWeakPtr();
 
   // Verify dialog is shown.
@@ -213,6 +222,8 @@
   EXPECT_EQ(static_cast<int>(deprecated_app_ids_for_testing_.size()),
             GetRowCountForDialog());
 
+  EXPECT_CALL(mock_callback, Run()).Times(1);
+
   // Verify dialog is closed on cancellation
   ASSERT_TRUE(test_dialog_view_->Cancel());
   WaitForDialogToBeDestroyed();
diff --git a/chrome/browser/ui/views/web_apps/force_installed_deprecated_apps_dialog_view.cc b/chrome/browser/ui/views/web_apps/force_installed_deprecated_apps_dialog_view.cc
index 49868a3..d0b0236 100644
--- a/chrome/browser/ui/views/web_apps/force_installed_deprecated_apps_dialog_view.cc
+++ b/chrome/browser/ui/views/web_apps/force_installed_deprecated_apps_dialog_view.cc
@@ -13,6 +13,7 @@
 #include "extensions/browser/extension_registry.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
+#include "ui/base/ui_base_types.h"
 #include "ui/base/window_open_disposition.h"
 #include "ui/views/controls/link.h"
 #include "ui/views/controls/styled_label.h"
@@ -23,9 +24,10 @@
 // static
 void ForceInstalledDeprecatedAppsDialogView::CreateAndShowDialog(
     extensions::ExtensionId app_id,
-    content::WebContents* web_contents) {
+    content::WebContents* web_contents,
+    base::OnceClosure launch_anyways) {
   auto delegate = std::make_unique<views::DialogDelegate>();
-  delegate->SetButtons(ui::DIALOG_BUTTON_OK);
+  //   delegate->SetButtons(ui::DIALOG_BUTTON_OK);
   delegate->SetModalType(ui::MODAL_TYPE_CHILD);
   delegate->SetShowCloseButton(false);
   delegate->SetOwnedByWidget(true);
@@ -34,18 +36,16 @@
       extensions::ExtensionRegistry::Get(browser_context)
           ->GetInstalledExtension(app_id);
   std::u16string app_name = base::UTF8ToUTF16(extension->name());
-  bool is_preinstalled_app = extensions::IsPreinstalledAppId(app_id);
-  delegate->SetTitle(
-      is_preinstalled_app
-          ? l10n_util::GetStringFUTF16(
-                IDS_FORCE_INSTALLED_PREINSTALLED_DEPRECATED_APPS_TITLE,
-                app_name)
-          : l10n_util::GetPluralStringFUTF16(IDS_DEPRECATED_APPS_RENDERER_TITLE,
-                                             1));
+  delegate->SetTitle(l10n_util::GetStringFUTF16(
+      IDS_DEPRECATED_APPS_RENDERER_TITLE_WITH_APP_NAME, app_name));
+  delegate->SetButtonLabel(
+      ui::DIALOG_BUTTON_OK,
+      l10n_util::GetStringUTF16(IDS_DEPRECATED_APPS_LAUNCH_ANYWAY_LABEL));
+  delegate->SetAcceptCallback(std::move(launch_anyways));
+
   delegate->SetContentsView(
       base::WrapUnique<ForceInstalledDeprecatedAppsDialogView>(
-          new ForceInstalledDeprecatedAppsDialogView(
-              app_name, is_preinstalled_app, web_contents)));
+          new ForceInstalledDeprecatedAppsDialogView(app_name, web_contents)));
   delegate->set_fixed_width(views::LayoutProvider::Get()->GetDistanceMetric(
       views::DISTANCE_MODAL_DIALOG_PREFERRED_WIDTH));
   delegate->set_margins(
@@ -56,7 +56,6 @@
 
 ForceInstalledDeprecatedAppsDialogView::ForceInstalledDeprecatedAppsDialogView(
     std::u16string app_name,
-    bool is_preinstalled_app,
     content::WebContents* web_contents)
     : app_name_(app_name), web_contents_(web_contents) {
   SetLayoutManager(std::make_unique<views::BoxLayout>(
@@ -64,11 +63,7 @@
       ChromeLayoutProvider::Get()->GetDistanceMetric(
           views::DISTANCE_RELATED_CONTROL_VERTICAL)));
   auto* info_label = AddChildView(std::make_unique<views::Label>(
-      is_preinstalled_app
-          ? l10n_util::GetStringUTF16(
-                IDS_FORCE_INSTALLED_PREINSTALLED_DEPRECATED_APPS_CONTENT)
-          : l10n_util::GetStringFUTF16(
-                IDS_FORCE_INSTALLED_DEPRECATED_APPS_CONTENT, app_name_)));
+      l10n_util::GetStringUTF16(IDS_FORCE_INSTALLED_DEPRECATED_APPS_CONTENT)));
   info_label->SetMultiLine(true);
   info_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
 
@@ -84,8 +79,8 @@
             ui::PAGE_TRANSITION_LINK, /*is_renderer_initiated=*/false));
       },
       web_contents_));
-  learn_more->SetAccessibleName(l10n_util::GetStringUTF16(
-      IDS_FORCE_INSTALLED_DEPRECATED_APPS_LEARN_MORE_AX_LABEL));
+  learn_more->SetAccessibleName(
+      l10n_util::GetStringUTF16(IDS_DEPRECATED_APPS_LEARN_MORE_AX_LABEL));
   learn_more->SetHorizontalAlignment(gfx::ALIGN_LEFT);
 }
 
diff --git a/chrome/browser/ui/views/web_apps/force_installed_deprecated_apps_dialog_view.h b/chrome/browser/ui/views/web_apps/force_installed_deprecated_apps_dialog_view.h
index 2c32b644..21feadf8 100644
--- a/chrome/browser/ui/views/web_apps/force_installed_deprecated_apps_dialog_view.h
+++ b/chrome/browser/ui/views/web_apps/force_installed_deprecated_apps_dialog_view.h
@@ -21,11 +21,11 @@
 
   // Create the dialog metadata and show it.
   static void CreateAndShowDialog(extensions::ExtensionId app_id,
-                                  content::WebContents* web_contents);
+                                  content::WebContents* web_contents,
+                                  base::OnceClosure launch_anyways);
 
  private:
   ForceInstalledDeprecatedAppsDialogView(std::u16string app_name,
-                                         bool is_preinstalled_app,
                                          content::WebContents* web_contents);
 
   std::u16string app_name_;
diff --git a/chrome/browser/ui/views/web_apps/frame_toolbar/web_app_frame_toolbar_browsertest.cc b/chrome/browser/ui/views/web_apps/frame_toolbar/web_app_frame_toolbar_browsertest.cc
index 006d231..9038af7 100644
--- a/chrome/browser/ui/views/web_apps/frame_toolbar/web_app_frame_toolbar_browsertest.cc
+++ b/chrome/browser/ui/views/web_apps/frame_toolbar/web_app_frame_toolbar_browsertest.cc
@@ -260,6 +260,7 @@
   const SkColor original_ink_drop_color =
       views::InkDrop::Get(app_menu_button)->GetBaseColor();
 
+  // Change the theme-color.
   {
     content::ThemeChangeWaiter theme_change_waiter(web_contents);
     EXPECT_TRUE(content::ExecJs(web_contents,
@@ -271,10 +272,12 @@
               original_ink_drop_color);
   }
 
+  // Change the theme-color back to its original one.
   {
     content::ThemeChangeWaiter theme_change_waiter(web_contents);
-    EXPECT_TRUE(content::ExecJs(
-        web_contents, "document.getElementById('theme-color').remove()"));
+    EXPECT_TRUE(content::ExecJs(web_contents,
+                                "document.getElementById('theme-color')."
+                                "setAttribute('content', '#ace')"));
     theme_change_waiter.Wait();
 
     EXPECT_EQ(views::InkDrop::Get(app_menu_button)->GetBaseColor(),
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 f558f26d..2837c9b4 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
@@ -184,7 +184,8 @@
                          /*clear_cookies=*/true,
                          /*clear_storage=*/true, /*clear_cache=*/true,
                          /*avoid_closing_connections=*/false,
-                         net::CookiePartitionKey::Todo(), base::DoNothing());
+                         /*cookie_partition_key=*/absl::nullopt,
+                         base::DoNothing());
 }
 
 void WebAppUninstallDialogDelegateView::ProcessAutoConfirmValue() {
diff --git a/chrome/browser/ui/webui/about_ui.cc b/chrome/browser/ui/webui/about_ui.cc
index 8ba2d1b..f52125b 100644
--- a/chrome/browser/ui/webui/about_ui.cc
+++ b/chrome/browser/ui/webui/about_ui.cc
@@ -72,7 +72,7 @@
 #include "chrome/browser/ash/customization/customization_document.h"
 #include "chrome/browser/ash/login/demo_mode/demo_setup_controller.h"
 #include "chrome/browser/ash/login/wizard_controller.h"
-#include "chrome/browser/browser_process_platform_part_chromeos.h"
+#include "chrome/browser/browser_process_platform_part_ash.h"
 #include "chrome/browser/component_updater/cros_component_manager.h"
 #include "chrome/browser/ui/webui/chrome_web_ui_controller_factory.h"
 #include "chrome/common/webui_url_constants.h"
diff --git a/chrome/browser/ui/webui/chromeos/login/online_login_helper.cc b/chrome/browser/ui/webui/chromeos/login/online_login_helper.cc
index e96aa48..e37a58b3 100644
--- a/chrome/browser/ui/webui/chromeos/login/online_login_helper.cc
+++ b/chrome/browser/ui/webui/chromeos/login/online_login_helper.cc
@@ -74,7 +74,8 @@
   const GURL gaia_url = GaiaUrls::GetInstance()->gaia_url();
   std::unique_ptr<net::CanonicalCookie> cc(net::CanonicalCookie::Create(
       gaia_url, gaps_cookie_value, base::Time::Now(),
-      absl::nullopt /* server_time */, net::CookiePartitionKey::Todo()));
+      absl::nullopt /* server_time */,
+      absl::nullopt /* cookie_partition_key */));
   if (!cc)
     return;
 
diff --git a/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc b/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc
index 688de10f..bd17d21b 100644
--- a/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc
@@ -51,7 +51,7 @@
 #include "chrome/browser/ash/settings/cros_settings.h"
 #include "chrome/browser/ash/system/system_clock.h"
 #include "chrome/browser/browser_process.h"
-#include "chrome/browser/browser_process_platform_part_chromeos.h"
+#include "chrome/browser/browser_process_platform_part_ash.h"
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/lifetime/browser_shutdown.h"
 #include "chrome/browser/profiles/profile.h"
diff --git a/chrome/browser/ui/webui/ntp/app_launcher_handler.cc b/chrome/browser/ui/webui/ntp/app_launcher_handler.cc
index 965a676..cb95515 100644
--- a/chrome/browser/ui/webui/ntp/app_launcher_handler.cc
+++ b/chrome/browser/ui/webui/ntp/app_launcher_handler.cc
@@ -96,6 +96,7 @@
 #include "extensions/common/constants.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/extension_icon_set.h"
+#include "extensions/common/extension_id.h"
 #include "extensions/common/extension_set.h"
 #include "extensions/common/manifest_handlers/icons_handler.h"
 #include "net/base/url_util.h"
@@ -171,6 +172,8 @@
   return largest >= pixels;
 }
 
+// Query string for showing the deprecation dialog with deletion options.
+const char kDeprecationDialogQueryString[] = "showDeletionDialog";
 // Query string for showing the force installed apps deprecation dialog.
 // Should match with kChromeUIAppsWithForceInstalledDeprecationDialogURL.
 const char kForceInstallDialogQueryString[] = "showForceInstallDialog";
@@ -742,19 +745,33 @@
     install_manager_observation_.Observe(&web_app_provider_->install_manager());
 
     WebContents* web_contents = web_ui()->GetWebContents();
-    if (web_contents->GetLastCommittedURL() ==
-            GURL(chrome::kChromeUIAppsWithDeprecationDialogURL) &&
-        !deprecated_app_ids_.empty()) {
-      TabDialogs::FromWebContents(web_contents)
-          ->ShowDeprecatedAppsDialog(deprecated_app_ids_, web_contents);
-    }
     std::string app_id;
     if (net::GetValueForKeyInQuery(web_contents->GetLastCommittedURL(),
+                                   kDeprecationDialogQueryString, &app_id)) {
+      if (extensions::IsExtensionUnsupportedDeprecatedApp(profile, app_id) &&
+          !deprecated_app_ids_.empty()) {
+        TabDialogs::FromWebContents(web_contents)
+            ->ShowDeprecatedAppsDialog(
+                app_id, deprecated_app_ids_, web_contents,
+                base::BindOnce(
+                    &AppLauncherHandler::LaunchApp,
+                    weak_ptr_factory_.GetWeakPtr(), app_id,
+                    extension_misc::AppLaunchBucket::APP_LAUNCH_CMD_LINE_APP,
+                    "", WindowOpenDisposition::CURRENT_TAB, true));
+      }
+    }
+    if (net::GetValueForKeyInQuery(web_contents->GetLastCommittedURL(),
                                    kForceInstallDialogQueryString, &app_id)) {
       if (extensions::IsExtensionUnsupportedDeprecatedApp(profile, app_id) &&
           extensions::IsExtensionForceInstalled(profile, app_id, nullptr)) {
         TabDialogs::FromWebContents(web_contents)
-            ->ShowForceInstalledDeprecatedAppsDialog(app_id, web_contents);
+            ->ShowForceInstalledDeprecatedAppsDialog(
+                app_id, web_contents,
+                base::BindOnce(
+                    &AppLauncherHandler::LaunchApp,
+                    weak_ptr_factory_.GetWeakPtr(), app_id,
+                    extension_misc::AppLaunchBucket::APP_LAUNCH_CMD_LINE_APP,
+                    "", WindowOpenDisposition::CURRENT_TAB, true));
       }
     }
   }
@@ -764,27 +781,50 @@
 void AppLauncherHandler::HandleLaunchApp(const base::ListValue* args) {
   const std::string& extension_id = args->GetListDeprecated()[0].GetString();
   double source = args->GetListDeprecated()[1].GetDouble();
-  GURL override_url;
 
   extension_misc::AppLaunchBucket launch_bucket =
       static_cast<extension_misc::AppLaunchBucket>(static_cast<int>(source));
   CHECK(launch_bucket >= 0 &&
         launch_bucket < extension_misc::APP_LAUNCH_BUCKET_BOUNDARY);
 
+  WindowOpenDisposition disposition =
+      args->GetListDeprecated().size() > 3
+          ? webui::GetDispositionFromClick(args, 3)
+          : WindowOpenDisposition::CURRENT_TAB;
+  std::string source_value;
+  if (args->GetListDeprecated().size() > 2) {
+    source_value = args->GetListDeprecated()[2].GetString();
+  }
+  LaunchApp(extension_id, launch_bucket, source_value, disposition, false);
+}
+
+void AppLauncherHandler::LaunchApp(
+    std::string extension_id,
+    extension_misc::AppLaunchBucket launch_bucket,
+    const std::string& source_value,
+    WindowOpenDisposition disposition,
+    bool force_launch_deprecated_apps) {
   Profile* profile = extension_service_->profile();
 
-  if (extensions::IsExtensionUnsupportedDeprecatedApp(profile, extension_id) &&
+  if (!force_launch_deprecated_apps &&
+      extensions::IsExtensionUnsupportedDeprecatedApp(profile, extension_id) &&
       base::FeatureList::IsEnabled(features::kChromeAppsDeprecation)) {
     if (!extensions::IsExtensionForceInstalled(profile, extension_id,
                                                nullptr)) {
       TabDialogs::FromWebContents(web_ui()->GetWebContents())
-          ->ShowDeprecatedAppsDialog(deprecated_app_ids_,
-                                     web_ui()->GetWebContents());
+          ->ShowDeprecatedAppsDialog(
+              extension_id, deprecated_app_ids_, web_ui()->GetWebContents(),
+              base::BindOnce(&AppLauncherHandler::LaunchApp,
+                             weak_ptr_factory_.GetWeakPtr(), extension_id,
+                             launch_bucket, source_value, disposition, true));
       return;
     } else {
       TabDialogs::FromWebContents(web_ui()->GetWebContents())
-          ->ShowForceInstalledDeprecatedAppsDialog(extension_id,
-                                                   web_ui()->GetWebContents());
+          ->ShowForceInstalledDeprecatedAppsDialog(
+              extension_id, web_ui()->GetWebContents(),
+              base::BindOnce(&AppLauncherHandler::LaunchApp,
+                             weak_ptr_factory_.GetWeakPtr(), extension_id,
+                             launch_bucket, source_value, disposition, true));
       return;
     }
   }
@@ -816,24 +856,15 @@
         extensions::GetLaunchContainer(ExtensionPrefs::Get(profile), extension);
   }
 
-  WindowOpenDisposition disposition =
-      args->GetListDeprecated().size() > 3
-          ? webui::GetDispositionFromClick(args, 3)
-          : WindowOpenDisposition::CURRENT_TAB;
+  GURL override_url;
   if (extension_id != extensions::kWebStoreAppId) {
     CHECK_NE(launch_bucket, extension_misc::APP_LAUNCH_BUCKET_INVALID);
     extensions::RecordAppLaunchType(launch_bucket, type);
   } else {
     extensions::RecordWebStoreLaunch();
-
-    if (args->GetListDeprecated().size() > 2) {
-      const std::string& source_value =
-          args->GetListDeprecated()[2].GetString();
-      if (!source_value.empty()) {
-        override_url = net::AppendQueryParameter(
-            full_launch_url, extension_urls::kWebstoreSourceField,
-            source_value);
-      }
+    if (!source_value.empty()) {
+      override_url = net::AppendQueryParameter(
+          full_launch_url, extension_urls::kWebstoreSourceField, source_value);
     }
   }
 
@@ -1230,8 +1261,8 @@
 void AppLauncherHandler::HandleLaunchDeprecatedAppDialog(
     const base::ListValue* args) {
   TabDialogs::FromWebContents(web_ui()->GetWebContents())
-      ->ShowDeprecatedAppsDialog(deprecated_app_ids_,
-                                 web_ui()->GetWebContents());
+      ->ShowDeprecatedAppsDialog(extensions::ExtensionId(), deprecated_app_ids_,
+                                 web_ui()->GetWebContents(), base::DoNothing());
 }
 
 void AppLauncherHandler::OnFaviconForAppInstallFromLink(
diff --git a/chrome/browser/ui/webui/ntp/app_launcher_handler.h b/chrome/browser/ui/webui/ntp/app_launcher_handler.h
index cbb3bd66..df3a4969 100644
--- a/chrome/browser/ui/webui/ntp/app_launcher_handler.h
+++ b/chrome/browser/ui/webui/ntp/app_launcher_handler.h
@@ -137,6 +137,12 @@
   // CURRENT_TAB.
   void HandleLaunchApp(const base::ListValue* args);
 
+  void LaunchApp(std::string extension_id,
+                 extension_misc::AppLaunchBucket launch_bucket,
+                 const std::string& source_value,
+                 WindowOpenDisposition disposition,
+                 bool force_launch_deprecated_apps);
+
   // Handles the "setLaunchType" message with args containing [extension_id,
   // launch_type].
   void HandleSetLaunchType(const base::ListValue* args);
diff --git a/chrome/browser/ui/webui/policy/policy_ui_handler.cc b/chrome/browser/ui/webui/policy/policy_ui_handler.cc
index f75f18d8..f46e9d6 100644
--- a/chrome/browser/ui/webui/policy/policy_ui_handler.cc
+++ b/chrome/browser/ui/webui/policy/policy_ui_handler.cc
@@ -101,6 +101,7 @@
 #endif
 
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
+#include "chrome/browser/ui/webui/policy/status_provider/device_policy_status_provider_lacros.h"
 #include "chromeos/crosapi/mojom/policy_service.mojom.h"
 #include "chromeos/lacros/lacros_service.h"
 #include "components/policy/core/common/policy_loader_lacros.h"
@@ -531,9 +532,11 @@
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
 void PolicyUIHandler::OnGotDevicePolicy(base::Value device_policy,
                                         base::Value legend_data) {
-  // TODO(crbug.com/1243869): Parse also legend_data and use it.
   if (device_policy != device_policy_) {
     device_policy_ = std::move(device_policy);
+    static_cast<DevicePolicyStatusProviderLacros*>(
+        device_status_provider_.get())
+        ->SetDevicePolicyStatus(std::move(legend_data));
     SendPolicies();
   }
 }
@@ -663,6 +666,11 @@
   }
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+  device_status_provider_ =
+      std::make_unique<DevicePolicyStatusProviderLacros>();
+#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
+
 #if BUILDFLAG(IS_WIN) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
   ReloadUpdaterPoliciesAndState();
 #endif  // BUILDFLAG(IS_WIN) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
diff --git a/chrome/browser/ui/webui/policy/status_provider/device_cloud_policy_status_provider_chromeos.cc b/chrome/browser/ui/webui/policy/status_provider/device_cloud_policy_status_provider_chromeos.cc
index 9cdd72c..6d1ea57 100644
--- a/chrome/browser/ui/webui/policy/status_provider/device_cloud_policy_status_provider_chromeos.cc
+++ b/chrome/browser/ui/webui/policy/status_provider/device_cloud_policy_status_provider_chromeos.cc
@@ -12,7 +12,7 @@
 
 DeviceCloudPolicyStatusProviderChromeOS::
     DeviceCloudPolicyStatusProviderChromeOS(
-        policy::BrowserPolicyConnectorAsh* connector)
+        const policy::BrowserPolicyConnectorAsh* connector)
     : CloudPolicyCoreStatusProvider(
           connector->GetDeviceCloudPolicyManager()->core()) {
   enterprise_domain_manager_ = connector->GetEnterpriseDomainManager();
diff --git a/chrome/browser/ui/webui/policy/status_provider/device_cloud_policy_status_provider_chromeos.h b/chrome/browser/ui/webui/policy/status_provider/device_cloud_policy_status_provider_chromeos.h
index 53cfaae..dfbceaf 100644
--- a/chrome/browser/ui/webui/policy/status_provider/device_cloud_policy_status_provider_chromeos.h
+++ b/chrome/browser/ui/webui/policy/status_provider/device_cloud_policy_status_provider_chromeos.h
@@ -20,7 +20,7 @@
     : public CloudPolicyCoreStatusProvider {
  public:
   explicit DeviceCloudPolicyStatusProviderChromeOS(
-      policy::BrowserPolicyConnectorAsh* connector);
+      const policy::BrowserPolicyConnectorAsh* connector);
 
   DeviceCloudPolicyStatusProviderChromeOS(
       const DeviceCloudPolicyStatusProviderChromeOS&) = delete;
diff --git a/chrome/browser/ui/webui/policy/status_provider/device_policy_status_provider_lacros.cc b/chrome/browser/ui/webui/policy/status_provider/device_policy_status_provider_lacros.cc
new file mode 100644
index 0000000..e5e7b0a
--- /dev/null
+++ b/chrome/browser/ui/webui/policy/status_provider/device_policy_status_provider_lacros.cc
@@ -0,0 +1,24 @@
+// 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 "chrome/browser/ui/webui/policy/status_provider/device_policy_status_provider_lacros.h"
+
+DevicePolicyStatusProviderLacros::DevicePolicyStatusProviderLacros()
+    : PolicyStatusProvider() {}
+
+DevicePolicyStatusProviderLacros::~DevicePolicyStatusProviderLacros() {}
+
+void DevicePolicyStatusProviderLacros::SetDevicePolicyStatus(
+    base::Value status) {
+  device_policy_status_ = std::move(status);
+}
+
+void DevicePolicyStatusProviderLacros::GetStatus(base::DictionaryValue* dict) {
+  if (!device_policy_status_.is_dict()) {
+    return;
+  }
+  base::DictionaryValue* dict_value;
+  device_policy_status_.GetAsDictionary(&dict_value);
+  dict->Swap(dict_value);
+}
diff --git a/chrome/browser/ui/webui/policy/status_provider/device_policy_status_provider_lacros.h b/chrome/browser/ui/webui/policy/status_provider/device_policy_status_provider_lacros.h
new file mode 100644
index 0000000..b6f94e0c
--- /dev/null
+++ b/chrome/browser/ui/webui/policy/status_provider/device_policy_status_provider_lacros.h
@@ -0,0 +1,26 @@
+// 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 CHROME_BROWSER_UI_WEBUI_POLICY_STATUS_PROVIDER_DEVICE_POLICY_STATUS_PROVIDER_LACROS_H_
+#define CHROME_BROWSER_UI_WEBUI_POLICY_STATUS_PROVIDER_DEVICE_POLICY_STATUS_PROVIDER_LACROS_H_
+
+#include "base/values.h"
+#include "components/policy/core/browser/webui/policy_status_provider.h"
+
+// A policy status provider for device policy for Lacros.
+class DevicePolicyStatusProviderLacros : public policy::PolicyStatusProvider {
+ public:
+  DevicePolicyStatusProviderLacros();
+  ~DevicePolicyStatusProviderLacros() override;
+
+  void SetDevicePolicyStatus(base::Value status);
+
+  // PolicyStatusProvider implementation.
+  void GetStatus(base::DictionaryValue* dict) override;
+
+ private:
+  base::Value device_policy_status_;
+};
+
+#endif  // CHROME_BROWSER_UI_WEBUI_POLICY_STATUS_PROVIDER_DEVICE_POLICY_STATUS_PROVIDER_LACROS_H_
diff --git a/chrome/browser/user_agent/user_agent_browsertest.cc b/chrome/browser/user_agent/user_agent_browsertest.cc
index be6ef64e..c1df8bb5 100644
--- a/chrome/browser/user_agent/user_agent_browsertest.cc
+++ b/chrome/browser/user_agent/user_agent_browsertest.cc
@@ -24,7 +24,7 @@
 namespace policy {
 
 using ReductionPolicyState =
-    ChromeContentBrowserClient::UserAgentReductionEnterprisePolicyState;
+    embedder_support::UserAgentReductionEnterprisePolicyState;
 using ForceMajorVersionToMinorPolicyState =
     embedder_support::ForceMajorVersionToMinorPosition;
 
@@ -48,14 +48,15 @@
     InProcessBrowserTest::SetUp();
   }
 
-  void set_user_agent_reduction_policy(int policy) {
+  void set_user_agent_reduction_policy(ReductionPolicyState policy) {
     browser()->profile()->GetPrefs()->SetInteger(prefs::kUserAgentReduction,
-                                                 policy);
+                                                 static_cast<int>(policy));
   }
 
-  int user_agent_reduction_policy() {
-    return browser()->profile()->GetPrefs()->GetInteger(
-        prefs::kUserAgentReduction);
+  ReductionPolicyState user_agent_reduction_policy() {
+    return static_cast<ReductionPolicyState>(
+        browser()->profile()->GetPrefs()->GetInteger(
+            prefs::kUserAgentReduction));
   }
 
   void set_force_major_version_to_minor_policy(
@@ -90,7 +91,11 @@
 IN_PROC_BROWSER_TEST_P(UserAgentBrowserTest, ReductionPolicyDisabled) {
   set_user_agent_reduction_policy(ReductionPolicyState::kForceDisabled);
   ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), empty_url()));
-  EXPECT_EQ(observed_user_agent(), embedder_support::GetFullUserAgent());
+  EXPECT_EQ(observed_user_agent(),
+            embedder_support::GetFullUserAgent(
+                embedder_support::ForceMajorVersionToMinorPosition::kDefault,
+                embedder_support::UserAgentReductionEnterprisePolicyState::
+                    kForceDisabled));
 }
 
 IN_PROC_BROWSER_TEST_P(UserAgentBrowserTest, ReductionPolicyEnabled) {
diff --git a/chrome/browser/web_applications/app_service/web_app_publisher_helper.cc b/chrome/browser/web_applications/app_service/web_app_publisher_helper.cc
index 6ac2579..05ecad6a2 100644
--- a/chrome/browser/web_applications/app_service/web_app_publisher_helper.cc
+++ b/chrome/browser/web_applications/app_service/web_app_publisher_helper.cc
@@ -722,8 +722,8 @@
                              },
                              base::Unretained(profile())),
                          origin, kClearCookies, kClearStorage, kClearCache,
-                         kAvoidClosingConnections,
-                         net::CookiePartitionKey::Todo(), base::DoNothing());
+                         kAvoidClosingConnections, absl::nullopt,
+                         base::DoNothing());
 }
 
 apps::mojom::IconKeyPtr WebAppPublisherHelper::MakeIconKey(
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt
index b5905a2..d00b932 100644
--- a/chrome/build/mac-arm.pgo.txt
+++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@
-chrome-mac-arm-main-1654170771-aca3aa80be76cbddffc868b4086dbafc406545dc.profdata
+chrome-mac-arm-main-1654192625-5758f252c39321335b090c4593a40a74f10cfa06.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index 627093f..5842ef5 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1654170771-34e2a5d7745eff202372efe6c59ca9a8c73d838e.profdata
+chrome-win32-main-1654181823-fb3de41b167f5917c604f31a8354af7ebad78a71.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index 8102d05..0aa3ea0a 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1654170771-4af9924baf9e1e2e6cf772fa553a23067250fc7c.profdata
+chrome-win64-main-1654181823-28fbf3a336a3849dac7000fc271da3d17155285a.profdata
diff --git a/chrome/common/webui_url_constants.cc b/chrome/common/webui_url_constants.cc
index 6731b459..dad88d3 100644
--- a/chrome/common/webui_url_constants.cc
+++ b/chrome/common/webui_url_constants.cc
@@ -33,7 +33,7 @@
 const char kChromeUIAppLauncherPageHost[] = "apps";
 const char kChromeUIAppsURL[] = "chrome://apps/";
 const char kChromeUIAppsWithDeprecationDialogURL[] =
-    "chrome://apps?showDeletionDialog";
+    "chrome://apps?showDeletionDialog=";
 const char kChromeUIAppsWithForceInstalledDeprecationDialogURL[] =
     "chrome://apps?showForceInstallDialog=";
 const char kChromeUIAutofillInternalsHost[] = "autofill-internals";
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 9827e0f..785b8b8 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -3224,6 +3224,7 @@
         "../browser/ui/views/frame/system_web_app_non_client_frame_view_browsertest.cc",
         "../browser/ui/views/hung_renderer_view_browsertest.cc",
         "../browser/ui/views/importer/import_lock_dialog_view_browsertest.cc",
+        "../browser/ui/views/javascript_tab_modal_dialog_view_views_browsertest.cc",
         "../browser/ui/views/location_bar/content_setting_bubble_dialog_browsertest.cc",
         "../browser/ui/views/location_bar/cookie_controls_bubble_view_browsertest.cc",
         "../browser/ui/views/location_bar/custom_tab_bar_view_browsertest.cc",
diff --git a/chrome/test/base/browser_process_platform_part_test_api_chromeos.cc b/chrome/test/base/browser_process_platform_part_test_api_chromeos.cc
index d5b5e2b..58680b6e 100644
--- a/chrome/test/base/browser_process_platform_part_test_api_chromeos.cc
+++ b/chrome/test/base/browser_process_platform_part_test_api_chromeos.cc
@@ -6,7 +6,7 @@
 
 #include <utility>
 
-#include "chrome/browser/browser_process_platform_part_chromeos.h"
+#include "chrome/browser/browser_process_platform_part_ash.h"
 #include "chrome/browser/component_updater/cros_component_manager.h"
 
 BrowserProcessPlatformPartTestApi::BrowserProcessPlatformPartTestApi(
diff --git a/chrome/test/chromedriver/chrome/chrome_android_impl.cc b/chrome/test/chromedriver/chrome/chrome_android_impl.cc
index 49666b7..691615f 100644
--- a/chrome/test/chromedriver/chrome/chrome_android_impl.cc
+++ b/chrome/test/chromedriver/chrome/chrome_android_impl.cc
@@ -8,7 +8,6 @@
 
 #include "base/strings/string_split.h"
 #include "chrome/test/chromedriver/chrome/device_manager.h"
-#include "chrome/test/chromedriver/chrome/device_metrics.h"
 #include "chrome/test/chromedriver/chrome/devtools_client.h"
 #include "chrome/test/chromedriver/chrome/devtools_event_listener.h"
 #include "chrome/test/chromedriver/chrome/devtools_http_client.h"
@@ -20,15 +19,11 @@
     std::unique_ptr<DevToolsClient> websocket_client,
     std::vector<std::unique_ptr<DevToolsEventListener>>
         devtools_event_listeners,
-    std::unique_ptr<DeviceMetrics> device_metrics,
-    SyncWebSocketFactory socket_factory,
     std::string page_load_strategy,
     std::unique_ptr<Device> device)
     : ChromeImpl(std::move(http_client),
                  std::move(websocket_client),
                  std::move(devtools_event_listeners),
-                 std::move(device_metrics),
-                 std::move(socket_factory),
                  page_load_strategy),
       device_(std::move(device)) {}
 
diff --git a/chrome/test/chromedriver/chrome/chrome_android_impl.h b/chrome/test/chromedriver/chrome/chrome_android_impl.h
index 871dd1a..3621af23 100644
--- a/chrome/test/chromedriver/chrome/chrome_android_impl.h
+++ b/chrome/test/chromedriver/chrome/chrome_android_impl.h
@@ -12,7 +12,6 @@
 #include "chrome/test/chromedriver/chrome/chrome_impl.h"
 
 class Device;
-struct DeviceMetrics;
 class DevToolsClient;
 class DevToolsHttpClient;
 
@@ -22,8 +21,6 @@
                     std::unique_ptr<DevToolsClient> websocket_client,
                     std::vector<std::unique_ptr<DevToolsEventListener>>
                         devtools_event_listeners,
-                    std::unique_ptr<DeviceMetrics> device_metrics,
-                    SyncWebSocketFactory socket_factory,
                     std::string page_load_strategy,
                     std::unique_ptr<Device> device);
   ~ChromeAndroidImpl() override;
diff --git a/chrome/test/chromedriver/chrome/chrome_desktop_impl.cc b/chrome/test/chromedriver/chrome/chrome_desktop_impl.cc
index c623f36..a0dba51 100644
--- a/chrome/test/chromedriver/chrome/chrome_desktop_impl.cc
+++ b/chrome/test/chromedriver/chrome/chrome_desktop_impl.cc
@@ -17,7 +17,6 @@
 #include "base/threading/platform_thread.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
-#include "chrome/test/chromedriver/chrome/device_metrics.h"
 #include "chrome/test/chromedriver/chrome/devtools_client.h"
 #include "chrome/test/chromedriver/chrome/devtools_event_listener.h"
 #include "chrome/test/chromedriver/chrome/devtools_http_client.h"
@@ -76,8 +75,6 @@
     std::unique_ptr<DevToolsClient> websocket_client,
     std::vector<std::unique_ptr<DevToolsEventListener>>
         devtools_event_listeners,
-    std::unique_ptr<DeviceMetrics> device_metrics,
-    SyncWebSocketFactory socket_factory,
     std::string page_load_strategy,
     base::Process process,
     const base::CommandLine& command,
@@ -87,8 +84,6 @@
     : ChromeImpl(std::move(http_client),
                  std::move(websocket_client),
                  std::move(devtools_event_listeners),
-                 std::move(device_metrics),
-                 std::move(socket_factory),
                  page_load_strategy),
       process_(std::move(process)),
       command_(command),
@@ -146,7 +141,7 @@
   if (id.empty())
     return Status(kUnknownError, "page could not be found: " + url);
 
-  const DeviceMetrics* device_metrics = device_metrics_.get();
+  const DeviceMetrics* device_metrics = devtools_http_client_->device_metrics();
   if (type == WebViewInfo::Type::kApp ||
       type == WebViewInfo::Type::kBackgroundPage) {
     // Apps and extensions don't work on Android, so it doesn't make sense to
@@ -157,7 +152,8 @@
   }
   std::unique_ptr<WebView> web_view_tmp(new WebViewImpl(
       id, w3c_compliant, nullptr, devtools_http_client_->browser_info(),
-      CreateClient(id), device_metrics, page_load_strategy()));
+      devtools_http_client_->CreateClient(id), device_metrics,
+      page_load_strategy()));
   Status status = web_view_tmp->ConnectIfNecessary();
   if (status.IsError())
     return status;
@@ -179,7 +175,7 @@
 }
 
 bool ChromeDesktopImpl::IsMobileEmulationEnabled() const {
-  return static_cast<bool>(device_metrics_);
+  return devtools_http_client_->device_metrics() != NULL;
 }
 
 bool ChromeDesktopImpl::HasTouchScreen() const {
diff --git a/chrome/test/chromedriver/chrome/chrome_desktop_impl.h b/chrome/test/chromedriver/chrome/chrome_desktop_impl.h
index 1fc4c14..e03f2070 100644
--- a/chrome/test/chromedriver/chrome/chrome_desktop_impl.h
+++ b/chrome/test/chromedriver/chrome/chrome_desktop_impl.h
@@ -13,7 +13,6 @@
 #include "base/process/process.h"
 #include "chrome/test/chromedriver/chrome/chrome_impl.h"
 #include "chrome/test/chromedriver/chrome/scoped_temp_dir_with_retry.h"
-#include "chrome/test/chromedriver/net/sync_websocket_factory.h"
 
 namespace base {
 class TimeDelta;
@@ -23,7 +22,6 @@
 class DevToolsHttpClient;
 class Status;
 class WebView;
-struct DeviceMetrics;
 
 class ChromeDesktopImpl : public ChromeImpl {
  public:
@@ -31,8 +29,6 @@
                     std::unique_ptr<DevToolsClient> websocket_client,
                     std::vector<std::unique_ptr<DevToolsEventListener>>
                         devtools_event_listeners,
-                    std::unique_ptr<DeviceMetrics> device_metrics,
-                    SyncWebSocketFactory socket_factory,
                     std::string page_load_strategy,
                     base::Process process,
                     const base::CommandLine& command,
diff --git a/chrome/test/chromedriver/chrome/chrome_impl.cc b/chrome/test/chromedriver/chrome/chrome_impl.cc
index 47bf735b..7c0ef44 100644
--- a/chrome/test/chromedriver/chrome/chrome_impl.cc
+++ b/chrome/test/chromedriver/chrome/chrome_impl.cc
@@ -5,19 +5,13 @@
 #include "chrome/test/chromedriver/chrome/chrome_impl.h"
 
 #include <stddef.h>
-#include <algorithm>
 #include <utility>
 
-#include "base/bind.h"
-#include "base/logging.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/threading/platform_thread.h"
 #include "base/time/time.h"
 #include "base/values.h"
-#include "chrome/test/chromedriver/chrome/chrome.h"
-#include "chrome/test/chromedriver/chrome/device_metrics.h"
 #include "chrome/test/chromedriver/chrome/devtools_client.h"
-#include "chrome/test/chromedriver/chrome/devtools_client_impl.h"
 #include "chrome/test/chromedriver/chrome/devtools_event_listener.h"
 #include "chrome/test/chromedriver/chrome/devtools_http_client.h"
 #include "chrome/test/chromedriver/chrome/page_load_strategy.h"
@@ -111,7 +105,8 @@
         }
       }
       if (!found) {
-        std::unique_ptr<DevToolsClient> client = CreateClient(view.id);
+        std::unique_ptr<DevToolsClient> client(
+            devtools_http_client_->CreateClient(view.id));
         for (const auto& listener : devtools_event_listeners_)
           client->AddListener(listener.get());
         // OnConnected will fire when DevToolsClient connects later.
@@ -124,7 +119,7 @@
           web_views_.push_back(std::make_unique<WebViewImpl>(
               view.id, w3c_compliant, nullptr,
               devtools_http_client_->browser_info(), std::move(client),
-              device_metrics_.get(), page_load_strategy_));
+              devtools_http_client_->device_metrics(), page_load_strategy_));
         }
       }
     }
@@ -171,89 +166,6 @@
   return Status(kOk);
 }
 
-std::unique_ptr<DevToolsClient> ChromeImpl::CreateClient(
-    const std::string& id) {
-  auto result = std::make_unique<DevToolsClientImpl>(
-      id, "", devtools_http_client_->endpoint().GetDebuggerUrl(id),
-      socket_factory_);
-  result->SetFrontendCloserFunc(base::BindRepeating(
-      &ChromeImpl::CloseFrontends, base::Unretained(this), id));
-  return result;
-}
-
-Status ChromeImpl::CloseFrontends(const std::string& for_client_id) {
-  WebViewsInfo views_info;
-  Status status = devtools_http_client_->GetWebViewsInfo(&views_info);
-  if (status.IsError())
-    return status;
-
-  // Close frontends. Usually frontends are docked in the same page, although
-  // some may be in tabs (undocked, chrome://inspect, the DevTools
-  // discovery page, etc.). Tabs can be closed via the DevTools HTTP close
-  // URL, but docked frontends can only be closed, by design, by connecting
-  // to them and clicking the close button. Close the tab frontends first
-  // in case one of them is debugging a docked frontend, which would prevent
-  // the code from being able to connect to the docked one.
-  std::list<std::string> tab_frontend_ids;
-  std::list<std::string> docked_frontend_ids;
-  for (size_t i = 0; i < views_info.GetSize(); ++i) {
-    const WebViewInfo& view_info = views_info.Get(i);
-    if (view_info.IsFrontend()) {
-      if (view_info.type == WebViewInfo::kPage)
-        tab_frontend_ids.push_back(view_info.id);
-      else if (view_info.type == WebViewInfo::kOther)
-        docked_frontend_ids.push_back(view_info.id);
-      else
-        return Status(kUnknownError, "unknown type of DevTools frontend");
-    }
-  }
-
-  for (std::list<std::string>::const_iterator it = tab_frontend_ids.begin();
-       it != tab_frontend_ids.end(); ++it) {
-    status = CloseWebView(*it);
-    if (status.IsError())
-      return status;
-  }
-
-  for (std::list<std::string>::const_iterator it = docked_frontend_ids.begin();
-       it != docked_frontend_ids.end(); ++it) {
-    std::unique_ptr<DevToolsClient> client(new DevToolsClientImpl(
-        *it, "", devtools_http_client_->endpoint().GetDebuggerUrl(*it),
-        socket_factory_));
-    std::unique_ptr<WebViewImpl> web_view(new WebViewImpl(
-        *it, false, nullptr, devtools_http_client_->browser_info(),
-        std::move(client), nullptr, page_load_strategy_));
-
-    status = web_view->ConnectIfNecessary();
-    // Ignore disconnected error, because the debugger might have closed when
-    // its container page was closed above.
-    if (status.IsError() && status.code() != kDisconnected)
-      return status;
-
-    status = CloseWebView(*it);
-    // Ignore disconnected error, because it may be closed already.
-    if (status.IsError() && status.code() != kDisconnected)
-      return status;
-  }
-
-  // Wait until DevTools UI disconnects from the given web view.
-  base::TimeTicks deadline = base::TimeTicks::Now() + base::Seconds(20);
-  while (base::TimeTicks::Now() < deadline) {
-    status = devtools_http_client_->GetWebViewsInfo(&views_info);
-    if (status.IsError())
-      return status;
-
-    const WebViewInfo* view_info = views_info.GetForId(for_client_id);
-    if (!view_info)
-      return Status(kNoSuchWindow, "window was already closed");
-    if (view_info->debugger_url.size())
-      return Status(kOk);
-
-    base::PlatformThread::Sleep(base::Milliseconds(50));
-  }
-  return Status(kUnknownError, "failed to close UI debuggers");
-}
-
 Status ChromeImpl::GetWindow(const std::string& target_id, Window* window) {
   Status status = devtools_websocket_client_->ConnectIfNecessary();
   if (status.IsError())
@@ -558,26 +470,15 @@
 }
 
 Status ChromeImpl::CloseWebView(const std::string& id) {
-  Status status = devtools_websocket_client_->ConnectIfNecessary();
-  if (status.IsError()) {
-    return status;
-  }
-
-  base::Value params{base::Value::Type::DICT};
-  params.GetDict().Set("targetId", id);
-  status = devtools_websocket_client_->SendCommand(
-      "Target.closeTarget", base::Value::AsDictionaryValue(params));
-
+  Status status = devtools_http_client_->CloseWebView(id);
   if (status.IsError())
     return status;
-
-  auto it =
-      std::find_if(web_views_.begin(), web_views_.end(),
-                   [&id](const auto& view) { return view->GetId() == id; });
-  if (it != web_views_.end()) {
-    web_views_.erase(it);
+  for (auto iter = web_views_.begin(); iter != web_views_.end(); ++iter) {
+    if ((*iter)->GetId() == id) {
+      web_views_.erase(iter);
+      break;
+    }
   }
-
   return Status(kOk);
 }
 
@@ -586,17 +487,7 @@
   GetWebViewById(id, &webview);
   if (webview && webview->IsServiceWorker())
     return Status(kOk);
-
-  Status status = devtools_websocket_client_->ConnectIfNecessary();
-  if (status.IsError()) {
-    return status;
-  }
-
-  base::Value params{base::Value::Type::DICT};
-  params.GetDict().Set("targetId", id);
-  status = devtools_websocket_client_->SendCommand(
-      "Target.activateTarget", base::Value::AsDictionaryValue(params));
-  return status;
+  return devtools_http_client_->ActivateWebView(id);
 }
 
 Status ChromeImpl::SetAcceptInsecureCerts() {
@@ -666,11 +557,8 @@
                        std::unique_ptr<DevToolsClient> websocket_client,
                        std::vector<std::unique_ptr<DevToolsEventListener>>
                            devtools_event_listeners,
-                       std::unique_ptr<DeviceMetrics> device_metrics,
-                       SyncWebSocketFactory socket_factory,
                        std::string page_load_strategy)
-    : device_metrics_(std::move(device_metrics)),
-      socket_factory_(std::move(socket_factory)),
+    : quit_(false),
       devtools_http_client_(std::move(http_client)),
       devtools_websocket_client_(std::move(websocket_client)),
       devtools_event_listeners_(std::move(devtools_event_listeners)),
diff --git a/chrome/test/chromedriver/chrome/chrome_impl.h b/chrome/test/chromedriver/chrome/chrome_impl.h
index 1aa4957a..834d3255 100644
--- a/chrome/test/chromedriver/chrome/chrome_impl.h
+++ b/chrome/test/chromedriver/chrome/chrome_impl.h
@@ -12,8 +12,8 @@
 
 #include "base/values.h"
 #include "chrome/test/chromedriver/chrome/chrome.h"
-#include "chrome/test/chromedriver/net/sync_websocket_factory.h"
 
+struct BrowserInfo;
 class DevToolsClient;
 class DevToolsEventListener;
 class DevToolsHttpClient;
@@ -21,8 +21,6 @@
 class WebView;
 class WebViewImpl;
 class WebViewsInfo;
-struct BrowserInfo;
-struct DeviceMetrics;
 
 class ChromeImpl : public Chrome {
  public:
@@ -63,15 +61,10 @@
              std::unique_ptr<DevToolsClient> websocket_client,
              std::vector<std::unique_ptr<DevToolsEventListener>>
                  devtools_event_listeners,
-             std::unique_ptr<DeviceMetrics> device_metrics,
-             SyncWebSocketFactory socket_factory,
              std::string page_load_strategy);
 
   virtual Status QuitImpl() = 0;
 
-  std::unique_ptr<DevToolsClient> CreateClient(const std::string& id);
-  Status CloseFrontends(const std::string& for_client_id);
-
   struct Window {
     int id;
     std::string state;
@@ -88,9 +81,7 @@
                          const std::string& target_id,
                          std::unique_ptr<base::DictionaryValue> bounds);
 
-  bool quit_ = false;
-  std::unique_ptr<DeviceMetrics> device_metrics_;
-  SyncWebSocketFactory socket_factory_;
+  bool quit_;
   std::unique_ptr<DevToolsHttpClient> devtools_http_client_;
   std::unique_ptr<DevToolsClient> devtools_websocket_client_;
 
diff --git a/chrome/test/chromedriver/chrome/chrome_remote_impl.cc b/chrome/test/chromedriver/chrome/chrome_remote_impl.cc
index 4154d9e..9c4547b 100644
--- a/chrome/test/chromedriver/chrome/chrome_remote_impl.cc
+++ b/chrome/test/chromedriver/chrome/chrome_remote_impl.cc
@@ -6,7 +6,6 @@
 
 #include <utility>
 
-#include "chrome/test/chromedriver/chrome/device_metrics.h"
 #include "chrome/test/chromedriver/chrome/devtools_client.h"
 #include "chrome/test/chromedriver/chrome/devtools_event_listener.h"
 #include "chrome/test/chromedriver/chrome/devtools_http_client.h"
@@ -17,14 +16,10 @@
     std::unique_ptr<DevToolsClient> websocket_client,
     std::vector<std::unique_ptr<DevToolsEventListener>>
         devtools_event_listeners,
-    std::unique_ptr<DeviceMetrics> device_metrics,
-    SyncWebSocketFactory socket_factory,
     std::string page_load_strategy)
     : ChromeImpl(std::move(http_client),
                  std::move(websocket_client),
                  std::move(devtools_event_listeners),
-                 std::move(device_metrics),
-                 std::move(socket_factory),
                  page_load_strategy) {}
 
 ChromeRemoteImpl::~ChromeRemoteImpl() {}
diff --git a/chrome/test/chromedriver/chrome/chrome_remote_impl.h b/chrome/test/chromedriver/chrome/chrome_remote_impl.h
index 1c081a0..5abbe9d 100644
--- a/chrome/test/chromedriver/chrome/chrome_remote_impl.h
+++ b/chrome/test/chromedriver/chrome/chrome_remote_impl.h
@@ -9,11 +9,9 @@
 #include <string>
 
 #include "chrome/test/chromedriver/chrome/chrome_impl.h"
-#include "chrome/test/chromedriver/net/sync_websocket_factory.h"
 
 class DevToolsClient;
 class DevToolsHttpClient;
-struct DeviceMetrics;
 
 class ChromeRemoteImpl : public ChromeImpl {
  public:
@@ -21,8 +19,6 @@
                    std::unique_ptr<DevToolsClient> websocket_client,
                    std::vector<std::unique_ptr<DevToolsEventListener>>
                        devtools_event_listeners,
-                   std::unique_ptr<DeviceMetrics> device_metrics,
-                   SyncWebSocketFactory socket_factory,
                    std::string page_load_strategy);
   ~ChromeRemoteImpl() override;
 
diff --git a/chrome/test/chromedriver/chrome/devtools_endpoint.cc b/chrome/test/chromedriver/chrome/devtools_endpoint.cc
index 85de9db..9bba6b5 100644
--- a/chrome/test/chromedriver/chrome/devtools_endpoint.cc
+++ b/chrome/test/chromedriver/chrome/devtools_endpoint.cc
@@ -52,3 +52,7 @@
 std::string DevToolsEndpoint::GetCloseUrl(const std::string& id) const {
   return server_url_.Resolve("json/close/" + id).spec();
 }
+
+std::string DevToolsEndpoint::GetActivateUrl(const std::string& id) const {
+  return server_url_.Resolve("json/activate/" + id).spec();
+}
diff --git a/chrome/test/chromedriver/chrome/devtools_endpoint.h b/chrome/test/chromedriver/chrome/devtools_endpoint.h
index a446abd..702447b7 100644
--- a/chrome/test/chromedriver/chrome/devtools_endpoint.h
+++ b/chrome/test/chromedriver/chrome/devtools_endpoint.h
@@ -26,6 +26,7 @@
   std::string GetVersionUrl() const;
   std::string GetListUrl() const;
   std::string GetCloseUrl(const std::string& id) const;
+  std::string GetActivateUrl(const std::string& id) const;
 
  private:
   GURL server_url_;
diff --git a/chrome/test/chromedriver/chrome/devtools_endpoint_unittest.cc b/chrome/test/chromedriver/chrome/devtools_endpoint_unittest.cc
index 93d366b..b138095 100644
--- a/chrome/test/chromedriver/chrome/devtools_endpoint_unittest.cc
+++ b/chrome/test/chromedriver/chrome/devtools_endpoint_unittest.cc
@@ -24,6 +24,8 @@
   ASSERT_EQ(endpoint.GetListUrl(), "http://localhost:9999/json/list");
   ASSERT_EQ(endpoint.GetCloseUrl("xyz"),
             "http://localhost:9999/json/close/xyz");
+  ASSERT_EQ(endpoint.GetActivateUrl("xyz"),
+            "http://localhost:9999/json/activate/xyz");
 }
 
 TEST(DevToolsEndpoint, FromNetAddress) {
@@ -38,6 +40,8 @@
   ASSERT_EQ(endpoint.GetListUrl(), "http://localhost:9222/json/list");
   ASSERT_EQ(endpoint.GetCloseUrl("xyz"),
             "http://localhost:9222/json/close/xyz");
+  ASSERT_EQ(endpoint.GetActivateUrl("xyz"),
+            "http://localhost:9222/json/activate/xyz");
 }
 
 TEST(DevToolsEndpoint, FromHttpUrl) {
@@ -54,6 +58,8 @@
   ASSERT_EQ(endpoint.GetListUrl(), "http://remote:9223/custom/path/json/list");
   ASSERT_EQ(endpoint.GetCloseUrl("xyz"),
             "http://remote:9223/custom/path/json/close/xyz");
+  ASSERT_EQ(endpoint.GetActivateUrl("xyz"),
+            "http://remote:9223/custom/path/json/activate/xyz");
 }
 
 TEST(DevToolsEndpoint, FromHttpsUrl) {
@@ -70,4 +76,6 @@
   ASSERT_EQ(endpoint.GetListUrl(), "https://secure:9224/custom/path/json/list");
   ASSERT_EQ(endpoint.GetCloseUrl("xyz"),
             "https://secure:9224/custom/path/json/close/xyz");
+  ASSERT_EQ(endpoint.GetActivateUrl("xyz"),
+            "https://secure:9224/custom/path/json/activate/xyz");
 }
diff --git a/chrome/test/chromedriver/chrome/devtools_http_client.cc b/chrome/test/chromedriver/chrome/devtools_http_client.cc
index 439425b..45e5a95 100644
--- a/chrome/test/chromedriver/chrome/devtools_http_client.cc
+++ b/chrome/test/chromedriver/chrome/devtools_http_client.cc
@@ -15,9 +15,11 @@
 #include "base/threading/platform_thread.h"
 #include "base/time/time.h"
 #include "base/values.h"
+#include "chrome/test/chromedriver/chrome/device_metrics.h"
 #include "chrome/test/chromedriver/chrome/devtools_client_impl.h"
 #include "chrome/test/chromedriver/chrome/log.h"
 #include "chrome/test/chromedriver/chrome/status.h"
+#include "chrome/test/chromedriver/chrome/web_view_impl.h"
 #include "chrome/test/chromedriver/net/net_util.h"
 #include "services/network/public/mojom/url_loader_factory.mojom.h"
 
@@ -65,10 +67,16 @@
 DevToolsHttpClient::DevToolsHttpClient(
     const DevToolsEndpoint& endpoint,
     network::mojom::URLLoaderFactory* factory,
-    std::unique_ptr<std::set<WebViewInfo::Type>> window_types)
+    const SyncWebSocketFactory& socket_factory,
+    std::unique_ptr<DeviceMetrics> device_metrics,
+    std::unique_ptr<std::set<WebViewInfo::Type>> window_types,
+    std::string page_load_strategy)
     : url_loader_factory_(factory),
+      socket_factory_(socket_factory),
       endpoint_(endpoint),
-      window_types_(std::move(window_types)) {
+      device_metrics_(std::move(device_metrics)),
+      window_types_(std::move(window_types)),
+      page_load_strategy_(page_load_strategy) {
   window_types_->insert(WebViewInfo::kPage);
   window_types_->insert(WebViewInfo::kApp);
 }
@@ -103,17 +111,127 @@
   return internal::ParseWebViewsInfo(data, views_info);
 }
 
+std::unique_ptr<DevToolsClient> DevToolsHttpClient::CreateClient(
+    const std::string& id) {
+  auto result = std::make_unique<DevToolsClientImpl>(
+      id, "", endpoint_.GetDebuggerUrl(id), socket_factory_);
+  result->SetFrontendCloserFunc(base::BindRepeating(
+      &DevToolsHttpClient::CloseFrontends, base::Unretained(this), id));
+  return result;
+}
+
+Status DevToolsHttpClient::CloseWebView(const std::string& id) {
+  std::string data;
+  if (!FetchUrlAndLog(endpoint_.GetCloseUrl(id), &data)) {
+    return Status(kOk);  // Closing the last web view leads chrome to quit.
+  }
+
+  // Wait for the target window to be completely closed.
+  base::TimeTicks deadline = base::TimeTicks::Now() + base::Seconds(20);
+  while (base::TimeTicks::Now() < deadline) {
+    WebViewsInfo views_info;
+    Status status = GetWebViewsInfo(&views_info);
+    if (status.code() == kChromeNotReachable)
+      return Status(kOk);
+    if (status.IsError())
+      return status;
+    if (!views_info.GetForId(id))
+      return Status(kOk);
+    base::PlatformThread::Sleep(base::Milliseconds(50));
+  }
+  return Status(kUnknownError, "failed to close window in 20 seconds");
+}
+
+Status DevToolsHttpClient::ActivateWebView(const std::string& id) {
+  std::string data;
+  if (!FetchUrlAndLog(endpoint_.GetActivateUrl(id), &data))
+    return Status(kUnknownError, "cannot activate web view");
+  return Status(kOk);
+}
+
 const BrowserInfo* DevToolsHttpClient::browser_info() {
   return &browser_info_;
 }
 
+const DeviceMetrics* DevToolsHttpClient::device_metrics() {
+  return device_metrics_.get();
+}
+
 bool DevToolsHttpClient::IsBrowserWindow(const WebViewInfo& view) const {
   return base::Contains(*window_types_, view.type) ||
          (view.type == WebViewInfo::kOther && view.url == "chrome://print/");
 }
 
-const DevToolsEndpoint& DevToolsHttpClient::endpoint() const {
-  return endpoint_;
+Status DevToolsHttpClient::CloseFrontends(const std::string& for_client_id) {
+  WebViewsInfo views_info;
+  Status status = GetWebViewsInfo(&views_info);
+  if (status.IsError())
+    return status;
+
+  // Close frontends. Usually frontends are docked in the same page, although
+  // some may be in tabs (undocked, chrome://inspect, the DevTools
+  // discovery page, etc.). Tabs can be closed via the DevTools HTTP close
+  // URL, but docked frontends can only be closed, by design, by connecting
+  // to them and clicking the close button. Close the tab frontends first
+  // in case one of them is debugging a docked frontend, which would prevent
+  // the code from being able to connect to the docked one.
+  std::list<std::string> tab_frontend_ids;
+  std::list<std::string> docked_frontend_ids;
+  for (size_t i = 0; i < views_info.GetSize(); ++i) {
+    const WebViewInfo& view_info = views_info.Get(i);
+    if (view_info.IsFrontend()) {
+      if (view_info.type == WebViewInfo::kPage)
+        tab_frontend_ids.push_back(view_info.id);
+      else if (view_info.type == WebViewInfo::kOther)
+        docked_frontend_ids.push_back(view_info.id);
+      else
+        return Status(kUnknownError, "unknown type of DevTools frontend");
+    }
+  }
+
+  for (std::list<std::string>::const_iterator it = tab_frontend_ids.begin();
+       it != tab_frontend_ids.end(); ++it) {
+    status = CloseWebView(*it);
+    if (status.IsError())
+      return status;
+  }
+
+  for (std::list<std::string>::const_iterator it = docked_frontend_ids.begin();
+       it != docked_frontend_ids.end(); ++it) {
+    std::unique_ptr<DevToolsClient> client(new DevToolsClientImpl(
+        *it, "", endpoint_.GetDebuggerUrl(*it), socket_factory_));
+    std::unique_ptr<WebViewImpl> web_view(
+        new WebViewImpl(*it, false, nullptr, &browser_info_, std::move(client),
+                        nullptr, page_load_strategy_));
+
+    status = web_view->ConnectIfNecessary();
+    // Ignore disconnected error, because the debugger might have closed when
+    // its container page was closed above.
+    if (status.IsError() && status.code() != kDisconnected)
+      return status;
+
+    status = CloseWebView(*it);
+    // Ignore disconnected error, because it may be closed already.
+    if (status.IsError() && status.code() != kDisconnected)
+      return status;
+  }
+
+  // Wait until DevTools UI disconnects from the given web view.
+  base::TimeTicks deadline = base::TimeTicks::Now() + base::Seconds(20);
+  while (base::TimeTicks::Now() < deadline) {
+    status = GetWebViewsInfo(&views_info);
+    if (status.IsError())
+      return status;
+
+    const WebViewInfo* view_info = views_info.GetForId(for_client_id);
+    if (!view_info)
+      return Status(kNoSuchWindow, "window was already closed");
+    if (view_info->debugger_url.size())
+      return Status(kOk);
+
+    base::PlatformThread::Sleep(base::Milliseconds(50));
+  }
+  return Status(kUnknownError, "failed to close UI debuggers");
 }
 
 bool DevToolsHttpClient::FetchUrlAndLog(const std::string& url,
diff --git a/chrome/test/chromedriver/chrome/devtools_http_client.h b/chrome/test/chromedriver/chrome/devtools_http_client.h
index 11515d3..7b74380 100644
--- a/chrome/test/chromedriver/chrome/devtools_http_client.h
+++ b/chrome/test/chromedriver/chrome/devtools_http_client.h
@@ -16,6 +16,7 @@
 #include "base/memory/ref_counted.h"
 #include "chrome/test/chromedriver/chrome/browser_info.h"
 #include "chrome/test/chromedriver/chrome/devtools_endpoint.h"
+#include "chrome/test/chromedriver/net/sync_websocket_factory.h"
 
 namespace base {
 class TimeDelta;
@@ -27,6 +28,8 @@
 }
 }  // namespace network
 
+struct DeviceMetrics;
+class DevToolsClient;
 class Status;
 
 struct WebViewInfo {
@@ -78,7 +81,10 @@
  public:
   DevToolsHttpClient(const DevToolsEndpoint& endpoint,
                      network::mojom::URLLoaderFactory* factory,
-                     std::unique_ptr<std::set<WebViewInfo::Type>> window_types);
+                     const SyncWebSocketFactory& socket_factory,
+                     std::unique_ptr<DeviceMetrics> device_metrics,
+                     std::unique_ptr<std::set<WebViewInfo::Type>> window_types,
+                     std::string page_load_strategy);
 
   DevToolsHttpClient(const DevToolsHttpClient&) = delete;
   DevToolsHttpClient& operator=(const DevToolsHttpClient&) = delete;
@@ -89,17 +95,27 @@
 
   Status GetWebViewsInfo(WebViewsInfo* views_info);
 
+  std::unique_ptr<DevToolsClient> CreateClient(const std::string& id);
+
+  Status CloseWebView(const std::string& id);
+
+  Status ActivateWebView(const std::string& id);
+
   const BrowserInfo* browser_info();
+  const DeviceMetrics* device_metrics();
   bool IsBrowserWindow(const WebViewInfo& view) const;
-  const DevToolsEndpoint& endpoint() const;
 
  private:
+  Status CloseFrontends(const std::string& for_client_id);
   virtual bool FetchUrlAndLog(const std::string& url, std::string* response);
 
   raw_ptr<network::mojom::URLLoaderFactory> url_loader_factory_;
+  SyncWebSocketFactory socket_factory_;
   DevToolsEndpoint endpoint_;
   BrowserInfo browser_info_;
+  std::unique_ptr<DeviceMetrics> device_metrics_;
   std::unique_ptr<std::set<WebViewInfo::Type>> window_types_;
+  std::string page_load_strategy_;
 };
 
 Status ParseType(const std::string& data, WebViewInfo::Type* type);
diff --git a/chrome/test/chromedriver/chrome_launcher.cc b/chrome/test/chromedriver/chrome_launcher.cc
index d29a628..756410a 100644
--- a/chrome/test/chromedriver/chrome_launcher.cc
+++ b/chrome/test/chromedriver/chrome_launcher.cc
@@ -234,12 +234,19 @@
 Status WaitForDevToolsAndCheckVersion(
     const DevToolsEndpoint& endpoint,
     network::mojom::URLLoaderFactory* factory,
+    const SyncWebSocketFactory& socket_factory,
     const Capabilities* capabilities,
     int wait_time,
     std::unique_ptr<DevToolsHttpClient>* user_client,
     bool* retry,
     ChromeType ct,
     std::string fp = "") {
+  std::unique_ptr<DeviceMetrics> device_metrics;
+  if (capabilities && capabilities->device_metrics) {
+    device_metrics =
+        std::make_unique<DeviceMetrics>(*capabilities->device_metrics);
+  }
+
   std::unique_ptr<std::set<WebViewInfo::Type>> window_types;
   if (capabilities && !capabilities->window_types.empty()) {
     window_types = std::make_unique<std::set<WebViewInfo::Type>>(
@@ -255,10 +262,13 @@
         cmd_line->GetSwitchValueNative("devtools-replay");
     base::FilePath log_file_path(log_path);
     client = std::make_unique<ReplayHttpClient>(
-        endpoint, factory, std::move(window_types), log_file_path);
+        endpoint, factory, socket_factory, std::move(device_metrics),
+        std::move(window_types), capabilities->page_load_strategy,
+        log_file_path);
   } else {
-    client = std::make_unique<DevToolsHttpClient>(endpoint, factory,
-                                                  std::move(window_types));
+    client = std::make_unique<DevToolsHttpClient>(
+        endpoint, factory, socket_factory, std::move(device_metrics),
+        std::move(window_types), capabilities->page_load_strategy);
   }
 
   const base::TimeTicks initial = base::TimeTicks::Now();
@@ -377,8 +387,8 @@
   std::unique_ptr<DevToolsHttpClient> devtools_http_client;
   bool retry = true;
   status = WaitForDevToolsAndCheckVersion(
-      DevToolsEndpoint(capabilities.debugger_address), factory, &capabilities,
-      60, &devtools_http_client, &retry, ChromeType::Remote);
+      DevToolsEndpoint(capabilities.debugger_address), factory, socket_factory,
+      &capabilities, 60, &devtools_http_client, &retry, ChromeType::Remote);
   if (status.IsError()) {
     return Status(
         kUnknownError,
@@ -399,16 +409,9 @@
                  << status.message();
   }
 
-  std::unique_ptr<DeviceMetrics> device_metrics;
-  if (capabilities.device_metrics) {
-    device_metrics =
-        std::make_unique<DeviceMetrics>(*capabilities.device_metrics);
-  }
-
   *chrome = std::make_unique<ChromeRemoteImpl>(
       std::move(devtools_http_client), std::move(devtools_websocket_client),
-      std::move(devtools_event_listeners), std::move(device_metrics),
-      socket_factory, capabilities.page_load_strategy);
+      std::move(devtools_event_listeners), capabilities.page_load_strategy);
   return Status(kOk);
 }
 
@@ -589,8 +592,9 @@
       std::ostringstream oss;
       oss << command.GetProgram();
       status = WaitForDevToolsAndCheckVersion(
-          DevToolsEndpoint(devtools_port), factory, &capabilities, 1,
-          &devtools_http_client, &retry, ChromeType::Desktop, oss.str());
+          DevToolsEndpoint(devtools_port), factory, socket_factory,
+          &capabilities, 1, &devtools_http_client, &retry, ChromeType::Desktop,
+          oss.str());
       if (!retry) {
         break;
       }
@@ -664,18 +668,11 @@
                  << status.message();
   }
 
-  std::unique_ptr<DeviceMetrics> device_metrics;
-  if (capabilities.device_metrics) {
-    device_metrics =
-        std::make_unique<DeviceMetrics>(*capabilities.device_metrics);
-  }
-
   std::unique_ptr<ChromeDesktopImpl> chrome_desktop =
       std::make_unique<ChromeDesktopImpl>(
           std::move(devtools_http_client), std::move(devtools_websocket_client),
-          std::move(devtools_event_listeners), std::move(device_metrics),
-          socket_factory, capabilities.page_load_strategy, std::move(process),
-          command, &user_data_dir_temp_dir, &extension_dir,
+          std::move(devtools_event_listeners), capabilities.page_load_strategy,
+          std::move(process), command, &user_data_dir_temp_dir, &extension_dir,
           capabilities.network_emulation_enabled);
   if (!capabilities.extension_load_timeout.is_zero()) {
     for (size_t i = 0; i < extension_bg_pages.size(); ++i) {
@@ -740,8 +737,8 @@
   std::unique_ptr<DevToolsHttpClient> devtools_http_client;
   bool retry = true;
   status = WaitForDevToolsAndCheckVersion(
-      DevToolsEndpoint(devtools_port), factory, &capabilities, 60,
-      &devtools_http_client, &retry, ChromeType::Android);
+      DevToolsEndpoint(devtools_port), factory, socket_factory, &capabilities,
+      60, &devtools_http_client, &retry, ChromeType::Android);
   if (status.IsError()) {
     device->TearDown();
     return status;
@@ -758,16 +755,10 @@
                  << status.message();
   }
 
-  std::unique_ptr<DeviceMetrics> device_metrics;
-  if (capabilities.device_metrics) {
-    device_metrics =
-        std::make_unique<DeviceMetrics>(*capabilities.device_metrics);
-  }
-
   *chrome = std::make_unique<ChromeAndroidImpl>(
       std::move(devtools_http_client), std::move(devtools_websocket_client),
-      std::move(devtools_event_listeners), std::move(device_metrics),
-      socket_factory, capabilities.page_load_strategy, std::move(device));
+      std::move(devtools_event_listeners), capabilities.page_load_strategy,
+      std::move(device));
   return Status(kOk);
 }
 
@@ -801,8 +792,8 @@
   std::unique_ptr<DevToolsHttpClient> devtools_http_client;
   bool retry = true;
   status = WaitForDevToolsAndCheckVersion(
-      DevToolsEndpoint(0), factory, &capabilities, 1, &devtools_http_client,
-      &retry, ChromeType::Replay);
+      DevToolsEndpoint(0), factory, socket_factory, &capabilities, 1,
+      &devtools_http_client, &retry, ChromeType::Replay);
   if (status.IsError())
     return status;
   std::unique_ptr<DevToolsClient> devtools_websocket_client;
@@ -815,19 +806,11 @@
     LOG(WARNING) << "Browser-wide DevTools client failed to connect: "
                  << status.message();
   }
-
-  std::unique_ptr<DeviceMetrics> device_metrics;
-  if (capabilities.device_metrics) {
-    device_metrics =
-        std::make_unique<DeviceMetrics>(*capabilities.device_metrics);
-  }
-
   base::Process dummy_process;
   std::unique_ptr<ChromeDesktopImpl> chrome_impl =
       std::make_unique<ChromeReplayImpl>(
           std::move(devtools_http_client), std::move(devtools_websocket_client),
-          std::move(devtools_event_listeners), std::move(device_metrics),
-          socket_factory, capabilities.page_load_strategy,
+          std::move(devtools_event_listeners), capabilities.page_load_strategy,
           std::move(dummy_process), command, &user_data_dir_temp_dir,
           &extension_dir, capabilities.network_emulation_enabled);
 
diff --git a/chrome/test/chromedriver/log_replay/chrome_replay_impl.cc b/chrome/test/chromedriver/log_replay/chrome_replay_impl.cc
index 2d59b28d..009ae6c 100644
--- a/chrome/test/chromedriver/log_replay/chrome_replay_impl.cc
+++ b/chrome/test/chromedriver/log_replay/chrome_replay_impl.cc
@@ -3,7 +3,6 @@
 // found in the LICENSE file.
 #include "chrome/test/chromedriver/log_replay/chrome_replay_impl.h"
 
-#include "chrome/test/chromedriver/chrome/device_metrics.h"
 #include "chrome/test/chromedriver/chrome/devtools_client.h"
 #include "chrome/test/chromedriver/chrome/devtools_event_listener.h"
 #include "chrome/test/chromedriver/chrome/devtools_http_client.h"
@@ -14,8 +13,6 @@
     std::unique_ptr<DevToolsClient> websocket_client,
     std::vector<std::unique_ptr<DevToolsEventListener>>
         devtools_event_listeners,
-    std::unique_ptr<DeviceMetrics> device_metrics,
-    SyncWebSocketFactory socket_factory,
     std::string page_load_strategy,
     base::Process process,
     const base::CommandLine& command,
@@ -25,8 +22,6 @@
     : ChromeDesktopImpl(std::move(http_client),
                         std::move(websocket_client),
                         std::move(devtools_event_listeners),
-                        std::move(device_metrics),
-                        std::move(socket_factory),
                         page_load_strategy,
                         std::move(process),
                         command,
diff --git a/chrome/test/chromedriver/log_replay/chrome_replay_impl.h b/chrome/test/chromedriver/log_replay/chrome_replay_impl.h
index 087b4578..ab7e5cb0 100644
--- a/chrome/test/chromedriver/log_replay/chrome_replay_impl.h
+++ b/chrome/test/chromedriver/log_replay/chrome_replay_impl.h
@@ -4,14 +4,11 @@
 #ifndef CHROME_TEST_CHROMEDRIVER_LOG_REPLAY_CHROME_REPLAY_IMPL_H_
 #define CHROME_TEST_CHROMEDRIVER_LOG_REPLAY_CHROME_REPLAY_IMPL_H_
 
-#include <memory>
 #include "chrome/test/chromedriver/chrome/chrome_desktop_impl.h"
-#include "chrome/test/chromedriver/chrome/device_metrics.h"
 
 class DevToolsClient;
 class DevToolsHttpClient;
 class Status;
-struct DeviceMetrics;
 
 // Same as ChromeDesktopImpl except that it completely ignores the existence
 // of the |process| passed into the constructor. This allows running Chrome
@@ -23,8 +20,6 @@
                    std::unique_ptr<DevToolsClient> websocket_client,
                    std::vector<std::unique_ptr<DevToolsEventListener>>
                        devtools_event_listeners,
-                   std::unique_ptr<DeviceMetrics> device_metrics,
-                   SyncWebSocketFactory socket_factory,
                    std::string page_load_strategy,
                    base::Process process,
                    const base::CommandLine& command,
diff --git a/chrome/test/chromedriver/log_replay/replay_http_client.cc b/chrome/test/chromedriver/log_replay/replay_http_client.cc
index 3437c48..46718fc 100644
--- a/chrome/test/chromedriver/log_replay/replay_http_client.cc
+++ b/chrome/test/chromedriver/log_replay/replay_http_client.cc
@@ -5,6 +5,7 @@
 
 #include <utility>
 
+#include "chrome/test/chromedriver/chrome/device_metrics.h"
 #include "services/network/public/mojom/url_loader_factory.mojom.h"
 #include "url/gurl.h"
 
@@ -21,9 +22,17 @@
 ReplayHttpClient::ReplayHttpClient(
     const DevToolsEndpoint& endpoint,
     network::mojom::URLLoaderFactory* factory,
+    const SyncWebSocketFactory& socket_factory,
+    std::unique_ptr<DeviceMetrics> device_metrics,
     std::unique_ptr<std::set<WebViewInfo::Type>> window_types,
+    std::string page_load_strategy,
     const base::FilePath& log_path)
-    : DevToolsHttpClient(endpoint, factory, std::move(window_types)),
+    : DevToolsHttpClient(endpoint,
+                         factory,
+                         socket_factory,
+                         std::move(device_metrics),
+                         std::move(window_types),
+                         page_load_strategy),
       log_reader_(log_path) {}
 ReplayHttpClient::~ReplayHttpClient() {}
 
diff --git a/chrome/test/chromedriver/log_replay/replay_http_client.h b/chrome/test/chromedriver/log_replay/replay_http_client.h
index 9f114acc..b714f7d9 100644
--- a/chrome/test/chromedriver/log_replay/replay_http_client.h
+++ b/chrome/test/chromedriver/log_replay/replay_http_client.h
@@ -29,7 +29,10 @@
   // Initializes a DevToolsLogReader with the given log file.
   ReplayHttpClient(const DevToolsEndpoint& endpoint,
                    network::mojom::URLLoaderFactory* factory,
+                   const SyncWebSocketFactory& socket_factory,
+                   std::unique_ptr<DeviceMetrics> device_metrics,
                    std::unique_ptr<std::set<WebViewInfo::Type>> window_types,
+                   std::string page_load_strategy,
                    const base::FilePath& log_file);
   ~ReplayHttpClient() override;
 
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 b6e6504..40d2031 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
@@ -101,8 +101,12 @@
     await initializePage();
     textAreaElement = page.shadowRoot.querySelector('#descriptionText');
     assertTrue(!!textAreaElement);
-    // Verify the textarea is empty.
+    // Verify the textarea is empty and hint is showing.
     assertEquals('', textAreaElement.value);
+    assertEquals(
+        'Share your feedback or describe your issue. ' +
+            'If possible, include steps to reproduce your issue.',
+        textAreaElement.placeholder);
 
     // Enter three chars.
     textAreaElement.value = 'abc';
diff --git a/chrome/updater/app/server/win/com_classes_legacy_unittest.cc b/chrome/updater/app/server/win/com_classes_legacy_unittest.cc
index 8fe31a79..c9262af 100644
--- a/chrome/updater/app/server/win/com_classes_legacy_unittest.cc
+++ b/chrome/updater/app/server/win/com_classes_legacy_unittest.cc
@@ -208,6 +208,13 @@
     }
   }
 
+  std::wstring GetCommandLine(int key, const std::wstring& exe_name) {
+    base::FilePath programfiles_path;
+    EXPECT_TRUE(base::PathService::Get(key, &programfiles_path));
+    return base::CommandLine(programfiles_path.Append(exe_name))
+        .GetCommandLineString();
+  }
+
   base::CommandLine test_process_command_line_;
   base::FilePath temp_directory_;
 };
@@ -228,48 +235,43 @@
             absl::nullopt);
 }
 
-TEST_F(LegacyAppCommandWebImplTest, NoArguments) {
-  EXPECT_EQ(FormatCommandLine(L"\"C:\\Program Files (x86)\\process.exe\"", {})
-                .value(),
-            L"\"C:\\Program Files (x86)\\process.exe\"");
-  EXPECT_EQ(FormatCommandLine(L"\"C:\\Program Files (x86)\\process.exe\"", {})
-                .value(),
-            L"\"C:\\Program Files (x86)\\process.exe\"");
+TEST_F(LegacyAppCommandWebImplTest, ProgramFilesPaths) {
+  for (const int key : {base::DIR_PROGRAM_FILES, base::DIR_PROGRAM_FILESX86,
+                        base::DIR_PROGRAM_FILES6432}) {
+    const std::wstring process_command_line =
+        GetCommandLine(key, L"process.exe");
+    EXPECT_EQ(FormatCommandLine(process_command_line, {}).value(),
+              process_command_line);
+  }
 }
 
 TEST_F(LegacyAppCommandWebImplTest, UnformattedParameters) {
   std::wstring process_name;
   std::wstring arguments;
+  const std::wstring process_command_line =
+      GetCommandLine(base::DIR_PROGRAM_FILES, L"process.exe");
+
+  EXPECT_EQ(FormatCommandLine(process_command_line + L" abc=1", {}).value(),
+            process_command_line + L" abc=1");
   EXPECT_EQ(
-      FormatCommandLine(L"\"C:\\Program Files (x86)\\process.exe\" abc=1", {})
-          .value(),
-      L"\"C:\\Program Files (x86)\\process.exe\" abc=1");
-  EXPECT_EQ(FormatCommandLine(
-                L"\"C:\\Program Files (x86)\\process.exe\" abc=1 xyz=2", {})
+      FormatCommandLine(process_command_line + L" abc=1 xyz=2", {}).value(),
+      process_command_line + L" abc=1 xyz=2");
+  EXPECT_EQ(FormatCommandLine(process_command_line + L"  abc=1  xyz=2   q ", {})
                 .value(),
-            L"\"C:\\Program Files (x86)\\process.exe\" abc=1 xyz=2");
+            process_command_line + L" abc=1 xyz=2 q");
   EXPECT_EQ(
-      FormatCommandLine(
-          L"\"C:\\Program Files (x86)\\process.exe\"  abc=1  xyz=2   q ", {})
-          .value(),
-      L"\"C:\\Program Files (x86)\\process.exe\" abc=1 xyz=2 q");
-  EXPECT_EQ(FormatCommandLine(
-                L"\"C:\\Program Files (x86)\\process.exe\" \"abc = 1\"", {})
-                .value(),
-            L"\"C:\\Program Files (x86)\\process.exe\" \"abc = 1\"");
-  EXPECT_EQ(FormatCommandLine(
-                L"\"C:\\Program Files (x86)\\process.exe\" abc\" = \"1", {})
-                .value(),
-            L"\"C:\\Program Files (x86)\\process.exe\" \"abc = 1\"");
+      FormatCommandLine(process_command_line + L" \"abc = 1\"", {}).value(),
+      process_command_line + L" \"abc = 1\"");
+  EXPECT_EQ(
+      FormatCommandLine(process_command_line + L" abc\" = \"1", {}).value(),
+      process_command_line + L" \"abc = 1\"");
 
   EXPECT_EQ(
-      FormatCommandLine(L"\"c:\\Program Files\\process.exe\" \"abc = 1\"", {})
-          .value(),
-      L"\"c:\\Program Files\\process.exe\" \"abc = 1\"");
+      FormatCommandLine(process_command_line + L" \"abc = 1\"", {}).value(),
+      process_command_line + L" \"abc = 1\"");
   EXPECT_EQ(
-      FormatCommandLine(L"\"c:\\Program Files\\process.exe\" abc\" = \"1", {})
-          .value(),
-      L"\"c:\\Program Files\\process.exe\" \"abc = 1\"");
+      FormatCommandLine(process_command_line + L" abc\" = \"1", {}).value(),
+      process_command_line + L" \"abc = 1\"");
 }
 
 TEST_F(LegacyAppCommandWebImplTest, SimpleParameters) {
@@ -277,25 +279,26 @@
   parameters.push_back(L"p1");
   parameters.push_back(L"p2");
   parameters.push_back(L"p3");
+  const std::wstring process_command_line =
+      GetCommandLine(base::DIR_PROGRAM_FILES, L"process.exe");
 
-  EXPECT_EQ(FormatCommandLine(
-                L"\"C:\\Program Files (x86)\\process.exe\" abc=%1", parameters)
-                .value(),
-            L"\"C:\\Program Files (x86)\\process.exe\" abc=p1");
-  EXPECT_EQ(FormatCommandLine(
-                L"\"C:\\Program Files (x86)\\process.exe\" abc=%1 %3 %2=x",
-                parameters)
-                .value(),
-            L"\"C:\\Program Files (x86)\\process.exe\" abc=p1 p3 p2=x");
+  EXPECT_EQ(
+      FormatCommandLine(process_command_line + L" abc=%1", parameters).value(),
+      process_command_line + L" abc=p1");
+  EXPECT_EQ(
+      FormatCommandLine(process_command_line + L" abc=%1 %3 %2=x", parameters)
+          .value(),
+      process_command_line + L" abc=p1 p3 p2=x");
 
-  EXPECT_EQ(FormatCommandLine(L"\"C:\\Program Files (x86)\\process.exe\" %4",
-                              parameters),
+  EXPECT_EQ(FormatCommandLine(process_command_line + L" %4", parameters),
             absl::nullopt);
 }
 
 TEST_F(LegacyAppCommandWebImplTest, SimpleParametersNoFormatParameters) {
   EXPECT_EQ(
-      FormatCommandLine(L"\"C:\\Program Files (x86)\\process.exe\" abc=%1", {}),
+      FormatCommandLine(
+          GetCommandLine(base::DIR_PROGRAM_FILES, L"process.exe") + L" abc=%1",
+          {}),
       absl::nullopt);
 }
 
@@ -308,15 +311,15 @@
       {L"%%%1", L"%p1"}, {L"abc%%def%%", L"abc%def%"},
       {L"%12", L"p12"},  {L"%1%2", L"p1p2"},
   };
+  const std::wstring process_command_line =
+      GetCommandLine(base::DIR_PROGRAM_FILES, L"process.exe");
 
   for (const auto& test_case : test_cases) {
     EXPECT_EQ(FormatCommandLine(
-                  base::StrCat({L"\"C:\\Program Files (x86)\\process.exe\" ",
-                                test_case.input}),
+                  base::StrCat({process_command_line, L" ", test_case.input}),
                   {L"p1", L"p2", L"p3"})
                   .value(),
-              base::StrCat({L"\"C:\\Program Files (x86)\\process.exe\" ",
-                            test_case.output}));
+              base::StrCat({process_command_line, L" ", test_case.output}));
   }
 }
 
@@ -330,13 +333,14 @@
       L"placeholder %4  is > size of input substitutions",
       L"%1 is ok, but %8 or %9 is not ok",
   };
+  const std::wstring process_command_line =
+      GetCommandLine(base::DIR_PROGRAM_FILES, L"process.exe");
 
   for (const wchar_t* test_case : test_cases) {
-    EXPECT_EQ(FormatCommandLine(
-                  base::StrCat({L"\"C:\\Program Files (x86)\\process.exe\" ",
-                                test_case}),
-                  {L"p1", L"p2", L"p3"}),
-              absl::nullopt);
+    EXPECT_EQ(
+        FormatCommandLine(base::StrCat({process_command_line, L" ", test_case}),
+                          {L"p1", L"p2", L"p3"}),
+        absl::nullopt);
   }
 }
 
@@ -370,15 +374,15 @@
       // leading space.
       {L" abcdef", L"\" abcdef\""},
   };
+  const std::wstring process_command_line =
+      GetCommandLine(base::DIR_PROGRAM_FILES, L"process.exe");
 
   for (const auto& test_case : test_cases) {
     std::wstring command_line =
-        FormatCommandLine(L"\"C:\\Program Files (x86)\\process.exe\" %1",
-                          {test_case.input})
+        FormatCommandLine(process_command_line + L" %1", {test_case.input})
             .value();
     EXPECT_EQ(command_line,
-              base::StrCat({L"\"C:\\Program Files (x86)\\process.exe\" ",
-                            test_case.output}));
+              base::StrCat({process_command_line, L" ", test_case.output}));
 
     // The formatted output is now sent through ::CommandLineToArgvW to verify
     // that it produces the original input.
diff --git a/chromecast/bindings/BUILD.gn b/chromecast/bindings/BUILD.gn
index cd898f9..7187c51c 100644
--- a/chromecast/bindings/BUILD.gn
+++ b/chromecast/bindings/BUILD.gn
@@ -42,7 +42,7 @@
     ]
     public_deps = [
       ":bindings_manager",
-      "//fuchsia/runners/cast/fidl",
+      "//fuchsia_web/runners/cast/fidl",
       "//third_party/fuchsia-sdk/sdk/fidl/fuchsia.mem",
     ]
     deps = [
diff --git a/chromecast/bindings/DEPS b/chromecast/bindings/DEPS
index 4ad31ef..956e08a 100644
--- a/chromecast/bindings/DEPS
+++ b/chromecast/bindings/DEPS
@@ -13,6 +13,6 @@
 
 specific_include_rules = {
   "bindings_manager_fuchsia\..*": [
-    "+fuchsia/runners/cast/fidl/fidl/chromium/cast/cpp/fidl.h",
+    "+fuchsia_web/runners/cast/fidl/fidl/chromium/cast/cpp/fidl.h",
   ],
 }
diff --git a/chromecast/bindings/bindings_manager_fuchsia.h b/chromecast/bindings/bindings_manager_fuchsia.h
index 926e67f..7e73fd6 100644
--- a/chromecast/bindings/bindings_manager_fuchsia.h
+++ b/chromecast/bindings/bindings_manager_fuchsia.h
@@ -12,7 +12,7 @@
 #include <string>
 
 #include "chromecast/bindings/bindings_manager.h"
-#include "fuchsia/runners/cast/fidl/fidl/chromium/cast/cpp/fidl.h"
+#include "fuchsia_web/runners/cast/fidl/fidl/chromium/cast/cpp/fidl.h"
 
 namespace chromecast {
 namespace bindings {
diff --git a/chromeos/chromeos_strings.grd b/chromeos/chromeos_strings.grd
index 17a87f1..7e58f0a 100644
--- a/chromeos/chromeos_strings.grd
+++ b/chromeos/chromeos_strings.grd
@@ -3347,6 +3347,9 @@
       <message name="IDS_FEEDBACK_TOOL_FEEDBACK_HELP_LINK_LABEL" translateable="false" desc="The text used for the link that navigates to the feedback help webpage.">
         Tips on writing feedback
       </message>
+      <message name="IDS_FEEDBACK_TOOL_DESCRIPTION_HINT" translateable="false" desc="Label for the hint in description textarea">
+        Share your feedback or describe your issue. If possible, include steps to reproduce your issue.
+      </message>
       <!-- End of Feedback Tool -->
     </messages>
   </release>
diff --git a/chromeos/services/assistant/public/cpp/assistant_prefs.cc b/chromeos/services/assistant/public/cpp/assistant_prefs.cc
index 9ddd07d3..b98c555 100644
--- a/chromeos/services/assistant/public/cpp/assistant_prefs.cc
+++ b/chromeos/services/assistant/public/cpp/assistant_prefs.cc
@@ -25,6 +25,11 @@
 // VoiceInteractionContextEnabled administrator policy.
 const char kAssistantContextEnabled[] =
     "settings.voice_interaction.context.enabled";
+// A preference that indicates that the user has already triggered metalayer
+// mode while it is deprecated and been shown a toast that the what's on my
+// screen feature has been deprecated.
+const char kAssistantDeprecateStylusToast[] =
+    "settings.assistant.deprecate_stylus_toast";
 // A preference that indicates the Assistant has been disabled by domain policy.
 // If true, the Assistant will always been disabled and user cannot enable it.
 // This preference should only be changed in browser.
@@ -64,6 +69,7 @@
   registry->RegisterIntegerPref(kAssistantConsentStatus,
                                 ConsentStatus::kUnknown);
   registry->RegisterBooleanPref(kAssistantContextEnabled, false);
+  registry->RegisterBooleanPref(kAssistantDeprecateStylusToast, false);
   registry->RegisterBooleanPref(kAssistantDisabledByPolicy, false);
   registry->RegisterBooleanPref(kAssistantEnabled, false);
   registry->RegisterBooleanPref(kAssistantHotwordAlwaysOn, false);
diff --git a/chromeos/services/assistant/public/cpp/assistant_prefs.h b/chromeos/services/assistant/public/cpp/assistant_prefs.h
index 99f130dd..873250a4 100644
--- a/chromeos/services/assistant/public/cpp/assistant_prefs.h
+++ b/chromeos/services/assistant/public/cpp/assistant_prefs.h
@@ -52,6 +52,8 @@
 COMPONENT_EXPORT(ASSISTANT_SERVICE_PUBLIC)
 extern const char kAssistantContextEnabled[];
 COMPONENT_EXPORT(ASSISTANT_SERVICE_PUBLIC)
+extern const char kAssistantDeprecateStylusToast[];
+COMPONENT_EXPORT(ASSISTANT_SERVICE_PUBLIC)
 extern const char kAssistantDisabledByPolicy[];
 COMPONENT_EXPORT(ASSISTANT_SERVICE_PUBLIC)
 extern const char kAssistantEnabled[];
diff --git a/chromeos/services/libassistant/settings_controller.cc b/chromeos/services/libassistant/settings_controller.cc
index 42b91e2b..442b8c1 100644
--- a/chromeos/services/libassistant/settings_controller.cc
+++ b/chromeos/services/libassistant/settings_controller.cc
@@ -231,6 +231,7 @@
 
 void SettingsController::SetLocale(const std::string& value) {
   locale_ = LocaleOrDefault(value);
+  UpdateLocaleOverride(locale_);
   UpdateInternalOptions(locale_, spoken_feedback_enabled_, dark_mode_enabled_);
   UpdateDeviceSettings(locale_, hotword_enabled_);
 }
@@ -298,9 +299,6 @@
   if (!assistant_client_)
     return;
 
-  if (locale.has_value())
-    assistant_client_->SetLocaleOverride(locale.value());
-
   if (locale.has_value() && spoken_feedback_enabled.has_value() &&
       dark_mode_enabled.has_value()) {
     assistant_client_->SetDeviceAttributes(dark_mode_enabled.value());
@@ -309,6 +307,17 @@
   }
 }
 
+void SettingsController::UpdateLocaleOverride(
+    const absl::optional<std::string>& locale) {
+  if (!assistant_client_)
+    return;
+
+  if (!locale.has_value())
+    return;
+
+  assistant_client_->SetLocaleOverride(locale.value());
+}
+
 void SettingsController::UpdateDeviceSettings(
     const absl::optional<std::string>& locale,
     absl::optional<bool> hotword_enabled) {
@@ -329,7 +338,10 @@
   // Libassistant to be fully ready.
   UpdateAuthenticationTokens(authentication_tokens_);
   UpdateInternalOptions(locale_, spoken_feedback_enabled_, dark_mode_enabled_);
-  UpdateListeningEnabled(listening_enabled_);
+  if (!chromeos::assistant::features::IsLibAssistantV2Enabled()) {
+    UpdateLocaleOverride(locale_);
+    UpdateListeningEnabled(listening_enabled_);
+  }
 }
 
 void SettingsController::OnAssistantClientRunning(
@@ -338,6 +350,10 @@
       std::make_unique<DeviceSettingsUpdater>(this, assistant_client);
 
   UpdateDeviceSettings(locale_, hotword_enabled_);
+  if (chromeos::assistant::features::IsLibAssistantV2Enabled()) {
+    UpdateLocaleOverride(locale_);
+    UpdateListeningEnabled(listening_enabled_);
+  }
 }
 
 void SettingsController::OnDestroyingAssistantClient(
diff --git a/chromeos/services/libassistant/settings_controller.h b/chromeos/services/libassistant/settings_controller.h
index f4871241..3c63090 100644
--- a/chromeos/services/libassistant/settings_controller.h
+++ b/chromeos/services/libassistant/settings_controller.h
@@ -57,6 +57,7 @@
   void UpdateInternalOptions(const absl::optional<std::string>& locale,
                              absl::optional<bool> spoken_feedback_enabled,
                              absl::optional<bool> dark_mode_enabled);
+  void UpdateLocaleOverride(const absl::optional<std::string>& locale);
   void UpdateDeviceSettings(const absl::optional<std::string>& locale,
                             absl::optional<bool> hotword_enabled);
 
diff --git a/components/autofill/core/browser/BUILD.gn b/components/autofill/core/browser/BUILD.gn
index 05713ac..41504cf 100644
--- a/components/autofill/core/browser/BUILD.gn
+++ b/components/autofill/core/browser/BUILD.gn
@@ -289,6 +289,10 @@
     "payments/payments_requests/get_details_for_enrollment_request.h",
     "payments/payments_requests/get_unmask_details_request.cc",
     "payments/payments_requests/get_unmask_details_request.h",
+    "payments/payments_requests/get_upload_details_request.cc",
+    "payments/payments_requests/get_upload_details_request.h",
+    "payments/payments_requests/migrate_cards_request.cc",
+    "payments/payments_requests/migrate_cards_request.h",
     "payments/payments_requests/opt_change_request.cc",
     "payments/payments_requests/opt_change_request.h",
     "payments/payments_requests/payments_request.cc",
@@ -299,6 +303,8 @@
     "payments/payments_requests/unmask_card_request.h",
     "payments/payments_requests/update_virtual_card_enrollment_request.cc",
     "payments/payments_requests/update_virtual_card_enrollment_request.h",
+    "payments/payments_requests/upload_card_request.cc",
+    "payments/payments_requests/upload_card_request.h",
     "payments/payments_service_url.cc",
     "payments/payments_service_url.h",
     "payments/payments_util.cc",
diff --git a/components/autofill/core/browser/payments/payments_client.cc b/components/autofill/core/browser/payments/payments_client.cc
index 892f83ff..aa8c507 100644
--- a/components/autofill/core/browser/payments/payments_client.cc
+++ b/components/autofill/core/browser/payments/payments_client.cc
@@ -13,13 +13,9 @@
 #include "base/bind.h"
 #include "base/command_line.h"
 #include "base/json/json_reader.h"
-#include "base/json/json_writer.h"
-#include "base/strings/escape.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_split.h"
-#include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
-#include "base/strings/utf_string_conversions.h"
 #include "base/values.h"
 #include "build/build_config.h"
 #include "components/autofill/core/browser/autofill_experiments.h"
@@ -30,14 +26,16 @@
 #include "components/autofill/core/browser/payments/local_card_migration_manager.h"
 #include "components/autofill/core/browser/payments/payments_requests/get_details_for_enrollment_request.h"
 #include "components/autofill/core/browser/payments/payments_requests/get_unmask_details_request.h"
+#include "components/autofill/core/browser/payments/payments_requests/get_upload_details_request.h"
+#include "components/autofill/core/browser/payments/payments_requests/migrate_cards_request.h"
 #include "components/autofill/core/browser/payments/payments_requests/opt_change_request.h"
 #include "components/autofill/core/browser/payments/payments_requests/payments_request.h"
 #include "components/autofill/core/browser/payments/payments_requests/select_challenge_option_request.h"
 #include "components/autofill/core/browser/payments/payments_requests/unmask_card_request.h"
 #include "components/autofill/core/browser/payments/payments_requests/update_virtual_card_enrollment_request.h"
+#include "components/autofill/core/browser/payments/payments_requests/upload_card_request.h"
 #include "components/autofill/core/browser/payments/payments_service_url.h"
 #include "components/autofill/core/common/autofill_features.h"
-#include "components/autofill/core/common/autofill_payments_features.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
 #include "components/signin/public/identity_manager/primary_account_access_token_fetcher.h"
 #include "components/signin/public/identity_manager/scope_set.h"
@@ -54,25 +52,6 @@
 
 namespace {
 
-const char kGetUploadDetailsRequestPath[] =
-    "payments/apis/chromepaymentsservice/getdetailsforsavecard";
-
-const char kUploadCardRequestPath[] =
-    "payments/apis-secure/chromepaymentsservice/savecard"
-    "?s7e_suffix=chromewallet";
-const char kUploadCardRequestFormat[] =
-    "requestContentType=application/json; charset=utf-8&request=%s"
-    "&s7e_1_pan=%s&s7e_13_cvc=%s";
-const char kUploadCardRequestFormatWithoutCvc[] =
-    "requestContentType=application/json; charset=utf-8&request=%s"
-    "&s7e_1_pan=%s";
-
-const char kMigrateCardsRequestPath[] =
-    "payments/apis-secure/chromepaymentsservice/migratecards"
-    "?s7e_suffix=chromewallet";
-const char kMigrateCardsRequestFormat[] =
-    "requestContentType=application/json; charset=utf-8&request=%s";
-
 const char kTokenFetchId[] = "wallet_client";
 const char kPaymentsOAuth2Scope[] =
     "https://www.googleapis.com/auth/wallet.chrome";
@@ -95,587 +74,6 @@
   return GetBaseSecureUrl().Resolve(path);
 }
 
-void SetStringIfNotEmpty(const AutofillDataModel& profile,
-                         const ServerFieldType& type,
-                         const std::string& app_locale,
-                         const std::string& path,
-                         base::Value& dictionary) {
-  const std::u16string value = profile.GetInfo(AutofillType(type), app_locale);
-  if (!value.empty())
-    dictionary.SetKey(path, base::Value(value));
-}
-
-void AppendStringIfNotEmpty(const AutofillProfile& profile,
-                            const ServerFieldType& type,
-                            const std::string& app_locale,
-                            base::Value& list) {
-  const std::u16string value = profile.GetInfo(type, app_locale);
-  if (!value.empty())
-    list.Append(value);
-}
-
-// Returns a dictionary with the structure expected by Payments RPCs, containing
-// each of the fields in |profile|, formatted according to |app_locale|. If
-// |include_non_location_data| is false, the name and phone number in |profile|
-// are not included.
-base::Value BuildAddressDictionary(const AutofillProfile& profile,
-                                   const std::string& app_locale,
-                                   bool include_non_location_data) {
-  base::Value postal_address(base::Value::Type::DICTIONARY);
-
-  if (include_non_location_data) {
-    SetStringIfNotEmpty(profile, NAME_FULL, app_locale,
-                        PaymentsClient::kRecipientName, postal_address);
-  }
-
-  base::Value address_lines(base::Value::Type::LIST);
-  AppendStringIfNotEmpty(profile, ADDRESS_HOME_LINE1, app_locale,
-                         address_lines);
-  AppendStringIfNotEmpty(profile, ADDRESS_HOME_LINE2, app_locale,
-                         address_lines);
-  AppendStringIfNotEmpty(profile, ADDRESS_HOME_LINE3, app_locale,
-                         address_lines);
-  if (!address_lines.GetListDeprecated().empty())
-    postal_address.SetKey("address_line", std::move(address_lines));
-
-  SetStringIfNotEmpty(profile, ADDRESS_HOME_CITY, app_locale, "locality_name",
-                      postal_address);
-  SetStringIfNotEmpty(profile, ADDRESS_HOME_STATE, app_locale,
-                      "administrative_area_name", postal_address);
-  SetStringIfNotEmpty(profile, ADDRESS_HOME_ZIP, app_locale,
-                      "postal_code_number", postal_address);
-
-  // Use GetRawInfo to get a country code instead of the country name:
-  const std::u16string country_code = profile.GetRawInfo(ADDRESS_HOME_COUNTRY);
-  if (!country_code.empty())
-    postal_address.SetKey("country_name_code", base::Value(country_code));
-
-  base::Value address(base::Value::Type::DICTIONARY);
-  address.SetKey("postal_address", std::move(postal_address));
-
-  if (include_non_location_data) {
-    SetStringIfNotEmpty(profile, PHONE_HOME_WHOLE_NUMBER, app_locale,
-                        PaymentsClient::kPhoneNumber, address);
-  }
-
-  return address;
-}
-
-// Returns a dictionary of the credit card with the structure expected by
-// Payments RPCs, containing expiration month, expiration year and cardholder
-// name (if any) fields in |credit_card|, formatted according to |app_locale|.
-// |pan_field_name| is the field name for the encrypted pan. We use each credit
-// card's guid as the unique id.
-base::Value BuildCreditCardDictionary(const CreditCard& credit_card,
-                                      const std::string& app_locale,
-                                      const std::string& pan_field_name) {
-  base::Value card(base::Value::Type::DICTIONARY);
-  card.SetKey("unique_id", base::Value(credit_card.guid()));
-
-  const std::u16string exp_month =
-      credit_card.GetInfo(AutofillType(CREDIT_CARD_EXP_MONTH), app_locale);
-  const std::u16string exp_year = credit_card.GetInfo(
-      AutofillType(CREDIT_CARD_EXP_4_DIGIT_YEAR), app_locale);
-  int value = 0;
-  if (base::StringToInt(exp_month, &value))
-    card.SetKey("expiration_month", base::Value(value));
-  if (base::StringToInt(exp_year, &value))
-    card.SetKey("expiration_year", base::Value(value));
-  SetStringIfNotEmpty(credit_card, CREDIT_CARD_NAME_FULL, app_locale,
-                      "cardholder_name", card);
-
-  if (credit_card.HasNonEmptyValidNickname())
-    card.SetKey("nickname", base::Value(credit_card.nickname()));
-
-  card.SetKey("encrypted_pan", base::Value("__param:" + pan_field_name));
-  return card;
-}
-
-// Populates the list of active experiments that affect either the data sent in
-// payments RPCs or whether the RPCs are sent or not.
-void SetActiveExperiments(const std::vector<const char*>& active_experiments,
-                          base::Value& request_dict) {
-  if (active_experiments.empty())
-    return;
-
-  base::Value active_chrome_experiments(base::Value::Type::LIST);
-  for (const char* it : active_experiments)
-    active_chrome_experiments.Append(it);
-
-  request_dict.SetKey("active_chrome_experiments",
-                      std::move(active_chrome_experiments));
-}
-
-// TODO(crbug.com/1249665): Move requests to separate files.
-class GetUploadDetailsRequest : public PaymentsRequest {
- public:
-  GetUploadDetailsRequest(
-      const std::vector<AutofillProfile>& addresses,
-      const int detected_values,
-      const std::vector<const char*>& active_experiments,
-      const bool full_sync_enabled,
-      const std::string& app_locale,
-      base::OnceCallback<void(AutofillClient::PaymentsRpcResult,
-                              const std::u16string&,
-                              std::unique_ptr<base::Value>,
-                              std::vector<std::pair<int, int>>)> callback,
-      const int billable_service_number,
-      const int64_t billing_customer_number,
-      PaymentsClient::UploadCardSource upload_card_source)
-      : addresses_(addresses),
-        detected_values_(detected_values),
-        active_experiments_(active_experiments),
-        full_sync_enabled_(full_sync_enabled),
-        app_locale_(app_locale),
-        callback_(std::move(callback)),
-        billable_service_number_(billable_service_number),
-        upload_card_source_(upload_card_source),
-        billing_customer_number_(billing_customer_number) {}
-
-  GetUploadDetailsRequest(const GetUploadDetailsRequest&) = delete;
-  GetUploadDetailsRequest& operator=(const GetUploadDetailsRequest&) = delete;
-
-  ~GetUploadDetailsRequest() override = default;
-
-  std::string GetRequestUrlPath() override {
-    return kGetUploadDetailsRequestPath;
-  }
-
-  std::string GetRequestContentType() override { return "application/json"; }
-
-  std::string GetRequestContent() override {
-    base::Value request_dict(base::Value::Type::DICTIONARY);
-    base::Value context(base::Value::Type::DICTIONARY);
-    context.SetKey("language_code", base::Value(app_locale_));
-    context.SetKey("billable_service", base::Value(billable_service_number_));
-    if (base::FeatureList::IsEnabled(
-            features::kAutofillEnableSendingBcnInGetUploadDetails) &&
-        billing_customer_number_ != 0) {
-      context.SetKey("customer_context",
-                     BuildCustomerContextDictionary(billing_customer_number_));
-    }
-    request_dict.SetKey("context", std::move(context));
-
-    base::Value chrome_user_context(base::Value::Type::DICTIONARY);
-    chrome_user_context.SetKey("full_sync_enabled",
-                               base::Value(full_sync_enabled_));
-    request_dict.SetKey("chrome_user_context", std::move(chrome_user_context));
-
-    base::Value addresses(base::Value::Type::LIST);
-    for (const AutofillProfile& profile : addresses_) {
-      // These addresses are used by Payments to (1) accurately determine the
-      // user's country in order to show the correct legal documents and (2) to
-      // verify that the addresses are valid for their purposes so that we don't
-      // offer save in a case where it would definitely fail (e.g. P.O. boxes if
-      // min address is not possible). The final parameter directs
-      // BuildAddressDictionary to omit names and phone numbers, which aren't
-      // useful for these purposes.
-      addresses.Append(BuildAddressDictionary(profile, app_locale_, false));
-    }
-    request_dict.SetKey("address", std::move(addresses));
-
-    // It's possible we may not have found name/address/CVC in the checkout
-    // flow. The detected_values_ bitmask tells Payments what *was* found, and
-    // Payments will decide if the provided data is enough to offer upload save.
-    request_dict.SetKey("detected_values", base::Value(detected_values_));
-
-    SetActiveExperiments(active_experiments_, request_dict);
-
-    switch (upload_card_source_) {
-      case PaymentsClient::UploadCardSource::UNKNOWN_UPLOAD_CARD_SOURCE:
-        request_dict.SetKey("upload_card_source",
-                            base::Value("UNKNOWN_UPLOAD_CARD_SOURCE"));
-        break;
-      case PaymentsClient::UploadCardSource::UPSTREAM_CHECKOUT_FLOW:
-        request_dict.SetKey("upload_card_source",
-                            base::Value("UPSTREAM_CHECKOUT_FLOW"));
-        break;
-      case PaymentsClient::UploadCardSource::UPSTREAM_SETTINGS_PAGE:
-        request_dict.SetKey("upload_card_source",
-                            base::Value("UPSTREAM_SETTINGS_PAGE"));
-        break;
-      case PaymentsClient::UploadCardSource::UPSTREAM_CARD_OCR:
-        request_dict.SetKey("upload_card_source",
-                            base::Value("UPSTREAM_CARD_OCR"));
-        break;
-      case PaymentsClient::UploadCardSource::LOCAL_CARD_MIGRATION_CHECKOUT_FLOW:
-        request_dict.SetKey("upload_card_source",
-                            base::Value("LOCAL_CARD_MIGRATION_CHECKOUT_FLOW"));
-        break;
-      case PaymentsClient::UploadCardSource::LOCAL_CARD_MIGRATION_SETTINGS_PAGE:
-        request_dict.SetKey("upload_card_source",
-                            base::Value("LOCAL_CARD_MIGRATION_SETTINGS_PAGE"));
-        break;
-      default:
-        NOTREACHED();
-    }
-
-    std::string request_content;
-    base::JSONWriter::Write(request_dict, &request_content);
-    VLOG(3) << "getdetailsforsavecard request body: " << request_content;
-    return request_content;
-  }
-
-  void ParseResponse(const base::Value& response) override {
-    const auto* context_token = response.FindStringKey("context_token");
-    context_token_ =
-        context_token ? base::UTF8ToUTF16(*context_token) : std::u16string();
-
-    const base::Value* dictionary_value =
-        response.FindKeyOfType("legal_message", base::Value::Type::DICTIONARY);
-    if (dictionary_value)
-      legal_message_ = std::make_unique<base::Value>(dictionary_value->Clone());
-
-    const auto* supported_card_bin_ranges_string =
-        response.FindStringKey("supported_card_bin_ranges_string");
-    supported_card_bin_ranges_ = ParseSupportedCardBinRangesString(
-        supported_card_bin_ranges_string ? *supported_card_bin_ranges_string
-                                         : base::EmptyString());
-  }
-
-  bool IsResponseComplete() override {
-    return !context_token_.empty() && legal_message_;
-  }
-
-  void RespondToDelegate(AutofillClient::PaymentsRpcResult result) override {
-    std::move(callback_).Run(result, context_token_, std::move(legal_message_),
-                             supported_card_bin_ranges_);
-  }
-
- private:
-  // Helper for ParseResponse(). Input format should be :"1234,30000-55555,765",
-  // where ranges are separated by commas and items separated with a dash means
-  // the start and ends of the range. Items without a dash have the same start
-  // and end (ex. 1234-1234)
-  std::vector<std::pair<int, int>> ParseSupportedCardBinRangesString(
-      const std::string& supported_card_bin_ranges_string) {
-    std::vector<std::pair<int, int>> supported_card_bin_ranges;
-    std::vector<std::string> range_strings =
-        base::SplitString(supported_card_bin_ranges_string, ",",
-                          base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
-
-    for (std::string& range_string : range_strings) {
-      std::vector<std::string> range = base::SplitString(
-          range_string, "-", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
-      DCHECK(range.size() <= 2);
-      int start;
-      base::StringToInt(range[0], &start);
-      if (range.size() == 1) {
-        supported_card_bin_ranges.emplace_back(start, start);
-      } else {
-        int end;
-        base::StringToInt(range[1], &end);
-        DCHECK_LE(start, end);
-        supported_card_bin_ranges.emplace_back(start, end);
-      }
-    }
-    return supported_card_bin_ranges;
-  }
-
-  const std::vector<AutofillProfile> addresses_;
-  const int detected_values_;
-  const std::vector<const char*> active_experiments_;
-  const bool full_sync_enabled_;
-  std::string app_locale_;
-  base::OnceCallback<void(AutofillClient::PaymentsRpcResult,
-                          const std::u16string&,
-                          std::unique_ptr<base::Value>,
-                          std::vector<std::pair<int, int>>)>
-      callback_;
-  std::u16string context_token_;
-  std::unique_ptr<base::Value> legal_message_;
-  std::vector<std::pair<int, int>> supported_card_bin_ranges_;
-  const int billable_service_number_;
-  PaymentsClient::UploadCardSource upload_card_source_;
-  const int64_t billing_customer_number_;
-};
-
-class UploadCardRequest : public PaymentsRequest {
- public:
-  UploadCardRequest(
-      const PaymentsClient::UploadRequestDetails& request_details,
-      const bool full_sync_enabled,
-      base::OnceCallback<void(AutofillClient::PaymentsRpcResult,
-                              const PaymentsClient::UploadCardResponseDetails&)>
-          callback)
-      : request_details_(request_details),
-        full_sync_enabled_(full_sync_enabled),
-        callback_(std::move(callback)) {}
-
-  UploadCardRequest(const UploadCardRequest&) = delete;
-  UploadCardRequest& operator=(const UploadCardRequest&) = delete;
-
-  ~UploadCardRequest() override = default;
-
-  std::string GetRequestUrlPath() override { return kUploadCardRequestPath; }
-
-  std::string GetRequestContentType() override {
-    return "application/x-www-form-urlencoded";
-  }
-
-  std::string GetRequestContent() override {
-    base::Value request_dict(base::Value::Type::DICTIONARY);
-    request_dict.SetKey("encrypted_pan", base::Value("__param:s7e_1_pan"));
-    if (!request_details_.cvc.empty())
-      request_dict.SetKey("encrypted_cvc", base::Value("__param:s7e_13_cvc"));
-    request_dict.SetKey("risk_data_encoded",
-                        BuildRiskDictionary(request_details_.risk_data));
-
-    const std::string& app_locale = request_details_.app_locale;
-    base::Value context(base::Value::Type::DICTIONARY);
-    context.SetKey("language_code", base::Value(app_locale));
-    context.SetKey("billable_service",
-                   base::Value(kUploadCardBillableServiceNumber));
-    if (request_details_.billing_customer_number != 0) {
-      context.SetKey("customer_context",
-                     BuildCustomerContextDictionary(
-                         request_details_.billing_customer_number));
-    }
-    request_dict.SetKey("context", std::move(context));
-
-    base::Value chrome_user_context(base::Value::Type::DICTIONARY);
-    chrome_user_context.SetKey("full_sync_enabled",
-                               base::Value(full_sync_enabled_));
-    request_dict.SetKey("chrome_user_context", std::move(chrome_user_context));
-
-    SetStringIfNotEmpty(request_details_.card, CREDIT_CARD_NAME_FULL,
-                        app_locale, "cardholder_name", request_dict);
-
-    base::Value addresses(base::Value::Type::LIST);
-    for (const AutofillProfile& profile : request_details_.profiles) {
-      addresses.Append(BuildAddressDictionary(profile, app_locale, true));
-    }
-    request_dict.SetKey("address", std::move(addresses));
-
-    request_dict.SetKey("context_token",
-                        base::Value(request_details_.context_token));
-
-    int value = 0;
-    const std::u16string exp_month = request_details_.card.GetInfo(
-        AutofillType(CREDIT_CARD_EXP_MONTH), app_locale);
-    const std::u16string exp_year = request_details_.card.GetInfo(
-        AutofillType(CREDIT_CARD_EXP_4_DIGIT_YEAR), app_locale);
-    if (base::StringToInt(exp_month, &value))
-      request_dict.SetKey("expiration_month", base::Value(value));
-    if (base::StringToInt(exp_year, &value))
-      request_dict.SetKey("expiration_year", base::Value(value));
-
-    if (request_details_.card.HasNonEmptyValidNickname()) {
-      request_dict.SetKey("nickname",
-                          base::Value(request_details_.card.nickname()));
-    }
-
-    SetActiveExperiments(request_details_.active_experiments, request_dict);
-
-    const std::u16string pan = request_details_.card.GetInfo(
-        AutofillType(CREDIT_CARD_NUMBER), app_locale);
-    std::string json_request;
-    base::JSONWriter::Write(request_dict, &json_request);
-    std::string request_content;
-    if (request_details_.cvc.empty()) {
-      request_content = base::StringPrintf(
-          kUploadCardRequestFormatWithoutCvc,
-          base::EscapeUrlEncodedData(json_request, true).c_str(),
-          base::EscapeUrlEncodedData(base::UTF16ToASCII(pan), true).c_str());
-    } else {
-      request_content = base::StringPrintf(
-          kUploadCardRequestFormat,
-          base::EscapeUrlEncodedData(json_request, true).c_str(),
-          base::EscapeUrlEncodedData(base::UTF16ToASCII(pan), true).c_str(),
-          base::EscapeUrlEncodedData(base::UTF16ToASCII(request_details_.cvc),
-                                     true)
-              .c_str());
-    }
-    VLOG(3) << "savecard request body: " << request_content;
-    return request_content;
-  }
-
-  void ParseResponse(const base::Value& response) override {
-    const std::string* credit_card_id =
-        response.FindStringKey("credit_card_id");
-    upload_card_response_details_.server_id =
-        credit_card_id ? *credit_card_id : std::string();
-
-    const std::string* response_instrument_id =
-        response.FindStringKey("instrument_id");
-    if (response_instrument_id) {
-      int64_t instrument_id;
-      if (base::StringToInt64(base::StringPiece(*response_instrument_id),
-                              &instrument_id)) {
-        upload_card_response_details_.instrument_id = instrument_id;
-      }
-    }
-
-    const auto* virtual_card_metadata = response.FindKeyOfType(
-        "virtual_card_metadata", base::Value::Type::DICTIONARY);
-    if (virtual_card_metadata) {
-      const std::string* virtual_card_enrollment_status =
-          virtual_card_metadata->FindStringKey("status");
-      if (virtual_card_enrollment_status) {
-        if (*virtual_card_enrollment_status == "ENROLLED") {
-          upload_card_response_details_.virtual_card_enrollment_state =
-              CreditCard::VirtualCardEnrollmentState::ENROLLED;
-        } else if (*virtual_card_enrollment_status == "ENROLLMENT_ELIGIBLE") {
-          upload_card_response_details_.virtual_card_enrollment_state =
-              CreditCard::VirtualCardEnrollmentState::UNENROLLED_AND_ELIGIBLE;
-        } else {
-          upload_card_response_details_.virtual_card_enrollment_state =
-              CreditCard::VirtualCardEnrollmentState::
-                  UNENROLLED_AND_NOT_ELIGIBLE;
-        }
-      }
-    }
-
-    const std::string* card_art_url = response.FindStringKey("card_art_url");
-    upload_card_response_details_.card_art_url =
-        card_art_url ? GURL(*card_art_url) : GURL();
-  }
-
-  bool IsResponseComplete() override { return true; }
-
-  void RespondToDelegate(AutofillClient::PaymentsRpcResult result) override {
-    std::move(callback_).Run(result, upload_card_response_details_);
-  }
-
- private:
-  const PaymentsClient::UploadRequestDetails request_details_;
-  const bool full_sync_enabled_;
-  base::OnceCallback<void(AutofillClient::PaymentsRpcResult,
-                          const PaymentsClient::UploadCardResponseDetails&)>
-      callback_;
-  PaymentsClient::UploadCardResponseDetails upload_card_response_details_;
-};
-
-class MigrateCardsRequest : public PaymentsRequest {
- public:
-  MigrateCardsRequest(
-      const PaymentsClient::MigrationRequestDetails& request_details,
-      const std::vector<MigratableCreditCard>& migratable_credit_cards,
-      const bool full_sync_enabled,
-      MigrateCardsCallback callback)
-      : request_details_(request_details),
-        migratable_credit_cards_(migratable_credit_cards),
-        full_sync_enabled_(full_sync_enabled),
-        callback_(std::move(callback)) {}
-
-  MigrateCardsRequest(const MigrateCardsRequest&) = delete;
-  MigrateCardsRequest& operator=(const MigrateCardsRequest&) = delete;
-
-  ~MigrateCardsRequest() override = default;
-
-  std::string GetRequestUrlPath() override { return kMigrateCardsRequestPath; }
-
-  std::string GetRequestContentType() override {
-    return "application/x-www-form-urlencoded";
-  }
-
-  std::string GetRequestContent() override {
-    base::Value request_dict(base::Value::Type::DICTIONARY);
-
-    request_dict.SetKey("risk_data_encoded",
-                        BuildRiskDictionary(request_details_.risk_data));
-
-    const std::string& app_locale = request_details_.app_locale;
-    base::Value context(base::Value::Type::DICTIONARY);
-    context.SetKey("language_code", base::Value(app_locale));
-    context.SetKey("billable_service",
-                   base::Value(kMigrateCardsBillableServiceNumber));
-    if (request_details_.billing_customer_number != 0) {
-      context.SetKey("customer_context",
-                     BuildCustomerContextDictionary(
-                         request_details_.billing_customer_number));
-    }
-    request_dict.SetKey("context", std::move(context));
-
-    base::Value chrome_user_context(base::Value::Type::DICTIONARY);
-    chrome_user_context.SetKey("full_sync_enabled",
-                               base::Value(full_sync_enabled_));
-    request_dict.SetKey("chrome_user_context", std::move(chrome_user_context));
-
-    request_dict.SetKey("context_token",
-                        base::Value(request_details_.context_token));
-
-    std::string all_pans_data = std::string();
-    base::Value migrate_cards(base::Value::Type::LIST);
-    for (size_t index = 0; index < migratable_credit_cards_.size(); ++index) {
-      std::string pan_field_name = GetPanFieldName(index);
-      // Generate credit card dictionary.
-      migrate_cards.Append(BuildCreditCardDictionary(
-          migratable_credit_cards_[index].credit_card(), app_locale,
-          pan_field_name));
-      // Append pan data to the |all_pans_data|.
-      all_pans_data +=
-          GetAppendPan(migratable_credit_cards_[index].credit_card(),
-                       app_locale, pan_field_name);
-    }
-    request_dict.SetKey("local_card", std::move(migrate_cards));
-
-    std::string json_request;
-    base::JSONWriter::Write(request_dict, &json_request);
-    std::string request_content = base::StringPrintf(
-        kMigrateCardsRequestFormat,
-        base::EscapeUrlEncodedData(json_request, true).c_str());
-    request_content += all_pans_data;
-    return request_content;
-  }
-
-  void ParseResponse(const base::Value& response) override {
-    const auto* found_list =
-        response.FindKeyOfType("save_result", base::Value::Type::LIST);
-    if (!found_list)
-      return;
-
-    save_result_ =
-        std::make_unique<std::unordered_map<std::string, std::string>>();
-    for (const base::Value& result : found_list->GetListDeprecated()) {
-      if (result.is_dict()) {
-        const std::string* unique_id = result.FindStringKey("unique_id");
-        const std::string* status = result.FindStringKey("status");
-        save_result_->insert(
-            std::make_pair(unique_id ? *unique_id : std::string(),
-                           status ? *status : std::string()));
-      }
-    }
-
-    const std::string* display_text =
-        response.FindStringKey("value_prop_display_text");
-    display_text_ = display_text ? *display_text : std::string();
-  }
-
-  bool IsResponseComplete() override {
-    return !display_text_.empty() && save_result_;
-  }
-
-  void RespondToDelegate(AutofillClient::PaymentsRpcResult result) override {
-    std::move(callback_).Run(result, std::move(save_result_), display_text_);
-  }
-
- private:
-  // Return the pan field name for the encrypted pan based on the |index|.
-  std::string GetPanFieldName(const size_t& index) {
-    return "s7e_1_pan" + std::to_string(index);
-  }
-
-  // Return the formatted pan to append to the end of the request.
-  std::string GetAppendPan(const CreditCard& credit_card,
-                           const std::string& app_locale,
-                           const std::string& pan_field_name) {
-    const std::u16string pan =
-        credit_card.GetInfo(AutofillType(CREDIT_CARD_NUMBER), app_locale);
-    std::string pan_str =
-        base::EscapeUrlEncodedData(base::UTF16ToASCII(pan), true).c_str();
-    std::string append_pan = "&" + pan_field_name + "=" + pan_str;
-    return append_pan;
-  }
-
-  const PaymentsClient::MigrationRequestDetails request_details_;
-  const std::vector<MigratableCreditCard>& migratable_credit_cards_;
-  const bool full_sync_enabled_;
-  MigrateCardsCallback callback_;
-  std::unique_ptr<std::unordered_map<std::string, std::string>> save_result_;
-  std::string display_text_;
-};
-
 }  // namespace
 
 const char PaymentsClient::kRecipientName[] = "recipient_name";
diff --git a/components/autofill/core/browser/payments/payments_requests/get_upload_details_request.cc b/components/autofill/core/browser/payments/payments_requests/get_upload_details_request.cc
new file mode 100644
index 0000000..99c0651
--- /dev/null
+++ b/components/autofill/core/browser/payments/payments_requests/get_upload_details_request.cc
@@ -0,0 +1,179 @@
+// 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 "components/autofill/core/browser/payments/payments_requests/get_upload_details_request.h"
+
+#include <string>
+
+#include "base/json/json_writer.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+
+namespace autofill::payments {
+
+namespace {
+const char kGetUploadDetailsRequestPath[] =
+    "payments/apis/chromepaymentsservice/getdetailsforsavecard";
+}  // namespace
+
+GetUploadDetailsRequest::GetUploadDetailsRequest(
+    const std::vector<AutofillProfile>& addresses,
+    const int detected_values,
+    const std::vector<const char*>& active_experiments,
+    const bool full_sync_enabled,
+    const std::string& app_locale,
+    base::OnceCallback<void(AutofillClient::PaymentsRpcResult,
+                            const std::u16string&,
+                            std::unique_ptr<base::Value>,
+                            std::vector<std::pair<int, int>>)> callback,
+    const int billable_service_number,
+    const int64_t billing_customer_number,
+    PaymentsClient::UploadCardSource upload_card_source)
+    : addresses_(addresses),
+      detected_values_(detected_values),
+      active_experiments_(active_experiments),
+      full_sync_enabled_(full_sync_enabled),
+      app_locale_(app_locale),
+      callback_(std::move(callback)),
+      billable_service_number_(billable_service_number),
+      upload_card_source_(upload_card_source),
+      billing_customer_number_(billing_customer_number) {}
+
+GetUploadDetailsRequest::~GetUploadDetailsRequest() = default;
+
+std::string GetUploadDetailsRequest::GetRequestUrlPath() {
+  return kGetUploadDetailsRequestPath;
+}
+
+std::string GetUploadDetailsRequest::GetRequestContentType() {
+  return "application/json";
+}
+
+std::string GetUploadDetailsRequest::GetRequestContent() {
+  base::Value request_dict(base::Value::Type::DICTIONARY);
+  base::Value context(base::Value::Type::DICTIONARY);
+  context.SetKey("language_code", base::Value(app_locale_));
+  context.SetKey("billable_service", base::Value(billable_service_number_));
+  if (base::FeatureList::IsEnabled(
+          features::kAutofillEnableSendingBcnInGetUploadDetails) &&
+      billing_customer_number_ != 0) {
+    context.SetKey("customer_context",
+                   BuildCustomerContextDictionary(billing_customer_number_));
+  }
+  request_dict.SetKey("context", std::move(context));
+
+  base::Value chrome_user_context(base::Value::Type::DICTIONARY);
+  chrome_user_context.SetKey("full_sync_enabled",
+                             base::Value(full_sync_enabled_));
+  request_dict.SetKey("chrome_user_context", std::move(chrome_user_context));
+
+  base::Value addresses(base::Value::Type::LIST);
+  for (const AutofillProfile& profile : addresses_) {
+    // These addresses are used by Payments to (1) accurately determine the
+    // user's country in order to show the correct legal documents and (2) to
+    // verify that the addresses are valid for their purposes so that we don't
+    // offer save in a case where it would definitely fail (e.g. P.O. boxes if
+    // min address is not possible). The final parameter directs
+    // BuildAddressDictionary to omit names and phone numbers, which aren't
+    // useful for these purposes.
+    addresses.Append(BuildAddressDictionary(profile, app_locale_, false));
+  }
+  request_dict.SetKey("address", std::move(addresses));
+
+  // It's possible we may not have found name/address/CVC in the checkout
+  // flow. The detected_values_ bitmask tells Payments what *was* found, and
+  // Payments will decide if the provided data is enough to offer upload save.
+  request_dict.SetKey("detected_values", base::Value(detected_values_));
+
+  SetActiveExperiments(active_experiments_, request_dict);
+
+  switch (upload_card_source_) {
+    case PaymentsClient::UploadCardSource::UNKNOWN_UPLOAD_CARD_SOURCE:
+      request_dict.SetKey("upload_card_source",
+                          base::Value("UNKNOWN_UPLOAD_CARD_SOURCE"));
+      break;
+    case PaymentsClient::UploadCardSource::UPSTREAM_CHECKOUT_FLOW:
+      request_dict.SetKey("upload_card_source",
+                          base::Value("UPSTREAM_CHECKOUT_FLOW"));
+      break;
+    case PaymentsClient::UploadCardSource::UPSTREAM_SETTINGS_PAGE:
+      request_dict.SetKey("upload_card_source",
+                          base::Value("UPSTREAM_SETTINGS_PAGE"));
+      break;
+    case PaymentsClient::UploadCardSource::UPSTREAM_CARD_OCR:
+      request_dict.SetKey("upload_card_source",
+                          base::Value("UPSTREAM_CARD_OCR"));
+      break;
+    case PaymentsClient::UploadCardSource::LOCAL_CARD_MIGRATION_CHECKOUT_FLOW:
+      request_dict.SetKey("upload_card_source",
+                          base::Value("LOCAL_CARD_MIGRATION_CHECKOUT_FLOW"));
+      break;
+    case PaymentsClient::UploadCardSource::LOCAL_CARD_MIGRATION_SETTINGS_PAGE:
+      request_dict.SetKey("upload_card_source",
+                          base::Value("LOCAL_CARD_MIGRATION_SETTINGS_PAGE"));
+      break;
+    default:
+      NOTREACHED();
+  }
+
+  std::string request_content;
+  base::JSONWriter::Write(request_dict, &request_content);
+  VLOG(3) << "getdetailsforsavecard request body: " << request_content;
+  return request_content;
+}
+
+void GetUploadDetailsRequest::ParseResponse(const base::Value& response) {
+  const auto* context_token = response.FindStringKey("context_token");
+  context_token_ =
+      context_token ? base::UTF8ToUTF16(*context_token) : std::u16string();
+
+  const base::Value* dictionary_value =
+      response.FindKeyOfType("legal_message", base::Value::Type::DICTIONARY);
+  if (dictionary_value)
+    legal_message_ = std::make_unique<base::Value>(dictionary_value->Clone());
+
+  const auto* supported_card_bin_ranges_string =
+      response.FindStringKey("supported_card_bin_ranges_string");
+  supported_card_bin_ranges_ = ParseSupportedCardBinRangesString(
+      supported_card_bin_ranges_string ? *supported_card_bin_ranges_string
+                                       : base::EmptyString());
+}
+
+bool GetUploadDetailsRequest::IsResponseComplete() {
+  return !context_token_.empty() && legal_message_;
+}
+
+void GetUploadDetailsRequest::RespondToDelegate(
+    AutofillClient::PaymentsRpcResult result) {
+  std::move(callback_).Run(result, context_token_, std::move(legal_message_),
+                           supported_card_bin_ranges_);
+}
+
+std::vector<std::pair<int, int>>
+GetUploadDetailsRequest::ParseSupportedCardBinRangesString(
+    const std::string& supported_card_bin_ranges_string) {
+  std::vector<std::pair<int, int>> supported_card_bin_ranges;
+  std::vector<std::string> range_strings =
+      base::SplitString(supported_card_bin_ranges_string, ",",
+                        base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+
+  for (std::string& range_string : range_strings) {
+    std::vector<std::string> range = base::SplitString(
+        range_string, "-", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+    DCHECK(range.size() <= 2);
+    int start;
+    base::StringToInt(range[0], &start);
+    if (range.size() == 1) {
+      supported_card_bin_ranges.emplace_back(start, start);
+    } else {
+      int end;
+      base::StringToInt(range[1], &end);
+      DCHECK_LE(start, end);
+      supported_card_bin_ranges.emplace_back(start, end);
+    }
+  }
+  return supported_card_bin_ranges;
+}
+
+}  // namespace autofill::payments
diff --git a/components/autofill/core/browser/payments/payments_requests/get_upload_details_request.h b/components/autofill/core/browser/payments/payments_requests/get_upload_details_request.h
new file mode 100644
index 0000000..673728df
--- /dev/null
+++ b/components/autofill/core/browser/payments/payments_requests/get_upload_details_request.h
@@ -0,0 +1,76 @@
+// 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 COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_PAYMENTS_REQUESTS_GET_UPLOAD_DETAILS_REQUEST_H_
+#define COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_PAYMENTS_REQUESTS_GET_UPLOAD_DETAILS_REQUEST_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "components/autofill/core/browser/autofill_client.h"
+#include "components/autofill/core/browser/payments/payments_client.h"
+#include "components/autofill/core/browser/payments/payments_requests/payments_request.h"
+
+namespace base {
+class Value;
+}  // namespace base
+
+namespace autofill::payments {
+
+class GetUploadDetailsRequest : public PaymentsRequest {
+ public:
+  GetUploadDetailsRequest(
+      const std::vector<AutofillProfile>& addresses,
+      const int detected_values,
+      const std::vector<const char*>& active_experiments,
+      const bool full_sync_enabled,
+      const std::string& app_locale,
+      base::OnceCallback<void(AutofillClient::PaymentsRpcResult,
+                              const std::u16string&,
+                              std::unique_ptr<base::Value>,
+                              std::vector<std::pair<int, int>>)> callback,
+      const int billable_service_number,
+      const int64_t billing_customer_number,
+      PaymentsClient::UploadCardSource upload_card_source);
+  GetUploadDetailsRequest(const GetUploadDetailsRequest&) = delete;
+  GetUploadDetailsRequest& operator=(const GetUploadDetailsRequest&) = delete;
+  ~GetUploadDetailsRequest() override;
+
+  // PaymentsRequest:
+  std::string GetRequestUrlPath() override;
+  std::string GetRequestContentType() override;
+  std::string GetRequestContent() override;
+  void ParseResponse(const base::Value& response) override;
+  bool IsResponseComplete() override;
+  void RespondToDelegate(AutofillClient::PaymentsRpcResult result) override;
+
+ private:
+  // Helper for ParseResponse(). Input format should be :"1234,30000-55555,765",
+  // where ranges are separated by commas and items separated with a dash means
+  // the start and ends of the range. Items without a dash have the same start
+  // and end (ex. 1234-1234)
+  std::vector<std::pair<int, int>> ParseSupportedCardBinRangesString(
+      const std::string& supported_card_bin_ranges_string);
+
+  const std::vector<AutofillProfile> addresses_;
+  const int detected_values_;
+  const std::vector<const char*> active_experiments_;
+  const bool full_sync_enabled_;
+  std::string app_locale_;
+  base::OnceCallback<void(AutofillClient::PaymentsRpcResult,
+                          const std::u16string&,
+                          std::unique_ptr<base::Value>,
+                          std::vector<std::pair<int, int>>)>
+      callback_;
+  std::u16string context_token_;
+  std::unique_ptr<base::Value> legal_message_;
+  std::vector<std::pair<int, int>> supported_card_bin_ranges_;
+  const int billable_service_number_;
+  PaymentsClient::UploadCardSource upload_card_source_;
+  const int64_t billing_customer_number_;
+};
+
+}  // namespace autofill::payments
+
+#endif  // COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_PAYMENTS_REQUESTS_GET_UPLOAD_DETAILS_REQUEST_H_
diff --git a/components/autofill/core/browser/payments/payments_requests/migrate_cards_request.cc b/components/autofill/core/browser/payments/payments_requests/migrate_cards_request.cc
new file mode 100644
index 0000000..f1365c48
--- /dev/null
+++ b/components/autofill/core/browser/payments/payments_requests/migrate_cards_request.cc
@@ -0,0 +1,141 @@
+// 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 "components/autofill/core/browser/payments/payments_requests/migrate_cards_request.h"
+
+#include <string>
+
+#include "base/json/json_writer.h"
+#include "base/strings/escape.h"
+#include "base/strings/utf_string_conversions.h"
+#include "components/autofill/core/browser/payments/local_card_migration_manager.h"
+
+namespace autofill::payments {
+
+namespace {
+const char kMigrateCardsRequestPath[] =
+    "payments/apis-secure/chromepaymentsservice/migratecards"
+    "?s7e_suffix=chromewallet";
+const char kMigrateCardsRequestFormat[] =
+    "requestContentType=application/json; charset=utf-8&request=%s";
+}  // namespace
+
+MigrateCardsRequest::MigrateCardsRequest(
+    const PaymentsClient::MigrationRequestDetails& request_details,
+    const std::vector<MigratableCreditCard>& migratable_credit_cards,
+    const bool full_sync_enabled,
+    MigrateCardsCallback callback)
+    : request_details_(request_details),
+      migratable_credit_cards_(migratable_credit_cards),
+      full_sync_enabled_(full_sync_enabled),
+      callback_(std::move(callback)) {}
+
+MigrateCardsRequest::~MigrateCardsRequest() = default;
+
+std::string MigrateCardsRequest::GetRequestUrlPath() {
+  return kMigrateCardsRequestPath;
+}
+
+std::string MigrateCardsRequest::GetRequestContentType() {
+  return "application/x-www-form-urlencoded";
+}
+
+std::string MigrateCardsRequest::GetRequestContent() {
+  base::Value request_dict(base::Value::Type::DICTIONARY);
+
+  request_dict.SetKey("risk_data_encoded",
+                      BuildRiskDictionary(request_details_.risk_data));
+
+  const std::string& app_locale = request_details_.app_locale;
+  base::Value context(base::Value::Type::DICTIONARY);
+  context.SetKey("language_code", base::Value(app_locale));
+  context.SetKey("billable_service",
+                 base::Value(kMigrateCardsBillableServiceNumber));
+  if (request_details_.billing_customer_number != 0) {
+    context.SetKey("customer_context",
+                   BuildCustomerContextDictionary(
+                       request_details_.billing_customer_number));
+  }
+  request_dict.SetKey("context", std::move(context));
+
+  base::Value chrome_user_context(base::Value::Type::DICTIONARY);
+  chrome_user_context.SetKey("full_sync_enabled",
+                             base::Value(full_sync_enabled_));
+  request_dict.SetKey("chrome_user_context", std::move(chrome_user_context));
+
+  request_dict.SetKey("context_token",
+                      base::Value(request_details_.context_token));
+
+  std::string all_pans_data = std::string();
+  base::Value migrate_cards(base::Value::Type::LIST);
+  for (size_t index = 0; index < migratable_credit_cards_.size(); ++index) {
+    std::string pan_field_name = GetPanFieldName(index);
+    // Generate credit card dictionary.
+    migrate_cards.Append(
+        BuildCreditCardDictionary(migratable_credit_cards_[index].credit_card(),
+                                  app_locale, pan_field_name));
+    // Append pan data to the |all_pans_data|.
+    all_pans_data += GetAppendPan(migratable_credit_cards_[index].credit_card(),
+                                  app_locale, pan_field_name);
+  }
+  request_dict.SetKey("local_card", std::move(migrate_cards));
+
+  std::string json_request;
+  base::JSONWriter::Write(request_dict, &json_request);
+  std::string request_content = base::StringPrintf(
+      kMigrateCardsRequestFormat,
+      base::EscapeUrlEncodedData(json_request, true).c_str());
+  request_content += all_pans_data;
+  return request_content;
+}
+
+void MigrateCardsRequest::ParseResponse(const base::Value& response) {
+  const auto* found_list =
+      response.FindKeyOfType("save_result", base::Value::Type::LIST);
+  if (!found_list)
+    return;
+
+  save_result_ =
+      std::make_unique<std::unordered_map<std::string, std::string>>();
+  for (const base::Value& result : found_list->GetListDeprecated()) {
+    if (result.is_dict()) {
+      const std::string* unique_id = result.FindStringKey("unique_id");
+      const std::string* status = result.FindStringKey("status");
+      save_result_->insert(
+          std::make_pair(unique_id ? *unique_id : std::string(),
+                         status ? *status : std::string()));
+    }
+  }
+
+  const std::string* display_text =
+      response.FindStringKey("value_prop_display_text");
+  display_text_ = display_text ? *display_text : std::string();
+}
+
+bool MigrateCardsRequest::IsResponseComplete() {
+  return !display_text_.empty() && save_result_;
+}
+
+void MigrateCardsRequest::RespondToDelegate(
+    AutofillClient::PaymentsRpcResult result) {
+  std::move(callback_).Run(result, std::move(save_result_), display_text_);
+}
+
+std::string MigrateCardsRequest::GetPanFieldName(const size_t& index) {
+  return "s7e_1_pan" + base::NumberToString(index);
+}
+
+std::string MigrateCardsRequest::GetAppendPan(
+    const CreditCard& credit_card,
+    const std::string& app_locale,
+    const std::string& pan_field_name) {
+  const std::u16string pan =
+      credit_card.GetInfo(AutofillType(CREDIT_CARD_NUMBER), app_locale);
+  std::string pan_str =
+      base::EscapeUrlEncodedData(base::UTF16ToASCII(pan), true).c_str();
+  std::string append_pan = "&" + pan_field_name + "=" + pan_str;
+  return append_pan;
+}
+
+}  // namespace autofill::payments
diff --git a/components/autofill/core/browser/payments/payments_requests/migrate_cards_request.h b/components/autofill/core/browser/payments/payments_requests/migrate_cards_request.h
new file mode 100644
index 0000000..6784422
--- /dev/null
+++ b/components/autofill/core/browser/payments/payments_requests/migrate_cards_request.h
@@ -0,0 +1,59 @@
+// 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 COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_PAYMENTS_REQUESTS_MIGRATE_CARDS_REQUEST_H_
+#define COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_PAYMENTS_REQUESTS_MIGRATE_CARDS_REQUEST_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "components/autofill/core/browser/autofill_client.h"
+#include "components/autofill/core/browser/payments/payments_client.h"
+#include "components/autofill/core/browser/payments/payments_requests/payments_request.h"
+
+namespace base {
+class Value;
+}  // namespace base
+
+namespace autofill::payments {
+
+class MigrateCardsRequest : public PaymentsRequest {
+ public:
+  MigrateCardsRequest(
+      const PaymentsClient::MigrationRequestDetails& request_details,
+      const std::vector<MigratableCreditCard>& migratable_credit_cards,
+      const bool full_sync_enabled,
+      MigrateCardsCallback callback);
+  MigrateCardsRequest(const MigrateCardsRequest&) = delete;
+  MigrateCardsRequest& operator=(const MigrateCardsRequest&) = delete;
+  ~MigrateCardsRequest() override;
+
+  // PaymentsRequest:
+  std::string GetRequestUrlPath() override;
+  std::string GetRequestContentType() override;
+  std::string GetRequestContent() override;
+  void ParseResponse(const base::Value& response) override;
+  bool IsResponseComplete() override;
+  void RespondToDelegate(AutofillClient::PaymentsRpcResult result) override;
+
+ private:
+  // Return the pan field name for the encrypted pan based on the |index|.
+  std::string GetPanFieldName(const size_t& index);
+
+  // Return the formatted pan to append to the end of the request.
+  std::string GetAppendPan(const CreditCard& credit_card,
+                           const std::string& app_locale,
+                           const std::string& pan_field_name);
+
+  const PaymentsClient::MigrationRequestDetails request_details_;
+  const std::vector<MigratableCreditCard>& migratable_credit_cards_;
+  const bool full_sync_enabled_;
+  MigrateCardsCallback callback_;
+  std::unique_ptr<std::unordered_map<std::string, std::string>> save_result_;
+  std::string display_text_;
+};
+
+}  // namespace autofill::payments
+
+#endif  // COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_PAYMENTS_REQUESTS_MIGRATE_CARDS_REQUEST_H_
diff --git a/components/autofill/core/browser/payments/payments_requests/payments_request.cc b/components/autofill/core/browser/payments/payments_requests/payments_request.cc
index 0de08f93..b6c85788 100644
--- a/components/autofill/core/browser/payments/payments_requests/payments_request.cc
+++ b/components/autofill/core/browser/payments/payments_requests/payments_request.cc
@@ -7,9 +7,9 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/values.h"
 #include "build/build_config.h"
+#include "components/autofill/core/browser/payments/payments_client.h"
 
-namespace autofill {
-namespace payments {
+namespace autofill::payments {
 
 PaymentsRequest::~PaymentsRequest() = default;
 
@@ -41,5 +41,107 @@
   return customer_context;
 }
 
-}  // namespace payments
-}  // namespace autofill
+void PaymentsRequest::SetActiveExperiments(
+    const std::vector<const char*>& active_experiments,
+    base::Value& request_dict) {
+  if (active_experiments.empty())
+    return;
+
+  base::Value active_chrome_experiments(base::Value::Type::LIST);
+  for (const char* it : active_experiments)
+    active_chrome_experiments.Append(it);
+
+  request_dict.SetKey("active_chrome_experiments",
+                      std::move(active_chrome_experiments));
+}
+
+base::Value PaymentsRequest::BuildAddressDictionary(
+    const AutofillProfile& profile,
+    const std::string& app_locale,
+    bool include_non_location_data) {
+  base::Value postal_address(base::Value::Type::DICTIONARY);
+
+  if (include_non_location_data) {
+    SetStringIfNotEmpty(profile, NAME_FULL, app_locale,
+                        PaymentsClient::kRecipientName, postal_address);
+  }
+
+  base::Value address_lines(base::Value::Type::LIST);
+  AppendStringIfNotEmpty(profile, ADDRESS_HOME_LINE1, app_locale,
+                         address_lines);
+  AppendStringIfNotEmpty(profile, ADDRESS_HOME_LINE2, app_locale,
+                         address_lines);
+  AppendStringIfNotEmpty(profile, ADDRESS_HOME_LINE3, app_locale,
+                         address_lines);
+  if (!address_lines.GetListDeprecated().empty())
+    postal_address.SetKey("address_line", std::move(address_lines));
+
+  SetStringIfNotEmpty(profile, ADDRESS_HOME_CITY, app_locale, "locality_name",
+                      postal_address);
+  SetStringIfNotEmpty(profile, ADDRESS_HOME_STATE, app_locale,
+                      "administrative_area_name", postal_address);
+  SetStringIfNotEmpty(profile, ADDRESS_HOME_ZIP, app_locale,
+                      "postal_code_number", postal_address);
+
+  // Use GetRawInfo to get a country code instead of the country name:
+  const std::u16string country_code = profile.GetRawInfo(ADDRESS_HOME_COUNTRY);
+  if (!country_code.empty())
+    postal_address.SetKey("country_name_code", base::Value(country_code));
+
+  base::Value address(base::Value::Type::DICTIONARY);
+  address.SetKey("postal_address", std::move(postal_address));
+
+  if (include_non_location_data) {
+    SetStringIfNotEmpty(profile, PHONE_HOME_WHOLE_NUMBER, app_locale,
+                        PaymentsClient::kPhoneNumber, address);
+  }
+
+  return address;
+}
+
+base::Value PaymentsRequest::BuildCreditCardDictionary(
+    const CreditCard& credit_card,
+    const std::string& app_locale,
+    const std::string& pan_field_name) {
+  base::Value card(base::Value::Type::DICTIONARY);
+  card.SetKey("unique_id", base::Value(credit_card.guid()));
+
+  const std::u16string exp_month =
+      credit_card.GetInfo(AutofillType(CREDIT_CARD_EXP_MONTH), app_locale);
+  const std::u16string exp_year = credit_card.GetInfo(
+      AutofillType(CREDIT_CARD_EXP_4_DIGIT_YEAR), app_locale);
+  int value = 0;
+  if (base::StringToInt(exp_month, &value))
+    card.SetKey("expiration_month", base::Value(value));
+  if (base::StringToInt(exp_year, &value))
+    card.SetKey("expiration_year", base::Value(value));
+  SetStringIfNotEmpty(credit_card, CREDIT_CARD_NAME_FULL, app_locale,
+                      "cardholder_name", card);
+
+  if (credit_card.HasNonEmptyValidNickname())
+    card.SetKey("nickname", base::Value(credit_card.nickname()));
+
+  card.SetKey("encrypted_pan", base::Value("__param:" + pan_field_name));
+  return card;
+}
+
+void PaymentsRequest::AppendStringIfNotEmpty(const AutofillProfile& profile,
+                                             const ServerFieldType& type,
+                                             const std::string& app_locale,
+                                             base::Value& list) {
+  const std::u16string value = profile.GetInfo(type, app_locale);
+  if (!value.empty())
+    list.Append(value);
+}
+
+void PaymentsRequest::SetStringIfNotEmpty(const AutofillDataModel& profile,
+                                          const ServerFieldType& type,
+                                          const std::string& app_locale,
+                                          const std::string& path,
+                                          base::Value& dictionary) {
+  const std::u16string value = profile.GetInfo(AutofillType(type), app_locale);
+  if (!value.empty())
+    dictionary.SetKey(path, base::Value(value));
+}
+
+}  // namespace autofill::payments
diff --git a/components/autofill/core/browser/payments/payments_requests/payments_request.h b/components/autofill/core/browser/payments/payments_requests/payments_request.h
index be88bca..dd2f017 100644
--- a/components/autofill/core/browser/payments/payments_requests/payments_request.h
+++ b/components/autofill/core/browser/payments/payments_requests/payments_request.h
@@ -8,13 +8,15 @@
 #include <string>
 
 #include "components/autofill/core/browser/autofill_client.h"
+#include "components/autofill/core/browser/data_model/autofill_profile.h"
+#include "components/autofill/core/browser/data_model/credit_card.h"
+#include "components/autofill/core/common/autofill_payments_features.h"
 
 namespace base {
 class Value;
 }
 
-namespace autofill {
-namespace payments {
+namespace autofill::payments {
 
 // Shared class for the various Payments request types.
 class PaymentsRequest {
@@ -47,9 +49,42 @@
 
   // Shared helper function to build the customer context sent in the request.
   base::Value BuildCustomerContextDictionary(int64_t external_customer_id);
+
+  // Shared helper function that populates the list of active experiments that
+  // affect either the data sent in payments RPCs or whether the RPCs are sent
+  // or not.
+  void SetActiveExperiments(const std::vector<const char*>& active_experiments,
+                            base::Value& request_dict);
+
+  // Shared helper functoin that returns a dictionary with the structure
+  // expected by Payments RPCs, containing each of the fields in |profile|,
+  // formatted according to |app_locale|. If |include_non_location_data| is
+  // false, the name and phone number in |profile| are not included.
+  base::Value BuildAddressDictionary(const AutofillProfile& profile,
+                                     const std::string& app_locale,
+                                     bool include_non_location_data);
+
+  // Shared helper function that returns a dictionary of the credit card with
+  // the structure expected by Payments RPCs, containing expiration month,
+  // expiration year and cardholder name (if any) fields in |credit_card|,
+  // formatted according to |app_locale|. |pan_field_name| is the field name for
+  // the encrypted pan. We use each credit card's guid as the unique id.
+  base::Value BuildCreditCardDictionary(const CreditCard& credit_card,
+                                        const std::string& app_locale,
+                                        const std::string& pan_field_name);
+
+  // Shared helper functions for string operations.
+  void AppendStringIfNotEmpty(const AutofillProfile& profile,
+                              const ServerFieldType& type,
+                              const std::string& app_locale,
+                              base::Value& list);
+  void SetStringIfNotEmpty(const AutofillDataModel& profile,
+                           const ServerFieldType& type,
+                           const std::string& app_locale,
+                           const std::string& path,
+                           base::Value& dictionary);
 };
 
-}  // namespace payments
-}  // namespace autofill
+}  // namespace autofill::payments
 
 #endif  // COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_PAYMENTS_REQUESTS_PAYMENTS_REQUEST_H_
diff --git a/components/autofill/core/browser/payments/payments_requests/upload_card_request.cc b/components/autofill/core/browser/payments/payments_requests/upload_card_request.cc
new file mode 100644
index 0000000..dfe4928
--- /dev/null
+++ b/components/autofill/core/browser/payments/payments_requests/upload_card_request.cc
@@ -0,0 +1,172 @@
+// 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 "components/autofill/core/browser/payments/payments_requests/upload_card_request.h"
+
+#include <string>
+
+#include "base/json/json_writer.h"
+#include "base/strings/escape.h"
+#include "base/strings/utf_string_conversions.h"
+
+namespace autofill::payments {
+
+namespace {
+const char kUploadCardRequestPath[] =
+    "payments/apis-secure/chromepaymentsservice/savecard"
+    "?s7e_suffix=chromewallet";
+const char kUploadCardRequestFormat[] =
+    "requestContentType=application/json; charset=utf-8&request=%s"
+    "&s7e_1_pan=%s&s7e_13_cvc=%s";
+const char kUploadCardRequestFormatWithoutCvc[] =
+    "requestContentType=application/json; charset=utf-8&request=%s"
+    "&s7e_1_pan=%s";
+}  // namespace
+
+UploadCardRequest::UploadCardRequest(
+    const PaymentsClient::UploadRequestDetails& request_details,
+    const bool full_sync_enabled,
+    base::OnceCallback<void(AutofillClient::PaymentsRpcResult,
+                            const PaymentsClient::UploadCardResponseDetails&)>
+        callback)
+    : request_details_(request_details),
+      full_sync_enabled_(full_sync_enabled),
+      callback_(std::move(callback)) {}
+
+UploadCardRequest::~UploadCardRequest() = default;
+
+std::string UploadCardRequest::GetRequestUrlPath() {
+  return kUploadCardRequestPath;
+}
+
+std::string UploadCardRequest::GetRequestContentType() {
+  return "application/x-www-form-urlencoded";
+}
+
+std::string UploadCardRequest::GetRequestContent() {
+  base::Value request_dict(base::Value::Type::DICTIONARY);
+  request_dict.SetKey("encrypted_pan", base::Value("__param:s7e_1_pan"));
+  if (!request_details_.cvc.empty())
+    request_dict.SetKey("encrypted_cvc", base::Value("__param:s7e_13_cvc"));
+  request_dict.SetKey("risk_data_encoded",
+                      BuildRiskDictionary(request_details_.risk_data));
+
+  const std::string& app_locale = request_details_.app_locale;
+  base::Value context(base::Value::Type::DICTIONARY);
+  context.SetKey("language_code", base::Value(app_locale));
+  context.SetKey("billable_service",
+                 base::Value(kUploadCardBillableServiceNumber));
+  if (request_details_.billing_customer_number != 0) {
+    context.SetKey("customer_context",
+                   BuildCustomerContextDictionary(
+                       request_details_.billing_customer_number));
+  }
+  request_dict.SetKey("context", std::move(context));
+
+  base::Value chrome_user_context(base::Value::Type::DICTIONARY);
+  chrome_user_context.SetKey("full_sync_enabled",
+                             base::Value(full_sync_enabled_));
+  request_dict.SetKey("chrome_user_context", std::move(chrome_user_context));
+
+  SetStringIfNotEmpty(request_details_.card, CREDIT_CARD_NAME_FULL, app_locale,
+                      "cardholder_name", request_dict);
+
+  base::Value addresses(base::Value::Type::LIST);
+  for (const AutofillProfile& profile : request_details_.profiles) {
+    addresses.Append(BuildAddressDictionary(profile, app_locale, true));
+  }
+  request_dict.SetKey("address", std::move(addresses));
+
+  request_dict.SetKey("context_token",
+                      base::Value(request_details_.context_token));
+
+  int value = 0;
+  const std::u16string exp_month = request_details_.card.GetInfo(
+      AutofillType(CREDIT_CARD_EXP_MONTH), app_locale);
+  const std::u16string exp_year = request_details_.card.GetInfo(
+      AutofillType(CREDIT_CARD_EXP_4_DIGIT_YEAR), app_locale);
+  if (base::StringToInt(exp_month, &value))
+    request_dict.SetKey("expiration_month", base::Value(value));
+  if (base::StringToInt(exp_year, &value))
+    request_dict.SetKey("expiration_year", base::Value(value));
+
+  if (request_details_.card.HasNonEmptyValidNickname()) {
+    request_dict.SetKey("nickname",
+                        base::Value(request_details_.card.nickname()));
+  }
+
+  SetActiveExperiments(request_details_.active_experiments, request_dict);
+
+  const std::u16string pan = request_details_.card.GetInfo(
+      AutofillType(CREDIT_CARD_NUMBER), app_locale);
+  std::string json_request;
+  base::JSONWriter::Write(request_dict, &json_request);
+  std::string request_content;
+  if (request_details_.cvc.empty()) {
+    request_content = base::StringPrintf(
+        kUploadCardRequestFormatWithoutCvc,
+        base::EscapeUrlEncodedData(json_request, true).c_str(),
+        base::EscapeUrlEncodedData(base::UTF16ToASCII(pan), true).c_str());
+  } else {
+    request_content = base::StringPrintf(
+        kUploadCardRequestFormat,
+        base::EscapeUrlEncodedData(json_request, true).c_str(),
+        base::EscapeUrlEncodedData(base::UTF16ToASCII(pan), true).c_str(),
+        base::EscapeUrlEncodedData(base::UTF16ToASCII(request_details_.cvc),
+                                   true)
+            .c_str());
+  }
+  VLOG(3) << "savecard request body: " << request_content;
+  return request_content;
+}
+
+void UploadCardRequest::ParseResponse(const base::Value& response) {
+  const std::string* credit_card_id = response.FindStringKey("credit_card_id");
+  upload_card_response_details_.server_id =
+      credit_card_id ? *credit_card_id : std::string();
+
+  const std::string* response_instrument_id =
+      response.FindStringKey("instrument_id");
+  if (response_instrument_id) {
+    int64_t instrument_id;
+    if (base::StringToInt64(base::StringPiece(*response_instrument_id),
+                            &instrument_id)) {
+      upload_card_response_details_.instrument_id = instrument_id;
+    }
+  }
+
+  const auto* virtual_card_metadata = response.FindKeyOfType(
+      "virtual_card_metadata", base::Value::Type::DICTIONARY);
+  if (virtual_card_metadata) {
+    const std::string* virtual_card_enrollment_status =
+        virtual_card_metadata->FindStringKey("status");
+    if (virtual_card_enrollment_status) {
+      if (*virtual_card_enrollment_status == "ENROLLED") {
+        upload_card_response_details_.virtual_card_enrollment_state =
+            CreditCard::VirtualCardEnrollmentState::ENROLLED;
+      } else if (*virtual_card_enrollment_status == "ENROLLMENT_ELIGIBLE") {
+        upload_card_response_details_.virtual_card_enrollment_state =
+            CreditCard::VirtualCardEnrollmentState::UNENROLLED_AND_ELIGIBLE;
+      } else {
+        upload_card_response_details_.virtual_card_enrollment_state =
+            CreditCard::VirtualCardEnrollmentState::UNENROLLED_AND_NOT_ELIGIBLE;
+      }
+    }
+  }
+
+  const std::string* card_art_url = response.FindStringKey("card_art_url");
+  upload_card_response_details_.card_art_url =
+      card_art_url ? GURL(*card_art_url) : GURL();
+}
+
+bool UploadCardRequest::IsResponseComplete() {
+  return true;
+}
+
+void UploadCardRequest::RespondToDelegate(
+    AutofillClient::PaymentsRpcResult result) {
+  std::move(callback_).Run(result, upload_card_response_details_);
+}
+
+}  // namespace autofill::payments
diff --git a/components/autofill/core/browser/payments/payments_requests/upload_card_request.h b/components/autofill/core/browser/payments/payments_requests/upload_card_request.h
new file mode 100644
index 0000000..80b0f86
--- /dev/null
+++ b/components/autofill/core/browser/payments/payments_requests/upload_card_request.h
@@ -0,0 +1,52 @@
+// 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 COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_PAYMENTS_REQUESTS_UPLOAD_CARD_REQUEST_H_
+#define COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_PAYMENTS_REQUESTS_UPLOAD_CARD_REQUEST_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "components/autofill/core/browser/autofill_client.h"
+#include "components/autofill/core/browser/payments/payments_client.h"
+#include "components/autofill/core/browser/payments/payments_requests/payments_request.h"
+
+namespace base {
+class Value;
+}  // namespace base
+
+namespace autofill::payments {
+
+class UploadCardRequest : public PaymentsRequest {
+ public:
+  UploadCardRequest(
+      const PaymentsClient::UploadRequestDetails& request_details,
+      const bool full_sync_enabled,
+      base::OnceCallback<void(AutofillClient::PaymentsRpcResult,
+                              const PaymentsClient::UploadCardResponseDetails&)>
+          callback);
+  UploadCardRequest(const UploadCardRequest&) = delete;
+  UploadCardRequest& operator=(const UploadCardRequest&) = delete;
+  ~UploadCardRequest() override;
+
+  // PaymentsRequest:
+  std::string GetRequestUrlPath() override;
+  std::string GetRequestContentType() override;
+  std::string GetRequestContent() override;
+  void ParseResponse(const base::Value& response) override;
+  bool IsResponseComplete() override;
+  void RespondToDelegate(AutofillClient::PaymentsRpcResult result) override;
+
+ private:
+  const PaymentsClient::UploadRequestDetails request_details_;
+  const bool full_sync_enabled_;
+  base::OnceCallback<void(AutofillClient::PaymentsRpcResult,
+                          const PaymentsClient::UploadCardResponseDetails&)>
+      callback_;
+  PaymentsClient::UploadCardResponseDetails upload_card_response_details_;
+};
+
+}  // namespace autofill::payments
+
+#endif  // COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_PAYMENTS_REQUESTS_UPLOAD_CARD_REQUEST_H_
diff --git a/components/autofill/core/common/autofill_features.cc b/components/autofill/core/common/autofill_features.cc
index 2d72a55f..4c58970 100644
--- a/components/autofill/core/common/autofill_features.cc
+++ b/components/autofill/core/common/autofill_features.cc
@@ -331,7 +331,7 @@
 // shown. This is to prevent double clicks accidentally accepting suggestions.
 // TODO(crbug/1279268): Remove once launched.
 const base::Feature kAutofillIgnoreEarlyClicksOnPopup{
-    "AutofillIgnoreEarlyClicksOnPopup", base::FEATURE_DISABLED_BY_DEFAULT};
+    "AutofillIgnoreEarlyClicksOnPopup", base::FEATURE_ENABLED_BY_DEFAULT};
 
 // The duration for which clicks on the just-shown Autofill popup should be
 // ignored if AutofillIgnoreEarlyClicksOnPopup is enabled.
@@ -340,7 +340,7 @@
 const base::FeatureParam<base::TimeDelta>
     kAutofillIgnoreEarlyClicksOnPopupDuration{
         &kAutofillIgnoreEarlyClicksOnPopup, "duration",
-        base::Milliseconds(500)};
+        base::Milliseconds(250)};
 
 // When enabled, only changed values are highlighted in preview mode.
 // TODO(crbug/1248585): Remove when launched.
diff --git a/components/autofill/core/common/form_data.h b/components/autofill/core/common/form_data.h
index 1f65bc4c..4574e89 100644
--- a/components/autofill/core/common/form_data.h
+++ b/components/autofill/core/common/form_data.h
@@ -173,7 +173,6 @@
   // The name attribute of the form.
   std::u16string name_attribute;
 
-  // NOTE: update IdentityComparator                when adding new a member.
   // NOTE: update SameFormAs()            if needed when adding new a member.
   // NOTE: update SimilarFormAs()         if needed when adding new a member.
   // NOTE: update DynamicallySameFormAs() if needed when adding new a member.
diff --git a/components/autofill/core/common/form_field_data.cc b/components/autofill/core/common/form_field_data.cc
index abe5b86..2cacc08 100644
--- a/components/autofill/core/common/form_field_data.cc
+++ b/components/autofill/core/common/form_field_data.cc
@@ -268,12 +268,6 @@
   return DynamicIdentityTuple(*this) == DynamicIdentityTuple(field);
 }
 
-bool FormFieldData::IdentityComparator::operator()(
-    const FormFieldData& a,
-    const FormFieldData& b) const {
-  return IdentityTuple(a) < IdentityTuple(b);
-}
-
 bool FormFieldData::IsTextInputElement() const {
   return form_control_type == "text" || form_control_type == "password" ||
          form_control_type == "search" || form_control_type == "tel" ||
diff --git a/components/autofill/core/common/form_field_data.h b/components/autofill/core/common/form_field_data.h
index ba879c2..86cf599f 100644
--- a/components/autofill/core/common/form_field_data.h
+++ b/components/autofill/core/common/form_field_data.h
@@ -70,13 +70,6 @@
   using RoleAttribute = mojom::FormFieldData_RoleAttribute;
   using LabelSource = mojom::FormFieldData_LabelSource;
 
-  // TODO(crbug/1211834): This comparator is deprecated.
-  // Less-than relation for STL containers. Compares only members needed to
-  // uniquely identify a field.
-  struct IdentityComparator {
-    bool operator()(const FormFieldData& a, const FormFieldData& b) const;
-  };
-
   // Returns true if all members of fields |a| and |b| are identical.
   static bool DeepEqual(const FormFieldData& a, const FormFieldData& b);
 
@@ -149,7 +142,6 @@
 #define EXPECT_EQ_UNIQUE_ID(expected, actual)
 #endif
 
-  // NOTE: update IdentityComparator                 when adding new a member.
   // NOTE: update SameFieldAs()            if needed when adding new a member.
   // NOTE: update SimilarFieldAs()         if needed when adding new a member.
   // NOTE: update DynamicallySameFieldAs() if needed when adding new a member.
diff --git a/components/certificate_transparency/data/log_list.json b/components/certificate_transparency/data/log_list.json
index d86f90d..4c0f2a7 100644
--- a/components/certificate_transparency/data/log_list.json
+++ b/components/certificate_transparency/data/log_list.json
@@ -1,6 +1,6 @@
 {
-  "version": "9.30",
-  "log_list_timestamp": "2022-06-01T12:54:29Z",
+  "version": "9.31",
+  "log_list_timestamp": "2022-06-02T12:54:07Z",
   "operators": [
     {
       "name": "Google",
diff --git a/components/desks_storage/core/local_desk_data_manager.cc b/components/desks_storage/core/local_desk_data_manager.cc
index 7db7914..3b48f62 100644
--- a/components/desks_storage/core/local_desk_data_manager.cc
+++ b/components/desks_storage/core/local_desk_data_manager.cc
@@ -142,14 +142,19 @@
           user_data_dir_path.AppendASCII(kSavedDeskDirectoryName)),
       account_id_(account_id),
       cache_status_(CacheStatus::kNotInitialized) {
-  // Load the cache.
-  task_runner_->PostTask(
-      FROM_HERE, base::BindOnce(&LocalDeskDataManager::EnsureCacheIsLoaded,
-                                base::Unretained(this)));
   // Populate `saved_desks_list_` with all the desk types.
   for (const auto& desk_type : kDeskTypes) {
     saved_desks_list_[desk_type];
   }
+  auto entries = std::make_unique<
+      std::map<base::GUID, std::unique_ptr<ash::DeskTemplate>>>();
+  // Load the cache.
+  task_runner_->PostTaskAndReply(
+      FROM_HERE,
+      base::BindOnce(&LocalDeskDataManager::EnsureCacheIsLoaded,
+                     base::Unretained(this), entries.get()),
+      base::BindOnce(&LocalDeskDataManager::MoveEntriesIntoCache,
+                     weak_ptr_factory_.GetWeakPtr(), std::move(entries)));
 }
 
 LocalDeskDataManager::~LocalDeskDataManager() = default;
@@ -158,31 +163,66 @@
     DeskModel::GetAllEntriesCallback callback) {
   auto status = std::make_unique<DeskModel::GetAllEntriesStatus>();
   auto entries = std::make_unique<std::vector<const ash::DeskTemplate*>>();
+  if (cache_status_ != CacheStatus::kOk) {
+    *status = DeskModel::GetAllEntriesStatus::kFailure;
+    std::move(callback).Run(*status, *entries);
+    return;
+  }
+  for (const auto& it : policy_entries_)
+    entries->push_back(it.get());
 
+  for (auto& saved_desk : saved_desks_list_) {
+    for (auto& [uuid, template_entry] : saved_desk.second) {
+      DCHECK_EQ(uuid, template_entry->uuid());
+      entries->push_back(template_entry.get());
+    }
+  }
   // It's safe to pass base::Unretained(this) since the LocalDeskDataManager is
   // a long-lived object that should persist during user session.
   task_runner_->PostTaskAndReply(
       FROM_HERE,
       base::BindOnce(&LocalDeskDataManager::GetAllEntriesTask,
-                     base::Unretained(this), status.get(), entries.get()),
+                     base::Unretained(this), status.get()),
       base::BindOnce(&LocalDeskDataManager::OnGetAllEntries,
                      weak_ptr_factory_.GetWeakPtr(), std::move(status),
                      std::move(entries), std::move(callback)));
 }
 
 void LocalDeskDataManager::GetEntryByUUID(
-    const std::string& uuid,
+    const std::string& uuid_str,
     DeskModel::GetEntryByUuidCallback callback) {
   auto status = std::make_unique<DeskModel::GetEntryByUuidStatus>();
   auto entry_ptr = std::make_unique<ash::DeskTemplate*>();
+
+  if (cache_status_ != LocalDeskDataManager::CacheStatus::kOk) {
+    *status = DeskModel::GetEntryByUuidStatus::kFailure;
+    std::move(callback).Run(*status, nullptr);
+    return;
+  }
+
+  const base::GUID uuid = base::GUID::ParseCaseInsensitive(uuid_str);
+  if (!uuid.is_valid()) {
+    *status = DeskModel::GetEntryByUuidStatus::kInvalidUuid;
+  }
+  const ash::DeskTemplateType desk_type = GetDeskTypeOfUuid(uuid);
+
+  const auto cache_entry = saved_desks_list_[desk_type].find(uuid);
+
+  if (cache_entry != saved_desks_list_[desk_type].end()) {
+    *status = DeskModel::GetEntryByUuidStatus::kOk;
+    *entry_ptr = cache_entry->second.get();
+  } else {
+    *status = DeskModel::GetEntryByUuidStatus::kNotFound;
+  }
+
   task_runner_->PostTaskAndReply(
       FROM_HERE,
       base::BindOnce(&LocalDeskDataManager::GetEntryByUuidTask,
-                     base::Unretained(this), uuid, status.get(),
-                     entry_ptr.get()),
+                     base::Unretained(this), uuid_str, status.get()),
       base::BindOnce(&LocalDeskDataManager::OnGetEntryByUuid,
-                     weak_ptr_factory_.GetWeakPtr(), uuid, std::move(status),
-                     std::move(entry_ptr), std::move(callback)));
+                     weak_ptr_factory_.GetWeakPtr(), uuid_str,
+                     std::move(status), std::move(entry_ptr),
+                     std::move(callback)));
 }
 
 void LocalDeskDataManager::AddOrUpdateEntry(
@@ -235,6 +275,18 @@
   std::unique_ptr<ash::DeskTemplate> deserialize_entry =
       desk_template_conversion::ParseDeskTemplateFromSource(
           template_base_value, new_entry->source());
+  bool is_update =
+      std::find_if(
+          saved_desks_list_[desk_type].begin(),
+          saved_desks_list_[desk_type].end(),
+          [&uuid](const std::pair<const base::GUID,
+                                  std::unique_ptr<ash::DeskTemplate>>& entry) {
+            return entry.first == uuid;
+          }) != saved_desks_list_[desk_type].end();
+  std::unique_ptr<ash::DeskTemplate> old_entry = nullptr;
+  if (is_update)
+    old_entry = saved_desks_list_[desk_type][uuid]->Clone();
+
   saved_desks_list_[desk_type][uuid] = std::move(deserialize_entry);
 
   task_runner_->PostTaskAndReply(
@@ -244,34 +296,73 @@
                      std::move(template_base_value)),
       base::BindOnce(&LocalDeskDataManager::OnAddOrUpdateEntry,
                      weak_ptr_factory_.GetWeakPtr(), std::move(status),
-                     std::move(callback)));
+                     std::move(callback), is_update, desk_type, uuid,
+                     std::move(old_entry)));
 }
 
 void LocalDeskDataManager::DeleteEntry(
     const std::string& uuid_str,
     DeskModel::DeleteEntryCallback callback) {
   auto status = std::make_unique<DeskModel::DeleteEntryStatus>();
+  if (cache_status_ != CacheStatus::kOk) {
+    *status = DeskModel::DeleteEntryStatus::kFailure;
+    std::move(callback).Run(*status);
+    return;
+  }
 
+  const base::GUID uuid = base::GUID::ParseCaseInsensitive(uuid_str);
+  if (!uuid.is_valid()) {
+    // There does not exist an entry with invalid UUID.
+    // Therefore the deletion request is vicariously successful.
+    *status = DeskModel::DeleteEntryStatus::kOk;
+    std::move(callback).Run(*status);
+    return;
+  }
+  const ash::DeskTemplateType desk_type = GetDeskTypeOfUuid(uuid);
+  // `entry` is used to keep track of the deleted entry in case we need to
+  // rollback the deletion if the file operation fails to delete it.
+  auto entry = std::make_unique<
+      std::map<base::GUID, std::unique_ptr<ash::DeskTemplate>>>();
+  (*entry)[uuid] = std::move(saved_desks_list_[desk_type][uuid]);
+  saved_desks_list_[desk_type].erase(uuid);
   task_runner_->PostTaskAndReply(
       FROM_HERE,
       base::BindOnce(&LocalDeskDataManager::DeleteEntryTask,
                      base::Unretained(this), uuid_str, status.get()),
       base::BindOnce(&LocalDeskDataManager::OnDeleteEntry,
                      weak_ptr_factory_.GetWeakPtr(), std::move(status),
-                     std::move(callback)));
+                     std::move(entry), std::move(callback)));
 }
 
 void LocalDeskDataManager::DeleteAllEntries(
     DeskModel::DeleteEntryCallback callback) {
   auto status = std::make_unique<DeskModel::DeleteEntryStatus>();
+  if (cache_status_ != CacheStatus::kOk) {
+    *status = DeskModel::DeleteEntryStatus::kFailure;
+    std::move(callback).Run(*status);
+    return;
+  }
 
+  // `entries` is used to keep track of any desk template entry that failed to
+  // be deleted by the file system. This is used to rollback the deletion of
+  // those fail to delete files.
+  auto entries = std::make_unique<
+      std::map<base::GUID, std::unique_ptr<ash::DeskTemplate>>>();
+
+  // Deletes all desk templates and save and recall desks.
+  for (auto& saved_desk : saved_desks_list_) {
+    for (auto& [uuid, template_entry] : saved_desk.second) {
+      (*entries)[uuid] = std::move(template_entry);
+    }
+    saved_desk.second.clear();
+  }
   task_runner_->PostTaskAndReply(
       FROM_HERE,
       base::BindOnce(&LocalDeskDataManager::DeleteAllEntriesTask,
-                     base::Unretained(this), status.get()),
+                     base::Unretained(this), status.get(), entries.get()),
       base::BindOnce(&LocalDeskDataManager::OnDeleteEntry,
                      weak_ptr_factory_.GetWeakPtr(), std::move(status),
-                     std::move(callback)));
+                     std::move(entries), std::move(callback)));
 }
 
 // TODO(crbug.com/1320805): Remove this function once both desk models support
@@ -336,7 +427,8 @@
   g_exclude_save_and_recall_desk_in_max_entry_count = exclude;
 }
 
-void LocalDeskDataManager::EnsureCacheIsLoaded() {
+void LocalDeskDataManager::EnsureCacheIsLoaded(
+    std::map<base::GUID, std::unique_ptr<ash::DeskTemplate>>* entries_ptr) {
   // Cache is already loaded. Do nothing.
   if (cache_status_ == CacheStatus::kOk)
     return;
@@ -351,25 +443,39 @@
     return;
   }
 
-  ReadFilesIntoCache();
+  // Set dir_reader to read from the `local_saved_desk_path_` directory.
+  // check to make sure there is a `local_saved_desk_path_` directory. If not
+  // create it.
+  bool dir_create_success = base::CreateDirectory(local_saved_desk_path_);
+  base::DirReaderPosix dir_reader(
+      local_saved_desk_path_.AsUTF8Unsafe().c_str());
+
+  if (!dir_create_success || !dir_reader.IsValid()) {
+    // Failed to find or create the `local_saved_desk_path_` directory path.
+    // This local storage cannot load any entry of `type` from disk.
+    cache_status_ = CacheStatus::kInvalidPath;
+    return;
+  }
+
+  while (dir_reader.Next()) {
+    if (!IsValidTemplateFileName(dir_reader.name())) {
+      continue;
+    }
+
+    std::unique_ptr<ash::DeskTemplate> entry =
+        ReadFileToTemplate(local_saved_desk_path_.Append(dir_reader.name()));
+    if (entry) {
+      (*entries_ptr)[entry->uuid()] = std::move(entry);
+    }
+  }
+
+  cache_status_ = CacheStatus::kOk;
 }
 
 void LocalDeskDataManager::GetAllEntriesTask(
-    DeskModel::GetAllEntriesStatus* out_status_ptr,
-    std::vector<const ash::DeskTemplate*>* out_entries_ptr) {
-  EnsureCacheIsLoaded();
+    DeskModel::GetAllEntriesStatus* out_status_ptr) {
   if (cache_status_ == CacheStatus::kInvalidPath) {
     *out_status_ptr = DeskModel::GetAllEntriesStatus::kFailure;
-    return;
-  }
-  for (const auto& it : policy_entries_)
-    out_entries_ptr->push_back(it.get());
-
-  for (auto& saved_desk : saved_desks_list_) {
-    for (auto& [uuid, template_entry] : saved_desk.second) {
-      DCHECK_EQ(uuid, template_entry->uuid());
-      out_entries_ptr->push_back(template_entry.get());
-    }
   }
   if (cache_status_ == CacheStatus::kOk) {
     *out_status_ptr = DeskModel::GetAllEntriesStatus::kOk;
@@ -387,10 +493,7 @@
 
 void LocalDeskDataManager::GetEntryByUuidTask(
     const std::string& uuid_str,
-    DeskModel::GetEntryByUuidStatus* out_status_ptr,
-    ash::DeskTemplate** out_entry_ptr_ptr) {
-  EnsureCacheIsLoaded();
-
+    DeskModel::GetEntryByUuidStatus* out_status_ptr) {
   if (cache_status_ == LocalDeskDataManager::CacheStatus::kInvalidPath) {
     *out_status_ptr = DeskModel::GetEntryByUuidStatus::kFailure;
     return;
@@ -401,17 +504,6 @@
     *out_status_ptr = DeskModel::GetEntryByUuidStatus::kInvalidUuid;
     return;
   }
-
-  const ash::DeskTemplateType desk_type = GetDeskTypeOfUuid(uuid);
-
-  const auto cache_entry = saved_desks_list_[desk_type].find(uuid);
-
-  if (cache_entry != saved_desks_list_[desk_type].end()) {
-    *out_status_ptr = DeskModel::GetEntryByUuidStatus::kOk;
-    *out_entry_ptr_ptr = cache_entry->second.get();
-  } else {
-    *out_status_ptr = DeskModel::GetEntryByUuidStatus::kNotFound;
-  }
 }
 
 void LocalDeskDataManager::OnGetEntryByUuid(
@@ -438,7 +530,6 @@
     const base::GUID uuid,
     DeskModel::AddOrUpdateEntryStatus* out_status_ptr,
     base::Value entry_base_value) {
-  EnsureCacheIsLoaded();
   const base::FilePath fully_qualified_path =
       GetFullyQualifiedPath(local_saved_desk_path_, uuid.AsLowercaseString());
   if (WriteTemplateFile(fully_qualified_path, std::move(entry_base_value))) {
@@ -450,28 +541,25 @@
 
 void LocalDeskDataManager::OnAddOrUpdateEntry(
     std::unique_ptr<DeskModel::AddOrUpdateEntryStatus> status_ptr,
-    DeskModel::AddOrUpdateEntryCallback callback) {
+    DeskModel::AddOrUpdateEntryCallback callback,
+    bool is_update,
+    ash::DeskTemplateType desk_type,
+    const base::GUID uuid,
+    std::unique_ptr<ash::DeskTemplate> entry) {
+  // Rollback the template addition to the cache if there's a failure.
+  if (*status_ptr == DeskModel::AddOrUpdateEntryStatus::kFailure) {
+    if (is_update) {
+      saved_desks_list_[desk_type][uuid] = std::move(entry);
+    } else {
+      saved_desks_list_[desk_type].erase(uuid);
+    }
+  }
   std::move(callback).Run(*status_ptr);
 }
 
 void LocalDeskDataManager::DeleteEntryTask(
     const std::string& uuid_str,
     DeskModel::DeleteEntryStatus* out_status_ptr) {
-  EnsureCacheIsLoaded();
-  if (cache_status_ == CacheStatus::kInvalidPath) {
-    *out_status_ptr = DeskModel::DeleteEntryStatus::kFailure;
-    return;
-  }
-  const base::GUID uuid = base::GUID::ParseCaseInsensitive(uuid_str);
-  if (!uuid.is_valid()) {
-    // There does not exist an entry with invalid UUID.
-    // Therefore the deletion request is vicariously successful.
-    *out_status_ptr = DeskModel::DeleteEntryStatus::kOk;
-    return;
-  }
-  const ash::DeskTemplateType desk_type = GetDeskTypeOfUuid(uuid);
-  saved_desks_list_[desk_type].erase(uuid);
-
   const base::FilePath fully_qualified_path =
       GetFullyQualifiedPath(local_saved_desk_path_, uuid_str);
   base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
@@ -484,24 +572,14 @@
 }
 
 void LocalDeskDataManager::DeleteAllEntriesTask(
-    DeskModel::DeleteEntryStatus* out_status_ptr) {
-  EnsureCacheIsLoaded();
-  if (cache_status_ == CacheStatus::kInvalidPath) {
-    *out_status_ptr = DeskModel::DeleteEntryStatus::kFailure;
-    return;
-  }
-  // Deletes all desk templates and save and recall desks.
-  for (auto& saved_desk : saved_desks_list_) {
-    saved_desk.second.clear();
-  }
+    DeskModel::DeleteEntryStatus* out_status_ptr,
+    std::map<base::GUID, std::unique_ptr<ash::DeskTemplate>>* out_entries_ptr) {
   base::DirReaderPosix dir_reader(
       local_saved_desk_path_.AsUTF8Unsafe().c_str());
-
   if (!dir_reader.IsValid()) {
     *out_status_ptr = DeskModel::DeleteEntryStatus::kFailure;
     return;
   }
-
   DeskModel::DeleteEntryStatus overall_delete_successes =
       DeskModel::DeleteEntryStatus::kOk;
 
@@ -513,16 +591,32 @@
         local_saved_desk_path_.Append(dir_reader.name()));
     base::ScopedBlockingCall scoped_blocking_call(
         FROM_HERE, base::BlockingType::MAY_BLOCK);
+    // Keep a copy of the entry in memory. If the delete fails, get the uuid of
+    // the failed deleted entry so it can be rolled back later.
+    std::unique_ptr<ash::DeskTemplate> entry =
+        ReadFileToTemplate(fully_qualified_path);
     bool delete_success = base::DeleteFile(fully_qualified_path);
-    if (!delete_success)
+    if (!delete_success) {
       overall_delete_successes = DeskModel::DeleteEntryStatus::kFailure;
+    } else {
+      // File deleted successfully, remove the backup of the deleted file.
+      if (entry) {
+        (*out_entries_ptr).erase(entry->uuid());
+      }
+    }
   }
   *out_status_ptr = overall_delete_successes;
 }
 
 void LocalDeskDataManager::OnDeleteEntry(
     std::unique_ptr<DeskModel::DeleteEntryStatus> status_ptr,
+    std::unique_ptr<std::map<base::GUID, std::unique_ptr<ash::DeskTemplate>>>
+        entries_ptr,
     DeskModel::DeleteEntryCallback callback) {
+  // Rollback deletes from the cache for the failed file deletes.
+  if (*status_ptr == DeskModel::DeleteEntryStatus::kFailure) {
+    MoveEntriesIntoCache(std::move(entries_ptr));
+  }
   std::move(callback).Run(*status_ptr);
 }
 
@@ -555,37 +649,15 @@
   return ash::DeskTemplateType::kTemplate;
 }
 
-void LocalDeskDataManager::ReadFilesIntoCache() {
-  // Set dir_reader to read from the `local_saved_desk_path_` directory.
-  // check to make sure there is a `local_saved_desk_path_` directory. If not
-  // create it.
-  base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
-                                                base::BlockingType::MAY_BLOCK);
-  bool dir_create_success = base::CreateDirectory(local_saved_desk_path_);
-  base::DirReaderPosix dir_reader(
-      local_saved_desk_path_.AsUTF8Unsafe().c_str());
-
-  if (!dir_create_success || !dir_reader.IsValid()) {
-    // Failed to find or create the `local_saved_desk_path_` directory path.
-    // This local storage cannot load any entry of `type` from disk.
-    cache_status_ = CacheStatus::kInvalidPath;
-    return;
-  }
-
-  while (dir_reader.Next()) {
-    if (!IsValidTemplateFileName(dir_reader.name())) {
-      continue;
-    }
-
-    std::unique_ptr<ash::DeskTemplate> entry =
-        ReadFileToTemplate(local_saved_desk_path_.Append(dir_reader.name()));
-    if (entry) {
-      const base::GUID uuid = entry->uuid();
-      saved_desks_list_[entry->type()][uuid] = std::move(entry);
+void LocalDeskDataManager::MoveEntriesIntoCache(
+    std::unique_ptr<std::map<base::GUID, std::unique_ptr<ash::DeskTemplate>>>
+        entries_ptr) {
+  for (auto& [uuid, template_entry] : *entries_ptr) {
+    if (template_entry) {
+      saved_desks_list_[template_entry->type()][uuid] =
+          std::move(template_entry);
     }
   }
-
-  cache_status_ = CacheStatus::kOk;
 }
 
 }  // namespace desks_storage
diff --git a/components/desks_storage/core/local_desk_data_manager.h b/components/desks_storage/core/local_desk_data_manager.h
index ffecc57..64b7b3f 100644
--- a/components/desks_storage/core/local_desk_data_manager.h
+++ b/components/desks_storage/core/local_desk_data_manager.h
@@ -54,7 +54,7 @@
 
   // DeskModel:
   void GetAllEntries(GetAllEntriesCallback callback) override;
-  void GetEntryByUUID(const std::string& uuid,
+  void GetEntryByUUID(const std::string& uuid_str,
                       GetEntryByUuidCallback callback) override;
   void AddOrUpdateEntry(std::unique_ptr<ash::DeskTemplate> new_entry,
                         AddOrUpdateEntryCallback callback) override;
@@ -81,22 +81,15 @@
   // Loads templates from `local_saved_desk_path_` into the
   // `saved_desks_list_`, based on the template's desk type, if the cache is not
   // loaded yet.
-  void EnsureCacheIsLoaded();
+  void EnsureCacheIsLoaded(
+      std::map<base::GUID, std::unique_ptr<ash::DeskTemplate>>* entries_ptr);
 
-  // Gets all entries from user's `local_saved_desk_path_`
-  void GetAllEntriesTask(DeskModel::GetAllEntriesStatus* status_ptr,
-                         std::vector<const ash::DeskTemplate*>* entries_ptr);
-
-  // Wrapper method to call GetAllEntriesCallback.
-  void OnGetAllEntries(
-      std::unique_ptr<DeskModel::GetAllEntriesStatus> status_ptr,
-      std::unique_ptr<std::vector<const ash::DeskTemplate*>> entries_ptr,
-      DeskModel::GetAllEntriesCallback callback);
+  // Gets all entries from user's `local_saved_desk_path_`.
+  void GetAllEntriesTask(DeskModel::GetAllEntriesStatus* status_ptr);
 
   // Get a specific entry by `uuid_str`.
   void GetEntryByUuidTask(const std::string& uuid_str,
-                          DeskModel::GetEntryByUuidStatus* status_ptr,
-                          ash::DeskTemplate** entry_ptr_ptr);
+                          DeskModel::GetEntryByUuidStatus* status_ptr);
 
   // Wrapper method to call GetEntryByUuidCallback.
   void OnGetEntryByUuid(
@@ -105,6 +98,12 @@
       std::unique_ptr<ash::DeskTemplate*> entry_ptr_ptr,
       DeskModel::GetEntryByUuidCallback callback);
 
+  // Wrapper method to call GetAllEntriesCallback.
+  void OnGetAllEntries(
+      std::unique_ptr<DeskModel::GetAllEntriesStatus> status_ptr,
+      std::unique_ptr<std::vector<const ash::DeskTemplate*>> entries_ptr,
+      DeskModel::GetAllEntriesCallback callback);
+
   // Add or update an entry by `new_entry`'s UUID.
   void AddOrUpdateEntryTask(const base::GUID uuid,
                             DeskModel::AddOrUpdateEntryStatus* status_ptr,
@@ -113,7 +112,11 @@
   // Wrapper method to call AddOrUpdateEntryCallback.
   void OnAddOrUpdateEntry(
       std::unique_ptr<DeskModel::AddOrUpdateEntryStatus> status_ptr,
-      DeskModel::AddOrUpdateEntryCallback callback);
+      DeskModel::AddOrUpdateEntryCallback callback,
+      bool is_update,
+      ash::DeskTemplateType desk_type,
+      const base::GUID uuid,
+      std::unique_ptr<ash::DeskTemplate> entry);
 
   // Remove entry with `uuid_str`. If the entry with `uuid_str` does not
   // exist, then the deletion is considered a success.
@@ -121,11 +124,16 @@
                        DeskModel::DeleteEntryStatus* status_ptr);
 
   // Delete all entries.
-  void DeleteAllEntriesTask(DeskModel::DeleteEntryStatus* status_ptr);
+  void DeleteAllEntriesTask(
+      DeskModel::DeleteEntryStatus* status_ptr,
+      std::map<base::GUID, std::unique_ptr<ash::DeskTemplate>>* entries_ptr);
 
   // Wrapper method to call DeleteEntryCallback.
-  void OnDeleteEntry(std::unique_ptr<DeskModel::DeleteEntryStatus> status_ptr,
-                     DeskModel::DeleteEntryCallback callback);
+  void OnDeleteEntry(
+      std::unique_ptr<DeskModel::DeleteEntryStatus> status_ptr,
+      std::unique_ptr<std::map<base::GUID, std::unique_ptr<ash::DeskTemplate>>>
+          entries_ptr,
+      DeskModel::DeleteEntryCallback callback);
 
   // Returns true if the storage model has an entry of desk type `type` with the
   // file name `name`.
@@ -135,8 +143,10 @@
   // Returns the desk type of the `uuid`.
   ash::DeskTemplateType GetDeskTypeOfUuid(const base::GUID uuid) const;
 
-  // Read template files into their appropriate caches.
-  void ReadFilesIntoCache();
+  // Wrapper method to load the read files into the `saved_desks_list_` cache.
+  void MoveEntriesIntoCache(
+      std::unique_ptr<std::map<base::GUID, std::unique_ptr<ash::DeskTemplate>>>
+          entries_ptr);
 
   // Task runner used to schedule tasks on the IO thread.
   scoped_refptr<base::SequencedTaskRunner> task_runner_;
diff --git a/components/desks_storage/core/local_desks_data_manager_unittests.cc b/components/desks_storage/core/local_desks_data_manager_unittests.cc
index 82be6b2..cef3707b 100644
--- a/components/desks_storage/core/local_desks_data_manager_unittests.cc
+++ b/components/desks_storage/core/local_desks_data_manager_unittests.cc
@@ -112,6 +112,11 @@
   EXPECT_EQ(status, DeskModel::AddOrUpdateEntryStatus::kOk);
 }
 
+// Verifies that the status passed into it is kFailure
+void VerifyEntryAddedFailure(DeskModel::AddOrUpdateEntryStatus status) {
+  EXPECT_EQ(status, DeskModel::AddOrUpdateEntryStatus::kFailure);
+}
+
 void VerifyEntryAddedErrorHitMaximumLimit(
     DeskModel::AddOrUpdateEntryStatus status) {
   EXPECT_EQ(status, DeskModel::AddOrUpdateEntryStatus::kHitMaximumLimit);
@@ -585,6 +590,7 @@
        GetEntryByUuidReturnsFailureIfDeskManagerHasInvalidPath) {
   data_manager_ =
       std::make_unique<LocalDeskDataManager>(kInvalidFilePath, account_id_);
+  task_environment_.RunUntilIdle();
 
   base::RunLoop loop;
   data_manager_->GetEntryByUUID(
@@ -624,14 +630,12 @@
   data_manager_->AddOrUpdateEntry(std::move(sample_desk_template_one_),
                                   base::BindOnce(&VerifyEntryAddedCorrectly));
 
-  base::RunLoop loop;
-
   data_manager_->DeleteEntry(
       kTestUuid1,
       base::BindLambdaForTesting([&](DeskModel::DeleteEntryStatus status) {
         EXPECT_EQ(status, DeskModel::DeleteEntryStatus::kOk);
       }));
-
+  base::RunLoop loop;
   data_manager_->GetAllEntries(base::BindLambdaForTesting(
       [&](DeskModel::GetAllEntriesStatus status,
           const std::vector<const ash::DeskTemplate*>& entries) {
@@ -639,7 +643,6 @@
         EXPECT_EQ(entries.size(), 0ul);
         loop.Quit();
       }));
-
   loop.Run();
 }
 
@@ -659,6 +662,7 @@
       base::BindLambdaForTesting([&](DeskModel::DeleteEntryStatus status) {
         EXPECT_EQ(status, DeskModel::DeleteEntryStatus::kOk);
       }));
+  task_environment_.RunUntilIdle();
 
   data_manager_->GetAllEntries(base::BindLambdaForTesting(
       [&](DeskModel::GetAllEntriesStatus status,
@@ -863,4 +867,109 @@
   EXPECT_EQ(data_manager_->GetSaveAndRecallDeskEntryCount(), 6ul);
 }
 
+TEST_F(LocalDeskDataManagerTest, RollbackUpdateTemplatesOnFileWriteFailure) {
+  // Add two user templates.
+  for (std::size_t index = 0u; index < 2u; ++index) {
+    data_manager_->AddOrUpdateEntry(
+        MakeTestDeskTemplate(index, ash::DeskTemplateType::kTemplate),
+        base::BindOnce(&VerifyEntryAddedCorrectly));
+  }
+
+  EXPECT_EQ(data_manager_->GetEntryCount(), 2ul);
+  EXPECT_EQ(data_manager_->GetDeskTemplateEntryCount(), 2ul);
+  task_environment_.RunUntilIdle();
+
+  base::SetPosixFilePermissions(temp_dir_.GetPath(),
+                                base::FILE_PERMISSION_READ_BY_USER);
+  data_manager_->AddOrUpdateEntry(
+      MakeTestDeskTemplate(1ul, ash::DeskTemplateType::kTemplate),
+      base::BindOnce(&VerifyEntryAddedFailure));
+
+  VerifyAllEntries(2ul,
+                   "Updated one desk template failed to write to file system");
+
+  base::SetPosixFilePermissions(temp_dir_.GetPath(),
+                                base::FILE_PERMISSION_READ_BY_USER |
+                                    base::FILE_PERMISSION_WRITE_BY_USER |
+                                    base::FILE_PERMISSION_EXECUTE_BY_USER);
+}
+
+TEST_F(LocalDeskDataManagerTest, RollbackAddTemplatesOnFileWriteFailure) {
+  // Add two user templates.
+  for (std::size_t index = 0u; index < 2u; ++index) {
+    data_manager_->AddOrUpdateEntry(
+        MakeTestDeskTemplate(index, ash::DeskTemplateType::kTemplate),
+        base::BindOnce(&VerifyEntryAddedCorrectly));
+  }
+
+  EXPECT_EQ(data_manager_->GetEntryCount(), 2ul);
+  EXPECT_EQ(data_manager_->GetDeskTemplateEntryCount(), 2ul);
+  task_environment_.RunUntilIdle();
+
+  base::SetPosixFilePermissions(temp_dir_.GetPath(),
+                                base::FILE_PERMISSION_READ_BY_USER);
+  data_manager_->AddOrUpdateEntry(
+      MakeTestDeskTemplate(3ul, ash::DeskTemplateType::kTemplate),
+      base::BindOnce(&VerifyEntryAddedFailure));
+  task_environment_.RunUntilIdle();
+
+  VerifyAllEntries(2ul, "Add one desk template failed to write to file system");
+
+  base::SetPosixFilePermissions(temp_dir_.GetPath(),
+                                base::FILE_PERMISSION_READ_BY_USER |
+                                    base::FILE_PERMISSION_WRITE_BY_USER |
+                                    base::FILE_PERMISSION_EXECUTE_BY_USER);
+}
+
+TEST_F(LocalDeskDataManagerTest, RollbackDeleteTemplatesOnFileDeleteFailure) {
+  data_manager_->AddOrUpdateEntry(std::move(sample_desk_template_one_),
+                                  base::BindOnce(&VerifyEntryAddedCorrectly));
+  EXPECT_EQ(data_manager_->GetEntryCount(), 1ul);
+  EXPECT_EQ(data_manager_->GetDeskTemplateEntryCount(), 1ul);
+  task_environment_.RunUntilIdle();
+  base::SetPosixFilePermissions(temp_dir_.GetPath(),
+                                base::FILE_PERMISSION_READ_BY_USER);
+  data_manager_->DeleteEntry(
+      kTestUuid1,
+      base::BindLambdaForTesting([&](DeskModel::DeleteEntryStatus status) {
+        EXPECT_EQ(status, DeskModel::DeleteEntryStatus::kFailure);
+      }));
+  task_environment_.RunUntilIdle();
+
+  VerifyAllEntries(1ul, "Delete desk template failed to delete on file system");
+
+  base::SetPosixFilePermissions(temp_dir_.GetPath(),
+                                base::FILE_PERMISSION_READ_BY_USER |
+                                    base::FILE_PERMISSION_WRITE_BY_USER |
+                                    base::FILE_PERMISSION_EXECUTE_BY_USER);
+}
+
+TEST_F(LocalDeskDataManagerTest,
+       RollbackDeleteAllTemplatesOnFileDeleteFailure) {
+  // Add four user templates.
+  for (std::size_t index = 0u; index < 4u; ++index) {
+    data_manager_->AddOrUpdateEntry(
+        MakeTestDeskTemplate(index, ash::DeskTemplateType::kTemplate),
+        base::BindOnce(&VerifyEntryAddedCorrectly));
+  }
+  EXPECT_EQ(data_manager_->GetEntryCount(), 4ul);
+  EXPECT_EQ(data_manager_->GetDeskTemplateEntryCount(), 4ul);
+  task_environment_.RunUntilIdle();
+  base::SetPosixFilePermissions(temp_dir_.GetPath(),
+                                base::FILE_PERMISSION_READ_BY_USER);
+  data_manager_->DeleteAllEntries(
+      base::BindLambdaForTesting([&](DeskModel::DeleteEntryStatus status) {
+        EXPECT_EQ(status, DeskModel::DeleteEntryStatus::kFailure);
+      }));
+  task_environment_.RunUntilIdle();
+
+  VerifyAllEntries(4ul,
+                   "Delete all desk template failed to delete on file system");
+
+  base::SetPosixFilePermissions(temp_dir_.GetPath(),
+                                base::FILE_PERMISSION_READ_BY_USER |
+                                    base::FILE_PERMISSION_WRITE_BY_USER |
+                                    base::FILE_PERMISSION_EXECUTE_BY_USER);
+}
+
 }  // namespace desks_storage
diff --git a/components/device_signals/core/common/win/BUILD.gn b/components/device_signals/core/common/win/BUILD.gn
index 022f4b8..89ed223 100644
--- a/components/device_signals/core/common/win/BUILD.gn
+++ b/components/device_signals/core/common/win/BUILD.gn
@@ -4,13 +4,18 @@
 
 static_library("win") {
   public = [
+    "win_types.h",
     "wmi_client.h",
     "wmi_client_impl.h",
+    "wsc_client.h",
+    "wsc_client_impl.h",
   ]
 
   sources = [
     "wmi_client.cc",
     "wmi_client_impl.cc",
+    "wsc_client.cc",
+    "wsc_client_impl.cc",
   ]
 
   public_deps = [
@@ -35,7 +40,10 @@
 
 source_set("unit_tests") {
   testonly = true
-  sources = [ "wmi_client_impl_unittest.cc" ]
+  sources = [
+    "wmi_client_impl_unittest.cc",
+    "wsc_client_impl_unittest.cc",
+  ]
 
   deps = [
     ":test_support",
diff --git a/components/device_signals/core/common/win/com_fakes.cc b/components/device_signals/core/common/win/com_fakes.cc
index e1d9edb..b9c74e9a 100644
--- a/components/device_signals/core/common/win/com_fakes.cc
+++ b/components/device_signals/core/common/win/com_fakes.cc
@@ -19,7 +19,26 @@
     return ::InterlockedDecrement(&ref_count_);                       \
   }
 
-IMPL_IUNKOWN_NOQI_WITH_REF(FakeWbemClassObject)
+#define IMPL_IDISPATCH(cls)                                                 \
+  IMPL_IUNKOWN_NOQI_WITH_REF(cls)                                           \
+  IFACEMETHODIMP cls::GetTypeInfoCount(UINT* pctinfo) { return E_NOTIMPL; } \
+  IFACEMETHODIMP cls::GetTypeInfo(UINT iTInfo, LCID lcid,                   \
+                                  ITypeInfo** ppTInfo) {                    \
+    return E_NOTIMPL;                                                       \
+  }                                                                         \
+  IFACEMETHODIMP cls::GetIDsOfNames(REFIID riid, LPOLESTR* rgszNames,       \
+                                    UINT cNames, LCID lcid,                 \
+                                    DISPID* rgDispId) {                     \
+    return E_NOTIMPL;                                                       \
+  }                                                                         \
+  IFACEMETHODIMP cls::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid,   \
+                             WORD wFlags, DISPPARAMS* pDispParams,          \
+                             VARIANT* pVarResult, EXCEPINFO* pExcepInfo,    \
+                             UINT* puArgErr) {                              \
+    return E_NOTIMPL;                                                       \
+  }
+
+// FakeEnumWbemClassObject
 
 FakeEnumWbemClassObject::FakeEnumWbemClassObject() = default;
 
@@ -74,6 +93,8 @@
 
 IMPL_IUNKOWN_NOQI_WITH_REF(FakeEnumWbemClassObject)
 
+// FakeWbemClassObject
+
 FakeWbemClassObject::FakeWbemClassObject() = default;
 FakeWbemClassObject::FakeWbemClassObject(FakeWbemClassObject&&) = default;
 FakeWbemClassObject& FakeWbemClassObject::operator=(FakeWbemClassObject&&) =
@@ -221,4 +242,128 @@
   return E_NOTIMPL;
 }
 
+IMPL_IUNKOWN_NOQI_WITH_REF(FakeWbemClassObject)
+
+// FakeWscProduct
+
+FakeWscProduct::FakeWscProduct() = default;
+
+FakeWscProduct::FakeWscProduct(const wchar_t* name,
+                               const wchar_t* id,
+                               WSC_SECURITY_PRODUCT_STATE state)
+    : name_(name), id_(id), state_(state) {}
+
+FakeWscProduct::FakeWscProduct(FakeWscProduct&& other)
+    : failed_step_(other.failed_step_), state_(other.state_) {
+  name_.Reset(other.name_.Release());
+  id_.Reset(other.id_.Release());
+}
+
+FakeWscProduct& FakeWscProduct::operator=(FakeWscProduct&& other) {
+  failed_step_ = other.failed_step_;
+  state_ = other.state_;
+  name_.Reset(other.name_.Release());
+  id_.Reset(other.id_.Release());
+  return *this;
+}
+
+FakeWscProduct::~FakeWscProduct() = default;
+
+HRESULT FakeWscProduct::get_ProductName(BSTR* pVal) {
+  if (ShouldFail(FailureStep::kProductName)) {
+    return E_FAIL;
+  }
+
+  *pVal = name_.Get();
+  return S_OK;
+}
+
+HRESULT FakeWscProduct::get_ProductGuid(BSTR* pVal) {
+  if (ShouldFail(FailureStep::kProductId)) {
+    return E_FAIL;
+  }
+
+  *pVal = id_.Get();
+  return S_OK;
+}
+
+HRESULT FakeWscProduct::get_ProductState(WSC_SECURITY_PRODUCT_STATE* pVal) {
+  if (ShouldFail(FailureStep::kProductState)) {
+    return E_FAIL;
+  }
+
+  *pVal = state_;
+  return S_OK;
+}
+
+HRESULT FakeWscProduct::get_ProductStateTimestamp(BSTR* pVal) {
+  return E_NOTIMPL;
+}
+
+HRESULT FakeWscProduct::get_RemediationPath(BSTR* pVal) {
+  return E_NOTIMPL;
+}
+
+HRESULT FakeWscProduct::get_SignatureStatus(
+    WSC_SECURITY_SIGNATURE_STATUS* pVal) {
+  return E_NOTIMPL;
+}
+
+HRESULT FakeWscProduct::get_ProductIsDefault(BOOL* pVal) {
+  return E_NOTIMPL;
+}
+
+bool FakeWscProduct::ShouldFail(FakeWscProduct::FailureStep step) {
+  return failed_step_.has_value() && failed_step_.value() == step;
+}
+
+IMPL_IDISPATCH(FakeWscProduct)
+
+// FakeWSCProductList
+
+FakeWSCProductList::FakeWSCProductList() = default;
+
+FakeWSCProductList::~FakeWSCProductList() = default;
+
+HRESULT FakeWSCProductList::get_Count(LONG* pVal) {
+  if (ShouldFail(FailureStep::kGetCount)) {
+    return E_FAIL;
+  }
+
+  *pVal = products_.size();
+  return S_OK;
+}
+
+HRESULT FakeWSCProductList::get_Item(ULONG index, IWscProduct** pVal) {
+  if (ShouldFail(FailureStep::kGetItem)) {
+    return E_FAIL;
+  }
+
+  if (index < 0 || index >= products_.size()) {
+    return E_FAIL;
+  }
+
+  *pVal = products_[index];
+
+  return S_OK;
+}
+
+HRESULT FakeWSCProductList::Initialize(ULONG provider) {
+  if (ShouldFail(FailureStep::kInitialize)) {
+    return E_FAIL;
+  }
+
+  // Initialize can only be called once.
+  DCHECK(!provider_.has_value());
+
+  provider_ = provider;
+  return S_OK;
+}
+
+bool FakeWSCProductList::ShouldFail(FakeWSCProductList::FailureStep step) {
+  return failed_step_.has_value() && failed_step_.value() == step;
+}
+
+IMPL_IDISPATCH(FakeWSCProductList)
+
 }  // namespace device_signals
diff --git a/components/device_signals/core/common/win/com_fakes.h b/components/device_signals/core/common/win/com_fakes.h
index ba4c1ef..06fc731 100644
--- a/components/device_signals/core/common/win/com_fakes.h
+++ b/components/device_signals/core/common/win/com_fakes.h
@@ -6,6 +6,7 @@
 #define COMPONENTS_DEVICE_SIGNALS_CORE_COMMON_WIN_COM_FAKES_H_
 
 #include <atlcomcli.h>
+#include <iwscapi.h>
 #include <wbemidl.h>
 #include <iterator>
 #include <map>
@@ -24,6 +25,18 @@
   ULONG STDMETHODCALLTYPE Release(void) override;                  \
   ULONG ref_count_ = 1;
 
+#define DECLARE_IDISPATCH()                                                   \
+  DECLARE_IUNKOWN_NOQI_WITH_REF()                                             \
+  IFACEMETHODIMP GetTypeInfoCount(UINT* pctinfo) override;                    \
+  IFACEMETHODIMP GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo** ppTInfo)     \
+      override;                                                               \
+  IFACEMETHODIMP GetIDsOfNames(REFIID riid, LPOLESTR* rgszNames, UINT cNames, \
+                               LCID lcid, DISPID* rgDispId) override;         \
+  IFACEMETHODIMP Invoke(DISPID dispIdMember, REFIID riid, LCID lcid,          \
+                        WORD wFlags, DISPPARAMS* pDispParams,                 \
+                        VARIANT* pVarResult, EXCEPINFO* pExcepInfo,           \
+                        UINT* puArgErr) override;
+
 class FakeEnumWbemClassObject : public IEnumWbemClassObject {
  public:
   FakeEnumWbemClassObject();
@@ -133,6 +146,89 @@
   std::map<std::wstring, base::win::ScopedVariant> map_;
 };
 
+class FakeWscProduct : public IWscProduct {
+ public:
+  FakeWscProduct();
+  FakeWscProduct(const wchar_t* name,
+                 const wchar_t* id,
+                 WSC_SECURITY_PRODUCT_STATE state);
+
+  FakeWscProduct(const FakeWscProduct& copy) = delete;
+  FakeWscProduct& operator=(const FakeWscProduct&) = delete;
+  FakeWscProduct(FakeWscProduct&&);
+  FakeWscProduct& operator=(FakeWscProduct&&);
+
+  virtual ~FakeWscProduct();
+
+  enum class FailureStep {
+    kProductName = 0,
+    kProductId = 1,
+    kProductState = 2,
+  };
+
+  // IWscProduct:
+  IFACEMETHODIMP get_ProductName(BSTR* pVal) override;
+  IFACEMETHODIMP get_ProductGuid(BSTR* pVal) override;
+  IFACEMETHODIMP get_ProductState(WSC_SECURITY_PRODUCT_STATE* pVal) override;
+
+  // Can be used to force a failure to happen in one of the functions
+  // represented by `step`.
+  void set_failed_step(FailureStep step) { failed_step_ = step; }
+
+ private:
+  // IWscProduct:
+  DECLARE_IDISPATCH()
+  IFACEMETHODIMP get_ProductStateTimestamp(BSTR* pVal) override;
+  IFACEMETHODIMP get_RemediationPath(BSTR* pVal) override;
+  IFACEMETHODIMP get_SignatureStatus(
+      WSC_SECURITY_SIGNATURE_STATUS* pVal) override;
+  IFACEMETHODIMP get_ProductIsDefault(BOOL* pVal) override;
+
+  bool ShouldFail(FailureStep step);
+
+  absl::optional<FailureStep> failed_step_;
+
+  base::win::ScopedBstr name_;
+  base::win::ScopedBstr id_;
+  WSC_SECURITY_PRODUCT_STATE state_;
+};
+
+class FakeWSCProductList : public IWSCProductList {
+ public:
+  FakeWSCProductList();
+
+  virtual ~FakeWSCProductList();
+
+  enum class FailureStep {
+    kInitialize = 0,
+    kGetCount = 1,
+    kGetItem = 2,
+  };
+
+  void Add(IWscProduct* product) { products_.push_back(product); }
+  // IWSCProductList:
+  IFACEMETHODIMP get_Count(LONG* pVal) override;
+  IFACEMETHODIMP get_Item(ULONG index, IWscProduct** pVal) override;
+  IFACEMETHODIMP Initialize(ULONG provider) override;
+
+  // Can be used to force a failure to happen in one of the functions
+  // represented by `step`.
+  void set_failed_step(FailureStep step) { failed_step_ = step; }
+
+  const absl::optional<ULONG>& provider() { return provider_; }
+
+ private:
+  // IWSCProductList:
+  DECLARE_IDISPATCH()
+
+  bool ShouldFail(FailureStep step);
+
+  absl::optional<FailureStep> failed_step_;
+
+  absl::optional<ULONG> provider_;
+  std::vector<IWscProduct*> products_;
+};
+
 }  // namespace device_signals
 
 #endif  // COMPONENTS_DEVICE_SIGNALS_CORE_COMMON_WIN_COM_FAKES_H_
diff --git a/components/device_signals/core/common/win/win_types.h b/components/device_signals/core/common/win/win_types.h
new file mode 100644
index 0000000..2b40f68
--- /dev/null
+++ b/components/device_signals/core/common/win/win_types.h
@@ -0,0 +1,39 @@
+// Copyright (c) 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 COMPONENTS_DEVICE_SIGNALS_CORE_COMMON_WIN_WIN_TYPES_H_
+#define COMPONENTS_DEVICE_SIGNALS_CORE_COMMON_WIN_WIN_TYPES_H_
+
+#include <string>
+
+namespace device_signals {
+
+// Various states in which an AntiVirus software can be.
+enum class AvProductState { kOn, kOff, kSnoozed, kExpired };
+
+// Metadata about an installed AntiVirus software product.
+// Can be retrieve via WSC on Windows 8 and above, and below properties are
+// collected via this interface:
+// https://docs.microsoft.com/en-us/windows/win32/api/iwscapi/nn-iwscapi-iwscproduct
+struct AvProduct {
+  std::string display_name;
+  AvProductState state;
+
+  // Although not present on the documentation, IWscProduct exposes a
+  // `get_ProductGuid` function to retrieve an GUID representing an Antivirus
+  // software.
+  std::string product_id;
+};
+
+// Metadata about an installed Hotfix update.
+struct InstalledHotfix {
+  // In WMI, this value represents the `HotFixID` property from entries in
+  // "Win32_QuickFixEngineering". They have a format looking like `KB123123`.
+  // https://docs.microsoft.com/en-us/windows/win32/cimwin32prov/win32-quickfixengineering
+  std::string hotfix_id;
+};
+
+}  // namespace device_signals
+
+#endif  // COMPONENTS_DEVICE_SIGNALS_CORE_COMMON_WIN_WIN_TYPES_H_
diff --git a/components/device_signals/core/common/win/wmi_client.h b/components/device_signals/core/common/win/wmi_client.h
index a46ebe0..6215633c 100644
--- a/components/device_signals/core/common/win/wmi_client.h
+++ b/components/device_signals/core/common/win/wmi_client.h
@@ -9,6 +9,7 @@
 #include <vector>
 
 #include "base/win/wmi.h"
+#include "components/device_signals/core/common/win/win_types.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 // WMI interfaces are available on Windows Vista and above, and are officially
@@ -22,14 +23,6 @@
   kMaxValue = kFailedToGetName
 };
 
-// Metadata about an installed Hotfix update.
-struct InstalledHotfix {
-  // In WMI, this value represents the `HotFixID` property from entries in
-  // "Win32_QuickFixEngineering". They have a format looking like `KB123123`.
-  // https://docs.microsoft.com/en-us/windows/win32/cimwin32prov/win32-quickfixengineering
-  std::string hotfix_id;
-};
-
 // Response object for calls to retrieve information about installed hotfix
 // updates.
 struct WmiHotfixesResponse {
diff --git a/components/device_signals/core/common/win/wmi_client_impl_unittest.cc b/components/device_signals/core/common/win/wmi_client_impl_unittest.cc
index d8c67f3..cd83f1f 100644
--- a/components/device_signals/core/common/win/wmi_client_impl_unittest.cc
+++ b/components/device_signals/core/common/win/wmi_client_impl_unittest.cc
@@ -17,6 +17,7 @@
 #include "base/test/task_environment.h"
 #include "base/win/wmi.h"
 #include "components/device_signals/core/common/win/com_fakes.h"
+#include "components/device_signals/core/common/win/win_types.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
diff --git a/components/device_signals/core/common/win/wsc_client.cc b/components/device_signals/core/common/win/wsc_client.cc
new file mode 100644
index 0000000..4b44c20
--- /dev/null
+++ b/components/device_signals/core/common/win/wsc_client.cc
@@ -0,0 +1,15 @@
+// Copyright (c) 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 "components/device_signals/core/common/win/wsc_client.h"
+
+namespace device_signals {
+
+WscAvProductsResponse::WscAvProductsResponse() = default;
+WscAvProductsResponse::~WscAvProductsResponse() = default;
+
+WscAvProductsResponse::WscAvProductsResponse(
+    const WscAvProductsResponse& other) = default;
+
+}  // namespace device_signals
diff --git a/components/device_signals/core/common/win/wsc_client.h b/components/device_signals/core/common/win/wsc_client.h
new file mode 100644
index 0000000..089808e
--- /dev/null
+++ b/components/device_signals/core/common/win/wsc_client.h
@@ -0,0 +1,59 @@
+// Copyright (c) 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 COMPONENTS_DEVICE_SIGNALS_CORE_COMMON_WIN_WSC_CLIENT_H_
+#define COMPONENTS_DEVICE_SIGNALS_CORE_COMMON_WIN_WSC_CLIENT_H_
+
+#include <string>
+#include <vector>
+
+#include "components/device_signals/core/common/win/win_types.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+// WSC interfaces are available on Windows 8 and above. More information at:
+// https://docs.microsoft.com/en-us/windows/win32/api/iwscapi/
+namespace device_signals {
+
+enum class WscQueryError {
+  kFailedToCreateInstance = 0,
+  kFailedToInitializeProductList = 1,
+  kFailedToGetProductCount = 2,
+};
+
+// Errors that can occur when calling WSC, or parsing response values.
+enum class WscParsingError {
+  kFailedToGetItem = 0,
+  kFailedToGetState = 1,
+  kStateInvalid = 2,
+  kFailedToGetName = 3,
+  kFailedToGetId = 4,
+  kMaxValue = kFailedToGetId
+};
+
+// Response object for calls to retrieve information about installed AntiVirus
+// software.
+struct WscAvProductsResponse {
+  WscAvProductsResponse();
+  ~WscAvProductsResponse();
+
+  WscAvProductsResponse(const WscAvProductsResponse& other);
+
+  std::vector<AvProduct> av_products;
+  absl::optional<WscQueryError> query_error;
+  std::vector<WscParsingError> parsing_errors;
+};
+
+// Interface for a client instance used to get information from Windows Security
+// Center.
+class WscClient {
+ public:
+  virtual ~WscClient() = default;
+
+  // Will retrieve information about installed AntiVirus software.
+  virtual WscAvProductsResponse GetAntiVirusProducts() = 0;
+};
+
+}  // namespace device_signals
+
+#endif  // COMPONENTS_DEVICE_SIGNALS_CORE_COMMON_WIN_WSC_CLIENT_H_
diff --git a/components/device_signals/core/common/win/wsc_client_impl.cc b/components/device_signals/core/common/win/wsc_client_impl.cc
new file mode 100644
index 0000000..fae2aed1
--- /dev/null
+++ b/components/device_signals/core/common/win/wsc_client_impl.cc
@@ -0,0 +1,125 @@
+// Copyright (c) 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 "components/device_signals/core/common/win/wsc_client_impl.h"
+
+#include <iwscapi.h>
+#include <windows.h>
+#include <wrl/client.h>
+#include <wscapi.h>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/check.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/threading/scoped_blocking_call.h"
+#include "base/win/com_init_util.h"
+#include "base/win/scoped_bstr.h"
+#include "base/win/scoped_variant.h"
+#include "base/win/windows_version.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+using Microsoft::WRL::ComPtr;
+
+namespace device_signals {
+
+namespace {
+
+AvProductState ConvertState(WSC_SECURITY_PRODUCT_STATE state) {
+  switch (state) {
+    case WSC_SECURITY_PRODUCT_STATE_ON:
+      return AvProductState::kOn;
+    case WSC_SECURITY_PRODUCT_STATE_OFF:
+      return AvProductState::kOff;
+    case WSC_SECURITY_PRODUCT_STATE_SNOOZED:
+      return AvProductState::kSnoozed;
+    case WSC_SECURITY_PRODUCT_STATE_EXPIRED:
+      return AvProductState::kExpired;
+  }
+}
+
+HRESULT CreateProductList(IWSCProductList** product_list) {
+  return ::CoCreateInstance(__uuidof(WSCProductList), nullptr,
+                            CLSCTX_INPROC_SERVER, IID_PPV_ARGS(product_list));
+}
+
+}  // namespace
+
+WscClientImpl::WscClientImpl()
+    : create_callback_(base::BindRepeating(CreateProductList)) {}
+
+WscClientImpl::WscClientImpl(CreateProductListCallback create_callback)
+    : create_callback_(std::move(create_callback)) {}
+
+WscClientImpl::~WscClientImpl() = default;
+
+WscAvProductsResponse WscClientImpl::GetAntiVirusProducts() {
+  base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
+                                                base::BlockingType::MAY_BLOCK);
+  WscAvProductsResponse response;
+
+  ComPtr<IWSCProductList> product_list;
+  HRESULT hr = create_callback_.Run(&product_list);
+  if (FAILED(hr)) {
+    response.query_error = WscQueryError::kFailedToCreateInstance;
+    return response;
+  }
+
+  hr = product_list->Initialize(WSC_SECURITY_PROVIDER_ANTIVIRUS);
+  if (FAILED(hr)) {
+    response.query_error = WscQueryError::kFailedToInitializeProductList;
+    return response;
+  }
+
+  LONG product_count;
+  hr = product_list->get_Count(&product_count);
+  if (FAILED(hr)) {
+    response.query_error = WscQueryError::kFailedToGetProductCount;
+    return response;
+  }
+
+  for (LONG i = 0; i < product_count; i++) {
+    ComPtr<IWscProduct> product;
+    hr = product_list->get_Item(i, &product);
+    if (FAILED(hr)) {
+      response.parsing_errors.push_back(WscParsingError::kFailedToGetItem);
+      continue;
+    }
+
+    AvProduct av_product;
+    WSC_SECURITY_PRODUCT_STATE product_state;
+    hr = product->get_ProductState(&product_state);
+    if (FAILED(hr)) {
+      response.parsing_errors.push_back(WscParsingError::kFailedToGetState);
+      continue;
+    }
+
+    av_product.state = ConvertState(product_state);
+
+    base::win::ScopedBstr product_name;
+    hr = product->get_ProductName(product_name.Receive());
+    if (FAILED(hr)) {
+      response.parsing_errors.push_back(WscParsingError::kFailedToGetName);
+      continue;
+    }
+    av_product.display_name = base::SysWideToUTF8(
+        std::wstring(product_name.Get(), product_name.Length()));
+
+    base::win::ScopedBstr product_id;
+    hr = product->get_ProductGuid(product_id.Receive());
+    if (FAILED(hr)) {
+      response.parsing_errors.push_back(WscParsingError::kFailedToGetId);
+      continue;
+    }
+    av_product.product_id = base::SysWideToUTF8(
+        std::wstring(product_id.Get(), product_id.Length()));
+
+    response.av_products.push_back(std::move(av_product));
+  }
+
+  return response;
+}
+
+}  // namespace device_signals
diff --git a/components/device_signals/core/common/win/wsc_client_impl.h b/components/device_signals/core/common/win/wsc_client_impl.h
new file mode 100644
index 0000000..e17df9d3
--- /dev/null
+++ b/components/device_signals/core/common/win/wsc_client_impl.h
@@ -0,0 +1,39 @@
+// Copyright (c) 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 COMPONENTS_DEVICE_SIGNALS_CORE_COMMON_WIN_WSC_CLIENT_IMPL_H_
+#define COMPONENTS_DEVICE_SIGNALS_CORE_COMMON_WIN_WSC_CLIENT_IMPL_H_
+
+#include <iwscapi.h>
+
+#include "base/callback.h"
+#include "components/device_signals/core/common/win/wsc_client.h"
+
+namespace device_signals {
+
+class WscClientImpl : public WscClient {
+ public:
+  using CreateProductListCallback =
+      base::RepeatingCallback<HRESULT(IWSCProductList**)>;
+
+  WscClientImpl();
+
+  ~WscClientImpl() override;
+
+  // WscClient:
+  WscAvProductsResponse GetAntiVirusProducts() override;
+
+ private:
+  friend class WscClientImplTest;
+
+  // Constructor taking in a `create_callback` which can be used to mock
+  // creating the product list COM object.
+  explicit WscClientImpl(CreateProductListCallback create_callback);
+
+  CreateProductListCallback create_callback_;
+};
+
+}  // namespace device_signals
+
+#endif  // COMPONENTS_DEVICE_SIGNALS_CORE_COMMON_WIN_WSC_CLIENT_IMPL_H_
diff --git a/components/device_signals/core/common/win/wsc_client_impl_unittest.cc b/components/device_signals/core/common/win/wsc_client_impl_unittest.cc
new file mode 100644
index 0000000..e922328
--- /dev/null
+++ b/components/device_signals/core/common/win/wsc_client_impl_unittest.cc
@@ -0,0 +1,251 @@
+// Copyright (c) 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 "components/device_signals/core/common/win/wsc_client_impl.h"
+
+#include <iwscapi.h>
+#include <windows.h>
+#include <wrl/client.h>
+#include <wscapi.h>
+#include <memory>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/test/task_environment.h"
+#include "base/win/windows_version.h"
+#include "components/device_signals/core/common/win/com_fakes.h"
+#include "components/device_signals/core/common/win/win_types.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+using Microsoft::WRL::ComPtr;
+
+namespace device_signals {
+
+struct AvTestData {
+  const wchar_t* name;
+  const wchar_t* id;
+  WSC_SECURITY_PRODUCT_STATE state;
+  AvProductState expected_state;
+};
+
+class WscClientImplTest : public testing::Test {
+ protected:
+  WscClientImplTest()
+      : wsc_client_(base::BindRepeating(&WscClientImplTest::CreateProductList,
+                                        base::Unretained(this))) {}
+
+  HRESULT CreateProductList(IWSCProductList** product_list) {
+    if (fail_list_creation_) {
+      return E_FAIL;
+    }
+
+    *product_list = &product_list_;
+    return S_OK;
+  }
+
+  void ExpectAvInitialized() {
+    EXPECT_TRUE(product_list_.provider().has_value());
+    EXPECT_EQ(product_list_.provider().value(),
+              WSC_SECURITY_PROVIDER_ANTIVIRUS);
+  }
+
+  bool fail_list_creation_ = false;
+
+  FakeWSCProductList product_list_;
+  WscClientImpl wsc_client_;
+};
+
+// Tests how the client handles all different product states when parsing
+// products received from the list.
+TEST_F(WscClientImplTest, GetAntiVirusProducts_AllStates) {
+  std::vector<AvTestData> test_data;
+  test_data.push_back({L"first name", L"first product id",
+                       WSC_SECURITY_PRODUCT_STATE_ON, AvProductState::kOn});
+  test_data.push_back({L"second name", L"second product id",
+                       WSC_SECURITY_PRODUCT_STATE_OFF, AvProductState::kOff});
+  test_data.push_back({L"third name", L"third product id",
+                       WSC_SECURITY_PRODUCT_STATE_SNOOZED,
+                       AvProductState::kSnoozed});
+  test_data.push_back({L"fourth name", L"fourth product id",
+                       WSC_SECURITY_PRODUCT_STATE_EXPIRED,
+                       AvProductState::kExpired});
+
+  // Used to keep products from going out of scope.
+  std::vector<FakeWscProduct> products;
+
+  for (const auto& data : test_data) {
+    products.emplace_back(data.name, data.id, data.state);
+  }
+
+  for (auto& product : products) {
+    product_list_.Add(&product);
+  }
+
+  auto response = wsc_client_.GetAntiVirusProducts();
+
+  ExpectAvInitialized();
+  EXPECT_EQ(response.query_error, absl::nullopt);
+
+  EXPECT_EQ(response.parsing_errors.size(), 0U);
+
+  ASSERT_EQ(response.av_products.size(), test_data.size());
+  ASSERT_GT(response.av_products.size(), 0U);
+
+  for (size_t i = 0; i < test_data.size(); i++) {
+    EXPECT_EQ(response.av_products[i].display_name,
+              base::SysWideToUTF8(test_data[i].name));
+    EXPECT_EQ(response.av_products[i].product_id,
+              base::SysWideToUTF8(test_data[i].id));
+    EXPECT_EQ(response.av_products[i].state, test_data[i].expected_state);
+  }
+}
+
+// Tests how the client reacts to a failure occurring when trying to create a
+// product list instance.
+TEST_F(WscClientImplTest, GetAntiVirusProducts_FailedCreate) {
+  fail_list_creation_ = true;
+
+  auto response = wsc_client_.GetAntiVirusProducts();
+
+  EXPECT_EQ(response.av_products.size(), 0U);
+  EXPECT_EQ(response.parsing_errors.size(), 0U);
+
+  ASSERT_TRUE(response.query_error.has_value());
+  EXPECT_EQ(response.query_error.value(),
+            WscQueryError::kFailedToCreateInstance);
+}
+
+// Tests how the client reacts to a failure occurring when trying to initialize
+// a product list instance.
+TEST_F(WscClientImplTest, GetAntiVirusProducts_FailedInitialize) {
+  product_list_.set_failed_step(FakeWSCProductList::FailureStep::kInitialize);
+
+  auto response = wsc_client_.GetAntiVirusProducts();
+
+  EXPECT_EQ(response.av_products.size(), 0U);
+  EXPECT_EQ(response.parsing_errors.size(), 0U);
+
+  ASSERT_TRUE(response.query_error.has_value());
+  EXPECT_EQ(response.query_error.value(),
+            WscQueryError::kFailedToInitializeProductList);
+}
+
+// Tests how the client reacts to a failure occurring when trying to get a
+// product count from a product list.
+TEST_F(WscClientImplTest, GetAntiVirusProducts_FailedGetProductCount) {
+  product_list_.set_failed_step(FakeWSCProductList::FailureStep::kGetCount);
+
+  auto response = wsc_client_.GetAntiVirusProducts();
+
+  ExpectAvInitialized();
+  EXPECT_EQ(response.av_products.size(), 0U);
+  EXPECT_EQ(response.parsing_errors.size(), 0U);
+
+  ASSERT_TRUE(response.query_error.has_value());
+  EXPECT_EQ(response.query_error.value(),
+            WscQueryError::kFailedToGetProductCount);
+}
+
+// Tests how the client reacts to a failure occurring when trying to get an item
+// from a product list.
+TEST_F(WscClientImplTest, GetAntiVirusProducts_FailedGetItem) {
+  product_list_.set_failed_step(FakeWSCProductList::FailureStep::kGetItem);
+
+  auto* name1 = L"first name";
+  auto* id1 = L"first product id";
+  FakeWscProduct on_product(name1, id1, WSC_SECURITY_PRODUCT_STATE_ON);
+  product_list_.Add(&on_product);
+
+  auto response = wsc_client_.GetAntiVirusProducts();
+
+  ExpectAvInitialized();
+  EXPECT_EQ(response.av_products.size(), 0U);
+  EXPECT_FALSE(response.query_error.has_value());
+
+  ASSERT_EQ(response.parsing_errors.size(), 1U);
+  EXPECT_EQ(response.parsing_errors[0], WscParsingError::kFailedToGetItem);
+}
+
+// Tests how the client reacts to a failure occurring when facing product
+// parsing errors.
+TEST_F(WscClientImplTest, GetAntiVirusProducts_ProductErrors) {
+  // Valid product.
+  auto* name1 = L"first name";
+  auto* id1 = L"first product id";
+  FakeWscProduct on_product(name1, id1, WSC_SECURITY_PRODUCT_STATE_ON);
+
+  // Product missing a state.
+  auto* name2 = L"second name";
+  auto* id2 = L"second product id";
+  FakeWscProduct stateless_product(name2, id2, WSC_SECURITY_PRODUCT_STATE_OFF);
+  stateless_product.set_failed_step(FakeWscProduct::FailureStep::kProductState);
+
+  // Product missing a name.
+  auto* name3 = L"third name";
+  auto* id3 = L"third product id";
+  FakeWscProduct nameless_product(name3, id3,
+                                  WSC_SECURITY_PRODUCT_STATE_SNOOZED);
+  nameless_product.set_failed_step(FakeWscProduct::FailureStep::kProductName);
+
+  // Product missing a name.
+  auto* name4 = L"fourth name";
+  auto* id4 = L"fourth product id";
+  FakeWscProduct id_less_product(name4, id4,
+                                 WSC_SECURITY_PRODUCT_STATE_SNOOZED);
+  id_less_product.set_failed_step(FakeWscProduct::FailureStep::kProductId);
+
+  product_list_.Add(&on_product);
+  product_list_.Add(&stateless_product);
+  product_list_.Add(&nameless_product);
+  product_list_.Add(&id_less_product);
+
+  auto response = wsc_client_.GetAntiVirusProducts();
+
+  ExpectAvInitialized();
+  EXPECT_FALSE(response.query_error.has_value());
+
+  ASSERT_EQ(response.av_products.size(), 1U);
+  EXPECT_EQ(response.av_products[0].display_name, base::SysWideToUTF8(name1));
+  EXPECT_EQ(response.av_products[0].product_id, base::SysWideToUTF8(id1));
+  EXPECT_EQ(response.av_products[0].state, AvProductState::kOn);
+
+  ASSERT_EQ(response.parsing_errors.size(), 3U);
+  EXPECT_EQ(response.parsing_errors[0], WscParsingError::kFailedToGetState);
+  EXPECT_EQ(response.parsing_errors[1], WscParsingError::kFailedToGetName);
+  EXPECT_EQ(response.parsing_errors[2], WscParsingError::kFailedToGetId);
+}
+
+// Smoke/sanity test to verify that Defender's instance GUID does not change
+// over time. This test actually calls WSC.
+TEST(RealWscClientImplTest, SmokeWsc_GetAntiVirusProducts) {
+  // That part of the display name is not translated when getting it from WSC,
+  // so it can be used quite simply.
+  constexpr char kPartialDefenderName[] = "Microsoft Defender";
+
+  constexpr char kDefenderProductGuid[] =
+      "{D68DDC3A-831F-4fae-9E44-DA132C1ACF46}";
+
+  // WSC is only supported on Win8+ (and not server).
+  base::win::OSInfo* os_info = base::win::OSInfo::GetInstance();
+  if (os_info->version_type() == base::win::SUITE_SERVER ||
+      os_info->version() < base::win::Version::WIN8) {
+    return;
+  }
+
+  WscClientImpl wsc_client;
+  auto response = wsc_client.GetAntiVirusProducts();
+
+  for (const auto& av_product : response.av_products) {
+    if (av_product.display_name.find(kPartialDefenderName) !=
+        std::string::npos) {
+      EXPECT_EQ(av_product.product_id, kDefenderProductGuid);
+    }
+  }
+}
+
+}  // namespace device_signals
diff --git a/components/discardable_memory/client/client_discardable_shared_memory_manager.cc b/components/discardable_memory/client/client_discardable_shared_memory_manager.cc
index 571ff78f..87299ea 100644
--- a/components/discardable_memory/client/client_discardable_shared_memory_manager.cc
+++ b/components/discardable_memory/client/client_discardable_shared_memory_manager.cc
@@ -29,6 +29,9 @@
 namespace discardable_memory {
 namespace {
 
+const base::Feature kShorterPeriodicPurge{"ShorterPeriodicPurge",
+                                          base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Global atomic to generate unique discardable shared memory IDs.
 base::AtomicSequenceNumber g_next_discardable_shared_memory_id;
 
@@ -420,8 +423,13 @@
   // recover the memory without adverse latency effects.
   // TODO(crbug.com/1123679): Determine if |kMinAgeForScheduledPurge| and the
   // constant from |ScheduledPurge| need to be tuned.
-  PurgeUnlockedMemory(
-      ClientDiscardableSharedMemoryManager::kMinAgeForScheduledPurge);
+  if (base::FeatureList::IsEnabled(kShorterPeriodicPurge)) {
+    PurgeUnlockedMemory(
+        ClientDiscardableSharedMemoryManager::kMinAgeForScheduledPurge / 2);
+  } else {
+    PurgeUnlockedMemory(
+        ClientDiscardableSharedMemoryManager::kMinAgeForScheduledPurge);
+  }
 
   bool should_schedule = false;
   {
diff --git a/components/discardable_memory/client/client_discardable_shared_memory_manager.h b/components/discardable_memory/client/client_discardable_shared_memory_manager.h
index 055c8f66..b22f65a 100644
--- a/components/discardable_memory/client/client_discardable_shared_memory_manager.h
+++ b/components/discardable_memory/client/client_discardable_shared_memory_manager.h
@@ -78,6 +78,8 @@
     bytes_allocated_limit_for_testing_ = limit;
   }
 
+  // Anything younger than |kMinAgeForScheduledPurge| is not discarded when we
+  // do our periodic purge.
   static constexpr base::TimeDelta kMinAgeForScheduledPurge = base::Minutes(5);
 
   // The expected cost of purging should be very small (< 1ms), so it can be
diff --git a/components/embedder_support/android/metrics/android_metrics_service_client.cc b/components/embedder_support/android/metrics/android_metrics_service_client.cc
index ad3eca7..0fef2d04d 100644
--- a/components/embedder_support/android/metrics/android_metrics_service_client.cc
+++ b/components/embedder_support/android/metrics/android_metrics_service_client.cc
@@ -324,6 +324,7 @@
         pref_service_, /* metrics_reporting_enabled */ false));
     OnMetricsNotStarted();
     pref_service_->ClearPref(prefs::kMetricsClientID);
+    pref_service_->ClearPref(prefs::kMetricsProvisionalClientID);
   }
 }
 
diff --git a/components/embedder_support/pref_names.cc b/components/embedder_support/pref_names.cc
index e0b70f2e..c2a04d0 100644
--- a/components/embedder_support/pref_names.cc
+++ b/components/embedder_support/pref_names.cc
@@ -15,4 +15,8 @@
 const char kForceMajorVersionToMinorPosition[] =
     "force_major_version_to_minor_position_in_user_agent";
 
+// Enum indicating if the user agent reduction feature should be forced enabled
+// or disabled. Defaults to blink::features::kReduceUserAgentMinorVersion trial.
+const char kReduceUserAgentMinorVersion[] = "user_agent_reduction";
+
 }  // namespace embedder_support
diff --git a/components/embedder_support/pref_names.h b/components/embedder_support/pref_names.h
index 1cfd57dc..806ed3c 100644
--- a/components/embedder_support/pref_names.h
+++ b/components/embedder_support/pref_names.h
@@ -12,6 +12,7 @@
 
 extern const char kAlternateErrorPagesEnabled[];
 extern const char kForceMajorVersionToMinorPosition[];
+extern const char kReduceUserAgentMinorVersion[];
 
 }  // namespace embedder_support
 
diff --git a/components/embedder_support/user_agent_utils.cc b/components/embedder_support/user_agent_utils.cc
index 7f9f64b..b8764e5 100644
--- a/components/embedder_support/user_agent_utils.cc
+++ b/components/embedder_support/user_agent_utils.cc
@@ -173,6 +173,18 @@
       force_major_to_minor == ForceMajorVersionToMinorPosition::kForceEnabled);
 }
 
+// Returns true if the user agent reduction should be forced (or prevented).
+// TODO(crbug.com/1330890): Remove this method along with policy.
+bool ShouldReduceUserAgentMinorVersion(
+    UserAgentReductionEnterprisePolicyState user_agent_reduction) {
+  return ((user_agent_reduction !=
+               UserAgentReductionEnterprisePolicyState::kForceDisabled &&
+           base::FeatureList::IsEnabled(
+               blink::features::kReduceUserAgentMinorVersion)) ||
+          user_agent_reduction ==
+              UserAgentReductionEnterprisePolicyState::kForceEnabled);
+}
+
 const std::string& GetMajorInMinorVersionNumber() {
   static const base::NoDestructor<std::string> version_number([] {
     base::Version version(version_info::GetVersionNumber());
@@ -330,18 +342,17 @@
 }  // namespace
 
 std::string GetProductAndVersion(
-    ForceMajorVersionToMinorPosition force_major_to_minor) {
+    ForceMajorVersionToMinorPosition force_major_to_minor,
+    UserAgentReductionEnterprisePolicyState user_agent_reduction) {
   if (ShouldForceMajorVersionToMinorPosition(force_major_to_minor)) {
     // Force major version to 99 and major version to minor version position.
-    if (base::FeatureList::IsEnabled(
-            blink::features::kReduceUserAgentMinorVersion)) {
+    if (ShouldReduceUserAgentMinorVersion(user_agent_reduction)) {
       return "Chrome/" + GetReducedMajorInMinorVersionNumber();
     } else {
       return "Chrome/" + GetMajorInMinorVersionNumber();
     }
   } else {
-    if (base::FeatureList::IsEnabled(
-            blink::features::kReduceUserAgentMinorVersion)) {
+    if (ShouldReduceUserAgentMinorVersion(user_agent_reduction)) {
       return version_info::GetProductNameAndVersionForReducedUserAgent(
           blink::features::kUserAgentFrozenBuildVersion.Get().data());
     } else {
@@ -351,7 +362,8 @@
 }
 
 std::string GetUserAgent(
-    ForceMajorVersionToMinorPosition force_major_to_minor) {
+    ForceMajorVersionToMinorPosition force_major_to_minor,
+    UserAgentReductionEnterprisePolicyState user_agent_reduction) {
   base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
   if (command_line->HasSwitch(kUserAgent)) {
     std::string ua = command_line->GetSwitchValueASCII(kUserAgent);
@@ -366,7 +378,7 @@
   if (base::FeatureList::IsEnabled(blink::features::kReduceUserAgent))
     return GetReducedUserAgent(force_major_to_minor);
 
-  return GetFullUserAgent(force_major_to_minor);
+  return GetFullUserAgent(force_major_to_minor, user_agent_reduction);
 }
 
 std::string GetReducedUserAgent(
@@ -378,8 +390,10 @@
 }
 
 std::string GetFullUserAgent(
-    ForceMajorVersionToMinorPosition force_major_to_minor) {
-  std::string product = GetProductAndVersion(force_major_to_minor);
+    ForceMajorVersionToMinorPosition force_major_to_minor,
+    UserAgentReductionEnterprisePolicyState user_agent_reduction) {
+  std::string product =
+      GetProductAndVersion(force_major_to_minor, user_agent_reduction);
 #if BUILDFLAG(IS_ANDROID)
   if (base::CommandLine::ForCurrentProcess()->HasSwitch(
           switches::kUseMobileUserAgent))
@@ -615,4 +629,19 @@
   }
 }
 
+embedder_support::UserAgentReductionEnterprisePolicyState
+GetUserAgentReductionFromPrefs(const PrefService* pref_service) {
+  if (!pref_service->HasPrefPath(kReduceUserAgentMinorVersion))
+    return UserAgentReductionEnterprisePolicyState::kDefault;
+  switch (pref_service->GetInteger(kReduceUserAgentMinorVersion)) {
+    case 1:
+      return UserAgentReductionEnterprisePolicyState::kForceDisabled;
+    case 2:
+      return UserAgentReductionEnterprisePolicyState::kForceEnabled;
+    case 0:
+    default:
+      return UserAgentReductionEnterprisePolicyState::kDefault;
+  }
+}
+
 }  // namespace embedder_support
diff --git a/components/embedder_support/user_agent_utils.h b/components/embedder_support/user_agent_utils.h
index a345622..bf6db619 100644
--- a/components/embedder_support/user_agent_utils.h
+++ b/components/embedder_support/user_agent_utils.h
@@ -29,6 +29,13 @@
   kForceEnabled = 2,
 };
 
+// TODO(crbug.com/1330890): Remove this enum along with policy.
+enum class UserAgentReductionEnterprisePolicyState {
+  kDefault = 0,
+  kForceDisabled = 1,
+  kForceEnabled = 2,
+};
+
 struct UserAgentOptions {
   bool force_major_version_100 = false;
   ForceMajorVersionToMinorPosition force_major_to_minor =
@@ -43,13 +50,17 @@
 // TODO(crbug.com/1291612): modify to accept an optional PrefService*.
 std::string GetProductAndVersion(
     ForceMajorVersionToMinorPosition force_major_to_minor =
-        ForceMajorVersionToMinorPosition::kDefault);
+        ForceMajorVersionToMinorPosition::kDefault,
+    UserAgentReductionEnterprisePolicyState user_agent_reduction =
+        UserAgentReductionEnterprisePolicyState::kDefault);
 
 // Returns the user agent string for Chrome.
 // TODO(crbug.com/1291612): modify to accept an optional PrefService*.
 std::string GetFullUserAgent(
     ForceMajorVersionToMinorPosition force_major_to_minor =
-        ForceMajorVersionToMinorPosition::kDefault);
+        ForceMajorVersionToMinorPosition::kDefault,
+    UserAgentReductionEnterprisePolicyState user_agent_reduction =
+        UserAgentReductionEnterprisePolicyState::kDefault);
 
 // Returns the reduced user agent string for Chrome.
 // TODO(crbug.com/1291612): modify to accept an optional PrefService*.
@@ -60,8 +71,11 @@
 // Returns the full or "reduced" user agent string, depending on the
 // UserAgentReduction enterprise policy and blink::features::kReduceUserAgent
 // TODO(crbug.com/1291612): modify to accept an optional PrefService*.
-std::string GetUserAgent(ForceMajorVersionToMinorPosition force_major_to_minor =
-                             ForceMajorVersionToMinorPosition::kDefault);
+std::string GetUserAgent(
+    ForceMajorVersionToMinorPosition force_major_to_minor =
+        ForceMajorVersionToMinorPosition::kDefault,
+    UserAgentReductionEnterprisePolicyState user_agent_reduction =
+        UserAgentReductionEnterprisePolicyState::kDefault);
 
 // Returns UserAgentMetadata per the default policy.
 // This override is currently used in fuchsia, where the enterprise policy
@@ -114,6 +128,12 @@
 embedder_support::ForceMajorVersionToMinorPosition GetMajorToMinorFromPrefs(
     const PrefService* pref_service);
 
+// Returns the UserAgentReductionEnterprisePolicyState enum value corresponding
+// to the provided integer policy value for UserAgentReduction.
+// TODO(crbug.com/1330890): Remove this function with policy.
+embedder_support::UserAgentReductionEnterprisePolicyState
+GetUserAgentReductionFromPrefs(const PrefService* pref_service);
+
 }  // namespace embedder_support
 
 #endif  // COMPONENTS_EMBEDDER_SUPPORT_USER_AGENT_UTILS_H_
diff --git a/components/embedder_support/user_agent_utils_unittest.cc b/components/embedder_support/user_agent_utils_unittest.cc
index d9a50f7..af497d7 100644
--- a/components/embedder_support/user_agent_utils_unittest.cc
+++ b/components/embedder_support/user_agent_utils_unittest.cc
@@ -997,38 +997,68 @@
 }
 
 TEST_F(UserAgentUtilsTest, GetProductAndVersion) {
+  std::string product;
+  std::string major_version;
+  std::string minor_version;
+  std::string build_version;
+  std::string patch_version;
+
+  // (1) Features: UserAgentReduction and MajorVersionInMinor disabled.
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitWithFeatures(
       /*enabled_features=*/{}, /*disabled_features=*/{
           blink::features::kForceMajorVersionInMinorPositionInUserAgent,
           blink::features::kReduceUserAgentMinorVersion});
 
-  std::string product = GetProductAndVersion();
-  std::string major_version;
-  std::string minor_version;
+  // (1a) Policies: UserAgentReduction and MajorVersionInMinor default.
+  product =
+      GetProductAndVersion(ForceMajorVersionToMinorPosition::kDefault,
+                           UserAgentReductionEnterprisePolicyState::kDefault);
   EXPECT_TRUE(re2::RE2::FullMatch(product, kChromeProductVersionRegex,
-                                  &major_version, &minor_version));
+                                  &major_version, &minor_version,
+                                  &build_version, &patch_version));
   EXPECT_EQ(major_version, version_info::GetMajorVersionNumber());
   EXPECT_EQ(minor_version, "0");
+  EXPECT_NE(build_version, "0");
+  EXPECT_EQ(patch_version, "0");
 
-  // Ensure policy is respected if ForceMajorToMinor is force enabled
-  product =
-      GetProductAndVersion(ForceMajorVersionToMinorPosition::kForceEnabled);
+  // (1b) Policies: UserAgentReduction and MajorVersionInMinor force enabled.
+  product = GetProductAndVersion(
+      ForceMajorVersionToMinorPosition::kForceEnabled,
+      UserAgentReductionEnterprisePolicyState::kForceEnabled);
   EXPECT_TRUE(re2::RE2::FullMatch(product, kChromeProductVersionRegex,
-                                  &major_version, &minor_version));
+                                  &major_version, &minor_version,
+                                  &build_version, &patch_version));
   EXPECT_EQ(major_version, "99");
   EXPECT_EQ(minor_version, version_info::GetMajorVersionNumber());
+  EXPECT_EQ(build_version, "0");
+  EXPECT_EQ(patch_version, "0");
 
-  // Ensure the build version FeatureParam is used when set.
+  // (1c) Policies:: UserAgentReduction and MajorVersionInMinor force disabled.
+  product = GetProductAndVersion(
+      ForceMajorVersionToMinorPosition::kForceDisabled,
+      UserAgentReductionEnterprisePolicyState::kForceDisabled);
+  EXPECT_TRUE(re2::RE2::FullMatch(product, kChromeProductVersionRegex,
+                                  &major_version, &minor_version,
+                                  &build_version, &patch_version));
+  EXPECT_EQ(major_version, version_info::GetMajorVersionNumber());
+  EXPECT_EQ(minor_version, "0");
+  EXPECT_NE(build_version, "0");
+  EXPECT_EQ(patch_version, "0");
+
+  // (2) Features: UserAgentReduction enabled with version and
+  // MajorVersionInMinor disabled.
   scoped_feature_list.Reset();
   scoped_feature_list.InitWithFeaturesAndParameters(
       /*enabled_features=*/{{blink::features::kReduceUserAgentMinorVersion,
                              {{{"build_version", "5555"}}}}},
       /*disabled_features=*/{
           blink::features::kForceMajorVersionInMinorPositionInUserAgent});
-  product = GetProductAndVersion();
-  std::string build_version;
-  std::string patch_version;
+
+  // (2a) Policies: UserAgentReduction and MajorVersionInMinor default.
+  product =
+      GetProductAndVersion(ForceMajorVersionToMinorPosition::kDefault,
+                           UserAgentReductionEnterprisePolicyState::kDefault);
   EXPECT_TRUE(re2::RE2::FullMatch(product, kChromeProductVersionRegex,
                                   &major_version, &minor_version,
                                   &build_version, &patch_version));
@@ -1037,36 +1067,84 @@
   EXPECT_EQ(build_version, "5555");
   EXPECT_EQ(patch_version, "0");
 
-  // Ensure policy is respected if ForcemajorToMinor is force disabled, even if
-  // the respective Blink feature is enabled.
+  // (2b) Policies: UserAgentReduction and MajorVersionInMinor force enabled.
+  product = GetProductAndVersion(
+      ForceMajorVersionToMinorPosition::kForceEnabled,
+      UserAgentReductionEnterprisePolicyState::kForceEnabled);
+  EXPECT_TRUE(re2::RE2::FullMatch(product, kChromeProductVersionRegex,
+                                  &major_version, &minor_version,
+                                  &build_version, &patch_version));
+  EXPECT_EQ(major_version, "99");
+  EXPECT_EQ(minor_version, version_info::GetMajorVersionNumber());
+  EXPECT_EQ(build_version, "0");
+  EXPECT_EQ(patch_version, "0");
+
+  // (2c) Policies:: UserAgentReduction and MajorVersionInMinor force disabled.
+  product = GetProductAndVersion(
+      ForceMajorVersionToMinorPosition::kForceDisabled,
+      UserAgentReductionEnterprisePolicyState::kForceDisabled);
+  EXPECT_TRUE(re2::RE2::FullMatch(product, kChromeProductVersionRegex,
+                                  &major_version, &minor_version,
+                                  &build_version, &patch_version));
+  EXPECT_EQ(major_version, version_info::GetMajorVersionNumber());
+  EXPECT_EQ(minor_version, "0");
+  EXPECT_NE(build_version, "5555");
+  EXPECT_EQ(patch_version, "0");
+
+  // (3) Features: UserAgentReduction disabled and MajorVersionInMinor enabled.
   scoped_feature_list.Reset();
   scoped_feature_list.InitWithFeatures(
       /*enabled_features=*/{blink::features::
                                 kForceMajorVersionInMinorPositionInUserAgent},
       /*disabled_features=*/{blink::features::kReduceUserAgentMinorVersion});
-  product =
-      GetProductAndVersion(ForceMajorVersionToMinorPosition::kForceDisabled);
-  EXPECT_TRUE(re2::RE2::FullMatch(product, kChromeProductVersionRegex,
-                                  &major_version, &minor_version,
-                                  &build_version));
-  EXPECT_EQ(major_version, version_info::GetMajorVersionNumber());
-  EXPECT_EQ(minor_version, "0");
-  EXPECT_NE(build_version, "9999");
 
-  product = GetProductAndVersion();
+  // (3a) Policies: UserAgentReduction and MajorVersionInMinor default.
+  product =
+      GetProductAndVersion(ForceMajorVersionToMinorPosition::kDefault,
+                           UserAgentReductionEnterprisePolicyState::kDefault);
   EXPECT_TRUE(re2::RE2::FullMatch(product, kChromeProductVersionRegex,
                                   &major_version, &minor_version,
-                                  &build_version));
+                                  &build_version, &patch_version));
   EXPECT_EQ(major_version, "99");
   EXPECT_EQ(minor_version, version_info::GetMajorVersionNumber());
   EXPECT_NE(build_version, "0");
+  EXPECT_EQ(patch_version, "0");
 
+  // (3b) Policies: UserAgentReduction and MajorVersionInMinor force enabled.
+  product = GetProductAndVersion(
+      ForceMajorVersionToMinorPosition::kForceEnabled,
+      UserAgentReductionEnterprisePolicyState::kForceEnabled);
+  EXPECT_TRUE(re2::RE2::FullMatch(product, kChromeProductVersionRegex,
+                                  &major_version, &minor_version,
+                                  &build_version, &patch_version));
+  EXPECT_EQ(major_version, "99");
+  EXPECT_EQ(minor_version, version_info::GetMajorVersionNumber());
+  EXPECT_EQ(build_version, "0");
+  EXPECT_EQ(patch_version, "0");
+
+  // (3c) Policies:: UserAgentReduction and MajorVersionInMinor force disabled.
+  product = GetProductAndVersion(
+      ForceMajorVersionToMinorPosition::kForceDisabled,
+      UserAgentReductionEnterprisePolicyState::kForceDisabled);
+  EXPECT_TRUE(re2::RE2::FullMatch(product, kChromeProductVersionRegex,
+                                  &major_version, &minor_version,
+                                  &build_version, &patch_version));
+  EXPECT_EQ(major_version, version_info::GetMajorVersionNumber());
+  EXPECT_EQ(minor_version, "0");
+  EXPECT_NE(build_version, "0");
+  EXPECT_EQ(patch_version, "0");
+
+  // (4) Features: UserAgentReduction enabled and MajorVersionInMinor disabled.
   scoped_feature_list.Reset();
   scoped_feature_list.InitWithFeatures(
       /*enabled_features=*/{blink::features::kReduceUserAgentMinorVersion},
       /*disabled_features=*/{
           blink::features::kForceMajorVersionInMinorPositionInUserAgent});
-  product = GetProductAndVersion();
+
+  // (4a) Policies: UserAgentReduction and MajorVersionInMinor default.
+  product =
+      GetProductAndVersion(ForceMajorVersionToMinorPosition::kDefault,
+                           UserAgentReductionEnterprisePolicyState::kDefault);
   EXPECT_TRUE(re2::RE2::FullMatch(product, kChromeProductVersionRegex,
                                   &major_version, &minor_version,
                                   &build_version, &patch_version));
@@ -1075,13 +1153,10 @@
   EXPECT_EQ(build_version, "0");
   EXPECT_EQ(patch_version, "0");
 
-  scoped_feature_list.Reset();
-  scoped_feature_list.InitWithFeatures(
-      /*enabled_features=*/{blink::features::kReduceUserAgentMinorVersion,
-                            blink::features::
-                                kForceMajorVersionInMinorPositionInUserAgent},
-      /*disabled_features=*/{});
-  product = GetProductAndVersion();
+  // (4b) Policies: UserAgentReduction and MajorVersionInMinor force enabled.
+  product = GetProductAndVersion(
+      ForceMajorVersionToMinorPosition::kForceEnabled,
+      UserAgentReductionEnterprisePolicyState::kForceEnabled);
   EXPECT_TRUE(re2::RE2::FullMatch(product, kChromeProductVersionRegex,
                                   &major_version, &minor_version,
                                   &build_version, &patch_version));
@@ -1089,6 +1164,62 @@
   EXPECT_EQ(minor_version, version_info::GetMajorVersionNumber());
   EXPECT_EQ(build_version, "0");
   EXPECT_EQ(patch_version, "0");
+
+  // (4c) Policies:: UserAgentReduction and MajorVersionInMinor force disabled.
+  product = GetProductAndVersion(
+      ForceMajorVersionToMinorPosition::kForceDisabled,
+      UserAgentReductionEnterprisePolicyState::kForceDisabled);
+  EXPECT_TRUE(re2::RE2::FullMatch(product, kChromeProductVersionRegex,
+                                  &major_version, &minor_version,
+                                  &build_version, &patch_version));
+  EXPECT_EQ(major_version, version_info::GetMajorVersionNumber());
+  EXPECT_EQ(minor_version, "0");
+  EXPECT_NE(build_version, "0");
+  EXPECT_EQ(patch_version, "0");
+
+  // (5) Features: UserAgentReduction and MajorVersionInMinor enabled.
+  scoped_feature_list.Reset();
+  scoped_feature_list.InitWithFeatures(
+      /*enabled_features=*/{blink::features::kReduceUserAgentMinorVersion,
+                            blink::features::
+                                kForceMajorVersionInMinorPositionInUserAgent},
+      /*disabled_features=*/{});
+
+  // (5a) Policies: UserAgentReduction and MajorVersionInMinor default.
+  product =
+      GetProductAndVersion(ForceMajorVersionToMinorPosition::kDefault,
+                           UserAgentReductionEnterprisePolicyState::kDefault);
+  EXPECT_TRUE(re2::RE2::FullMatch(product, kChromeProductVersionRegex,
+                                  &major_version, &minor_version,
+                                  &build_version, &patch_version));
+  EXPECT_EQ(major_version, "99");
+  EXPECT_EQ(minor_version, version_info::GetMajorVersionNumber());
+  EXPECT_EQ(build_version, "0");
+  EXPECT_EQ(patch_version, "0");
+
+  // (5b) Policies: UserAgentReduction and MajorVersionInMinor force enabled.
+  product = GetProductAndVersion(
+      ForceMajorVersionToMinorPosition::kForceEnabled,
+      UserAgentReductionEnterprisePolicyState::kForceEnabled);
+  EXPECT_TRUE(re2::RE2::FullMatch(product, kChromeProductVersionRegex,
+                                  &major_version, &minor_version,
+                                  &build_version, &patch_version));
+  EXPECT_EQ(major_version, "99");
+  EXPECT_EQ(minor_version, version_info::GetMajorVersionNumber());
+  EXPECT_EQ(build_version, "0");
+  EXPECT_EQ(patch_version, "0");
+
+  // (5c) Policies:: UserAgentReduction and MajorVersionInMinor force disabled.
+  product = GetProductAndVersion(
+      ForceMajorVersionToMinorPosition::kForceDisabled,
+      UserAgentReductionEnterprisePolicyState::kForceDisabled);
+  EXPECT_TRUE(re2::RE2::FullMatch(product, kChromeProductVersionRegex,
+                                  &major_version, &minor_version,
+                                  &build_version, &patch_version));
+  EXPECT_EQ(major_version, version_info::GetMajorVersionNumber());
+  EXPECT_EQ(minor_version, "0");
+  EXPECT_NE(build_version, "0");
+  EXPECT_EQ(patch_version, "0");
 }
 
 TEST_F(UserAgentUtilsTest, GetUserAgent) {
diff --git a/components/fuchsia_component_support/BUILD.gn b/components/fuchsia_component_support/BUILD.gn
index 21afde6..f007975 100644
--- a/components/fuchsia_component_support/BUILD.gn
+++ b/components/fuchsia_component_support/BUILD.gn
@@ -13,7 +13,7 @@
     ":unit_tests",
     "//chromecast/internal/*",
     "//fuchsia/engine/*",
-    "//fuchsia/runners/*",
+    "//fuchsia_web/runners/*",
   ]
   public = [
     "config_reader.h",
diff --git a/components/gcm_driver/crypto/encryption_header_parsers.cc b/components/gcm_driver/crypto/encryption_header_parsers.cc
index b70dc527..a695f354 100644
--- a/components/gcm_driver/crypto/encryption_header_parsers.cc
+++ b/components/gcm_driver/crypto/encryption_header_parsers.cc
@@ -83,16 +83,16 @@
     const base::StringPiece name = name_value_pairs.name_piece();
     const base::StringPiece value = name_value_pairs.value_piece();
 
-    if (base::LowerCaseEqualsASCII(name, "keyid")) {
+    if (base::EqualsCaseInsensitiveASCII(name, "keyid")) {
       if (found_keyid)
         return false;
       keyid_.assign(value.data(), value.size());
       found_keyid = true;
-    } else if (base::LowerCaseEqualsASCII(name, "salt")) {
+    } else if (base::EqualsCaseInsensitiveASCII(name, "salt")) {
       if (found_salt || !ValueToDecodedString(value, &salt_))
         return false;
       found_salt = true;
-    } else if (base::LowerCaseEqualsASCII(name, "rs")) {
+    } else if (base::EqualsCaseInsensitiveASCII(name, "rs")) {
       if (found_rs || !RecordSizeToInt(value, &rs_))
         return false;
       found_rs = true;
@@ -132,16 +132,16 @@
     const base::StringPiece name = name_value_pairs.name_piece();
     const base::StringPiece value = name_value_pairs.value_piece();
 
-    if (base::LowerCaseEqualsASCII(name, "keyid")) {
+    if (base::EqualsCaseInsensitiveASCII(name, "keyid")) {
       if (found_keyid)
         return false;
       keyid_.assign(value.data(), value.size());
       found_keyid = true;
-    } else if (base::LowerCaseEqualsASCII(name, "aesgcm128")) {
+    } else if (base::EqualsCaseInsensitiveASCII(name, "aesgcm128")) {
       if (found_aesgcm128 || !ValueToDecodedString(value, &aesgcm128_))
         return false;
       found_aesgcm128 = true;
-    } else if (base::LowerCaseEqualsASCII(name, "dh")) {
+    } else if (base::EqualsCaseInsensitiveASCII(name, "dh")) {
       if (found_dh || !ValueToDecodedString(value, &dh_))
         return false;
       found_dh = true;
diff --git a/components/history_clusters/core/config.h b/components/history_clusters/core/config.h
index 9ed8b632..b360353 100644
--- a/components/history_clusters/core/config.h
+++ b/components/history_clusters/core/config.h
@@ -74,7 +74,10 @@
   bool omnibox_action = false;
 
   // If enabled, allows the Omnibox Action chip to also appear on URLs. This
-  // does nothing if `omnibox_action` is disabled.
+  // does nothing if `omnibox_action` is disabled. Note, that if you turn this
+  // flag to true, you almost certainly will want to set
+  // `omnibox_action_on_navigation_intents` to true as well, as otherwise your
+  // desired action chips on URLs will almost certainly all be suppressed.
   bool omnibox_action_on_urls = false;
 
   // If enabled, allows the Omnibox Action chip to appear on URLs from noisy
@@ -85,7 +88,7 @@
   // user is intending to perform a navigation. This does not affect which
   // suggestions are allowed to display the chip. Does nothing if
   // `omnibox_action` is disabled.
-  bool omnibox_action_on_navigation_intents = true;
+  bool omnibox_action_on_navigation_intents = false;
 
   // If `omnibox_action_on_navigation_intents` is enabled, this threshold
   // helps determine when the user is intending to perform a navigation.
@@ -93,7 +96,7 @@
 
   // If enabled, allows the Omnibox Action chip to appear when the suggestions
   // contain pedals. Does nothing if `omnibox_action` is disabled.
-  bool omnibox_action_with_pedals = true;
+  bool omnibox_action_with_pedals = false;
 
   // If enabled, adds the keywords of aliases for detected entity names to a
   // cluster.
diff --git a/components/metrics/metrics_pref_names.cc b/components/metrics/metrics_pref_names.cc
index d7be87e..b5e39cb 100644
--- a/components/metrics/metrics_pref_names.cc
+++ b/components/metrics/metrics_pref_names.cc
@@ -12,6 +12,18 @@
 // Note: the 'uninstall_metrics' name is a legacy name and doesn't mean much.
 const char kInstallDate[] = "uninstall_metrics.installation_date2";
 
+// A provisional metrics client GUID used for field trial group assignments
+// before metrics reporting consent is known (i.e., during first run). This GUID
+// is never reported directly. However, if the user enables UMA, this
+// provisional client GUID becomes the metrics client GUID (see
+// |kMetricsClientID|), and this pref is cleared. In that case, the GUID may
+// be reported.
+// Note: This GUID is stored in prefs because it is possible that the user
+// closes Chrome during the FRE. We re-use this GUID in subsequent FRE runs
+// until metrics reporting consent is truly known.
+const char kMetricsProvisionalClientID[] =
+    "user_experience_metrics.provisional_client_id";
+
 // The metrics client GUID.
 // Note: The name client_id2 is a result of creating
 // new prefs to do a one-time reset of the previous values.
diff --git a/components/metrics/metrics_pref_names.h b/components/metrics/metrics_pref_names.h
index 20eb93c4..ec9bbe74 100644
--- a/components/metrics/metrics_pref_names.h
+++ b/components/metrics/metrics_pref_names.h
@@ -20,6 +20,7 @@
 extern const char kMetricsInitialLogsMetadata[];
 extern const char kMetricsLowEntropySource[];
 extern const char kMetricsOldLowEntropySource[];
+extern const char kMetricsProvisionalClientID[];
 extern const char kMetricsPseudoLowEntropySource[];
 extern const char kMetricsMachineId[];
 extern const char kMetricsOngoingLogs[];
diff --git a/components/metrics/metrics_state_manager.cc b/components/metrics/metrics_state_manager.cc
index b73db9f..6bbfe4b 100644
--- a/components/metrics/metrics_state_manager.cc
+++ b/components/metrics/metrics_state_manager.cc
@@ -28,6 +28,7 @@
 #include "base/strings/stringprintf.h"
 #include "base/threading/thread_restrictions.h"
 #include "base/time/time.h"
+#include "build/branding_buildflags.h"
 #include "build/build_config.h"
 #include "components/metrics/cloned_install_detector.h"
 #include "components/metrics/enabled_state_provider.h"
@@ -226,6 +227,9 @@
 // static
 bool MetricsStateManager::instance_exists_ = false;
 
+// static
+bool MetricsStateManager::enable_provisional_client_id_for_testing_ = false;
+
 MetricsStateManager::MetricsStateManager(
     PrefService* local_state,
     EnabledStateProvider* enabled_state_provider,
@@ -283,33 +287,26 @@
 #endif  // BUILDFLAG(IS_ANDROID)
   }
 
-#if !BUILDFLAG(IS_WIN)
-  if (is_first_run) {
-    // If this is a first run (no install date) and there's no client id, then
-    // generate a provisional client id now. This id will be used for field
-    // trial randomization on first run and will be promoted to become the
-    // client id if UMA is enabled during this session, via the logic in
-    // ForceClientIdCreation().
-    //
-    // Note: We don't do this on Windows because on Windows, there's no UMA
-    // checkbox on first run and instead it comes from the install page. So if
-    // UMA is not enabled at this point, it's unlikely it will be enabled in
-    // the same session since that requires the user to manually do that via
-    // settings page after they unchecked it on the download page.
-    //
-    // Note: Windows first run is covered by browser tests
-    // FirstRunMasterPrefsVariationsSeedTest.PRE_SecondRun and
-    // FirstRunMasterPrefsVariationsSeedTest.SecondRun. If the platform ifdef
-    // for this logic changes, the tests should be updated as well.
-    if (client_id_.empty())
-      provisional_client_id_ = base::GenerateGUID();
+  // Generate and store a provisional client ID if necessary. This ID will be
+  // used for field trial randomization on first run (and possibly in future
+  // runs if the user closes Chrome during the FRE) and will be promoted to
+  // become the client ID if UMA is enabled during this session, via the logic
+  // in ForceClientIdCreation(). If UMA is disabled (refused), we discard it.
+  //
+  // Note: This means that if a provisional client ID is used for this session,
+  // and the user disables (refuses) UMA, then starting from the next run, the
+  // field trial randomization (group assignment) will be different.
+  if (ShouldGenerateProvisionalClientId(is_first_run)) {
+    local_state_->SetString(prefs::kMetricsProvisionalClientID,
+                            base::GenerateGUID());
   }
-#endif  // !BUILDFLAG(IS_WIN)
 
   // The |initial_client_id_| should only be set if UMA is enabled or there's a
   // provisional client id.
   initial_client_id_ =
-      (client_id_.empty() ? provisional_client_id_ : client_id_);
+      (client_id_.empty()
+           ? local_state_->GetString(prefs::kMetricsProvisionalClientID)
+           : client_id_);
   DCHECK(!instance_exists_);
   instance_exists_ = true;
 }
@@ -501,7 +498,9 @@
   // so generate a new one. If there's a provisional client id (e.g. UMA
   // was enabled as part of first run), promote that to the client id,
   // otherwise (e.g. UMA enabled in a future session), generate a new one.
-  if (provisional_client_id_.empty()) {
+  std::string provisional_client_id =
+      local_state_->GetString(prefs::kMetricsProvisionalClientID);
+  if (provisional_client_id.empty()) {
     client_id_ = base::GenerateGUID();
     base::UmaHistogramEnumeration("UMA.ClientIdSource",
                                   ClientIdSource::kClientIdNew);
@@ -511,8 +510,8 @@
         previous_client_id);
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
   } else {
-    client_id_ = provisional_client_id_;
-    provisional_client_id_.clear();
+    client_id_ = provisional_client_id;
+    local_state_->ClearPref(prefs::kMetricsProvisionalClientID);
     base::UmaHistogramEnumeration("UMA.ClientIdSource",
                                   ClientIdSource::kClientIdFromProvisionalId);
 #if BUILDFLAG(IS_CHROMEOS_ASH)
@@ -593,6 +592,8 @@
 
 // static
 void MetricsStateManager::RegisterPrefs(PrefRegistrySimple* registry) {
+  registry->RegisterStringPref(prefs::kMetricsProvisionalClientID,
+                               std::string());
   registry->RegisterStringPref(prefs::kMetricsClientID, std::string());
   registry->RegisterInt64Pref(prefs::kMetricsReportingEnabledTimestamp, 0);
   registry->RegisterInt64Pref(prefs::kInstallDate, 0);
@@ -672,6 +673,55 @@
   store_client_info_.Run(ClientInfo());
 }
 
+bool MetricsStateManager::ShouldGenerateProvisionalClientId(bool is_first_run) {
+#if BUILDFLAG(IS_WIN)
+  // We do not want to generate a provisional client ID on Windows because
+  // there's no UMA checkbox on first run. Instead it comes from the install
+  // page. So if UMA is not enabled at this point, it's unlikely it will be
+  // enabled in the same session since that requires the user to manually do
+  // that via settings page after they unchecked it on the download page.
+  //
+  // Note: Windows first run is covered by browser tests
+  // FirstRunMasterPrefsVariationsSeedTest.PRE_SecondRun and
+  // FirstRunMasterPrefsVariationsSeedTest.SecondRun. If the platform ifdef
+  // for this logic changes, the tests should be updated as well.
+  return false;
+#else
+  // We should only generate a provisional client ID on the first run. If for
+  // some reason there is already a client ID, we do not generate one either.
+  // This can happen if metrics reporting is managed by a policy.
+  if (!is_first_run || !client_id_.empty())
+    return false;
+
+  // Return false if |kMetricsReportingEnabled| is managed by a policy. For
+  // example, if metrics reporting is disabled by a policy, then
+  // |kMetricsReportingEnabled| will always be set to false, so there is no
+  // reason to generate a provisional client ID. If metrics reporting is enabled
+  // by a policy, then the default value of |kMetricsReportingEnabled| will be
+  // true, and so a client ID will have already been generated (we would have
+  // returned false already because of the previous check).
+  if (local_state_->IsManagedPreference(prefs::kMetricsReportingEnabled))
+    return false;
+
+  // If this is a non-Google-Chrome-branded build, we do not want to generate a
+  // provisional client ID because metrics reporting is not enabled on those
+  // builds. This would be problematic because we store the provisional client
+  // ID in the Local State, and clear it when either 1) we enable UMA (the
+  // provisional client ID becomes the client ID), or 2) we disable UMA. Since
+  // in non-Google-Chrome-branded builds we never actually go through the code
+  // paths to either enable or disable UMA, the pref storing the provisional
+  // client ID would never be cleared. However, for test consistency between
+  // the different builds, we do not return false here if
+  // |enable_provisional_client_id_for_testing_| is set to true.
+  if (!BUILDFLAG(GOOGLE_CHROME_BRANDING) &&
+      !enable_provisional_client_id_for_testing_) {
+    return false;
+  }
+
+  return true;
+#endif  // BUILDFLAG(IS_WIN)
+}
+
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 void MetricsStateManager::LogClientIdChanged(
     metrics::structured::NeutrinoDevicesLocation location,
diff --git a/components/metrics/metrics_state_manager.h b/components/metrics/metrics_state_manager.h
index 27128fc..513efe4 100644
--- a/components/metrics/metrics_state_manager.h
+++ b/components/metrics/metrics_state_manager.h
@@ -215,7 +215,7 @@
   FRIEND_TEST_ALL_PREFIXES(MetricsStateManagerTest,
                            ProvisionalClientId_PromotedToClientId);
   FRIEND_TEST_ALL_PREFIXES(MetricsStateManagerTest,
-                           ProvisionalClientId_NotPersisted);
+                           ProvisionalClientId_PersistedAcrossFirstRuns);
   FRIEND_TEST_ALL_PREFIXES(MetricsStateManagerTest, ResetBackup);
   FRIEND_TEST_ALL_PREFIXES(MetricsStateManagerTest, ResetMetricsIDs);
 
@@ -284,7 +284,7 @@
   // Returns the high entropy source for this client, which is composed of a
   // client ID and the low entropy source. This is intended to be unique for
   // each install. UMA must be enabled (and |client_id_| must be set) or
-  // |provisional_client_id_| must be set before calling this.
+  // |kMetricsProvisionalClientID| must be set before calling this.
   std::string GetHighEntropySource();
 
   // Returns the old low entropy source for this client.
@@ -309,6 +309,8 @@
   // pref is true.
   void ResetMetricsIDsIfNecessary();
 
+  bool ShouldGenerateProvisionalClientId(bool is_first_run);
+
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   // Log to structured metrics when the client id is changed.
   void LogClientIdChanged(metrics::structured::NeutrinoDevicesLocation location,
@@ -341,14 +343,6 @@
   // The identifier that's sent to the server with the log reports.
   std::string client_id_;
 
-  // A provisional client id that's generated at start up before we know whether
-  // metrics consent has been received from the client. This id becomes the
-  // |client_id_| if consent is given within the same session, or is cleared
-  // otherwise. Does not control transmission of UMA metrics, only used for the
-  // high entropy source used for field trial randomization so that field
-  // trials don't toggle state between first and second run.
-  std::string provisional_client_id_;
-
   // The client id that was used do field trial randomization. This field should
   // only be changed when we need to do group assignment. |initial_client_id|
   // should left blank iff a client id was not used to do field trial
@@ -384,6 +378,10 @@
   // used only during startup. On Android WebLayer, Android WebView, and iOS,
   // the visibility is unknown at this point in startup.
   const StartupVisibility startup_visibility_;
+
+  // Force enables the creation of a provisional client ID on first run even if
+  // this is not a Chrome-branded build. Used for testing.
+  static bool enable_provisional_client_id_for_testing_;
 };
 
 }  // namespace metrics
diff --git a/components/metrics/metrics_state_manager_unittest.cc b/components/metrics/metrics_state_manager_unittest.cc
index caab1380..1c7efc1 100644
--- a/components/metrics/metrics_state_manager_unittest.cc
+++ b/components/metrics/metrics_state_manager_unittest.cc
@@ -322,15 +322,21 @@
 
 #if !BUILDFLAG(IS_WIN)
 TEST_F(MetricsStateManagerTest, ProvisionalClientId_PromotedToClientId) {
+  // Force enable the creation of a provisional client ID on first run for
+  // consistency between Chromium and Chrome builds.
+  MetricsStateManager::enable_provisional_client_id_for_testing_ = true;
+
   std::unique_ptr<MetricsStateManager> state_manager(CreateStateManager());
 
   // Verify that there was a provisional client id created.
-  std::string provisional_client_id = state_manager->provisional_client_id_;
+  std::string provisional_client_id =
+      prefs_.GetString(prefs::kMetricsProvisionalClientID);
   VerifyClientId(provisional_client_id);
   // No client id should have been stored.
   EXPECT_TRUE(prefs_.FindPreference(prefs::kMetricsClientID)->IsDefaultValue());
   int low_entropy_source = state_manager->GetLowEntropySource();
-  // The default entropy provider should be the high entropy one.
+  // The default entropy provider should be the high entropy one since we a
+  // the provisional client ID.
   state_manager->CreateDefaultEntropyProvider();
   EXPECT_EQ(state_manager->entropy_source_returned(),
             MetricsStateManager::ENTROPY_SOURCE_HIGH);
@@ -342,45 +348,53 @@
   std::string client_id = state_manager->client_id();
   EXPECT_EQ(provisional_client_id, client_id);
   EXPECT_EQ(prefs_.GetString(prefs::kMetricsClientID), client_id);
-  EXPECT_TRUE(state_manager->provisional_client_id_.empty());
+  EXPECT_TRUE(prefs_.FindPreference(prefs::kMetricsProvisionalClientID)
+                  ->IsDefaultValue());
+  EXPECT_TRUE(prefs_.GetString(prefs::kMetricsProvisionalClientID).empty());
   EXPECT_EQ(state_manager->GetLowEntropySource(), low_entropy_source);
   EXPECT_EQ(client_info_load_count_, 1);
 }
 
-TEST_F(MetricsStateManagerTest, ProvisionalClientId_NotPersisted) {
-  std::string provisional_client_id;
-  int low_entropy_source;
+TEST_F(MetricsStateManagerTest, ProvisionalClientId_PersistedAcrossFirstRuns) {
+  // Force enable the creation of a provisional client ID on first run for
+  // consistency between Chromium and Chrome builds.
+  MetricsStateManager::enable_provisional_client_id_for_testing_ = true;
 
-  // First run, with a provisional client id.
+  std::string provisional_client_id;
+
+  // Simulate a first run, and verify that a provisional client id is generated.
+  // We also do not enable nor disable UMA in order to simulate exiting during
+  // the first run flow.
   {
     std::unique_ptr<MetricsStateManager> state_manager(CreateStateManager());
     // Verify that there was a provisional client id created.
-    std::string provisional_client_id = state_manager->provisional_client_id_;
+    provisional_client_id =
+        prefs_.GetString(prefs::kMetricsProvisionalClientID);
     VerifyClientId(provisional_client_id);
     // No client id should have been stored.
     EXPECT_TRUE(
         prefs_.FindPreference(prefs::kMetricsClientID)->IsDefaultValue());
-    low_entropy_source = state_manager->GetLowEntropySource();
-    // The default entropy provider should be the high entropy one.
+    // The default entropy provider should be the high entropy one since we a
+    // the provisional client ID.
     state_manager->CreateDefaultEntropyProvider();
     EXPECT_EQ(state_manager->entropy_source_returned(),
               MetricsStateManager::ENTROPY_SOURCE_HIGH);
   }
 
-  // Now, simulate a second run, such that UMA was not turned on during the
-  // first run. This should not result in any client id existing nor any
-  // provisional client id.
+  // Now, simulate a second run, and verify that the provisional client ID is
+  // the same.
   {
     std::unique_ptr<MetricsStateManager> state_manager(CreateStateManager());
-    EXPECT_TRUE(state_manager->provisional_client_id_.empty());
-    EXPECT_TRUE(state_manager->client_id().empty());
-    EXPECT_EQ(state_manager->GetLowEntropySource(), low_entropy_source);
-    EXPECT_TRUE(
-        prefs_.FindPreference(prefs::kMetricsClientID)->IsDefaultValue());
-    // The default entropy provider should be the low entropy one.
+    // Verify that the same provisional client ID as the first run is used.
+    EXPECT_EQ(provisional_client_id,
+              prefs_.GetString(prefs::kMetricsProvisionalClientID));
+    // There still should not be any stored client ID.
+    EXPECT_TRUE(prefs_.FindPreference(prefs::kMetricsClientID));
+    // The default entropy provider should be the high entropy one since we a
+    // the provisional client ID.
     state_manager->CreateDefaultEntropyProvider();
     EXPECT_EQ(state_manager->entropy_source_returned(),
-              MetricsStateManager::ENTROPY_SOURCE_LOW);
+              MetricsStateManager::ENTROPY_SOURCE_HIGH);
   }
 }
 #endif  // !BUILDFLAG(IS_WIN)
diff --git a/components/metrics/unsent_log_store.cc b/components/metrics/unsent_log_store.cc
index 6b0ce7ce..1db3af5 100644
--- a/components/metrics/unsent_log_store.cc
+++ b/components/metrics/unsent_log_store.cc
@@ -290,10 +290,12 @@
   std::vector<std::unique_ptr<LogInfo>> trimmed_list;
   size_t bytes_used = 0;
 
-  // The distance of the staged log from the end of the list of logs. Usually
-  // this is 0 (end of list). We mark so we can correct adjust the
-  // staged_log_index after log trimming.
-  size_t staged_index_distance = 0;
+  // The distance of the staged log from the end of the list of logs, which is
+  // usually 0 (end of list). This is used in case there is currently a staged
+  // log, which may or may not get trimmed. We want to keep track of the new
+  // position of the staged log after trimming so that we can update
+  // |staged_log_index_|.
+  absl::optional<size_t> staged_index_distance;
 
   // Reverse order, so newest ones are prioritized.
   for (int i = list_.size() - 1; i >= 0; --i) {
@@ -330,8 +332,14 @@
   // We may need to adjust the staged index since the number of logs may be
   // reduced. However, we want to make sure not to change the index if there is
   // no log staged.
-  if (staged_log_index_ != -1) {
-    staged_log_index_ = list_.size() - 1 - staged_index_distance;
+  if (staged_index_distance.has_value()) {
+    staged_log_index_ = list_.size() - 1 - staged_index_distance.value();
+  } else {
+    // Set |staged_log_index_| to -1. It might already be -1. E.g., at the time
+    // we are trimming logs, there was no staged log. However, it is also
+    // possible that we trimmed away the staged log, so we need to update the
+    // index to -1.
+    staged_log_index_ = -1;
   }
 }
 
diff --git a/components/metrics/unsent_log_store_unittest.cc b/components/metrics/unsent_log_store_unittest.cc
index c79ce10..be2888e5 100644
--- a/components/metrics/unsent_log_store_unittest.cc
+++ b/components/metrics/unsent_log_store_unittest.cc
@@ -296,6 +296,44 @@
   result_unsent_log_store.ExpectNextLog(target_log);
 }
 
+// Store a set of logs over the length limit, and over the minimum number of
+// bytes. The first log will be a staged log that should be trimmed away. This
+// should make the log store not have a staged log anymore.
+TEST_F(UnsentLogStoreTest, TrimStagedLog) {
+  TestUnsentLogStore unsent_log_store(&prefs_, kLogByteLimit);
+
+  // Make each log byte count the limit.
+  size_t log_size = kLogByteLimit;
+
+  // Create a target log that will be the staged log that we want to trim away.
+  std::string target_log = "First that should be trimmed";
+  target_log += GenerateLogWithMinCompressedSize(log_size);
+  LogMetadata log_metadata;
+  unsent_log_store.StoreLog(target_log, log_metadata);
+  unsent_log_store.StageNextLog();
+  EXPECT_TRUE(unsent_log_store.has_staged_log());
+
+  // Add |kLogCountLimit| additional logs.
+  std::string log_data = GenerateLogWithMinCompressedSize(log_size);
+  for (size_t i = 0; i < kLogCountLimit; ++i) {
+    unsent_log_store.StoreLog(log_data, log_metadata);
+  }
+
+  EXPECT_EQ(kLogCountLimit + 1, unsent_log_store.size());
+  unsent_log_store.TrimAndPersistUnsentLogs();
+
+  // Verify that the first log (the staged one) was trimmed away, and that the
+  // log store does not consider to have any staged log anymore. The other logs
+  // are not trimmed because the most recent logs are prioritized and we trim
+  // until we have |kLogCountLimit| logs.
+  EXPECT_EQ(kLogCountLimit, unsent_log_store.size());
+  EXPECT_FALSE(unsent_log_store.has_staged_log());
+  // Verify that all of the logs in the log store are not the |target_log|.
+  while (unsent_log_store.size() > 0) {
+    unsent_log_store.ExpectNextLog(log_data);
+  }
+}
+
 // Check that the store/stage/discard functions work as expected.
 TEST_F(UnsentLogStoreTest, Staging) {
   TestUnsentLogStore unsent_log_store(&prefs_, kLogByteLimit);
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 b0b19fb..4b08a2ca 100644
--- a/components/password_manager/core/browser/ui/credential_ui_entry.cc
+++ b/components/password_manager/core/browser/ui/credential_ui_entry.cc
@@ -8,8 +8,6 @@
 
 namespace password_manager {
 
-CredentialUIEntry::CredentialUIEntry() = default;
-
 CredentialUIEntry::CredentialUIEntry(const PasswordForm& form)
     : signon_realm(form.signon_realm),
       url(form.url),
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 a5bd721..da72546 100644
--- a/components/password_manager/core/browser/ui/credential_ui_entry.h
+++ b/components/password_manager/core/browser/ui/credential_ui_entry.h
@@ -20,7 +20,6 @@
 // construction from PasswordForm for convenience. A single entry might
 // correspond to multiple PasswordForms.
 struct CredentialUIEntry {
-  CredentialUIEntry();
   explicit CredentialUIEntry(const PasswordForm& form);
   CredentialUIEntry(const CredentialUIEntry& other);
   CredentialUIEntry(CredentialUIEntry&& other);
diff --git a/components/password_manager/core/browser/ui/insecure_credentials_manager.cc b/components/password_manager/core/browser/ui/insecure_credentials_manager.cc
index 9540153..d7d4ae02 100644
--- a/components/password_manager/core/browser/ui/insecure_credentials_manager.cc
+++ b/components/password_manager/core/browser/ui/insecure_credentials_manager.cc
@@ -285,30 +285,22 @@
   if (it == credentials_to_forms_.end())
     return false;
 
-  // Mute all matching compromised credentials from the store.
-  // For a match, all insecureity types saved in the store are muted.
-  // Return whether any credentials were muted.
   const auto& saved_passwords = it->second.forms;
-  bool muted = false;
-  for (const PasswordForm& saved_password : saved_passwords) {
-    PasswordForm form_to_update = saved_password;
-    bool form_changed = false;
-    for (const auto& password_issue : saved_password.password_issues) {
-      if (!password_issue.second.is_muted.value() &&
-          SupportsMuteOperation(password_issue.first)) {
-        form_to_update.password_issues.insert_or_assign(
-            password_issue.first,
-            InsecurityMetadata(password_issue.second.create_time,
-                               IsMuted(true)));
-        form_changed = true;
-      }
-    }
-    if (form_changed) {
-      GetStoreFor(saved_password).UpdateLogin(form_to_update);
-      muted = true;
+  DCHECK(!saved_passwords.empty());
+
+  return MuteCredential(CredentialUIEntry(saved_passwords[0]));
+}
+
+bool InsecureCredentialsManager::MuteCredential(
+    const CredentialUIEntry& credential) {
+  CredentialUIEntry updated_credential = credential;
+  for (auto& password_issue : updated_credential.password_issues) {
+    if (!password_issue.second.is_muted.value() &&
+        SupportsMuteOperation(password_issue.first)) {
+      password_issue.second.is_muted = IsMuted(true);
     }
   }
-  return muted;
+  return presenter_->EditSavedCredentials(updated_credential);
 }
 
 bool InsecureCredentialsManager::UnmuteCredential(
@@ -317,31 +309,22 @@
   if (it == credentials_to_forms_.end())
     return false;
 
-  // Unmute all matching compromised credentials from the store.
-  // For a match, all insecureity types saved in the store are unmuted.
-  // Return whether any credentials were unmuted.
   const auto& saved_passwords = it->second.forms;
-  bool unmuted = false;
+  DCHECK(!saved_passwords.empty());
 
-  for (const PasswordForm& saved_password : saved_passwords) {
-    PasswordForm form_to_update = saved_password;
-    bool form_changed = false;
-    for (const auto& password_issue : saved_password.password_issues) {
-      if (password_issue.second.is_muted.value() &&
-          SupportsMuteOperation(password_issue.first)) {
-        form_to_update.password_issues.insert_or_assign(
-            password_issue.first,
-            InsecurityMetadata(password_issue.second.create_time,
-                               IsMuted(false)));
-        form_changed = true;
-      }
-    }
-    if (form_changed) {
-      GetStoreFor(saved_password).UpdateLogin(form_to_update);
-      unmuted = true;
+  return UnmuteCredential(CredentialUIEntry(saved_passwords[0]));
+}
+
+bool InsecureCredentialsManager::UnmuteCredential(
+    const CredentialUIEntry& credential) {
+  CredentialUIEntry updated_credential = credential;
+  for (auto& password_issue : updated_credential.password_issues) {
+    if (password_issue.second.is_muted.value() &&
+        SupportsMuteOperation(password_issue.first)) {
+      password_issue.second.is_muted = IsMuted(false);
     }
   }
-  return unmuted;
+  return presenter_->EditSavedCredentials(updated_credential);
 }
 
 bool InsecureCredentialsManager::UpdateCredential(
diff --git a/components/password_manager/core/browser/ui/insecure_credentials_manager.h b/components/password_manager/core/browser/ui/insecure_credentials_manager.h
index 905c3d8..20cf5eb 100644
--- a/components/password_manager/core/browser/ui/insecure_credentials_manager.h
+++ b/components/password_manager/core/browser/ui/insecure_credentials_manager.h
@@ -189,11 +189,15 @@
 
   // Attempts to mute |credential| from the password store.
   // Returns whether the mute succeeded.
+  // TODO(crbug.com/1330549): Use CredentialUIEntry only.
   bool MuteCredential(const CredentialView& credential);
+  bool MuteCredential(const CredentialUIEntry& credential);
 
   // Attempts to unmute |credential| from the password store.
   // Returns whether the unmute succeeded.
+  // TODO(crbug.com/1330549): Use CredentialUIEntry only.
   bool UnmuteCredential(const CredentialView& credential);
+  bool UnmuteCredential(const CredentialUIEntry& credential);
 
   // Returns a vector of currently insecure credentials.
   // TODO(crbug.com/1330549): Use CredentialUIEntry only.
diff --git a/components/password_manager/core/browser/ui/insecure_credentials_manager_unittest.cc b/components/password_manager/core/browser/ui/insecure_credentials_manager_unittest.cc
index 8778292..ddbabe54 100644
--- a/components/password_manager/core/browser/ui/insecure_credentials_manager_unittest.cc
+++ b/components/password_manager/core/browser/ui/insecure_credentials_manager_unittest.cc
@@ -705,14 +705,16 @@
   store().AddLogin(password);
   RunUntilIdle();
 
-  CredentialWithPassword expected = MakeCompromisedCredential(password);
+  ASSERT_THAT(provider().GetInsecureCredentialEntries(),
+              ElementsAre(CredentialUIEntry(password)));
 
-  EXPECT_THAT(provider().GetInsecureCredentials(), ElementsAre(expected));
-  EXPECT_FALSE(provider().GetInsecureCredentials()[0].is_muted);
-
-  EXPECT_TRUE(provider().MuteCredential(expected));
+  EXPECT_TRUE(provider().MuteCredential(CredentialUIEntry(password)));
   RunUntilIdle();
-  EXPECT_TRUE(provider().GetInsecureCredentials()[0].is_muted);
+
+  EXPECT_TRUE(provider()
+                  .GetInsecureCredentialEntries()[0]
+                  .password_issues.at(InsecureType::kLeaked)
+                  .is_muted.value());
   EXPECT_TRUE(store()
                   .stored_passwords()
                   .at(kExampleCom)
@@ -730,15 +732,15 @@
   store().AddLogin(password);
   RunUntilIdle();
 
-  CredentialWithPassword expected = MakeCompromisedCredential(
-      password, InsecureCredentialTypeFlags::kCredentialLeaked, true);
+  ASSERT_THAT(provider().GetInsecureCredentialEntries(),
+              ElementsAre(CredentialUIEntry(password)));
 
-  EXPECT_THAT(provider().GetInsecureCredentials(), ElementsAre(expected));
-  EXPECT_TRUE(provider().GetInsecureCredentials()[0].is_muted);
-
-  EXPECT_TRUE(provider().UnmuteCredential(expected));
+  EXPECT_TRUE(provider().UnmuteCredential(CredentialUIEntry(password)));
   RunUntilIdle();
-  EXPECT_FALSE(provider().GetInsecureCredentials()[0].is_muted);
+  EXPECT_FALSE(provider()
+                   .GetInsecureCredentialEntries()[0]
+                   .password_issues.at(InsecureType::kLeaked)
+                   .is_muted.value());
   EXPECT_FALSE(store()
                    .stored_passwords()
                    .at(kExampleCom)
@@ -757,15 +759,15 @@
   store().AddLogin(password);
   RunUntilIdle();
 
-  CredentialWithPassword expected = MakeCompromisedCredential(
-      password, InsecureCredentialTypeFlags::kCredentialLeaked, false);
+  ASSERT_THAT(provider().GetInsecureCredentialEntries(),
+              ElementsAre(CredentialUIEntry(password)));
 
-  EXPECT_THAT(provider().GetInsecureCredentials(), ElementsAre(expected));
-  EXPECT_FALSE(provider().GetInsecureCredentials()[0].is_muted);
-
-  EXPECT_FALSE(provider().UnmuteCredential(expected));
+  EXPECT_FALSE(provider().UnmuteCredential(CredentialUIEntry(password)));
   RunUntilIdle();
-  EXPECT_FALSE(provider().GetInsecureCredentials()[0].is_muted);
+  EXPECT_FALSE(provider()
+                   .GetInsecureCredentialEntries()[0]
+                   .password_issues.at(InsecureType::kLeaked)
+                   .is_muted.value());
   EXPECT_FALSE(store()
                    .stored_passwords()
                    .at(kExampleCom)
@@ -787,19 +789,19 @@
   store().AddLogin(password);
   RunUntilIdle();
 
-  CredentialWithPassword expected = MakeCompromisedCredential(
-      password,
-      InsecureCredentialTypeFlags::kCredentialLeaked |
-          InsecureCredentialTypeFlags::kCredentialPhished,
-      true);
+  ASSERT_THAT(provider().GetInsecureCredentialEntries(),
+              ElementsAre(CredentialUIEntry(password)));
 
-  EXPECT_THAT(provider().GetInsecureCredentials(), ElementsAre(expected));
-
-  EXPECT_TRUE(provider().GetInsecureCredentials()[0].is_muted);
-
-  EXPECT_TRUE(provider().UnmuteCredential(expected));
+  EXPECT_TRUE(provider().UnmuteCredential(CredentialUIEntry(password)));
   RunUntilIdle();
-  EXPECT_FALSE(provider().GetInsecureCredentials()[0].is_muted);
+  EXPECT_FALSE(provider()
+                   .GetInsecureCredentialEntries()[0]
+                   .password_issues.at(InsecureType::kLeaked)
+                   .is_muted.value());
+  EXPECT_FALSE(provider()
+                   .GetInsecureCredentialEntries()[0]
+                   .password_issues.at(InsecureType::kPhished)
+                   .is_muted.value());
   EXPECT_FALSE(store()
                    .stored_passwords()
                    .at(kExampleCom)
@@ -831,22 +833,17 @@
   store().AddLogin(password);
   RunUntilIdle();
 
-  CredentialWithPassword expected = MakeCompromisedCredential(
-      password,
-      InsecureCredentialTypeFlags::kCredentialLeaked |
-          InsecureCredentialTypeFlags::kCredentialPhished |
-          InsecureCredentialTypeFlags::kReusedCredential |
-          InsecureCredentialTypeFlags::kWeakCredential,
-      true);
+  ASSERT_THAT(provider().GetInsecureCredentialEntries(),
+              ElementsAre(CredentialUIEntry(password)));
 
-  EXPECT_THAT(provider().GetInsecureCredentials(), ElementsAre(expected));
-
-  EXPECT_TRUE(provider().GetInsecureCredentials()[0].is_muted);
-
-  EXPECT_TRUE(provider().UnmuteCredential(expected));
+  EXPECT_TRUE(provider().UnmuteCredential(CredentialUIEntry(password)));
   RunUntilIdle();
 
-  EXPECT_FALSE(provider().GetInsecureCredentials()[0].is_muted);
+  PasswordForm expected = password;
+  expected.password_issues[InsecureType::kLeaked].is_muted = IsMuted(false);
+  expected.password_issues[InsecureType::kPhished].is_muted = IsMuted(false);
+  EXPECT_THAT(provider().GetInsecureCredentialEntries(),
+              ElementsAre(CredentialUIEntry(expected)));
   EXPECT_FALSE(store()
                    .stored_passwords()
                    .at(kExampleCom)
@@ -882,15 +879,13 @@
   store().AddLogin(password);
   RunUntilIdle();
 
-  CredentialWithPassword expected = MakeCompromisedCredential(
-      password, InsecureCredentialTypeFlags::kCredentialLeaked, true);
+  ASSERT_THAT(provider().GetInsecureCredentialEntries(),
+              ElementsAre(CredentialUIEntry(password)));
 
-  EXPECT_THAT(provider().GetInsecureCredentials(), ElementsAre(expected));
-  EXPECT_TRUE(provider().GetInsecureCredentials()[0].is_muted);
-
-  EXPECT_FALSE(provider().MuteCredential(expected));
+  EXPECT_FALSE(provider().MuteCredential(CredentialUIEntry(password)));
   RunUntilIdle();
-  EXPECT_TRUE(provider().GetInsecureCredentials()[0].is_muted);
+  EXPECT_THAT(provider().GetInsecureCredentialEntries(),
+              ElementsAre(CredentialUIEntry(password)));
   EXPECT_TRUE(store()
                   .stored_passwords()
                   .at(kExampleCom)
@@ -913,19 +908,16 @@
   store().AddLogin(password);
   RunUntilIdle();
 
-  CredentialWithPassword expected = MakeCompromisedCredential(
-      password,
-      InsecureCredentialTypeFlags::kCredentialLeaked |
-          InsecureCredentialTypeFlags::kCredentialPhished,
-      false);
+  ASSERT_THAT(provider().GetInsecureCredentialEntries(),
+              ElementsAre(CredentialUIEntry(password)));
 
-  EXPECT_THAT(provider().GetInsecureCredentials(), ElementsAre(expected));
-
-  EXPECT_FALSE(provider().GetInsecureCredentials()[0].is_muted);
-
-  EXPECT_TRUE(provider().MuteCredential(expected));
+  EXPECT_TRUE(provider().MuteCredential(CredentialUIEntry(password)));
   RunUntilIdle();
-  EXPECT_TRUE(provider().GetInsecureCredentials()[0].is_muted);
+  PasswordForm expected = password;
+  expected.password_issues[InsecureType::kLeaked].is_muted = IsMuted(true);
+  expected.password_issues[InsecureType::kPhished].is_muted = IsMuted(true);
+  EXPECT_THAT(provider().GetInsecureCredentialEntries(),
+              ElementsAre(CredentialUIEntry(expected)));
   EXPECT_TRUE(store()
                   .stored_passwords()
                   .at(kExampleCom)
@@ -958,23 +950,18 @@
   store().AddLogin(password);
   RunUntilIdle();
 
-  CredentialWithPassword expected = MakeCompromisedCredential(
-      password,
-      InsecureCredentialTypeFlags::kCredentialLeaked |
-          InsecureCredentialTypeFlags::kCredentialPhished |
-          InsecureCredentialTypeFlags::kWeakCredential |
-          InsecureCredentialTypeFlags::kReusedCredential,
-      false);
+  ASSERT_THAT(provider().GetInsecureCredentialEntries(),
+              ElementsAre(CredentialUIEntry(password)));
 
-  EXPECT_THAT(provider().GetInsecureCredentials(), ElementsAre(expected));
-
-  EXPECT_FALSE(provider().GetInsecureCredentials()[0].is_muted);
-
-  EXPECT_TRUE(provider().MuteCredential(expected));
+  EXPECT_TRUE(provider().MuteCredential(CredentialUIEntry(password)));
 
   RunUntilIdle();
 
-  EXPECT_TRUE(provider().GetInsecureCredentials()[0].is_muted);
+  PasswordForm expected = password;
+  expected.password_issues[InsecureType::kLeaked].is_muted = IsMuted(true);
+  expected.password_issues[InsecureType::kPhished].is_muted = IsMuted(true);
+  EXPECT_THAT(provider().GetInsecureCredentialEntries(),
+              ElementsAre(CredentialUIEntry(expected)));
   EXPECT_TRUE(store()
                   .stored_passwords()
                   .at(kExampleCom)
@@ -1011,17 +998,13 @@
   store().AddLogin(password);
   RunUntilIdle();
 
-  CredentialWithPassword expected = MakeCompromisedCredential(
-      password, InsecureCredentialTypeFlags::kWeakCredential, false);
+  ASSERT_THAT(provider().GetInsecureCredentialEntries(), IsEmpty());
 
-  EXPECT_TRUE(provider().GetInsecureCredentials().empty());
-
-  EXPECT_FALSE(provider().MuteCredential(expected));
+  EXPECT_FALSE(provider().MuteCredential(CredentialUIEntry(password)));
 
   RunUntilIdle();
 
-  EXPECT_TRUE(provider().GetInsecureCredentials().empty());
-
+  EXPECT_THAT(provider().GetInsecureCredentialEntries(), IsEmpty());
   EXPECT_FALSE(store()
                    .stored_passwords()
                    .at(kExampleCom)
@@ -1040,16 +1023,13 @@
   store().AddLogin(password);
   RunUntilIdle();
 
-  CredentialWithPassword expected = MakeCompromisedCredential(
-      password, InsecureCredentialTypeFlags::kWeakCredential, false);
+  ASSERT_THAT(provider().GetInsecureCredentialEntries(), IsEmpty());
 
-  EXPECT_TRUE(provider().GetInsecureCredentials().empty());
-
-  EXPECT_FALSE(provider().UnmuteCredential(expected));
+  EXPECT_FALSE(provider().UnmuteCredential(CredentialUIEntry(password)));
 
   RunUntilIdle();
 
-  EXPECT_TRUE(provider().GetInsecureCredentials().empty());
+  EXPECT_THAT(provider().GetInsecureCredentialEntries(), IsEmpty());
 
   EXPECT_TRUE(store()
                   .stored_passwords()
@@ -1070,17 +1050,13 @@
   store().AddLogin(password);
   RunUntilIdle();
 
-  CredentialWithPassword expected = MakeCompromisedCredential(
-      password, InsecureCredentialTypeFlags::kReusedCredential, false);
+  ASSERT_THAT(provider().GetInsecureCredentialEntries(), IsEmpty());
 
-  EXPECT_TRUE(provider().GetInsecureCredentials().empty());
-
-  EXPECT_FALSE(provider().MuteCredential(expected));
+  EXPECT_FALSE(provider().MuteCredential(CredentialUIEntry(password)));
 
   RunUntilIdle();
 
-  EXPECT_TRUE(provider().GetInsecureCredentials().empty());
-
+  EXPECT_THAT(provider().GetInsecureCredentialEntries(), IsEmpty());
   EXPECT_FALSE(store()
                    .stored_passwords()
                    .at(kExampleCom)
@@ -1099,17 +1075,13 @@
   store().AddLogin(password);
   RunUntilIdle();
 
-  CredentialWithPassword expected = MakeCompromisedCredential(
-      password, InsecureCredentialTypeFlags::kReusedCredential, false);
+  ASSERT_THAT(provider().GetInsecureCredentialEntries(), IsEmpty());
 
-  EXPECT_TRUE(provider().GetInsecureCredentials().empty());
-
-  EXPECT_FALSE(provider().UnmuteCredential(expected));
+  EXPECT_FALSE(provider().UnmuteCredential(CredentialUIEntry(password)));
 
   RunUntilIdle();
 
-  EXPECT_TRUE(provider().GetInsecureCredentials().empty());
-
+  EXPECT_THAT(provider().GetInsecureCredentialEntries(), IsEmpty());
   EXPECT_TRUE(store()
                   .stored_passwords()
                   .at(kExampleCom)
diff --git a/components/password_manager/core/browser/ui/saved_passwords_presenter.cc b/components/password_manager/core/browser/ui/saved_passwords_presenter.cc
index d8da304..ece02a88 100644
--- a/components/password_manager/core/browser/ui/saved_passwords_presenter.cc
+++ b/components/password_manager/core/browser/ui/saved_passwords_presenter.cc
@@ -193,10 +193,26 @@
   base::ranges::transform(range.first, range.second,
                           std::back_inserter(forms_to_change),
                           [](const auto& pair) { return pair.second; });
+  if (forms_to_change.empty())
+    return false;
+
   std::u16string new_note =
       credential.notes.empty() ? u"" : credential.notes[0].value;
-  return EditSavedPasswords(forms_to_change, credential.username,
-                            credential.password, new_note);
+
+  // TODO(crbug.com/1184691): Merge into a single method.
+  if (credential.username != forms_to_change[0].username_value ||
+      credential.password != forms_to_change[0].password_value ||
+      credential.notes != forms_to_change[0].notes) {
+    return EditSavedPasswords(forms_to_change, credential.username,
+                              credential.password, new_note);
+  } else if (credential.password_issues != forms_to_change[0].password_issues) {
+    for (auto& old_form : forms_to_change) {
+      old_form.password_issues = credential.password_issues;
+      GetStoreFor(old_form).UpdateLogin(old_form);
+    }
+    return true;
+  }
+  return false;
 }
 
 bool SavedPasswordsPresenter::EditSavedPasswords(
diff --git a/components/performance_manager/features.cc b/components/performance_manager/features.cc
index 54e2f96..67ce5ff 100644
--- a/components/performance_manager/features.cc
+++ b/components/performance_manager/features.cc
@@ -53,6 +53,9 @@
 const base::Feature kHighEfficiencyModeAvailable{
     "HighEfficiencyModeAvailable", base::FEATURE_DISABLED_BY_DEFAULT};
 
+const base::Feature kBatterySaverModeAvailable{
+    "BatterySaverModeAvailable", base::FEATURE_DISABLED_BY_DEFAULT};
+
 const base::FeatureParam<base::TimeDelta> kHighEfficiencyModeTimeBeforeDiscard{
     &kHighEfficiencyModeAvailable, "time_before_discard", base::Minutes(5)};
 #endif
diff --git a/components/performance_manager/public/features.h b/components/performance_manager/public/features.h
index c59b8fb..2863748 100644
--- a/components/performance_manager/public/features.h
+++ b/components/performance_manager/public/features.h
@@ -61,10 +61,11 @@
 // directly from Performance Manager rather than via TabLoader.
 extern const base::Feature kBackgroundTabLoadingFromPerformanceManager;
 
-// Makes the High-Efficiency Mode available to users. If this is enabled, it
-// doesn't mean High-Efficiency Mode is enabled, just that the user has the
-// option of toggling it.
+// Make the High-Efficiency or Battery Saver Modes available to users. If this
+// is enabled, it doesn't mean the specific Mode is enabled, just that the user
+// has the option of toggling it.
 extern const base::Feature kHighEfficiencyModeAvailable;
+extern const base::Feature kBatterySaverModeAvailable;
 
 // Defines the time in seconds before a background tab is discarded for
 // High-Efficiency Mode.
diff --git a/components/services/screen_ai/DEPS b/components/services/screen_ai/DEPS
index 7a6bb31..30107b9 100644
--- a/components/services/screen_ai/DEPS
+++ b/components/services/screen_ai/DEPS
@@ -3,6 +3,7 @@
   "+ui/accessibility/ax_enum_util.h",
   "+ui/accessibility/ax_enums.mojom.h",
   "+ui/accessibility/ax_node_data.h",
+  "+ui/accessibility/ax_role_properties.h",
   "+ui/accessibility/ax_tree_update.h",
   "+ui/gfx/geometry",
 ]
diff --git a/components/services/screen_ai/proto/proto_convertor.cc b/components/services/screen_ai/proto/proto_convertor.cc
index 5a46c5c..222cdae4 100644
--- a/components/services/screen_ai/proto/proto_convertor.cc
+++ b/components/services/screen_ai/proto/proto_convertor.cc
@@ -4,9 +4,19 @@
 
 #include "components/services/screen_ai/proto/proto_convertor.h"
 
-#include <memory>
+#include <stdint.h>
 
-#include "base/containers/contains.h"
+#include <algorithm>
+#include <array>
+#include <iterator>
+#include <map>
+#include <memory>
+#include <numeric>
+#include <utility>
+#include <vector>
+
+#include "base/check_op.h"
+#include "base/notreached.h"
 #include "components/services/screen_ai/proto/chrome_screen_ai.pb.h"
 #include "components/services/screen_ai/proto/dimension.pb.h"
 #include "components/services/screen_ai/proto/view_hierarchy.pb.h"
@@ -15,6 +25,7 @@
 #include "ui/accessibility/ax_enum_util.h"
 #include "ui/accessibility/ax_enums.mojom.h"
 #include "ui/accessibility/ax_node_data.h"
+#include "ui/accessibility/ax_role_properties.h"
 #include "ui/gfx/geometry/rect_f.h"
 #include "ui/gfx/geometry/transform.h"
 
@@ -24,38 +35,50 @@
 // accepted.
 // TODO(https://crbug.com/1278249): Add experiment or heuristics to better
 // adjust this threshold.
-const float kScreenAIMinConfidenceThreshold = 0.1;
+constexpr float kScreenAIMinConfidenceThreshold = 0.1f;
 
 // Returns the next valid ID that can be used for identifying `AXNode`s in the
 // accessibility tree.
 ui::AXNodeID GetNextNodeID() {
-  static int next_node_id = 1;
+  static ui::AXNodeID next_node_id{1};
   return next_node_id++;
 }
 
-void SerializePredictedType(
-    const chrome_screen_ai::UIComponent_PredictedType& predicted_type,
+// Returns whether the provided `predicted_type` is:
+// A) set, and
+// B) has a confidence that is above our acceptance threshold.
+bool SerializePredictedType(
+    const chrome_screen_ai::UIComponent::PredictedType& predicted_type,
     ui::AXNodeData& out_data) {
+  DCHECK_EQ(out_data.role, ax::mojom::Role::kUnknown);
+  if (predicted_type.confidence() < 0.0f ||
+      predicted_type.confidence() > 1.0f) {
+    NOTREACHED()
+        << "Unrecognized chrome_screen_ai::PredictedType::confidence value: "
+        << predicted_type.confidence();
+    return false;  // Confidence is out of bounds.
+  }
+  if (predicted_type.confidence() < kScreenAIMinConfidenceThreshold)
+    return false;
   switch (predicted_type.type_of_case()) {
-    case chrome_screen_ai::UIComponent_PredictedType::kEnumType:
-      // TODO(https://crbug.com/1278249): Add tests to ensure these two types
-      // match. Add a PRESUBMIT test that compares the proto and enum.
-      // TODO(accessibility): Why do we even need an enum. Couldn't all
-      // predicted types be strings? We could easily map from a string to an
-      // ax::mojom::Role. Then, we won't need to keep the enums synced.
+    case chrome_screen_ai::UIComponent::PredictedType::kEnumType:
+      // TODO(https://crbug.com/1278249): We do not actually need an enum. All
+      // predicted types could be strings. We could easily map from a string to
+      // an `ax::mojom::Role`. Then, we won't need to keep the enums synced.
       out_data.role = static_cast<ax::mojom::Role>(predicted_type.enum_type());
       break;
-    case chrome_screen_ai::UIComponent_PredictedType::kStringType:
+    case chrome_screen_ai::UIComponent::PredictedType::kStringType:
       out_data.role = ax::mojom::Role::kGenericContainer;
       out_data.AddStringAttribute(ax::mojom::StringAttribute::kRoleDescription,
                                   predicted_type.string_type());
       break;
-    case chrome_screen_ai::UIComponent_PredictedType::TYPE_OF_NOT_SET:
-      // TODO(accessibility): Why is this a possibility if the member in the
-      // proto is not marked optional?
-      NOTREACHED();
-      break;
+    case chrome_screen_ai::UIComponent::PredictedType::TYPE_OF_NOT_SET:
+      NOTREACHED() << "Malformed proto message: Required field "
+                      "`chrome_screen_ai::UIComponent::PredictedType` not set.";
+      return false;
   }
+
+  return true;
 }
 
 void SerializeBoundingBox(const chrome_screen_ai::Rect& bounding_box,
@@ -64,6 +87,9 @@
   out_data.relative_bounds.bounds =
       gfx::RectF(bounding_box.x(), bounding_box.y(), bounding_box.width(),
                  bounding_box.height());
+  // A negative width or height will result in an empty rect.
+  if (out_data.relative_bounds.bounds.IsEmpty())
+    return;
   if (container_id != ui::kInvalidAXNodeID)
     out_data.relative_bounds.offset_container_id = container_id;
   if (bounding_box.angle()) {
@@ -72,21 +98,199 @@
   }
 }
 
-absl::optional<ui::AXNodeData> SerializeUIComponent(
-    const chrome_screen_ai::UIComponent& ui_component) {
-  // The score is only used to prune very low confidence detections and we don't
-  // use it in the accessibility tree.
-  if (ui_component.predicted_type().confidence() <
-      kScreenAIMinConfidenceThreshold) {
-    return absl::nullopt;
+void SerializeDirection(const chrome_screen_ai::Orientation& direction,
+                        ui::AXNodeData& out_data) {
+  if (!chrome_screen_ai::Orientation_IsValid(direction)) {
+    NOTREACHED() << "Unrecognized chrome_screen_ai::Direction value: "
+                 << direction;
+    return;
   }
+  // TODO(accessibility): Why is writing direction represented using the
+  // orientation enum whose values are in part non-sensical for use as a writing
+  // direction? E.g., what does `ORIENTATION_ROTATED_HORIZONTAL` mean?
+  switch (direction) {
+    case chrome_screen_ai::Orientation::ORIENTATION_DEFAULT:
+    case chrome_screen_ai::Orientation::ORIENTATION_HORIZONTAL:
+      out_data.AddIntAttribute(
+          ax::mojom::IntAttribute::kTextDirection,
+          static_cast<int32_t>(ax::mojom::WritingDirection::kLtr));
+      break;
+    case chrome_screen_ai::Orientation::ORIENTATION_VERTICAL:
+      out_data.AddIntAttribute(
+          ax::mojom::IntAttribute::kTextDirection,
+          static_cast<int32_t>(ax::mojom::WritingDirection::kTtb));
+      break;
+    case chrome_screen_ai::Orientation::ORIENTATION_ROTATED_HORIZONTAL:
+      out_data.AddIntAttribute(
+          ax::mojom::IntAttribute::kTextDirection,
+          static_cast<int32_t>(ax::mojom::WritingDirection::kRtl));
+      break;
+    case chrome_screen_ai::Orientation::ORIENTATION_ROTATED_VERTICAL:
+      out_data.AddIntAttribute(
+          ax::mojom::IntAttribute::kTextDirection,
+          static_cast<int32_t>(ax::mojom::WritingDirection::kBtt));
+      break;
+    case google::protobuf::kint32min:
+    case google::protobuf::kint32max:
+      // Ordinarily, a default case should have been added to permit future
+      // additions to `chrome_screen_ai::Orientation`. However, in this
+      // case, both the screen_ai library and this code should always be in
+      // sync.
+      NOTREACHED() << "Unrecognized chrome_screen_ai::Direction value: "
+                   << direction;
+      break;
+  }
+}
 
-  ui::AXNodeData node_data;
-  node_data.id = GetNextNodeID();
-  SerializePredictedType(ui_component.predicted_type(), node_data);
-  SerializeBoundingBox(ui_component.bounding_box(),
-                       /* container_id */ ui::kInvalidAXNodeID, node_data);
-  return node_data;
+void SerializeContentType(const chrome_screen_ai::ContentType& content_type,
+                          ui::AXNodeData& out_data) {
+  if (!chrome_screen_ai::ContentType_IsValid(content_type)) {
+    NOTREACHED() << "Unrecognized chrome_screen_ai::ContentType value: "
+                 << content_type;
+    return;
+  }
+  switch (content_type) {
+    case chrome_screen_ai::CONTENT_TYPE_PRINTED_TEXT:
+    case chrome_screen_ai::CONTENT_TYPE_HANDWRITTEN_TEXT:
+      out_data.role = ax::mojom::Role::kStaticText;
+      break;
+    case chrome_screen_ai::CONTENT_TYPE_IMAGE:
+      out_data.role = ax::mojom::Role::kImage;
+      break;
+    case chrome_screen_ai::CONTENT_TYPE_LINE_DRAWING:
+      out_data.role = ax::mojom::Role::kGraphicsObject;
+      break;
+    case chrome_screen_ai::CONTENT_TYPE_SEPARATOR:
+      out_data.role = ax::mojom::Role::kSplitter;
+      break;
+    case chrome_screen_ai::CONTENT_TYPE_UNREADABLE_TEXT:
+      out_data.role = ax::mojom::Role::kGraphicsObject;
+      break;
+    case chrome_screen_ai::CONTENT_TYPE_FORMULA:
+    case chrome_screen_ai::CONTENT_TYPE_HANDWRITTEN_FORMULA:
+      // Note that `Role::kMath` indicates that the formula is not represented
+      // as a subtree of MathML elements in the accessibility tree, but as a raw
+      // string which may optionally be written in MathML, but could also be
+      // written in plain text.
+      out_data.role = ax::mojom::Role::kMath;
+      break;
+    case chrome_screen_ai::CONTENT_TYPE_SIGNATURE:
+      // Signatures may be readable, but even when they are not we could still
+      // try our best.
+      // TODO(accessibility): Explore adding a description attribute informing
+      // the user that this is a signature, e.g. via ARIA Annotations.
+      out_data.role = ax::mojom::Role::kStaticText;
+      break;
+    case chrome_screen_ai::CONTENT_TYPE_UNKNOWN:
+      // This should be "Role::kPresentational" but it has been erroniously
+      // removed from the codebase.
+      // TODO(nektar): Add presentational role back to avoid confusion with the
+      // meaning of kNone vs. kUnknown.
+      out_data.role = ax::mojom::Role::kNone;  // Presentational.
+      break;
+    case google::protobuf::kint32min:
+    case google::protobuf::kint32max:
+      // Ordinarily, a default case should have been added to permit future
+      // additions to `chrome_screen_ai::ContentType`. However, in this
+      // case, both the screen_ai library and this code should always be in
+      // sync.
+      NOTREACHED() << "Unrecognized chrome_screen_ai::ContentType value: "
+                   << content_type;
+      break;
+  }
+}
+
+void SerializeWordBox(const chrome_screen_ai::WordBox& word_box,
+                      const size_t index,
+                      ui::AXNodeData& parent_node,
+                      std::vector<ui::AXNodeData>& node_data) {
+  DCHECK_LT(index, node_data.size());
+  DCHECK_NE(parent_node.id, ui::kInvalidAXNodeID);
+  ui::AXNodeData& word_box_node = node_data[index];
+  DCHECK_EQ(word_box_node.role, ax::mojom::Role::kUnknown);
+  if (word_box.confidence() < 0.0f || word_box.confidence() > 1.0f) {
+    NOTREACHED() << "Unrecognized chrome_screen_ai::WordBox::confidence value: "
+                 << word_box.confidence();
+    return;  // Confidence is out of bounds.
+  }
+  if (word_box.confidence() < kScreenAIMinConfidenceThreshold)
+    return;
+  word_box_node.role = ax::mojom::Role::kInlineTextBox;
+  word_box_node.id = GetNextNodeID();
+  SerializeBoundingBox(word_box.bounding_box(), parent_node.id, word_box_node);
+  // Since the role is `kInlineTextBox`, NameFrom would automatically and
+  // correctly be set to `ax::mojom::NameFrom::kContents`.
+  if (word_box.has_space_after()) {
+    word_box_node.SetName(word_box.utf8_string() + " ");
+  } else {
+    word_box_node.SetName(word_box.utf8_string());
+  }
+  // TODO(nektar): DCHECK that line box's text is equal to the concatenation of
+  // the text found in all contained word boxes.
+  // TODO(nektar): Set character bounding box information.
+  if (word_box.estimate_color_success()) {
+    word_box_node.AddIntAttribute(ax::mojom::IntAttribute::kBackgroundColor,
+                                  word_box.background_rgb_value());
+    word_box_node.AddIntAttribute(ax::mojom::IntAttribute::kColor,
+                                  word_box.foreground_rgb_value());
+  }
+  SerializeDirection(
+      static_cast<chrome_screen_ai::Orientation>(word_box.direction()),
+      word_box_node);
+  parent_node.child_ids.push_back(word_box_node.id);
+}
+
+void SerializeUIComponent(const chrome_screen_ai::UIComponent& ui_component,
+                          const size_t index,
+                          ui::AXNodeData& parent_node,
+                          std::vector<ui::AXNodeData>& node_data) {
+  DCHECK_LT(index, node_data.size());
+  DCHECK_NE(parent_node.id, ui::kInvalidAXNodeID);
+  ui::AXNodeData& current_node = node_data[index];
+  if (SerializePredictedType(ui_component.predicted_type(), current_node))
+    return;
+  current_node.id = GetNextNodeID();
+  SerializeBoundingBox(ui_component.bounding_box(), parent_node.id,
+                       current_node);
+  parent_node.child_ids.push_back(current_node.id);
+}
+
+void SerializeLineBox(const chrome_screen_ai::LineBox& line_box,
+                      const size_t index,
+                      ui::AXNodeData& parent_node,
+                      std::vector<ui::AXNodeData>& node_data) {
+  DCHECK_LT(index, node_data.size());
+  DCHECK_NE(parent_node.id, ui::kInvalidAXNodeID);
+  ui::AXNodeData& line_box_node = node_data[index];
+  DCHECK_EQ(line_box_node.role, ax::mojom::Role::kUnknown);
+  if (line_box.confidence() < 0.0f || line_box.confidence() > 1.0f) {
+    NOTREACHED() << "Unrecognized chrome_screen_ai::LineBox::confidence value: "
+                 << line_box.confidence();
+    return;  // Confidence is out of bounds.
+  }
+  if (line_box.confidence() < kScreenAIMinConfidenceThreshold)
+    return;
+  SerializeContentType(line_box.content_type(), line_box_node);
+  line_box_node.id = GetNextNodeID();
+  if (ui::IsText(line_box_node.role)) {
+    size_t word_node_index = index + 1u;
+    for (const auto& word : line_box.words())
+      SerializeWordBox(word, word_node_index++, line_box_node, node_data);
+  }
+  SerializeBoundingBox(line_box.bounding_box(), parent_node.id, line_box_node);
+  // Since the role is `kStaticText`, NameFrom would automatically and correctly
+  // be set to `ax::mojom::NameFrom::kContents`.
+  line_box_node.SetName(line_box.utf8_string());
+  if (!line_box.language().empty()) {
+    // TODO(nektar): Only set language if different from parent node to
+    // minimize memory usage.
+    line_box_node.AddStringAttribute(ax::mojom::StringAttribute::kLanguage,
+                                     line_box.language());
+  }
+  SerializeDirection(
+      static_cast<chrome_screen_ai::Orientation>(line_box.direction()),
+      line_box_node);
+  parent_node.child_ids.push_back(line_box_node.id);
 }
 
 // Adds the subtree of |nodes[node_index_to_add]| to |nodes_order| with
@@ -113,20 +317,92 @@
 
   chrome_screen_ai::VisualAnnotation visual_annotation;
   if (!visual_annotation.ParseFromString(serialized_proto)) {
-    VLOG(1) << "Could not parse Screen AI library output.";
+    NOTREACHED() << "Could not parse Screen AI library output.";
     return update;
   }
 
   // TODO(https://crbug.com/1278249): Create an AXTreeSource and create the
   // update using AXTreeSerializer.
 
-  for (const auto& ui_component : visual_annotation.ui_component()) {
-    absl::optional<ui::AXNodeData> node_data =
-        SerializeUIComponent(ui_component);
-    if (node_data)
-      update.nodes.push_back(*node_data);
+  // Each `UIComponent`, `LineBox`, and `WordBox` will take up one node in the
+  // accessibility tree, resulting in hundreds of nodes, making it inefficient
+  // to push_back one node at a time. We pre-allocate the needed nodes making
+  // node creation an O(n) operation.
+  const size_t word_count = std::accumulate(
+      std::begin(visual_annotation.lines()),
+      std::end(visual_annotation.lines()), 0u,
+      [](const size_t& count, const chrome_screen_ai::LineBox& line_box) {
+        return count + line_box.words().size();
+      });
+
+  // Each unique `chrome_screen_ai::LineBox::block_id` creates a new
+  // paragraph, each paragraph is placed in its correct reading order,
+  // and each paragraph has a sorted set of line boxes. Line boxes are sorted
+  // using their `chrome_screen_ai::LineBox::order_within_block` member and they
+  // are identified by their index in the container of line boxes. Use std::map
+  // to sort both paragraphs and lines, both operations having an O(n * log(n))
+  // complexity.
+  // TODO(accessibility): Determine reading order based on visual positioning of
+  // paragraphs, not on their block IDs.
+  std::map<int32_t, std::map<int32_t, int>> blocks_to_lines_map;
+  for (int i = 0; i < visual_annotation.lines_size(); ++i) {
+    const chrome_screen_ai::LineBox& line = visual_annotation.lines(i);
+    blocks_to_lines_map[line.block_id()].emplace(
+        std::make_pair(line.order_within_block(), i));
   }
 
+  size_t rootnodes_count = 0u;
+  if (!visual_annotation.ui_component().empty())
+    ++rootnodes_count;
+  if (!visual_annotation.lines().empty())
+    ++rootnodes_count;
+
+  std::vector<ui::AXNodeData> nodes(
+      rootnodes_count + visual_annotation.ui_component().size() +
+      blocks_to_lines_map.size() + visual_annotation.lines().size() +
+      word_count);
+  size_t index = 0u;
+
+  if (!visual_annotation.ui_component().empty()) {
+    ui::AXNodeData& rootnode = nodes[index++];
+    rootnode.role = ax::mojom::Role::kDialog;
+    rootnode.id = GetNextNodeID();
+    // TODO(nektar): Set the bounding box of `rootnode` to the bounding box of
+    // the screen snapshot.
+    for (const auto& ui_component : visual_annotation.ui_component())
+      SerializeUIComponent(ui_component, index++, rootnode, nodes);
+  }
+
+  if (!visual_annotation.lines().empty()) {
+    // We assume that OCR is performed on a page-by-page basis.
+    ui::AXNodeData& page_node = nodes[index++];
+    page_node.role = ax::mojom::Role::kRegion;
+    page_node.id = GetNextNodeID();
+    page_node.AddBoolAttribute(ax::mojom::BoolAttribute::kIsPageBreakingObject,
+                               true);
+    // TODO(nektar): Set the bounding box of `page_node` to the bounding box of
+    // the captured image.
+    for (const auto& block_to_lines_pair : blocks_to_lines_map) {
+      for (const auto& line_sequence_number_to_index_pair :
+           block_to_lines_pair.second) {
+        const chrome_screen_ai::LineBox& line_box =
+            visual_annotation.lines(line_sequence_number_to_index_pair.second);
+        SerializeLineBox(line_box, index++, page_node, nodes);
+        index += line_box.words().size();
+      }
+    }
+  }
+
+  // Filter out invalid / unrecognized / unused nodes from the update.
+  update.nodes.resize(nodes.size());
+  auto end_node_iter =
+      std::copy_if(std::begin(nodes), std::end(nodes), std::begin(update.nodes),
+                   [](const ui::AXNodeData& node_data) {
+                     return node_data.role != ax::mojom::Role::kUnknown &&
+                            node_data.id != ui::kInvalidAXNodeID;
+                   });
+  update.nodes.resize(std::distance(std::begin(update.nodes), end_node_iter));
+
   // TODO(https://crbug.com/1278249): Add UMA metrics to record the number of
   // annotations, item types, confidence levels, etc.
 
@@ -179,8 +455,7 @@
 
   for (int node_index : nodes_order) {
     const ui::AXNodeData& node = snapshot.nodes[node_index];
-    int ax_node_id = static_cast<int>(node.id);
-
+    const ui::AXNodeID& ax_node_id = node.id;
     screenai::UiElement* uie = view_hierarchy.add_ui_elements();
     screenai::UiElementAttribute* attrib = nullptr;
 
diff --git a/components/services/screen_ai/proto/proto_convertor_unittest.cc b/components/services/screen_ai/proto/proto_convertor_unittest.cc
index 1259763..5af6458 100644
--- a/components/services/screen_ai/proto/proto_convertor_unittest.cc
+++ b/components/services/screen_ai/proto/proto_convertor_unittest.cc
@@ -3,6 +3,10 @@
 // found in the LICENSE file.
 
 #include "components/services/screen_ai/proto/proto_convertor.h"
+
+#include <string>
+
+#include "components/services/screen_ai/proto/chrome_screen_ai.pb.h"
 #include "components/services/screen_ai/proto/view_hierarchy.pb.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/accessibility/ax_node_data.h"
@@ -10,14 +14,14 @@
 
 namespace {
 
-const int kMaxChildInTemplate = 3;
+constexpr int kMaxChildInTemplate = 3;
 
 // A dummy tree node definition.
-typedef struct {
-  int node_id;
+struct NodeTemplate {
+  ui::AXNodeID node_id;
   int child_count;
-  int child_ids[kMaxChildInTemplate];
-} NodeTemplate;
+  ui::AXNodeID child_ids[kMaxChildInTemplate];
+};
 
 ui::AXTreeUpdate CreateAXTreeUpdateFromTemplate(int root_id,
                                                 NodeTemplate* nodes_template,
@@ -49,6 +53,140 @@
 
 using ProtoConvertorTest = testing::Test;
 
+TEST_F(ProtoConvertorTest, ScreenAIVisualAnnotationToAXTreeUpdate) {
+  chrome_screen_ai::VisualAnnotation annotation;
+
+  {
+    chrome_screen_ai::UIComponent* component_0 = annotation.add_ui_component();
+    chrome_screen_ai::UIComponent::PredictedType* type_0 =
+        component_0->mutable_predicted_type();
+    type_0->set_enum_type(chrome_screen_ai::UIComponent::BUTTON);
+    type_0->set_confidence(0.8f);
+    chrome_screen_ai::Rect* box_0 = component_0->mutable_bounding_box();
+    box_0->set_x(0);
+    box_0->set_y(1);
+    box_0->set_width(2);
+    box_0->set_height(3);
+    box_0->set_angle(90.0f);
+
+    chrome_screen_ai::UIComponent* component_1 = annotation.add_ui_component();
+    chrome_screen_ai::UIComponent::PredictedType* type_1 =
+        component_1->mutable_predicted_type();
+    type_1->set_string_type("Presentational");
+    // If the confidence is low, this component together with all its fields
+    // should be ignored.
+    type_1->set_confidence(0.05f);
+    chrome_screen_ai::Rect* box_1 = component_1->mutable_bounding_box();
+    box_1->set_x(0);
+    box_1->set_y(0);
+    box_1->set_width(5);
+    box_1->set_height(5);
+
+    chrome_screen_ai::UIComponent* component_2 = annotation.add_ui_component();
+    chrome_screen_ai::UIComponent::PredictedType* type_2 =
+        component_2->mutable_predicted_type();
+    type_2->set_string_type("Signature");
+    type_2->set_confidence(0.6f);
+    chrome_screen_ai::Rect* box_2 = component_2->mutable_bounding_box();
+    // `x`, `y`, and `angle` should be defaulted to 0 since they are singular
+    // proto3 fields, not proto2.
+    box_2->set_width(5);
+    box_2->set_height(5);
+  }
+
+  {
+    std::string serialized_annotation;
+    ASSERT_TRUE(annotation.SerializeToString(&serialized_annotation));
+    const ui::AXTreeUpdate update =
+        ScreenAIVisualAnnotationToAXTreeUpdate(serialized_annotation);
+
+    const std::string expected_update(
+        "id=1 dialog (0, 0)-(0, 0) child_ids=2\n");
+    EXPECT_EQ(expected_update, update.ToString());
+  }
+
+  {
+    chrome_screen_ai::LineBox* line_0 = annotation.add_lines();
+
+    chrome_screen_ai::WordBox* word_0_0 = line_0->add_words();
+    chrome_screen_ai::Rect* box_0_0 = word_0_0->mutable_bounding_box();
+    box_0_0->set_x(100);
+    box_0_0->set_y(100);
+    box_0_0->set_width(250);
+    box_0_0->set_height(20);
+    word_0_0->set_utf8_string("Hello");
+    word_0_0->set_has_space_after(true);
+    word_0_0->set_confidence(0.9f);
+    word_0_0->set_estimate_color_success(true);
+    word_0_0->set_background_rgb_value(50000);
+    word_0_0->set_foreground_rgb_value(25000);
+
+    chrome_screen_ai::WordBox* word_0_1 = line_0->add_words();
+    chrome_screen_ai::Rect* box_0_1 = word_0_1->mutable_bounding_box();
+    box_0_1->set_x(350);
+    box_0_1->set_y(100);
+    box_0_1->set_width(250);
+    box_0_1->set_height(20);
+    word_0_1->set_utf8_string("world");
+    // `word_0_1.has_space_after()` should be defaulted to false.
+    word_0_1->set_confidence(0.9f);
+    word_0_1->set_estimate_color_success(true);
+    word_0_1->set_background_rgb_value(50000);
+    word_0_1->set_foreground_rgb_value(25000);
+
+    chrome_screen_ai::Rect* box_0 = line_0->mutable_bounding_box();
+    box_0->set_x(100);
+    box_0->set_y(100);
+    box_0->set_width(500);
+    box_0->set_height(20);
+    line_0->set_utf8_string("Hello world");
+    line_0->set_confidence(0.9f);
+    line_0->set_language("en");
+    line_0->set_block_id(2);
+    line_0->set_order_within_block(1);
+
+    chrome_screen_ai::LineBox* line_1 = annotation.add_lines();
+    line_1->set_confidence(0.0f);
+    // Language, and the line as a whole,  should be ignored since the
+    // confidence is zero.
+    line_1->set_language("en");
+    line_1->set_block_id(1);
+    line_1->set_order_within_block(0);
+
+    chrome_screen_ai::LineBox* line_2 = annotation.add_lines();
+    line_2->set_confidence(0.7f);
+    chrome_screen_ai::Rect* box_2 = line_2->mutable_bounding_box();
+    // No bounding box should be created in the AXTree because the height is -5.
+    box_2->set_width(5);
+    box_2->set_height(-5);
+    line_2->set_block_id(2);
+    line_2->set_order_within_block(0);
+  }
+
+  {
+    std::string serialized_annotation;
+    ASSERT_TRUE(annotation.SerializeToString(&serialized_annotation));
+    const ui::AXTreeUpdate update =
+        ScreenAIVisualAnnotationToAXTreeUpdate(serialized_annotation);
+
+    const std::string expected_update(
+        "id=3 dialog (0, 0)-(0, 0) child_ids=4\n"
+        "id=5 region (0, 0)-(0, 0) is_page_breaking_object=true child_ids=6,7\n"
+        "  id=6 staticText (0, 0)-(5, 0) name_from=contents text_direction=ltr "
+        "name=\n"
+        "  id=7 staticText offset_container_id=5 (100, 100)-(500, 20) "
+        "name_from=contents text_direction=ltr name=Hello world language=en "
+        "child_ids=8,9\n"
+        "    id=8 inlineTextBox offset_container_id=7 (100, 100)-(250, 20) "
+        "name_from=contents background_color=&C350 color=&61A8 "
+        "text_direction=ltr name=Hello \n"
+        "    id=9 inlineTextBox offset_container_id=7 (350, 100)-(250, 20) "
+        "name_from=contents background_color=&C350 color=&61A8 "
+        "text_direction=ltr name=world\n");
+    EXPECT_EQ(expected_update, update.ToString());
+  }
+}
+
 // Tests if the given tree is properly traversed and new ids are assigned.
 TEST_F(ProtoConvertorTest, PreOrderTreeGeneration) {
   // Input Tree:
@@ -78,7 +216,7 @@
       CreateAXTreeUpdateFromTemplate(1, input_tree, nodes_count);
   std::string serialized_proto = Screen2xSnapshotToViewHierarchy(tree_update);
   screenai::ViewHierarchy view_hierarchy;
-  EXPECT_TRUE(view_hierarchy.ParseFromString(serialized_proto));
+  ASSERT_TRUE(view_hierarchy.ParseFromString(serialized_proto));
 
   // Verify.
   EXPECT_EQ(view_hierarchy.ui_elements().size(), nodes_count);
diff --git a/components/signin/public/base/signin_metrics.cc b/components/signin/public/base/signin_metrics.cc
index 3965889d..6b14513 100644
--- a/components/signin/public/base/signin_metrics.cc
+++ b/components/signin/public/base/signin_metrics.cc
@@ -120,6 +120,10 @@
       base::RecordAction(base::UserMetricsAction(
           "Signin_Signin_FromSigninInterceptFirstRunExperience"));
       break;
+    case AccessPoint::ACCESS_POINT_NTP_FEED_TOP_PROMO:
+      base::RecordAction(
+          base::UserMetricsAction("Signin_Signin_FromNTPFeedTopPromo"));
+      break;
     case AccessPoint::ACCESS_POINT_KALEIDOSCOPE:
       NOTREACHED() << "Access point " << static_cast<int>(access_point)
                    << " is only used to trigger non-sync sign-in and this"
@@ -186,6 +190,10 @@
       base::RecordAction(base::UserMetricsAction(
           "Signin_SigninWithDefault_FromNTPContentSuggestions"));
       break;
+    case AccessPoint::ACCESS_POINT_NTP_FEED_TOP_PROMO:
+      base::RecordAction(base::UserMetricsAction(
+          "Signin_SigninWithDefault_FromNTPFeedTopPromo"));
+      break;
     case AccessPoint::ACCESS_POINT_ENTERPRISE_SIGNOUT_COORDINATOR:
     case AccessPoint::ACCESS_POINT_START_PAGE:
     case AccessPoint::ACCESS_POINT_NTP_LINK:
@@ -260,6 +268,10 @@
       base::RecordAction(base::UserMetricsAction(
           "Signin_SigninNotDefault_FromNTPContentSuggestions"));
       break;
+    case AccessPoint::ACCESS_POINT_NTP_FEED_TOP_PROMO:
+      base::RecordAction(base::UserMetricsAction(
+          "Signin_SigninNotDefault_FromNTPFeedTopPromo"));
+      break;
     case AccessPoint::ACCESS_POINT_ENTERPRISE_SIGNOUT_COORDINATOR:
     case AccessPoint::ACCESS_POINT_START_PAGE:
     case AccessPoint::ACCESS_POINT_NTP_LINK:
@@ -338,6 +350,10 @@
           "Signin_SigninNewAccountNoExistingAccount_FromNTPContentSuggestions"));  // NOLINT(whitespace/line_length)
       // clang-format on
       break;
+    case AccessPoint::ACCESS_POINT_NTP_FEED_TOP_PROMO:
+      base::RecordAction(base::UserMetricsAction(
+          "Signin_SigninNewAccountNoExistingAccount_FromNTPFeedTopPromo"));
+      break;
     case AccessPoint::ACCESS_POINT_ENTERPRISE_SIGNOUT_COORDINATOR:
     case AccessPoint::ACCESS_POINT_START_PAGE:
     case AccessPoint::ACCESS_POINT_NTP_LINK:
@@ -419,6 +435,10 @@
       base::RecordAction(base::UserMetricsAction(
           "Signin_SigninNewAccountExistingAccount_FromNTPContentSuggestions"));
       break;
+    case AccessPoint::ACCESS_POINT_NTP_FEED_TOP_PROMO:
+      base::RecordAction(base::UserMetricsAction(
+          "Signin_SigninNewAccountExistingAccount_FromNTPFeedTopPromo"));
+      break;
     case AccessPoint::ACCESS_POINT_ENTERPRISE_SIGNOUT_COORDINATOR:
     case AccessPoint::ACCESS_POINT_START_PAGE:
     case AccessPoint::ACCESS_POINT_NTP_LINK:
@@ -841,6 +861,10 @@
       base::RecordAction(
           base::UserMetricsAction("Signin_Impression_FromSendTabToSelfPromo"));
       break;
+    case AccessPoint::ACCESS_POINT_NTP_FEED_TOP_PROMO:
+      base::RecordAction(
+          base::UserMetricsAction("Signin_Impression_FromNTPFeedTopPromo"));
+      break;
     case AccessPoint::ACCESS_POINT_ENTERPRISE_SIGNOUT_COORDINATOR:
     case AccessPoint::ACCESS_POINT_CONTENT_AREA:
     case AccessPoint::ACCESS_POINT_EXTENSIONS:
@@ -948,6 +972,15 @@
             "Signin_ImpressionWithNoAccount_FromNTPContentSuggestions"));
       }
       break;
+    case AccessPoint::ACCESS_POINT_NTP_FEED_TOP_PROMO:
+      if (with_account) {
+        base::RecordAction(base::UserMetricsAction(
+            "Signin_ImpressionWithAccount_FromNTPFeedTopPromo"));
+      } else {
+        base::RecordAction(base::UserMetricsAction(
+            "Signin_ImpressionWithNoAccount_FromNTPFeedTopPromo"));
+      }
+      break;
     case AccessPoint::ACCESS_POINT_ENTERPRISE_SIGNOUT_COORDINATOR:
     case AccessPoint::ACCESS_POINT_START_PAGE:
     case AccessPoint::ACCESS_POINT_NTP_LINK:
diff --git a/components/signin/public/base/signin_metrics.h b/components/signin/public/base/signin_metrics.h
index 300389cf..af14197 100644
--- a/components/signin/public/base/signin_metrics.h
+++ b/components/signin/public/base/signin_metrics.h
@@ -182,6 +182,7 @@
   ACCESS_POINT_ENTERPRISE_SIGNOUT_COORDINATOR = 34,
   ACCESS_POINT_SIGNIN_INTERCEPT_FIRST_RUN_EXPERIENCE = 35,
   ACCESS_POINT_SEND_TAB_TO_SELF_PROMO = 36,
+  ACCESS_POINT_NTP_FEED_TOP_PROMO = 37,
   // Add values above this line with a corresponding label to the
   // "SigninAccessPoint" enum in tools/metrics/histograms/enums.xml
   ACCESS_POINT_MAX,  // This must be last.
diff --git a/components/signin/public/base/signin_metrics_unittest.cc b/components/signin/public/base/signin_metrics_unittest.cc
index 7742a5f..33ab44e 100644
--- a/components/signin/public/base/signin_metrics_unittest.cc
+++ b/components/signin/public/base/signin_metrics_unittest.cc
@@ -38,7 +38,8 @@
     AccessPoint::ACCESS_POINT_RESIGNIN_INFOBAR,
     AccessPoint::ACCESS_POINT_TAB_SWITCHER,
     AccessPoint::ACCESS_POINT_MACHINE_LOGON,
-    AccessPoint::ACCESS_POINT_GOOGLE_SERVICES_SETTINGS};
+    AccessPoint::ACCESS_POINT_GOOGLE_SERVICES_SETTINGS,
+    AccessPoint::ACCESS_POINT_NTP_FEED_TOP_PROMO};
 
 const AccessPoint kAccessPointsThatSupportImpression[] = {
     AccessPoint::ACCESS_POINT_START_PAGE,
@@ -57,7 +58,8 @@
     AccessPoint::ACCESS_POINT_AUTOFILL_DROPDOWN,
     AccessPoint::ACCESS_POINT_NTP_CONTENT_SUGGESTIONS,
     AccessPoint::ACCESS_POINT_RESIGNIN_INFOBAR,
-    AccessPoint::ACCESS_POINT_TAB_SWITCHER};
+    AccessPoint::ACCESS_POINT_TAB_SWITCHER,
+    AccessPoint::ACCESS_POINT_NTP_FEED_TOP_PROMO};
 
 const AccessPoint kAccessPointsThatSupportPersonalizedPromos[] = {
     AccessPoint::ACCESS_POINT_BOOKMARK_MANAGER,
@@ -68,7 +70,8 @@
     AccessPoint::ACCESS_POINT_AVATAR_BUBBLE_SIGN_IN,
     AccessPoint::ACCESS_POINT_PASSWORD_BUBBLE,
     AccessPoint::ACCESS_POINT_BOOKMARK_BUBBLE,
-    AccessPoint::ACCESS_POINT_NTP_CONTENT_SUGGESTIONS};
+    AccessPoint::ACCESS_POINT_NTP_CONTENT_SUGGESTIONS,
+    AccessPoint::ACCESS_POINT_NTP_FEED_TOP_PROMO};
 
 class SigninMetricsTest : public ::testing::Test {
  public:
@@ -140,6 +143,8 @@
         return "SigninInterceptFirstRunExperience";
       case AccessPoint::ACCESS_POINT_SEND_TAB_TO_SELF_PROMO:
         return "SendTabToSelfPromo";
+      case AccessPoint::ACCESS_POINT_NTP_FEED_TOP_PROMO:
+        return "NTPFeedTopPromo";
       case AccessPoint::ACCESS_POINT_MAX:
         NOTREACHED();
         return "";
diff --git a/components/signin/public/base/signin_switches.cc b/components/signin/public/base/signin_switches.cc
index 73245fa7..2413900 100644
--- a/components/signin/public/base/signin_switches.cc
+++ b/components/signin/public/base/signin_switches.cc
@@ -45,6 +45,12 @@
     "ForceDisableExtendedSyncPromos", base::FEATURE_DISABLED_BY_DEFAULT};
 
 #if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
+// Decouples signing out from clearing browsing data on Android. Users are
+// no longer signed-out when they clear browsing data. Instead they may
+// choose to sign out separately by pressing another button.
+const base::Feature kEnableCbdSignOut{"EnableCbdSignOut",
+                                      base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Features to trigger the startup sign-in promo at boot.
 const base::Feature kForceStartupSigninPromo{"ForceStartupSigninPromo",
                                              base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/components/signin/public/base/signin_switches.h b/components/signin/public/base/signin_switches.h
index e751931..37c8689 100644
--- a/components/signin/public/base/signin_switches.h
+++ b/components/signin/public/base/signin_switches.h
@@ -36,6 +36,7 @@
 extern const base::Feature kForceDisableExtendedSyncPromos;
 
 #if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
+extern const base::Feature kEnableCbdSignOut;
 extern const base::Feature kForceStartupSigninPromo;
 extern const base::Feature kTangibleSync;
 #endif
diff --git a/components/viz/host/host_frame_sink_manager.cc b/components/viz/host/host_frame_sink_manager.cc
index 1ad1e78b..90429aee 100644
--- a/components/viz/host/host_frame_sink_manager.cc
+++ b/components/viz/host/host_frame_sink_manager.cc
@@ -288,6 +288,15 @@
   frame_sink_manager_->Throttle(ids, interval);
 }
 
+void HostFrameSinkManager::StartThrottlingAllFrameSinks(
+    base::TimeDelta interval) {
+  frame_sink_manager_->StartThrottlingAllFrameSinks(interval);
+}
+
+void HostFrameSinkManager::StopThrottlingAllFrameSinks() {
+  frame_sink_manager_->StopThrottlingAllFrameSinks();
+}
+
 void HostFrameSinkManager::AddHitTestRegionObserver(
     HitTestRegionObserver* observer) {
   observers_.AddObserver(observer);
diff --git a/components/viz/host/host_frame_sink_manager.h b/components/viz/host/host_frame_sink_manager.h
index 5a0d894..44db2fc3 100644
--- a/components/viz/host/host_frame_sink_manager.h
+++ b/components/viz/host/host_frame_sink_manager.h
@@ -194,6 +194,8 @@
                            std::unique_ptr<CopyOutputRequest> request);
 
   void Throttle(const std::vector<FrameSinkId>& ids, base::TimeDelta interval);
+  void StartThrottlingAllFrameSinks(base::TimeDelta interval);
+  void StopThrottlingAllFrameSinks();
 
   // Add/Remove an observer to receive notifications of when the host receives
   // new hit test data.
diff --git a/components/viz/service/display/direct_renderer.cc b/components/viz/service/display/direct_renderer.cc
index 774d3ae..ce0b2474 100644
--- a/components/viz/service/display/direct_renderer.cc
+++ b/components/viz/service/display/direct_renderer.cc
@@ -215,18 +215,6 @@
   auto* root_render_pass = render_passes_in_draw_order->back().get();
   DCHECK(root_render_pass);
 
-  bool overdraw_feedback = debug_settings_->show_overdraw_feedback;
-  if (overdraw_feedback && !output_surface_->capabilities().supports_stencil) {
-#if DCHECK_IS_ON()
-    DLOG_IF(WARNING, !overdraw_feedback_support_missing_logged_once_)
-        << "Overdraw feedback enabled on platform without support.";
-    overdraw_feedback_support_missing_logged_once_ = true;
-#endif
-    overdraw_feedback = false;
-  }
-  base::AutoReset<bool> auto_reset_overdraw_feedback(&overdraw_feedback_,
-                                                     overdraw_feedback);
-
   current_frame_valid_ = true;
   current_frame_ = DrawingFrame();
   current_frame()->render_passes_in_draw_order = render_passes_in_draw_order;
@@ -345,7 +333,6 @@
   // Only reshape when we know we are going to draw. Otherwise, the reshape
   // can leave the window at the wrong size if we never draw and the proper
   // viewport size is never set.
-  bool use_stencil = overdraw_feedback_;
   bool needs_full_frame_redraw = false;
   auto display_transform = output_surface_->GetDisplayTransform();
   OutputSurface::ReshapeParams reshape_params;
@@ -354,7 +341,6 @@
   reshape_params.color_space = frame_color_space;
   reshape_params.sdr_white_level = CurrentFrameSDRWhiteLevel();
   reshape_params.format = frame_buffer_format;
-  reshape_params.use_stencil = use_stencil;
   if (next_frame_needs_full_frame_redraw_ ||
       reshape_params != reshape_params_ ||
       display_transform != reshape_display_transform_) {
@@ -398,16 +384,6 @@
   if (!skip_drawing_root_render_pass)
     DrawRenderPassAndExecuteCopyRequests(root_render_pass);
 
-  // Use a fence to synchronize display of the main fb used by the output
-  // surface. Note that gpu_fence_id may have the special value 0 ("no fence")
-  // if fences are not supported. In that case synchronization will happen
-  // through other means on the service side.
-  // TODO(afrantzis): Consider using per-overlay fences instead of the one
-  // associated with the output surface when possible.
-  if (current_frame()->output_surface_plane)
-    current_frame()->output_surface_plane->gpu_fence_id =
-        output_surface_->UpdateGpuFence();
-
   if (overlay_processor_)
     overlay_processor_->TakeOverlayCandidates(&current_frame()->overlay_list);
 
@@ -633,16 +609,8 @@
   const bool render_pass_requires_scissor =
       render_pass_is_clipped || (supports_dc_layers && is_root_render_pass);
 
-  const bool has_external_stencil_test =
-      is_root_render_pass && output_surface_->HasExternalStencilTest();
   const bool should_clear_surface =
-      !has_external_stencil_test &&
-      (!is_root_render_pass || settings_->should_clear_root_render_pass);
-
-  // If |has_external_stencil_test| we can't discard or clear. Make sure we
-  // don't need to.
-  DCHECK(!has_external_stencil_test ||
-         !current_frame()->current_render_pass->has_transparent_background);
+      !is_root_render_pass || settings_->should_clear_root_render_pass;
 
   SurfaceInitializationMode mode;
   if (should_clear_surface && render_pass_requires_scissor) {
@@ -713,9 +681,6 @@
                 render_pass_requires_scissor);
   FinishDrawingQuadList();
 
-  if (is_root_render_pass && overdraw_feedback_)
-    FlushOverdrawFeedback(render_pass_scissor_in_draw_space);
-
   if (render_pass->generate_mipmap)
     GenerateMipmap();
 }
diff --git a/components/viz/service/display/direct_renderer.h b/components/viz/service/display/direct_renderer.h
index cab2564..27bd75e 100644
--- a/components/viz/service/display/direct_renderer.h
+++ b/components/viz/service/display/direct_renderer.h
@@ -264,7 +264,6 @@
   virtual void DoDrawQuad(const DrawQuad* quad,
                           const gfx::QuadF* clip_region) = 0;
   virtual void BeginDrawingFrame() = 0;
-  virtual void FlushOverdrawFeedback(const gfx::Rect& output_rect) {}
   virtual void FinishDrawingFrame() = 0;
   // If a pass contains a single tile draw quad and can be drawn without
   // a render pass (e.g. applying a filter directly to the tile quad)
@@ -316,8 +315,6 @@
   bool allow_empty_swap_ = false;
   // Whether partial swap can be used.
   bool use_partial_swap_ = false;
-  // Whether overdraw feedback is enabled and can be used.
-  bool overdraw_feedback_ = false;
 
   // A map from RenderPass id to the single quad present in and replacing the
   // RenderPass. The DrawQuads are owned by their RenderPasses, which outlive
@@ -375,9 +372,7 @@
   virtual void DrawDelegatedInkTrail();
 
   bool initialized_ = false;
-#if DCHECK_IS_ON()
-  bool overdraw_feedback_support_missing_logged_once_ = false;
-#endif
+
   gfx::Rect last_root_render_pass_scissor_rect_;
   gfx::Size enlarge_pass_texture_amount_;
 
diff --git a/components/viz/service/display/null_renderer.h b/components/viz/service/display/null_renderer.h
index fa291a47a..99935df3 100644
--- a/components/viz/service/display/null_renderer.h
+++ b/components/viz/service/display/null_renderer.h
@@ -45,7 +45,6 @@
   void DoDrawQuad(const DrawQuad* quad,
                   const gfx::QuadF* clip_region) override {}
   void BeginDrawingFrame() override;
-  void FlushOverdrawFeedback(const gfx::Rect& output_rect) override {}
   void FinishDrawingFrame() override {}
   bool FlippedFramebuffer() const override;
   void EnsureScissorTestEnabled() override {}
diff --git a/components/viz/service/display/output_surface.cc b/components/viz/service/display/output_surface.cc
index 6088ff13..9354f7d 100644
--- a/components/viz/service/display/output_surface.cc
+++ b/components/viz/service/display/output_surface.cc
@@ -30,14 +30,9 @@
 
 OutputSurface::OutputSurface(Type type) : type_(type) {}
 
-OutputSurface::OutputSurface(scoped_refptr<ContextProvider> context_provider)
-    : context_provider_(std::move(context_provider)), type_(Type::kOpenGL) {
-  DCHECK(context_provider_);
-}
-
 OutputSurface::OutputSurface(
     std::unique_ptr<SoftwareOutputDevice> software_device)
-    : software_device_(std::move(software_device)), type_(Type::kSoftware) {
+    : type_(Type::kSoftware), software_device_(std::move(software_device)) {
   DCHECK(software_device_);
 }
 
diff --git a/components/viz/service/display/output_surface.h b/components/viz/service/display/output_surface.h
index f43dd23..4ec62b3 100644
--- a/components/viz/service/display/output_surface.h
+++ b/components/viz/service/display/output_surface.h
@@ -12,7 +12,6 @@
 #include "base/memory/ref_counted.h"
 #include "base/threading/thread_checker.h"
 #include "components/viz/common/display/update_vsync_parameters_callback.h"
-#include "components/viz/common/gpu/context_provider.h"
 #include "components/viz/common/gpu/gpu_vsync_callback.h"
 #include "components/viz/common/resources/resource_format.h"
 #include "components/viz/common/resources/returned_resource.h"
@@ -26,6 +25,7 @@
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/skia/include/core/SkM44.h"
+#include "ui/gfx/buffer_types.h"
 #include "ui/gfx/color_space.h"
 #include "ui/gfx/overlay_transform.h"
 #include "ui/gfx/surface_origin.h"
@@ -77,10 +77,6 @@
     bool uses_default_gl_framebuffer = true;
     // Where (0,0) is on this OutputSurface.
     gfx::SurfaceOrigin output_surface_origin = gfx::SurfaceOrigin::kBottomLeft;
-    // Whether this OutputSurface supports stencil operations or not.
-    // Note: HasExternalStencilTest() must return false when an output surface
-    // has been configured for stencil usage.
-    bool supports_stencil = false;
     // Whether this OutputSurface supports post sub buffer or not.
     bool supports_post_sub_buffer = false;
     // Whether this OutputSurface supports commit overlay planes.
@@ -145,8 +141,6 @@
 
   // Constructor for skia-based compositing.
   explicit OutputSurface(Type type);
-  // Constructor for GL-based compositing.
-  explicit OutputSurface(scoped_refptr<ContextProvider> context_provider);
   // Constructor for software compositing.
   explicit OutputSurface(std::unique_ptr<SoftwareOutputDevice> software_device);
 
@@ -158,11 +152,9 @@
   const Capabilities& capabilities() const { return capabilities_; }
   Type type() const { return type_; }
 
-  // Obtain the 3d context or the software device associated with this output
-  // surface. Either of these may return a null pointer, but not both.
-  // In the event of a lost context, the entire output surface should be
-  // recreated.
-  ContextProvider* context_provider() const { return context_provider_.get(); }
+  // Obtain the software device associated with this output surface. This will
+  // return non-null for a software output surface and null for skia output
+  // surface.
   SoftwareOutputDevice* software_device() const {
     return software_device_.get();
   }
@@ -183,10 +175,6 @@
   virtual void EnsureBackbuffer() = 0;
   virtual void DiscardBackbuffer() = 0;
 
-  // Bind the default framebuffer for drawing to, only valid for GL backed
-  // OutputSurfaces.
-  virtual void BindFramebuffer() = 0;
-
   // Marks that the given rectangle will be drawn to on the default, bound
   // framebuffer. The contents of the framebuffer are undefined after this
   // command and must be filled in completely before a swap happens. Drawing
@@ -204,9 +192,6 @@
   // Returns true if a main image overlay plane should be scheduled.
   virtual bool IsDisplayedAsOverlayPlane() const = 0;
 
-  // Get the texture for the main image's overlay.
-  virtual unsigned GetOverlayTextureId() const = 0;
-
   // Returns the |mailbox| corresponding to the main image's overlay.
   virtual gpu::Mailbox GetOverlayMailbox() const;
 
@@ -217,14 +202,12 @@
     gfx::ColorSpace color_space;
     float sdr_white_level = gfx::ColorSpace::kDefaultSDRWhiteLevel;
     gfx::BufferFormat format = gfx::BufferFormat::RGBX_8888;
-    bool use_stencil = false;
 
     bool operator==(const ReshapeParams& other) const {
       return size == other.size &&
              device_scale_factor == other.device_scale_factor &&
              color_space == other.color_space &&
-             sdr_white_level == other.sdr_white_level &&
-             format == other.format && use_stencil == other.use_stencil;
+             sdr_white_level == other.sdr_white_level;
     }
     bool operator!=(const ReshapeParams& other) const {
       return !(*this == other);
@@ -232,13 +215,6 @@
   };
   virtual void Reshape(const ReshapeParams& params) = 0;
 
-  virtual bool HasExternalStencilTest() const = 0;
-  virtual void ApplyExternalStencil() = 0;
-
-  // Gives the GL internal format that should be used for calling CopyTexImage2D
-  // when the framebuffer is bound via BindFramebuffer().
-  virtual uint32_t GetFramebufferCopyTextureFormat() = 0;
-
   // Swaps the current backbuffer to the screen. For successful swaps, the
   // implementation must call OutputSurfaceClient::DidReceiveSwapBuffersAck()
   // after returning from this method in order to unblock the next frame.
@@ -254,14 +230,6 @@
   // TODO(dcastagna): Consider making the following pure virtual.
   virtual gfx::Rect GetCurrentFramebufferDamage() const;
 
-  // Updates the GpuFence associated with this surface. The id of a newly
-  // created GpuFence is returned, or if an error occurs, or fences are not
-  // supported, the special id of 0 (meaning "no fence") is returned.  In all
-  // cases, any previously associated fence is destroyed. The returned fence id
-  // corresponds to the GL id used by the CHROMIUM_gpu_fence GL extension and
-  // can be passed directly to any related extension functions.
-  virtual unsigned UpdateGpuFence() = 0;
-
   // Sets callback to receive updated vsync parameters after SwapBuffers() if
   // supported.
   virtual void SetUpdateVSyncParametersCallback(
@@ -320,11 +288,10 @@
 
  protected:
   struct OutputSurface::Capabilities capabilities_;
-  scoped_refptr<ContextProvider> context_provider_;
-  std::unique_ptr<SoftwareOutputDevice> software_device_;
 
  private:
   const Type type_;
+  std::unique_ptr<SoftwareOutputDevice> software_device_;
   SkM44 color_matrix_;
 };
 
diff --git a/components/viz/service/display/overlay_processor_interface.h b/components/viz/service/display/overlay_processor_interface.h
index 22cb824..2d4c5ff 100644
--- a/components/viz/service/display/overlay_processor_interface.h
+++ b/components/viz/service/display/overlay_processor_interface.h
@@ -33,6 +33,10 @@
 class DisplayResourceProvider;
 }
 
+namespace gpu {
+class SharedImageInterface;
+}
+
 namespace viz {
 struct DebugRendererSettings;
 class OutputSurface;
@@ -95,9 +99,6 @@
     // Opacity of the overlay independent of buffer alpha. When rendered:
     // src-alpha = |opacity| * buffer-component-alpha.
     float opacity;
-    // TODO(weiliangc): Should be replaced by SharedImage mailbox.
-    // Gpu fence to wait for before overlay is ready for display.
-    unsigned gpu_fence_id;
     // Mailbox corresponding to the buffer backing the primary plane.
     gpu::Mailbox mailbox;
     // Hints for overlay prioritization.
diff --git a/components/viz/service/display/skia_output_surface.h b/components/viz/service/display/skia_output_surface.h
index 7b8bec2..2378966 100644
--- a/components/viz/service/display/skia_output_surface.h
+++ b/components/viz/service/display/skia_output_surface.h
@@ -33,6 +33,10 @@
 class ColorSpace;
 }  // namespace gfx
 
+namespace gpu {
+class SharedImageInterface;
+}
+
 namespace viz {
 
 class OverlayCandidate;
diff --git a/components/viz/service/display/skia_readback_pixeltest.cc b/components/viz/service/display/skia_readback_pixeltest.cc
index 86516244..dd1c51d 100644
--- a/components/viz/service/display/skia_readback_pixeltest.cc
+++ b/components/viz/service/display/skia_readback_pixeltest.cc
@@ -21,6 +21,7 @@
 #include "components/viz/common/frame_sinks/copy_output_request.h"
 #include "components/viz/common/frame_sinks/copy_output_result.h"
 #include "components/viz/common/frame_sinks/copy_output_util.h"
+#include "components/viz/common/gpu/context_provider.h"
 #include "components/viz/service/display_embedder/skia_output_surface_impl.h"
 #include "components/viz/service/gl/gpu_service_impl.h"
 #include "components/viz/test/gl_scaler_test_util.h"
diff --git a/components/viz/service/display/skia_renderer.cc b/components/viz/service/display/skia_renderer.cc
index dec5b55..fbd06e7 100644
--- a/components/viz/service/display/skia_renderer.cc
+++ b/components/viz/service/display/skia_renderer.cc
@@ -929,8 +929,6 @@
 }
 
 void SkiaRenderer::BindFramebufferToOutputSurface() {
-  DCHECK(!output_surface_->HasExternalStencilTest());
-
   root_canvas_ = skia_output_surface_->BeginPaintCurrentFrame();
   current_canvas_ = root_canvas_;
   current_surface_ = root_surface_.get();
diff --git a/components/viz/service/display/software_renderer.cc b/components/viz/service/display/software_renderer.cc
index f018b200..a088937 100644
--- a/components/viz/service/display/software_renderer.cc
+++ b/components/viz/service/display/software_renderer.cc
@@ -134,7 +134,6 @@
 }
 
 void SoftwareRenderer::BindFramebufferToOutputSurface() {
-  DCHECK(!output_surface_->HasExternalStencilTest());
   DCHECK(!root_canvas_);
 
   current_framebuffer_canvas_.reset();
diff --git a/components/viz/service/display_embedder/output_surface_unified.cc b/components/viz/service/display_embedder/output_surface_unified.cc
index f01790c..a5c52de 100644
--- a/components/viz/service/display_embedder/output_surface_unified.cc
+++ b/components/viz/service/display_embedder/output_surface_unified.cc
@@ -29,22 +29,6 @@
   return false;
 }
 
-unsigned OutputSurfaceUnified::GetOverlayTextureId() const {
-  return 0;
-}
-
-bool OutputSurfaceUnified::HasExternalStencilTest() const {
-  return false;
-}
-
-uint32_t OutputSurfaceUnified::GetFramebufferCopyTextureFormat() {
-  return 0;
-}
-
-unsigned OutputSurfaceUnified::UpdateGpuFence() {
-  return 0;
-}
-
 gfx::OverlayTransform OutputSurfaceUnified::GetDisplayTransform() {
   return gfx::OVERLAY_TRANSFORM_NONE;
 }
diff --git a/components/viz/service/display_embedder/output_surface_unified.h b/components/viz/service/display_embedder/output_surface_unified.h
index 31d8e86..8d9d4deb 100644
--- a/components/viz/service/display_embedder/output_surface_unified.h
+++ b/components/viz/service/display_embedder/output_surface_unified.h
@@ -34,15 +34,9 @@
   void BindToClient(OutputSurfaceClient* client) override {}
   void EnsureBackbuffer() override {}
   void DiscardBackbuffer() override {}
-  void BindFramebuffer() override {}
   void Reshape(const ReshapeParams& params) override {}
   void SwapBuffers(OutputSurfaceFrame frame) override;
   bool IsDisplayedAsOverlayPlane() const override;
-  unsigned GetOverlayTextureId() const override;
-  bool HasExternalStencilTest() const override;
-  void ApplyExternalStencil() override {}
-  uint32_t GetFramebufferCopyTextureFormat() override;
-  unsigned UpdateGpuFence() override;
   void SetUpdateVSyncParametersCallback(
       UpdateVSyncParametersCallback callback) override {}
   void SetDisplayTransformHint(gfx::OverlayTransform transform) override {}
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 94627867..a037c08 100644
--- a/components/viz/service/display_embedder/skia_output_surface_impl.cc
+++ b/components/viz/service/display_embedder/skia_output_surface_impl.cc
@@ -234,10 +234,6 @@
   client_ = client;
 }
 
-void SkiaOutputSurfaceImpl::BindFramebuffer() {
-  // TODO(penghuang): remove this method when GLRenderer is removed.
-}
-
 void SkiaOutputSurfaceImpl::SetDrawRectangle(const gfx::Rect& draw_rectangle) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   DCHECK(capabilities().supports_dc_layers);
@@ -1176,40 +1172,15 @@
   return GrBackendFormat();
 }
 
-uint32_t SkiaOutputSurfaceImpl::GetFramebufferCopyTextureFormat() {
-  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-
-  return GL_RGB;
-}
-
 bool SkiaOutputSurfaceImpl::IsDisplayedAsOverlayPlane() const {
   return is_displayed_as_overlay_;
 }
 
-unsigned SkiaOutputSurfaceImpl::GetOverlayTextureId() const {
-  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-  return 0;
-}
-
 gpu::Mailbox SkiaOutputSurfaceImpl::GetOverlayMailbox() const {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   return last_swapped_mailbox_;
 }
 
-bool SkiaOutputSurfaceImpl::HasExternalStencilTest() const {
-  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-
-  return false;
-}
-
-void SkiaOutputSurfaceImpl::ApplyExternalStencil() {
-  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-}
-
-unsigned SkiaOutputSurfaceImpl::UpdateGpuFence() {
-  return 0;
-}
-
 void SkiaOutputSurfaceImpl::SetNeedsSwapSizeNotifications(
     bool needs_swap_size_notifications) {
   needs_swap_size_notifications_ = needs_swap_size_notifications;
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 cf65c04b..c78d40b 100644
--- a/components/viz/service/display_embedder/skia_output_surface_impl.h
+++ b/components/viz/service/display_embedder/skia_output_surface_impl.h
@@ -76,7 +76,6 @@
   // OutputSurface implementation:
   gpu::SurfaceHandle GetSurfaceHandle() const override;
   void BindToClient(OutputSurfaceClient* client) override;
-  void BindFramebuffer() override;
   void SetDrawRectangle(const gfx::Rect& draw_rectangle) override;
   void SetEnableDCLayers(bool enable) override;
   void EnsureBackbuffer() override;
@@ -89,13 +88,8 @@
   void SetDisplayTransformHint(gfx::OverlayTransform transform) override;
   gfx::OverlayTransform GetDisplayTransform() override;
   void SwapBuffers(OutputSurfaceFrame frame) override;
-  uint32_t GetFramebufferCopyTextureFormat() override;
   bool IsDisplayedAsOverlayPlane() const override;
-  unsigned GetOverlayTextureId() const override;
   gpu::Mailbox GetOverlayMailbox() const override;
-  bool HasExternalStencilTest() const override;
-  void ApplyExternalStencil() override;
-  unsigned UpdateGpuFence() override;
   void SetNeedsSwapSizeNotifications(
       bool needs_swap_size_notifications) override;
   base::ScopedClosureRunner GetCacheBackBufferCb() override;
diff --git a/components/viz/service/display_embedder/software_output_surface.cc b/components/viz/service/display_embedder/software_output_surface.cc
index 77bf3580..0fe3318e 100644
--- a/components/viz/service/display_embedder/software_output_surface.cc
+++ b/components/viz/service/display_embedder/software_output_surface.cc
@@ -24,12 +24,12 @@
 namespace viz {
 
 SoftwareOutputSurface::SoftwareOutputSurface(
-    std::unique_ptr<SoftwareOutputDevice> software_device)
-    : OutputSurface(std::move(software_device)) {
+    std::unique_ptr<SoftwareOutputDevice> device)
+    : OutputSurface(std::move(device)) {
   capabilities_.pending_swap_params.max_pending_swaps =
-      software_device_->MaxFramesPending();
+      software_device()->MaxFramesPending();
   capabilities_.resize_based_on_root_surface =
-      software_device_->SupportsOverridePlatformSize();
+      software_device()->SupportsOverridePlatformSize();
 }
 
 SoftwareOutputSurface::~SoftwareOutputSurface() = default;
@@ -48,11 +48,6 @@
   software_device()->DiscardBackbuffer();
 }
 
-void SoftwareOutputSurface::BindFramebuffer() {
-  // Not used for software surfaces.
-  NOTREACHED();
-}
-
 void SoftwareOutputSurface::Reshape(const ReshapeParams& params) {
   software_device()->Resize(params.size, params.device_scale_factor);
 }
@@ -85,22 +80,6 @@
   return false;
 }
 
-unsigned SoftwareOutputSurface::GetOverlayTextureId() const {
-  return 0;
-}
-
-bool SoftwareOutputSurface::HasExternalStencilTest() const {
-  return false;
-}
-
-void SoftwareOutputSurface::ApplyExternalStencil() {}
-
-uint32_t SoftwareOutputSurface::GetFramebufferCopyTextureFormat() {
-  // Not used for software surfaces.
-  NOTREACHED();
-  return 0;
-}
-
 void SoftwareOutputSurface::SwapBuffersCallback(base::TimeTicks swap_time,
                                                 const gfx::Size& pixel_size) {
   latency_tracker_.OnGpuSwapBuffersCompleted(
@@ -130,10 +109,6 @@
   update_vsync_parameters_callback_.Run(timebase, interval);
 }
 
-unsigned SoftwareOutputSurface::UpdateGpuFence() {
-  return 0;
-}
-
 void SoftwareOutputSurface::SetUpdateVSyncParametersCallback(
     UpdateVSyncParametersCallback callback) {
   update_vsync_parameters_callback_ = std::move(callback);
diff --git a/components/viz/service/display_embedder/software_output_surface.h b/components/viz/service/display_embedder/software_output_surface.h
index 26af7321a..a712789 100644
--- a/components/viz/service/display_embedder/software_output_surface.h
+++ b/components/viz/service/display_embedder/software_output_surface.h
@@ -38,15 +38,9 @@
   void BindToClient(OutputSurfaceClient* client) override;
   void EnsureBackbuffer() override;
   void DiscardBackbuffer() override;
-  void BindFramebuffer() override;
   void Reshape(const ReshapeParams& params) override;
   void SwapBuffers(OutputSurfaceFrame frame) override;
   bool IsDisplayedAsOverlayPlane() const override;
-  unsigned GetOverlayTextureId() const override;
-  bool HasExternalStencilTest() const override;
-  void ApplyExternalStencil() override;
-  uint32_t GetFramebufferCopyTextureFormat() override;
-  unsigned UpdateGpuFence() override;
   void SetUpdateVSyncParametersCallback(
       UpdateVSyncParametersCallback callback) override;
   void SetDisplayTransformHint(gfx::OverlayTransform transform) override {}
diff --git a/components/viz/service/frame_sinks/frame_sink_manager_impl.cc b/components/viz/service/frame_sinks/frame_sink_manager_impl.cc
index dad49bc..083f266 100644
--- a/components/viz/service/frame_sinks/frame_sink_manager_impl.cc
+++ b/components/viz/service/frame_sinks/frame_sink_manager_impl.cc
@@ -416,6 +416,11 @@
 
   for (auto& observer : observer_list_)
     observer.OnCreatedCompositorFrameSink(frame_sink_id, support->is_root());
+
+  if (global_throttle_interval_) {
+    UpdateThrottlingRecursively(frame_sink_id,
+                                global_throttle_interval_.value());
+  }
 }
 
 void FrameSinkManagerImpl::UnregisterCompositorFrameSinkSupport(
@@ -735,16 +740,41 @@
   UpdateThrottling();
 }
 
+void FrameSinkManagerImpl::StartThrottlingAllFrameSinks(
+    base::TimeDelta interval) {
+  global_throttle_interval_ = interval;
+  UpdateThrottling();
+}
+
+void FrameSinkManagerImpl::StopThrottlingAllFrameSinks() {
+  global_throttle_interval_ = absl::nullopt;
+  UpdateThrottling();
+}
+
 void FrameSinkManagerImpl::UpdateThrottling() {
   // Clear previous throttling effect on all frame sinks.
   for (auto& support_map_item : support_map_) {
     support_map_item.second->ThrottleBeginFrame(base::TimeDelta());
   }
-  if (throttle_interval_.is_zero())
+  if (throttle_interval_.is_zero() &&
+      (!global_throttle_interval_ ||
+       global_throttle_interval_.value().is_zero()))
     return;
 
-  for (const auto& id : frame_sink_ids_to_throttle_) {
-    UpdateThrottlingRecursively(id, throttle_interval_);
+  if (global_throttle_interval_) {
+    for (const auto& support : support_map_) {
+      support.second->ThrottleBeginFrame(global_throttle_interval_.value());
+    }
+  }
+
+  // If the per-frame sink throttle interval is more aggressive than the global
+  // throttling interval, apply it to those frame sinks effectively always
+  // throttling a frame sink as much as possible.
+  if (!global_throttle_interval_ ||
+      throttle_interval_ > global_throttle_interval_) {
+    for (const auto& id : frame_sink_ids_to_throttle_) {
+      UpdateThrottlingRecursively(id, throttle_interval_);
+    }
   }
   // Clear throttling on frame sinks currently being captured.
   for (const auto& id : captured_frame_sink_ids_) {
diff --git a/components/viz/service/frame_sinks/frame_sink_manager_impl.h b/components/viz/service/frame_sinks/frame_sink_manager_impl.h
index d736d6b..7c7c279 100644
--- a/components/viz/service/frame_sinks/frame_sink_manager_impl.h
+++ b/components/viz/service/frame_sinks/frame_sink_manager_impl.h
@@ -153,6 +153,8 @@
       const DebugRendererSettings& debug_settings) override;
   void Throttle(const std::vector<FrameSinkId>& ids,
                 base::TimeDelta interval) override;
+  void StartThrottlingAllFrameSinks(base::TimeDelta interval) override;
+  void StopThrottlingAllFrameSinks() override;
 
   void DestroyFrameSinkBundle(const FrameSinkBundleId& id);
 
@@ -395,9 +397,16 @@
   // Ids of the frame sinks that have been requested to throttle.
   std::vector<FrameSinkId> frame_sink_ids_to_throttle_;
 
-  // The throttling interval which defines how often BeginFrames are sent.
+  // The throttling interval which defines how often BeginFrames are sent for
+  // frame sinks in `frame_sink_ids_to_throttle_`, if
+  // `global_throttle_interval_` is unset or if this interval is longer than
+  // `global_throttle_interval_`.
   base::TimeDelta throttle_interval_ = BeginFrameArgs::DefaultInterval();
 
+  // If present, the throttling interval which defines the upper bound of how
+  // often BeginFrames are sent for all current and future frame sinks.
+  absl::optional<base::TimeDelta> global_throttle_interval_ = absl::nullopt;
+
   base::flat_map<uint32_t, base::ScopedClosureRunner> cached_back_buffers_;
 
   THREAD_CHECKER(thread_checker_);
diff --git a/components/viz/service/frame_sinks/frame_sink_manager_unittest.cc b/components/viz/service/frame_sinks/frame_sink_manager_unittest.cc
index c49494e9..8f8ba7e 100644
--- a/components/viz/service/frame_sinks/frame_sink_manager_unittest.cc
+++ b/components/viz/service/frame_sinks/frame_sink_manager_unittest.cc
@@ -33,6 +33,8 @@
 constexpr FrameSinkId kFrameSinkIdB(3, 1);
 constexpr FrameSinkId kFrameSinkIdC(4, 1);
 constexpr FrameSinkId kFrameSinkIdD(5, 1);
+constexpr FrameSinkId kFrameSinkIdE(6, 1);
+constexpr FrameSinkId kFrameSinkIdF(7, 1);
 
 // Holds the four interface objects needed to create a RootCompositorFrameSink.
 struct RootCompositorFrameSinkData {
@@ -472,6 +474,86 @@
                                         client_d->frame_sink_id());
 }
 
+TEST_F(FrameSinkManagerTest, GlobalThrottle) {
+  // root -> A -> B
+  //      -> C -> D
+  auto root = CreateCompositorFrameSinkSupport(kFrameSinkIdRoot);
+  auto client_a = CreateCompositorFrameSinkSupport(kFrameSinkIdA);
+  auto client_b = CreateCompositorFrameSinkSupport(kFrameSinkIdB);
+  auto client_c = CreateCompositorFrameSinkSupport(kFrameSinkIdC);
+  auto client_d = CreateCompositorFrameSinkSupport(kFrameSinkIdD);
+
+  // Set up the hierarchy.
+  manager_.RegisterFrameSinkHierarchy(root->frame_sink_id(),
+                                      client_a->frame_sink_id());
+  manager_.RegisterFrameSinkHierarchy(client_a->frame_sink_id(),
+                                      client_b->frame_sink_id());
+  manager_.RegisterFrameSinkHierarchy(root->frame_sink_id(),
+                                      client_c->frame_sink_id());
+  manager_.RegisterFrameSinkHierarchy(client_c->frame_sink_id(),
+                                      client_d->frame_sink_id());
+
+  constexpr base::TimeDelta global_interval = base::Hertz(30);
+  constexpr base::TimeDelta interval = base::Hertz(20);
+
+  std::vector<FrameSinkId> ids{kFrameSinkIdRoot, kFrameSinkIdA, kFrameSinkIdB,
+                               kFrameSinkIdC, kFrameSinkIdD};
+
+  // By default, a CompositorFrameSinkSupport shouldn't have its
+  // |begin_frame_interval| set.
+  VerifyThrottling(base::TimeDelta(), ids);
+
+  // Starting global throttling should throttle the entire hierarchy.
+  manager_.StartThrottlingAllFrameSinks(global_interval);
+  VerifyThrottling(global_interval, ids);
+
+  // Throttling more aggressively on top of global throttling should further
+  // throttle the specified frame sink hierarchy, but preserve global throttling
+  // on the unaffected framesinks.
+  manager_.Throttle({kFrameSinkIdC}, interval);
+  VerifyThrottling(global_interval,
+                   {kFrameSinkIdRoot, kFrameSinkIdA, kFrameSinkIdB});
+  VerifyThrottling(interval, {kFrameSinkIdC, kFrameSinkIdD});
+
+  // Attempting to per-sink throttle to an interval shorter than the global
+  // throttling should still throttle all frame sinks to the global interval.
+  manager_.Throttle({kFrameSinkIdA}, base::Hertz(40));
+  VerifyThrottling(global_interval, ids);
+
+  // Add a new branch to the hierarchy. These new frame sinks should be globally
+  // throttled immediately. root -> A -> B
+  //      -> C -> D
+  //      -> E -> F
+  auto client_e = CreateCompositorFrameSinkSupport(kFrameSinkIdE);
+  auto client_f = CreateCompositorFrameSinkSupport(kFrameSinkIdF);
+  manager_.RegisterFrameSinkHierarchy(root->frame_sink_id(),
+                                      client_e->frame_sink_id());
+  manager_.RegisterFrameSinkHierarchy(client_e->frame_sink_id(),
+                                      client_f->frame_sink_id());
+  VerifyThrottling(
+      global_interval,
+      {kFrameSinkIdRoot, kFrameSinkIdA, kFrameSinkIdB, kFrameSinkIdC,
+       kFrameSinkIdD, kFrameSinkIdE, kFrameSinkIdF});
+
+  // Disabling global throttling should revert back to only the up-to-date
+  // per-frame sink throttling.
+  manager_.StopThrottlingAllFrameSinks();
+  VerifyThrottling(base::Hertz(40), {kFrameSinkIdA, kFrameSinkIdB});
+
+  manager_.UnregisterFrameSinkHierarchy(root->frame_sink_id(),
+                                        client_a->frame_sink_id());
+  manager_.UnregisterFrameSinkHierarchy(client_a->frame_sink_id(),
+                                        client_b->frame_sink_id());
+  manager_.UnregisterFrameSinkHierarchy(root->frame_sink_id(),
+                                        client_c->frame_sink_id());
+  manager_.UnregisterFrameSinkHierarchy(client_c->frame_sink_id(),
+                                        client_d->frame_sink_id());
+  manager_.UnregisterFrameSinkHierarchy(root->frame_sink_id(),
+                                        client_e->frame_sink_id());
+  manager_.UnregisterFrameSinkHierarchy(client_e->frame_sink_id(),
+                                        client_f->frame_sink_id());
+}
+
 // Verifies if a frame sink is being captured, it should not be throttled.
 TEST_F(FrameSinkManagerTest, NoThrottleOnFrameSinksBeingCaptured) {
   // root -> A -> B -> C
diff --git a/components/viz/test/fake_output_surface.cc b/components/viz/test/fake_output_surface.cc
index 7430827..7c605e8d 100644
--- a/components/viz/test/fake_output_surface.cc
+++ b/components/viz/test/fake_output_surface.cc
@@ -47,8 +47,6 @@
   client_->DidReceivePresentationFeedback({now, base::TimeDelta(), 0});
 }
 
-void FakeSoftwareOutputSurface::BindFramebuffer() {}
-
 void FakeSoftwareOutputSurface::SetDrawRectangle(const gfx::Rect& rect) {
   NOTREACHED();
 }
@@ -57,32 +55,16 @@
   NOTREACHED();
 }
 
-uint32_t FakeSoftwareOutputSurface::GetFramebufferCopyTextureFormat() {
-  return GL_RGB;
-}
-
 void FakeSoftwareOutputSurface::BindToClient(OutputSurfaceClient* client) {
   DCHECK(client);
   DCHECK(!client_);
   client_ = client;
 }
 
-bool FakeSoftwareOutputSurface::HasExternalStencilTest() const {
-  return false;
-}
-
 bool FakeSoftwareOutputSurface::IsDisplayedAsOverlayPlane() const {
   return false;
 }
 
-unsigned FakeSoftwareOutputSurface::GetOverlayTextureId() const {
-  return 0;
-}
-
-unsigned FakeSoftwareOutputSurface::UpdateGpuFence() {
-  return 0;
-}
-
 void FakeSoftwareOutputSurface::SetUpdateVSyncParametersCallback(
     UpdateVSyncParametersCallback callback) {}
 
diff --git a/components/viz/test/fake_output_surface.h b/components/viz/test/fake_output_surface.h
index 71d40d5..c1f6fd0e 100644
--- a/components/viz/test/fake_output_surface.h
+++ b/components/viz/test/fake_output_surface.h
@@ -37,17 +37,11 @@
   void BindToClient(OutputSurfaceClient* client) override;
   void EnsureBackbuffer() override {}
   void DiscardBackbuffer() override {}
-  void BindFramebuffer() override;
   void SetDrawRectangle(const gfx::Rect& rect) override;
   void SetEnableDCLayers(bool enabled) override;
   void Reshape(const ReshapeParams& params) override;
   void SwapBuffers(OutputSurfaceFrame frame) override;
-  uint32_t GetFramebufferCopyTextureFormat() override;
-  bool HasExternalStencilTest() const override;
-  void ApplyExternalStencil() override {}
   bool IsDisplayedAsOverlayPlane() const override;
-  unsigned GetOverlayTextureId() const override;
-  unsigned UpdateGpuFence() override;
   void SetUpdateVSyncParametersCallback(
       UpdateVSyncParametersCallback callback) override;
   void SetDisplayTransformHint(gfx::OverlayTransform transform) override;
diff --git a/components/viz/test/fake_skia_output_surface.cc b/components/viz/test/fake_skia_output_surface.cc
index 15ce84b0..8c07fb66 100644
--- a/components/viz/test/fake_skia_output_surface.cc
+++ b/components/viz/test/fake_skia_output_surface.cc
@@ -56,10 +56,6 @@
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
 }
 
-void FakeSkiaOutputSurface::BindFramebuffer() {
-  // TODO(penghuang): remove this method when GLRenderer is removed.
-}
-
 void FakeSkiaOutputSurface::Reshape(const ReshapeParams& params) {
   auto& sk_surface = sk_surfaces_[AggregatedRenderPassId{0}];
   SkColorType color_type = kRGBA_8888_SkColorType;
@@ -87,34 +83,10 @@
   NOTIMPLEMENTED();
 }
 
-uint32_t FakeSkiaOutputSurface::GetFramebufferCopyTextureFormat() {
-  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-  return GL_RGB;
-}
-
 bool FakeSkiaOutputSurface::IsDisplayedAsOverlayPlane() const {
   return false;
 }
 
-unsigned FakeSkiaOutputSurface::GetOverlayTextureId() const {
-  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-  return 0;
-}
-
-bool FakeSkiaOutputSurface::HasExternalStencilTest() const {
-  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-  return false;
-}
-
-void FakeSkiaOutputSurface::ApplyExternalStencil() {
-  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-}
-
-unsigned FakeSkiaOutputSurface::UpdateGpuFence() {
-  NOTIMPLEMENTED();
-  return 0;
-}
-
 void FakeSkiaOutputSurface::SetNeedsSwapSizeNotifications(
     bool needs_swap_size_notifications) {
   NOTIMPLEMENTED();
diff --git a/components/viz/test/fake_skia_output_surface.h b/components/viz/test/fake_skia_output_surface.h
index cbbc88c0..e9893a0 100644
--- a/components/viz/test/fake_skia_output_surface.h
+++ b/components/viz/test/fake_skia_output_surface.h
@@ -44,18 +44,12 @@
   void BindToClient(OutputSurfaceClient* client) override;
   void EnsureBackbuffer() override;
   void DiscardBackbuffer() override;
-  void BindFramebuffer() override;
   void Reshape(const ReshapeParams& params) override;
   void SwapBuffers(OutputSurfaceFrame frame) override;
   void ScheduleOutputSurfaceAsOverlay(
       OverlayProcessorInterface::OutputSurfaceOverlayPlane output_surface_plane)
       override;
-  uint32_t GetFramebufferCopyTextureFormat() override;
   bool IsDisplayedAsOverlayPlane() const override;
-  unsigned GetOverlayTextureId() const override;
-  bool HasExternalStencilTest() const override;
-  void ApplyExternalStencil() override;
-  unsigned UpdateGpuFence() override;
   void SetNeedsSwapSizeNotifications(
       bool needs_swap_size_notifications) override;
   void SetUpdateVSyncParametersCallback(
diff --git a/components/viz/test/test_frame_sink_manager.h b/components/viz/test/test_frame_sink_manager.h
index 310dedd1..55cd4d3b 100644
--- a/components/viz/test/test_frame_sink_manager.h
+++ b/components/viz/test/test_frame_sink_manager.h
@@ -73,6 +73,8 @@
       const DebugRendererSettings& debug_settings) override {}
   void Throttle(const std::vector<FrameSinkId>& ids,
                 base::TimeDelta interval) override {}
+  void StartThrottlingAllFrameSinks(base::TimeDelta interval) override {}
+  void StopThrottlingAllFrameSinks() override {}
 
   mojo::Receiver<mojom::FrameSinkManager> receiver_{this};
   mojo::Remote<mojom::FrameSinkManagerClient> client_;
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index e9983d94..3444ee0 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -1345,6 +1345,7 @@
     "payments/respond_with_callback.cc",
     "payments/respond_with_callback.h",
     "per_web_ui_browser_interface_broker.cc",
+    "performance_manager/frame_rate_throttling.cc",
     "permissions/permission_controller_impl.cc",
     "permissions/permission_controller_impl.h",
     "permissions/permission_service_context.cc",
@@ -1910,10 +1911,14 @@
     "sms/webotp_service.h",
     "speculation_rules/prefetch/prefetch_container.cc",
     "speculation_rules/prefetch/prefetch_container.h",
+    "speculation_rules/prefetch/prefetch_cookie_listener.cc",
+    "speculation_rules/prefetch/prefetch_cookie_listener.h",
     "speculation_rules/prefetch/prefetch_document_manager.cc",
     "speculation_rules/prefetch/prefetch_document_manager.h",
     "speculation_rules/prefetch/prefetch_features.cc",
     "speculation_rules/prefetch/prefetch_features.h",
+    "speculation_rules/prefetch/prefetch_from_string_url_loader.cc",
+    "speculation_rules/prefetch/prefetch_from_string_url_loader.h",
     "speculation_rules/prefetch/prefetch_network_context.cc",
     "speculation_rules/prefetch/prefetch_network_context.h",
     "speculation_rules/prefetch/prefetch_network_context_client.cc",
@@ -1927,6 +1932,8 @@
     "speculation_rules/prefetch/prefetch_status.h",
     "speculation_rules/prefetch/prefetch_type.cc",
     "speculation_rules/prefetch/prefetch_type.h",
+    "speculation_rules/prefetch/prefetch_url_loader_interceptor.cc",
+    "speculation_rules/prefetch/prefetch_url_loader_interceptor.h",
     "speculation_rules/prefetch/prefetched_mainframe_response_container.cc",
     "speculation_rules/prefetch/prefetched_mainframe_response_container.h",
     "speculation_rules/speculation_host_impl.cc",
diff --git a/content/browser/accessibility/dump_accessibility_browsertest_base.cc b/content/browser/accessibility/dump_accessibility_browsertest_base.cc
index 289243b..f2d0c64 100644
--- a/content/browser/accessibility/dump_accessibility_browsertest_base.cc
+++ b/content/browser/accessibility/dump_accessibility_browsertest_base.cc
@@ -311,7 +311,7 @@
   if (!expected_file.empty()) {
     expected_lines = test_helper_.LoadExpectationFile(expected_file);
   }
-#if BUILDFLAG(IS_WIN)
+#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_FUCHSIA)
   else {
     LOG(INFO) << "No expectation file present, ignoring test on this "
                  "platform.";
diff --git a/content/browser/aggregation_service/README.md b/content/browser/aggregation_service/README.md
index 42b7cae5..2ebcd52e 100644
--- a/content/browser/aggregation_service/README.md
+++ b/content/browser/aggregation_service/README.md
@@ -1,6 +1,6 @@
 # Aggregation service
 
-This directory contains the implementation of the client-side logic for the [Aggregation service](https://github.com/WICG/conversion-measurement-api/blob/main/AGGREGATE.md#data-processing-through-the-aggregation-service) proposed for the [Attribution Reporting API](https://github.com/WICG/conversion-measurement-api).
+This directory contains the implementation of the client-side logic for the [Aggregation service](https://github.com/WICG/attribution-reporting-api/blob/main/AGGREGATE.md#data-processing-through-a-secure-aggregation-service) proposed for the [Attribution Reporting API](https://github.com/WICG/attribution-reporting-api).
 
 ## Command-line tool
 A command-line tool that generates aggregatable reports for testing is available. Please see //tools/aggregation_service's [README](../../../tools/aggregation_service/README.md) for more detail
diff --git a/content/browser/aggregation_service/aggregatable_report.h b/content/browser/aggregation_service/aggregatable_report.h
index 8d1037f..7b21d486 100644
--- a/content/browser/aggregation_service/aggregatable_report.h
+++ b/content/browser/aggregation_service/aggregatable_report.h
@@ -41,11 +41,11 @@
   enum class AggregationMode {
     // Uses a server-side Trusted Execution Environment (TEE) to process the
     // encrypted payloads, see
-    // https://github.com/WICG/conversion-measurement-api/blob/main/AGGREGATION_SERVICE_TEE.md.
+    // https://github.com/WICG/attribution-reporting-api/blob/main/AGGREGATION_SERVICE_TEE.md.
     kTeeBased,
 
     // Implements a protocol similar to poplar VDAF in the PPM Framework, see
-    // https://github.com/WICG/conversion-measurement-api/blob/main/AGGREGATE.md#choosing-among-aggregation-services.
+    // https://github.com/WICG/attribution-reporting-api/blob/main/AGGREGATE.md#choosing-among-aggregation-services.
     kExperimentalPoplar,
 
     kDefault = kTeeBased,
diff --git a/content/browser/aggregation_service/aggregatable_report_sender.cc b/content/browser/aggregation_service/aggregatable_report_sender.cc
index d9a3e1a0..bc5fd51 100644
--- a/content/browser/aggregation_service/aggregatable_report_sender.cc
+++ b/content/browser/aggregation_service/aggregatable_report_sender.cc
@@ -92,7 +92,7 @@
             "Sends the aggregatable report to reporting endpoint requested by "
             "APIs that rely on private, secure aggregation (e.g. Attribution "
             "Reporting API, see "
-            "https://github.com/WICG/conversion-measurement-api)."
+            "https://github.com/WICG/attribution-reporting-api)."
           trigger:
             "When an aggregatable report has become eligible for reporting."
           data:
diff --git a/content/browser/aggregation_service/aggregation_service_network_fetcher_impl.cc b/content/browser/aggregation_service/aggregation_service_network_fetcher_impl.cc
index 967f147..71d4f6b 100644
--- a/content/browser/aggregation_service/aggregation_service_network_fetcher_impl.cc
+++ b/content/browser/aggregation_service/aggregation_service_network_fetcher_impl.cc
@@ -93,7 +93,7 @@
           description:
             "Downloads public keys for helper servers requested by APIs that "
             "rely on private, secure aggregation (e.g. Attribution Reporting "
-            "API, see https://github.com/WICG/conversion-measurement-api). "
+            "API, see https://github.com/WICG/attribution-reporting-api). "
             "Keys are requested prior to aggregate reports being sent and are "
             "used to encrypt payloads for the helper servers."
           trigger:
diff --git a/content/browser/aggregation_service/payload_encryption.md b/content/browser/aggregation_service/payload_encryption.md
index 178cbed9..29a10ed 100644
--- a/content/browser/aggregation_service/payload_encryption.md
+++ b/content/browser/aggregation_service/payload_encryption.md
@@ -3,7 +3,7 @@
 Here, we briefly describe the precise details of payload encryption for
 aggregatable reports used in the aggregation service. The format of the payload
 itself is provided in the
-[explainer](https://github.com/WICG/conversion-measurement-api/blob/3d0a541c708391d73905afafa155d6753c8565af/AGGREGATE.md#encrypted-payload)[^1],
+[explainer](https://github.com/WICG/attribution-reporting-api/blob/3d0a541c708391d73905afafa155d6753c8565af/AGGREGATE.md#encrypted-payload)[^1],
 but some of these details aren’t. Note that these details are subject to change,
 but reflect the current API offered by `aggregation_service/`.
 
@@ -20,7 +20,7 @@
 
 The unencrypted payload is first generated as a [CBOR](https://cbor.io/) map
 with the format described in the
-[explainer](https://github.com/WICG/conversion-measurement-api/blob/3d0a541c708391d73905afafa155d6753c8565af/AGGREGATE.md#encrypted-payload)[^1].
+[explainer](https://github.com/WICG/attribution-reporting-api/blob/3d0a541c708391d73905afafa155d6753c8565af/AGGREGATE.md#encrypted-payload)[^1].
 This map is serialized to binary and used as the plaintext input.
 
 ### Associated data
@@ -56,7 +56,7 @@
 
 The public key is a 32-byte (256-bit) bytestring. The browser downloads public
 keys from the aggregation service according to the process described in the
-[explainer](https://github.com/WICG/conversion-measurement-api/blob/3d0a541c708391d73905afafa155d6753c8565af/AGGREGATE.md#encrypted-payload)[^1]
+[explainer](https://github.com/WICG/attribution-reporting-api/blob/3d0a541c708391d73905afafa155d6753c8565af/AGGREGATE.md#encrypted-payload)[^1]
 and picks one uniformly at random (if multiple are specified). Note that this
 key must be base64 decoded by the client. The browser provides the matching `id`
 of the key used to encrypt the payload as `key_id`.
@@ -91,7 +91,7 @@
 [^1]: Note that these links point to a specific commit of the explainer that
     reflects what is currently implemented as of the latest update to this file.
     The
-    [up-to-date explainer](https://github.com/WICG/conversion-measurement-api/blob/main/AGGREGATE.md#encrypted-payload)
+    [up-to-date explainer](https://github.com/WICG/attribution-reporting-api/blob/main/AGGREGATE.md#encrypted-payload)
     may have recent changes that have not yet been implemented.
 
 [^2]: This ensures that ciphertexts for one API cannot be accepted for a
diff --git a/content/browser/android/navigation_handle_proxy.cc b/content/browser/android/navigation_handle_proxy.cc
index 51d08b50..9cf52b07 100644
--- a/content/browser/android/navigation_handle_proxy.cc
+++ b/content/browser/android/navigation_handle_proxy.cc
@@ -60,9 +60,12 @@
                          ? cpp_navigation_handle_->GetURL()
                          : cpp_navigation_handle_->GetBaseURLForDataURL();
 
-  bool is_fragment_navigation = cpp_navigation_handle_->IsSameDocument();
+  bool is_primary_main_frame_fragment_navigation =
+      cpp_navigation_handle_->IsInPrimaryMainFrame() &&
+      cpp_navigation_handle_->IsSameDocument();
 
-  if (cpp_navigation_handle_->HasCommitted()) {
+  if (is_primary_main_frame_fragment_navigation &&
+      cpp_navigation_handle_->HasCommitted()) {
     // See http://crbug.com/251330 for why it's determined this way.
     GURL::Replacements replacements;
     replacements.ClearRef();
@@ -70,7 +73,7 @@
         cpp_navigation_handle_->GetURL().ReplaceComponents(replacements) ==
         cpp_navigation_handle_->GetPreviousMainFrameURL().ReplaceComponents(
             replacements);
-    is_fragment_navigation &= urls_same_ignoring_fragment;
+    is_primary_main_frame_fragment_navigation = urls_same_ignoring_fragment;
   }
 
   bool is_valid_search_form_url =
@@ -81,7 +84,8 @@
   Java_NavigationHandle_didFinish(
       env, java_navigation_handle_, url::GURLAndroid::FromNativeGURL(env, gurl),
       cpp_navigation_handle_->IsErrorPage(),
-      cpp_navigation_handle_->HasCommitted(), is_fragment_navigation,
+      cpp_navigation_handle_->HasCommitted(),
+      is_primary_main_frame_fragment_navigation,
       cpp_navigation_handle_->IsDownload(), is_valid_search_form_url,
       cpp_navigation_handle_->GetPageTransition(),
       cpp_navigation_handle_->GetNetErrorCode(),
diff --git a/content/browser/attribution_reporting/attribution_src_browsertest.cc b/content/browser/attribution_reporting/attribution_src_browsertest.cc
index 5abe21d..b05a6d03 100644
--- a/content/browser/attribution_reporting/attribution_src_browsertest.cc
+++ b/content/browser/attribution_reporting/attribution_src_browsertest.cc
@@ -604,8 +604,21 @@
   EXPECT_TRUE(request->headers.find("Referer") == request->headers.end());
 }
 
-IN_PROC_BROWSER_TEST_F(AttributionSrcBrowserTest,
-                       AttributionSrcImg_TriggerRegistered) {
+class AttributionSrcBasicTriggerBrowserTest
+    : public AttributionSrcBrowserTest,
+      public ::testing::WithParamInterface<
+          std::pair<std::string, std::string>> {};
+
+INSTANTIATE_TEST_SUITE_P(
+    All,
+    AttributionSrcBasicTriggerBrowserTest,
+    ::testing::Values(
+        std::make_pair("attributionsrcimg", "createAttributionSrcImg($1)"),
+        std::make_pair("fetch", "window.fetch($1, {mode:'no-cors'})")),
+    [](const auto& info) { return info.param.first; });  // test name generator
+
+IN_PROC_BROWSER_TEST_P(AttributionSrcBasicTriggerBrowserTest,
+                       TriggerRegistered) {
   GURL page_url =
       https_server()->GetURL("b.test", "/page_with_impression_creator.html");
   EXPECT_TRUE(NavigateToURL(web_contents(), page_url));
@@ -622,8 +635,8 @@
   GURL register_url =
       https_server()->GetURL("c.test", "/register_trigger_headers.html");
 
-  EXPECT_TRUE(ExecJs(web_contents(),
-                     JsReplace("createAttributionSrcImg($1);", register_url)));
+  const std::string& js_template = GetParam().second;
+  EXPECT_TRUE(ExecJs(web_contents(), JsReplace(js_template, register_url)));
   if (!data_host)
     loop.Run();
   data_host->WaitForTriggerData(/*num_trigger_data=*/1);
diff --git a/content/browser/blob_storage/chrome_blob_storage_context.cc b/content/browser/blob_storage/chrome_blob_storage_context.cc
index 7dead742..1beba57f 100644
--- a/content/browser/blob_storage/chrome_blob_storage_context.cc
+++ b/content/browser/blob_storage/chrome_blob_storage_context.cc
@@ -13,7 +13,6 @@
 #include "base/files/file_enumerator.h"
 #include "base/files/file_util.h"
 #include "base/guid.h"
-#include "base/metrics/histogram_macros.h"
 #include "base/supports_user_data.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/task/task_runner.h"
@@ -54,16 +53,12 @@
   }
   base::FileEnumerator enumerator(blob_storage_parent, false /* recursive */,
                                   base::FileEnumerator::DIRECTORIES);
-  bool success = true;
-  bool cleanup_needed = false;
+
   for (FilePath name = enumerator.Next(); !name.empty();
        name = enumerator.Next()) {
-    cleanup_needed = true;
     if (current_run_dir.empty() || name != current_run_dir)
-      success &= base::DeletePathRecursively(name);
+      base::DeletePathRecursively(name);
   }
-  if (cleanup_needed)
-    UMA_HISTOGRAM_BOOLEAN("Storage.Blob.CleanupSuccess", success);
 }
 
 class BlobHandleImpl : public BlobHandle {
diff --git a/content/browser/devtools/devtools_url_loader_interceptor.cc b/content/browser/devtools/devtools_url_loader_interceptor.cc
index cd09ef2..5fe42f0 100644
--- a/content/browser/devtools/devtools_url_loader_interceptor.cc
+++ b/content/browser/devtools/devtools_url_loader_interceptor.cc
@@ -1171,7 +1171,7 @@
   while (headers.EnumerateHeader(&iter, name, &cookie_line)) {
     std::unique_ptr<net::CanonicalCookie> cookie = net::CanonicalCookie::Create(
         create_loader_params_->request.url, cookie_line, now, server_time,
-        net::CookiePartitionKey::Todo());
+        absl::nullopt);
     if (cookie)
       cookies.emplace_back(std::move(cookie));
   }
diff --git a/content/browser/loader/navigation_url_loader_impl.cc b/content/browser/loader/navigation_url_loader_impl.cc
index 46fec2cf..cd8ed02 100644
--- a/content/browser/loader/navigation_url_loader_impl.cc
+++ b/content/browser/loader/navigation_url_loader_impl.cc
@@ -38,6 +38,7 @@
 #include "content/browser/service_worker/service_worker_container_host.h"
 #include "content/browser/service_worker/service_worker_main_resource_handle.h"
 #include "content/browser/service_worker/service_worker_main_resource_loader_interceptor.h"
+#include "content/browser/speculation_rules/prefetch/prefetch_url_loader_interceptor.h"
 #include "content/browser/storage_partition_impl.h"
 #include "content/browser/url_loader_factory_getter.h"
 #include "content/browser/web_package/prefetched_signed_exchange_cache.h"
@@ -474,6 +475,14 @@
         std::move(accept_langs)));
   }
 
+  // Set up an interceptor for prefetch.
+  std::unique_ptr<PrefetchURLLoaderInterceptor> prefetch_interceptor =
+      content::PrefetchURLLoaderInterceptor::MaybeCreateInterceptor(
+          frame_tree_node_id_);
+  if (prefetch_interceptor) {
+    interceptors_.push_back(std::move(prefetch_interceptor));
+  }
+
   // See if embedders want to add interceptors.
   std::vector<std::unique_ptr<URLLoaderRequestInterceptor>>
       browser_interceptors =
diff --git a/content/browser/performance_manager/frame_rate_throttling.cc b/content/browser/performance_manager/frame_rate_throttling.cc
new file mode 100644
index 0000000..c47c0eb7
--- /dev/null
+++ b/content/browser/performance_manager/frame_rate_throttling.cc
@@ -0,0 +1,23 @@
+// 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 "content/public/browser/frame_rate_throttling.h"
+
+#include "components/viz/host/host_frame_sink_manager.h"
+#include "content/browser/compositor/surface_utils.h"
+#include "content/public/browser/browser_thread.h"
+
+namespace content {
+
+CONTENT_EXPORT void StartThrottlingAllFrameSinks(base::TimeDelta interval) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  GetHostFrameSinkManager()->StartThrottlingAllFrameSinks(interval);
+}
+
+CONTENT_EXPORT void StopThrottlingAllFrameSinks() {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  GetHostFrameSinkManager()->StopThrottlingAllFrameSinks();
+}
+
+}  // namespace content
diff --git a/content/browser/renderer_host/navigator.cc b/content/browser/renderer_host/navigator.cc
index ccf890a5..0588899 100644
--- a/content/browser/renderer_host/navigator.cc
+++ b/content/browser/renderer_host/navigator.cc
@@ -1306,6 +1306,10 @@
       navigation_data_->commit_navigation_sent_) {
     base::TimeTicks unload_start = params.unload_start.value();
     base::TimeTicks unload_end = params.unload_end.value();
+    // Note: we expect `commit_navigation_end` to be later than `unload_end`.
+    // `unload_end` is recorded when the unload handlers finish running, which
+    // happens prior to the navigation committing and recording
+    // `commit_navigation_end` in this same-process case.
     base::TimeTicks commit_navigation_end =
         params.commit_navigation_end.value();
 
@@ -1316,7 +1320,7 @@
           blink::LocalTimeTicks::FromTimeTicks(first_before_unload_start_time),
           blink::LocalTimeTicks::FromTimeTicks(base::TimeTicks::Now()),
           blink::RemoteTimeTicks::FromTimeTicks(unload_start),
-          blink::RemoteTimeTicks::FromTimeTicks(unload_end));
+          blink::RemoteTimeTicks::FromTimeTicks(commit_navigation_end));
       blink::LocalTimeTicks converted_unload_start = converter.ToLocalTimeTicks(
           blink::RemoteTimeTicks::FromTimeTicks(unload_start));
       blink::LocalTimeTicks converted_unload_end = converter.ToLocalTimeTicks(
diff --git a/content/browser/speculation_rules/prefetch/prefetch_container.cc b/content/browser/speculation_rules/prefetch/prefetch_container.cc
index 5b1b8dc..3920e5e 100644
--- a/content/browser/speculation_rules/prefetch/prefetch_container.cc
+++ b/content/browser/speculation_rules/prefetch/prefetch_container.cc
@@ -6,6 +6,9 @@
 
 #include <memory>
 
+#include "base/callback.h"
+#include "base/time/time.h"
+#include "content/browser/speculation_rules/prefetch/prefetch_cookie_listener.h"
 #include "content/browser/speculation_rules/prefetch/prefetch_document_manager.h"
 #include "content/browser/speculation_rules/prefetch/prefetch_network_context.h"
 #include "content/browser/speculation_rules/prefetch/prefetch_service.h"
@@ -14,6 +17,7 @@
 #include "content/browser/speculation_rules/prefetch/prefetched_mainframe_response_container.h"
 #include "content/public/browser/global_routing_id.h"
 #include "services/network/public/cpp/simple_url_loader.h"
+#include "services/network/public/mojom/cookie_manager.mojom.h"
 #include "url/gurl.h"
 
 namespace content {
@@ -48,6 +52,58 @@
   return prefetch_document_manager_.get();
 }
 
+void PrefetchContainer::RegisterCookieListener(
+    network::mojom::CookieManager* cookie_manager) {
+  cookie_listener_ =
+      PrefetchCookieListener::MakeAndRegister(url_, cookie_manager);
+}
+
+void PrefetchContainer::StopCookieListener() {
+  if (cookie_listener_)
+    cookie_listener_->StopListening();
+}
+
+bool PrefetchContainer::HaveDefaultContextCookiesChanged() const {
+  if (cookie_listener_)
+    return cookie_listener_->HaveCookiesChanged();
+  return false;
+}
+
+bool PrefetchContainer::IsIsolatedCookieCopyInProgress() const {
+  switch (cookie_copy_status_) {
+    case CookieCopyStatus::kNotStarted:
+    case CookieCopyStatus::kCompleted:
+      return false;
+    case CookieCopyStatus::kInProgress:
+      return true;
+  }
+}
+
+void PrefetchContainer::OnIsolatedCookieCopyStart() {
+  DCHECK(!IsIsolatedCookieCopyInProgress());
+
+  // We don't want the cookie listener for this URL to get the changes from the
+  // copy.
+  StopCookieListener();
+
+  cookie_copy_status_ = CookieCopyStatus::kInProgress;
+}
+
+void PrefetchContainer::OnIsolatedCookieCopyComplete() {
+  DCHECK(IsIsolatedCookieCopyInProgress());
+
+  cookie_copy_status_ = CookieCopyStatus::kCompleted;
+
+  if (on_cookie_copy_complete_callback_)
+    std::move(on_cookie_copy_complete_callback_).Run();
+}
+
+void PrefetchContainer::SetOnCookieCopyCompleteCallback(
+    base::OnceClosure callback) {
+  DCHECK(IsIsolatedCookieCopyInProgress());
+  on_cookie_copy_complete_callback_ = std::move(callback);
+}
+
 void PrefetchContainer::TakeURLLoader(
     std::unique_ptr<network::SimpleURLLoader> loader) {
   DCHECK(!loader_);
@@ -59,17 +115,25 @@
   loader_.reset();
 }
 
-bool PrefetchContainer::HasPrefetchedResponse() const {
-  return prefetched_response_ != nullptr;
+bool PrefetchContainer::HasValidPrefetchedResponse(
+    base::TimeDelta cacheable_duration) const {
+  return prefetched_response_ != nullptr &&
+         prefetch_received_time_.has_value() &&
+         base::TimeTicks::Now() <
+             prefetch_received_time_.value() + cacheable_duration;
 }
 
 void PrefetchContainer::TakePrefetchedResponse(
     std::unique_ptr<PrefetchedMainframeResponseContainer> prefetched_response) {
+  DCHECK(!prefetched_response_);
+  DCHECK(!is_decoy_);
+  prefetch_received_time_ = base::TimeTicks::Now();
   prefetched_response_ = std::move(prefetched_response);
 }
 
 std::unique_ptr<PrefetchedMainframeResponseContainer>
 PrefetchContainer::ReleasePrefetchedResponse() {
+  prefetch_received_time_.reset();
   return std::move(prefetched_response_);
 }
 
diff --git a/content/browser/speculation_rules/prefetch/prefetch_container.h b/content/browser/speculation_rules/prefetch/prefetch_container.h
index 511b7ec..d4e3e7f 100644
--- a/content/browser/speculation_rules/prefetch/prefetch_container.h
+++ b/content/browser/speculation_rules/prefetch/prefetch_container.h
@@ -7,7 +7,9 @@
 
 #include <utility>
 
+#include "base/callback.h"
 #include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
 #include "content/browser/speculation_rules/prefetch/prefetch_status.h"
 #include "content/browser/speculation_rules/prefetch/prefetch_type.h"
 #include "content/common/content_export.h"
@@ -17,13 +19,17 @@
 
 namespace network {
 class SimpleURLLoader;
+namespace mojom {
+class CookieManager;
+}  // namespace mojom
 }  // namespace network
 
 namespace content {
 
-class PrefetchService;
+class PrefetchCookieListener;
 class PrefetchDocumentManager;
 class PrefetchNetworkContext;
+class PrefetchService;
 class PrefetchedMainframeResponseContainer;
 
 // This class contains the state for a request to prefetch a specific URL.
@@ -73,6 +79,22 @@
   void SetIsDecoy(bool is_decoy) { is_decoy_ = is_decoy; }
   bool IsDecoy() const { return is_decoy_; }
 
+  // After the initial eligiblity check for |url_|, a
+  // |PrefetchCookieListener| listens for any changes to the cookies
+  // associated with |url_|. If these cookies change, then no prefetched
+  // resources will be served.
+  void RegisterCookieListener(network::mojom::CookieManager* cookie_manager);
+  void StopCookieListener();
+  bool HaveDefaultContextCookiesChanged() const;
+
+  // Before a prefetch can be served, any cookies added to the isolated network
+  // context must be copied over to the default network context. These functions
+  // are used to check and update the status of this process.
+  bool IsIsolatedCookieCopyInProgress() const;
+  void OnIsolatedCookieCopyStart();
+  void OnIsolatedCookieCopyComplete();
+  void SetOnCookieCopyCompleteCallback(base::OnceClosure callback);
+
   // The network context used to make network requests for this prefetch.
   PrefetchNetworkContext* GetOrCreateNetworkContext(
       PrefetchService* prefetch_service);
@@ -87,7 +109,7 @@
   PrefetchDocumentManager* GetPrefetchDocumentManager() const;
 
   // Whether or not |this| has a prefetched response.
-  bool HasPrefetchedResponse() const;
+  bool HasValidPrefetchedResponse(base::TimeDelta cacheable_duration) const;
 
   // |this| takes ownership of the given |prefetched_response|.
   void TakePrefetchedResponse(
@@ -125,6 +147,10 @@
   // any prefetched resources will not be served.
   bool is_decoy_ = false;
 
+  // This tracks whether the cookies associated with |url_| have changed at some
+  // point after the initial eligibility check.
+  std::unique_ptr<PrefetchCookieListener> cookie_listener_;
+
   // The network context used to prefetch |url_|.
   std::unique_ptr<PrefetchNetworkContext> network_context_;
 
@@ -134,6 +160,23 @@
   // The prefetched response for |url_|.
   std::unique_ptr<PrefetchedMainframeResponseContainer> prefetched_response_;
 
+  // The time at which |prefetched_response_| was received. This is used to
+  // determine whether or not |prefetched_response_| is stale.
+  absl::optional<base::TimeTicks> prefetch_received_time_;
+
+  // The different possible states of the cookie copy process.
+  enum class CookieCopyStatus {
+    kNotStarted,
+    kInProgress,
+    kCompleted,
+  };
+
+  // The current state of the cookie copy process for this prefetch.
+  CookieCopyStatus cookie_copy_status_ = CookieCopyStatus::kNotStarted;
+
+  // A callback that runs once |cookie_copy_status_| is set to |kCompleted|.
+  base::OnceClosure on_cookie_copy_complete_callback_;
+
   base::WeakPtrFactory<PrefetchContainer> weak_method_factory_{this};
 };
 
diff --git a/content/browser/speculation_rules/prefetch/prefetch_container_unittest.cc b/content/browser/speculation_rules/prefetch/prefetch_container_unittest.cc
index 4898fc6..7eeff2e 100644
--- a/content/browser/speculation_rules/prefetch/prefetch_container_unittest.cc
+++ b/content/browser/speculation_rules/prefetch/prefetch_container_unittest.cc
@@ -6,13 +6,76 @@
 
 #include "content/browser/speculation_rules/prefetch/prefetch_status.h"
 #include "content/browser/speculation_rules/prefetch/prefetch_type.h"
+#include "content/browser/speculation_rules/prefetch/prefetched_mainframe_response_container.h"
+#include "content/public/browser/browser_context.h"
 #include "content/public/browser/global_routing_id.h"
+#include "content/public/browser/storage_partition.h"
+#include "content/public/test/test_renderer_host.h"
+#include "net/base/isolation_info.h"
+#include "services/network/public/mojom/cookie_manager.mojom.h"
+#include "services/network/public/mojom/network_context.mojom.h"
+#include "services/network/public/mojom/url_response_head.mojom.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace content {
 namespace {
 
-class PrefetchContainerTest : public ::testing::Test {};
+class PrefetchContainerTest : public RenderViewHostTestHarness {
+ public:
+  PrefetchContainerTest()
+      : RenderViewHostTestHarness(
+            base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
+
+  void SetUp() override {
+    RenderViewHostTestHarness::SetUp();
+
+    browser_context()
+        ->GetDefaultStoragePartition()
+        ->GetNetworkContext()
+        ->GetCookieManager(cookie_manager_.BindNewPipeAndPassReceiver());
+  }
+
+  network::mojom::CookieManager* cookie_manager() {
+    return cookie_manager_.get();
+  }
+
+  bool SetCookie(const GURL& url, const std::string& value) {
+    std::unique_ptr<net::CanonicalCookie> cookie(net::CanonicalCookie::Create(
+        url, value, base::Time::Now(), /*server_time=*/absl::nullopt,
+        /*cookie_partition_key=*/absl::nullopt));
+
+    EXPECT_TRUE(cookie.get());
+
+    bool result = false;
+    base::RunLoop run_loop;
+
+    net::CookieOptions options;
+    options.set_include_httponly();
+    options.set_same_site_cookie_context(
+        net::CookieOptions::SameSiteCookieContext::MakeInclusive());
+
+    cookie_manager_->SetCanonicalCookie(
+        *cookie.get(), url, options,
+        base::BindOnce(
+            [](bool* result, base::RunLoop* run_loop,
+               net::CookieAccessResult set_cookie_access_result) {
+              *result = set_cookie_access_result.status.IsInclude();
+              run_loop->Quit();
+            },
+            &result, &run_loop));
+
+    // This will run until the cookie is set.
+    run_loop.Run();
+
+    // This will run until the cookie listener is updated.
+    base::RunLoop().RunUntilIdle();
+
+    return result;
+  }
+
+ private:
+  mojo::Remote<network::mojom::CookieManager> cookie_manager_;
+};
 
 TEST_F(PrefetchContainerTest, CreatePrefetchContainer) {
   PrefetchContainer prefetch_container(
@@ -62,5 +125,71 @@
   EXPECT_TRUE(prefetch_container.IsDecoy());
 }
 
+TEST_F(PrefetchContainerTest, ValidResponse) {
+  PrefetchContainer prefetch_container(
+      GlobalRenderFrameHostId(1234, 5678), GURL("https://test.com"),
+      PrefetchType(/*use_isolated_network_context=*/true,
+                   /*use_prefetch_proxy=*/true),
+      nullptr);
+
+  prefetch_container.TakePrefetchedResponse(
+      std::make_unique<PrefetchedMainframeResponseContainer>(
+          net::IsolationInfo(), network::mojom::URLResponseHead::New(),
+          std::make_unique<std::string>("test body")));
+
+  task_environment()->FastForwardBy(base::Minutes(2));
+
+  EXPECT_FALSE(prefetch_container.HasValidPrefetchedResponse(base::Minutes(1)));
+  EXPECT_TRUE(prefetch_container.HasValidPrefetchedResponse(base::Minutes(3)));
+}
+
+TEST_F(PrefetchContainerTest, CookieListener) {
+  PrefetchContainer prefetch_container(
+      GlobalRenderFrameHostId(1234, 5678), GURL("https://test.com"),
+      PrefetchType(/*use_isolated_network_context=*/true,
+                   /*use_prefetch_proxy=*/true),
+      nullptr);
+
+  EXPECT_FALSE(prefetch_container.HaveDefaultContextCookiesChanged());
+
+  prefetch_container.RegisterCookieListener(cookie_manager());
+
+  EXPECT_FALSE(prefetch_container.HaveDefaultContextCookiesChanged());
+
+  ASSERT_TRUE(SetCookie(GURL("https://test.com"), "test-cookie"));
+
+  EXPECT_TRUE(prefetch_container.HaveDefaultContextCookiesChanged());
+}
+
+TEST_F(PrefetchContainerTest, CookieCopy) {
+  PrefetchContainer prefetch_container(
+      GlobalRenderFrameHostId(1234, 5678), GURL("https://test.com"),
+      PrefetchType(/*use_isolated_network_context=*/true,
+                   /*use_prefetch_proxy=*/true),
+      nullptr);
+  prefetch_container.RegisterCookieListener(cookie_manager());
+
+  EXPECT_FALSE(prefetch_container.IsIsolatedCookieCopyInProgress());
+
+  prefetch_container.OnIsolatedCookieCopyStart();
+
+  EXPECT_TRUE(prefetch_container.IsIsolatedCookieCopyInProgress());
+
+  // Once the cookie copy process has started, we should stop the cookie
+  // listener.
+  ASSERT_TRUE(SetCookie(GURL("https://test.com"), "test-cookie"));
+  EXPECT_FALSE(prefetch_container.HaveDefaultContextCookiesChanged());
+
+  bool callback_called = false;
+  prefetch_container.SetOnCookieCopyCompleteCallback(
+      base::BindOnce([](bool* callback_called) { *callback_called = true; },
+                     &callback_called));
+
+  prefetch_container.OnIsolatedCookieCopyComplete();
+
+  EXPECT_FALSE(prefetch_container.IsIsolatedCookieCopyInProgress());
+  EXPECT_TRUE(callback_called);
+}
+
 }  // namespace
 }  // namespace content
diff --git a/content/browser/speculation_rules/prefetch/prefetch_cookie_listener.cc b/content/browser/speculation_rules/prefetch/prefetch_cookie_listener.cc
new file mode 100644
index 0000000..6bbf3ff3
--- /dev/null
+++ b/content/browser/speculation_rules/prefetch/prefetch_cookie_listener.cc
@@ -0,0 +1,51 @@
+// 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 "content/browser/speculation_rules/prefetch/prefetch_cookie_listener.h"
+
+#include <memory>
+
+#include "base/check.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "services/network/public/mojom/cookie_manager.mojom.h"
+#include "url/gurl.h"
+
+namespace content {
+
+std::unique_ptr<PrefetchCookieListener> PrefetchCookieListener::MakeAndRegister(
+    const GURL& url,
+    network::mojom::CookieManager* cookie_manager) {
+  DCHECK(cookie_manager);
+
+  std::unique_ptr<PrefetchCookieListener> cookie_listener =
+      std::make_unique<PrefetchCookieListener>(url);
+
+  // |cookie_listener| will get updates whenever host cookies for |url| or
+  // domain cookies that match |url| are changed.
+  cookie_manager->AddCookieChangeListener(
+      url, absl::nullopt,
+      cookie_listener->cookie_listener_receiver_.BindNewPipeAndPassRemote());
+
+  return cookie_listener;
+}
+
+PrefetchCookieListener::PrefetchCookieListener(const GURL& url) : url_(url) {}
+
+PrefetchCookieListener::~PrefetchCookieListener() = default;
+
+void PrefetchCookieListener::StopListening() {
+  cookie_listener_receiver_.reset();
+}
+
+void PrefetchCookieListener::OnCookieChange(
+    const net::CookieChangeInfo& change) {
+  DCHECK(url_.DomainIs(change.cookie.DomainWithoutDot()));
+  have_cookies_changed_ = true;
+
+  // Once we record one change to the cookies associated with |url_|, we don't
+  // care about any subsequent changes.
+  StopListening();
+}
+
+}  // namespace content
\ No newline at end of file
diff --git a/content/browser/speculation_rules/prefetch/prefetch_cookie_listener.h b/content/browser/speculation_rules/prefetch/prefetch_cookie_listener.h
new file mode 100644
index 0000000..f96d52a
--- /dev/null
+++ b/content/browser/speculation_rules/prefetch/prefetch_cookie_listener.h
@@ -0,0 +1,57 @@
+// 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 CONTENT_BROWSER_SPECULATION_RULES_PREFETCH_PREFETCH_COOKIE_LISTENER_H_
+#define CONTENT_BROWSER_SPECULATION_RULES_PREFETCH_PREFETCH_COOKIE_LISTENER_H_
+
+#include <memory>
+
+#include "content/common/content_export.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "services/network/public/mojom/cookie_manager.mojom.h"
+#include "url/gurl.h"
+
+namespace content {
+
+// Listens for changes to the cookies for a specific URL. This includes both
+// host and domain cookies. Keeps track of whether the cookies have changed or
+// remained the same since the object is created until StopListening() is
+// called.
+class CONTENT_EXPORT PrefetchCookieListener
+    : public network::mojom::CookieChangeListener {
+ public:
+  static std::unique_ptr<PrefetchCookieListener> MakeAndRegister(
+      const GURL& url,
+      network::mojom::CookieManager* cookie_manager);
+
+  explicit PrefetchCookieListener(const GURL& url);
+  ~PrefetchCookieListener() override;
+
+  PrefetchCookieListener(const PrefetchCookieListener&) = delete;
+  PrefetchCookieListener& operator=(const PrefetchCookieListener&) = delete;
+
+  // Causes the Cookie Listener to stop listening to cookie changes to |url_|.
+  // After this is called the value of |have_cookies_changed_| will no longer
+  // change.
+  void StopListening();
+
+  // Gets whether the cookies of |url_| have changed between the creation of the
+  // object and either the first time |StopListening| is called or now (if
+  // |StopListening| has never been called).
+  bool HaveCookiesChanged() const { return have_cookies_changed_; }
+
+ private:
+  // network::mojom::CookieChangeListener
+  void OnCookieChange(const net::CookieChangeInfo& change) override;
+
+  bool have_cookies_changed_ = false;
+  GURL url_;
+
+  mojo::Receiver<network::mojom::CookieChangeListener>
+      cookie_listener_receiver_{this};
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_SPECULATION_RULES_PREFETCH_PREFETCH_COOKIE_LISTENER_H_
diff --git a/content/browser/speculation_rules/prefetch/prefetch_cookie_listener_unittest.cc b/content/browser/speculation_rules/prefetch/prefetch_cookie_listener_unittest.cc
new file mode 100644
index 0000000..2e93bbc
--- /dev/null
+++ b/content/browser/speculation_rules/prefetch/prefetch_cookie_listener_unittest.cc
@@ -0,0 +1,202 @@
+// 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 "content/browser/speculation_rules/prefetch/prefetch_cookie_listener.h"
+
+#include <memory>
+
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/storage_partition.h"
+#include "content/public/test/test_renderer_host.h"
+#include "services/network/public/mojom/cookie_manager.mojom.h"
+#include "services/network/public/mojom/network_context.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+namespace {
+
+class PrefetchCookieListenerTest : public RenderViewHostTestHarness {
+ public:
+  void SetUp() override {
+    RenderViewHostTestHarness::SetUp();
+
+    browser_context()
+        ->GetDefaultStoragePartition()
+        ->GetNetworkContext()
+        ->GetCookieManager(cookie_manager_.BindNewPipeAndPassReceiver());
+  }
+
+  std::unique_ptr<PrefetchCookieListener> MakeCookieListener(const GURL& url) {
+    std::unique_ptr<PrefetchCookieListener> cookie_listener =
+        PrefetchCookieListener::MakeAndRegister(url, cookie_manager_.get());
+    DCHECK(cookie_listener);
+    return cookie_listener;
+  }
+
+  // Creates a host cookie for the given url, and then adds it to the default
+  // partition using |cookie_manager_|.
+  bool SetHostCookie(const GURL& url, const std::string& value) {
+    std::unique_ptr<net::CanonicalCookie> cookie(net::CanonicalCookie::Create(
+        url, value, base::Time::Now(), /*server_time=*/absl::nullopt,
+        /*cookie_partition_key=*/absl::nullopt));
+    EXPECT_TRUE(cookie.get());
+    EXPECT_TRUE(cookie->IsHostCookie());
+
+    return SetCookie(*cookie.get(), url);
+  }
+
+  // Creates a domain cookie for the given url, and then adds it to the default
+  // partition using |cookie_manager_|.
+  bool SetDomainCookie(const GURL& url,
+                       const std::string& name,
+                       const std::string& value,
+                       const std::string& domain) {
+    net::CookieInclusionStatus status;
+    std::unique_ptr<net::CanonicalCookie> cookie(
+        net::CanonicalCookie::CreateSanitizedCookie(
+            url, name, value, domain, /*path=*/"", base::Time::Now(),
+            base::Time::Now() + base::Hours(1), base::Time::Now(),
+            /*secure=*/true, /*http_only=*/false,
+            net::CookieSameSite::NO_RESTRICTION, net::COOKIE_PRIORITY_DEFAULT,
+            /*same_party=*/false, /*partition_key=*/absl::nullopt, &status));
+    EXPECT_TRUE(cookie.get());
+    EXPECT_TRUE(cookie->IsDomainCookie());
+    EXPECT_TRUE(status.IsInclude());
+
+    return SetCookie(*cookie.get(), url);
+  }
+
+ private:
+  bool SetCookie(const net::CanonicalCookie& cookie, const GURL& url) {
+    bool result = false;
+    base::RunLoop run_loop;
+
+    net::CookieOptions options;
+    options.set_include_httponly();
+    options.set_same_site_cookie_context(
+        net::CookieOptions::SameSiteCookieContext::MakeInclusive());
+
+    cookie_manager_->SetCanonicalCookie(
+        cookie, url, options,
+        base::BindOnce(
+            [](bool* result, base::RunLoop* run_loop,
+               net::CookieAccessResult set_cookie_access_result) {
+              *result = set_cookie_access_result.status.IsInclude();
+              run_loop->Quit();
+            },
+            &result, &run_loop));
+
+    // This will run until the cookie is set.
+    run_loop.Run();
+
+    // This will run until the cookie listener is updated.
+    base::RunLoop().RunUntilIdle();
+
+    return result;
+  }
+
+  mojo::Remote<network::mojom::CookieManager> cookie_manager_;
+};
+
+TEST_F(PrefetchCookieListenerTest, NoCookiesChanged) {
+  std::unique_ptr<PrefetchCookieListener> cookie_listener =
+      MakeCookieListener(GURL("https://www.example.com/"));
+
+  EXPECT_FALSE(cookie_listener->HaveCookiesChanged());
+}
+
+TEST_F(PrefetchCookieListenerTest, ChangedHostCookiesForSameURL) {
+  std::unique_ptr<PrefetchCookieListener> cookie_listener =
+      MakeCookieListener(GURL("https://www.example.com/"));
+
+  ASSERT_TRUE(SetHostCookie(GURL("https://www.example.com/"), "test-cookie"));
+
+  EXPECT_TRUE(cookie_listener->HaveCookiesChanged());
+}
+
+TEST_F(PrefetchCookieListenerTest, ChangedHostCookiesForOtherURL) {
+  std::unique_ptr<PrefetchCookieListener> cookie_listener =
+      MakeCookieListener(GURL("https://www.example.com/"));
+
+  ASSERT_TRUE(SetHostCookie(GURL("https://www.other.com/"), "test-cookie"));
+
+  EXPECT_FALSE(cookie_listener->HaveCookiesChanged());
+}
+
+TEST_F(PrefetchCookieListenerTest, ChangedHostCookiesForGeneralDomain) {
+  std::unique_ptr<PrefetchCookieListener> cookie_listener =
+      MakeCookieListener(GURL("https://specificdomain.generaldomain.com/"));
+
+  ASSERT_TRUE(SetHostCookie(GURL("https://generaldomain.com/"), "test-cookie"));
+
+  EXPECT_FALSE(cookie_listener->HaveCookiesChanged());
+}
+
+TEST_F(PrefetchCookieListenerTest, ChangedHostCookiesForSubomain) {
+  std::unique_ptr<PrefetchCookieListener> cookie_listener =
+      MakeCookieListener(GURL("https://specificdomain.generaldomain.com/"));
+
+  ASSERT_TRUE(SetHostCookie(
+      GURL("https://veryspecificdomain.specificdomain.generaldomain.com/"),
+      "test-cookie"));
+
+  EXPECT_FALSE(cookie_listener->HaveCookiesChanged());
+}
+
+TEST_F(PrefetchCookieListenerTest, ChangedDomainCookiesForSameURL) {
+  std::unique_ptr<PrefetchCookieListener> cookie_listener =
+      MakeCookieListener(GURL("https://www.example.com/"));
+
+  ASSERT_TRUE(SetDomainCookie(GURL("https://www.example.com/"), "test",
+                              "cookie", "www.example.com"));
+
+  EXPECT_TRUE(cookie_listener->HaveCookiesChanged());
+}
+
+TEST_F(PrefetchCookieListenerTest, ChangedDomainCookiesForOtherURL) {
+  std::unique_ptr<PrefetchCookieListener> cookie_listener =
+      MakeCookieListener(GURL("https://www.example.com/"));
+
+  ASSERT_TRUE(SetDomainCookie(GURL("https://www.other.com/"), "test", "cookie",
+                              "www.other.com"));
+
+  EXPECT_FALSE(cookie_listener->HaveCookiesChanged());
+}
+
+TEST_F(PrefetchCookieListenerTest, ChangedDomainCookiesForGeneralDomain) {
+  std::unique_ptr<PrefetchCookieListener> cookie_listener =
+      MakeCookieListener(GURL("https://specificdomain.generaldomain.com/"));
+
+  ASSERT_TRUE(SetDomainCookie(GURL("https://generaldomain.com/"), "test",
+                              "cookie", "generaldomain.com"));
+
+  EXPECT_TRUE(cookie_listener->HaveCookiesChanged());
+}
+
+TEST_F(PrefetchCookieListenerTest, ChangedDomainCookiesForSubomain) {
+  std::unique_ptr<PrefetchCookieListener> cookie_listener =
+      MakeCookieListener(GURL("https://specificdomain.generaldomain.com/"));
+
+  ASSERT_TRUE(SetDomainCookie(
+      GURL("https://veryspecificdomain.specificdomain.generaldomain.com/"),
+      "test", "cookie", "veryspecificdomain.specificdomain.generaldomain.com"));
+
+  EXPECT_FALSE(cookie_listener->HaveCookiesChanged());
+}
+
+TEST_F(PrefetchCookieListenerTest, StopListening) {
+  std::unique_ptr<PrefetchCookieListener> cookie_listener =
+      MakeCookieListener(GURL("https://www.example.com/"));
+
+  cookie_listener->StopListening();
+
+  ASSERT_TRUE(SetHostCookie(GURL("https://www.example.com"), "test-cookie"));
+
+  // Since the cookies were changed after |StopListening| was called, the
+  // listener shouldn't update.
+  EXPECT_FALSE(cookie_listener->HaveCookiesChanged());
+}
+
+}  // namespace
+}  // namespace content
diff --git a/content/browser/speculation_rules/prefetch/prefetch_document_manager.cc b/content/browser/speculation_rules/prefetch/prefetch_document_manager.cc
index ec1585f6..cddc358 100644
--- a/content/browser/speculation_rules/prefetch/prefetch_document_manager.cc
+++ b/content/browser/speculation_rules/prefetch/prefetch_document_manager.cc
@@ -11,7 +11,10 @@
 #include "content/browser/speculation_rules/prefetch/prefetch_container.h"
 #include "content/browser/speculation_rules/prefetch/prefetch_service.h"
 #include "content/public/browser/browser_context.h"
+#include "content/public/browser/navigation_handle.h"
 #include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_contents_observer.h"
 #include "url/origin.h"
 
 namespace content {
@@ -21,10 +24,33 @@
 }  // namespace
 
 PrefetchDocumentManager::PrefetchDocumentManager(RenderFrameHost* rfh)
-    : DocumentUserData(rfh) {}
+    : DocumentUserData(rfh),
+      WebContentsObserver(WebContents::FromRenderFrameHost(rfh)) {}
 
 PrefetchDocumentManager::~PrefetchDocumentManager() = default;
 
+void PrefetchDocumentManager::DidStartNavigation(
+    NavigationHandle* navigation_handle) {
+  // Ignore navigations for a different RenderFrameHost.
+  if (render_frame_host().GetGlobalId() !=
+      navigation_handle->GetPreviousRenderFrameHostId())
+    return;
+
+  // Ignores any same document navigations since we can't use prefetches to
+  // speed them up.
+  if (navigation_handle->IsSameDocument())
+    return;
+
+  // Get the prefetch for the URL being navigated to. If there is no prefetch
+  // for that URL, then stop.
+  auto prefetch_iter = all_prefetches_.find(navigation_handle->GetURL());
+  if (prefetch_iter == all_prefetches_.end())
+    return;
+
+  // Inform |PrefetchService| of the navigation to the prefetch.
+  GetPrefetchService()->PrepareToServe(prefetch_iter->second);
+}
+
 void PrefetchDocumentManager::ProcessCandidates(
     std::vector<blink::mojom::SpeculationCandidatePtr>& candidates) {
   // Filter out candidates that can be handled by |PrefetchService| and
@@ -89,19 +115,9 @@
       weak_method_factory_.GetWeakPtr());
   all_prefetches_[url] = owned_prefetches_[url]->GetWeakPtr();
 
-  if (g_prefetch_service_for_testing) {
-    g_prefetch_service_for_testing->PrefetchUrl(
-        owned_prefetches_[url]->GetWeakPtr());
-    return;
-  }
-
   // Send a reference of the new |PrefetchContainer| to |PrefetchService| to
   // start the prefetch process.
-  DCHECK(BrowserContextImpl::From(render_frame_host().GetBrowserContext())
-             ->GetPrefetchService());
-  BrowserContextImpl::From(render_frame_host().GetBrowserContext())
-      ->GetPrefetchService()
-      ->PrefetchUrl(owned_prefetches_[url]->GetWeakPtr());
+  GetPrefetchService()->PrefetchUrl(owned_prefetches_[url]->GetWeakPtr());
 
   // TODO(https://crbug.com/1299059): Track metrics about the prefetches.
 }
@@ -121,6 +137,17 @@
   g_prefetch_service_for_testing = prefetch_service;
 }
 
+PrefetchService* PrefetchDocumentManager::GetPrefetchService() const {
+  if (g_prefetch_service_for_testing) {
+    return g_prefetch_service_for_testing;
+  }
+
+  DCHECK(BrowserContextImpl::From(render_frame_host().GetBrowserContext())
+             ->GetPrefetchService());
+  return BrowserContextImpl::From(render_frame_host().GetBrowserContext())
+      ->GetPrefetchService();
+}
+
 DOCUMENT_USER_DATA_KEY_IMPL(PrefetchDocumentManager);
 
 }  // namespace content
diff --git a/content/browser/speculation_rules/prefetch/prefetch_document_manager.h b/content/browser/speculation_rules/prefetch/prefetch_document_manager.h
index 7d5d162..344fb16 100644
--- a/content/browser/speculation_rules/prefetch/prefetch_document_manager.h
+++ b/content/browser/speculation_rules/prefetch/prefetch_document_manager.h
@@ -13,18 +13,21 @@
 #include "content/browser/speculation_rules/prefetch/prefetch_type.h"
 #include "content/common/content_export.h"
 #include "content/public/browser/document_user_data.h"
+#include "content/public/browser/web_contents_observer.h"
 #include "third_party/blink/public/mojom/speculation_rules/speculation_rules.mojom.h"
 #include "url/gurl.h"
 
 namespace content {
 
+class NavigationHandle;
 class PrefetchContainer;
 class PrefetchService;
 
 // Manages the state of and tracks metrics about prefetches for a single page
 // load.
 class CONTENT_EXPORT PrefetchDocumentManager
-    : public DocumentUserData<PrefetchDocumentManager> {
+    : public DocumentUserData<PrefetchDocumentManager>,
+      public WebContentsObserver {
  public:
   ~PrefetchDocumentManager() override;
 
@@ -32,6 +35,9 @@
   const PrefetchDocumentManager operator=(const PrefetchDocumentManager&) =
       delete;
 
+  // WebContentsObserver.
+  void DidStartNavigation(NavigationHandle* navigation_handle) override;
+
   // Processes the given speculation candidates to see if they can be
   // prefetched. Any candidates that can be prefetched are removed from
   // |candidates|, and a prefetch for the URL of the candidate is started.
@@ -52,6 +58,9 @@
   explicit PrefetchDocumentManager(RenderFrameHost* rfh);
   friend DocumentUserData;
 
+  // Helper function to get the |PrefetchService| associated with |this|.
+  PrefetchService* GetPrefetchService() const;
+
   // This map holds references to all |PrefetchContainer| associated with
   // |this|, regardless of ownership.
   std::map<GURL, base::WeakPtr<PrefetchContainer>> all_prefetches_;
diff --git a/content/browser/speculation_rules/prefetch/prefetch_from_string_url_loader.cc b/content/browser/speculation_rules/prefetch/prefetch_from_string_url_loader.cc
new file mode 100644
index 0000000..b7f02911
--- /dev/null
+++ b/content/browser/speculation_rules/prefetch/prefetch_from_string_url_loader.cc
@@ -0,0 +1,162 @@
+// 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 "content/browser/speculation_rules/prefetch/prefetch_from_string_url_loader.h"
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/check_op.h"
+#include "base/memory/weak_ptr.h"
+#include "base/notreached.h"
+#include "content/browser/speculation_rules/prefetch/prefetched_mainframe_response_container.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "mojo/public/cpp/system/data_pipe.h"
+#include "mojo/public/cpp/system/simple_watcher.h"
+#include "net/base/io_buffer.h"
+#include "services/network/public/cpp/resource_request.h"
+#include "services/network/public/mojom/url_loader.mojom.h"
+#include "services/network/public/mojom/url_response_head.mojom.h"
+
+namespace content {
+
+PrefetchFromStringURLLoader::PrefetchFromStringURLLoader(
+    std::unique_ptr<PrefetchedMainframeResponseContainer> response,
+    const network::ResourceRequest& tentative_resource_request)
+    : head_(response->ReleaseHead()),
+      body_buffer_(
+          base::MakeRefCounted<net::StringIOBuffer>(response->ReleaseBody())),
+      bytes_of_raw_data_to_transfer_(body_buffer_->size()) {}
+
+PrefetchFromStringURLLoader::~PrefetchFromStringURLLoader() = default;
+
+void PrefetchFromStringURLLoader::FollowRedirect(
+    const std::vector<std::string>& removed_headers,
+    const net::HttpRequestHeaders& modified_headers,
+    const net::HttpRequestHeaders& modified_cors_exempt_headers,
+    const absl::optional<GURL>& new_url) {
+  NOTREACHED();
+}
+
+void PrefetchFromStringURLLoader::SetPriority(net::RequestPriority priority,
+                                              int32_t intra_priority_value) {
+  // Ignore: this class doesn't have a concept of priority.
+}
+
+void PrefetchFromStringURLLoader::PauseReadingBodyFromNet() {
+  // Ignore: this class doesn't read from network.
+}
+
+void PrefetchFromStringURLLoader::ResumeReadingBodyFromNet() {
+  // Ignore: this class doesn't read from network.
+}
+
+void PrefetchFromStringURLLoader::TransferRawData() {
+  while (true) {
+    DCHECK_GE(bytes_of_raw_data_to_transfer_, write_position_);
+    uint32_t write_size =
+        static_cast<uint32_t>(bytes_of_raw_data_to_transfer_ - write_position_);
+    if (write_size == 0) {
+      Finish(net::OK);
+      return;
+    }
+
+    MojoResult result =
+        producer_handle_->WriteData(body_buffer_->data() + write_position_,
+                                    &write_size, MOJO_WRITE_DATA_FLAG_NONE);
+    if (result == MOJO_RESULT_SHOULD_WAIT) {
+      handle_watcher_->ArmOrNotify();
+      return;
+    }
+
+    if (result != MOJO_RESULT_OK) {
+      Finish(net::ERR_FAILED);
+      return;
+    }
+
+    // |write_position_| should only be updated when the mojo pipe has
+    // successfully been written to.
+    write_position_ += write_size;
+  }
+}
+
+PrefetchFromStringURLLoader::RequestHandler
+PrefetchFromStringURLLoader::ServingResponseHandler() {
+  return base::BindOnce(&PrefetchFromStringURLLoader::BindAndStart,
+                        weak_ptr_factory_.GetWeakPtr());
+}
+
+void PrefetchFromStringURLLoader::BindAndStart(
+    const network::ResourceRequest& request,
+    mojo::PendingReceiver<network::mojom::URLLoader> receiver,
+    mojo::PendingRemote<network::mojom::URLLoaderClient> client) {
+  DCHECK(!receiver_.is_bound());
+  receiver_.Bind(std::move(receiver));
+  receiver_.set_disconnect_handler(
+      base::BindOnce(&PrefetchFromStringURLLoader::OnMojoDisconnect,
+                     weak_ptr_factory_.GetWeakPtr()));
+  client_.Bind(std::move(client));
+
+  mojo::ScopedDataPipeProducerHandle producer_handle;
+  mojo::ScopedDataPipeConsumerHandle consumer_handle;
+  MojoResult rv =
+      mojo::CreateDataPipe(nullptr, producer_handle, consumer_handle);
+
+  if (rv != MOJO_RESULT_OK) {
+    Finish(net::ERR_FAILED);
+    return;
+  }
+
+  client_->OnReceiveResponse(std::move(head_), std::move(consumer_handle));
+
+  producer_handle_ = std::move(producer_handle);
+
+  handle_watcher_ = std::make_unique<mojo::SimpleWatcher>(
+      FROM_HERE, mojo::SimpleWatcher::ArmingPolicy::MANUAL,
+      base::SequencedTaskRunnerHandle::Get());
+  handle_watcher_->Watch(
+      producer_handle_.get(), MOJO_HANDLE_SIGNAL_WRITABLE,
+      MOJO_WATCH_CONDITION_SATISFIED,
+      base::BindRepeating(&PrefetchFromStringURLLoader::OnHandleReady,
+                          weak_ptr_factory_.GetWeakPtr()));
+
+  TransferRawData();
+}
+
+void PrefetchFromStringURLLoader::OnHandleReady(
+    MojoResult result,
+    const mojo::HandleSignalsState& state) {
+  if (result != MOJO_RESULT_OK) {
+    Finish(net::ERR_FAILED);
+    return;
+  }
+  TransferRawData();
+}
+
+void PrefetchFromStringURLLoader::Finish(int error) {
+  client_->OnComplete(network::URLLoaderCompletionStatus(error));
+  handle_watcher_.reset();
+  producer_handle_.reset();
+  client_.reset();
+  receiver_.reset();
+  weak_ptr_factory_.InvalidateWeakPtrs();
+  MaybeDeleteSelf();
+}
+
+void PrefetchFromStringURLLoader::OnMojoDisconnect() {
+  receiver_.reset();
+  client_.reset();
+  MaybeDeleteSelf();
+}
+
+void PrefetchFromStringURLLoader::MaybeDeleteSelf() {
+  if (!receiver_.is_bound() && !client_.is_bound()) {
+    delete this;
+  }
+}
+
+}  // namespace content
diff --git a/content/browser/speculation_rules/prefetch/prefetch_from_string_url_loader.h b/content/browser/speculation_rules/prefetch/prefetch_from_string_url_loader.h
new file mode 100644
index 0000000..871cdd11
--- /dev/null
+++ b/content/browser/speculation_rules/prefetch/prefetch_from_string_url_loader.h
@@ -0,0 +1,107 @@
+// 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 CONTENT_BROWSER_SPECULATION_RULES_PREFETCH_PREFETCH_FROM_STRING_URL_LOADER_H_
+#define CONTENT_BROWSER_SPECULATION_RULES_PREFETCH_PREFETCH_FROM_STRING_URL_LOADER_H_
+
+#include <memory>
+
+#include "base/memory/weak_ptr.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "mojo/public/cpp/system/data_pipe.h"
+#include "services/network/public/cpp/resource_request.h"
+#include "services/network/public/mojom/url_loader.mojom.h"
+#include "services/network/public/mojom/url_response_head.mojom.h"
+
+namespace mojo {
+class SimpleWatcher;
+}
+
+namespace net {
+class StringIOBuffer;
+}
+
+namespace content {
+
+class PrefetchedMainframeResponseContainer;
+
+class PrefetchFromStringURLLoader : public network::mojom::URLLoader {
+ public:
+  PrefetchFromStringURLLoader(
+      std::unique_ptr<PrefetchedMainframeResponseContainer> prefetched_response,
+      const network::ResourceRequest& tenative_resource_request);
+  ~PrefetchFromStringURLLoader() override;
+
+  PrefetchFromStringURLLoader(const PrefetchFromStringURLLoader&) = delete;
+  PrefetchFromStringURLLoader& operator=(const PrefetchFromStringURLLoader&) =
+      delete;
+
+  using RequestHandler = base::OnceCallback<void(
+      const network::ResourceRequest& resource_request,
+      mojo::PendingReceiver<network::mojom::URLLoader> url_loader_receiver,
+      mojo::PendingRemote<network::mojom::URLLoaderClient> client)>;
+
+  // Called when the response should be served to the user. Returns a handler.
+  RequestHandler ServingResponseHandler();
+
+ private:
+  // network::mojom::URLLoader
+  void FollowRedirect(
+      const std::vector<std::string>& removed_headers,
+      const net::HttpRequestHeaders& modified_headers,
+      const net::HttpRequestHeaders& modified_cors_exempt_headers,
+      const absl::optional<GURL>& new_url) override;
+  void SetPriority(net::RequestPriority priority,
+                   int32_t intra_priority_value) override;
+  void PauseReadingBodyFromNet() override;
+  void ResumeReadingBodyFromNet() override;
+
+  // Binds |this| to the mojo handlers and starts the network request using
+  // |request|. After this method is called, |this| manages its own lifetime.
+  void BindAndStart(
+      const network::ResourceRequest& request,
+      mojo::PendingReceiver<network::mojom::URLLoader> url_loader_receiver,
+      mojo::PendingRemote<network::mojom::URLLoaderClient> forwarding_client);
+
+  // Called when the mojo handle's state changes, either by being ready for more
+  // data or an error.
+  void OnHandleReady(MojoResult result, const mojo::HandleSignalsState& state);
+
+  // Finishes the request with the given net error.
+  void Finish(int error);
+
+  // Sends data on the mojo pipe.
+  void TransferRawData();
+
+  // Unbinds and deletes |this|.
+  void OnMojoDisconnect();
+
+  // Deletes |this| if it is not bound to the mojo pipes.
+  void MaybeDeleteSelf();
+
+  // The response that will be sent to mojo.
+  network::mojom::URLResponseHeadPtr head_;
+  scoped_refptr<net::StringIOBuffer> body_buffer_;
+
+  // Keeps track of the position of the data transfer.
+  int write_position_ = 0;
+
+  // The length of |body_buffer_|.
+  const int bytes_of_raw_data_to_transfer_ = 0;
+
+  // Mojo plumbing.
+  mojo::Receiver<network::mojom::URLLoader> receiver_{this};
+  mojo::Remote<network::mojom::URLLoaderClient> client_;
+  mojo::ScopedDataPipeProducerHandle producer_handle_;
+  std::unique_ptr<mojo::SimpleWatcher> handle_watcher_;
+
+  base::WeakPtrFactory<PrefetchFromStringURLLoader> weak_ptr_factory_{this};
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_SPECULATION_RULES_PREFETCH_PREFETCH_FROM_STRING_URL_LOADER_H_
\ No newline at end of file
diff --git a/content/browser/speculation_rules/prefetch/prefetch_params.cc b/content/browser/speculation_rules/prefetch/prefetch_params.cc
index 8ce44725..d440fca 100644
--- a/content/browser/speculation_rules/prefetch/prefetch_params.cc
+++ b/content/browser/speculation_rules/prefetch/prefetch_params.cc
@@ -115,4 +115,9 @@
       features::kPrefetchUseContentRefactor, "html_only", false);
 }
 
+base::TimeDelta PrefetchCacheableDuration() {
+  return base::Seconds(base::GetFieldTrialParamByFeatureAsInt(
+      features::kPrefetchUseContentRefactor, "cacheable_duration", 300));
+}
+
 }  // namespace content
diff --git a/content/browser/speculation_rules/prefetch/prefetch_params.h b/content/browser/speculation_rules/prefetch/prefetch_params.h
index bbcc681..0e8ad75 100644
--- a/content/browser/speculation_rules/prefetch/prefetch_params.h
+++ b/content/browser/speculation_rules/prefetch/prefetch_params.h
@@ -59,6 +59,9 @@
 // If this is false, there is no MIME type restriction.
 bool PrefetchServiceHTMLOnly();
 
+// The maximum time a prefetched response is servable.
+CONTENT_EXPORT base::TimeDelta PrefetchCacheableDuration();
+
 }  // namespace content
 
 #endif  // CONTENT_BROWSER_SPECULATION_RULES_PREFETCH_PREFETCH_PARAMS_H_
diff --git a/content/browser/speculation_rules/prefetch/prefetch_service.cc b/content/browser/speculation_rules/prefetch/prefetch_service.cc
index edf30d0..55506e8 100644
--- a/content/browser/speculation_rules/prefetch/prefetch_service.cc
+++ b/content/browser/speculation_rules/prefetch/prefetch_service.cc
@@ -7,6 +7,7 @@
 #include <memory>
 #include <utility>
 
+#include "base/barrier_closure.h"
 #include "base/feature_list.h"
 #include "base/location.h"
 #include "base/memory/weak_ptr.h"
@@ -33,6 +34,7 @@
 #include "net/base/load_flags.h"
 #include "net/base/url_util.h"
 #include "net/cookies/canonical_cookie.h"
+#include "net/cookies/cookie_partition_key_collection.h"
 #include "net/cookies/site_for_cookies.h"
 #include "net/http/http_status_code.h"
 #include "net/http/http_util.h"
@@ -147,6 +149,17 @@
   return end - start;
 }
 
+void RecordPrefetchProxyPrefetchMainframeCookiesToCopy(
+    size_t cookie_list_size) {
+  UMA_HISTOGRAM_COUNTS_100("PrefetchProxy.Prefetch.Mainframe.CookiesToCopy",
+                           cookie_list_size);
+}
+
+void CookieSetHelper(base::RepeatingClosure closure,
+                     net::CookieAccessResult access_result) {
+  closure.Run();
+}
+
 }  // namespace
 
 // static
@@ -370,6 +383,17 @@
   prefetch_queue_.push_back(prefetch_container);
 
   Prefetch();
+
+  // Registers a cookie listener for this prefetch if it is using an isolated
+  // network context. If the cookies in the default partition associated with
+  // this URL change after this point, then the prefetched resources should not
+  // be served.
+  if (prefetch_container->GetPrefetchType()
+          .IsIsolatedNetworkContextRequired()) {
+    prefetch_container->RegisterCookieListener(
+        browser_context_->GetDefaultStoragePartition()
+            ->GetCookieManagerForBrowserProcess());
+  }
 }
 
 void PrefetchService::Prefetch() {
@@ -472,6 +496,14 @@
       owned_prefetches_.end());
   owned_prefetches_.erase(
       owned_prefetches_.find(prefetch_container->GetPrefetchContainerKey()));
+
+  auto prefetches_ready_to_serve_iter =
+      prefetches_ready_to_serve_.find(prefetch_container->GetURL());
+  if (prefetches_ready_to_serve_iter != prefetches_ready_to_serve_.end() &&
+      prefetches_ready_to_serve_iter->second->GetPrefetchContainerKey() ==
+          prefetch_container->GetPrefetchContainerKey()) {
+    prefetches_ready_to_serve_.erase(prefetches_ready_to_serve_iter);
+  }
 }
 
 void PrefetchService::StartSinglePrefetch(
@@ -710,6 +742,100 @@
   prefetch_container->SetPrefetchStatus(PrefetchStatus::kPrefetchSuccessful);
 }
 
+void PrefetchService::PrepareToServe(
+    base::WeakPtr<PrefetchContainer> prefetch_container) {
+  // Ensure |this| has this prefetch.
+  if (all_prefetches_.find(prefetch_container->GetPrefetchContainerKey()) ==
+      all_prefetches_.end())
+    return;
+
+  // If the prefetch isn't ready to be served, then stop.
+  if (prefetch_container->HaveDefaultContextCookiesChanged() ||
+      !prefetch_container->HasValidPrefetchedResponse(
+          PrefetchCacheableDuration()))
+    return;
+
+  // If the prefetch has a valid response, then it must be in
+  // |owned_prefetches_|.
+  DCHECK(
+      owned_prefetches_.find(prefetch_container->GetPrefetchContainerKey()) !=
+      owned_prefetches_.end());
+
+  // 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())
+    return;
+
+  // Move prefetch into |prefetches_ready_to_serve_|.
+  prefetches_ready_to_serve_[prefetch_container->GetURL()] = prefetch_container;
+
+  // Start the process of copying cookies from the isolated network context used
+  // to make the prefetch to the default network context.
+  CopyIsolatedCookies(prefetch_container);
+}
+
+void PrefetchService::CopyIsolatedCookies(
+    base::WeakPtr<PrefetchContainer> prefetch_container) {
+  DCHECK(prefetch_container);
+
+  if (!prefetch_container->GetNetworkContext()) {
+    // Not set in unit tests.
+    return;
+  }
+
+  // We only need to copy cookies if the prefetch used an isolated network
+  // context.
+  if (!prefetch_container->GetPrefetchType()
+           .IsIsolatedNetworkContextRequired()) {
+    return;
+  }
+
+  prefetch_container->OnIsolatedCookieCopyStart();
+  net::CookieOptions options = net::CookieOptions::MakeAllInclusive();
+  prefetch_container->GetNetworkContext()->GetCookieManager()->GetCookieList(
+      prefetch_container->GetURL(), options,
+      net::CookiePartitionKeyCollection::Todo(),
+      base::BindOnce(&PrefetchService::OnGotIsolatedCookiesForCopy,
+                     weak_method_factory_.GetWeakPtr(), prefetch_container));
+}
+
+void PrefetchService::OnGotIsolatedCookiesForCopy(
+    base::WeakPtr<PrefetchContainer> prefetch_container,
+    const net::CookieAccessResultList& cookie_list,
+    const net::CookieAccessResultList& excluded_cookies) {
+  RecordPrefetchProxyPrefetchMainframeCookiesToCopy(cookie_list.size());
+
+  if (cookie_list.empty()) {
+    prefetch_container->OnIsolatedCookieCopyComplete();
+    return;
+  }
+
+  base::RepeatingClosure barrier = base::BarrierClosure(
+      cookie_list.size(),
+      base::BindOnce(&PrefetchContainer::OnIsolatedCookieCopyComplete,
+                     prefetch_container));
+
+  net::CookieOptions options = net::CookieOptions::MakeAllInclusive();
+  for (const net::CookieWithAccessResult& cookie : cookie_list) {
+    browser_context_->GetDefaultStoragePartition()
+        ->GetCookieManagerForBrowserProcess()
+        ->SetCanonicalCookie(cookie.cookie, prefetch_container->GetURL(),
+                             options,
+                             base::BindOnce(&CookieSetHelper, barrier));
+  }
+}
+
+base::WeakPtr<PrefetchContainer> PrefetchService::GetPrefetchToServe(
+    const GURL& url) const {
+  auto prefetch_iter = prefetches_ready_to_serve_.find(url);
+
+  if (prefetch_iter == prefetches_ready_to_serve_.end())
+    return nullptr;
+
+  return prefetch_iter->second;
+}
+
 // static
 void PrefetchService::SetServiceWorkerContextForTesting(
     ServiceWorkerContext* context) {
diff --git a/content/browser/speculation_rules/prefetch/prefetch_service.h b/content/browser/speculation_rules/prefetch/prefetch_service.h
index b34bbc9..6a241ac 100644
--- a/content/browser/speculation_rules/prefetch/prefetch_service.h
+++ b/content/browser/speculation_rules/prefetch/prefetch_service.h
@@ -59,6 +59,15 @@
 
   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);
+
+  // 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
+  // called with the prefetch.
+  base::WeakPtr<PrefetchContainer> GetPrefetchToServe(const GURL& url) const;
+
   // Returns the current prefetches associated with |this|. Used to check the
   // state of the prefetches.
   // TODO(https://crbug.com/1299059): Remove this once we can get metrics
@@ -158,6 +167,14 @@
       network::mojom::URLResponseHeadPtr head,
       std::unique_ptr<std::string> body);
 
+  // Copies any cookies in the isolated network context associated with
+  // |prefetch_container| to the default network context.
+  void CopyIsolatedCookies(base::WeakPtr<PrefetchContainer> prefetch_container);
+  void OnGotIsolatedCookiesForCopy(
+      base::WeakPtr<PrefetchContainer> prefetch_container,
+      const net::CookieAccessResultList& cookie_list,
+      const net::CookieAccessResultList& excluded_cookies);
+
   raw_ptr<BrowserContext> browser_context_;
 
   // The custom proxy configurator for Prefetch Proxy. Only used on prefetches
@@ -186,6 +203,13 @@
                      std::unique_ptr<base::OneShotTimer>>>
       owned_prefetches_;
 
+  // The set of prefetches that are ready to serve. In order to be in this map,
+  // the prefetch must also be in |owned_prefetches_|, have a valid prefetched
+  // response, and have started the cookie copy process. A prefetch is added to
+  // this map when |PrepareToServe| is called on it, and once in this map, it
+  // can be returned by |GetPrefetchToServe|.
+  std::map<GURL, base::WeakPtr<PrefetchContainer>> prefetches_ready_to_serve_;
+
   SEQUENCE_CHECKER(sequence_checker_);
 
   base::WeakPtrFactory<PrefetchService> weak_method_factory_{this};
diff --git a/content/browser/speculation_rules/prefetch/prefetch_service_unittest.cc b/content/browser/speculation_rules/prefetch/prefetch_service_unittest.cc
index 577c3528f..3ba40ea 100644
--- a/content/browser/speculation_rules/prefetch/prefetch_service_unittest.cc
+++ b/content/browser/speculation_rules/prefetch/prefetch_service_unittest.cc
@@ -15,6 +15,7 @@
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/storage_partition.h"
 #include "content/public/test/fake_service_worker_context.h"
+#include "content/public/test/mock_navigation_handle.h"
 #include "content/public/test/test_renderer_host.h"
 #include "content/public/test/test_utils.h"
 #include "content/test/test_content_browser_client.h"
@@ -26,6 +27,7 @@
 #include "services/network/test/test_utils.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/common/web_preferences/web_preferences.h"
+#include "url/gurl.h"
 
 namespace content {
 namespace {
@@ -206,6 +208,19 @@
     return result;
   }
 
+  void Navigate(const GURL& url,
+                const GlobalRenderFrameHostId& previous_rfh_id) {
+    testing::NiceMock<MockNavigationHandle> handle(web_contents());
+    handle.set_url(url);
+
+    ON_CALL(handle, GetPreviousRenderFrameHostId)
+        .WillByDefault(testing::Return(previous_rfh_id));
+
+    PrefetchDocumentManager* prefetch_document_manager =
+        PrefetchDocumentManager::GetOrCreateForCurrentDocument(main_rfh());
+    prefetch_document_manager->DidStartNavigation(&handle);
+  }
+
  protected:
   FakeServiceWorkerContext service_worker_context_;
   mojo::Remote<network::mojom::CookieManager> cookie_manager_;
@@ -253,6 +268,8 @@
                       /*use_prefetch_proxy=*/true,
                       {{"X-Testing", "Hello World"}}, kHTMLBody);
 
+  Navigate(GURL("https://example.com"), main_rfh()->GetGlobalId());
+
   histogram_tester.ExpectUniqueSample(
       "PrefetchProxy.Prefetch.Mainframe.RespCode", net::HTTP_OK, 1);
   histogram_tester.ExpectUniqueSample(
@@ -275,7 +292,14 @@
   EXPECT_TRUE(prefetch_iter->second->HasPrefetchStatus());
   EXPECT_EQ(prefetch_iter->second->GetPrefetchStatus(),
             PrefetchStatus::kPrefetchSuccessful);
-  EXPECT_TRUE(prefetch_iter->second->HasPrefetchedResponse());
+  EXPECT_TRUE(prefetch_iter->second->HasValidPrefetchedResponse(
+      base::TimeDelta::Max()));
+
+  base::WeakPtr<PrefetchContainer> serveable_prefetch_container =
+      prefetch_service_->GetPrefetchToServe(prefetch_iter->second->GetURL());
+  ASSERT_TRUE(serveable_prefetch_container);
+  EXPECT_EQ(serveable_prefetch_container->GetPrefetchContainerKey(),
+            prefetch_iter->second->GetPrefetchContainerKey());
 }
 
 TEST_F(PrefetchServiceTest, NotEligibleHostnameNonUnique) {
@@ -291,6 +315,8 @@
 
   EXPECT_EQ(RequestCount(), 0);
 
+  Navigate(GURL("https://example.com"), main_rfh()->GetGlobalId());
+
   histogram_tester.ExpectTotalCount("PrefetchProxy.Prefetch.Mainframe.RespCode",
                                     0);
   histogram_tester.ExpectTotalCount("PrefetchProxy.Prefetch.Mainframe.NetError",
@@ -313,7 +339,12 @@
   EXPECT_TRUE(prefetch_iter->second->HasPrefetchStatus());
   EXPECT_EQ(prefetch_iter->second->GetPrefetchStatus(),
             PrefetchStatus::kPrefetchNotEligibleHostIsNonUnique);
-  EXPECT_FALSE(prefetch_iter->second->HasPrefetchedResponse());
+  EXPECT_FALSE(prefetch_iter->second->HasValidPrefetchedResponse(
+      base::TimeDelta::Max()));
+
+  base::WeakPtr<PrefetchContainer> serveable_prefetch_container =
+      prefetch_service_->GetPrefetchToServe(GURL("https://example.com"));
+  EXPECT_FALSE(serveable_prefetch_container);
 }
 
 namespace {
@@ -371,7 +402,8 @@
   EXPECT_TRUE(prefetch_iter->second->HasPrefetchStatus());
   EXPECT_EQ(prefetch_iter->second->GetPrefetchStatus(),
             PrefetchStatus::kPrefetchNotEligibleDataSaverEnabled);
-  EXPECT_FALSE(prefetch_iter->second->HasPrefetchedResponse());
+  EXPECT_FALSE(prefetch_iter->second->HasValidPrefetchedResponse(
+      base::TimeDelta::Max()));
 }
 
 TEST_F(PrefetchServiceTest, NotEligibleNonHttps) {
@@ -384,6 +416,8 @@
 
   EXPECT_EQ(RequestCount(), 0);
 
+  Navigate(GURL("http://example.com"), main_rfh()->GetGlobalId());
+
   histogram_tester.ExpectTotalCount("PrefetchProxy.Prefetch.Mainframe.RespCode",
                                     0);
   histogram_tester.ExpectTotalCount("PrefetchProxy.Prefetch.Mainframe.NetError",
@@ -406,7 +440,12 @@
   EXPECT_TRUE(prefetch_iter->second->HasPrefetchStatus());
   EXPECT_EQ(prefetch_iter->second->GetPrefetchStatus(),
             PrefetchStatus::kPrefetchNotEligibleSchemeIsNotHttps);
-  EXPECT_FALSE(prefetch_iter->second->HasPrefetchedResponse());
+  EXPECT_FALSE(prefetch_iter->second->HasValidPrefetchedResponse(
+      base::TimeDelta::Max()));
+
+  base::WeakPtr<PrefetchContainer> serveable_prefetch_container =
+      prefetch_service_->GetPrefetchToServe(GURL("http://example.com"));
+  EXPECT_FALSE(serveable_prefetch_container);
 }
 
 TEST_F(PrefetchServiceTest, EligibleNonHttpsNonProxiedPotentiallyTrustworthy) {
@@ -423,6 +462,8 @@
                       /*use_prefetch_proxy=*/false,
                       {{"X-Testing", "Hello World"}}, kHTMLBody);
 
+  Navigate(GURL("http://localhost"), main_rfh()->GetGlobalId());
+
   histogram_tester.ExpectUniqueSample(
       "PrefetchProxy.Prefetch.Mainframe.RespCode", net::HTTP_OK, 1);
   histogram_tester.ExpectUniqueSample(
@@ -445,7 +486,14 @@
   EXPECT_TRUE(prefetch_iter->second->HasPrefetchStatus());
   EXPECT_EQ(prefetch_iter->second->GetPrefetchStatus(),
             PrefetchStatus::kPrefetchSuccessful);
-  EXPECT_TRUE(prefetch_iter->second->HasPrefetchedResponse());
+  EXPECT_TRUE(prefetch_iter->second->HasValidPrefetchedResponse(
+      base::TimeDelta::Max()));
+
+  base::WeakPtr<PrefetchContainer> serveable_prefetch_container =
+      prefetch_service_->GetPrefetchToServe(GURL("http://localhost"));
+  ASSERT_TRUE(serveable_prefetch_container);
+  EXPECT_EQ(serveable_prefetch_container->GetPrefetchContainerKey(),
+            prefetch_iter->second->GetPrefetchContainerKey());
 }
 
 TEST_F(PrefetchServiceTest, NotEligibleServiceWorkerRegistered) {
@@ -461,6 +509,8 @@
 
   EXPECT_EQ(RequestCount(), 0);
 
+  Navigate(GURL("https://example.com"), main_rfh()->GetGlobalId());
+
   histogram_tester.ExpectTotalCount("PrefetchProxy.Prefetch.Mainframe.RespCode",
                                     0);
   histogram_tester.ExpectTotalCount("PrefetchProxy.Prefetch.Mainframe.NetError",
@@ -483,7 +533,12 @@
   EXPECT_TRUE(prefetch_iter->second->HasPrefetchStatus());
   EXPECT_EQ(prefetch_iter->second->GetPrefetchStatus(),
             PrefetchStatus::kPrefetchNotEligibleUserHasServiceWorker);
-  EXPECT_FALSE(prefetch_iter->second->HasPrefetchedResponse());
+  EXPECT_FALSE(prefetch_iter->second->HasValidPrefetchedResponse(
+      base::TimeDelta::Max()));
+
+  base::WeakPtr<PrefetchContainer> serveable_prefetch_container =
+      prefetch_service_->GetPrefetchToServe(GURL("https://example.com"));
+  EXPECT_FALSE(serveable_prefetch_container);
 }
 
 TEST_F(PrefetchServiceTest, EligibleServiceWorkerNotRegistered) {
@@ -503,6 +558,8 @@
                       /*use_prefetch_proxy=*/true,
                       {{"X-Testing", "Hello World"}}, kHTMLBody);
 
+  Navigate(GURL("https://example.com"), main_rfh()->GetGlobalId());
+
   histogram_tester.ExpectUniqueSample(
       "PrefetchProxy.Prefetch.Mainframe.RespCode", net::HTTP_OK, 1);
   histogram_tester.ExpectUniqueSample(
@@ -525,7 +582,14 @@
   EXPECT_TRUE(prefetch_iter->second->HasPrefetchStatus());
   EXPECT_EQ(prefetch_iter->second->GetPrefetchStatus(),
             PrefetchStatus::kPrefetchSuccessful);
-  EXPECT_TRUE(prefetch_iter->second->HasPrefetchedResponse());
+  EXPECT_TRUE(prefetch_iter->second->HasValidPrefetchedResponse(
+      base::TimeDelta::Max()));
+
+  base::WeakPtr<PrefetchContainer> serveable_prefetch_container =
+      prefetch_service_->GetPrefetchToServe(GURL("https://example.com"));
+  ASSERT_TRUE(serveable_prefetch_container);
+  EXPECT_EQ(serveable_prefetch_container->GetPrefetchContainerKey(),
+            prefetch_iter->second->GetPrefetchContainerKey());
 }
 
 TEST_F(PrefetchServiceTest, NotEligibleUserHasCookies) {
@@ -540,6 +604,8 @@
 
   EXPECT_EQ(RequestCount(), 0);
 
+  Navigate(GURL("https://example.com"), main_rfh()->GetGlobalId());
+
   histogram_tester.ExpectTotalCount("PrefetchProxy.Prefetch.Mainframe.RespCode",
                                     0);
   histogram_tester.ExpectTotalCount("PrefetchProxy.Prefetch.Mainframe.NetError",
@@ -562,7 +628,12 @@
   EXPECT_TRUE(prefetch_iter->second->HasPrefetchStatus());
   EXPECT_EQ(prefetch_iter->second->GetPrefetchStatus(),
             PrefetchStatus::kPrefetchNotEligibleUserHasCookies);
-  EXPECT_FALSE(prefetch_iter->second->HasPrefetchedResponse());
+  EXPECT_FALSE(prefetch_iter->second->HasValidPrefetchedResponse(
+      base::TimeDelta::Max()));
+
+  base::WeakPtr<PrefetchContainer> serveable_prefetch_container =
+      prefetch_service_->GetPrefetchToServe(GURL("https://example.com"));
+  EXPECT_FALSE(serveable_prefetch_container);
 }
 
 TEST_F(PrefetchServiceTest, EligibleUserHasCookiesForDifferentUrl) {
@@ -581,6 +652,8 @@
                       /*use_prefetch_proxy=*/true,
                       {{"X-Testing", "Hello World"}}, kHTMLBody);
 
+  Navigate(GURL("https://example.com"), main_rfh()->GetGlobalId());
+
   histogram_tester.ExpectUniqueSample(
       "PrefetchProxy.Prefetch.Mainframe.RespCode", net::HTTP_OK, 1);
   histogram_tester.ExpectUniqueSample(
@@ -603,7 +676,14 @@
   EXPECT_TRUE(prefetch_iter->second->HasPrefetchStatus());
   EXPECT_EQ(prefetch_iter->second->GetPrefetchStatus(),
             PrefetchStatus::kPrefetchSuccessful);
-  EXPECT_TRUE(prefetch_iter->second->HasPrefetchedResponse());
+  EXPECT_TRUE(prefetch_iter->second->HasValidPrefetchedResponse(
+      base::TimeDelta::Max()));
+
+  base::WeakPtr<PrefetchContainer> serveable_prefetch_container =
+      prefetch_service_->GetPrefetchToServe(GURL("https://example.com"));
+  ASSERT_TRUE(serveable_prefetch_container);
+  EXPECT_EQ(serveable_prefetch_container->GetPrefetchContainerKey(),
+            prefetch_iter->second->GetPrefetchContainerKey());
 }
 
 TEST_F(PrefetchServiceTest, EligibleSameOriginPrefetchCanHaveExistingCookies) {
@@ -622,6 +702,8 @@
                       /*use_prefetch_proxy=*/false,
                       {{"X-Testing", "Hello World"}}, kHTMLBody);
 
+  Navigate(GURL("https://example.com"), main_rfh()->GetGlobalId());
+
   histogram_tester.ExpectUniqueSample(
       "PrefetchProxy.Prefetch.Mainframe.RespCode", net::HTTP_OK, 1);
   histogram_tester.ExpectUniqueSample(
@@ -644,7 +726,14 @@
   EXPECT_TRUE(prefetch_iter->second->HasPrefetchStatus());
   EXPECT_EQ(prefetch_iter->second->GetPrefetchStatus(),
             PrefetchStatus::kPrefetchSuccessful);
-  EXPECT_TRUE(prefetch_iter->second->HasPrefetchedResponse());
+  EXPECT_TRUE(prefetch_iter->second->HasValidPrefetchedResponse(
+      base::TimeDelta::Max()));
+
+  base::WeakPtr<PrefetchContainer> serveable_prefetch_container =
+      prefetch_service_->GetPrefetchToServe(GURL("https://example.com"));
+  ASSERT_TRUE(serveable_prefetch_container);
+  EXPECT_EQ(serveable_prefetch_container->GetPrefetchContainerKey(),
+            prefetch_iter->second->GetPrefetchContainerKey());
 }
 
 TEST_F(PrefetchServiceTest, FailedNon2XXResponseCode) {
@@ -661,6 +750,8 @@
                       /*use_prefetch_proxy=*/true,
                       {{"X-Testing", "Hello World"}}, kHTMLBody);
 
+  Navigate(GURL("https://example.com"), main_rfh()->GetGlobalId());
+
   histogram_tester.ExpectUniqueSample(
       "PrefetchProxy.Prefetch.Mainframe.RespCode", net::HTTP_NOT_FOUND, 1);
   histogram_tester.ExpectUniqueSample(
@@ -683,7 +774,12 @@
   EXPECT_TRUE(prefetch_iter->second->HasPrefetchStatus());
   EXPECT_EQ(prefetch_iter->second->GetPrefetchStatus(),
             PrefetchStatus::kPrefetchFailedNon2XX);
-  EXPECT_FALSE(prefetch_iter->second->HasPrefetchedResponse());
+  EXPECT_FALSE(prefetch_iter->second->HasValidPrefetchedResponse(
+      base::TimeDelta::Max()));
+
+  base::WeakPtr<PrefetchContainer> serveable_prefetch_container =
+      prefetch_service_->GetPrefetchToServe(GURL("https://example.com"));
+  EXPECT_FALSE(serveable_prefetch_container);
 }
 
 TEST_F(PrefetchServiceTest, FailedNetError) {
@@ -700,6 +796,8 @@
                       /*use_prefetch_proxy=*/true,
                       {{"X-Testing", "Hello World"}}, kHTMLBody);
 
+  Navigate(GURL("https://example.com"), main_rfh()->GetGlobalId());
+
   histogram_tester.ExpectTotalCount("PrefetchProxy.Prefetch.Mainframe.RespCode",
                                     0);
   histogram_tester.ExpectUniqueSample(
@@ -723,7 +821,12 @@
   EXPECT_TRUE(prefetch_iter->second->HasPrefetchStatus());
   EXPECT_EQ(prefetch_iter->second->GetPrefetchStatus(),
             PrefetchStatus::kPrefetchFailedNetError);
-  EXPECT_FALSE(prefetch_iter->second->HasPrefetchedResponse());
+  EXPECT_FALSE(prefetch_iter->second->HasValidPrefetchedResponse(
+      base::TimeDelta::Max()));
+
+  base::WeakPtr<PrefetchContainer> serveable_prefetch_container =
+      prefetch_service_->GetPrefetchToServe(GURL("https://example.com"));
+  EXPECT_FALSE(serveable_prefetch_container);
 }
 
 TEST_F(PrefetchServiceTest, SuccessNonHTML) {
@@ -742,6 +845,8 @@
                       /*use_prefetch_proxy=*/true,
                       {{"X-Testing", "Hello World"}}, body);
 
+  Navigate(GURL("https://example.com"), main_rfh()->GetGlobalId());
+
   histogram_tester.ExpectUniqueSample(
       "PrefetchProxy.Prefetch.Mainframe.RespCode", net::HTTP_OK, 1);
   histogram_tester.ExpectUniqueSample(
@@ -764,7 +869,66 @@
   EXPECT_TRUE(prefetch_iter->second->HasPrefetchStatus());
   EXPECT_EQ(prefetch_iter->second->GetPrefetchStatus(),
             PrefetchStatus::kPrefetchSuccessful);
-  EXPECT_TRUE(prefetch_iter->second->HasPrefetchedResponse());
+  EXPECT_TRUE(prefetch_iter->second->HasValidPrefetchedResponse(
+      base::TimeDelta::Max()));
+
+  base::WeakPtr<PrefetchContainer> serveable_prefetch_container =
+      prefetch_service_->GetPrefetchToServe(GURL("https://example.com"));
+  ASSERT_TRUE(serveable_prefetch_container);
+  EXPECT_EQ(serveable_prefetch_container->GetPrefetchContainerKey(),
+            prefetch_iter->second->GetPrefetchContainerKey());
+}
+
+TEST_F(PrefetchServiceTest, NotServeableNavigationInDifferentRenderFrameHost) {
+  base::HistogramTester histogram_tester;
+
+  MakePrefetchOnMainFrame(GURL("https://example.com"),
+                          PrefetchType(/*use_isolated_network_context=*/true,
+                                       /*use_prefetch_proxy=*/true));
+  base::RunLoop().RunUntilIdle();
+
+  VerifyCommonRequestState(GURL("https://example.com"),
+                           /*use_prefetch_proxy=*/true);
+  MakeResponseAndWait(net::HTTP_OK, net::OK, kHTMLMimeType,
+                      /*use_prefetch_proxy=*/true,
+                      {{"X-Testing", "Hello World"}}, kHTMLBody);
+
+  // Since the navigation is occurring in a RenderFrameHost other than where the
+  // prefetch was requested from, we cannot use it.
+  GlobalRenderFrameHostId other_rfh_id(
+      main_rfh()->GetGlobalId().child_id + 1,
+      main_rfh()->GetGlobalId().frame_routing_id + 1);
+  ASSERT_NE(other_rfh_id, main_rfh()->GetGlobalId());
+  Navigate(GURL("https://example.com"), other_rfh_id);
+
+  histogram_tester.ExpectUniqueSample(
+      "PrefetchProxy.Prefetch.Mainframe.RespCode", net::HTTP_OK, 1);
+  histogram_tester.ExpectUniqueSample(
+      "PrefetchProxy.Prefetch.Mainframe.NetError", net::OK, 1);
+  histogram_tester.ExpectUniqueSample(
+      "PrefetchProxy.Prefetch.Mainframe.BodyLength", std::size(kHTMLBody), 1);
+  histogram_tester.ExpectUniqueSample(
+      "PrefetchProxy.Prefetch.Mainframe.TotalTime", kTotalTimeDuration, 1);
+  histogram_tester.ExpectUniqueSample(
+      "PrefetchProxy.Prefetch.Mainframe.ConnectTime", kConnectTimeDuration, 1);
+
+  auto all_prefetches = prefetch_service_->GetAllPrefetchesForTesting();
+
+  EXPECT_EQ(all_prefetches.size(), 1U);
+
+  auto prefetch_iter = all_prefetches.find(
+      std::make_pair(main_rfh()->GetGlobalId(), GURL("https://example.com")));
+  ASSERT_TRUE(prefetch_iter != all_prefetches.end());
+
+  EXPECT_TRUE(prefetch_iter->second->HasPrefetchStatus());
+  EXPECT_EQ(prefetch_iter->second->GetPrefetchStatus(),
+            PrefetchStatus::kPrefetchSuccessful);
+  EXPECT_TRUE(prefetch_iter->second->HasValidPrefetchedResponse(
+      base::TimeDelta::Max()));
+
+  base::WeakPtr<PrefetchContainer> serveable_prefetch_container =
+      prefetch_service_->GetPrefetchToServe(prefetch_iter->second->GetURL());
+  EXPECT_FALSE(serveable_prefetch_container);
 }
 
 class PrefetchServiceWithHTMLOnlyTest : public PrefetchServiceTest {
@@ -795,6 +959,8 @@
                       /*use_prefetch_proxy=*/true,
                       {{"X-Testing", "Hello World"}}, body);
 
+  Navigate(GURL("https://example.com"), main_rfh()->GetGlobalId());
+
   histogram_tester.ExpectUniqueSample(
       "PrefetchProxy.Prefetch.Mainframe.RespCode", net::HTTP_OK, 1);
   histogram_tester.ExpectUniqueSample(
@@ -817,7 +983,12 @@
   EXPECT_TRUE(prefetch_iter->second->HasPrefetchStatus());
   EXPECT_EQ(prefetch_iter->second->GetPrefetchStatus(),
             PrefetchStatus::kPrefetchFailedMIMENotSupported);
-  EXPECT_FALSE(prefetch_iter->second->HasPrefetchedResponse());
+  EXPECT_FALSE(prefetch_iter->second->HasValidPrefetchedResponse(
+      base::TimeDelta::Max()));
+
+  base::WeakPtr<PrefetchContainer> serveable_prefetch_container =
+      prefetch_service_->GetPrefetchToServe(GURL("https://example.com"));
+  EXPECT_FALSE(serveable_prefetch_container);
 }
 
 class PrefetchServiceAlwaysMakeDecoyRequestTest : public PrefetchServiceTest {
@@ -845,6 +1016,8 @@
                       /*use_prefetch_proxy=*/true,
                       {{"X-Testing", "Hello World"}}, kHTMLBody);
 
+  Navigate(GURL("https://example.com"), main_rfh()->GetGlobalId());
+
   auto all_prefetches = prefetch_service_->GetAllPrefetchesForTesting();
 
   EXPECT_EQ(all_prefetches.size(), 1U);
@@ -856,7 +1029,12 @@
   EXPECT_TRUE(prefetch_iter->second->HasPrefetchStatus());
   EXPECT_EQ(prefetch_iter->second->GetPrefetchStatus(),
             PrefetchStatus::kPrefetchIsPrivacyDecoy);
-  EXPECT_FALSE(prefetch_iter->second->HasPrefetchedResponse());
+  EXPECT_FALSE(prefetch_iter->second->HasValidPrefetchedResponse(
+      base::TimeDelta::Max()));
+
+  base::WeakPtr<PrefetchContainer> serveable_prefetch_container =
+      prefetch_service_->GetPrefetchToServe(GURL("https://example.com"));
+  EXPECT_FALSE(serveable_prefetch_container);
 }
 
 // TODO(https://crbug.com/1299059): Add test for incognito mode.
diff --git a/content/browser/speculation_rules/prefetch/prefetch_url_loader_interceptor.cc b/content/browser/speculation_rules/prefetch/prefetch_url_loader_interceptor.cc
new file mode 100644
index 0000000..2ce0fa7
--- /dev/null
+++ b/content/browser/speculation_rules/prefetch/prefetch_url_loader_interceptor.cc
@@ -0,0 +1,158 @@
+// 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 "content/browser/speculation_rules/prefetch/prefetch_url_loader_interceptor.h"
+
+#include <memory>
+
+#include "base/memory/weak_ptr.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/time/time.h"
+#include "content/browser/browser_context_impl.h"
+#include "content/browser/loader/navigation_loader_interceptor.h"
+#include "content/browser/loader/single_request_url_loader_factory.h"
+#include "content/browser/renderer_host/frame_tree_node.h"
+#include "content/browser/speculation_rules/prefetch/prefetch_container.h"
+#include "content/browser/speculation_rules/prefetch/prefetch_features.h"
+#include "content/browser/speculation_rules/prefetch/prefetch_from_string_url_loader.h"
+#include "content/browser/speculation_rules/prefetch/prefetch_params.h"
+#include "content/browser/speculation_rules/prefetch/prefetch_service.h"
+#include "content/browser/speculation_rules/prefetch/prefetched_mainframe_response_container.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/web_contents.h"
+#include "services/network/public/cpp/resource_request.h"
+#include "url/gurl.h"
+
+namespace content {
+namespace {
+
+BrowserContext* BrowserContextFromFrameTreeNodeId(int frame_tree_node_id) {
+  WebContents* web_content =
+      WebContents::FromFrameTreeNodeId(frame_tree_node_id);
+  if (!web_content)
+    return nullptr;
+  return web_content->GetBrowserContext();
+}
+
+PrefetchService* PrefetchServiceFromFrameTreeNodeId(int frame_tree_node_id) {
+  BrowserContext* browser_context =
+      BrowserContextFromFrameTreeNodeId(frame_tree_node_id);
+  if (!browser_context)
+    return nullptr;
+  return BrowserContextImpl::From(browser_context)->GetPrefetchService();
+}
+
+void RecordCookieWaitTime(base::TimeDelta wait_time) {
+  UMA_HISTOGRAM_CUSTOM_TIMES(
+      "PrefetchProxy.AfterClick.Mainframe.CookieWaitTime", wait_time,
+      base::TimeDelta(), base::Seconds(5), 50);
+}
+
+}  // namespace
+
+// static
+std::unique_ptr<PrefetchURLLoaderInterceptor>
+PrefetchURLLoaderInterceptor::MaybeCreateInterceptor(int frame_tree_node_id) {
+  if (!base::FeatureList::IsEnabled(features::kPrefetchUseContentRefactor))
+    return nullptr;
+
+  return std::make_unique<PrefetchURLLoaderInterceptor>(frame_tree_node_id);
+}
+
+PrefetchURLLoaderInterceptor::PrefetchURLLoaderInterceptor(
+    int frame_tree_node_id)
+    : frame_tree_node_id_(frame_tree_node_id) {}
+
+PrefetchURLLoaderInterceptor::~PrefetchURLLoaderInterceptor() = default;
+
+void PrefetchURLLoaderInterceptor::MaybeCreateLoader(
+    const network::ResourceRequest& tenative_resource_request,
+    BrowserContext* browser_context,
+    NavigationLoaderInterceptor::LoaderCallback callback,
+    NavigationLoaderInterceptor::FallbackCallback fallback_callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  DCHECK(!loader_callback_);
+  loader_callback_ = std::move(callback);
+  url_ = tenative_resource_request.url;
+
+  base::WeakPtr<PrefetchContainer> prefetch_container = GetPrefetch(url_);
+  if (!prefetch_container ||
+      !prefetch_container->HasValidPrefetchedResponse(
+          PrefetchCacheableDuration()) ||
+      prefetch_container->HaveDefaultContextCookiesChanged()) {
+    DoNotInterceptNavigation();
+    return;
+  }
+
+  // TODO(crbug.com/1299059): Check if we need to probe the origin
+
+  EnsureCookiesCopiedAndInterceptPrefetchedNavigation(tenative_resource_request,
+                                                      prefetch_container);
+}
+
+base::WeakPtr<PrefetchContainer> PrefetchURLLoaderInterceptor::GetPrefetch(
+    const GURL& url) const {
+  PrefetchService* prefetch_service =
+      PrefetchServiceFromFrameTreeNodeId(frame_tree_node_id_);
+  if (!prefetch_service)
+    return nullptr;
+
+  return prefetch_service->GetPrefetchToServe(url);
+}
+
+void PrefetchURLLoaderInterceptor::
+    EnsureCookiesCopiedAndInterceptPrefetchedNavigation(
+        const network::ResourceRequest& tenative_resource_request,
+        base::WeakPtr<PrefetchContainer> prefetch_container) {
+  if (prefetch_container &&
+      prefetch_container->IsIsolatedCookieCopyInProgress()) {
+    cookie_copy_start_time_ = base::TimeTicks::Now();
+    prefetch_container->SetOnCookieCopyCompleteCallback(base::BindOnce(
+        &PrefetchURLLoaderInterceptor::InterceptPrefetchedNavigation,
+        weak_factory_.GetWeakPtr(), tenative_resource_request,
+        prefetch_container));
+    return;
+  }
+
+  RecordCookieWaitTime(base::TimeDelta());
+
+  InterceptPrefetchedNavigation(tenative_resource_request, prefetch_container);
+}
+
+void PrefetchURLLoaderInterceptor::InterceptPrefetchedNavigation(
+    const network::ResourceRequest& tenative_resource_request,
+    base::WeakPtr<PrefetchContainer> prefetch_container) {
+  if (cookie_copy_start_time_) {
+    base::TimeDelta wait_time =
+        base::TimeTicks::Now() - cookie_copy_start_time_.value();
+    DCHECK_GT(wait_time, base::TimeDelta());
+    RecordCookieWaitTime(wait_time);
+  }
+
+  if (!prefetch_container) {
+    DoNotInterceptNavigation();
+    return;
+  }
+
+  prefetch_container->SetPrefetchStatus(PrefetchStatus::kPrefetchUsedNoProbe);
+
+  std::unique_ptr<PrefetchFromStringURLLoader> url_loader =
+      std::make_unique<PrefetchFromStringURLLoader>(
+          prefetch_container->ReleasePrefetchedResponse(),
+          tenative_resource_request);
+
+  std::move(loader_callback_)
+      .Run(base::MakeRefCounted<SingleRequestURLLoaderFactory>(
+          url_loader->ServingResponseHandler()));
+
+  // url_loader manages its own lifetime once bound to the mojo pipes.
+  url_loader.release();
+}
+
+void PrefetchURLLoaderInterceptor::DoNotInterceptNavigation() {
+  std::move(loader_callback_).Run({});
+}
+
+}  // namespace content
diff --git a/content/browser/speculation_rules/prefetch/prefetch_url_loader_interceptor.h b/content/browser/speculation_rules/prefetch/prefetch_url_loader_interceptor.h
new file mode 100644
index 0000000..554538bb
--- /dev/null
+++ b/content/browser/speculation_rules/prefetch/prefetch_url_loader_interceptor.h
@@ -0,0 +1,83 @@
+// 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 CONTENT_BROWSER_SPECULATION_RULES_PREFETCH_PREFETCH_URL_LOADER_INTERCEPTOR_H_
+#define CONTENT_BROWSER_SPECULATION_RULES_PREFETCH_PREFETCH_URL_LOADER_INTERCEPTOR_H_
+
+#include <memory>
+
+#include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
+#include "base/time/time.h"
+#include "content/browser/loader/navigation_loader_interceptor.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/global_routing_id.h"
+#include "services/network/public/cpp/resource_request.h"
+#include "url/gurl.h"
+
+namespace content {
+
+class BrowserContext;
+class PrefetchContainer;
+
+// Intercepts navigations that can use prefetched resources.
+class CONTENT_EXPORT PrefetchURLLoaderInterceptor
+    : public NavigationLoaderInterceptor {
+ public:
+  static std::unique_ptr<PrefetchURLLoaderInterceptor> MaybeCreateInterceptor(
+      int frame_tree_node_id);
+
+  explicit PrefetchURLLoaderInterceptor(int frame_tree_node_id);
+  ~PrefetchURLLoaderInterceptor() override;
+
+  PrefetchURLLoaderInterceptor(const PrefetchURLLoaderInterceptor&) = delete;
+  PrefetchURLLoaderInterceptor& operator=(const PrefetchURLLoaderInterceptor&) =
+      delete;
+
+  // NavigationLoaderInterceptor
+  void MaybeCreateLoader(
+      const network::ResourceRequest& tenative_resource_request,
+      BrowserContext* browser_context,
+      NavigationLoaderInterceptor::LoaderCallback callback,
+      NavigationLoaderInterceptor::FallbackCallback fallback_callback) override;
+
+ private:
+  // Gets the prefetch associated with |url| form |PrefetchService|.
+  virtual base::WeakPtr<PrefetchContainer> GetPrefetch(const GURL& url) const;
+
+  // Ensures that the cookies for prefetch are copied from its isolated network
+  // context to the default network context before calling
+  // |InterceptPrefetchedNavigation|.
+  void EnsureCookiesCopiedAndInterceptPrefetchedNavigation(
+      const network::ResourceRequest& tenative_resource_request,
+      base::WeakPtr<PrefetchContainer> prefetch_container);
+
+  void InterceptPrefetchedNavigation(
+      const network::ResourceRequest& tenative_resource_request,
+      base::WeakPtr<PrefetchContainer> prefetch_container);
+  void DoNotInterceptNavigation();
+
+  // The frame tree node |this| is associated with, used to retrieve
+  // |PrefetchService|.
+  const int frame_tree_node_id_;
+
+  // The URL being navigated to.
+  GURL url_;
+
+  // Called once |this| has decided whether to intercept or not intercept the
+  // navigation.
+  NavigationLoaderInterceptor::LoaderCallback loader_callback_;
+
+  // The time when we started waiting for cookies to be copied, delaying the
+  // navigation. Used to calculate total cookie wait time.
+  absl::optional<base::TimeTicks> cookie_copy_start_time_;
+
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  base::WeakPtrFactory<PrefetchURLLoaderInterceptor> weak_factory_{this};
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_SPECULATION_RULES_PREFETCH_PREFETCH_URL_LOADER_INTERCEPTOR_H_
diff --git a/content/browser/speculation_rules/prefetch/prefetch_url_loader_interceptor_unittest.cc b/content/browser/speculation_rules/prefetch/prefetch_url_loader_interceptor_unittest.cc
new file mode 100644
index 0000000..c327ca65
--- /dev/null
+++ b/content/browser/speculation_rules/prefetch/prefetch_url_loader_interceptor_unittest.cc
@@ -0,0 +1,446 @@
+// 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 "content/browser/speculation_rules/prefetch/prefetch_url_loader_interceptor.h"
+
+#include <map>
+#include <memory>
+
+#include "base/callback.h"
+#include "base/memory/weak_ptr.h"
+#include "base/test/scoped_feature_list.h"
+#include "content/browser/speculation_rules/prefetch/prefetch_container.h"
+#include "content/browser/speculation_rules/prefetch/prefetch_features.h"
+#include "content/browser/speculation_rules/prefetch/prefetch_params.h"
+#include "content/browser/speculation_rules/prefetch/prefetch_type.h"
+#include "content/browser/speculation_rules/prefetch/prefetched_mainframe_response_container.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/storage_partition.h"
+#include "content/public/test/test_renderer_host.h"
+#include "net/base/isolation_info.h"
+#include "services/network/public/mojom/cookie_manager.mojom.h"
+#include "services/network/public/mojom/network_context.mojom.h"
+#include "services/network/public/mojom/url_response_head.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "third_party/blink/public/mojom/loader/resource_load_info.mojom-shared.h"
+#include "url/gurl.h"
+
+namespace content {
+namespace {
+
+// These tests leak mojo objects (like the PrefetchFromStringURLLoader) because
+// they do not have valid mojo channels, which would normally delete the bound
+// objects on destruction. This is expected and cannot be easily fixed without
+// rewriting these as browsertests. The trade off for the speed and flexibility
+// of unittests is an intentional decision.
+#if defined(LEAK_SANITIZER)
+#define DISABLE_ASAN(x) DISABLED_##x
+#else
+#define DISABLE_ASAN(x) x
+#endif
+
+class TestPrefetchURLLoaderInterceptor : public PrefetchURLLoaderInterceptor {
+ public:
+  explicit TestPrefetchURLLoaderInterceptor(int frame_tree_node_id)
+      : PrefetchURLLoaderInterceptor(frame_tree_node_id) {}
+  ~TestPrefetchURLLoaderInterceptor() override = default;
+
+  void AddPrefetch(base::WeakPtr<PrefetchContainer> prefetch_container) {
+    prefetches_[prefetch_container->GetURL()] = prefetch_container;
+  }
+
+ private:
+  base::WeakPtr<PrefetchContainer> GetPrefetch(const GURL& url) const override {
+    const auto& iter = prefetches_.find(url);
+    if (iter == prefetches_.end())
+      return nullptr;
+    return iter->second;
+  }
+
+  std::map<GURL, base::WeakPtr<PrefetchContainer>> prefetches_;
+};
+
+class PrefetchURLLoaderInterceptorTest : public RenderViewHostTestHarness {
+ public:
+  PrefetchURLLoaderInterceptorTest()
+      : RenderViewHostTestHarness(
+            base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
+
+  void SetUp() override {
+    RenderViewHostTestHarness::SetUp();
+
+    browser_context()
+        ->GetDefaultStoragePartition()
+        ->GetNetworkContext()
+        ->GetCookieManager(cookie_manager_.BindNewPipeAndPassReceiver());
+
+    interceptor_ = std::make_unique<TestPrefetchURLLoaderInterceptor>(
+        web_contents()->GetMainFrame()->GetFrameTreeNodeId());
+  }
+
+  void TearDown() override {
+    interceptor_.release();
+
+    RenderViewHostTestHarness::TearDown();
+  }
+
+  TestPrefetchURLLoaderInterceptor* interceptor() { return interceptor_.get(); }
+
+  void WaitForCallback() {
+    if (was_intercepted_.has_value())
+      return;
+
+    base::RunLoop run_loop;
+    on_loader_callback_closure_ = run_loop.QuitClosure();
+    run_loop.Run();
+  }
+
+  void LoaderCallback(
+      scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory) {
+    was_intercepted_ = url_loader_factory != nullptr;
+    if (on_loader_callback_closure_) {
+      std::move(on_loader_callback_closure_).Run();
+    }
+  }
+
+  absl::optional<bool> was_intercepted() { return was_intercepted_; }
+
+  bool SetCookie(const GURL& url, const std::string& value) {
+    bool result = false;
+    base::RunLoop run_loop;
+
+    std::unique_ptr<net::CanonicalCookie> cookie(net::CanonicalCookie::Create(
+        url, value, base::Time::Now(), /*server_time=*/absl::nullopt,
+        /*cookie_partition_key=*/absl::nullopt));
+    EXPECT_TRUE(cookie.get());
+    EXPECT_TRUE(cookie->IsHostCookie());
+
+    net::CookieOptions options;
+    options.set_include_httponly();
+    options.set_same_site_cookie_context(
+        net::CookieOptions::SameSiteCookieContext::MakeInclusive());
+
+    cookie_manager_->SetCanonicalCookie(
+        *cookie.get(), url, options,
+        base::BindOnce(
+            [](bool* result, base::RunLoop* run_loop,
+               net::CookieAccessResult set_cookie_access_result) {
+              *result = set_cookie_access_result.status.IsInclude();
+              run_loop->Quit();
+            },
+            &result, &run_loop));
+
+    // This will run until the cookie is set.
+    run_loop.Run();
+
+    // This will run until the cookie listener gets the cookie change.
+    base::RunLoop().RunUntilIdle();
+
+    return result;
+  }
+
+  network::mojom::CookieManager* cookie_manager() {
+    return cookie_manager_.get();
+  }
+
+  const base::HistogramTester& histogram_tester() { return histogram_tester_; }
+
+ private:
+  std::unique_ptr<TestPrefetchURLLoaderInterceptor> interceptor_;
+
+  base::HistogramTester histogram_tester_;
+
+  absl::optional<bool> was_intercepted_;
+  base::OnceClosure on_loader_callback_closure_;
+
+  mojo::Remote<network::mojom::CookieManager> cookie_manager_;
+};
+
+TEST_F(PrefetchURLLoaderInterceptorTest,
+       DISABLE_ASAN(InterceptNavigationCookieCopyCompleted)) {
+  const GURL kTestUrl("https://example.com");
+
+  std::unique_ptr<PrefetchContainer> prefetch_container =
+      std::make_unique<PrefetchContainer>(
+          main_rfh()->GetGlobalId(), kTestUrl,
+          PrefetchType(/*use_isolated_network_context=*/true,
+                       /*use_prefetch_proxy=*/true),
+          nullptr);
+
+  prefetch_container->TakePrefetchedResponse(
+      std::make_unique<PrefetchedMainframeResponseContainer>(
+          net::IsolationInfo(), network::mojom::URLResponseHead::New(),
+          std::make_unique<std::string>("test body")));
+
+  // Simulate the cookie copy process starting and finishing before
+  // |MaybeCreateLoader| is called.
+  prefetch_container->OnIsolatedCookieCopyStart();
+  task_environment()->FastForwardBy(base::Milliseconds(10));
+  prefetch_container->OnIsolatedCookieCopyComplete();
+
+  interceptor()->AddPrefetch(prefetch_container->GetWeakPtr());
+
+  network::ResourceRequest request;
+  request.url = kTestUrl;
+  request.resource_type =
+      static_cast<int>(blink::mojom::ResourceType::kMainFrame);
+  request.method = "GET";
+
+  interceptor()->MaybeCreateLoader(
+      request, browser_context(),
+      base::BindOnce(&PrefetchURLLoaderInterceptorTest::LoaderCallback,
+                     base::Unretained(this)),
+      base::BindOnce([](bool) { NOTREACHED(); }));
+  WaitForCallback();
+
+  EXPECT_TRUE(was_intercepted().has_value());
+  EXPECT_TRUE(was_intercepted().value());
+
+  histogram_tester().ExpectUniqueTimeSample(
+      "PrefetchProxy.AfterClick.Mainframe.CookieWaitTime", base::TimeDelta(),
+      1);
+}
+
+TEST_F(PrefetchURLLoaderInterceptorTest,
+       DISABLE_ASAN(InterceptNavigationCookieCopyInProgress)) {
+  const GURL kTestUrl("https://example.com");
+
+  std::unique_ptr<PrefetchContainer> prefetch_container =
+      std::make_unique<PrefetchContainer>(
+          main_rfh()->GetGlobalId(), kTestUrl,
+          PrefetchType(/*use_isolated_network_context=*/true,
+                       /*use_prefetch_proxy=*/true),
+          nullptr);
+
+  prefetch_container->TakePrefetchedResponse(
+      std::make_unique<PrefetchedMainframeResponseContainer>(
+          net::IsolationInfo(), network::mojom::URLResponseHead::New(),
+          std::make_unique<std::string>("test body")));
+
+  // Simulate the cookie copy process starting, but not finishing until after
+  // |MaybeCreateLoader| is called.
+  prefetch_container->OnIsolatedCookieCopyStart();
+  task_environment()->FastForwardBy(base::Milliseconds(10));
+
+  interceptor()->AddPrefetch(prefetch_container->GetWeakPtr());
+
+  network::ResourceRequest request;
+  request.url = kTestUrl;
+  request.resource_type =
+      static_cast<int>(blink::mojom::ResourceType::kMainFrame);
+  request.method = "GET";
+
+  interceptor()->MaybeCreateLoader(
+      request, browser_context(),
+      base::BindOnce(&PrefetchURLLoaderInterceptorTest::LoaderCallback,
+                     base::Unretained(this)),
+      base::BindOnce([](bool) { NOTREACHED(); }));
+
+  // A decision on whether the navigation should be intercepted shouldn't be
+  // made until after the cookie copy process is completed.
+  EXPECT_FALSE(was_intercepted().has_value());
+
+  task_environment()->FastForwardBy(base::Milliseconds(20));
+
+  prefetch_container->OnIsolatedCookieCopyComplete();
+  WaitForCallback();
+
+  EXPECT_TRUE(was_intercepted().has_value());
+  EXPECT_TRUE(was_intercepted().value());
+
+  histogram_tester().ExpectUniqueTimeSample(
+      "PrefetchProxy.AfterClick.Mainframe.CookieWaitTime",
+      base::Milliseconds(20), 1);
+}
+
+TEST_F(PrefetchURLLoaderInterceptorTest,
+       DISABLE_ASAN(InterceptNavigationNoCookieCopyNeeded)) {
+  const GURL kTestUrl("https://example.com");
+
+  // No cookies are copied for prefetches where |use_isolated_network_context|
+  // is false (i.e. same origin prefetches).
+  std::unique_ptr<PrefetchContainer> prefetch_container =
+      std::make_unique<PrefetchContainer>(
+          main_rfh()->GetGlobalId(), kTestUrl,
+          PrefetchType(/*use_isolated_network_context=*/false,
+                       /*use_prefetch_proxy=*/false),
+          nullptr);
+
+  prefetch_container->TakePrefetchedResponse(
+      std::make_unique<PrefetchedMainframeResponseContainer>(
+          net::IsolationInfo(), network::mojom::URLResponseHead::New(),
+          std::make_unique<std::string>("test body")));
+
+  interceptor()->AddPrefetch(prefetch_container->GetWeakPtr());
+
+  network::ResourceRequest request;
+  request.url = kTestUrl;
+  request.resource_type =
+      static_cast<int>(blink::mojom::ResourceType::kMainFrame);
+  request.method = "GET";
+
+  interceptor()->MaybeCreateLoader(
+      request, browser_context(),
+      base::BindOnce(&PrefetchURLLoaderInterceptorTest::LoaderCallback,
+                     base::Unretained(this)),
+      base::BindOnce([](bool) { NOTREACHED(); }));
+  WaitForCallback();
+
+  EXPECT_TRUE(was_intercepted().has_value());
+  EXPECT_TRUE(was_intercepted().value());
+
+  histogram_tester().ExpectUniqueTimeSample(
+      "PrefetchProxy.AfterClick.Mainframe.CookieWaitTime", base::TimeDelta(),
+      1);
+}
+
+TEST_F(PrefetchURLLoaderInterceptorTest,
+       DISABLE_ASAN(DoNotInterceptNavigationNoPrefetch)) {
+  const GURL kTestUrl("https://example.com");
+
+  // With no prefetch set, the navigation shouldn't be intercepted.
+
+  network::ResourceRequest request;
+  request.url = kTestUrl;
+  request.resource_type =
+      static_cast<int>(blink::mojom::ResourceType::kMainFrame);
+  request.method = "GET";
+
+  interceptor()->MaybeCreateLoader(
+      request, browser_context(),
+      base::BindOnce(&PrefetchURLLoaderInterceptorTest::LoaderCallback,
+                     base::Unretained(this)),
+      base::BindOnce([](bool) { NOTREACHED(); }));
+  WaitForCallback();
+
+  EXPECT_TRUE(was_intercepted().has_value());
+  EXPECT_FALSE(was_intercepted().value());
+
+  histogram_tester().ExpectTotalCount(
+      "PrefetchProxy.AfterClick.Mainframe.CookieWaitTime", 0);
+}
+
+TEST_F(PrefetchURLLoaderInterceptorTest,
+       DISABLE_ASAN(DoNotInterceptNavigationNoPrefetchedResponse)) {
+  const GURL kTestUrl("https://example.com");
+
+  // Without a prefetched response, the navigation shouldn't be intercepted.
+  std::unique_ptr<PrefetchContainer> prefetch_container =
+      std::make_unique<PrefetchContainer>(
+          main_rfh()->GetGlobalId(), kTestUrl,
+          PrefetchType(/*use_isolated_network_context=*/true,
+                       /*use_prefetch_proxy=*/true),
+          nullptr);
+
+  interceptor()->AddPrefetch(prefetch_container->GetWeakPtr());
+
+  // Set up ResourceRequest
+  network::ResourceRequest request;
+  request.url = kTestUrl;
+  request.resource_type =
+      static_cast<int>(blink::mojom::ResourceType::kMainFrame);
+  request.method = "GET";
+
+  // Try to create loader
+  interceptor()->MaybeCreateLoader(
+      request, browser_context(),
+      base::BindOnce(&PrefetchURLLoaderInterceptorTest::LoaderCallback,
+                     base::Unretained(this)),
+      base::BindOnce([](bool) { NOTREACHED(); }));
+  WaitForCallback();
+
+  EXPECT_TRUE(was_intercepted().has_value());
+  EXPECT_FALSE(was_intercepted().value());
+
+  histogram_tester().ExpectTotalCount(
+      "PrefetchProxy.AfterClick.Mainframe.CookieWaitTime", 0);
+}
+
+TEST_F(PrefetchURLLoaderInterceptorTest,
+       DISABLE_ASAN(DoNotInterceptNavigationStalePrefetchedResponse)) {
+  const GURL kTestUrl("https://example.com");
+
+  std::unique_ptr<PrefetchContainer> prefetch_container =
+      std::make_unique<PrefetchContainer>(
+          main_rfh()->GetGlobalId(), kTestUrl,
+          PrefetchType(/*use_isolated_network_context=*/true,
+                       /*use_prefetch_proxy=*/true),
+          nullptr);
+
+  prefetch_container->TakePrefetchedResponse(
+      std::make_unique<PrefetchedMainframeResponseContainer>(
+          net::IsolationInfo(), network::mojom::URLResponseHead::New(),
+          std::make_unique<std::string>("test body")));
+
+  // Advance time enough so that the response is considered stale.
+  task_environment()->FastForwardBy(2 * PrefetchCacheableDuration());
+
+  interceptor()->AddPrefetch(prefetch_container->GetWeakPtr());
+
+  network::ResourceRequest request;
+  request.url = kTestUrl;
+  request.resource_type =
+      static_cast<int>(blink::mojom::ResourceType::kMainFrame);
+  request.method = "GET";
+
+  interceptor()->MaybeCreateLoader(
+      request, browser_context(),
+      base::BindOnce(&PrefetchURLLoaderInterceptorTest::LoaderCallback,
+                     base::Unretained(this)),
+      base::BindOnce([](bool) { NOTREACHED(); }));
+  WaitForCallback();
+
+  EXPECT_TRUE(was_intercepted().has_value());
+  EXPECT_FALSE(was_intercepted().value());
+
+  histogram_tester().ExpectTotalCount(
+      "PrefetchProxy.AfterClick.Mainframe.CookieWaitTime", 0);
+}
+
+TEST_F(PrefetchURLLoaderInterceptorTest,
+       DISABLE_ASAN(DoNotInterceptNavigationCookiesChanged)) {
+  const GURL kTestUrl("https://example.com");
+
+  std::unique_ptr<PrefetchContainer> prefetch_container =
+      std::make_unique<PrefetchContainer>(
+          main_rfh()->GetGlobalId(), kTestUrl,
+          PrefetchType(/*use_isolated_network_context=*/true,
+                       /*use_prefetch_proxy=*/true),
+          nullptr);
+
+  prefetch_container->TakePrefetchedResponse(
+      std::make_unique<PrefetchedMainframeResponseContainer>(
+          net::IsolationInfo(), network::mojom::URLResponseHead::New(),
+          std::make_unique<std::string>("test body")));
+
+  // Since the cookies associated with |kTestUrl| have changed, the prefetch can
+  // no longer be served.
+  prefetch_container->RegisterCookieListener(cookie_manager());
+  ASSERT_TRUE(SetCookie(kTestUrl, "test-cookie"));
+
+  interceptor()->AddPrefetch(prefetch_container->GetWeakPtr());
+
+  network::ResourceRequest request;
+  request.url = kTestUrl;
+  request.resource_type =
+      static_cast<int>(blink::mojom::ResourceType::kMainFrame);
+  request.method = "GET";
+
+  interceptor()->MaybeCreateLoader(
+      request, browser_context(),
+      base::BindOnce(&PrefetchURLLoaderInterceptorTest::LoaderCallback,
+                     base::Unretained(this)),
+      base::BindOnce([](bool) { NOTREACHED(); }));
+  WaitForCallback();
+
+  EXPECT_TRUE(was_intercepted().has_value());
+  EXPECT_FALSE(was_intercepted().value());
+
+  histogram_tester().ExpectTotalCount(
+      "PrefetchProxy.AfterClick.Mainframe.CookieWaitTime", 0);
+}
+
+}  // namespace
+}  // namespace content
diff --git a/content/browser/speculation_rules/prefetch/prefetched_mainframe_response_container.h b/content/browser/speculation_rules/prefetch/prefetched_mainframe_response_container.h
index b1b2d84d..2675ce5a 100644
--- a/content/browser/speculation_rules/prefetch/prefetched_mainframe_response_container.h
+++ b/content/browser/speculation_rules/prefetch/prefetched_mainframe_response_container.h
@@ -8,12 +8,13 @@
 #include <memory>
 #include <string>
 
+#include "content/common/content_export.h"
 #include "net/base/isolation_info.h"
 #include "services/network/public/mojom/url_response_head.mojom.h"
 
 namespace content {
 
-class PrefetchedMainframeResponseContainer {
+class CONTENT_EXPORT PrefetchedMainframeResponseContainer {
  public:
   PrefetchedMainframeResponseContainer(const net::IsolationInfo& info,
                                        network::mojom::URLResponseHeadPtr head,
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index f7b4b67..4893e261 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -1294,6 +1294,10 @@
 }
 
 RenderFrameHostImpl* WebContentsImpl::GetMainFrame() {
+  return GetPrimaryMainFrame();
+}
+
+RenderFrameHostImpl* WebContentsImpl::GetPrimaryMainFrame() {
   return primary_frame_tree_.root()->current_frame_host();
 }
 
diff --git a/content/browser/web_contents/web_contents_impl.h b/content/browser/web_contents/web_contents_impl.h
index 67c3168..4d6a72a 100644
--- a/content/browser/web_contents/web_contents_impl.h
+++ b/content/browser/web_contents/web_contents_impl.h
@@ -334,6 +334,7 @@
   const GURL& GetVisibleURL() override;
   const GURL& GetLastCommittedURL() override;
   RenderFrameHostImpl* GetMainFrame() override;
+  RenderFrameHostImpl* GetPrimaryMainFrame() override;
   PageImpl& GetPrimaryPage() override;
   RenderFrameHostImpl* GetFocusedFrame() override;
   bool IsPrerenderedFrame(int frame_tree_node_id) override;
diff --git a/content/browser/webid/federated_auth_request_impl.cc b/content/browser/webid/federated_auth_request_impl.cc
index 7e2a83b..05894c4 100644
--- a/content/browser/webid/federated_auth_request_impl.cc
+++ b/content/browser/webid/federated_auth_request_impl.cc
@@ -1003,6 +1003,21 @@
 void FederatedAuthRequestImpl::OnAccountSelected(const std::string& account_id,
                                                  bool is_sign_in,
                                                  bool should_embargo) {
+  // Check if the user has disabled the FedCM API after the FedCM UI is
+  // displayed. This ensures that requests are not wrongfully sent to IDPs when
+  // settings are changed while an existing FedCM UI is displayed. Ideally, we
+  // should enforce this check before all requests but users typically won't
+  // have time to disable the FedCM API in other types of requests.
+  if (GetApiPermissionContext()->GetApiPermissionStatus(origin_) !=
+      FederatedApiPermissionStatus::GRANTED) {
+    RecordRequestIdTokenStatus(IdTokenStatus::kDisabledInSettings,
+                               render_frame_host_->GetPageUkmSourceId());
+
+    CompleteRequest(FederatedAuthRequestResult::kErrorDisabledInSettings, "",
+                    /*should_call_callback=*/false);
+    return;
+  }
+
   // This could happen if user didn't select any accounts.
   if (account_id.empty()) {
     base::TimeTicks dismiss_dialog_time = base::TimeTicks::Now();
diff --git a/content/browser/webid/federated_auth_request_impl_unittest.cc b/content/browser/webid/federated_auth_request_impl_unittest.cc
index b9d5418..87ecbea 100644
--- a/content/browser/webid/federated_auth_request_impl_unittest.cc
+++ b/content/browser/webid/federated_auth_request_impl_unittest.cc
@@ -1756,4 +1756,60 @@
   EXPECT_EQ(RequestIdTokenStatus::kErrorCanceled, auth_helper_.status());
 }
 
+// Test that the request fails if user proceeds with the sign in workflow after
+// disabling the API while an existing accounts dialog is shown.
+TEST_F(BasicFederatedAuthRequestImplTest, ApiDisabledAfterAccountsDialogShown) {
+  base::HistogramTester histogram_tester_;
+
+  EXPECT_CALL(*mock_dialog_controller(),
+              ShowAccountsDialog(_, _, _, _, _, _, _))
+      .WillOnce(Invoke(
+          [&](content::WebContents* rp_web_contents, const GURL& idp_signin_url,
+              base::span<const content::IdentityRequestAccount> accounts,
+              const IdentityProviderMetadata& idp_metadata,
+              const ClientIdData& client_id_data, SignInMode sign_in_mode,
+              IdentityRequestDialogController::AccountSelectionCallback
+                  on_selected) {
+            // Disable FedCM API
+            test_api_permission_delegate_->permission_override_ =
+                std::make_pair(main_test_rfh()->GetLastCommittedOrigin(),
+                               ApiPermissionStatus::BLOCKED_SETTINGS);
+
+            std::move(on_selected)
+                .Run(/*account_id=*/"", /*is_sign_in=*/false,
+                     /*should_embargo=*/false);
+          }));
+
+  base::RunLoop ukm_loop;
+  ukm_recorder()->SetOnAddEntryCallback(Entry::kEntryName,
+                                        ukm_loop.QuitClosure());
+
+  MockConfiguration configuration = kConfigurationValid;
+  configuration.customized_dialog = true;
+  RequestExpectations expectations = {
+      RequestIdTokenStatus::kError,
+      FederatedAuthRequestResult::kErrorDisabledInSettings,
+      FETCH_ENDPOINT_ALL_REQUEST_ID_TOKEN & ~FetchedEndpoint::TOKEN};
+
+  RunAuthTest(kDefaultRequestParameters, expectations, configuration);
+
+  ukm_loop.Run();
+
+  histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.ShowAccountsDialog",
+                                     1);
+  histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.ContinueOnDialog", 0);
+  histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.IdTokenResponse", 0);
+  histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.TurnaroundTime", 0);
+
+  histogram_tester_.ExpectUniqueSample("Blink.FedCm.Status.RequestIdToken",
+                                       IdTokenStatus::kDisabledInSettings, 1);
+
+  ExpectTimingUKM("Timing.ShowAccountsDialog");
+  ExpectNoTimingUKM("Timing.ContinueOnDialog");
+  ExpectNoTimingUKM("Timing.IdTokenResponse");
+  ExpectNoTimingUKM("Timing.TurnaroundTime");
+
+  ExpectRequestIdTokenStatusUKM(IdTokenStatus::kDisabledInSettings);
+}
+
 }  // namespace content
diff --git a/content/browser/webid/idp_network_request_manager.cc b/content/browser/webid/idp_network_request_manager.cc
index 71bc4d2..e24c374 100644
--- a/content/browser/webid/idp_network_request_manager.cc
+++ b/content/browser/webid/idp_network_request_manager.cc
@@ -136,17 +136,22 @@
 std::unique_ptr<network::ResourceRequest> CreateCredentialedResourceRequest(
     GURL target_url,
     bool send_referrer,
-    url::Origin initiator,
+    url::Origin rp_origin,
     network::mojom::ClientSecurityStatePtr client_security_state) {
   auto resource_request = std::make_unique<network::ResourceRequest>();
   auto target_origin = url::Origin::Create(target_url);
   auto site_for_cookies = net::SiteForCookies::FromOrigin(target_origin);
   AddCsrfHeader(resource_request.get());
-  resource_request->request_initiator = initiator;
+  // We set the initiator to the target origin so that this request is
+  // considered first-party. We want to send first-party cookies because
+  // this is not a real third-party request as it is mediated by the browser,
+  // and third-party cookies will be going away with 3pc deprecation, but we
+  // still need to send cookies in these requests.
+  resource_request->request_initiator = target_origin;
   resource_request->url = target_url;
   resource_request->site_for_cookies = site_for_cookies;
   if (send_referrer) {
-    resource_request->referrer = initiator.GetURL();
+    resource_request->referrer = rp_origin.GetURL();
     // Since referrer_policy only affects redirects and we disable redirects
     // below, we don't need to set referrer_policy here.
   }
diff --git a/content/public/android/java/src/org/chromium/content_public/browser/NavigationHandle.java b/content/public/android/java/src/org/chromium/content_public/browser/NavigationHandle.java
index 5ad156ab..61929f5 100644
--- a/content/public/android/java/src/org/chromium/content_public/browser/NavigationHandle.java
+++ b/content/public/android/java/src/org/chromium/content_public/browser/NavigationHandle.java
@@ -30,7 +30,7 @@
     private boolean mHasCommitted;
     private boolean mIsDownload;
     private boolean mIsErrorPage;
-    private boolean mIsFragmentNavigation;
+    private boolean mIsPrimaryMainFrameFragmentNavigation;
     private boolean mIsValidSearchFormUrl;
     private @NetError int mErrorCode;
     private int mHttpStatusCode;
@@ -81,12 +81,13 @@
      */
     @CalledByNative
     public void didFinish(@NonNull GURL url, boolean isErrorPage, boolean hasCommitted,
-            boolean isFragmentNavigation, boolean isDownload, boolean isValidSearchFormUrl,
-            @PageTransition int transition, @NetError int errorCode, int httpStatuscode) {
+            boolean isPrimaryMainFrameFragmentNavigation, boolean isDownload,
+            boolean isValidSearchFormUrl, @PageTransition int transition, @NetError int errorCode,
+            int httpStatuscode) {
         mUrl = url;
         mIsErrorPage = isErrorPage;
         mHasCommitted = hasCommitted;
-        mIsFragmentNavigation = isFragmentNavigation;
+        mIsPrimaryMainFrameFragmentNavigation = isPrimaryMainFrameFragmentNavigation;
         mIsDownload = isDownload;
         mIsValidSearchFormUrl = isValidSearchFormUrl;
         mPageTransition = transition;
@@ -206,10 +207,10 @@
     }
 
     /**
-     * Returns true on same-document navigation with fragment change.
+     * Returns true on same-document navigation with fragment change in the primary main frame.
      */
-    public boolean isFragmentNavigation() {
-        return mIsFragmentNavigation;
+    public boolean isPrimaryMainFrameFragmentNavigation() {
+        return mIsPrimaryMainFrameFragmentNavigation;
     }
 
     /**
diff --git a/content/public/browser/BUILD.gn b/content/public/browser/BUILD.gn
index c01888b..f381b6a 100644
--- a/content/public/browser/BUILD.gn
+++ b/content/public/browser/BUILD.gn
@@ -181,6 +181,7 @@
     "font_list_async.h",
     "frame_accept_header.cc",
     "frame_accept_header.h",
+    "frame_rate_throttling.h",
     "frame_type.h",
     "generated_code_cache_settings.h",
     "global_request_id.cc",
diff --git a/content/public/browser/browsing_data_remover.h b/content/public/browser/browsing_data_remover.h
index 541e5bf1..ed436ce 100644
--- a/content/public/browser/browsing_data_remover.h
+++ b/content/public/browser/browsing_data_remover.h
@@ -107,11 +107,11 @@
   static constexpr DataType DATA_TYPE_TRUST_TOKENS = 1 << 16;
 
   // Conversion measurement API
-  // (https://github.com/WICG/conversion-measurement-api) persistent storage.
+  // (https://github.com/WICG/attribution-reporting-api) persistent storage.
   static constexpr DataType DATA_TYPE_CONVERSIONS = 1 << 17;
 
   // Aggregation Service
-  // (https://github.com/WICG/conversion-measurement-api/blob/main/AGGREGATE.md#data-processing-through-the-aggregation-service)
+  // (https://github.com/WICG/attribution-reporting-api/blob/main/AGGREGATE.md#data-processing-through-a-secure-aggregation-service)
   // persistent storage.
   static constexpr DataType DATA_TYPE_AGGREGATION_SERVICE = 1 << 18;
 
diff --git a/content/public/browser/frame_rate_throttling.h b/content/public/browser/frame_rate_throttling.h
new file mode 100644
index 0000000..e9daa6e4
--- /dev/null
+++ b/content/public/browser/frame_rate_throttling.h
@@ -0,0 +1,35 @@
+// 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 CONTENT_PUBLIC_BROWSER_FRAME_RATE_THROTTLING_H_
+#define CONTENT_PUBLIC_BROWSER_FRAME_RATE_THROTTLING_H_
+
+#include "base/time/time.h"
+#include "content/common/content_export.h"
+
+namespace content {
+
+// Signals the frame sink manager that all current and future frame sinks should
+// start sending BeginFrames at an interval that is at least as long `interval`.
+// Because there is a single viz process, which itself contains a single host
+// frame sink manager, calling this function multiple times from anywhere will
+// apply the throttling described by the latest call. For instance:
+//
+// Foo calling StartThrottlingAllFrameSinks(Hertz(15));
+//
+// followed by
+//
+// Bar calling StartThrottlingAllFrameSinks(Hertz(30));
+//
+// Will result in framerate being throttled at 30hz
+// Should be called from the UI thread.
+CONTENT_EXPORT void StartThrottlingAllFrameSinks(base::TimeDelta interval);
+
+// Stops the BeginFrame throttling enabled by `StartThrottlingAllFrameSinks()`.
+// Should be called from the UI thread.
+CONTENT_EXPORT void StopThrottlingAllFrameSinks();
+
+}  // namespace content
+
+#endif  // CONTENT_PUBLIC_BROWSER_FRAME_RATE_THROTTLING_H_
diff --git a/content/public/browser/web_contents.h b/content/public/browser/web_contents.h
index e82a5b7..c98dd2d9 100644
--- a/content/public/browser/web_contents.h
+++ b/content/public/browser/web_contents.h
@@ -357,12 +357,14 @@
   // WebContents' main frame.
   virtual const GURL& GetLastCommittedURL() = 0;
 
-  // Returns the main frame for the currently active view. Always non-null
-  // except during WebContents destruction. With MPArch, this returns the
-  // primary main frame. This WebContents may have additional main frames for
-  // prerendered pages, bfcached pages, etc.
+  // Deprecated. Use `GetPrimaryMainFrame` instead.
   virtual RenderFrameHost* GetMainFrame() = 0;
 
+  // Returns the primary main frame for the currently active page. Always
+  // non-null except during WebContents destruction. This WebContents may
+  // have additional main frames for prerendered pages, bfcached pages, etc.
+  virtual RenderFrameHost* GetPrimaryMainFrame() = 0;
+
   // Returns the current page in the primary frame tree of this WebContents.
   // If this WebContents is associated with an omnibox, usually the URL of the
   // main document of this page will be displayed in it.
diff --git a/content/public/common/content_features.cc b/content/public/common/content_features.cc
index 7ee5bfd..b282437a 100644
--- a/content/public/common/content_features.cc
+++ b/content/public/common/content_features.cc
@@ -801,8 +801,8 @@
 const base::Feature kServiceWorkerPaymentApps{"ServiceWorkerPaymentApps",
                                               base::FEATURE_ENABLED_BY_DEFAULT};
 
-// Enable the basic-card payment method from the PaymentRequest API. This flag
-// will be used to deprecate basic-card eventually: crbug.com/1209835.
+// Enable the basic-card payment method from the PaymentRequest API. This has
+// been disabled since M100 and is soon to be removed: crbug.com/1209835.
 const base::Feature kPaymentRequestBasicCard{"PaymentRequestBasicCard",
                                              base::FEATURE_DISABLED_BY_DEFAULT};
 
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index 9a54afa..50d49a64 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -2390,11 +2390,13 @@
     "../browser/sms/user_consent_handler_unittest.cc",
     "../browser/sms/webotp_service_unittest.cc",
     "../browser/speculation_rules/prefetch/prefetch_container_unittest.cc",
+    "../browser/speculation_rules/prefetch/prefetch_cookie_listener_unittest.cc",
     "../browser/speculation_rules/prefetch/prefetch_document_manager_unittest.cc",
     "../browser/speculation_rules/prefetch/prefetch_params_unittest.cc",
     "../browser/speculation_rules/prefetch/prefetch_proxy_configurator_unittest.cc",
     "../browser/speculation_rules/prefetch/prefetch_service_unittest.cc",
     "../browser/speculation_rules/prefetch/prefetch_type_unittest.cc",
+    "../browser/speculation_rules/prefetch/prefetch_url_loader_interceptor_unittest.cc",
     "../browser/speculation_rules/speculation_host_impl_unittest.cc",
     "../browser/speech/tts_controller_unittest.cc",
     "../browser/startup_task_runner_unittest.cc",
diff --git a/content/test/data/attribution_reporting/interop/README.md b/content/test/data/attribution_reporting/interop/README.md
index 695d43b..35836b4 100644
--- a/content/test/data/attribution_reporting/interop/README.md
+++ b/content/test/data/attribution_reporting/interop/README.md
@@ -3,7 +3,7 @@
 This directory contains a set of tests which ensure the attribution logic as
 implemented matches the intended behavior of the Attribution Reporting API.
 
-See https://wicg.github.io/conversion-measurement-api/ for the draft specification.
+See https://wicg.github.io/attribution-reporting-api/ for the draft specification.
 
 See //content/browser/attribution_reporting/attribution_interop_unittest.cc
 for the tests.
diff --git a/content/test/data/attribution_reporting/register_aggregatable_trigger_data_headers.html.mock-http-headers b/content/test/data/attribution_reporting/register_aggregatable_trigger_data_headers.html.mock-http-headers
index 90309f2..144393aa 100644
--- a/content/test/data/attribution_reporting/register_aggregatable_trigger_data_headers.html.mock-http-headers
+++ b/content/test/data/attribution_reporting/register_aggregatable_trigger_data_headers.html.mock-http-headers
@@ -1,3 +1,2 @@
 HTTP/1.1 200 OK
-Attribution-Reporting-Register-Aggregatable-Trigger-Data:[{"key_piece":"0x1","source_keys":["key1"],"filters":{"a":["b"]},"not_filters":{"c":[]}},{"key_piece":"0x0","source_keys":[],"not_filters":{"d":["e","f"],"g":[]}}]
-Attribution-Reporting-Register-Aggregatable-Values:{"key1": 123, "key2": 456}
+Attribution-Reporting-Register-Trigger:{"aggregatable_trigger_data":[{"key_piece":"0x1","source_keys":["key1"],"filters":{"a":["b"]},"not_filters":{"c":[]}},{"key_piece":"0x0","source_keys":[],"not_filters":{"d":["e","f"],"g":[]}}],"aggregatable_values":{"key1": 123, "key2": 456}}
diff --git a/content/test/data/attribution_reporting/register_trigger_headers.html.mock-http-headers b/content/test/data/attribution_reporting/register_trigger_headers.html.mock-http-headers
index 924ce5a..be618df 100644
--- a/content/test/data/attribution_reporting/register_trigger_headers.html.mock-http-headers
+++ b/content/test/data/attribution_reporting/register_trigger_headers.html.mock-http-headers
@@ -1,2 +1,2 @@
 HTTP/1.1 200 OK
-Attribution-Reporting-Register-Event-Trigger:[{"trigger_data": "7"}]
\ No newline at end of file
+Attribution-Reporting-Register-Trigger:{"event_trigger_data":[{"trigger_data": "7"}]}
diff --git a/content/test/data/attribution_reporting/register_trigger_headers_all_params.html.mock-http-headers b/content/test/data/attribution_reporting/register_trigger_headers_all_params.html.mock-http-headers
index 75a2c896..55f2ace9 100644
--- a/content/test/data/attribution_reporting/register_trigger_headers_all_params.html.mock-http-headers
+++ b/content/test/data/attribution_reporting/register_trigger_headers_all_params.html.mock-http-headers
@@ -1,6 +1,2 @@
 HTTP/1.1 200 OK
-Attribution-Reporting-Filters:{"w":[],"x":["y","z"]}
-Attribution-Reporting-Register-Event-Trigger:[{"trigger_data": "1","priority":"5","deduplication_key":"1024","filters":{"a":["b"]},"not_filters":{"c":[]}},{"trigger_data":"2","priority":"10","not_filters":{"d":["e","f"],"g":[]}}]
-Attribution-Reporting-Register-Aggregatable-Trigger-Data:[{"key_piece":"0x1","source_keys":["key"]}]
-Attribution-Reporting-Register-Aggregatable-Values:{"key": 123}
-Attribution-Reporting-Trigger-Debug-Key:789
+Attribution-Reporting-Register-Trigger:{"filters":{"w":[],"x":["y","z"]},"event_trigger_data":[{"trigger_data": "1","priority":"5","deduplication_key":"1024","filters":{"a":["b"]},"not_filters":{"c":[]}},{"trigger_data":"2","priority":"10","not_filters":{"d":["e","f"],"g":[]}}],"aggregatable_trigger_data":[{"key_piece":"0x1","source_keys":["key"]}],"aggregatable_values":{"key": 123},"debug_key":"789"}
diff --git a/content/test/data/attribution_reporting/register_trigger_headers_and_redirect.html.mock-http-headers b/content/test/data/attribution_reporting/register_trigger_headers_and_redirect.html.mock-http-headers
index f795869..dc7af487 100644
--- a/content/test/data/attribution_reporting/register_trigger_headers_and_redirect.html.mock-http-headers
+++ b/content/test/data/attribution_reporting/register_trigger_headers_and_redirect.html.mock-http-headers
@@ -1,3 +1,3 @@
 HTTP/1.1 301 Yo
-Attribution-Reporting-Register-Event-Trigger:[{"trigger_data": "5"}]
+Attribution-Reporting-Register-Trigger:{"event_trigger_data":[{"trigger_data": "5"}]}
 Location: /register_trigger_headers.html
\ No newline at end of file
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
index ec4cd06..38358f8 100644
--- 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
@@ -1,2 +1,2 @@
 HTTP/1.1 200 OK
-Attribution-Reporting-Register-Event-Trigger:[{"trigger_data": "1", "deduplication_key":"123"}]
\ No newline at end of file
+Attribution-Reporting-Register-Trigger:{"event_trigger_data":[{"trigger_data": "1", "deduplication_key":"123"}]}
diff --git a/content/test/data/attribution_reporting/register_trigger_headers_then_redirect_invalid.html.mock-http-headers b/content/test/data/attribution_reporting/register_trigger_headers_then_redirect_invalid.html.mock-http-headers
index 09ca1ff..af8d9bea 100644
--- a/content/test/data/attribution_reporting/register_trigger_headers_then_redirect_invalid.html.mock-http-headers
+++ b/content/test/data/attribution_reporting/register_trigger_headers_then_redirect_invalid.html.mock-http-headers
@@ -1,3 +1,3 @@
 HTTP/1.1 301 Yo
-Attribution-Reporting-Register-Event-Trigger:{[]}
+Attribution-Reporting-Register-Trigger:!
 Location: /register_trigger_headers.html
\ No newline at end of file
diff --git a/content/test/data/attribution_reporting/register_trigger_source_trigger.html.mock-http-headers b/content/test/data/attribution_reporting/register_trigger_source_trigger.html.mock-http-headers
index 65f4c0ea..10db407 100644
--- a/content/test/data/attribution_reporting/register_trigger_source_trigger.html.mock-http-headers
+++ b/content/test/data/attribution_reporting/register_trigger_source_trigger.html.mock-http-headers
@@ -1,3 +1,3 @@
 HTTP/1.1 301 Yo
-Attribution-Reporting-Register-Event-Trigger:[{"trigger_data": "5"}]
+Attribution-Reporting-Register-Trigger:{"event_trigger_data":[{"trigger_data": "5"}]}
 Location: /register_source_trigger_redirect_chain.html
\ No newline at end of file
diff --git a/content/test/data/attribution_reporting/simulator/README.md b/content/test/data/attribution_reporting/simulator/README.md
index 1730d289..fb61706 100644
--- a/content/test/data/attribution_reporting/simulator/README.md
+++ b/content/test/data/attribution_reporting/simulator/README.md
@@ -206,7 +206,7 @@
       },
 
       // The report itself. See
-      // https://github.com/WICG/conversion-measurement-api/blob/main/EVENT.md#attribution-reports
+      // https://github.com/WICG/attribution-reporting-api/blob/main/EVENT.md#attribution-reports
       // for details about its fields.
       "report": {}
     },
@@ -238,7 +238,7 @@
         ],
 
         // The report itself. See
-        // https://github.com/WICG/conversion-measurement-api/blob/main/AGGREGATE.md#aggregatable-reports
+        // https://github.com/WICG/attribution-reporting-api/blob/main/AGGREGATE.md#aggregatable-reports
         // for details about its fields.
         "report": {}
       }
@@ -291,7 +291,7 @@
       },
 
       // The report itself. See
-      // https://github.com/WICG/conversion-measurement-api/blob/main/EVENT.md#attribution-reports
+      // https://github.com/WICG/attribution-reporting-api/blob/main/EVENT.md#attribution-reports
       // for details about its fields.
       "report": {}
     }
diff --git a/content/test/data/fuzzer_corpus/attribution_simulator/all_fields.textproto b/content/test/data/fuzzer_corpus/attribution_simulator/all_fields.textproto
index 3cb451a..92172b8 100644
--- a/content/test/data/fuzzer_corpus/attribution_simulator/all_fields.textproto
+++ b/content/test/data/fuzzer_corpus/attribution_simulator/all_fields.textproto
@@ -127,6 +127,21 @@
                       }
                     }
                   }
+                  field {
+                    name: "aggregation_keys"
+                    value {
+                      object_value {
+                        field {
+                          name: "a"
+                          value {
+                            string_value {
+                              value: "0x1"
+                            }
+                          }
+                        }
+                      }
+                    }
+                  }
                 }
               }
             }
@@ -269,6 +284,90 @@
                       }
                     }
                   }
+                  field {
+                    name: "aggregatable_trigger_data"
+                    value {
+                      array_value {
+                        value {
+                          object_value {
+                            field {
+                              name: "key_piece"
+                              value {
+                                string_value {
+                                  value: "0x1"
+                                }
+                              }
+                            }
+                            field {
+                              name: "source_keys"
+                              value {
+                                array_value {
+                                  value {
+                                    string_value {
+                                      value: "a"
+                                    }
+                                  }
+                                }
+                              }
+                            }
+                            field {
+                              name: "filters"
+                              value {
+                                object_value {
+                                  field {
+                                    name: "e"
+                                    value {
+                                      array_value {
+                                        value {
+                                          string_value {
+                                            value: "f"
+                                          }
+                                        }
+                                      }
+                                    }
+                                  }
+                                }
+                              }
+                            }
+                            field {
+                              name: "not_filters"
+                              value {
+                                object_value {
+                                  field {
+                                    name: "g"
+                                    value {
+                                      array_value {
+                                        value {
+                                          string_value {
+                                            value: "h"
+                                          }
+                                        }
+                                      }
+                                    }
+                                  }
+                                }
+                              }
+                            }
+                          }
+                        }
+                      }
+                    }
+                  }
+                  field {
+                    name: "aggregatable_values"
+                    value {
+                      object_value {
+                        field {
+                          name: "a"
+                          value: {
+                            number_value {
+                              value: 123
+                            }
+                          }
+                        }
+                      }
+                    }
+                  }
                 }
               }
             }
diff --git a/content/web_test/renderer/web_ax_object_proxy.cc b/content/web_test/renderer/web_ax_object_proxy.cc
index edf6658..85af3c6 100644
--- a/content/web_test/renderer/web_ax_object_proxy.cc
+++ b/content/web_test/renderer/web_ax_object_proxy.cc
@@ -256,11 +256,7 @@
                                    WebAXObjectProxy::Factory* factory)
     : accessibility_object_(object), factory_(factory) {}
 
-WebAXObjectProxy::~WebAXObjectProxy() {
-  // v8::Persistent will leak on destroy, due to the default
-  // NonCopyablePersistentTraits (it claims this may change in the future).
-  notification_callback_.Reset();
-}
+WebAXObjectProxy::~WebAXObjectProxy() = default;
 
 void WebAXObjectProxy::UpdateLayout() {
   blink::WebAXObject::UpdateLayout(accessibility_object_.GetDocument());
diff --git a/content/web_test/renderer/web_ax_object_proxy.h b/content/web_test/renderer/web_ax_object_proxy.h
index 963c1f3e..2560ff2 100644
--- a/content/web_test/renderer/web_ax_object_proxy.h
+++ b/content/web_test/renderer/web_ax_object_proxy.h
@@ -241,7 +241,7 @@
   blink::WebAXObject accessibility_object_;
   Factory* factory_;
 
-  v8::Persistent<v8::Function> notification_callback_;
+  v8::Global<v8::Function> notification_callback_;
 };
 
 class RootWebAXObjectProxy : public WebAXObjectProxy {
@@ -264,13 +264,7 @@
   v8::Local<v8::Object> GetOrCreate(const blink::WebAXObject&) override;
 
  private:
-  // Defines the Persistents as copyable because v8 does not support moving
-  // in non-copyable (default) traits either.
-  using CopyablePersistentObject =
-      v8::Persistent<v8::Object, v8::CopyablePersistentTraits<v8::Object>>;
-  // Because the v8::Persistent in this container uses CopyablePersistentObject
-  // traits, it will not leak on destruction.
-  std::vector<CopyablePersistentObject> elements_;
+  std::vector<v8::Global<v8::Object>> elements_;
 };
 
 }  // namespace content
diff --git a/device/bluetooth/floss/floss_manager_client.cc b/device/bluetooth/floss/floss_manager_client.cc
index 328bce5c..91519c7 100644
--- a/device/bluetooth/floss/floss_manager_client.cc
+++ b/device/bluetooth/floss/floss_manager_client.cc
@@ -17,6 +17,7 @@
 #include "base/logging.h"
 #include "base/observer_list.h"
 #include "base/strings/stringprintf.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "dbus/bus.h"
 #include "dbus/exported_object.h"
 #include "dbus/message.h"
@@ -63,6 +64,35 @@
 
 }  // namespace
 
+FlossManagerClient::PoweredCallback::PoweredCallback(ResponseCallback<Void> cb,
+                                                     int timeout_ms) {
+  cb_ = std::move(cb);
+  timeout_ms_ = timeout_ms;
+}
+
+FlossManagerClient::PoweredCallback::~PoweredCallback() = default;
+
+// static
+std::unique_ptr<FlossManagerClient::PoweredCallback>
+FlossManagerClient::PoweredCallback::CreateWithTimeout(
+    ResponseCallback<Void> cb,
+    int timeout_ms) {
+  std::unique_ptr<FlossManagerClient::PoweredCallback> self =
+      std::make_unique<FlossManagerClient::PoweredCallback>(std::move(cb),
+                                                            timeout_ms);
+  self->PostDelayedError();
+
+  return self;
+}
+
+void FlossManagerClient::PoweredCallback::PostDelayedError() {
+  base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+      FROM_HERE,
+      base::BindOnce(&PoweredCallback::RunError,
+                     weak_ptr_factory_.GetWeakPtr()),
+      base::Milliseconds(timeout_ms_));
+}
+
 // static
 const char FlossManagerClient::kExportedCallbacksPath[] =
     "/org/chromium/bluetooth/managerclient";
@@ -140,6 +170,10 @@
 void FlossManagerClient::SetAdapterEnabled(int adapter,
                                            bool enabled,
                                            ResponseCallback<Void> callback) {
+  if (adapter != GetDefaultAdapter()) {
+    return;
+  }
+
   dbus::ObjectProxy* object_proxy =
       bus_->GetObjectProxy(service_name_, dbus::ObjectPath(kManagerObject));
   if (!object_proxy) {
@@ -155,10 +189,22 @@
   dbus::MessageWriter writer(&method_call);
   writer.AppendInt32(adapter);
 
+  powered_callback_ =
+      PoweredCallback::CreateWithTimeout(std::move(callback), kDBusTimeoutMs);
+
   object_proxy->CallMethodWithErrorResponse(
       &method_call, kDBusTimeoutMs,
-      base::BindOnce(&FlossManagerClient::DefaultResponseWithCallback<Void>,
-                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
+      base::BindOnce(&FlossManagerClient::OnSetAdapterEnabled,
+                     weak_ptr_factory_.GetWeakPtr()));
+}
+
+void FlossManagerClient::OnSetAdapterEnabled(
+    dbus::Response* response,
+    dbus::ErrorResponse* error_response) {
+  // Only handle error cases since non-error called in OnHciEnabledChange
+  if (powered_callback_ && (!response || error_response)) {
+    powered_callback_.release()->RunError();
+  }
 }
 
 // Register manager client against manager.
@@ -360,6 +406,10 @@
     return;
   }
 
+  if (adapter == GetDefaultAdapter() && powered_callback_) {
+    powered_callback_.release()->RunNoError();
+  }
+
   adapter_to_powered_[adapter] = enabled;
 
   for (auto& observer : observers_) {
diff --git a/device/bluetooth/floss/floss_manager_client.h b/device/bluetooth/floss/floss_manager_client.h
index 69a00dd..5d695bb 100644
--- a/device/bluetooth/floss/floss_manager_client.h
+++ b/device/bluetooth/floss/floss_manager_client.h
@@ -50,6 +50,33 @@
     virtual void AdapterEnabledChanged(int adapter, bool enabled) {}
   };
 
+  class PoweredCallback {
+   public:
+    explicit PoweredCallback(ResponseCallback<Void> cb, int timeout_ms);
+    ~PoweredCallback();
+
+    static std::unique_ptr<FlossManagerClient::PoweredCallback>
+    CreateWithTimeout(ResponseCallback<Void> cb, int timeout_ms);
+    void RunError() {
+      if (cb_) {
+        std::move(cb_).Run(
+            /*ret=*/absl::nullopt, Error(kErrorNoResponse, std::string()));
+      }
+    };
+    void RunNoError() {
+      if (cb_) {
+        std::move(cb_).Run(/*ret=*/absl::nullopt, /*err=*/absl::nullopt);
+      }
+    };
+
+   private:
+    void PostDelayedError();
+
+    ResponseCallback<Void> cb_;
+    int timeout_ms_;
+    base::WeakPtrFactory<PoweredCallback> weak_ptr_factory_{this};
+  };
+
   // Convert adapter number to object path.
   static dbus::ObjectPath GenerateAdapterPath(int adapter);
 
@@ -155,12 +182,19 @@
   base::ObserverList<Observer> observers_;
 
  private:
+  // Handle response to SetAdapterEnabled
+  void OnSetAdapterEnabled(dbus::Response* response,
+                           dbus::ErrorResponse* error_response);
+
   // Object path for exported callbacks registered against manager interface.
   static const char kExportedCallbacksPath[];
 
   // Floss Manager registers ObjectManager at this path.
   static const char kObjectManagerPath[];
 
+  // Powered callback called only when adapter actually powers on
+  std::unique_ptr<PoweredCallback> powered_callback_;
+
   base::WeakPtrFactory<FlossManagerClient> weak_ptr_factory_{this};
 };
 
diff --git a/fuchsia/base/BUILD.gn b/fuchsia/base/BUILD.gn
index a665f3c..a50ac34 100644
--- a/fuchsia/base/BUILD.gn
+++ b/fuchsia/base/BUILD.gn
@@ -14,7 +14,7 @@
     "./test/*",
     "//chromecast/internal/*",
     "//fuchsia/engine/*",
-    "//fuchsia/runners/*",
+    "//fuchsia_web/runners/*",
   ]
   sources = [
     "fuchsia_dir_scheme.cc",
@@ -44,7 +44,7 @@
     "./test/*",
     "//chromecast/internal/*",
     "//fuchsia/engine/*",
-    "//fuchsia/runners/*",
+    "//fuchsia_web/runners/*",
   ]
   sources = [
     "agent_impl.cc",
@@ -84,7 +84,7 @@
   testonly = true
   visibility = [
     "//fuchsia/engine/*",
-    "//fuchsia/runners/*",
+    "//fuchsia_web/runners/*",
   ]
   sources = [ "run_all_integration_tests.cc" ]
   deps = [ "//base/test:test_support" ]
diff --git a/fuchsia/engine/BUILD.gn b/fuchsia/engine/BUILD.gn
index 4fd4f5e..54fcf93 100644
--- a/fuchsia/engine/BUILD.gn
+++ b/fuchsia/engine/BUILD.gn
@@ -337,7 +337,7 @@
   # TODO(crbug.com/1022542): SwiftShader is used only in tests. It should
   # not be included in the WebEngine package.
   # Whenever this list is updated the exclusions in the cast_runner package
-  # should be updated as well (see fuchsia/runners/BUILD.gn).
+  # should be updated as well (see fuchsia_web/runners/BUILD.gn).
   "lib/libvk_swiftshader.so",
   "vk_swiftshader_icd.json",
 ]
@@ -443,7 +443,7 @@
   ]
   visibility = [
     ":*",
-    "//fuchsia/runners:*",
+    "//fuchsia_web/runners:*",
   ]
 }
 
diff --git a/fuchsia_web/BUILD.gn b/fuchsia_web/BUILD.gn
index b5cb091..81a6767 100644
--- a/fuchsia_web/BUILD.gn
+++ b/fuchsia_web/BUILD.gn
@@ -12,8 +12,8 @@
 # they are moved.
 group("gn_all") {
   deps = [
+    "runners:cast_runner",
+    "runners:web_runner",
     "//fuchsia/engine:web_engine",
-    "//fuchsia/runners:cast_runner",
-    "//fuchsia/runners:web_runner",
   ]
 }
diff --git a/fuchsia/runners/BUILD.gn b/fuchsia_web/runners/BUILD.gn
similarity index 100%
rename from fuchsia/runners/BUILD.gn
rename to fuchsia_web/runners/BUILD.gn
diff --git a/fuchsia/runners/DEPS b/fuchsia_web/runners/DEPS
similarity index 100%
rename from fuchsia/runners/DEPS
rename to fuchsia_web/runners/DEPS
diff --git a/fuchsia/runners/cast/DEPS b/fuchsia_web/runners/cast/DEPS
similarity index 100%
rename from fuchsia/runners/cast/DEPS
rename to fuchsia_web/runners/cast/DEPS
diff --git a/fuchsia/runners/cast/OWNERS b/fuchsia_web/runners/cast/OWNERS
similarity index 100%
rename from fuchsia/runners/cast/OWNERS
rename to fuchsia_web/runners/cast/OWNERS
diff --git a/fuchsia/runners/cast/api_bindings_client.cc b/fuchsia_web/runners/cast/api_bindings_client.cc
similarity index 98%
rename from fuchsia/runners/cast/api_bindings_client.cc
rename to fuchsia_web/runners/cast/api_bindings_client.cc
index 2a436a46..699d36147 100644
--- a/fuchsia/runners/cast/api_bindings_client.cc
+++ b/fuchsia_web/runners/cast/api_bindings_client.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 "fuchsia/runners/cast/api_bindings_client.h"
+#include "fuchsia_web/runners/cast/api_bindings_client.h"
 
 #include <utility>
 
diff --git a/fuchsia/runners/cast/api_bindings_client.h b/fuchsia_web/runners/cast/api_bindings_client.h
similarity index 91%
rename from fuchsia/runners/cast/api_bindings_client.h
rename to fuchsia_web/runners/cast/api_bindings_client.h
index 249b899..1a1d48b 100644
--- a/fuchsia/runners/cast/api_bindings_client.h
+++ b/fuchsia_web/runners/cast/api_bindings_client.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 FUCHSIA_RUNNERS_CAST_API_BINDINGS_CLIENT_H_
-#define FUCHSIA_RUNNERS_CAST_API_BINDINGS_CLIENT_H_
+#ifndef FUCHSIA_WEB_RUNNERS_CAST_API_BINDINGS_CLIENT_H_
+#define FUCHSIA_WEB_RUNNERS_CAST_API_BINDINGS_CLIENT_H_
 
 #include <fuchsia/web/cpp/fidl.h>
 #include <vector>
 
 #include "components/cast/message_port/message_port.h"
 #include "components/cast/named_message_port_connector/named_message_port_connector.h"
-#include "fuchsia/runners/cast/fidl/fidl/chromium/cast/cpp/fidl.h"
+#include "fuchsia_web/runners/cast/fidl/fidl/chromium/cast/cpp/fidl.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 // Injects scripts received from the ApiBindings service, and provides connected
@@ -63,4 +63,4 @@
   base::OnceClosure on_initialization_complete_;
 };
 
-#endif  // FUCHSIA_RUNNERS_CAST_API_BINDINGS_CLIENT_H_
+#endif  // FUCHSIA_WEB_RUNNERS_CAST_API_BINDINGS_CLIENT_H_
diff --git a/fuchsia/runners/cast/api_bindings_client_browsertest.cc b/fuchsia_web/runners/cast/api_bindings_client_browsertest.cc
similarity index 94%
rename from fuchsia/runners/cast/api_bindings_client_browsertest.cc
rename to fuchsia_web/runners/cast/api_bindings_client_browsertest.cc
index b14f1f41..c43511c 100644
--- a/fuchsia/runners/cast/api_bindings_client_browsertest.cc
+++ b/fuchsia_web/runners/cast/api_bindings_client_browsertest.cc
@@ -18,10 +18,10 @@
 #include "fuchsia/base/test/test_navigation_listener.h"
 #include "fuchsia/engine/test/frame_for_test.h"
 #include "fuchsia/engine/test/web_engine_browser_test.h"
-#include "fuchsia/runners/cast/api_bindings_client.h"
-#include "fuchsia/runners/cast/create_web_message.h"
-#include "fuchsia/runners/cast/fake_api_bindings.h"
-#include "fuchsia/runners/cast/named_message_port_connector_fuchsia.h"
+#include "fuchsia_web/runners/cast/api_bindings_client.h"
+#include "fuchsia_web/runners/cast/create_web_message.h"
+#include "fuchsia_web/runners/cast/fake_api_bindings.h"
+#include "fuchsia_web/runners/cast/named_message_port_connector_fuchsia.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace {
@@ -29,7 +29,7 @@
 class ApiBindingsClientTest : public cr_fuchsia::WebEngineBrowserTest {
  public:
   ApiBindingsClientTest() : api_service_binding_(&api_service_) {
-    set_test_server_root(base::FilePath("fuchsia/runners/cast/testdata"));
+    set_test_server_root(base::FilePath("fuchsia_web/runners/cast/testdata"));
   }
 
   ~ApiBindingsClientTest() override = default;
diff --git a/fuchsia/runners/cast/application_controller_impl.cc b/fuchsia_web/runners/cast/application_controller_impl.cc
similarity index 95%
rename from fuchsia/runners/cast/application_controller_impl.cc
rename to fuchsia_web/runners/cast/application_controller_impl.cc
index 414e2ff..f38dcc5 100644
--- a/fuchsia/runners/cast/application_controller_impl.cc
+++ b/fuchsia_web/runners/cast/application_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 "fuchsia/runners/cast/application_controller_impl.h"
+#include "fuchsia_web/runners/cast/application_controller_impl.h"
 
 #include <fuchsia/diagnostics/cpp/fidl.h>
 
diff --git a/fuchsia/runners/cast/application_controller_impl.h b/fuchsia_web/runners/cast/application_controller_impl.h
similarity index 82%
rename from fuchsia/runners/cast/application_controller_impl.h
rename to fuchsia_web/runners/cast/application_controller_impl.h
index d4d5de98..2ddd4bbc 100644
--- a/fuchsia/runners/cast/application_controller_impl.h
+++ b/fuchsia_web/runners/cast/application_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 FUCHSIA_RUNNERS_CAST_APPLICATION_CONTROLLER_IMPL_H_
-#define FUCHSIA_RUNNERS_CAST_APPLICATION_CONTROLLER_IMPL_H_
+#ifndef FUCHSIA_WEB_RUNNERS_CAST_APPLICATION_CONTROLLER_IMPL_H_
+#define FUCHSIA_WEB_RUNNERS_CAST_APPLICATION_CONTROLLER_IMPL_H_
 
 #include <fuchsia/diagnostics/cpp/fidl.h>
 #include <fuchsia/media/sessions2/cpp/fidl.h>
@@ -11,7 +11,7 @@
 #include <lib/fidl/cpp/binding.h>
 #include <lib/fidl/cpp/interface_request.h>
 
-#include "fuchsia/runners/cast/fidl/fidl/chromium/cast/cpp/fidl.h"
+#include "fuchsia_web/runners/cast/fidl/fidl/chromium/cast/cpp/fidl.h"
 
 class ApplicationControllerImpl final
     : public chromium::cast::ApplicationController {
@@ -39,4 +39,4 @@
   fuchsia::web::Frame* const frame_;
 };
 
-#endif  // FUCHSIA_RUNNERS_CAST_APPLICATION_CONTROLLER_IMPL_H_
+#endif  // FUCHSIA_WEB_RUNNERS_CAST_APPLICATION_CONTROLLER_IMPL_H_
diff --git a/fuchsia/runners/cast/application_controller_impl_unittest.cc b/fuchsia_web/runners/cast/application_controller_impl_unittest.cc
similarity index 96%
rename from fuchsia/runners/cast/application_controller_impl_unittest.cc
rename to fuchsia_web/runners/cast/application_controller_impl_unittest.cc
index c40d473..06280026 100644
--- a/fuchsia/runners/cast/application_controller_impl_unittest.cc
+++ b/fuchsia_web/runners/cast/application_controller_impl_unittest.cc
@@ -12,8 +12,8 @@
 #include "base/test/task_environment.h"
 #include "base/test/test_future.h"
 #include "fuchsia/base/test/fit_adapter.h"
-#include "fuchsia/runners/cast/fidl/fidl/chromium/cast/cpp/fidl.h"
-#include "fuchsia/runners/cast/application_controller_impl.h"
+#include "fuchsia_web/runners/cast/application_controller_impl.h"
+#include "fuchsia_web/runners/cast/fidl/fidl/chromium/cast/cpp/fidl.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
diff --git a/fuchsia/runners/cast/cast_component.cc b/fuchsia_web/runners/cast/cast_component.cc
similarity index 96%
rename from fuchsia/runners/cast/cast_component.cc
rename to fuchsia_web/runners/cast/cast_component.cc
index d79c10c..985cb75e 100644
--- a/fuchsia/runners/cast/cast_component.cc
+++ b/fuchsia_web/runners/cast/cast_component.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 "fuchsia/runners/cast/cast_component.h"
+#include "fuchsia_web/runners/cast/cast_component.h"
 
 #include <lib/fidl/cpp/binding.h>
 #include <lib/ui/scenic/cpp/view_ref_pair.h>
@@ -19,11 +19,11 @@
 #include "components/cast/message_port/fuchsia/message_port_fuchsia.h"
 #include "components/cast/message_port/platform_message_port.h"
 #include "fuchsia/base/agent_manager.h"
-#include "fuchsia/runners/cast/cast_runner.h"
-#include "fuchsia/runners/cast/cast_streaming.h"
-#include "fuchsia/runners/cast/create_web_message.h"
-#include "fuchsia/runners/cast/fidl/fidl/chromium/cast/cpp/fidl.h"
-#include "fuchsia/runners/common/web_component.h"
+#include "fuchsia_web/runners/cast/cast_runner.h"
+#include "fuchsia_web/runners/cast/cast_streaming.h"
+#include "fuchsia_web/runners/cast/create_web_message.h"
+#include "fuchsia_web/runners/cast/fidl/fidl/chromium/cast/cpp/fidl.h"
+#include "fuchsia_web/runners/common/web_component.h"
 
 namespace {
 
diff --git a/fuchsia/runners/cast/cast_component.h b/fuchsia_web/runners/cast/cast_component.h
similarity index 91%
rename from fuchsia/runners/cast/cast_component.h
rename to fuchsia_web/runners/cast/cast_component.h
index 53056c1..cffb3a89 100644
--- a/fuchsia/runners/cast/cast_component.h
+++ b/fuchsia_web/runners/cast/cast_component.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 FUCHSIA_RUNNERS_CAST_CAST_COMPONENT_H_
-#define FUCHSIA_RUNNERS_CAST_CAST_COMPONENT_H_
+#ifndef FUCHSIA_WEB_RUNNERS_CAST_CAST_COMPONENT_H_
+#define FUCHSIA_WEB_RUNNERS_CAST_CAST_COMPONENT_H_
 
 #include <fuchsia/camera3/cpp/fidl.h>
 #include <fuchsia/legacymetrics/cpp/fidl.h>
@@ -20,11 +20,11 @@
 #include "base/gtest_prod_util.h"
 #include "base/message_loop/message_pump_for_io.h"
 #include "base/message_loop/message_pump_fuchsia.h"
-#include "fuchsia/runners/cast/fidl/fidl/chromium/cast/cpp/fidl.h"
-#include "fuchsia/runners/cast/api_bindings_client.h"
-#include "fuchsia/runners/cast/application_controller_impl.h"
-#include "fuchsia/runners/cast/named_message_port_connector_fuchsia.h"
-#include "fuchsia/runners/common/web_component.h"
+#include "fuchsia_web/runners/cast/api_bindings_client.h"
+#include "fuchsia_web/runners/cast/application_controller_impl.h"
+#include "fuchsia_web/runners/cast/fidl/fidl/chromium/cast/cpp/fidl.h"
+#include "fuchsia_web/runners/cast/named_message_port_connector_fuchsia.h"
+#include "fuchsia_web/runners/common/web_component.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace cr_fuchsia {
@@ -143,4 +143,4 @@
   base::MessagePumpForIO::ZxHandleWatchController headless_disconnect_watch_;
 };
 
-#endif  // FUCHSIA_RUNNERS_CAST_CAST_COMPONENT_H_
+#endif  // FUCHSIA_WEB_RUNNERS_CAST_CAST_COMPONENT_H_
diff --git a/fuchsia/runners/cast/cast_runner.cc b/fuchsia_web/runners/cast/cast_runner.cc
similarity index 99%
rename from fuchsia/runners/cast/cast_runner.cc
rename to fuchsia_web/runners/cast/cast_runner.cc
index 4edfedc..836d0b6 100644
--- a/fuchsia/runners/cast/cast_runner.cc
+++ b/fuchsia_web/runners/cast/cast_runner.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 "fuchsia/runners/cast/cast_runner.h"
+#include "fuchsia_web/runners/cast/cast_runner.h"
 
 #include <fuchsia/sys/cpp/fidl.h>
 #include <fuchsia/web/cpp/fidl.h>
@@ -28,9 +28,9 @@
 #include "components/cast/common/constants.h"
 #include "components/fuchsia_component_support/config_reader.h"
 #include "fuchsia/base/agent_manager.h"
-#include "fuchsia/runners/cast/cast_streaming.h"
-#include "fuchsia/runners/cast/pending_cast_component.h"
-#include "fuchsia/runners/common/web_content_runner.h"
+#include "fuchsia_web/runners/cast/cast_streaming.h"
+#include "fuchsia_web/runners/cast/pending_cast_component.h"
+#include "fuchsia_web/runners/common/web_content_runner.h"
 #include "media/base/media_switches.h"
 #include "url/gurl.h"
 
diff --git a/fuchsia/runners/cast/cast_runner.cml b/fuchsia_web/runners/cast/cast_runner.cml
similarity index 100%
rename from fuchsia/runners/cast/cast_runner.cml
rename to fuchsia_web/runners/cast/cast_runner.cml
diff --git a/fuchsia/runners/cast/cast_runner.cmx b/fuchsia_web/runners/cast/cast_runner.cmx
similarity index 100%
rename from fuchsia/runners/cast/cast_runner.cmx
rename to fuchsia_web/runners/cast/cast_runner.cmx
diff --git a/fuchsia/runners/cast/cast_runner.h b/fuchsia_web/runners/cast/cast_runner.h
similarity index 95%
rename from fuchsia/runners/cast/cast_runner.h
rename to fuchsia_web/runners/cast/cast_runner.h
index 49db9e0..d6946c41 100644
--- a/fuchsia/runners/cast/cast_runner.h
+++ b/fuchsia_web/runners/cast/cast_runner.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 FUCHSIA_RUNNERS_CAST_CAST_RUNNER_H_
-#define FUCHSIA_RUNNERS_CAST_CAST_RUNNER_H_
+#ifndef FUCHSIA_WEB_RUNNERS_CAST_CAST_RUNNER_H_
+#define FUCHSIA_WEB_RUNNERS_CAST_CAST_RUNNER_H_
 
 #include <fuchsia/camera3/cpp/fidl.h>
 #include <fuchsia/legacymetrics/cpp/fidl.h>
@@ -18,10 +18,10 @@
 #include "base/containers/flat_set.h"
 #include "base/containers/unique_ptr_adapters.h"
 #include "base/fuchsia/startup_context.h"
-#include "fuchsia/runners/cast/cast_component.h"
-#include "fuchsia/runners/cast/fidl/fidl/chromium/cast/cpp/fidl.h"
-#include "fuchsia/runners/cast/pending_cast_component.h"
-#include "fuchsia/runners/common/web_content_runner.h"
+#include "fuchsia_web/runners/cast/cast_component.h"
+#include "fuchsia_web/runners/cast/fidl/fidl/chromium/cast/cpp/fidl.h"
+#include "fuchsia_web/runners/cast/pending_cast_component.h"
+#include "fuchsia_web/runners/common/web_content_runner.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace base {
@@ -193,4 +193,4 @@
   bool was_cache_sentinel_created_ = false;
 };
 
-#endif  // FUCHSIA_RUNNERS_CAST_CAST_RUNNER_H_
+#endif  // FUCHSIA_WEB_RUNNERS_CAST_CAST_RUNNER_H_
diff --git a/fuchsia/runners/cast/cast_runner_integration_test.cc b/fuchsia_web/runners/cast/cast_runner_integration_test.cc
similarity index 98%
rename from fuchsia/runners/cast/cast_runner_integration_test.cc
rename to fuchsia_web/runners/cast/cast_runner_integration_test.cc
index 6b7fa8b..9fbd02a 100644
--- a/fuchsia/runners/cast/cast_runner_integration_test.cc
+++ b/fuchsia_web/runners/cast/cast_runner_integration_test.cc
@@ -49,11 +49,11 @@
 #include "fuchsia/base/test/frame_test_util.h"
 #include "fuchsia/base/test/test_devtools_list_fetcher.h"
 #include "fuchsia/base/test/url_request_rewrite_test_util.h"
-#include "fuchsia/runners/cast/cast_runner.h"
-#include "fuchsia/runners/cast/cast_runner_switches.h"
-#include "fuchsia/runners/cast/fake_api_bindings.h"
-#include "fuchsia/runners/cast/fake_application_config_manager.h"
-#include "fuchsia/runners/cast/fidl/fidl/chromium/cast/cpp/fidl.h"
+#include "fuchsia_web/runners/cast/cast_runner.h"
+#include "fuchsia_web/runners/cast/cast_runner_switches.h"
+#include "fuchsia_web/runners/cast/fake_api_bindings.h"
+#include "fuchsia_web/runners/cast/fake_application_config_manager.h"
+#include "fuchsia_web/runners/cast/fidl/fidl/chromium/cast/cpp/fidl.h"
 #include "media/fuchsia/audio/fake_audio_device_enumerator.h"
 #include "net/test/embedded_test_server/default_handlers.h"
 #include "net/test/embedded_test_server/http_request.h"
@@ -67,7 +67,7 @@
 constexpr char kBlankAppUrl[] = "/defaultresponse";
 constexpr char kEchoHeaderPath[] = "/echoheader?Test";
 
-constexpr char kTestServerRoot[] = "fuchsia/runners/cast/testdata";
+constexpr char kTestServerRoot[] = "fuchsia_web/runners/cast/testdata";
 
 constexpr char kDummyAgentUrl[] =
     "fuchsia-pkg://fuchsia.com/dummy_agent#meta/dummy_agent.cmx";
@@ -334,7 +334,7 @@
     ASSERT_TRUE(
         base::PathService::Get(base::DIR_SRC_TEST_DATA_ROOT, &pkg_path));
     provider.set_directory(base::OpenDirectoryHandle(
-        pkg_path.AppendASCII("fuchsia/runners/cast/testdata")));
+        pkg_path.AppendASCII("fuchsia_web/runners/cast/testdata")));
     std::vector<fuchsia::web::ContentDirectoryProvider> providers;
     providers.emplace_back(std::move(provider));
 
diff --git a/fuchsia/runners/cast/cast_runner_switches.cc b/fuchsia_web/runners/cast/cast_runner_switches.cc
similarity index 85%
rename from fuchsia/runners/cast/cast_runner_switches.cc
rename to fuchsia_web/runners/cast/cast_runner_switches.cc
index f207f2f..1224604 100644
--- a/fuchsia/runners/cast/cast_runner_switches.cc
+++ b/fuchsia_web/runners/cast/cast_runner_switches.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 "fuchsia/runners/cast/cast_runner_switches.h"
+#include "fuchsia_web/runners/cast/cast_runner_switches.h"
 
 const char kDisableVulkanForTestsSwitch[] = "disable-vulkan-for-tests";
 
diff --git a/fuchsia/runners/cast/cast_runner_switches.h b/fuchsia_web/runners/cast/cast_runner_switches.h
similarity index 73%
rename from fuchsia/runners/cast/cast_runner_switches.h
rename to fuchsia_web/runners/cast/cast_runner_switches.h
index b7d9a63..3662a0e 100644
--- a/fuchsia/runners/cast/cast_runner_switches.h
+++ b/fuchsia_web/runners/cast/cast_runner_switches.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 FUCHSIA_RUNNERS_CAST_CAST_RUNNER_SWITCHES_H_
-#define FUCHSIA_RUNNERS_CAST_CAST_RUNNER_SWITCHES_H_
+#ifndef FUCHSIA_WEB_RUNNERS_CAST_CAST_RUNNER_SWITCHES_H_
+#define FUCHSIA_WEB_RUNNERS_CAST_CAST_RUNNER_SWITCHES_H_
 
 // Disable Vulkan flag for the cast runner. Used for tests.
 extern const char kDisableVulkanForTestsSwitch[];
@@ -15,4 +15,4 @@
 // Force headless mode.
 extern const char kForceHeadlessForTestsSwitch[];
 
-#endif  // FUCHSIA_RUNNERS_CAST_CAST_RUNNER_SWITCHES_H_
+#endif  // FUCHSIA_WEB_RUNNERS_CAST_CAST_RUNNER_SWITCHES_H_
diff --git a/fuchsia/runners/cast/cast_streaming.cc b/fuchsia_web/runners/cast/cast_streaming.cc
similarity index 95%
rename from fuchsia/runners/cast/cast_streaming.cc
rename to fuchsia_web/runners/cast/cast_streaming.cc
index 765e995..d2af03bd 100644
--- a/fuchsia/runners/cast/cast_streaming.cc
+++ b/fuchsia_web/runners/cast/cast_streaming.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 "fuchsia/runners/cast/cast_streaming.h"
+#include "fuchsia_web/runners/cast/cast_streaming.h"
 
 #include <string>
 
@@ -14,7 +14,7 @@
 namespace {
 
 constexpr char kCastStreamingAppUrl[] = "cast-streaming:receiver";
-constexpr char kCastDataDirectory[] = "fuchsia/runners/cast/data";
+constexpr char kCastDataDirectory[] = "fuchsia_web/runners/cast/data";
 constexpr char kCastStreamingContentDirectoryName[] = "cast-streaming";
 constexpr char kCastStreamingMessagePortOrigin[] = "cast-streaming:receiver";
 constexpr char kCastStreamingVideoOnlyMessagePortOrigin[] =
diff --git a/fuchsia/runners/cast/cast_streaming.h b/fuchsia_web/runners/cast/cast_streaming.h
similarity index 81%
rename from fuchsia/runners/cast/cast_streaming.h
rename to fuchsia_web/runners/cast/cast_streaming.h
index d55dbc9f..fbad9b8 100644
--- a/fuchsia/runners/cast/cast_streaming.h
+++ b/fuchsia_web/runners/cast/cast_streaming.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 FUCHSIA_RUNNERS_CAST_CAST_STREAMING_H_
-#define FUCHSIA_RUNNERS_CAST_CAST_STREAMING_H_
+#ifndef FUCHSIA_WEB_RUNNERS_CAST_CAST_STREAMING_H_
+#define FUCHSIA_WEB_RUNNERS_CAST_CAST_STREAMING_H_
 
 #include <fuchsia/web/cpp/fidl.h>
 
 #include "base/callback.h"
 #include "base/strings/string_piece.h"
-#include "fuchsia/runners/cast/fidl/fidl/chromium/cast/cpp/fidl.h"
+#include "fuchsia_web/runners/cast/fidl/fidl/chromium/cast/cpp/fidl.h"
 
 // TODO(crbug.com/1082821): Remove unused methods here once the
 // Cast Streaming Receiver component has been implemented.
@@ -30,4 +30,4 @@
 // Returns the proper origin value for the MessagePort for |app_id|.
 std::string GetMessagePortOriginForAppId(const std::string& app_id);
 
-#endif  // FUCHSIA_RUNNERS_CAST_CAST_STREAMING_H_
+#endif  // FUCHSIA_WEB_RUNNERS_CAST_CAST_STREAMING_H_
diff --git a/fuchsia/runners/cast/create_web_message.cc b/fuchsia_web/runners/cast/create_web_message.cc
similarity index 94%
rename from fuchsia/runners/cast/create_web_message.cc
rename to fuchsia_web/runners/cast/create_web_message.cc
index c850bbfc..c1457f5 100644
--- a/fuchsia/runners/cast/create_web_message.cc
+++ b/fuchsia_web/runners/cast/create_web_message.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 "fuchsia/runners/cast/create_web_message.h"
+#include "fuchsia_web/runners/cast/create_web_message.h"
 
 #include "base/fuchsia/mem_buffer_util.h"
 #include "components/cast/message_port/fuchsia/message_port_fuchsia.h"
diff --git a/fuchsia/runners/cast/create_web_message.h b/fuchsia_web/runners/cast/create_web_message.h
similarity index 77%
rename from fuchsia/runners/cast/create_web_message.h
rename to fuchsia_web/runners/cast/create_web_message.h
index 0be842d..3c63e24 100644
--- a/fuchsia/runners/cast/create_web_message.h
+++ b/fuchsia_web/runners/cast/create_web_message.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 FUCHSIA_RUNNERS_CAST_CREATE_WEB_MESSAGE_H_
-#define FUCHSIA_RUNNERS_CAST_CREATE_WEB_MESSAGE_H_
+#ifndef FUCHSIA_WEB_RUNNERS_CAST_CREATE_WEB_MESSAGE_H_
+#define FUCHSIA_WEB_RUNNERS_CAST_CREATE_WEB_MESSAGE_H_
 
 #include <fuchsia/web/cpp/fidl.h>
 #include <memory>
@@ -17,4 +17,4 @@
     base::StringPiece message,
     std::unique_ptr<cast_api_bindings::MessagePort> port);
 
-#endif  // FUCHSIA_RUNNERS_CAST_CREATE_WEB_MESSAGE_H_
+#endif  // FUCHSIA_WEB_RUNNERS_CAST_CREATE_WEB_MESSAGE_H_
diff --git a/fuchsia/runners/cast/data/receiver.html b/fuchsia_web/runners/cast/data/receiver.html
similarity index 100%
rename from fuchsia/runners/cast/data/receiver.html
rename to fuchsia_web/runners/cast/data/receiver.html
diff --git a/fuchsia/runners/cast/fake_api_bindings.cc b/fuchsia_web/runners/cast/fake_api_bindings.cc
similarity index 96%
rename from fuchsia/runners/cast/fake_api_bindings.cc
rename to fuchsia_web/runners/cast/fake_api_bindings.cc
index c7c17695..2940321 100644
--- a/fuchsia/runners/cast/fake_api_bindings.cc
+++ b/fuchsia_web/runners/cast/fake_api_bindings.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 "fuchsia/runners/cast/fake_api_bindings.h"
+#include "fuchsia_web/runners/cast/fake_api_bindings.h"
 
 #include "base/auto_reset.h"
 #include "base/fuchsia/fuchsia_logging.h"
diff --git a/fuchsia/runners/cast/fake_api_bindings.h b/fuchsia_web/runners/cast/fake_api_bindings.h
similarity index 89%
rename from fuchsia/runners/cast/fake_api_bindings.h
rename to fuchsia_web/runners/cast/fake_api_bindings.h
index 5f8a183..a1d571b 100644
--- a/fuchsia/runners/cast/fake_api_bindings.h
+++ b/fuchsia_web/runners/cast/fake_api_bindings.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 FUCHSIA_RUNNERS_CAST_FAKE_API_BINDINGS_H_
-#define FUCHSIA_RUNNERS_CAST_FAKE_API_BINDINGS_H_
+#ifndef FUCHSIA_WEB_RUNNERS_CAST_FAKE_API_BINDINGS_H_
+#define FUCHSIA_WEB_RUNNERS_CAST_FAKE_API_BINDINGS_H_
 
 #include <string>
 #include <utility>
@@ -12,7 +12,7 @@
 #include "base/callback.h"
 #include "base/containers/flat_map.h"
 #include "base/strings/string_piece.h"
-#include "fuchsia/runners/cast/fidl/fidl/chromium/cast/cpp/fidl.h"
+#include "fuchsia_web/runners/cast/fidl/fidl/chromium/cast/cpp/fidl.h"
 
 // Simple implementation of the ApiBindings service, for use by tests.
 class FakeApiBindingsImpl : public chromium::cast::ApiBindings {
@@ -55,4 +55,4 @@
   base::OnceClosure on_expected_port_received_;
 };
 
-#endif  // FUCHSIA_RUNNERS_CAST_FAKE_API_BINDINGS_H_
+#endif  // FUCHSIA_WEB_RUNNERS_CAST_FAKE_API_BINDINGS_H_
diff --git a/fuchsia/runners/cast/fake_application_config_manager.cc b/fuchsia_web/runners/cast/fake_application_config_manager.cc
similarity index 93%
rename from fuchsia/runners/cast/fake_application_config_manager.cc
rename to fuchsia_web/runners/cast/fake_application_config_manager.cc
index b56c5bb..eb05824 100644
--- a/fuchsia/runners/cast/fake_application_config_manager.cc
+++ b/fuchsia_web/runners/cast/fake_application_config_manager.cc
@@ -2,13 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "fuchsia/runners/cast/fake_application_config_manager.h"
+#include "fuchsia_web/runners/cast/fake_application_config_manager.h"
 
 #include <string>
 #include <utility>
 
 #include "base/logging.h"
-#include "fuchsia/runners/cast/cast_component.h"
+#include "fuchsia_web/runners/cast/cast_component.h"
 
 constexpr char FakeApplicationConfigManager::kFakeAgentUrl[] =
     "fuchsia-pkg://fuchsia.com/fake_agent#meta/fake_agent.cmx";
diff --git a/fuchsia/runners/cast/fake_application_config_manager.h b/fuchsia_web/runners/cast/fake_application_config_manager.h
similarity index 84%
rename from fuchsia/runners/cast/fake_application_config_manager.h
rename to fuchsia_web/runners/cast/fake_application_config_manager.h
index 1b85fc90..a3a5b9c 100644
--- a/fuchsia/runners/cast/fake_application_config_manager.h
+++ b/fuchsia_web/runners/cast/fake_application_config_manager.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 FUCHSIA_RUNNERS_CAST_FAKE_APPLICATION_CONFIG_MANAGER_H_
-#define FUCHSIA_RUNNERS_CAST_FAKE_APPLICATION_CONFIG_MANAGER_H_
+#ifndef FUCHSIA_WEB_RUNNERS_CAST_FAKE_APPLICATION_CONFIG_MANAGER_H_
+#define FUCHSIA_WEB_RUNNERS_CAST_FAKE_APPLICATION_CONFIG_MANAGER_H_
 
 #include <map>
 #include <string>
 #include <vector>
 
-#include "fuchsia/runners/cast/fidl/fidl/chromium/cast/cpp/fidl.h"
+#include "fuchsia_web/runners/cast/fidl/fidl/chromium/cast/cpp/fidl.h"
 #include "url/gurl.h"
 
 // Test cast.ApplicationConfigManager implementation which maps a test Cast
@@ -47,4 +47,4 @@
   std::map<std::string, chromium::cast::ApplicationConfig> id_to_config_;
 };
 
-#endif  // FUCHSIA_RUNNERS_CAST_FAKE_APPLICATION_CONFIG_MANAGER_H_
+#endif  // FUCHSIA_WEB_RUNNERS_CAST_FAKE_APPLICATION_CONFIG_MANAGER_H_
diff --git a/fuchsia/runners/cast/fidl/BUILD.gn b/fuchsia_web/runners/cast/fidl/BUILD.gn
similarity index 88%
rename from fuchsia/runners/cast/fidl/BUILD.gn
rename to fuchsia_web/runners/cast/fidl/BUILD.gn
index ff07b77..63edb42 100644
--- a/fuchsia/runners/cast/fidl/BUILD.gn
+++ b/fuchsia_web/runners/cast/fidl/BUILD.gn
@@ -26,7 +26,7 @@
   visibility = [
     "//chromecast/bindings:*",
     "//chromecast/internal/*",
-    "//fuchsia/runners:cast_runner_core",
-    "//fuchsia/runners:cast_runner_test_core",
+    "//fuchsia_web/runners:cast_runner_core",
+    "//fuchsia_web/runners:cast_runner_test_core",
   ]
 }
diff --git a/fuchsia/runners/cast/fidl/OWNERS b/fuchsia_web/runners/cast/fidl/OWNERS
similarity index 100%
rename from fuchsia/runners/cast/fidl/OWNERS
rename to fuchsia_web/runners/cast/fidl/OWNERS
diff --git a/fuchsia/runners/cast/fidl/api_bindings.fidl b/fuchsia_web/runners/cast/fidl/api_bindings.fidl
similarity index 100%
rename from fuchsia/runners/cast/fidl/api_bindings.fidl
rename to fuchsia_web/runners/cast/fidl/api_bindings.fidl
diff --git a/fuchsia/runners/cast/fidl/application_config.fidl b/fuchsia_web/runners/cast/fidl/application_config.fidl
similarity index 100%
rename from fuchsia/runners/cast/fidl/application_config.fidl
rename to fuchsia_web/runners/cast/fidl/application_config.fidl
diff --git a/fuchsia/runners/cast/fidl/application_context.fidl b/fuchsia_web/runners/cast/fidl/application_context.fidl
similarity index 100%
rename from fuchsia/runners/cast/fidl/application_context.fidl
rename to fuchsia_web/runners/cast/fidl/application_context.fidl
diff --git a/fuchsia/runners/cast/fidl/application_controller.fidl b/fuchsia_web/runners/cast/fidl/application_controller.fidl
similarity index 100%
rename from fuchsia/runners/cast/fidl/application_controller.fidl
rename to fuchsia_web/runners/cast/fidl/application_controller.fidl
diff --git a/fuchsia/runners/cast/fidl/cors_exempt_headers.fidl b/fuchsia_web/runners/cast/fidl/cors_exempt_headers.fidl
similarity index 100%
rename from fuchsia/runners/cast/fidl/cors_exempt_headers.fidl
rename to fuchsia_web/runners/cast/fidl/cors_exempt_headers.fidl
diff --git a/fuchsia/runners/cast/fidl/data_reset.fidl b/fuchsia_web/runners/cast/fidl/data_reset.fidl
similarity index 100%
rename from fuchsia/runners/cast/fidl/data_reset.fidl
rename to fuchsia_web/runners/cast/fidl/data_reset.fidl
diff --git a/fuchsia/runners/cast/fidl/url_request_rewriter.fidl b/fuchsia_web/runners/cast/fidl/url_request_rewriter.fidl
similarity index 100%
rename from fuchsia/runners/cast/fidl/url_request_rewriter.fidl
rename to fuchsia_web/runners/cast/fidl/url_request_rewriter.fidl
diff --git a/fuchsia/runners/cast/main.cc b/fuchsia_web/runners/cast/main.cc
similarity index 97%
rename from fuchsia/runners/cast/main.cc
rename to fuchsia_web/runners/cast/main.cc
index c84ece3..aadaaac 100644
--- a/fuchsia/runners/cast/main.cc
+++ b/fuchsia_web/runners/cast/main.cc
@@ -26,8 +26,8 @@
 #include "fuchsia/base/fuchsia_dir_scheme.h"
 #include "fuchsia/base/init_logging.h"
 #include "fuchsia/engine/web_instance_host/web_instance_host.h"
-#include "fuchsia/runners/cast/cast_runner.h"
-#include "fuchsia/runners/cast/cast_runner_switches.h"
+#include "fuchsia_web/runners/cast/cast_runner.h"
+#include "fuchsia_web/runners/cast/cast_runner_switches.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace {
diff --git a/fuchsia/runners/cast/named_message_port_connector_fuchsia.cc b/fuchsia_web/runners/cast/named_message_port_connector_fuchsia.cc
similarity index 96%
rename from fuchsia/runners/cast/named_message_port_connector_fuchsia.cc
rename to fuchsia_web/runners/cast/named_message_port_connector_fuchsia.cc
index fdcfa74f..2d4bb718 100644
--- a/fuchsia/runners/cast/named_message_port_connector_fuchsia.cc
+++ b/fuchsia_web/runners/cast/named_message_port_connector_fuchsia.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 "fuchsia/runners/cast/named_message_port_connector_fuchsia.h"
+#include "fuchsia_web/runners/cast/named_message_port_connector_fuchsia.h"
 
 #include <fuchsia/mem/cpp/fidl.h>
 #include <fuchsia/web/cpp/fidl.h>
diff --git a/fuchsia/runners/cast/named_message_port_connector_fuchsia.h b/fuchsia_web/runners/cast/named_message_port_connector_fuchsia.h
similarity index 82%
rename from fuchsia/runners/cast/named_message_port_connector_fuchsia.h
rename to fuchsia_web/runners/cast/named_message_port_connector_fuchsia.h
index 75742a5..edba48d 100644
--- a/fuchsia/runners/cast/named_message_port_connector_fuchsia.h
+++ b/fuchsia_web/runners/cast/named_message_port_connector_fuchsia.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 FUCHSIA_RUNNERS_CAST_NAMED_MESSAGE_PORT_CONNECTOR_FUCHSIA_H_
-#define FUCHSIA_RUNNERS_CAST_NAMED_MESSAGE_PORT_CONNECTOR_FUCHSIA_H_
+#ifndef FUCHSIA_WEB_RUNNERS_CAST_NAMED_MESSAGE_PORT_CONNECTOR_FUCHSIA_H_
+#define FUCHSIA_WEB_RUNNERS_CAST_NAMED_MESSAGE_PORT_CONNECTOR_FUCHSIA_H_
 
 #include "components/cast/named_message_port_connector/named_message_port_connector.h"
 
@@ -33,4 +33,4 @@
   fuchsia::web::Frame* frame_ = nullptr;
 };
 
-#endif  // FUCHSIA_RUNNERS_CAST_NAMED_MESSAGE_PORT_CONNECTOR_FUCHSIA_H_
+#endif  // FUCHSIA_WEB_RUNNERS_CAST_NAMED_MESSAGE_PORT_CONNECTOR_FUCHSIA_H_
diff --git a/fuchsia/runners/cast/named_message_port_connector_fuchsia_browsertest.cc b/fuchsia_web/runners/cast/named_message_port_connector_fuchsia_browsertest.cc
similarity index 96%
rename from fuchsia/runners/cast/named_message_port_connector_fuchsia_browsertest.cc
rename to fuchsia_web/runners/cast/named_message_port_connector_fuchsia_browsertest.cc
index 250624c8..ddbc6e8 100644
--- a/fuchsia/runners/cast/named_message_port_connector_fuchsia_browsertest.cc
+++ b/fuchsia_web/runners/cast/named_message_port_connector_fuchsia_browsertest.cc
@@ -15,8 +15,8 @@
 #include "fuchsia/base/test/test_navigation_listener.h"
 #include "fuchsia/engine/test/frame_for_test.h"
 #include "fuchsia/engine/test/web_engine_browser_test.h"
-#include "fuchsia/runners/cast/create_web_message.h"
-#include "fuchsia/runners/cast/named_message_port_connector_fuchsia.h"
+#include "fuchsia_web/runners/cast/create_web_message.h"
+#include "fuchsia_web/runners/cast/named_message_port_connector_fuchsia.h"
 #include "testing/gmock/include/gmock/gmock-matchers.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -30,7 +30,7 @@
     : public cr_fuchsia::WebEngineBrowserTest {
  public:
   NamedMessagePortConnectorFuchsiaTest() {
-    set_test_server_root(base::FilePath("fuchsia/runners/cast/testdata"));
+    set_test_server_root(base::FilePath("fuchsia_web/runners/cast/testdata"));
   }
 
   ~NamedMessagePortConnectorFuchsiaTest() override = default;
diff --git a/fuchsia/runners/cast/pending_cast_component.cc b/fuchsia_web/runners/cast/pending_cast_component.cc
similarity index 98%
rename from fuchsia/runners/cast/pending_cast_component.cc
rename to fuchsia_web/runners/cast/pending_cast_component.cc
index bf5e60f8..ae97e50 100644
--- a/fuchsia/runners/cast/pending_cast_component.cc
+++ b/fuchsia_web/runners/cast/pending_cast_component.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 "fuchsia/runners/cast/pending_cast_component.h"
+#include "fuchsia_web/runners/cast/pending_cast_component.h"
 
 #include "base/bind.h"
 #include "base/check.h"
diff --git a/fuchsia/runners/cast/pending_cast_component.h b/fuchsia_web/runners/cast/pending_cast_component.h
similarity index 89%
rename from fuchsia/runners/cast/pending_cast_component.h
rename to fuchsia_web/runners/cast/pending_cast_component.h
index 86c61b5..b124b74d 100644
--- a/fuchsia/runners/cast/pending_cast_component.h
+++ b/fuchsia_web/runners/cast/pending_cast_component.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 FUCHSIA_RUNNERS_CAST_PENDING_CAST_COMPONENT_H_
-#define FUCHSIA_RUNNERS_CAST_PENDING_CAST_COMPONENT_H_
+#ifndef FUCHSIA_WEB_RUNNERS_CAST_PENDING_CAST_COMPONENT_H_
+#define FUCHSIA_WEB_RUNNERS_CAST_PENDING_CAST_COMPONENT_H_
 
 #include <fuchsia/sys/cpp/fidl.h>
 #include <lib/fidl/cpp/interface_request.h>
 #include <memory>
 
 #include "base/strings/string_piece.h"
-#include "fuchsia/runners/cast/fidl/fidl/chromium/cast/cpp/fidl.h"
-#include "fuchsia/runners/cast/cast_component.h"
+#include "fuchsia_web/runners/cast/cast_component.h"
+#include "fuchsia_web/runners/cast/fidl/fidl/chromium/cast/cpp/fidl.h"
 
 namespace base {
 class StartupContext;
@@ -76,4 +76,4 @@
   chromium::cast::ApplicationConfigManagerPtr application_config_manager_;
 };
 
-#endif  // FUCHSIA_RUNNERS_CAST_PENDING_CAST_COMPONENT_H_
+#endif  // FUCHSIA_WEB_RUNNERS_CAST_PENDING_CAST_COMPONENT_H_
diff --git a/fuchsia/runners/cast/testdata/camera.html b/fuchsia_web/runners/cast/testdata/camera.html
similarity index 100%
rename from fuchsia/runners/cast/testdata/camera.html
rename to fuchsia_web/runners/cast/testdata/camera.html
diff --git a/fuchsia/runners/cast/testdata/connector.html b/fuchsia_web/runners/cast/testdata/connector.html
similarity index 100%
rename from fuchsia/runners/cast/testdata/connector.html
rename to fuchsia_web/runners/cast/testdata/connector.html
diff --git a/fuchsia/runners/cast/testdata/connector_multiple_ports.html b/fuchsia_web/runners/cast/testdata/connector_multiple_ports.html
similarity index 100%
rename from fuchsia/runners/cast/testdata/connector_multiple_ports.html
rename to fuchsia_web/runners/cast/testdata/connector_multiple_ports.html
diff --git a/fuchsia/runners/cast/testdata/css_animation.html b/fuchsia_web/runners/cast/testdata/css_animation.html
similarity index 100%
rename from fuchsia/runners/cast/testdata/css_animation.html
rename to fuchsia_web/runners/cast/testdata/css_animation.html
diff --git a/fuchsia/runners/cast/testdata/echo.html b/fuchsia_web/runners/cast/testdata/echo.html
similarity index 100%
rename from fuchsia/runners/cast/testdata/echo.html
rename to fuchsia_web/runners/cast/testdata/echo.html
diff --git a/fuchsia/runners/cast/testdata/empty.html b/fuchsia_web/runners/cast/testdata/empty.html
similarity index 100%
rename from fuchsia/runners/cast/testdata/empty.html
rename to fuchsia_web/runners/cast/testdata/empty.html
diff --git a/fuchsia/runners/cast/testdata/microphone.html b/fuchsia_web/runners/cast/testdata/microphone.html
similarity index 100%
rename from fuchsia/runners/cast/testdata/microphone.html
rename to fuchsia_web/runners/cast/testdata/microphone.html
diff --git a/fuchsia/runners/cast/testdata/webgl_presence.html b/fuchsia_web/runners/cast/testdata/webgl_presence.html
similarity index 100%
rename from fuchsia/runners/cast/testdata/webgl_presence.html
rename to fuchsia_web/runners/cast/testdata/webgl_presence.html
diff --git a/fuchsia/runners/common/web_component.cc b/fuchsia_web/runners/common/web_component.cc
similarity index 98%
rename from fuchsia/runners/common/web_component.cc
rename to fuchsia_web/runners/common/web_component.cc
index c8e9aaf..a417909 100644
--- a/fuchsia/runners/common/web_component.cc
+++ b/fuchsia_web/runners/common/web_component.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 "fuchsia/runners/common/web_component.h"
+#include "fuchsia_web/runners/common/web_component.h"
 
 #include <fuchsia/ui/views/cpp/fidl.h>
 #include <lib/fit/function.h>
@@ -13,7 +13,7 @@
 #include "base/fuchsia/fuchsia_logging.h"
 #include "base/logging.h"
 #include "base/strings/string_piece.h"
-#include "fuchsia/runners/common/web_content_runner.h"
+#include "fuchsia_web/runners/common/web_content_runner.h"
 
 WebComponent::WebComponent(
     base::StringPiece debug_name,
diff --git a/fuchsia/runners/common/web_component.h b/fuchsia_web/runners/common/web_component.h
similarity index 97%
rename from fuchsia/runners/common/web_component.h
rename to fuchsia_web/runners/common/web_component.h
index 45c828c..f1968dd 100644
--- a/fuchsia/runners/common/web_component.h
+++ b/fuchsia_web/runners/common/web_component.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 FUCHSIA_RUNNERS_COMMON_WEB_COMPONENT_H_
-#define FUCHSIA_RUNNERS_COMMON_WEB_COMPONENT_H_
+#ifndef FUCHSIA_WEB_RUNNERS_COMMON_WEB_COMPONENT_H_
+#define FUCHSIA_WEB_RUNNERS_COMMON_WEB_COMPONENT_H_
 
 #include <fuchsia/modular/cpp/fidl.h>
 #include <fuchsia/sys/cpp/fidl.h>
@@ -143,4 +143,4 @@
       navigation_listener_binding_;
 };
 
-#endif  // FUCHSIA_RUNNERS_COMMON_WEB_COMPONENT_H_
+#endif  // FUCHSIA_WEB_RUNNERS_COMMON_WEB_COMPONENT_H_
diff --git a/fuchsia/runners/common/web_content_runner.cc b/fuchsia_web/runners/common/web_content_runner.cc
similarity index 97%
rename from fuchsia/runners/common/web_content_runner.cc
rename to fuchsia_web/runners/common/web_content_runner.cc
index b07f2de..8384bdc 100644
--- a/fuchsia/runners/common/web_content_runner.cc
+++ b/fuchsia_web/runners/common/web_content_runner.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 "fuchsia/runners/common/web_content_runner.h"
+#include "fuchsia_web/runners/common/web_content_runner.h"
 
 #include <fuchsia/sys/cpp/fidl.h>
 #include <lib/fdio/directory.h>
@@ -23,8 +23,8 @@
 #include "base/logging.h"
 #include "base/strings/stringprintf.h"
 #include "fuchsia/engine/web_instance_host/web_instance_host.h"
-#include "fuchsia/runners/buildflags.h"
-#include "fuchsia/runners/common/web_component.h"
+#include "fuchsia_web/runners/buildflags.h"
+#include "fuchsia_web/runners/common/web_component.h"
 #include "url/gurl.h"
 
 namespace {
diff --git a/fuchsia/runners/common/web_content_runner.h b/fuchsia_web/runners/common/web_content_runner.h
similarity index 96%
rename from fuchsia/runners/common/web_content_runner.h
rename to fuchsia_web/runners/common/web_content_runner.h
index e13865f..cf9e3ea9 100644
--- a/fuchsia/runners/common/web_content_runner.h
+++ b/fuchsia_web/runners/common/web_content_runner.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 FUCHSIA_RUNNERS_COMMON_WEB_CONTENT_RUNNER_H_
-#define FUCHSIA_RUNNERS_COMMON_WEB_CONTENT_RUNNER_H_
+#ifndef FUCHSIA_WEB_RUNNERS_COMMON_WEB_CONTENT_RUNNER_H_
+#define FUCHSIA_WEB_RUNNERS_COMMON_WEB_CONTENT_RUNNER_H_
 
 #include <fuchsia/io/cpp/fidl.h>
 #include <fuchsia/sys/cpp/fidl.h>
@@ -124,4 +124,4 @@
   base::OnceClosure on_empty_callback_;
 };
 
-#endif  // FUCHSIA_RUNNERS_COMMON_WEB_CONTENT_RUNNER_H_
+#endif  // FUCHSIA_WEB_RUNNERS_COMMON_WEB_CONTENT_RUNNER_H_
diff --git a/fuchsia/runners/web/DEPS b/fuchsia_web/runners/web/DEPS
similarity index 100%
rename from fuchsia/runners/web/DEPS
rename to fuchsia_web/runners/web/DEPS
diff --git a/fuchsia/runners/web/OWNERS b/fuchsia_web/runners/web/OWNERS
similarity index 100%
rename from fuchsia/runners/web/OWNERS
rename to fuchsia_web/runners/web/OWNERS
diff --git a/fuchsia/runners/web/main.cc b/fuchsia_web/runners/web/main.cc
similarity index 96%
rename from fuchsia/runners/web/main.cc
rename to fuchsia_web/runners/web/main.cc
index c473c29..3ce02a0 100644
--- a/fuchsia/runners/web/main.cc
+++ b/fuchsia_web/runners/web/main.cc
@@ -17,8 +17,8 @@
 #include "fuchsia/base/fuchsia_dir_scheme.h"
 #include "fuchsia/base/init_logging.h"
 #include "fuchsia/engine/web_instance_host/web_instance_host.h"
-#include "fuchsia/runners/buildflags.h"
-#include "fuchsia/runners/common/web_content_runner.h"
+#include "fuchsia_web/runners/buildflags.h"
+#include "fuchsia_web/runners/common/web_content_runner.h"
 
 namespace {
 
diff --git a/fuchsia/runners/web/web_runner.cmx b/fuchsia_web/runners/web/web_runner.cmx
similarity index 100%
rename from fuchsia/runners/web/web_runner.cmx
rename to fuchsia_web/runners/web/web_runner.cmx
diff --git a/fuchsia/runners/web/web_runner_smoke_test.cc b/fuchsia_web/runners/web/web_runner_smoke_test.cc
similarity index 100%
rename from fuchsia/runners/web/web_runner_smoke_test.cc
rename to fuchsia_web/runners/web/web_runner_smoke_test.cc
diff --git a/headless/BUILD.gn b/headless/BUILD.gn
index f0291b82..a6e8bfd7 100644
--- a/headless/BUILD.gn
+++ b/headless/BUILD.gn
@@ -239,24 +239,6 @@
   ]
 }
 
-if (headless_fontconfig_utils && !is_fuchsia) {
-  static_library("headless_fontconfig_utils") {
-    sources = [
-      "public/headless_export.h",
-      "public/util/fontconfig.cc",
-      "public/util/fontconfig.h",
-    ]
-
-    deps = [
-      "//base",
-      "//build/config/freetype",
-      "//third_party/fontconfig",
-    ]
-
-    configs += [ ":inside_headless_component" ]
-  }
-}
-
 inspector_protocol_generate("protocol_sources") {
   visibility = [
     ":backend_cdp_bindings",
@@ -624,10 +606,6 @@
     deps += [ "//device/bluetooth" ]
   }
 
-  if (headless_fontconfig_utils && !is_fuchsia) {
-    deps += [ ":headless_fontconfig_utils" ]
-  }
-
   configs += [ ":inside_headless_component" ]
   configs += [ ":headless_defines_config" ]
 }
diff --git a/headless/headless.gni b/headless/headless.gni
index 11b102d..11aed66a 100644
--- a/headless/headless.gni
+++ b/headless/headless.gni
@@ -6,9 +6,6 @@
   # Embed resource.pak file into the binary for easier distribution.
   headless_use_embedded_resources = false
 
-  # Provide bindings for font loading for headless embedders.
-  headless_fontconfig_utils = false
-
   # Use Prefs component to access Local State and other preferences.
   headless_use_prefs = true
 
diff --git a/headless/public/util/fontconfig.cc b/headless/public/util/fontconfig.cc
deleted file mode 100644
index ef0f2ba..0000000
--- a/headless/public/util/fontconfig.cc
+++ /dev/null
@@ -1,121 +0,0 @@
-// Copyright 2017 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 "headless/public/util/fontconfig.h"
-
-// Should be included before freetype.h.
-#include <ft2build.h>
-
-#include <dirent.h>
-#include <fontconfig/fontconfig.h>
-#include <freetype/freetype.h>
-#include <set>
-#include <string>
-
-#include "base/check.h"
-#include "base/check_op.h"
-#include "base/logging.h"
-
-namespace headless {
-namespace {
-void GetFontFileNames(const FcFontSet* font_set,
-                      std::set<std::string>* file_names) {
-  if (font_set == NULL)
-    return;
-  for (int i = 0; i < font_set->nfont; ++i) {
-    FcPattern* pattern = font_set->fonts[i];
-    FcValue font_file;
-    if (FcPatternGet(pattern, "file", 0, &font_file) == FcResultMatch) {
-      file_names->insert(reinterpret_cast<const char*>(font_file.u.s));
-    } else {
-      VLOG(1) << "Failed to find filename.";
-      FcPatternPrint(pattern);
-    }
-  }
-}
-FcConfig* g_config = nullptr;
-}  // namespace
-
-void InitFonts(const char* fontconfig_path) {
-  // The following is roughly equivalent to calling FcInit().  We jump through
-  // a bunch of hoops here to avoid using fontconfig's directory scanning
-  // logic. The problem with fontconfig is that it follows symlinks when doing
-  // recursive directory scans.
-  //
-  // The approach below ignores any <dir>...</dir> entries in fonts.conf.  This
-  // is deliberate.  Specifying dirs is problematic because they're either
-  // absolute or relative to our process's current working directory which
-  // could be anything.  Instead we assume that all font files will be in the
-  // same directory as fonts.conf.  We'll scan + load them here.
-  FcConfig* config = FcConfigCreate();
-  g_config = config;
-  CHECK(config);
-
-  // FcConfigParseAndLoad is a seriously goofy function. Depending on whether
-  // name passed in begins with a slash, it will treat it either as a file name
-  // to be found in the directory where it expects to find the font
-  // configuration OR it will will treat it as a directory where it expects to
-  // find fonts.conf. The latter behavior is the one we want. Passing
-  // fontconfig_path via the environment is a quick and dirty way to get
-  // uniform behavior regardless whether it's a relative path or not.
-  setenv("FONTCONFIG_PATH", fontconfig_path, 1);
-  CHECK(FcConfigParseAndLoad(config, nullptr, FcTrue))
-      << "Failed to load font configuration.  FONTCONFIG_PATH="
-      << fontconfig_path;
-
-  DIR* fc_dir = opendir(fontconfig_path);
-  CHECK(fc_dir) << "Failed to open font directory " << fontconfig_path << ": "
-                << strerror(errno);
-
-  // The fonts must be loaded in a consistent order. This makes rendered results
-  // stable across runs, otherwise replacement font picks are random
-  // and cause flakiness.
-  std::set<std::string> fonts;
-  struct dirent *result;
-  while ((result = readdir(fc_dir))) {
-    fonts.insert(result->d_name);
-  }
-  for (const std::string& font : fonts) {
-    const std::string full_path = fontconfig_path + ("/" + font);
-    struct stat statbuf;
-    CHECK_EQ(0, stat(full_path.c_str(), &statbuf))
-        << "Failed to stat " << full_path << ": " << strerror(errno);
-    if (S_ISREG(statbuf.st_mode)) {
-      // FcConfigAppFontAddFile will silently ignore non-fonts.
-      FcConfigAppFontAddFile(
-          config, reinterpret_cast<const FcChar8*>(full_path.c_str()));
-    }
-  }
-  closedir(fc_dir);
-  CHECK(FcConfigSetCurrent(config));
-
-  // Retrieve font from both of fontconfig's font sets for pre-loading.
-  std::set<std::string> font_files;
-  GetFontFileNames(FcConfigGetFonts(NULL, FcSetSystem), &font_files);
-  GetFontFileNames(FcConfigGetFonts(NULL, FcSetApplication), &font_files);
-  CHECK_GT(font_files.size(), 0u)
-      << "Font configuration doesn't contain any fonts!";
-
-  // Get freetype to load every font file we know about.  This will cause the
-  // font files to get cached in memory.  Once that's done we shouldn't have to
-  // access the file system for fonts at all.
-  FT_Library library;
-  FT_Init_FreeType(&library);
-  for (std::set<std::string>::const_iterator iter = font_files.begin();
-       iter != font_files.end(); ++iter) {
-    FT_Face face;
-    CHECK_EQ(0, FT_New_Face(library, iter->c_str(), 0, &face))
-        << "Failed to load font face: " << *iter;
-    FT_Done_Face(face);
-  }
-  FT_Done_FreeType(library);  // Cached stuff will stick around... ?
-}
-
-void ReleaseFonts() {
-  CHECK(g_config);
-  FcConfigDestroy(g_config);
-  FcFini();
-}
-
-}  // namespace headless
diff --git a/headless/public/util/fontconfig.h b/headless/public/util/fontconfig.h
deleted file mode 100644
index b3e1ec1e..0000000
--- a/headless/public/util/fontconfig.h
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright 2017 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 HEADLESS_PUBLIC_UTIL_FONTCONFIG_H_
-#define HEADLESS_PUBLIC_UTIL_FONTCONFIG_H_
-
-#include "headless/public/headless_export.h"
-
-namespace headless {
-
-// Initialize fontconfig by loading fonts from given path without following
-// symlinks. This is a wrapper around FcInit from libfreetype bundled with
-// Chromium modified to enable headless embedders to deploy in custom
-// environments.
-HEADLESS_EXPORT void InitFonts(const char* font_config_path);
-
-HEADLESS_EXPORT void ReleaseFonts();
-
-}  // namespace headless
-
-#endif  // HEADLESS_PUBLIC_UTIL_FONTCONFIG_H_
diff --git a/infra/orchestrator/BUILD.gn b/infra/orchestrator/BUILD.gn
index 2ba1f5d..d26c636 100644
--- a/infra/orchestrator/BUILD.gn
+++ b/infra/orchestrator/BUILD.gn
@@ -26,6 +26,13 @@
   if (use_clang_coverage) {
     data += [ "//tools/clang/scripts/update.py" ]
   }
+
+  if (use_jacoco_coverage) {
+    data += [
+      "//third_party/jdk/current/bin/java",
+      "//third_party/jacoco/lib/jacococli.jar",
+    ]
+  }
   write_runtime_deps = "$root_out_dir/orchestrator_all.runtime_deps"
 }
 
diff --git a/ios/chrome/app/application_delegate/metrics_mediator.mm b/ios/chrome/app/application_delegate/metrics_mediator.mm
index 08f502f3..0701885 100644
--- a/ios/chrome/app/application_delegate/metrics_mediator.mm
+++ b/ios/chrome/app/application_delegate/metrics_mediator.mm
@@ -563,6 +563,8 @@
     GetApplicationContext()->GetLocalState()->ClearPref(
         metrics::prefs::kMetricsClientID);
     GetApplicationContext()->GetLocalState()->ClearPref(
+        metrics::prefs::kMetricsProvisionalClientID);
+    GetApplicationContext()->GetLocalState()->ClearPref(
         metrics::prefs::kMetricsReportingEnabledTimestamp);
     crash_keys::ClearMetricsClientId();
   }
diff --git a/ios/chrome/app/strings/ios_strings.grd b/ios/chrome/app/strings/ios_strings.grd
index 463ebe5..ae50c94 100644
--- a/ios/chrome/app/strings/ios_strings.grd
+++ b/ios/chrome/app/strings/ios_strings.grd
@@ -969,6 +969,9 @@
       <message name="IDS_IOS_FOLLOW_MANAGEMENT_UNFOLLOW_ACTION" desc="The 'Unfollow' action for a channel in the following feed management UI">
         Unfollow
       </message>
+      <message name="IDS_IOS_FOLLOW_MANAGEMENT_VISIT_SITE_ACTION" desc="The 'Visit Site' action for a followed item in the following feed management UI">
+        Visit Site
+      </message>
       <message name="IDS_IOS_FOLLOWING_FEED_TITLE" desc="The title in the Following feed header label.">
         Following
       </message>
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_FOLLOW_MANAGEMENT_VISIT_SITE_ACTION.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_FOLLOW_MANAGEMENT_VISIT_SITE_ACTION.png.sha1
new file mode 100644
index 0000000..3407cb2
--- /dev/null
+++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_FOLLOW_MANAGEMENT_VISIT_SITE_ACTION.png.sha1
@@ -0,0 +1 @@
+cd6496ca58d1b2e6bc86bf29e4bfc09dca771d9c
\ No newline at end of file
diff --git a/ios/chrome/browser/flags/about_flags.mm b/ios/chrome/browser/flags/about_flags.mm
index 1561718..fc2ca15d 100644
--- a/ios/chrome/browser/flags/about_flags.mm
+++ b/ios/chrome/browser/flags/about_flags.mm
@@ -664,11 +664,6 @@
      flags_ui::kOsIos,
      FEATURE_VALUE_TYPE(
          autofill::features::kAutofillFillMerchantPromoCodeFields)},
-    {"context-menu-phase2",
-     flag_descriptions::kWebViewNativeContextMenuPhase2Name,
-     flag_descriptions::kWebViewNativeContextMenuPhase2Description,
-     flags_ui::kOsIos,
-     FEATURE_VALUE_TYPE(web::features::kWebViewNativeContextMenuPhase2)},
     {"default-wkwebview-context-menu",
      flag_descriptions::kDefaultWebViewContextMenuName,
      flag_descriptions::kDefaultWebViewContextMenuDescription, flags_ui::kOsIos,
@@ -836,12 +831,6 @@
      flag_descriptions::kEnhancedProtectionPhase2Name,
      flag_descriptions::kEnhancedProtectionPhase2Description, flags_ui::kOsIos,
      FEATURE_VALUE_TYPE(safe_browsing::kEnhancedProtectionPhase2IOS)},
-    {"context-menu-phase2-screenshot",
-     flag_descriptions::kWebViewNativeContextMenuPhase2ScreenshotName,
-     flag_descriptions::kWebViewNativeContextMenuPhase2ScreenshotDescription,
-     flags_ui::kOsIos,
-     FEATURE_VALUE_TYPE(
-         web::features::kWebViewNativeContextMenuPhase2Screenshot)},
     {"autofill-enable-unmask-card-request-set-instrument-id",
      flag_descriptions::kAutofillEnableUnmaskCardRequestSetInstrumentIdName,
      flag_descriptions::
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
index a3d60031..62fe7df 100644
--- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
+++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
@@ -696,19 +696,6 @@
 const char kWebPageAlternativeTextZoomDescription[] =
     "When enabled, switches the method used to zoom web pages.";
 
-const char kWebViewNativeContextMenuPhase2Name[] =
-    "Context Menu with non-live preview";
-const char kWebViewNativeContextMenuPhase2Description[] =
-    "When enabled, the context menu displayed when long pressing on a link or "
-    "an image has a non-live preview.";
-
-const char kWebViewNativeContextMenuPhase2ScreenshotName[] =
-    "Screenshot preview animation for Context Menu";
-
-const char kWebViewNativeContextMenuPhase2ScreenshotDescription[] =
-    "When enabled with phase2, uses a screenshot as transition animation to "
-    "the context menu.";
-
 // Please insert your name/description above in alphabetical order.
 
 }  // namespace flag_descriptions
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
index ce3b91b..cb7be79 100644
--- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
+++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
@@ -627,16 +627,6 @@
 extern const char kWebPageAlternativeTextZoomName[];
 extern const char kWebPageAlternativeTextZoomDescription[];
 
-// Title and description for the flag to enable the phase 2 of context menus in
-// the WebView.
-extern const char kWebViewNativeContextMenuPhase2Name[];
-extern const char kWebViewNativeContextMenuPhase2Description[];
-
-// Title and description for the flag to enable screenshot preview in the phase
-// 2 of context menus in the WebView.
-extern const char kWebViewNativeContextMenuPhase2ScreenshotName[];
-extern const char kWebViewNativeContextMenuPhase2ScreenshotDescription[];
-
 // Please add names and descriptions above in alphabetical order.
 
 }  // namespace flag_descriptions
diff --git a/ios/chrome/browser/pref_names.cc b/ios/chrome/browser/pref_names.cc
index c7585a3..ae4fc986 100644
--- a/ios/chrome/browser/pref_names.cc
+++ b/ios/chrome/browser/pref_names.cc
@@ -98,6 +98,16 @@
 const char kIosSettingsSigninPromoDisplayedCount[] =
     "ios.settings.signin_promo_displayed_count";
 
+// Preference that hold a boolean indicating if the user has already dismissed
+// the sign-in promo in the ntp feed top section.
+const char kIosNtpFeedTopPromoAlreadySeen[] =
+    "ios.ntp_feed_top.promo_already_seen";
+
+// Integer to represent the number of time the sign-in promo has been displayed
+// in the ntp feed top section.
+const char kIosNtpFeedTopSigninPromoDisplayedCount[] =
+    "ios.ntp_feed_top.signin_promo_displayed_count";
+
 // Preference that holds a boolean indicating whether the link previews are
 // enabled. Link previews display a live preview of the selected link after a
 // long press.
diff --git a/ios/chrome/browser/pref_names.h b/ios/chrome/browser/pref_names.h
index acdbdaa..1f3ec8a36 100644
--- a/ios/chrome/browser/pref_names.h
+++ b/ios/chrome/browser/pref_names.h
@@ -30,6 +30,8 @@
 extern const char kIosDiscoverFeedLastRefreshTime[];
 extern const char kIosSettingsPromoAlreadySeen[];
 extern const char kIosSettingsSigninPromoDisplayedCount[];
+extern const char kIosNtpFeedTopPromoAlreadySeen[];
+extern const char kIosNtpFeedTopSigninPromoDisplayedCount[];
 extern const char kLinkPreviewEnabled[];
 extern const char kNTPContentSuggestionsEnabled[];
 extern const char kNTPFollowingFeedSortType[];
diff --git a/ios/chrome/browser/ui/authentication/signin_promo_view_mediator.mm b/ios/chrome/browser/ui/authentication/signin_promo_view_mediator.mm
index da6cbb4c..250d4d2 100644
--- a/ios/chrome/browser/ui/authentication/signin_promo_view_mediator.mm
+++ b/ios/chrome/browser/ui/authentication/signin_promo_view_mediator.mm
@@ -6,6 +6,7 @@
 
 #include <memory>
 
+#include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/metrics/user_metrics.h"
 #include "base/metrics/user_metrics_action.h"
@@ -43,6 +44,7 @@
     case signin_metrics::AccessPoint::ACCESS_POINT_BOOKMARK_MANAGER:
     case signin_metrics::AccessPoint::ACCESS_POINT_RECENT_TABS:
     case signin_metrics::AccessPoint::ACCESS_POINT_TAB_SWITCHER:
+    case signin_metrics::AccessPoint::ACCESS_POINT_NTP_FEED_TOP_PROMO:
       return true;
     case signin_metrics::AccessPoint::ACCESS_POINT_START_PAGE:
     case signin_metrics::AccessPoint::ACCESS_POINT_NTP_LINK:
@@ -87,15 +89,20 @@
     int displayed_count) {
   switch (access_point) {
     case signin_metrics::AccessPoint::ACCESS_POINT_BOOKMARK_MANAGER:
-      UMA_HISTOGRAM_COUNTS_100(
+      base::UmaHistogramCounts100(
           "MobileSignInPromo.BookmarkManager.ImpressionsTilSigninButtons",
           displayed_count);
       break;
     case signin_metrics::AccessPoint::ACCESS_POINT_SETTINGS:
-      UMA_HISTOGRAM_COUNTS_100(
+      base::UmaHistogramCounts100(
           "MobileSignInPromo.SettingsManager.ImpressionsTilSigninButtons",
           displayed_count);
       break;
+    case signin_metrics::AccessPoint::ACCESS_POINT_NTP_FEED_TOP_PROMO:
+      base::UmaHistogramCounts100(
+          "MobileSignInPromo.NTPFeedTop.ImpressionsTilSigninButtons",
+          displayed_count);
+      break;
     case signin_metrics::AccessPoint::
         ACCESS_POINT_ENTERPRISE_SIGNOUT_COORDINATOR:
     case signin_metrics::AccessPoint::ACCESS_POINT_RECENT_TABS:
@@ -143,15 +150,20 @@
     int displayed_count) {
   switch (access_point) {
     case signin_metrics::AccessPoint::ACCESS_POINT_BOOKMARK_MANAGER:
-      UMA_HISTOGRAM_COUNTS_100(
+      base::UmaHistogramCounts100(
           "MobileSignInPromo.BookmarkManager.ImpressionsTilDismiss",
           displayed_count);
       break;
     case signin_metrics::AccessPoint::ACCESS_POINT_SETTINGS:
-      UMA_HISTOGRAM_COUNTS_100(
+      base::UmaHistogramCounts100(
           "MobileSignInPromo.SettingsManager.ImpressionsTilDismiss",
           displayed_count);
       break;
+    case signin_metrics::AccessPoint::ACCESS_POINT_NTP_FEED_TOP_PROMO:
+      base::UmaHistogramCounts100(
+          "MobileSignInPromo.NTPFeedTop.ImpressionsTilDismiss",
+          displayed_count);
+      break;
     case signin_metrics::AccessPoint::
         ACCESS_POINT_ENTERPRISE_SIGNOUT_COORDINATOR:
     case signin_metrics::AccessPoint::ACCESS_POINT_RECENT_TABS:
@@ -199,15 +211,20 @@
     int displayed_count) {
   switch (access_point) {
     case signin_metrics::AccessPoint::ACCESS_POINT_BOOKMARK_MANAGER:
-      UMA_HISTOGRAM_COUNTS_100(
+      base::UmaHistogramCounts100(
           "MobileSignInPromo.BookmarkManager.ImpressionsTilXButton",
           displayed_count);
       break;
     case signin_metrics::AccessPoint::ACCESS_POINT_SETTINGS:
-      UMA_HISTOGRAM_COUNTS_100(
+      base::UmaHistogramCounts100(
           "MobileSignInPromo.SettingsManager.ImpressionsTilXButton",
           displayed_count);
       break;
+    case signin_metrics::AccessPoint::ACCESS_POINT_NTP_FEED_TOP_PROMO:
+      base::UmaHistogramCounts100(
+          "MobileSignInPromo.NTPFeedTop.ImpressionsTilXButton",
+          displayed_count);
+      break;
     case signin_metrics::AccessPoint::
         ACCESS_POINT_ENTERPRISE_SIGNOUT_COORDINATOR:
     case signin_metrics::AccessPoint::ACCESS_POINT_RECENT_TABS:
@@ -256,6 +273,8 @@
       return prefs::kIosBookmarkSigninPromoDisplayedCount;
     case signin_metrics::AccessPoint::ACCESS_POINT_SETTINGS:
       return prefs::kIosSettingsSigninPromoDisplayedCount;
+    case signin_metrics::AccessPoint::ACCESS_POINT_NTP_FEED_TOP_PROMO:
+      return prefs::kIosNtpFeedTopSigninPromoDisplayedCount;
     case signin_metrics::AccessPoint::ACCESS_POINT_RECENT_TABS:
     case signin_metrics::AccessPoint::ACCESS_POINT_TAB_SWITCHER:
     case signin_metrics::AccessPoint::ACCESS_POINT_START_PAGE:
@@ -302,6 +321,8 @@
       return prefs::kIosBookmarkPromoAlreadySeen;
     case signin_metrics::AccessPoint::ACCESS_POINT_SETTINGS:
       return prefs::kIosSettingsPromoAlreadySeen;
+    case signin_metrics::AccessPoint::ACCESS_POINT_NTP_FEED_TOP_PROMO:
+      return prefs::kIosNtpFeedTopPromoAlreadySeen;
     case signin_metrics::AccessPoint::ACCESS_POINT_RECENT_TABS:
     case signin_metrics::AccessPoint::ACCESS_POINT_TAB_SWITCHER:
     case signin_metrics::AccessPoint::ACCESS_POINT_START_PAGE:
@@ -391,6 +412,10 @@
   registry->RegisterBooleanPref(prefs::kIosSettingsPromoAlreadySeen, false);
   registry->RegisterIntegerPref(prefs::kIosSettingsSigninPromoDisplayedCount,
                                 0);
+  // NTP Feed
+  registry->RegisterBooleanPref(prefs::kIosNtpFeedTopPromoAlreadySeen, false);
+  registry->RegisterIntegerPref(prefs::kIosNtpFeedTopSigninPromoDisplayedCount,
+                                0);
 }
 
 + (BOOL)shouldDisplaySigninPromoViewWithAccessPoint:
diff --git a/ios/chrome/browser/ui/content_suggestions/ntp_home_mediator.h b/ios/chrome/browser/ui/content_suggestions/ntp_home_mediator.h
index e226f03..e836077 100644
--- a/ios/chrome/browser/ui/content_suggestions/ntp_home_mediator.h
+++ b/ios/chrome/browser/ui/content_suggestions/ntp_home_mediator.h
@@ -26,6 +26,7 @@
 @class ContentSuggestionsCollectionViewController;
 @protocol FeedControlDelegate;
 @class FeedMetricsRecorder;
+class GURL;
 @protocol LogoVendor;
 @class NewTabPageViewController;
 @protocol NTPHomeConsumer;
@@ -112,6 +113,10 @@
 // feed menu.
 - (void)handleFeedLearnMoreTapped;
 
+// Handles the actions following a tap on the "Visit Site" item in the followed
+// item edit menu of the follow management page.
+- (void)handleVisitSiteFromFollowManagementList:(const GURL&)url;
+
 @end
 
 #endif  // IOS_CHROME_BROWSER_UI_CONTENT_SUGGESTIONS_NTP_HOME_MEDIATOR_H_
diff --git a/ios/chrome/browser/ui/content_suggestions/ntp_home_mediator.mm b/ios/chrome/browser/ui/content_suggestions/ntp_home_mediator.mm
index c3cf3f70..aff9877e 100644
--- a/ios/chrome/browser/ui/content_suggestions/ntp_home_mediator.mm
+++ b/ios/chrome/browser/ui/content_suggestions/ntp_home_mediator.mm
@@ -260,6 +260,11 @@
   [self.feedMetricsRecorder recordHeaderMenuLearnMoreTapped];
 }
 
+- (void)handleVisitSiteFromFollowManagementList:(const GURL&)url {
+  // TODO(crbug.com/1331102): Add metrics.
+  [self openMenuItemWebPage:url];
+}
+
 #pragma mark - Properties.
 
 - (void)setWebState:(web::WebState*)webState {
diff --git a/ios/chrome/browser/ui/context_menu/BUILD.gn b/ios/chrome/browser/ui/context_menu/BUILD.gn
index 9237cdd5..f1b8ccaf 100644
--- a/ios/chrome/browser/ui/context_menu/BUILD.gn
+++ b/ios/chrome/browser/ui/context_menu/BUILD.gn
@@ -48,12 +48,6 @@
   sources = [
     "context_menu_utils.h",
     "context_menu_utils.mm",
-    "image_preview_view_controller.h",
-    "image_preview_view_controller.mm",
-    "link_no_preview_view.h",
-    "link_no_preview_view.mm",
-    "link_no_preview_view_controller.h",
-    "link_no_preview_view_controller.mm",
   ]
   deps = [
     "//base",
diff --git a/ios/chrome/browser/ui/context_menu/context_menu_configuration_provider.mm b/ios/chrome/browser/ui/context_menu/context_menu_configuration_provider.mm
index 63561300..ee99ccce 100644
--- a/ios/chrome/browser/ui/context_menu/context_menu_configuration_provider.mm
+++ b/ios/chrome/browser/ui/context_menu/context_menu_configuration_provider.mm
@@ -24,8 +24,6 @@
 #import "ios/chrome/browser/ui/commands/reading_list_add_command.h"
 #import "ios/chrome/browser/ui/commands/search_image_with_lens_command.h"
 #import "ios/chrome/browser/ui/context_menu/context_menu_utils.h"
-#import "ios/chrome/browser/ui/context_menu/image_preview_view_controller.h"
-#import "ios/chrome/browser/ui/context_menu/link_no_preview_view_controller.h"
 #import "ios/chrome/browser/ui/image_util/image_copier.h"
 #import "ios/chrome/browser/ui/image_util/image_saver.h"
 #import "ios/chrome/browser/ui/incognito_reauth/incognito_reauth_commands.h"
@@ -299,19 +297,14 @@
 
   NSString* menuTitle = nil;
   if (isLink || isImage) {
-    if (!base::FeatureList::IsEnabled(
-            web::features::kWebViewNativeContextMenuPhase2)) {
-      menuTitle = GetContextMenuTitle(params);
+    menuTitle = GetContextMenuTitle(params);
 
-      // Truncate context meny titles that originate from URLs, leaving text
-      // titles untruncated.
-      if (!IsImageTitle(params) &&
-          menuTitle.length > kContextMenuMaxURLTitleLength + 1) {
-        menuTitle = [[menuTitle substringToIndex:kContextMenuMaxURLTitleLength]
-            stringByAppendingString:kContextMenuEllipsis];
-      }
-    } else if (!isLink) {
-      menuTitle = GetContextMenuTitle(params);
+    // Truncate context meny titles that originate from URLs, leaving text
+    // titles untruncated.
+    if (!IsImageTitle(params) &&
+        menuTitle.length > kContextMenuMaxURLTitleLength + 1) {
+      menuTitle = [[menuTitle substringToIndex:kContextMenuMaxURLTitleLength]
+          stringByAppendingString:kContextMenuEllipsis];
     }
   }
 
@@ -323,55 +316,9 @@
         return menu;
       };
 
-  UIContextMenuContentPreviewProvider previewProvider = nil;
-  if (isLink || isImage) {
-    previewProvider = ^UIViewController* {
-      if (!weakSelf || !base::FeatureList::IsEnabled(
-                           web::features::kWebViewNativeContextMenuPhase2)) {
-        return nil;
-      }
-      if (isLink) {
-        NSString* title = GetContextMenuTitle(params);
-        NSString* subtitle = GetContextMenuSubtitle(params);
-        LinkNoPreviewViewController* previewViewController =
-            [[LinkNoPreviewViewController alloc] initWithTitle:title
-                                                      subtitle:subtitle];
-
-        __weak LinkNoPreviewViewController* weakPreview = previewViewController;
-        FaviconLoader* faviconLoader =
-            IOSChromeFaviconLoaderFactory::GetForBrowserState(
-                weakSelf.browser->GetBrowserState());
-        faviconLoader->FaviconForPageUrl(
-            linkURL, kDesiredSmallFaviconSizePt, kDesiredSmallFaviconSizePt,
-            /*fallback_to_google_server=*/false,
-            ^(FaviconAttributes* attributes) {
-              [weakPreview configureFaviconWithAttributes:attributes];
-            });
-        return previewViewController;
-      }
-      DCHECK(isImage);
-      ImagePreviewViewController* preview = [[ImagePreviewViewController alloc]
-          initWithPreferredContentSize:CGSizeMake(params.natural_width,
-                                                  params.natural_height)];
-      if (params.screenshot) {
-        [preview updateImage:params.screenshot];
-      }
-      __weak ImagePreviewViewController* weakPreview = preview;
-
-      ImageFetchTabHelper* imageFetcher =
-          ImageFetchTabHelper::FromWebState(weakSelf.currentWebState);
-      DCHECK(imageFetcher);
-      imageFetcher->GetImageData(imageURL, referrer, ^(NSData* data) {
-        [weakPreview updateImageData:data];
-      });
-
-      return preview;
-    };
-  }
-
   return
       [UIContextMenuConfiguration configurationWithIdentifier:nil
-                                              previewProvider:previewProvider
+                                              previewProvider:nil
                                                actionProvider:actionProvider];
 }
 
diff --git a/ios/chrome/browser/ui/context_menu/context_menu_egtest.mm b/ios/chrome/browser/ui/context_menu/context_menu_egtest.mm
index 9d35c2d..a213802 100644
--- a/ios/chrome/browser/ui/context_menu/context_menu_egtest.mm
+++ b/ios/chrome/browser/ui/context_menu/context_menu_egtest.mm
@@ -361,100 +361,8 @@
       assertWithMatcher:grey_notNil()];
 }
 
-// Tests context menu for image that are also links.
-- (void)testContextMenuImageLink {
-  if (![ChromeEarlGrey isContextMenuInWebViewEnabled]) {
-    EARL_GREY_TEST_SKIPPED(@"Test for the new implementation of context menu");
-  }
-
-  const GURL shortTileURL = self.testServer->GetURL(kLinkImagePageUrl);
-  [ChromeEarlGrey loadURL:shortTileURL];
-  [ChromeEarlGrey waitForPageToFinishLoading];
-  [ChromeEarlGrey waitForWebStateZoomScale:1.0];
-
-  LongPressElement(kLogoPageChromiumImageId);
-  // Check that the title is shown.
-  std::string displayedHost = shortTileURL.host() + ":" + shortTileURL.port();
-  [[EarlGrey selectElementWithMatcher:
-                 grey_allOf(chrome_test_util::StaticTextWithAccessibilityLabel(
-                                base::SysUTF8ToNSString(displayedHost)),
-                            grey_not(grey_ancestor(grey_kindOfClassName(
-                                @"LocationBarSteadyView"))),
-                            nil)] assertWithMatcher:grey_notNil()];
-  // Check the subtitle is shown.
-  const GURL shortLinkHrefURL =
-      self.testServer->GetURL(base::SysNSStringToUTF8(kShortLinkHref));
-  [[EarlGrey selectElementWithMatcher:chrome_test_util::ContainsPartialText(
-                                          base::SysUTF8ToNSString(
-                                              shortLinkHrefURL.spec()))]
-      assertWithMatcher:grey_notNil()];
-
-  ClearContextMenu();
-}
-
 // Tests context menu title truncation cases.
 - (void)testContextMenuTitleTruncation {
-  if (![ChromeEarlGrey isContextMenuInWebViewEnabled]) {
-    EARL_GREY_TEST_SKIPPED(@"Test for the new implementation of context menu");
-  }
-
-  const GURL shortTileURL = self.testServer->GetURL(kShortTruncationPageUrl);
-  [ChromeEarlGrey loadURL:shortTileURL];
-  [ChromeEarlGrey waitForPageToFinishLoading];
-  [ChromeEarlGrey waitForWebStateZoomScale:1.0];
-
-  LongPressElement(kLogoPageChromiumImageId);
-  [[EarlGrey selectElementWithMatcher:grey_text(kShortImgTitle)]
-      assertWithMatcher:grey_notNil()];
-  ClearContextMenu();
-
-  LongPressElement(kInitialPageDestinationLinkId);
-  // Check that the title is shown.
-  std::string displayedHost = shortTileURL.host() + ":" + shortTileURL.port();
-  [[EarlGrey selectElementWithMatcher:
-                 grey_allOf(chrome_test_util::StaticTextWithAccessibilityLabel(
-                                base::SysUTF8ToNSString(displayedHost)),
-                            grey_not(grey_ancestor(grey_kindOfClassName(
-                                @"LocationBarSteadyView"))),
-                            nil)] assertWithMatcher:grey_notNil()];
-
-  // Check the subtitle is shown.
-  const GURL shortLinkHrefURL =
-      self.testServer->GetURL(base::SysNSStringToUTF8(kShortLinkHref));
-  [[EarlGrey selectElementWithMatcher:chrome_test_util::ContainsPartialText(
-                                          base::SysUTF8ToNSString(
-                                              shortLinkHrefURL.spec()))]
-      assertWithMatcher:grey_notNil()];
-  ClearContextMenu();
-
-  // Check the long title.
-  const GURL longTitleURL = self.testServer->GetURL(kLongTruncationPageUrl);
-  [ChromeEarlGrey loadURL:longTitleURL];
-  [ChromeEarlGrey waitForPageToFinishLoading];
-  [ChromeEarlGrey waitForWebStateZoomScale:1.0];
-
-  LongPressElement(kLogoPageChromiumImageId);
-  [[EarlGrey selectElementWithMatcher:grey_text(kLongImgTitle)]
-      assertWithMatcher:grey_notNil()];
-  ClearContextMenu();
-
-  LongPressElement(kInitialPageDestinationLinkId);
-  // The menu will be fully displayed (the truncation is done by UILabel).
-  const GURL longLinkHrefURL =
-      self.testServer->GetURL(base::SysNSStringToUTF8(kLongLinkHref));
-  [[EarlGrey selectElementWithMatcher:chrome_test_util::ContainsPartialText(
-                                          base::SysUTF8ToNSString(
-                                              longLinkHrefURL.spec()))]
-      assertWithMatcher:grey_notNil()];
-  ClearContextMenu();
-}
-
-// Tests context menu title truncation cases.
-- (void)testLegacyContextMenuTitleTruncation {
-  if ([ChromeEarlGrey isContextMenuInWebViewEnabled]) {
-    EARL_GREY_TEST_SKIPPED(@"Test for the old implementation of context menu");
-  }
-
   const GURL shortTtileURL = self.testServer->GetURL(kShortTruncationPageUrl);
   [ChromeEarlGrey loadURL:shortTtileURL];
   [ChromeEarlGrey waitForPageToFinishLoading];
@@ -679,38 +587,6 @@
   [ChromeEarlGrey waitForMainTabCount:0 inWindowWithNumber:1];
 }
 
-// Tests that tapping on the preview loads the page pointed by the destination
-// URL.
-- (void)testTappingOnPreview {
-  if (![ChromeEarlGrey isContextMenuInWebViewEnabled]) {
-    EARL_GREY_TEST_SKIPPED(@"Test for the new implementation of context menu");
-  }
-
-  const GURL initialURL = self.testServer->GetURL(kInitialPageUrl);
-  [ChromeEarlGrey loadURL:initialURL];
-  [ChromeEarlGrey
-      waitForWebStateContainingText:kInitialPageDestinationLinkText];
-  [ChromeEarlGrey waitForWebStateZoomScale:1.0];
-
-  LongPressElement(kInitialPageDestinationLinkId);
-
-  const GURL destinationURL = self.testServer->GetURL(kDestinationPageUrl);
-
-  [[EarlGrey
-      selectElementWithMatcher:grey_allOf(grey_accessibilityLabel(
-                                              base::SysUTF8ToNSString(
-                                                  destinationURL.spec())),
-                                          grey_sufficientlyVisible(), nil)]
-      performAction:grey_tap()];
-
-  [ChromeEarlGrey waitForWebStateContainingText:kDestinationPageText];
-  [ChromeEarlGrey waitForMainTabCount:1];
-
-  // Verify url.
-  [[EarlGrey selectElementWithMatcher:OmniboxText(destinationURL.GetContent())]
-      assertWithMatcher:grey_notNil()];
-}
-
 // Checks that JavaScript links only have the "copy" option.
 - (void)testJavaScriptLinks {
   const GURL initialURL = self.testServer->GetURL(kJavaScriptPageUrl);
diff --git a/ios/chrome/browser/ui/context_menu/context_menu_utils.h b/ios/chrome/browser/ui/context_menu/context_menu_utils.h
index a2c9190..685060b 100644
--- a/ios/chrome/browser/ui/context_menu/context_menu_utils.h
+++ b/ios/chrome/browser/ui/context_menu/context_menu_utils.h
@@ -17,7 +17,6 @@
 // Returns the subtitle for the context menu |params|.
 NSString* GetContextMenuSubtitle(web::ContextMenuParams params);
 
-// DEPRECATED.
 // Returns whether the title for context menu |params| is an image title.
 bool IsImageTitle(web::ContextMenuParams params);
 
diff --git a/ios/chrome/browser/ui/context_menu/context_menu_utils.mm b/ios/chrome/browser/ui/context_menu/context_menu_utils.mm
index 97109e5a..a70b6e97 100644
--- a/ios/chrome/browser/ui/context_menu/context_menu_utils.mm
+++ b/ios/chrome/browser/ui/context_menu/context_menu_utils.mm
@@ -27,11 +27,8 @@
 
 typedef std::pair<NSString*, ContextMenuTitleOrigin> TitleAndOrigin;
 
-// DEPRECATED
 // Returns the title and origin for |params|.
 TitleAndOrigin GetContextMenuTitleAndOrigin(web::ContextMenuParams params) {
-  DCHECK(!base::FeatureList::IsEnabled(
-      web::features::kWebViewNativeContextMenuPhase2));
   NSString* title = nil;
   ContextMenuTitleOrigin origin = ContextMenuTitleOrigin::kUnknown;
 
@@ -78,36 +75,7 @@
 }  // namespace
 
 NSString* GetContextMenuTitle(web::ContextMenuParams params) {
-  if (!base::FeatureList::IsEnabled(
-          web::features::kWebViewNativeContextMenuPhase2)) {
-    return GetContextMenuTitleAndOrigin(params).first;
-  }
-  if (params.link_url.is_valid()) {
-    if (params.link_url.SchemeIsHTTPOrHTTPS()) {
-      url_formatter::FormatUrlTypes format_types =
-          url_formatter::kFormatUrlOmitDefaults |
-          url_formatter::kFormatUrlTrimAfterHost |
-          url_formatter::kFormatUrlOmitHTTPS |
-          url_formatter::kFormatUrlOmitTrivialSubdomains;
-
-      std::u16string formatted_url = url_formatter::FormatUrl(
-          params.link_url, format_types, base::UnescapeRule::NORMAL,
-          /*new_parsed=*/nullptr,
-          /*prefix_end=*/nullptr, /*offset_for_adjustment=*/nullptr);
-      return base::SysUTF16ToNSString(formatted_url);
-    } else {
-      return base::SysUTF8ToNSString(params.link_url.scheme());
-    }
-  }
-  NSString* title = params.title_attribute;
-  if (params.alt_text && params.src_url.is_valid()) {
-    if (title) {
-      title = [NSString stringWithFormat:@"%@ – %@", params.alt_text, title];
-    } else {
-      title = params.alt_text;
-    }
-  }
-  return title;
+  return GetContextMenuTitleAndOrigin(params).first;
 }
 
 NSString* GetContextMenuSubtitle(web::ContextMenuParams params) {
@@ -115,8 +83,6 @@
 }
 
 bool IsImageTitle(web::ContextMenuParams params) {
-  DCHECK(!base::FeatureList::IsEnabled(
-      web::features::kWebViewNativeContextMenuPhase2));
   return GetContextMenuTitleAndOrigin(params).second ==
          ContextMenuTitleOrigin::kImageTitle;
 }
diff --git a/ios/chrome/browser/ui/context_menu/context_menu_utils_unittest.mm b/ios/chrome/browser/ui/context_menu/context_menu_utils_unittest.mm
index 2a9b670c6..03dd0fc8 100644
--- a/ios/chrome/browser/ui/context_menu/context_menu_utils_unittest.mm
+++ b/ios/chrome/browser/ui/context_menu/context_menu_utils_unittest.mm
@@ -95,83 +95,3 @@
   EXPECT_TRUE([GetContextMenuTitle(params) hasSuffix:@(kTitle)]);
   EXPECT_TRUE(IsImageTitle(params));
 }
-
-// Tests that a link with HTTP scheme returns the simplified domain as title.
-TEST_F(ContextMenuUtilsTest, TitleForHTTPLink) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeature(
-      web::features::kWebViewNativeContextMenuPhase2);
-
-  web::ContextMenuParams params;
-  params.link_url = GURL(kLinkUrl);
-  params.title_attribute = @(kTitle);
-
-  EXPECT_NSEQ(@"link.url", GetContextMenuTitle(params));
-}
-
-// Tests that a link with a JavaScript scheme returns the scheme.
-TEST_F(ContextMenuUtilsTest, TitleForJavaScript) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeature(
-      web::features::kWebViewNativeContextMenuPhase2);
-
-  web::ContextMenuParams params;
-  params.link_url = GURL(kJavaScriptLinkUrl);
-  params.title_attribute = @(kTitle);
-
-  EXPECT_NSEQ(@"javascript", GetContextMenuTitle(params));
-}
-
-// Tests that a link with a data scheme returns the scheme.
-TEST_F(ContextMenuUtilsTest, TitleForData) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeature(
-      web::features::kWebViewNativeContextMenuPhase2);
-
-  web::ContextMenuParams params;
-  params.link_url = GURL(kDataUrl);
-  params.title_attribute = @(kTitle);
-
-  EXPECT_NSEQ(@"data", GetContextMenuTitle(params));
-}
-
-// Tests that the title is returned when there is no alt text.
-TEST_F(ContextMenuUtilsTest, TitleForTitle) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeature(
-      web::features::kWebViewNativeContextMenuPhase2);
-
-  web::ContextMenuParams params;
-  params.src_url = GURL(kSrcUrl);
-  params.title_attribute = @(kTitle);
-
-  EXPECT_NSEQ(@(kTitle), GetContextMenuTitle(params));
-}
-
-// Tests that the alt text is returned when there is no title.
-TEST_F(ContextMenuUtilsTest, TitleForAlt) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeature(
-      web::features::kWebViewNativeContextMenuPhase2);
-
-  web::ContextMenuParams params;
-  params.src_url = GURL(kSrcUrl);
-  params.alt_text = @(kAltText);
-
-  EXPECT_NSEQ(@(kAltText), GetContextMenuTitle(params));
-}
-
-// Tests that the title and the alt text are returned.
-TEST_F(ContextMenuUtilsTest, TitleForTitleAndAlt) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeature(
-      web::features::kWebViewNativeContextMenuPhase2);
-
-  web::ContextMenuParams params;
-  params.src_url = GURL(kSrcUrl);
-  params.title_attribute = @(kTitle);
-  params.alt_text = @(kAltText);
-
-  NSString* expected = [NSString stringWithFormat:@"%s – %s", kAltText, kTitle];
-  EXPECT_NSEQ(expected, GetContextMenuTitle(params));
-}
diff --git a/ios/chrome/browser/ui/context_menu/image_preview_view_controller.h b/ios/chrome/browser/ui/context_menu/image_preview_view_controller.h
deleted file mode 100644
index aaa9b8d..0000000
--- a/ios/chrome/browser/ui/context_menu/image_preview_view_controller.h
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright 2021 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 IOS_CHROME_BROWSER_UI_CONTEXT_MENU_IMAGE_PREVIEW_VIEW_CONTROLLER_H_
-#define IOS_CHROME_BROWSER_UI_CONTEXT_MENU_IMAGE_PREVIEW_VIEW_CONTROLLER_H_
-
-#import <UIKit/UIKit.h>
-
-// View Controller showing a preview of an image.
-@interface ImagePreviewViewController : UIViewController
-
-// Initializes with |preferredContentSize|.
-- (instancetype)initWithPreferredContentSize:(CGSize)preferredContentSize
-    NS_DESIGNATED_INITIALIZER;
-
-- (instancetype)init NS_UNAVAILABLE;
-- (instancetype)initWithCoder:(NSCoder*)coder NS_UNAVAILABLE;
-
-- (instancetype)initWithNibName:(NSString*)nibNameOrNil
-                         bundle:(NSBundle*)nibBundleOrNil NS_UNAVAILABLE;
-
-// Sets the displayed image to |image|.
-- (void)updateImage:(UIImage*)image;
-
-// Sets the displayed image to |data|.
-- (void)updateImageData:(NSData*)data;
-
-@end
-
-#endif  // IOS_CHROME_BROWSER_UI_CONTEXT_MENU_IMAGE_PREVIEW_VIEW_CONTROLLER_H_
diff --git a/ios/chrome/browser/ui/context_menu/image_preview_view_controller.mm b/ios/chrome/browser/ui/context_menu/image_preview_view_controller.mm
deleted file mode 100644
index ccaa16ce..0000000
--- a/ios/chrome/browser/ui/context_menu/image_preview_view_controller.mm
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright 2021 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.
-
-#import "ios/chrome/browser/ui/context_menu/image_preview_view_controller.h"
-
-#if !defined(__has_feature) || !__has_feature(objc_arc)
-#error "This file requires ARC support."
-#endif
-
-namespace {
-const CGFloat kDefaultSize = 50;
-}  // namespace
-
-@interface ImagePreviewViewController ()
-
-// The image view containing the image.
-@property(nonatomic, strong) UIImageView* view;
-
-@end
-
-@implementation ImagePreviewViewController
-
-@dynamic view;
-
-#pragma mark - Public
-
-- (instancetype)initWithPreferredContentSize:(CGSize)preferredContentSize {
-  self = [super initWithNibName:nil bundle:nil];
-  if (self) {
-    if (preferredContentSize.width <= 0.0 ||
-        preferredContentSize.height <= 0.0) {
-      self.preferredContentSize = CGSizeMake(kDefaultSize, kDefaultSize);
-    } else {
-      self.preferredContentSize = preferredContentSize;
-    }
-  }
-  return self;
-}
-
-- (void)loadView {
-  self.view = [[UIImageView alloc] init];
-}
-
-- (void)updateImage:(UIImage*)image {
-  self.view.image = image;
-}
-
-- (void)updateImageData:(NSData*)data {
-  UIImage* image = [UIImage imageWithData:data];
-  self.view.image = image;
-
-  // UIPreviewProvider cannot animate |preferredContentSize| changes.
-  // Changing |preferredContentSize| during animation will make it glitch.
-  // See crbug.com/1288017.
-  // Here we set |preferredContentSize| as a last resort, if
-  // the |preferredContentSize| provided during initialization is invalid.
-  if (self.preferredContentSize.width == kDefaultSize &&
-      self.preferredContentSize.height == kDefaultSize) {
-    self.preferredContentSize = image.size;
-  }
-}
-
-@end
diff --git a/ios/chrome/browser/ui/context_menu/link_no_preview_view.h b/ios/chrome/browser/ui/context_menu/link_no_preview_view.h
deleted file mode 100644
index 0a022a5..0000000
--- a/ios/chrome/browser/ui/context_menu/link_no_preview_view.h
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright 2021 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 IOS_CHROME_BROWSER_UI_CONTEXT_MENU_LINK_NO_PREVIEW_VIEW_H_
-#define IOS_CHROME_BROWSER_UI_CONTEXT_MENU_LINK_NO_PREVIEW_VIEW_H_
-
-#import <UIKit/UIKit.h>
-
-@class FaviconAttributes;
-
-// View showing the information for a link when a preview of the destination is
-// not displayed.
-@interface LinkNoPreviewView : UIView
-
-// Initializes the view with its |title| and |subtitle|.
-- (instancetype)initWithTitle:(NSString*)title subtitle:(NSString*)subtitle;
-
-// Sets the favicon for the preview.
-- (void)configureWithAttributes:(FaviconAttributes*)attributes;
-
-@end
-
-#endif  // IOS_CHROME_BROWSER_UI_CONTEXT_MENU_LINK_NO_PREVIEW_VIEW_H_
diff --git a/ios/chrome/browser/ui/context_menu/link_no_preview_view.mm b/ios/chrome/browser/ui/context_menu/link_no_preview_view.mm
deleted file mode 100644
index a4b0826..0000000
--- a/ios/chrome/browser/ui/context_menu/link_no_preview_view.mm
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright 2021 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.
-
-#import "ios/chrome/browser/ui/context_menu/link_no_preview_view.h"
-
-#import "ios/chrome/common/ui/colors/semantic_color_names.h"
-#import "ios/chrome/common/ui/favicon/favicon_container_view.h"
-#import "ios/chrome/common/ui/favicon/favicon_view.h"
-
-#if !defined(__has_feature) || !__has_feature(objc_arc)
-#error "This file requires ARC support."
-#endif
-
-namespace {
-
-// Padding for top.
-const CGFloat kPaddingTopMargin = 14;
-// Padding for leading/trailing/bottom.
-const CGFloat kPaddingMargin = 16;
-// Margin between the favicon and the text.
-const CGFloat kFaviconToTextMargin = 12;
-// Number of lines for the subtitle.
-const CGFloat kNumberOfSubtitleLines = 3;
-
-}  // namespace
-
-@interface LinkNoPreviewView ()
-
-@property(nonatomic, strong) UILabel* title;
-
-@property(nonatomic, strong) UILabel* subtitle;
-
-@property(nonatomic, strong) FaviconContainerView* faviconContainer;
-
-@end
-
-@implementation LinkNoPreviewView
-
-- (instancetype)initWithTitle:(NSString*)title subtitle:(NSString*)subtitle {
-  self = [super init];
-  if (self) {
-    _title = [[UILabel alloc] init];
-    _title.translatesAutoresizingMaskIntoConstraints = NO;
-    _title.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
-    _title.adjustsFontForContentSizeCategory = YES;
-    _title.textColor = [UIColor colorNamed:kTextPrimaryColor];
-    _title.text = title;
-    [self addSubview:_title];
-
-    _subtitle = [[UILabel alloc] init];
-    _subtitle.translatesAutoresizingMaskIntoConstraints = NO;
-    _subtitle.font = [UIFont preferredFontForTextStyle:UIFontTextStyleFootnote];
-    _subtitle.adjustsFontForContentSizeCategory = YES;
-    _subtitle.numberOfLines = kNumberOfSubtitleLines;
-    _subtitle.textColor = [UIColor colorNamed:kTextSecondaryColor];
-    _subtitle.lineBreakMode = NSLineBreakByCharWrapping;
-    _subtitle.text = subtitle;
-    [self addSubview:_subtitle];
-
-    _faviconContainer = [[FaviconContainerView alloc] init];
-    _faviconContainer.translatesAutoresizingMaskIntoConstraints = NO;
-    [self addSubview:_faviconContainer];
-
-    [NSLayoutConstraint activateConstraints:@[
-      [_title.topAnchor constraintEqualToAnchor:self.topAnchor
-                                       constant:kPaddingTopMargin],
-      [_title.trailingAnchor constraintEqualToAnchor:self.trailingAnchor
-                                            constant:-kPaddingMargin],
-
-      [_subtitle.topAnchor constraintEqualToAnchor:_title.bottomAnchor],
-
-      [_subtitle.leadingAnchor constraintEqualToAnchor:_title.leadingAnchor],
-      [_subtitle.trailingAnchor constraintEqualToAnchor:_title.trailingAnchor],
-      [_subtitle.bottomAnchor constraintEqualToAnchor:self.bottomAnchor
-                                             constant:-kPaddingMargin],
-
-      [_faviconContainer.leadingAnchor
-          constraintEqualToAnchor:self.leadingAnchor
-                         constant:kPaddingMargin],
-      [_faviconContainer.trailingAnchor
-          constraintEqualToAnchor:_title.leadingAnchor
-                         constant:-kFaviconToTextMargin],
-      [_faviconContainer.centerYAnchor
-          constraintEqualToAnchor:self.centerYAnchor],
-    ]];
-  }
-  return self;
-}
-
-- (void)configureWithAttributes:(FaviconAttributes*)attributes {
-  [self.faviconContainer.faviconView configureWithAttributes:attributes];
-}
-
-@end
diff --git a/ios/chrome/browser/ui/context_menu/link_no_preview_view_controller.h b/ios/chrome/browser/ui/context_menu/link_no_preview_view_controller.h
deleted file mode 100644
index ef452b4..0000000
--- a/ios/chrome/browser/ui/context_menu/link_no_preview_view_controller.h
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright 2021 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 IOS_CHROME_BROWSER_UI_CONTEXT_MENU_LINK_NO_PREVIEW_VIEW_CONTROLLER_H_
-#define IOS_CHROME_BROWSER_UI_CONTEXT_MENU_LINK_NO_PREVIEW_VIEW_CONTROLLER_H_
-
-#import <UIKit/UIKit.h>
-
-@class FaviconAttributes;
-
-// View Controller showing the information for a link when a preview of the
-// destination is not displayed.
-@interface LinkNoPreviewViewController : UIViewController
-
-// Initializes with the |title| and |subtitle| to be displayed.
-- (instancetype)initWithTitle:(NSString*)title
-                     subtitle:(NSString*)subtitle NS_DESIGNATED_INITIALIZER;
-
-- (instancetype)init NS_UNAVAILABLE;
-- (instancetype)initWithCoder:(NSCoder*)coder NS_UNAVAILABLE;
-
-- (instancetype)initWithNibName:(NSString*)nibNameOrNil
-                         bundle:(NSBundle*)nibBundleOrNil NS_UNAVAILABLE;
-
-// Configures the Favicon with |attributes|.
-- (void)configureFaviconWithAttributes:(FaviconAttributes*)attributes;
-
-@end
-
-#endif  // IOS_CHROME_BROWSER_UI_CONTEXT_MENU_LINK_NO_PREVIEW_VIEW_CONTROLLER_H_
diff --git a/ios/chrome/browser/ui/context_menu/link_no_preview_view_controller.mm b/ios/chrome/browser/ui/context_menu/link_no_preview_view_controller.mm
deleted file mode 100644
index d6c9be4..0000000
--- a/ios/chrome/browser/ui/context_menu/link_no_preview_view_controller.mm
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright 2021 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.
-
-#import "ios/chrome/browser/ui/context_menu/link_no_preview_view_controller.h"
-
-#import "ios/chrome/browser/ui/context_menu/link_no_preview_view.h"
-
-#if !defined(__has_feature) || !__has_feature(objc_arc)
-#error "This file requires ARC support."
-#endif
-
-@interface LinkNoPreviewViewController ()
-
-@property(nonatomic, copy) NSString* contextMenuTitle;
-@property(nonatomic, copy) NSString* subtitle;
-@property(nonatomic, strong) LinkNoPreviewView* view;
-
-@end
-
-@implementation LinkNoPreviewViewController
-
-@dynamic view;
-
-- (instancetype)initWithTitle:(NSString*)title subtitle:(NSString*)subtitle {
-  self = [super initWithNibName:nil bundle:nil];
-  if (self) {
-    _contextMenuTitle = title;
-    _subtitle = subtitle;
-  }
-  return self;
-}
-
-- (void)configureFaviconWithAttributes:(FaviconAttributes*)attributes {
-  [self.view configureWithAttributes:attributes];
-}
-
-- (void)loadView {
-  self.view = [[LinkNoPreviewView alloc] initWithTitle:self.contextMenuTitle
-                                              subtitle:self.subtitle];
-  self.view.backgroundColor = UIColor.systemBackgroundColor;
-}
-
-- (void)viewDidLayoutSubviews {
-  self.preferredContentSize = [self computePreferredContentSize];
-}
-
-- (CGSize)computePreferredContentSize {
-  CGFloat width = self.view.bounds.size.width;
-  CGSize minimalSize =
-      [self.view systemLayoutSizeFittingSize:CGSizeMake(width, 0)];
-  return CGSizeMake(width, minimalSize.height);
-}
-
-@end
diff --git a/ios/chrome/browser/ui/fullscreen/animated_scoped_fullscreen_disabler.h b/ios/chrome/browser/ui/fullscreen/animated_scoped_fullscreen_disabler.h
index 06cce69a..3f11106 100644
--- a/ios/chrome/browser/ui/fullscreen/animated_scoped_fullscreen_disabler.h
+++ b/ios/chrome/browser/ui/fullscreen/animated_scoped_fullscreen_disabler.h
@@ -45,7 +45,7 @@
   // A container object for the list of observers.
   __strong AnimatedScopedFullscreenDisablerObserverListContainer*
       observer_list_container_ = nil;
-  // Whether this disabler is contributing to |controller_|'s disabled counter.
+  // Whether this disabler is contributing to `controller_`'s disabled counter.
   bool disabling_ = false;
   // Used to implement animation blocks safely.
   base::WeakPtrFactory<AnimatedScopedFullscreenDisabler> weak_factory_{this};
diff --git a/ios/chrome/browser/ui/fullscreen/animated_scoped_fullscreen_disabler.mm b/ios/chrome/browser/ui/fullscreen/animated_scoped_fullscreen_disabler.mm
index 5879515..e3e1f45 100644
--- a/ios/chrome/browser/ui/fullscreen/animated_scoped_fullscreen_disabler.mm
+++ b/ios/chrome/browser/ui/fullscreen/animated_scoped_fullscreen_disabler.mm
@@ -26,7 +26,7 @@
 // The disabler passed on initialization.
 @property(nonatomic, readonly) AnimatedScopedFullscreenDisabler* disabler;
 
-// Designated initializer for a container containing |disabler|'s observer list.
+// Designated initializer for a container containing `disabler`'s observer list.
 - (instancetype)initWithDisabler:(AnimatedScopedFullscreenDisabler*)disabler
     NS_DESIGNATED_INITIALIZER;
 - (instancetype)init NS_UNAVAILABLE;
@@ -149,7 +149,7 @@
             animation_completed.Run();
         }];
   } else {
-    // If |controller_| is already disabled, no animation is necessary.
+    // If `controller_` is already disabled, no animation is necessary.
     controller_->IncrementDisabledCounter();
   }
 }
diff --git a/ios/chrome/browser/ui/fullscreen/chrome_coordinator+fullscreen_disabling.mm b/ios/chrome/browser/ui/fullscreen/chrome_coordinator+fullscreen_disabling.mm
index e0f8fef..7c29155 100644
--- a/ios/chrome/browser/ui/fullscreen/chrome_coordinator+fullscreen_disabling.mm
+++ b/ios/chrome/browser/ui/fullscreen/chrome_coordinator+fullscreen_disabling.mm
@@ -37,10 +37,10 @@
 @property(nonatomic, readonly) FullscreenController* controller;
 
 // Factory method that returns the disabler wrapper associated with
-// |coordinator|, lazily instantiating it if necessary.
+// `coordinator`, lazily instantiating it if necessary.
 + (instancetype)wrapperForCoordinator:(ChromeCoordinator*)coordinator;
 
-// Initializer for a wrapper that disables |controller|.
+// Initializer for a wrapper that disables `controller`.
 - (instancetype)initWithFullscreenController:(FullscreenController*)controller
     NS_DESIGNATED_INITIALIZER;
 - (instancetype)init NS_UNAVAILABLE;
diff --git a/ios/chrome/browser/ui/fullscreen/fullscreen_animator.h b/ios/chrome/browser/ui/fullscreen/fullscreen_animator.h
index 40b5c2f..de6ddfae 100644
--- a/ios/chrome/browser/ui/fullscreen/fullscreen_animator.h
+++ b/ios/chrome/browser/ui/fullscreen/fullscreen_animator.h
@@ -13,7 +13,7 @@
   EXIT_FULLSCREEN
 };
 
-// Returns the final fullscreen progress for an animation with |style|.
+// Returns the final fullscreen progress for an animation with `style`.
 CGFloat GetFinalFullscreenProgressForAnimation(FullscreenAnimatorStyle style);
 
 // Helper object for animating changes to fullscreen progress.  Subclasses of
@@ -28,7 +28,7 @@
 // The final calculated fullscreen value.
 @property(nonatomic, readonly) CGFloat finalProgress;
 // The current progress value.  This is the fullscreen progress value
-// interpolated between |startProgress| and |finalProgress| using the timing
+// interpolated between `startProgress` and `finalProgress` using the timing
 // curve and the fraction complete of the animation.
 @property(nonatomic, readonly) CGFloat currentProgress;
 
@@ -41,7 +41,7 @@
     NS_UNAVAILABLE;
 - (instancetype)init NS_UNAVAILABLE;
 
-// Returns the progress value corresponding with |position|.
+// Returns the progress value corresponding with `position`.
 - (CGFloat)progressForAnimatingPosition:(UIViewAnimatingPosition)position;
 
 @end
diff --git a/ios/chrome/browser/ui/fullscreen/fullscreen_animator.mm b/ios/chrome/browser/ui/fullscreen/fullscreen_animator.mm
index 8f9dda7..dfd806b 100644
--- a/ios/chrome/browser/ui/fullscreen/fullscreen_animator.mm
+++ b/ios/chrome/browser/ui/fullscreen/fullscreen_animator.mm
@@ -83,8 +83,8 @@
 
 - (void)stopAnimation:(BOOL)withoutFinishing {
   // Record the progress value when transitioning from the active to stopped
-  // state.  This allows |currentProgress| to return the correct value after
-  // stopping, as |fractionComplete| is reset to 0.0 for stopped animators.
+  // state.  This allows `currentProgress` to return the correct value after
+  // stopping, as `fractionComplete` is reset to 0.0 for stopped animators.
   if (self.state == UIViewAnimatingStateActive)
     _progressUponStopping = self.currentProgress;
   if (_progressUponStopping == _startProgress)
diff --git a/ios/chrome/browser/ui/fullscreen/fullscreen_browser_observer.mm b/ios/chrome/browser/ui/fullscreen/fullscreen_browser_observer.mm
index 7b5f9de..a2e2f31 100644
--- a/ios/chrome/browser/ui/fullscreen/fullscreen_browser_observer.mm
+++ b/ios/chrome/browser/ui/fullscreen/fullscreen_browser_observer.mm
@@ -15,7 +15,7 @@
     Browser* browser)
     : web_state_list_observer_(web_state_list_observer) {
   DCHECK(web_state_list_observer_);
-  // TODO(crbug.com/790886): DCHECK |browser| once FullscreenController is fully
+  // TODO(crbug.com/790886): DCHECK `browser` once FullscreenController is fully
   // scoped to a Browser.
   if (browser) {
     web_state_list_observer_->SetWebStateList(browser->GetWebStateList());
diff --git a/ios/chrome/browser/ui/fullscreen/fullscreen_content_adjustment_util.h b/ios/chrome/browser/ui/fullscreen/fullscreen_content_adjustment_util.h
index 64de38f..a1610be 100644
--- a/ios/chrome/browser/ui/fullscreen/fullscreen_content_adjustment_util.h
+++ b/ios/chrome/browser/ui/fullscreen/fullscreen_content_adjustment_util.h
@@ -10,7 +10,7 @@
 @protocol CRWWebViewProxy;
 class FullscreenModel;
 
-// Updates |proxy|'s content offset and top padding to ensure that the content
+// Updates `proxy`'s content offset and top padding to ensure that the content
 // is fully visible under the hdeader.
 void MoveContentBelowHeader(id<CRWWebViewProxy> proxy, FullscreenModel* model);
 
diff --git a/ios/chrome/browser/ui/fullscreen/fullscreen_controller.h b/ios/chrome/browser/ui/fullscreen/fullscreen_controller.h
index 5d7d3f7..ba3dc792 100644
--- a/ios/chrome/browser/ui/fullscreen/fullscreen_controller.h
+++ b/ios/chrome/browser/ui/fullscreen/fullscreen_controller.h
@@ -22,7 +22,7 @@
  public:
   explicit FullscreenController() = default;
 
-  // Retrieves the FullscreenController for |browser|. This should only be
+  // Retrieves the FullscreenController for `browser`. This should only be
   // called with the kFullscreenControllerBrowserScoped turned on.
   static FullscreenController* FromBrowser(Browser* browser);
 
diff --git a/ios/chrome/browser/ui/fullscreen/fullscreen_controller_impl.h b/ios/chrome/browser/ui/fullscreen/fullscreen_controller_impl.h
index 495bf30..8175f68 100644
--- a/ios/chrome/browser/ui/fullscreen/fullscreen_controller_impl.h
+++ b/ios/chrome/browser/ui/fullscreen/fullscreen_controller_impl.h
@@ -58,12 +58,12 @@
   FullscreenModel model_;
   // Object that manages sending signals to FullscreenControllerImplObservers.
   FullscreenMediator mediator_;
-  // A WebStateListObserver that updates |model_| for WebStateList changes.
+  // A WebStateListObserver that updates `model_` for WebStateList changes.
   FullscreenWebStateListObserver web_state_list_observer_;
-  // A FullscreenBrowserObserver that resets |web_state_list_| when the Browser
+  // A FullscreenBrowserObserver that resets `web_state_list_` when the Browser
   // is destroyed.
   FullscreenBrowserObserver fullscreen_browser_observer_;
-  // The bridge used to forward brodcasted UI to |model_|.
+  // The bridge used to forward brodcasted UI to `model_`.
   __strong ChromeBroadcastOberverBridge* bridge_ = nil;
   // A helper object that listens for system notifications.
   __strong FullscreenSystemNotificationObserver* notification_observer_ = nil;
diff --git a/ios/chrome/browser/ui/fullscreen/fullscreen_controller_observer.h b/ios/chrome/browser/ui/fullscreen/fullscreen_controller_observer.h
index d401375c..9fc87c0 100644
--- a/ios/chrome/browser/ui/fullscreen/fullscreen_controller_observer.h
+++ b/ios/chrome/browser/ui/fullscreen/fullscreen_controller_observer.h
@@ -21,26 +21,26 @@
 
   virtual ~FullscreenControllerObserver() = default;
 
-  // Invoked when the maximum or minimum viewport insets for |controller| have
+  // Invoked when the maximum or minimum viewport insets for `controller` have
   // been updated.
   virtual void FullscreenViewportInsetRangeChanged(
       FullscreenController* controller,
       UIEdgeInsets min_viewport_insets,
       UIEdgeInsets max_viewport_insets) {}
 
-  // Invoked after a scrolling event has caused |controller| to calculate
-  // |progress|.  A |progress| value of 1.0 denotes that the toolbar should be
-  // completely visible, while a |progress| value of 0.0 denotes that the
+  // Invoked after a scrolling event has caused `controller` to calculate
+  // `progress`.  A `progress` value of 1.0 denotes that the toolbar should be
+  // completely visible, while a `progress` value of 0.0 denotes that the
   // toolbar should be competely hidden.
   virtual void FullscreenProgressUpdated(FullscreenController* controller,
                                          CGFloat progress) {}
 
-  // Invoked with |controller| is enabled or disabled.
+  // Invoked with `controller` is enabled or disabled.
   virtual void FullscreenEnabledStateChanged(FullscreenController* controller,
                                              bool enabled) {}
 
-  // Invoked when |controller| is about to start an animation with |animator|.
-  // Observers are expected to add animations to update UI for |animator|'s
+  // Invoked when `controller` is about to start an animation with `animator`.
+  // Observers are expected to add animations to update UI for `animator`'s
   // final progress.
   virtual void FullscreenWillAnimate(FullscreenController* controller,
                                      FullscreenAnimator* animator) {}
@@ -50,7 +50,7 @@
   virtual void FullscreenControllerWillShutDown(
       FullscreenController* controller) {}
 
-  // Invoked when |controller| needs to resize its horizontal insets.
+  // Invoked when `controller` needs to resize its horizontal insets.
   // TODO(crbug.com/1114054) remove after fixing multiwindow resizing issue.
   virtual void ResizeHorizontalInsets(FullscreenController* controller) {}
 };
diff --git a/ios/chrome/browser/ui/fullscreen/fullscreen_egtest.mm b/ios/chrome/browser/ui/fullscreen/fullscreen_egtest.mm
index 6db5a25..c78482c 100644
--- a/ios/chrome/browser/ui/fullscreen/fullscreen_egtest.mm
+++ b/ios/chrome/browser/ui/fullscreen/fullscreen_egtest.mm
@@ -44,7 +44,7 @@
       performAction:grey_swipeFastInDirection(kGREYDirectionUp)];
 }
 
-// Asserts that the current URL is the |expectedURL| one.
+// Asserts that the current URL is the `expectedURL` one.
 void AssertURLIs(const GURL& expectedURL) {
   NSString* description = [NSString
       stringWithFormat:@"Timeout waiting for the url to be %@",
@@ -120,7 +120,7 @@
   // Test that the toolbar is still visible after a user swipes down.
   // Use a slow swipe here because in this combination of conditions (one
   // page PDF, overscroll actions enabled, fast swipe), the
-  // |UIScrollViewDelegate scrollViewDidEndDecelerating:| is not called leading
+  // `UIScrollViewDelegate scrollViewDidEndDecelerating:` is not called leading
   // to an EarlGrey infinite wait.
   [[EarlGrey selectElementWithMatcher:WebStateScrollViewMatcher()]
       performAction:grey_swipeSlowInDirection(kGREYDirectionDown)];
diff --git a/ios/chrome/browser/ui/fullscreen/fullscreen_mediator.h b/ios/chrome/browser/ui/fullscreen/fullscreen_mediator.h
index bb980a1..82f7f8d9 100644
--- a/ios/chrome/browser/ui/fullscreen/fullscreen_mediator.h
+++ b/ios/chrome/browser/ui/fullscreen/fullscreen_mediator.h
@@ -72,12 +72,12 @@
   void FullscreenModelScrollEventEnded(FullscreenModel* model) override;
   void FullscreenModelWasReset(FullscreenModel* model) override;
 
-  // Sets up |animator_| with |style|, notifies FullscreenControllerObservers,
+  // Sets up `animator_` with `style`, notifies FullscreenControllerObservers,
   // and starts the animation.
   void AnimateWithStyle(FullscreenAnimatorStyle style);
 
   // Stops the current scroll end animation if one is in progress.  If
-  // |update_model| is true, the FullscreenModel will be updated with the active
+  // `update_model` is true, the FullscreenModel will be updated with the active
   // animator's current progress value.
   void StopAnimating(bool update_model);
 
diff --git a/ios/chrome/browser/ui/fullscreen/fullscreen_model.h b/ios/chrome/browser/ui/fullscreen/fullscreen_model.h
index 5dca44c..4dcbe8d 100644
--- a/ios/chrome/browser/ui/fullscreen/fullscreen_model.h
+++ b/ios/chrome/browser/ui/fullscreen/fullscreen_model.h
@@ -79,7 +79,7 @@
     return GetToolbarInsetsAtProgress(progress_);
   }
 
-  // Returns the toolbar insets at |progress|.
+  // Returns the toolbar insets at `progress`.
   UIEdgeInsets GetToolbarInsetsAtProgress(CGFloat progress) const {
     return UIEdgeInsetsMake(
         GetCollapsedToolbarHeight() + progress * (GetExpandedToolbarHeight() -
@@ -87,7 +87,7 @@
         0, progress * bottom_toolbar_height_, 0);
   }
 
-  // Increments and decrements |disabled_counter_| for features that require the
+  // Increments and decrements `disabled_counter_` for features that require the
   // toolbar be completely visible.
   void IncrementDisabledCounter();
   void DecrementDisabledCounter();
@@ -98,10 +98,10 @@
   // Instructs the model to ignore broadcasted scroll updates for the remainder
   // of the current scroll.  Has no effect if not called while a scroll is
   // occurring.  The model will resume listening for scroll events when
-  // |scrolling_| is reset to false.
+  // `scrolling_` is reset to false.
   void IgnoreRemainderOfCurrentScroll();
 
-  // Called when a scroll end animation finishes.  |progress| is the fullscreen
+  // Called when a scroll end animation finishes.  `progress` is the fullscreen
   // progress corresponding to the final state of the aniamtion.
   void AnimationEndedWithProgress(CGFloat progress);
 
@@ -164,13 +164,13 @@
   bool GetFreezeToolbarHeight() const;
 
  private:
-  // Returns how a scroll to the current |y_content_offset_| from |from_offset|
+  // Returns how a scroll to the current `y_content_offset_` from `from_offset`
   // should be handled.
   enum class ScrollAction : short {
     kIgnore,                       // Ignore the scroll.
-    kUpdateBaseOffset,             // Update |base_offset_| only.
-    kUpdateProgress,               // Update |progress_| only.
-    kUpdateBaseOffsetAndProgress,  // Update |bse_offset_| and |progress_|.
+    kUpdateBaseOffset,             // Update `base_offset_` only.
+    kUpdateProgress,               // Update `progress_` only.
+    kUpdateBaseOffsetAndProgress,  // Update `bse_offset_` and `progress_`.
   };
   ScrollAction ActionForScrollFromOffset(CGFloat from_offset) const;
 
@@ -183,11 +183,11 @@
   void UpdateProgress();
 
   // Updates the disabled counter depending on the current values of
-  // |scroll_view_height_| and |content_height_|.
+  // `scroll_view_height_` and `content_height_`.
   void UpdateDisabledCounterForContentHeight();
 
-  // Setter for |progress_|.  Notifies observers of the new value if
-  // |notify_observers| is true.
+  // Setter for `progress_`.  Notifies observers of the new value if
+  // `notify_observers` is true.
   void SetProgress(CGFloat progress);
 
   // ChromeBroadcastObserverInterface:
@@ -207,7 +207,7 @@
   // The percentage of the toolbar that should be visible, where 1.0 denotes a
   // fully visible toolbar and 0.0 denotes a completely hidden one.
   CGFloat progress_ = 0.0;
-  // The base offset from which to calculate fullscreen state.  When |locked_|
+  // The base offset from which to calculate fullscreen state.  When `locked_`
   // is false, it is reset to the current offset after each scroll event.
   CGFloat base_offset_ = NAN;
   // The height of the toolbars being shown or hidden by this model.
diff --git a/ios/chrome/browser/ui/fullscreen/fullscreen_model.mm b/ios/chrome/browser/ui/fullscreen/fullscreen_model.mm
index 74f4bb3..965a3cb53 100644
--- a/ios/chrome/browser/ui/fullscreen/fullscreen_model.mm
+++ b/ios/chrome/browser/ui/fullscreen/fullscreen_model.mm
@@ -15,7 +15,7 @@
 #endif
 
 namespace {
-// Object that increments |counter| by 1 for its lifetime.
+// Object that increments `counter` by 1 for its lifetime.
 class ScopedIncrementer {
  public:
   explicit ScopedIncrementer(size_t* counter) : counter_(counter) {
diff --git a/ios/chrome/browser/ui/fullscreen/fullscreen_model_observer.h b/ios/chrome/browser/ui/fullscreen/fullscreen_model_observer.h
index 3b9597ad..2d43900 100644
--- a/ios/chrome/browser/ui/fullscreen/fullscreen_model_observer.h
+++ b/ios/chrome/browser/ui/fullscreen/fullscreen_model_observer.h
@@ -19,19 +19,19 @@
 
   virtual ~FullscreenModelObserver() = default;
 
-  // Invoked when |model|'s toolbar heights have been updated.
+  // Invoked when `model`'s toolbar heights have been updated.
   virtual void FullscreenModelToolbarHeightsUpdated(FullscreenModel* model) {}
 
-  // Invoked when |model|'s calculated progress() value is updated.
+  // Invoked when `model`'s calculated progress() value is updated.
   virtual void FullscreenModelProgressUpdated(FullscreenModel* model) {}
 
-  // Invoked when |model| is enabled or disabled.
+  // Invoked when `model` is enabled or disabled.
   virtual void FullscreenModelEnabledStateChanged(FullscreenModel* model) {}
 
-  // Invoked when a scroll event being tracked by |model| has started.
+  // Invoked when a scroll event being tracked by `model` has started.
   virtual void FullscreenModelScrollEventStarted(FullscreenModel* model) {}
 
-  // Invoked when a scroll event being tracked by |model| has ended.
+  // Invoked when a scroll event being tracked by `model` has ended.
   virtual void FullscreenModelScrollEventEnded(FullscreenModel* model) {}
 
   // Invoked when the model is reset.
diff --git a/ios/chrome/browser/ui/fullscreen/fullscreen_model_unittest.mm b/ios/chrome/browser/ui/fullscreen/fullscreen_model_unittest.mm
index f066e91..a3442ad 100644
--- a/ios/chrome/browser/ui/fullscreen/fullscreen_model_unittest.mm
+++ b/ios/chrome/browser/ui/fullscreen/fullscreen_model_unittest.mm
@@ -21,7 +21,7 @@
 const CGFloat kScrollViewHeight = 400.0;
 // The content height used for tests.
 const CGFloat kContentHeight = 5000.0;
-// Converts |insets| to a string for debugging.
+// Converts `insets` to a string for debugging.
 std::string GetStringFromInsets(UIEdgeInsets insets) {
   return base::SysNSStringToUTF8(NSStringFromUIEdgeInsets(insets));
 }
@@ -227,7 +227,7 @@
 
 // Tests that toolbar_insets() returns the correct values.
 TEST_F(FullscreenModelTest, ToolbarInsets) {
-  // Checks whether |insets| are equal to the expected insets at |progress|.
+  // Checks whether `insets` are equal to the expected insets at `progress`.
   void (^check_insets)(UIEdgeInsets insets, CGFloat progress) =
       ^void(UIEdgeInsets insets, CGFloat progress) {
         UIEdgeInsets expected_insets = UIEdgeInsetsMake(
diff --git a/ios/chrome/browser/ui/fullscreen/fullscreen_system_notification_observer.h b/ios/chrome/browser/ui/fullscreen/fullscreen_system_notification_observer.h
index 3919328c..39ae864 100644
--- a/ios/chrome/browser/ui/fullscreen/fullscreen_system_notification_observer.h
+++ b/ios/chrome/browser/ui/fullscreen/fullscreen_system_notification_observer.h
@@ -17,7 +17,7 @@
 // Additionally, this object notifies the mediator of foreground events.
 @interface FullscreenSystemNotificationObserver : NSObject
 
-// Designated initializer that updates |controller| and |mediator| for system
+// Designated initializer that updates `controller` and `mediator` for system
 // notifications.
 - (nullable instancetype)
 initWithController:(nonnull FullscreenController*)controller
diff --git a/ios/chrome/browser/ui/fullscreen/fullscreen_system_notification_observer.mm b/ios/chrome/browser/ui/fullscreen/fullscreen_system_notification_observer.mm
index eaff423..30da5b4 100644
--- a/ios/chrome/browser/ui/fullscreen/fullscreen_system_notification_observer.mm
+++ b/ios/chrome/browser/ui/fullscreen/fullscreen_system_notification_observer.mm
@@ -26,10 +26,10 @@
 // The FullscreenMediator through which foreground events are propagated to
 // FullscreenControllerObservers.
 @property(nonatomic, readonly, nonnull) FullscreenMediator* mediator;
-// Creates or destroys |_voiceOverDisabler| depending on whether VoiceOver is
+// Creates or destroys `_voiceOverDisabler` depending on whether VoiceOver is
 // enabled.
 - (void)voiceOverStatusChanged;
-// Called when the keyboard is shown/hidden to reset |_keyboardDisabler|.
+// Called when the keyboard is shown/hidden to reset `_keyboardDisabler`.
 - (void)keyboardWillShow;
 - (void)keyboardDidHide;
 // Called when the application is foregrounded.
@@ -80,7 +80,7 @@
 }
 
 - (void)dealloc {
-  // |-disconnect| should be called before deallocation.
+  // `-disconnect` should be called before deallocation.
   DCHECK(!_controller);
 }
 
diff --git a/ios/chrome/browser/ui/fullscreen/fullscreen_ui_element.h b/ios/chrome/browser/ui/fullscreen/fullscreen_ui_element.h
index bffdbb0..3ed8e67 100644
--- a/ios/chrome/browser/ui/fullscreen/fullscreen_ui_element.h
+++ b/ios/chrome/browser/ui/fullscreen/fullscreen_ui_element.h
@@ -13,28 +13,28 @@
 // protocol to react to changes in Fullscreen state.
 @protocol FullscreenUIElement<NSObject>
 
-// Tells the UI to update its state for |progress|.  A fullscreen |progress|
-// value denotes that the toolbar should be completely visible, and a |progress|
+// Tells the UI to update its state for `progress`.  A fullscreen `progress`
+// value denotes that the toolbar should be completely visible, and a `progress`
 // value of 0.0 denotes that the toolbar should be completely hidden.
 //
 // This selector is called for every scroll offset, it's not optional, as
-// checking |-respondsToSelector:| for every FullscreenUIElement at every scroll
+// checking `-respondsToSelector:` for every FullscreenUIElement at every scroll
 // offset can introduce performance issues.
 //
 // If the implementation of this selector uses batched layout updates (e.g.
-// updating the UI in |-layoutSubviews| or by updating constraints), then a
-// layout pass should be forced using |-setNeedsLayout| and |-layoutIfNeeded|.
+// updating the UI in `-layoutSubviews` or by updating constraints), then a
+// layout pass should be forced using `-setNeedsLayout` and `-layoutIfNeeded`.
 // This will ensure that the layout is updated for each scroll position rather
 // than batching multiple fullscreen progress updates together.  This is
 // especially important for FullscreenUIElements that do not implement
-// |-animateFullscreenWithAnimator:|, as this selctor is called by
+// `-animateFullscreenWithAnimator:`, as this selctor is called by
 // FullscreenUIUpdater in an animation block.
 - (void)updateForFullscreenProgress:(CGFloat)progress;
 
 @optional
 
 // Tells the UI to update its state after the max and min viewport insets have
-// been updated to new values.  |progress| is the current progress value, and
+// been updated to new values.  `progress` is the current progress value, and
 // can be used to update the UI at the current progress with the new viewport
 // inset range.
 - (void)updateForFullscreenMinViewportInsets:(UIEdgeInsets)minViewportInsets
@@ -42,13 +42,13 @@
 
 // Tells the UI that fullscreen is enabled or disabled.  FullscreenUIUpdater's
 // default behavior if this selector is not implemented is to call
-// |-updateForFullscreenProgress:| with a progress value of 1.0.
+// `-updateForFullscreenProgress:` with a progress value of 1.0.
 - (void)updateForFullscreenEnabled:(BOOL)enabled;
 
 // Called when fullscreen is about to initate an animation.
 // FullscreenUIUpdater's default behavior if this selector is not implemented is
-// to add an animation block calling |-updateForFullscreenProgress:| with a
-// progress value of |animator.finalProgress|.
+// to add an animation block calling `-updateForFullscreenProgress:` with a
+// progress value of `animator.finalProgress`.
 - (void)animateFullscreenWithAnimator:(FullscreenAnimator*)animator;
 
 @end
diff --git a/ios/chrome/browser/ui/fullscreen/fullscreen_ui_updater.h b/ios/chrome/browser/ui/fullscreen/fullscreen_ui_updater.h
index 12440e9..cb90dd4 100644
--- a/ios/chrome/browser/ui/fullscreen/fullscreen_ui_updater.h
+++ b/ios/chrome/browser/ui/fullscreen/fullscreen_ui_updater.h
@@ -15,16 +15,16 @@
 // FullscreenUIElements.
 class FullscreenUIUpdater {
  public:
-  // Constructor for an updater that updates |ui_element| for observed events
-  // from |controller|.  Both arguments must be non-null.  |ui_element| is not
-  // retained.  The updater will observe |controller| until the controller is
+  // Constructor for an updater that updates `ui_element` for observed events
+  // from `controller`.  Both arguments must be non-null.  `ui_element` is not
+  // retained.  The updater will observe `controller` until the controller is
   // shut down or the updater is destroyed.
   FullscreenUIUpdater(FullscreenController* controller,
                       id<FullscreenUIElement> ui_element);
   ~FullscreenUIUpdater();
 
  private:
-  // Stops observing |controller_|.
+  // Stops observing `controller_`.
   void Disconnect();
 
   // Helper object that forwards FullscreenControllerObserver callbacks to their
@@ -32,7 +32,7 @@
   class FullscreenControllerObserverForwarder
       : public FullscreenControllerObserver {
    public:
-    // Constructor for a forwarder that updates |ui_element| for |updater|.
+    // Constructor for a forwarder that updates `ui_element` for `updater`.
     FullscreenControllerObserverForwarder(FullscreenUIUpdater* updater,
                                           id<FullscreenUIElement> ui_element);
 
@@ -59,7 +59,7 @@
   FullscreenController* controller_ = nullptr;
   // The observer forwarder.
   FullscreenControllerObserverForwarder forwarder_;
-  // Scoped observer for |forwarder_|.
+  // Scoped observer for `forwarder_`.
   base::ScopedObservation<FullscreenController, FullscreenControllerObserver>
       observation_{&forwarder_};
 };
diff --git a/ios/chrome/browser/ui/fullscreen/fullscreen_web_state_list_observer.h b/ios/chrome/browser/ui/fullscreen/fullscreen_web_state_list_observer.h
index 0d4dee5..a1259104 100644
--- a/ios/chrome/browser/ui/fullscreen/fullscreen_web_state_list_observer.h
+++ b/ios/chrome/browser/ui/fullscreen/fullscreen_web_state_list_observer.h
@@ -19,8 +19,8 @@
 // FullscreenModel for navigation events.
 class FullscreenWebStateListObserver : public WebStateListObserver {
  public:
-  // Constructor for an observer for |web_state_list| that updates |model|.
-  // |controller| is used to create ScopedFullscreenDisablers for WebState
+  // Constructor for an observer for `web_state_list` that updates `model`.
+  // `controller` is used to create ScopedFullscreenDisablers for WebState
   // navigation events that require the toolbar to be visible.
   FullscreenWebStateListObserver(FullscreenController* controller,
                                  FullscreenModel* model,
@@ -58,13 +58,13 @@
                            int index,
                            bool user_action) override;
 
-  // Called when |web_state| is activated in |web_state_list_|.
+  // Called when `web_state` is activated in `web_state_list_`.
   void WebStateWasActivated(web::WebState* web_state);
 
-  // Called when |web_state| is removed from |web_state_list_|.
+  // Called when `web_state` is removed from `web_state_list_`.
   void WebStateWasRemoved(web::WebState* web_state);
 
-  // Whether |web_state| has been activated during the lifetime of this object.
+  // Whether `web_state` has been activated during the lifetime of this object.
   bool HasWebStateBeenActivated(web::WebState* web_state);
 
   // The controller passed on construction.
@@ -75,7 +75,7 @@
   WebStateList* web_state_list_ = nullptr;
   // The observer for the active WebState.
   FullscreenWebStateObserver web_state_observer_;
-  // The WebStates that have been activated in |web_state_list_|.
+  // The WebStates that have been activated in `web_state_list_`.
   std::set<web::WebState*> activated_web_states_;
 };
 
diff --git a/ios/chrome/browser/ui/fullscreen/fullscreen_web_state_observer.h b/ios/chrome/browser/ui/fullscreen/fullscreen_web_state_observer.h
index 3d00f62..26894a8e 100644
--- a/ios/chrome/browser/ui/fullscreen/fullscreen_web_state_observer.h
+++ b/ios/chrome/browser/ui/fullscreen/fullscreen_web_state_observer.h
@@ -16,13 +16,13 @@
 // A WebStateObserver that updates a FullscreenModel for navigation events.
 class FullscreenWebStateObserver : public web::WebStateObserver {
  public:
-  // Constructor for an observer that updates |controller| and |model|.
+  // Constructor for an observer that updates `controller` and `model`.
   FullscreenWebStateObserver(FullscreenController* controller,
                              FullscreenModel* model,
                              FullscreenMediator* mediator);
   ~FullscreenWebStateObserver() override;
 
-  // Tells the observer to start observing |web_state|.
+  // Tells the observer to start observing `web_state`.
   void SetWebState(web::WebState* web_state);
 
  private:
@@ -41,7 +41,7 @@
   FullscreenModel* model_;
   // The mediator passed on construction.
   FullscreenMediator* mediator_ = nullptr;
-  // Observer for |web_state_|'s scroll view proxy.
+  // Observer for `web_state_`'s scroll view proxy.
   __strong FullscreenWebViewProxyObserver* web_view_proxy_observer_;
   // The URL received in the NavigationContext of the last finished navigation.
   GURL last_navigation_url_;
diff --git a/ios/chrome/browser/ui/fullscreen/fullscreen_web_state_observer.mm b/ios/chrome/browser/ui/fullscreen/fullscreen_web_state_observer.mm
index 71d0d0f..f35105a 100644
--- a/ios/chrome/browser/ui/fullscreen/fullscreen_web_state_observer.mm
+++ b/ios/chrome/browser/ui/fullscreen/fullscreen_web_state_observer.mm
@@ -74,9 +74,9 @@
 
   // Due to limitations in WKWebView's rendering, different MIME types must be
   // inset using different techniques:
-  // - PDFs need to be inset using the scroll view's |contentInset| property or
+  // - PDFs need to be inset using the scroll view's `contentInset` property or
   //   the floating page indicator is laid out incorrectly.
-  // - For normal pages, using |contentInset| breaks the layout of fixed-
+  // - For normal pages, using `contentInset` breaks the layout of fixed-
   //   position DOM elements, so top padding must be accomplished by updating
   //   the WKWebView's frame.
   bool is_pdf = web_state->GetContentsMimeType() == "application/pdf";
diff --git a/ios/chrome/browser/ui/fullscreen/fullscreen_web_view_proxy_observer.h b/ios/chrome/browser/ui/fullscreen/fullscreen_web_view_proxy_observer.h
index c69e7af..e7f3ad0 100644
--- a/ios/chrome/browser/ui/fullscreen/fullscreen_web_view_proxy_observer.h
+++ b/ios/chrome/browser/ui/fullscreen/fullscreen_web_view_proxy_observer.h
@@ -17,7 +17,7 @@
 // The proxy being observed.
 @property(nonatomic, weak, nullable) id<CRWWebViewProxy> proxy;
 
-// Designated initializer for an observer that uses |model| to update its proxy.
+// Designated initializer for an observer that uses `model` to update its proxy.
 - (nullable instancetype)initWithModel:(nonnull FullscreenModel*)model
                               mediator:(nonnull FullscreenMediator*)mediator
     NS_DESIGNATED_INITIALIZER;
diff --git a/ios/chrome/browser/ui/fullscreen/fullscreen_web_view_resizer.h b/ios/chrome/browser/ui/fullscreen/fullscreen_web_view_resizer.h
index dbeadde..9bca1c3 100644
--- a/ios/chrome/browser/ui/fullscreen/fullscreen_web_view_resizer.h
+++ b/ios/chrome/browser/ui/fullscreen/fullscreen_web_view_resizer.h
@@ -18,7 +18,7 @@
 // feature.
 @interface FullscreenWebViewResizer : NSObject
 
-// Initializes the object with the fullscreen |model|, used to get the
+// Initializes the object with the fullscreen `model`, used to get the
 // information about the state of fullscreen.
 - (instancetype)initWithModel:(FullscreenModel*)model NS_DESIGNATED_INITIALIZER;
 
@@ -35,7 +35,7 @@
 // model.
 - (void)updateForCurrentState;
 
-// Force the updates of the WebView to |progress|. |progress| should be between
+// Force the updates of the WebView to `progress`. `progress` should be between
 // 0 and 1, 0 meaning that the application is in fullscreen, 1 that it is out of
 // fullscreen.
 - (void)forceToUpdateToProgress:(CGFloat)progress;
diff --git a/ios/chrome/browser/ui/fullscreen/fullscreen_web_view_resizer.mm b/ios/chrome/browser/ui/fullscreen/fullscreen_web_view_resizer.mm
index 830ef5c..0c462cf8 100644
--- a/ios/chrome/browser/ui/fullscreen/fullscreen_web_view_resizer.mm
+++ b/ios/chrome/browser/ui/fullscreen/fullscreen_web_view_resizer.mm
@@ -82,7 +82,7 @@
 #pragma mark - Private
 
 // Updates the WebView of the current webState to adjust it to the current
-// fullscreen |progress|. |progress| should be between 0 and 1, 0 meaning that
+// fullscreen `progress`. `progress` should be between 0 and 1, 0 meaning that
 // the application is in fullscreen, 1 that it is out of fullscreen.
 - (void)updateForFullscreenProgress:(CGFloat)progress {
   if (!self.webState || !self.webState->GetView().superview)
@@ -92,7 +92,7 @@
   self.model->SetWebViewSafeAreaInsets(self.webState->GetView().safeAreaInsets);
 }
 
-// Updates the WebState view, resizing it such as |insets| is the insets between
+// Updates the WebState view, resizing it such as `insets` is the insets between
 // the WebState view and its superview.
 - (void)updateForInsets:(UIEdgeInsets)insets {
   UIView* webView = self.webState->GetView();
@@ -139,7 +139,7 @@
   }
 }
 
-// Observes the frame property of the view of the |webState| using KVO.
+// Observes the frame property of the view of the `webState` using KVO.
 - (void)observeWebStateViewFrame:(web::WebState*)webState {
   if (!webState->GetView())
     return;
diff --git a/ios/chrome/browser/ui/fullscreen/test/fullscreen_model_test_util.h b/ios/chrome/browser/ui/fullscreen/test/fullscreen_model_test_util.h
index e487201..c8c8dc94 100644
--- a/ios/chrome/browser/ui/fullscreen/test/fullscreen_model_test_util.h
+++ b/ios/chrome/browser/ui/fullscreen/test/fullscreen_model_test_util.h
@@ -14,22 +14,22 @@
 void SetUpFullscreenModelForTesting(FullscreenModel* model,
                                     CGFloat toolbar_height);
 
-// Simulates a user scroll event in |model| for a scroll of |offset_delta|
+// Simulates a user scroll event in `model` for a scroll of `offset_delta`
 // points.
 void SimulateFullscreenUserScrollWithDelta(FullscreenModel* model,
                                            CGFloat offset_delta);
 
-// Simulates a user scroll event in |model| that will result in a progress value
-// of |progress|.
+// Simulates a user scroll event in `model` that will result in a progress value
+// of `progress`.
 void SimulateFullscreenUserScrollForProgress(FullscreenModel* model,
                                              CGFloat progress);
 
-// Returns the delta from |model|'s current Y offset that would result in
-// |progress|.
+// Returns the delta from `model`'s current Y offset that would result in
+// `progress`.
 CGFloat GetFullscreenOffsetDeltaForProgress(FullscreenModel* model,
                                             CGFloat progress);
 
-// Returns the base offset against which |model| would calculate |progress|,
+// Returns the base offset against which `model` would calculate `progress`,
 // given its toolbar height and content offset.
 CGFloat GetFullscreenBaseOffsetForProgress(FullscreenModel* model,
                                            CGFloat progress);
diff --git a/ios/chrome/browser/ui/ntp/feed_management/BUILD.gn b/ios/chrome/browser/ui/ntp/feed_management/BUILD.gn
index bb4613b8..0e34717f 100644
--- a/ios/chrome/browser/ui/ntp/feed_management/BUILD.gn
+++ b/ios/chrome/browser/ui/ntp/feed_management/BUILD.gn
@@ -79,6 +79,7 @@
     "//ios/chrome/browser/net:crurl",
     "//ios/chrome/browser/ui/follow",
     "//ios/chrome/browser/ui/ntp:metrics",
+    "//ios/chrome/browser/ui/ntp/feed_management:navigation_delegate",
     "//ios/chrome/browser/ui/table_view",
     "//ios/chrome/common/ui/favicon",
     "//ui/base",
diff --git a/ios/chrome/browser/ui/ntp/feed_management/feed_management_coordinator.mm b/ios/chrome/browser/ui/ntp/feed_management/feed_management_coordinator.mm
index 4e2c33b..b73dec0 100644
--- a/ios/chrome/browser/ui/ntp/feed_management/feed_management_coordinator.mm
+++ b/ios/chrome/browser/ui/ntp/feed_management/feed_management_coordinator.mm
@@ -79,6 +79,7 @@
       initWithBrowserState:self.browser->GetBrowserState()];
   followManagementViewController.followedWebChannelsDataSource = mediator;
   followManagementViewController.faviconDataSource = mediator;
+  followManagementViewController.navigationDelegate = self.navigationDelegate;
   self.followManagementMediator = mediator;
   followManagementViewController.feedMetricsRecorder = self.feedMetricsRecorder;
 
diff --git a/ios/chrome/browser/ui/ntp/feed_management/feed_management_navigation_delegate.h b/ios/chrome/browser/ui/ntp/feed_management/feed_management_navigation_delegate.h
index 3c75bd5..2f7e613 100644
--- a/ios/chrome/browser/ui/ntp/feed_management/feed_management_navigation_delegate.h
+++ b/ios/chrome/browser/ui/ntp/feed_management/feed_management_navigation_delegate.h
@@ -5,6 +5,8 @@
 #ifndef IOS_CHROME_BROWSER_UI_NTP_FEED_MANAGEMENT_FEED_MANAGEMENT_NAVIGATION_DELEGATE_H_
 #define IOS_CHROME_BROWSER_UI_NTP_FEED_MANAGEMENT_FEED_MANAGEMENT_NAVIGATION_DELEGATE_H_
 
+class GURL;
+
 // Delegate for handling navigation actions.
 @protocol FeedManagementNavigationDelegate
 
@@ -17,6 +19,9 @@
 // Navigate to hidden.
 - (void)handleNavigateToHidden;
 
+// Navigate to |url| of a followed site.
+- (void)handleNavigateToFollowedURL:(const GURL&)url;
+
 @end
 
 #endif  // IOS_CHROME_BROWSER_UI_NTP_FEED_MANAGEMENT_FEED_MANAGEMENT_NAVIGATION_DELEGATE_H_
diff --git a/ios/chrome/browser/ui/ntp/feed_management/feed_management_view_controller.mm b/ios/chrome/browser/ui/ntp/feed_management/feed_management_view_controller.mm
index 52c2053a..dab0ca0 100644
--- a/ios/chrome/browser/ui/ntp/feed_management/feed_management_view_controller.mm
+++ b/ios/chrome/browser/ui/ntp/feed_management/feed_management_view_controller.mm
@@ -37,6 +37,8 @@
 
 - (void)viewDidLoad {
   [super viewDidLoad];
+  self.tableView.separatorInset =
+      UIEdgeInsetsMake(0, kTableViewSeparatorInset, 0, 0);
   self.title = l10n_util::GetNSString(IDS_IOS_FEED_MANAGEMENT_TITLE);
   self.navigationController.navigationBar.prefersLargeTitles = YES;
 
diff --git a/ios/chrome/browser/ui/ntp/feed_management/follow_management_view_controller.h b/ios/chrome/browser/ui/ntp/feed_management/follow_management_view_controller.h
index 410ba10..4e0e41ce 100644
--- a/ios/chrome/browser/ui/ntp/feed_management/follow_management_view_controller.h
+++ b/ios/chrome/browser/ui/ntp/feed_management/follow_management_view_controller.h
@@ -8,10 +8,11 @@
 #import "ios/chrome/browser/ui/ntp/feed_management/follow_management_ui_updater.h"
 #import "ios/chrome/browser/ui/table_view/chrome_table_view_controller.h"
 
+@protocol FeedManagementNavigationDelegate;
 @class FeedMetricsRecorder;
 @protocol FollowedWebChannelsDataSource;
-@protocol TableViewFaviconDataSource;
 @protocol FollowManagementViewDelegate;
+@protocol TableViewFaviconDataSource;
 
 // The UI that displays the web channels that the user is following.
 @interface FollowManagementViewController
@@ -30,6 +31,10 @@
 // Feed metrics recorder.
 @property(nonatomic, weak) FeedMetricsRecorder* feedMetricsRecorder;
 
+// Delegate to execute user actions related to navigation.
+@property(nonatomic, weak) id<FeedManagementNavigationDelegate>
+    navigationDelegate;
+
 @end
 
 #endif  // IOS_CHROME_BROWSER_UI_NTP_FEED_MANAGEMENT_FOLLOW_MANAGEMENT_VIEW_CONTROLLER_H_
diff --git a/ios/chrome/browser/ui/ntp/feed_management/follow_management_view_controller.mm b/ios/chrome/browser/ui/ntp/feed_management/follow_management_view_controller.mm
index 9869fa3c..b3dba85 100644
--- a/ios/chrome/browser/ui/ntp/feed_management/follow_management_view_controller.mm
+++ b/ios/chrome/browser/ui/ntp/feed_management/follow_management_view_controller.mm
@@ -8,6 +8,7 @@
 #import "ios/chrome/browser/net/crurl.h"
 #import "ios/chrome/browser/ui/follow/follow_block_types.h"
 #import "ios/chrome/browser/ui/follow/followed_web_channel.h"
+#import "ios/chrome/browser/ui/ntp/feed_management/feed_management_navigation_delegate.h"
 #import "ios/chrome/browser/ui/ntp/feed_management/follow_management_view_delegate.h"
 #import "ios/chrome/browser/ui/ntp/feed_management/followed_web_channel_item.h"
 #import "ios/chrome/browser/ui/ntp/feed_management/followed_web_channels_data_source.h"
@@ -41,6 +42,9 @@
 // Saved placement of the item that was last attempted to unfollow.
 @property(nonatomic, strong) NSIndexPath* indexPathOfLastUnfollowAttempt;
 
+// Saved indexPath of the item that is being selected.
+@property(nonatomic, strong) NSIndexPath* indexPathOfSelectedRow;
+
 // Saved item that was attempted to unfollow.
 @property(nonatomic, strong)
     FollowedWebChannelItem* lastUnfollowedWebChannelItem;
@@ -111,7 +115,27 @@
 
 - (void)tableView:(UITableView*)tableView
     didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
-  [tableView deselectRowAtIndexPath:indexPath animated:YES];
+  self.indexPathOfSelectedRow = indexPath;
+
+  UIMenuController* menu = [UIMenuController sharedMenuController];
+  UIMenuItem* visitSiteOption = [[UIMenuItem alloc]
+      initWithTitle:l10n_util::GetNSString(
+                        IDS_IOS_FOLLOW_MANAGEMENT_VISIT_SITE_ACTION)
+             action:@selector(visitSiteTapped)];
+  UIMenuItem* unfollowOption = [[UIMenuItem alloc]
+      initWithTitle:l10n_util::GetNSString(
+                        IDS_IOS_FOLLOW_MANAGEMENT_UNFOLLOW_ACTION)
+             action:@selector(unfollowTapped)];
+  menu.menuItems = @[ visitSiteOption, unfollowOption ];
+  [self becomeFirstResponder];
+  [menu showMenuFromView:tableView
+                    rect:[tableView rectForRowAtIndexPath:indexPath]];
+
+  // When the menu is manually presented, it doesn't get focused by
+  // Voiceover. This notification forces voiceover to select the
+  // presented menu.
+  UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification,
+                                  menu);
 }
 
 - (UISwipeActionsConfiguration*)tableView:(UITableView*)tableView
@@ -163,6 +187,60 @@
                                                actionProvider:actionProvider];
 }
 
+#pragma mark - UIResponder
+
+- (BOOL)canBecomeFirstResponder {
+  return YES;
+}
+
+- (BOOL)becomeFirstResponder {
+  // starts listening for UIMenuControllerDidHideMenuNotification and triggers
+  // resignFirstResponder if received.
+  [[NSNotificationCenter defaultCenter]
+      addObserver:self
+         selector:@selector(resignFirstResponder)
+             name:UIMenuControllerDidHideMenuNotification
+           object:nil];
+  return [super becomeFirstResponder];
+}
+
+- (BOOL)resignFirstResponder {
+  [[NSNotificationCenter defaultCenter]
+      removeObserver:self
+                name:UIMenuControllerDidHideMenuNotification
+              object:nil];
+  self.indexPathOfSelectedRow = nil;
+  return [super resignFirstResponder];
+}
+
+- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
+  if (action == @selector(visitSiteTapped) || action == @selector
+                                                  (unfollowTapped)) {
+    return YES;
+  }
+  return NO;
+}
+
+#pragma mark - Edit Menu Actions
+
+- (void)visitSiteTapped {
+  FollowedWebChannelItem* followedWebChannelItem =
+      base::mac::ObjCCastStrict<FollowedWebChannelItem>(
+          [self.tableViewModel itemAtIndexPath:self.indexPathOfSelectedRow]);
+  const GURL& webPageURL =
+      followedWebChannelItem.followedWebChannel.webPageURL.gurl;
+  __weak FollowManagementViewController* weakSelf = self;
+  [self dismissViewControllerAnimated:YES
+                           completion:^{
+                             [weakSelf.navigationDelegate
+                                 handleNavigateToFollowedURL:webPageURL];
+                           }];
+}
+
+- (void)unfollowTapped {
+  [self requestUnfollowWebChannelAtIndexPath:self.indexPathOfSelectedRow];
+}
+
 #pragma mark - FollowManagementUIUpdater
 
 - (void)removeFollowedWebChannel:(FollowedWebChannel*)channel {
diff --git a/ios/chrome/browser/ui/ntp/new_tab_page_coordinator.mm b/ios/chrome/browser/ui/ntp/new_tab_page_coordinator.mm
index 17dd7deab..c27cd52c 100644
--- a/ios/chrome/browser/ui/ntp/new_tab_page_coordinator.mm
+++ b/ios/chrome/browser/ui/ntp/new_tab_page_coordinator.mm
@@ -995,6 +995,10 @@
   [self.ntpMediator handleFeedManageHiddenTapped];
 }
 
+- (void)handleNavigateToFollowedURL:(const GURL&)url {
+  [self.ntpMediator handleVisitSiteFromFollowManagementList:url];
+}
+
 #pragma mark - Private
 
 // Updates the NTP to take into account a new feed, or a change in feed
diff --git a/ios/chrome/browser/ui/ntp/new_tab_page_view_controller.mm b/ios/chrome/browser/ui/ntp/new_tab_page_view_controller.mm
index e263d3f..d344572 100644
--- a/ios/chrome/browser/ui/ntp/new_tab_page_view_controller.mm
+++ b/ios/chrome/browser/ui/ntp/new_tab_page_view_controller.mm
@@ -342,6 +342,7 @@
                          : self.discoverFeedWrapperViewController;
   // Configures the feed header in the view hierarchy if it is visible.
   if (self.feedHeaderViewController) {
+    self.feedHeaderViewController.view.layer.zPosition = FLT_MAX;
     [self addViewController:self.feedHeaderViewController
         toParentViewController:parentViewController];
   }
@@ -880,6 +881,8 @@
                self.fakeOmniboxPinnedToTop) {
       [self resetFakeOmniboxConstraints];
     }
+  } else if (self.fakeOmniboxPinnedToTop) {
+    [self resetFakeOmniboxConstraints];
   }
 
   // Handles the sticky feed header.
diff --git a/ios/chrome/browser/ui/screen_time/screen_time_consumer.h b/ios/chrome/browser/ui/screen_time/screen_time_consumer.h
index 1db7c37..017fc9a 100644
--- a/ios/chrome/browser/ui/screen_time/screen_time_consumer.h
+++ b/ios/chrome/browser/ui/screen_time/screen_time_consumer.h
@@ -9,7 +9,7 @@
 
 // A protocol to update information reported to the ScreenTime system.
 @protocol ScreenTimeConsumer
-// Sets |URL| as the active URL reported to the ScreenTime system when the
+// Sets `URL` as the active URL reported to the ScreenTime system when the
 // underlying web view is visible.
 - (void)setURL:(NSURL*)URL;
 
diff --git a/ios/chrome/browser/ui/screen_time/screen_time_mediator.h b/ios/chrome/browser/ui/screen_time/screen_time_mediator.h
index f821b8c..9a08319 100644
--- a/ios/chrome/browser/ui/screen_time/screen_time_mediator.h
+++ b/ios/chrome/browser/ui/screen_time/screen_time_mediator.h
@@ -15,8 +15,8 @@
 @interface ScreenTimeMediator : NSObject
 @property(nonatomic, weak) id<ScreenTimeConsumer> consumer;
 
-// This mediator reports information from |webStateList| to the ScreenTime
-// system. Recording is disabled if |suppressUsageRecording| is YES.
+// This mediator reports information from `webStateList` to the ScreenTime
+// system. Recording is disabled if `suppressUsageRecording` is YES.
 - (instancetype)initWithWebStateList:(WebStateList*)webStateList
               suppressUsageRecording:(BOOL)suppressUsageRecording
     NS_DESIGNATED_INITIALIZER;
diff --git a/ios/chrome/browser/ui/screen_time/screen_time_mediator.mm b/ios/chrome/browser/ui/screen_time/screen_time_mediator.mm
index a778d48..3b025a0f 100644
--- a/ios/chrome/browser/ui/screen_time/screen_time_mediator.mm
+++ b/ios/chrome/browser/ui/screen_time/screen_time_mediator.mm
@@ -49,7 +49,7 @@
 }
 
 - (void)dealloc {
-  // |-disconnect| must be called before deallocation.
+  // `-disconnect` must be called before deallocation.
   DCHECK(!_webStateList);
 }
 
diff --git a/ios/chrome/browser/ui/settings/content_settings/content_settings_table_view_controller.mm b/ios/chrome/browser/ui/settings/content_settings/content_settings_table_view_controller.mm
index de57098d..7d42f26 100644
--- a/ios/chrome/browser/ui/settings/content_settings/content_settings_table_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/content_settings/content_settings_table_view_controller.mm
@@ -186,9 +186,7 @@
     }
   }
 
-  if (IsDiscoverFeedPreviewEnabled() ||
-      base::FeatureList::IsEnabled(
-          web::features::kWebViewNativeContextMenuPhase2)) {
+  if (IsDiscoverFeedPreviewEnabled()) {
     [model addItem:[self linkPreviewItem]
         toSectionWithIdentifier:SectionIdentifierSettings];
   }
diff --git a/ios/chrome/browser/ui/settings/safety_check/safety_check_consumer.h b/ios/chrome/browser/ui/settings/safety_check/safety_check_consumer.h
index 40f13e0..3968ce3c 100644
--- a/ios/chrome/browser/ui/settings/safety_check/safety_check_consumer.h
+++ b/ios/chrome/browser/ui/settings/safety_check/safety_check_consumer.h
@@ -14,13 +14,13 @@
 // Consumer protocol for safety check.
 @protocol SafetyCheckConsumer <ChromeTableViewConsumer>
 
-// Initializes the check types section with |items|.
+// Initializes the check types section with `items`.
 - (void)setCheckItems:(NSArray<TableViewItem*>*)items;
 
-// Initializes the safety check header with |item|.
+// Initializes the safety check header with `item`.
 - (void)setSafetyCheckHeaderItem:(TableViewLinkHeaderFooterItem*)item;
 
-// Initializes the check start section with |item|.
+// Initializes the check start section with `item`.
 - (void)setCheckStartItem:(TableViewItem*)item;
 
 // Initializes the footer with timestamp of last completed run.
diff --git a/ios/chrome/browser/ui/settings/safety_check/safety_check_coordinator.h b/ios/chrome/browser/ui/settings/safety_check/safety_check_coordinator.h
index c39f899..16d1734 100644
--- a/ios/chrome/browser/ui/settings/safety_check/safety_check_coordinator.h
+++ b/ios/chrome/browser/ui/settings/safety_check/safety_check_coordinator.h
@@ -27,8 +27,8 @@
 - (instancetype)initWithBaseViewController:(UIViewController*)viewController
                                    browser:(Browser*)browser NS_UNAVAILABLE;
 
-// |navigationController|: Handles user movement to check subpages.
-// |browser|: browser state for preferences and password check.
+// `navigationController`: Handles user movement to check subpages.
+// `browser`: browser state for preferences and password check.
 - (instancetype)initWithBaseNavigationController:
                     (UINavigationController*)navigationController
                                          browser:(Browser*)browser
diff --git a/ios/chrome/browser/ui/settings/safety_check/safety_check_mediator.h b/ios/chrome/browser/ui/settings/safety_check/safety_check_mediator.h
index 42a7b9a..ee5431d 100644
--- a/ios/chrome/browser/ui/settings/safety_check/safety_check_mediator.h
+++ b/ios/chrome/browser/ui/settings/safety_check/safety_check_mediator.h
@@ -27,11 +27,11 @@
 @interface SafetyCheckMediator : NSObject <SafetyCheckServiceDelegate>
 
 // Designated initializer. All the parameters should not be null.
-// |userPrefService|: Preference service to access safe browsing state.
-// |passwordCheckManager|: Password check manager to enable use of the password
+// `userPrefService`: Preference service to access safe browsing state.
+// `passwordCheckManager`: Password check manager to enable use of the password
 // check service.
-// |authService|: Authentication service to check users authentication status.
-// |syncService|: Sync service to check sync and sync encryption status.
+// `authService`: Authentication service to check users authentication status.
+// `syncService`: Sync service to check sync and sync encryption status.
 - (instancetype)initWithUserPrefService:(PrefService*)userPrefService
                    passwordCheckManager:
                        (scoped_refptr<IOSChromePasswordCheckManager>)
diff --git a/ios/chrome/browser/ui/settings/safety_check/safety_check_mediator.mm b/ios/chrome/browser/ui/settings/safety_check/safety_check_mediator.mm
index 487ee651..53c1bf6 100644
--- a/ios/chrome/browser/ui/settings/safety_check/safety_check_mediator.mm
+++ b/ios/chrome/browser/ui/settings/safety_check/safety_check_mediator.mm
@@ -501,7 +501,7 @@
   // If not managed compute error info to show in popover, if available.
   NSAttributedString* info = [self popoverInfoForType:itemType];
 
-  // If |info| is empty there is no popover to display.
+  // If `info` is empty there is no popover to display.
   if (!info)
     return;
 
@@ -517,7 +517,7 @@
 
 #pragma mark - Private methods
 
-// Computes the text needed for a popover on |itemType| if available.
+// Computes the text needed for a popover on `itemType` if available.
 - (NSAttributedString*)popoverInfoForType:(NSInteger)itemType {
   SafteyCheckItemType type = static_cast<SafteyCheckItemType>(itemType);
   switch (type) {
@@ -541,7 +541,7 @@
 }
 
 // Computes the appropriate display state of the password check row based on
-// |currentPasswordCheckState|.
+// `currentPasswordCheckState`.
 - (PasswordCheckRowStates)computePasswordCheckRowState:
     (PasswordCheckState)newState {
   BOOL wasRunning =
@@ -781,7 +781,7 @@
     __weak __typeof__(self) weakSelf = self;
     // This handles a discrepancy between password check and safety check.  In
     // password check a user cannot start a check if they have no passwords, but
-    // in safety check they can, but the |passwordCheckManager| won't even start
+    // in safety check they can, but the `passwordCheckManager` won't even start
     // a check. This if block below allows safety check to push the disabled
     // state after check now is pressed.
     if (self.currentPasswordCheckState == PasswordCheckState::kNoPasswords) {
@@ -817,7 +817,7 @@
   }
 }
 
-// Checks if any of the safety checks are still running, resets |checkStartItem|
+// Checks if any of the safety checks are still running, resets `checkStartItem`
 // if all checks have finished.
 - (void)resetsCheckStartItemIfNeeded {
   if (self.checksRemaining) {
@@ -838,7 +838,7 @@
            forKey:kTimestampOfLastIssueFoundKey];
     self.checkDidRun = NO;
   }
-  // If no checks are still running, reset |checkStartItem|.
+  // If no checks are still running, reset `checkStartItem`.
   self.checkStartState = CheckStartStateDefault;
   [self reconfigureCheckStartSection];
 
@@ -857,7 +857,7 @@
   return updateCheckRunning || passwordCheckRunning || safeBrowsingCheckRunning;
 }
 
-// Updates |updateCheckItem| to reflect the device being offline if the check
+// Updates `updateCheckItem` to reflect the device being offline if the check
 // was running.
 - (void)handleUpdateCheckOffline {
   if (self.updateCheckRowState == UpdateCheckRowStateRunning) {
@@ -870,7 +870,7 @@
 }
 
 // Verifies if the Omaha service returned an answer, if not sets
-// |updateCheckItem| to an Omaha error state.
+// `updateCheckItem` to an Omaha error state.
 - (void)verifyUpdateCheckComplete {
   // If still in running state assume Omaha error.
   if (self.updateCheckRowState == UpdateCheckRowStateRunning) {
@@ -883,7 +883,7 @@
 }
 
 // If the update check would have completed too quickly, making the UI appear
-// jittery, delay the reconfigure call, using |newRowState|.
+// jittery, delay the reconfigure call, using `newRowState`.
 - (void)possiblyDelayReconfigureUpdateCheckItemWithState:
     (UpdateCheckRowStates)newRowState {
   double secondsSinceStart =
@@ -965,7 +965,7 @@
 }
 
 // Performs the update check and triggers the display update to
-// |updateCheckItem|.
+// `updateCheckItem`.
 - (void)performUpdateCheck {
   __weak __typeof__(self) weakSelf = self;
 
@@ -1005,8 +1005,8 @@
   [self reconfigureSafeBrowsingCheckItem];
 }
 
-// Reconfigures the display of the |updateCheckItem| based on current state of
-// |updateCheckRowState|.
+// Reconfigures the display of the `updateCheckItem` based on current state of
+// `updateCheckRowState`.
 - (void)reconfigureUpdateCheckItem {
   // Reset state to prevent conflicts.
   self.updateCheckItem.enabled = YES;
@@ -1016,7 +1016,7 @@
   self.updateCheckItem.trailingImageTintColor = nil;
   self.updateCheckItem.accessoryType = UITableViewCellAccessoryNone;
 
-  // On any item update, see if |checkStartItem| should be updated.
+  // On any item update, see if `checkStartItem` should be updated.
   [self resetsCheckStartItemIfNeeded];
 
   switch (self.updateCheckRowState) {
@@ -1100,8 +1100,8 @@
   [self.consumer reconfigureCellsForItems:@[ self.updateCheckItem ]];
 }
 
-// Reconfigures the display of the |passwordCheckItem| based on current state of
-// |passwordCheckRowState|.
+// Reconfigures the display of the `passwordCheckItem` based on current state of
+// `passwordCheckRowState`.
 - (void)reconfigurePasswordCheckItem {
   // Reset state to prevent conflicts.
   self.passwordCheckItem.enabled = YES;
@@ -1111,7 +1111,7 @@
   self.passwordCheckItem.trailingImageTintColor = nil;
   self.passwordCheckItem.accessoryType = UITableViewCellAccessoryNone;
 
-  // On any item update, see if |checkStartItem| should be updated.
+  // On any item update, see if `checkStartItem` should be updated.
   [self resetsCheckStartItemIfNeeded];
 
   switch (self.passwordCheckRowState) {
@@ -1172,8 +1172,8 @@
   [self.consumer reconfigureCellsForItems:@[ self.passwordCheckItem ]];
 }
 
-// Reconfigures the display of the |safeBrowsingCheckItem| based on current
-// state of |safeBrowsingCheckRowState|.
+// Reconfigures the display of the `safeBrowsingCheckItem` based on current
+// state of `safeBrowsingCheckRowState`.
 - (void)reconfigureSafeBrowsingCheckItem {
   // Reset state to prevent conflicts.
   self.safeBrowsingCheckItem.enabled = YES;
@@ -1183,7 +1183,7 @@
   self.safeBrowsingCheckItem.trailingImageTintColor = nil;
   self.safeBrowsingCheckItem.accessoryType = UITableViewCellAccessoryNone;
 
-  // On any item update, see if |checkStartItem| should be updated.
+  // On any item update, see if `checkStartItem` should be updated.
   [self resetsCheckStartItemIfNeeded];
 
   switch (self.safeBrowsingCheckRowState) {
diff --git a/ios/chrome/browser/ui/settings/safety_check/safety_check_navigation_commands.h b/ios/chrome/browser/ui/settings/safety_check/safety_check_navigation_commands.h
index afb9b55c..5c87e09 100644
--- a/ios/chrome/browser/ui/settings/safety_check/safety_check_navigation_commands.h
+++ b/ios/chrome/browser/ui/settings/safety_check/safety_check_navigation_commands.h
@@ -12,13 +12,13 @@
 // Shows password issues page.
 - (void)showPasswordIssuesPage;
 
-// Opens update page at |location|.
+// Opens update page at `location`.
 - (void)showUpdateAtLocation:(NSString*)location;
 
 // Shows page with Safe Browsing preference toggle.
 - (void)showSafeBrowsingPreferencePage;
 
-// Shows the error popover with the corresponding |text|.
+// Shows the error popover with the corresponding `text`.
 - (void)showErrorInfoFrom:(UIButton*)buttonView
                  withText:(NSAttributedString*)text;
 
diff --git a/ios/chrome/browser/ui/settings/safety_check/safety_check_service_delegate.h b/ios/chrome/browser/ui/settings/safety_check/safety_check_service_delegate.h
index bd72ee4..c3d57bea 100644
--- a/ios/chrome/browser/ui/settings/safety_check/safety_check_service_delegate.h
+++ b/ios/chrome/browser/ui/settings/safety_check/safety_check_service_delegate.h
@@ -15,13 +15,13 @@
 // Called when item is tapped.
 - (void)didSelectItem:(TableViewItem*)item;
 
-// Determines if selection animation should be shown for |item|.
+// Determines if selection animation should be shown for `item`.
 - (BOOL)isItemClickable:(TableViewItem*)item;
 
-// Checks if |item| should have an error popover.
+// Checks if `item` should have an error popover.
 - (BOOL)isItemWithErrorInfo:(TableViewItem*)item;
 
-// Notifies the mediator that an info button was tapped for |itemType|.
+// Notifies the mediator that an info button was tapped for `itemType`.
 - (void)infoButtonWasTapped:(UIButton*)buttonView
               usingItemType:(NSInteger)itemType;
 
diff --git a/ios/chrome/browser/ui/sharing/sharing_coordinator.h b/ios/chrome/browser/ui/sharing/sharing_coordinator.h
index f8f6d5a22..b10ee59e 100644
--- a/ios/chrome/browser/ui/sharing/sharing_coordinator.h
+++ b/ios/chrome/browser/ui/sharing/sharing_coordinator.h
@@ -18,17 +18,17 @@
 @interface SharingCoordinator : ChromeCoordinator
 
 // Creates a coordinator configured to share the current tab's URL using the
-// base |viewController|, a |browser|, |params| with all the necessary values
-// to drive the scenario, and an |originView| from which the scenario was
-// triggered. This initializer also uses the |originView|'s bounds to position
+// base `viewController`, a `browser`, `params` with all the necessary values
+// to drive the scenario, and an `originView` from which the scenario was
+// triggered. This initializer also uses the `originView`'s bounds to position
 // the activity view popover on iPad.
 - (instancetype)initWithBaseViewController:(UIViewController*)viewController
                                    browser:(Browser*)browser
                                     params:(ActivityParams*)params
                                 originView:(UIView*)originView;
 
-// Creates a coordinator configured to share the URLs specified in |params|.
-// This initializer uses |barButtonItem| to position the activity view popover
+// Creates a coordinator configured to share the URLs specified in `params`.
+// This initializer uses `barButtonItem` to position the activity view popover
 // on iPad.
 - (instancetype)initWithBaseViewController:(UIViewController*)viewController
                                    browser:(Browser*)browser
@@ -36,10 +36,10 @@
                                     anchor:(UIBarButtonItem*)barButtonItem;
 
 // Creates a coordinator configured to share the current tab's URL using the
-// base |viewController|, a |browser|, |params| with all the necessary values
-// to drive the scenario. If |barButtonItem| is non-null, it will be used
-// to present the activity view popover on iPad. Otherwise, |originView| and
-// |originRect| will be used to position the activity view popover on iPad.
+// base `viewController`, a `browser`, `params` with all the necessary values
+// to drive the scenario. If `barButtonItem` is non-null, it will be used
+// to present the activity view popover on iPad. Otherwise, `originView` and
+// `originRect` will be used to position the activity view popover on iPad.
 - (instancetype)initWithBaseViewController:(UIViewController*)viewController
                                    browser:(Browser*)browser
                                     params:(ActivityParams*)params
diff --git a/ios/chrome/browser/ui/side_swipe/card_side_swipe_view.mm b/ios/chrome/browser/ui/side_swipe/card_side_swipe_view.mm
index 467b3e64..6eac1a9 100644
--- a/ios/chrome/browser/ui/side_swipe/card_side_swipe_view.mm
+++ b/ios/chrome/browser/ui/side_swipe/card_side_swipe_view.mm
@@ -54,7 +54,7 @@
 - (BOOL)isEdgeSwipe;
 // Initialize card based on model_'s webstatelist index.
 - (void)setupCard:(SwipeView*)card withIndex:(int)index;
-// Build a |kResizeFactor| sized greyscaled version of |image|.
+// Build a `kResizeFactor` sized greyscaled version of `image`.
 - (UIImage*)smallGreyImage:(UIImage*)image;
 
 @property(nonatomic, strong) NSLayoutConstraint* backgroundTopConstraint;
@@ -164,7 +164,7 @@
   return greyImage;
 }
 
-// Create card view based on |_webStateList|'s index.
+// Create card view based on `_webStateList`'s index.
 - (void)setupCard:(SwipeView*)card withIndex:(int)index {
   if (index < 0 || index >= _webStateList->count()) {
     [card setHidden:YES];
@@ -189,9 +189,9 @@
 }
 
 // Helper method that is invoked once the color snapshot has been fetched
-// for the WebState returned by |weakWebState|. As the fetching is done
+// for the WebState returned by `weakWebState`. As the fetching is done
 // asynchronously, it is possible for the WebState to have been destroyed
-// and thus for |webStateGetter| to return nullptr.
+// and thus for `webStateGetter` to return nullptr.
 - (void)colorSnapshotRetrieved:(UIImage*)image
                           card:(SwipeView*)card
                   weakWebState:(base::WeakPtr<web::WebState>)weakWebState {
@@ -227,14 +227,14 @@
                  });
 }
 
-// Move cards according to |currentPoint_.x|. Edge cards only drag
-// |kEdgeCardDragPercentage| of |bounds|.
+// Move cards according to `currentPoint_.x`. Edge cards only drag
+// `kEdgeCardDragPercentage` of `bounds`.
 - (void)updateCardPositions {
   CGFloat width = [self cardWidth];
 
   if ([self isEdgeSwipe]) {
     // If an edge card, don't allow the card to be dragged across the screen.
-    // Instead, drag across |kEdgeCardDragPercentage| of the screen.
+    // Instead, drag across `kEdgeCardDragPercentage` of the screen.
     _rightCard.transform = CGAffineTransformMakeTranslation(
         (_currentPoint.x) * kEdgeCardDragPercentage, 0);
     _leftCard.transform = CGAffineTransformMakeTranslation(
@@ -290,7 +290,7 @@
 }
 
 // Update the current WebState and animate the proper card view if the
-// |currentPoint_| is past the center of |bounds|.
+// `currentPoint_` is past the center of `bounds`.
 - (void)finishPan {
   int currentIndex = _webStateList->active_index();
   // Something happened and now there is not active WebState.  End card side let
@@ -304,7 +304,7 @@
   int destinationWebStateIndex = currentIndex;
   CGFloat offset = UseRTLLayout() ? -1 : 1;
   if (_direction == UISwipeGestureRecognizerDirectionRight) {
-    // If swipe is right and |currentPoint_.x| is over the first 1/3, move left.
+    // If swipe is right and `currentPoint_.x` is over the first 1/3, move left.
     if (_currentPoint.x > width / 3.0 && ![self isEdgeSwipe]) {
       rightTransform =
           CGAffineTransformMakeTranslation(width + kCardHorizontalSpacing, 0);
@@ -320,7 +320,7 @@
       base::RecordAction(UserMetricsAction("MobileStackSwipeCancelled"));
     }
   } else {
-    // If swipe is left and |currentPoint_.x| is over the first 1/3, move right.
+    // If swipe is left and `currentPoint_.x` is over the first 1/3, move right.
     if (_currentPoint.x < (width / 3.0) * 2.0 && ![self isEdgeSwipe]) {
       leftTransform =
           CGAffineTransformMakeTranslation(-width - kCardHorizontalSpacing, 0);
diff --git a/ios/chrome/browser/ui/side_swipe/side_swipe_controller.h b/ios/chrome/browser/ui/side_swipe/side_swipe_controller.h
index c10561ea..944cfbb 100644
--- a/ios/chrome/browser/ui/side_swipe/side_swipe_controller.h
+++ b/ios/chrome/browser/ui/side_swipe/side_swipe_controller.h
@@ -33,14 +33,14 @@
 - (void)sideSwipeViewDismissAnimationDidEnd:(UIView*)sideSwipeView;
 // Returns the main content view.
 - (UIView*)sideSwipeContentView;
-// Makes |tab| the currently visible tab, displaying its view.
+// Makes `tab` the currently visible tab, displaying its view.
 - (void)sideSwipeRedisplayWebState:(web::WebState*)webState;
 // Controls the visibility of views such as the findbar, infobar and voice
 // search bar.
 - (void)updateAccessoryViewsForSideSwipeWithVisibility:(BOOL)visible;
 // Returns the height of the header view for the current tab.
 - (CGFloat)headerHeightForSideSwipe;
-// Returns |YES| if side swipe should be blocked from initiating, such as when
+// Returns `YES` if side swipe should be blocked from initiating, such as when
 // voice search is up, or if the tools menu is enabled.
 - (BOOL)preventSideSwipe;
 // Returns whether a swipe on the toolbar can start.
@@ -53,7 +53,7 @@
 // Controls how an edge gesture is processed, either as tab change or a page
 // change.  For tab changes two full screen CardSideSwipeView views are dragged
 // across the screen. For page changes the SideSwipeControllerDelegate
-// |contentView| is moved across the screen and a SideSwipeNavigationView is
+// `contentView` is moved across the screen and a SideSwipeNavigationView is
 // shown in the remaining space.
 @interface SideSwipeController
     : NSObject<CRWSwipeRecognizerProvider, UIGestureRecognizerDelegate>
@@ -87,7 +87,7 @@
 // Enable or disable the side swipe gesture recognizer.
 - (void)setEnabled:(BOOL)enabled;
 
-// Returns |NO| if the device should not rotate.
+// Returns `NO` if the device should not rotate.
 - (BOOL)shouldAutorotate;
 
 // Resets the swipeDelegate's contentView frame origin x position to zero.
diff --git a/ios/chrome/browser/ui/side_swipe/side_swipe_controller.mm b/ios/chrome/browser/ui/side_swipe/side_swipe_controller.mm
index 65ca0f6..2258ba8 100644
--- a/ios/chrome/browser/ui/side_swipe/side_swipe_controller.mm
+++ b/ios/chrome/browser/ui/side_swipe/side_swipe_controller.mm
@@ -64,7 +64,7 @@
                                    WebStateListObserving> {
  @private
 
-  // Zeroes out |_browser| when it is destroyed.
+  // Zeroes out `_browser` when it is destroyed.
   std::unique_ptr<SideSwipeControllerBrowserRemover> _browserRemover;
 
   // Side swipe view for tab navigation.
@@ -122,8 +122,8 @@
 // The webStateList owned by the current browser.
 @property(nonatomic, readonly) WebStateList* webStateList;
 
-// Load grey snapshots for the next |kIpadGreySwipeTabCount| tabs in
-// |direction|.
+// Load grey snapshots for the next `kIpadGreySwipeTabCount` tabs in
+// `direction`.
 - (void)createGreyCache:(UISwipeGestureRecognizerDirection)direction;
 // Tell snapshot cache to clear grey cache.
 - (void)deleteGreyCache;
@@ -132,15 +132,15 @@
 // Handle tab side swipe for iPhone. Introduces a CardSideSwipeView to convey
 // the tab change.
 - (void)handleiPhoneTabSwipe:(SideSwipeGestureRecognizer*)gesture;
-// Overlays |curtain_| as a white view to hide the web view while it updates.
-// Calls |completionHandler| when the curtain is removed.
+// Overlays `curtain_` as a white view to hide the web view while it updates.
+// Calls `completionHandler` when the curtain is removed.
 - (void)addCurtainWithCompletionHandler:(ProceduralBlock)completionHandler;
-// Removes the |curtain_| and calls |completionHandler| when the curtain is
+// Removes the `curtain_` and calls `completionHandler` when the curtain is
 // removed.
 - (void)dismissCurtainWithCompletionHandler:(ProceduralBlock)completionHandler;
 
-// Removes the |curtain_| if there was an active swipe, and resets
-// |inSwipe_| value.
+// Removes the `curtain_` if there was an active swipe, and resets
+// `inSwipe_` value.
 - (void)dismissCurtain;
 // Cleans up Browser, WebStateList, and WebState references in the instance of a
 // BrowserDestroyed BrowserObserver call.
@@ -293,7 +293,7 @@
   return NO;
 }
 
-// Gestures should only be recognized within |contentArea_| or the toolbar.
+// Gestures should only be recognized within `contentArea_` or the toolbar.
 - (BOOL)gestureRecognizerShouldBegin:(SideSwipeGestureRecognizer*)gesture {
   if (_inSwipe) {
     return NO;
@@ -315,7 +315,7 @@
     return [_swipeDelegate canBeginToolbarSwipe];
   }
 
-  // Otherwise, only allow contentView touches with |swipeGestureRecognizer_|.
+  // Otherwise, only allow contentView touches with `swipeGestureRecognizer_`.
   // The content view frame is inset by -1 because CGRectContainsPoint does
   // include points on the max X and Y edges, which will happen frequently with
   // edge swipes from the right side.
@@ -420,7 +420,7 @@
   } else if (gesture.state == UIGestureRecognizerStateChanged) {
     // Side swipe for iPad involves changing the selected tab as the swipe moves
     // across the width of the view.  The screen is broken up into
-    // |kIpadTabSwipeDistance| / |width| segments, with the current tab in the
+    // `kIpadTabSwipeDistance` / `width` segments, with the current tab in the
     // first section.  The swipe does not wrap edges.
     CGFloat distance = [gesture locationInView:gesture.view].x;
     if (gesture.direction == UISwipeGestureRecognizerDirectionLeft) {
diff --git a/ios/chrome/browser/ui/side_swipe/side_swipe_egtest.mm b/ios/chrome/browser/ui/side_swipe/side_swipe_egtest.mm
index bc17f47..6446f19 100644
--- a/ios/chrome/browser/ui/side_swipe/side_swipe_egtest.mm
+++ b/ios/chrome/browser/ui/side_swipe/side_swipe_egtest.mm
@@ -39,7 +39,7 @@
 
 #pragma mark - Helpers
 
-// Checks that side swipe on an element of class |klass| is working to change
+// Checks that side swipe on an element of class `klass` is working to change
 // tab.
 - (void)checkSideSwipeOnToolbarClassName:(NSString*)className {
   // Setup the server.
diff --git a/ios/chrome/browser/ui/side_swipe/side_swipe_gesture_recognizer.mm b/ios/chrome/browser/ui/side_swipe/side_swipe_gesture_recognizer.mm
index c3f4fabd..7c897491 100644
--- a/ios/chrome/browser/ui/side_swipe/side_swipe_gesture_recognizer.mm
+++ b/ios/chrome/browser/ui/side_swipe/side_swipe_gesture_recognizer.mm
@@ -14,7 +14,7 @@
 
 namespace {
 
-// The absolute maximum swipe angle from |x = y| for a swipe to begin.
+// The absolute maximum swipe angle from `x = y` for a swipe to begin.
 const CGFloat kMaxSwipeYAngle = 65;
 // The minimum distance between touches for a swipe to begin.
 const CGFloat kDefaultMinSwipeXThreshold = 4;
@@ -84,16 +84,16 @@
   }
 
   // In iOS10, sometimes a PanGestureRecognizer will fire a touchesMoved even
-  // after touchesBegan sets its state to |UIGestureRecognizerStateFailed|.
+  // after touchesBegan sets its state to `UIGestureRecognizerStateFailed`.
   // Somehow the state is re-set to UIGestureRecognizerStatePossible, and ends
-  // up in moved.  Checking if |_startPoint| has been set is a secondary way to
+  // up in moved.  Checking if `_startPoint` has been set is a secondary way to
   // catch for failed gestures.
   if (CGPointEqualToPoint(_startPoint, CGPointZero)) {
     self.state = UIGestureRecognizerStateFailed;
     return;
   }
 
-  // Don't swipe at an angle greater than |kMaxSwipeYAngle|.
+  // Don't swipe at an angle greater than `kMaxSwipeYAngle`.
   UITouch* touch = [[event allTouches] anyObject];
   CGPoint currentPoint = [touch locationInView:self.view];
   CGFloat dy = currentPoint.y - _startPoint.y;
@@ -104,7 +104,7 @@
     return;
   }
 
-  // On devices that support force presses a -touchesMoved fires when |force|
+  // On devices that support force presses a -touchesMoved fires when `force`
   // changes and not the location of the touch. Ignore these events.
   if (currentPoint.x == _startPoint.x) {
     self.state = UIGestureRecognizerStatePossible;
@@ -128,7 +128,7 @@
     }
   }
 
-  // Begin recognizer after |self.swipeThreshold| distance swiped.
+  // Begin recognizer after `self.swipeThreshold` distance swiped.
   if (std::abs(currentPoint.x - _startPoint.x) > self.swipeThreshold) {
     if (_direction == UISwipeGestureRecognizerDirectionRight) {
       _swipeOffset = currentPoint.x;
diff --git a/ios/chrome/browser/ui/side_swipe/side_swipe_navigation_view.h b/ios/chrome/browser/ui/side_swipe/side_swipe_navigation_view.h
index 0994865c..c0dc3f9 100644
--- a/ios/chrome/browser/ui/side_swipe/side_swipe_navigation_view.h
+++ b/ios/chrome/browser/ui/side_swipe/side_swipe_navigation_view.h
@@ -22,7 +22,7 @@
                         image:(UIImage*)image;
 
 // Update views for latest gesture, and call completion blocks whether
-// |threshold| is met.
+// `threshold` is met.
 - (void)handleHorizontalPan:(SideSwipeGestureRecognizer*)gesture
      onOverThresholdCompletion:(void (^)(void))onOverThresholdCompletion
     onUnderThresholdCompletion:(void (^)(void))onUnderThresholdCompletion;
diff --git a/ios/chrome/browser/ui/side_swipe/side_swipe_navigation_view.mm b/ios/chrome/browser/ui/side_swipe/side_swipe_navigation_view.mm
index 138db21..b3f891f2 100644
--- a/ios/chrome/browser/ui/side_swipe/side_swipe_navigation_view.mm
+++ b/ios/chrome/browser/ui/side_swipe/side_swipe_navigation_view.mm
@@ -47,7 +47,7 @@
 const CGFloat kSwipeThreshold = 0.53;
 
 // Convert the velocity (which is measured in points per second) to points per
-// |kSwipeVelocityFraction| of a second.
+// `kSwipeVelocityFraction` of a second.
 const CGFloat kSwipeVelocityFraction = 0.1;
 
 // Distance after which the arrow should animate in.
@@ -85,7 +85,7 @@
   // The selection bubble.
   CAShapeLayer* _selectionCircleLayer;
 
-  // If |NO| this is an edge gesture and navigation isn't possible. Don't show
+  // If `NO` this is an edge gesture and navigation isn't possible. Don't show
   // arrows and bubbles and don't allow navigate.
   BOOL _canNavigate;
 }
@@ -184,7 +184,7 @@
   }
   [self setFrame:frame];
 
-  // Move |selectionCircleLayer_| without animations.
+  // Move `selectionCircleLayer_` without animations.
   CGRect bounds = self.bounds;
   CGPoint center = CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds));
   [_arrowView setCenter:AlignPointToPixel(center)];
@@ -322,7 +322,7 @@
 
   CGFloat distance = currentPoint.x;
   // The snap back animation is 0.1 seconds, so convert the velocity distance
-  // to where the |x| position would in .1 seconds.
+  // to where the `x` position would in .1 seconds.
   CGFloat velocityOffset = velocityPoint.x * kSwipeVelocityFraction;
   CGFloat width = CGRectGetWidth(self.targetView.bounds);
   if (gesture.direction == UISwipeGestureRecognizerDirectionLeft) {
@@ -351,7 +351,7 @@
     CGFloat threshold = width * kSwipeThreshold;
     CGFloat finalDistance = distance + velocityOffset;
     // Ensure the actual distance traveled has met the minimum arrow threshold
-    // and that the distance including expected velocity is over |threshold|.
+    // and that the distance including expected velocity is over `threshold`.
     if (distance > kArrowThreshold && finalDistance > threshold &&
         _canNavigate && gesture.state == UIGestureRecognizerStateEnded) {
       TriggerHapticFeedbackForImpact(UIImpactFeedbackStyleMedium);
diff --git a/ios/chrome/browser/ui/side_swipe/side_swipe_util.h b/ios/chrome/browser/ui/side_swipe/side_swipe_util.h
index 715c145..b6bb911 100644
--- a/ios/chrome/browser/ui/side_swipe/side_swipe_util.h
+++ b/ios/chrome/browser/ui/side_swipe/side_swipe_util.h
@@ -17,7 +17,7 @@
 // If swiping to the left (or right in RTL).
 BOOL IsSwipingForward(UISwipeGestureRecognizerDirection direction);
 
-// Returns |YES| if the item should use Chromium native swipe.  This is true for
+// Returns `YES` if the item should use Chromium native swipe.  This is true for
 // the NTP and chrome://crash.
 BOOL UseNativeSwipe(web::NavigationItem* item);
 
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_cell.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_cell.mm
index 1f772d1..dd15a5cb 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_cell.mm
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_cell.mm
@@ -305,16 +305,17 @@
     return;
   }
   // Do not fade in if there is no previous snapshot
-  if (_snapshot == nil) {
-    return;
+  if (_snapshot != nil) {
+    [UIView transitionWithView:self.snapshotView
+                      duration:0.2f
+                       options:UIViewAnimationOptionTransitionCrossDissolve
+                    animations:^{
+                      self.snapshotView.image = snapshot;
+                    }
+                    completion:nil];
+  } else {
+    self.snapshotView.image = snapshot;
   }
-  [UIView transitionWithView:self.snapshotView
-                    duration:0.2f
-                     options:UIViewAnimationOptionTransitionCrossDissolve
-                  animations:^{
-                    self.snapshotView.image = snapshot;
-                  }
-                  completion:nil];
   _snapshot = snapshot;
 }
 
diff --git a/ios/chrome/test/app/signin_test_util.mm b/ios/chrome/test/app/signin_test_util.mm
index 30c6117..15404af9 100644
--- a/ios/chrome/test/app/signin_test_util.mm
+++ b/ios/chrome/test/app/signin_test_util.mm
@@ -117,6 +117,8 @@
   prefs->SetBoolean(prefs::kIosBookmarkPromoAlreadySeen, false);
   prefs->SetInteger(prefs::kIosSettingsSigninPromoDisplayedCount, 0);
   prefs->SetBoolean(prefs::kIosSettingsPromoAlreadySeen, false);
+  prefs->SetInteger(prefs::kIosNtpFeedTopSigninPromoDisplayedCount, 0);
+  prefs->SetBoolean(prefs::kIosNtpFeedTopPromoAlreadySeen, false);
   prefs->SetBoolean(prefs::kSigninShouldPromptForSigninAgain, false);
 }
 
diff --git a/ios/chrome/test/earl_grey/chrome_earl_grey.h b/ios/chrome/test/earl_grey/chrome_earl_grey.h
index 40abb5a1..88b66a7 100644
--- a/ios/chrome/test/earl_grey/chrome_earl_grey.h
+++ b/ios/chrome/test/earl_grey/chrome_earl_grey.h
@@ -654,9 +654,6 @@
 // can, open multiple windows.
 - (BOOL)areMultipleWindowsSupported;
 
-// Returns whether the new ContextMenu for web content feature is enabled.
-- (BOOL)isContextMenuInWebViewEnabled;
-
 // Returns whether the NewOverflowMenu feature is enabled.
 - (BOOL)isNewOverflowMenuEnabled;
 
diff --git a/ios/chrome/test/earl_grey/chrome_earl_grey.mm b/ios/chrome/test/earl_grey/chrome_earl_grey.mm
index 541a3c2..0881908 100644
--- a/ios/chrome/test/earl_grey/chrome_earl_grey.mm
+++ b/ios/chrome/test/earl_grey/chrome_earl_grey.mm
@@ -1241,10 +1241,6 @@
   return [ChromeEarlGreyAppInterface areMultipleWindowsSupported];
 }
 
-- (BOOL)isContextMenuInWebViewEnabled {
-  return [ChromeEarlGreyAppInterface isContextMenuInWebViewEnabled];
-}
-
 - (BOOL)isNewOverflowMenuEnabled {
   return [ChromeEarlGreyAppInterface isNewOverflowMenuEnabled];
 }
diff --git a/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.h b/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.h
index caa1190..c19574b 100644
--- a/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.h
+++ b/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.h
@@ -533,9 +533,6 @@
 // can, open multiple windows.
 + (BOOL)areMultipleWindowsSupported;
 
-// Returns whether the new ContextMenu for web content feature is enabled.
-+ (BOOL)isContextMenuInWebViewEnabled;
-
 // Returns whether the NewOverflowMenu feature is enabled.
 + (BOOL)isNewOverflowMenuEnabled;
 
diff --git a/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm b/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm
index c5116a56..6f8bea4 100644
--- a/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm
+++ b/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm
@@ -1119,11 +1119,6 @@
   return base::ios::IsMultipleScenesSupported();
 }
 
-+ (BOOL)isContextMenuInWebViewEnabled {
-  return base::FeatureList::IsEnabled(
-      web::features::kWebViewNativeContextMenuPhase2);
-}
-
 + (BOOL)isNewOverflowMenuEnabled {
   return IsNewOverflowMenuEnabled();
 }
diff --git a/ios/web/common/features.h b/ios/web/common/features.h
index e03a553..c9ae8564 100644
--- a/ios/web/common/features.h
+++ b/ios/web/common/features.h
@@ -43,13 +43,6 @@
 // WKWebView is set as NSURLRequestAttributionUser on iOS 15.
 extern const base::Feature kSetRequestAttribution;
 
-// When enabled, display non-live preview for context menus in web content.
-extern const base::Feature kWebViewNativeContextMenuPhase2;
-
-// When enabled, uses a screenshot transition to display context menus in web
-// content.
-extern const base::Feature kWebViewNativeContextMenuPhase2Screenshot;
-
 // When enabled, the default context menu from WKWebView is used.
 extern const base::Feature kDefaultWebViewContextMenu;
 
diff --git a/ios/web/common/features.mm b/ios/web/common/features.mm
index 37cc9e7..2e262e3c 100644
--- a/ios/web/common/features.mm
+++ b/ios/web/common/features.mm
@@ -37,13 +37,6 @@
 const base::Feature kSetRequestAttribution{"SetRequestAttribution",
                                            base::FEATURE_ENABLED_BY_DEFAULT};
 
-const base::Feature kWebViewNativeContextMenuPhase2{
-    "WebViewNativeContextMenuPhase2", base::FEATURE_DISABLED_BY_DEFAULT};
-
-const base::Feature kWebViewNativeContextMenuPhase2Screenshot{
-    "WebViewNativeContextMenuPhase2Screenshot",
-    base::FEATURE_DISABLED_BY_DEFAULT};
-
 const base::Feature kDefaultWebViewContextMenu{
     "DefaultWebViewContextMenu", base::FEATURE_DISABLED_BY_DEFAULT};
 
diff --git a/ios/web/js_features/context_menu/context_menu_constants.h b/ios/web/js_features/context_menu/context_menu_constants.h
index 302b9af..84f8cf2 100644
--- a/ios/web/js_features/context_menu/context_menu_constants.h
+++ b/ios/web/js_features/context_menu/context_menu_constants.h
@@ -47,21 +47,6 @@
 // only).
 extern const char kContextMenuElementAlt[];
 
-// Optional key. Represents element's naturalWidth attribute if present (<img>
-// element only).
-extern const char kContextMenuElementNaturalWidth[];
-
-// Optional key. Represents element's naturalHeight attribute if present (<img>
-// element only).
-extern const char kContextMenuElementNaturalHeight[];
-
-// Optional key. Represents element's client bounding box if present.
-extern const char kContextMenuElementBoundingBox[];
-extern const char kContextMenuElementBoundingBoxX[];
-extern const char kContextMenuElementBoundingBoxY[];
-extern const char kContextMenuElementBoundingBoxWidth[];
-extern const char kContextMenuElementBoundingBoxHeight[];
-
 }  // namespace web
 
 #endif  // IOS_WEB_JS_FEATURES_CONTEXT_MENU_CONTEXT_MENU_CONSTANTS_H_
diff --git a/ios/web/js_features/context_menu/context_menu_constants.mm b/ios/web/js_features/context_menu/context_menu_constants.mm
index 06fbd44..0cf961e 100644
--- a/ios/web/js_features/context_menu/context_menu_constants.mm
+++ b/ios/web/js_features/context_menu/context_menu_constants.mm
@@ -19,12 +19,5 @@
 const char kContextMenuElementInnerText[] = "innerText";
 const char kContextMenuElementTextOffset[] = "textOffset";
 const char kContextMenuElementAlt[] = "alt";
-const char kContextMenuElementNaturalWidth[] = "naturalWidth";
-const char kContextMenuElementNaturalHeight[] = "naturalHeight";
-const char kContextMenuElementBoundingBox[] = "boundingBox";
-const char kContextMenuElementBoundingBoxX[] = "x";
-const char kContextMenuElementBoundingBoxY[] = "y";
-const char kContextMenuElementBoundingBoxWidth[] = "width";
-const char kContextMenuElementBoundingBoxHeight[] = "height";
 
 }  // namespace web
diff --git a/ios/web/js_features/context_menu/context_menu_js_unittest.mm b/ios/web/js_features/context_menu/context_menu_js_unittest.mm
index 67d08db..2acb568a 100644
--- a/ios/web/js_features/context_menu/context_menu_js_unittest.mm
+++ b/ios/web/js_features/context_menu/context_menu_js_unittest.mm
@@ -82,21 +82,13 @@
     "CjxyZWN0IHdpZHRoPSI"
     "2MDAiIGhlaWdodD0iNjAwIiBmaWxsPSIjMDA2NmZmIi8+Cjwvc3ZnPg==";
 
-// Natural width of kImageSource after styling
-const double kImageNaturalWidth = 84.0;
-
-// Natural height of kImageSource after styling
-const double kImageNaturalHeight = 25.0;
-
 // Alt text on image element for accessibility.
 const char kImageAlt[] = "Some alt text for an image";
 
 // Style used to size the image returned by |GetHtmlForImage()|.
 const char kImageSizeStyle[] = "width:100%;height:25%;";
 
-// Style used to size a div with the background image set. The div should be
-// the same size as the image's natural size as specified in kImageNaturalWidth
-// andkImageNaturalHeight.
+// Style used to size a div with the background image set.
 const char kBackgroundDivStyle[] = "width:100%;height:25px;";
 
 // Style used to create an overlay div.
@@ -304,20 +296,6 @@
   // Returns the test page URL.
   NSURL* GetTestURL() { return net::NSURLWithGURL(GURL(kTestUrl)); }
 
-  // Returns the expected bounding box values for the test Image.
-  base::Value GetExpectedBoundingBoxForTestImage() {
-    base::Value bounding_box_expected_value(base::Value::Type::DICTIONARY);
-    bounding_box_expected_value.SetDoubleKey(kContextMenuElementBoundingBoxX,
-                                             8.0);
-    bounding_box_expected_value.SetDoubleKey(kContextMenuElementBoundingBoxY,
-                                             8.0);
-    bounding_box_expected_value.SetDoubleKey(
-        kContextMenuElementBoundingBoxWidth, 84.0);
-    bounding_box_expected_value.SetDoubleKey(
-        kContextMenuElementBoundingBoxHeight, 25.0);
-    return bounding_box_expected_value;
-  }
-
   // Executes __gCrWeb.findElementAtPoint script with the given |point| in the
   // web view viewport's coordinate space.
   id ExecuteFindElementFromPointJavaScript(CGPoint point) {
@@ -347,12 +325,6 @@
   expected_value.SetStringKey(kContextMenuElementSource, kImageSource);
   expected_value.SetStringKey(kContextMenuElementAlt, kImageAlt);
   expected_value.SetStringKey(kContextMenuElementReferrerPolicy, "default");
-  expected_value.SetDoubleKey(kContextMenuElementNaturalWidth,
-                              kImageNaturalWidth);
-  expected_value.SetDoubleKey(kContextMenuElementNaturalHeight,
-                              kImageNaturalHeight);
-  expected_value.SetKey(kContextMenuElementBoundingBox,
-                        GetExpectedBoundingBoxForTestImage());
   expected_value.SetStringKey(kContextMenuElementTagName, "img");
 
   CheckElementResult(kPointOnImage, expected_value);
@@ -375,12 +347,6 @@
   expected_value.SetStringKey(kContextMenuElementSource, kImageSource);
   expected_value.SetStringKey(kContextMenuElementAlt, kImageAlt);
   expected_value.SetStringKey(kContextMenuElementReferrerPolicy, "default");
-  expected_value.SetKey(kContextMenuElementBoundingBox,
-                        GetExpectedBoundingBoxForTestImage());
-  expected_value.SetDoubleKey(kContextMenuElementNaturalWidth,
-                              kImageNaturalWidth);
-  expected_value.SetDoubleKey(kContextMenuElementNaturalHeight,
-                              kImageNaturalHeight);
   expected_value.SetStringKey(kContextMenuElementTagName, "img");
 
   CheckElementResult(kPointOnImage, expected_value);
@@ -399,8 +365,6 @@
   expected_value.SetStringKey(kContextMenuElementRequestId, kRequestId);
   expected_value.SetStringKey(kContextMenuElementSource, kImageSource);
   expected_value.SetStringKey(kContextMenuElementReferrerPolicy, "default");
-  expected_value.SetKey(kContextMenuElementBoundingBox,
-                        GetExpectedBoundingBoxForTestImage());
   expected_value.SetStringKey(kContextMenuElementTagName, "img");
 
   CheckElementResult(kPointOnImage, expected_value);
@@ -422,12 +386,6 @@
   expected_value.SetStringKey(kContextMenuElementSource, kImageSource);
   expected_value.SetStringKey(kContextMenuElementAlt, kImageAlt);
   expected_value.SetStringKey(kContextMenuElementReferrerPolicy, "default");
-  expected_value.SetDoubleKey(kContextMenuElementNaturalWidth,
-                              kImageNaturalWidth);
-  expected_value.SetDoubleKey(kContextMenuElementNaturalHeight,
-                              kImageNaturalHeight);
-  expected_value.SetKey(kContextMenuElementBoundingBox,
-                        GetExpectedBoundingBoxForTestImage());
   expected_value.SetStringKey(kContextMenuElementTagName, "img");
 
   CheckElementResult(kPointOnImage, expected_value);
@@ -450,7 +408,6 @@
   expected_value.SetStringKey(kContextMenuElementTagName, "DIV");
 
   std::vector<const char*> ignored_keys;
-  ignored_keys.push_back(kContextMenuElementBoundingBox);
   ignored_keys.push_back(kContextMenuElementTextOffset);
 
   CheckElementResult(kPointOnImage, expected_value, ignored_keys);
@@ -470,12 +427,6 @@
   expected_value.SetStringKey(kContextMenuElementSource, kImageSource);
   expected_value.SetStringKey(kContextMenuElementAlt, kImageAlt);
   expected_value.SetStringKey(kContextMenuElementReferrerPolicy, "default");
-  expected_value.SetDoubleKey(kContextMenuElementNaturalWidth,
-                              kImageNaturalWidth);
-  expected_value.SetDoubleKey(kContextMenuElementNaturalHeight,
-                              kImageNaturalHeight);
-  expected_value.SetKey(kContextMenuElementBoundingBox,
-                        GetExpectedBoundingBoxForTestImage());
   expected_value.SetStringKey(kContextMenuElementTitle, image_title);
   expected_value.SetStringKey(kContextMenuElementTagName, "img");
 
@@ -493,12 +444,6 @@
   expected_value.SetStringKey(kContextMenuElementSource, kImageSource);
   expected_value.SetStringKey(kContextMenuElementAlt, kImageAlt);
   expected_value.SetStringKey(kContextMenuElementReferrerPolicy, "default");
-  expected_value.SetKey(kContextMenuElementBoundingBox,
-                        GetExpectedBoundingBoxForTestImage());
-  expected_value.SetDoubleKey(kContextMenuElementNaturalWidth,
-                              kImageNaturalWidth);
-  expected_value.SetDoubleKey(kContextMenuElementNaturalHeight,
-                              kImageNaturalHeight);
   expected_value.SetStringKey(kContextMenuElementTagName, "img");
 
   CheckElementResult(kPointOnImage, expected_value);
@@ -544,12 +489,6 @@
   expected_value.SetStringKey(kContextMenuElementSource, kImageSource);
   expected_value.SetStringKey(kContextMenuElementAlt, kImageAlt);
   expected_value.SetStringKey(kContextMenuElementReferrerPolicy, "default");
-  expected_value.SetDoubleKey(kContextMenuElementNaturalWidth,
-                              kImageNaturalWidth);
-  expected_value.SetDoubleKey(kContextMenuElementNaturalHeight,
-                              kImageNaturalHeight);
-  expected_value.SetKey(kContextMenuElementBoundingBox,
-                        GetExpectedBoundingBoxForTestImage());
   expected_value.SetStringKey(kContextMenuElementHyperlink, image_link);
   expected_value.SetStringKey(kContextMenuElementTagName, "img");
 
@@ -605,8 +544,6 @@
   expected_value.SetStringKey(kContextMenuElementSource, image_source);
   expected_value.SetStringKey(kContextMenuElementAlt, kImageAlt);
   expected_value.SetStringKey(kContextMenuElementReferrerPolicy, "default");
-  expected_value.SetKey(kContextMenuElementBoundingBox,
-                        GetExpectedBoundingBoxForTestImage());
   expected_value.SetStringKey(kContextMenuElementHyperlink, image_link);
   expected_value.SetStringKey(kContextMenuElementTagName, "img");
 
@@ -633,8 +570,6 @@
   expected_value.SetStringKey(kContextMenuElementSource, image_source);
   expected_value.SetStringKey(kContextMenuElementAlt, kImageAlt);
   expected_value.SetStringKey(kContextMenuElementReferrerPolicy, "default");
-  expected_value.SetKey(kContextMenuElementBoundingBox,
-                        GetExpectedBoundingBoxForTestImage());
   expected_value.SetStringKey(kContextMenuElementHyperlink, image_link);
   expected_value.SetStringKey(kContextMenuElementTagName, "img");
 
@@ -661,8 +596,6 @@
   expected_value.SetStringKey(kContextMenuElementSource, image_source);
   expected_value.SetStringKey(kContextMenuElementAlt, kImageAlt);
   expected_value.SetStringKey(kContextMenuElementReferrerPolicy, "default");
-  expected_value.SetKey(kContextMenuElementBoundingBox,
-                        GetExpectedBoundingBoxForTestImage());
   expected_value.SetStringKey(kContextMenuElementTagName, "img");
 
   // Make sure the returned JSON does not have an 'href' key.
@@ -688,8 +621,6 @@
   expected_value.SetStringKey(kContextMenuElementSource, image_source);
   expected_value.SetStringKey(kContextMenuElementAlt, kImageAlt);
   expected_value.SetStringKey(kContextMenuElementReferrerPolicy, "default");
-  expected_value.SetKey(kContextMenuElementBoundingBox,
-                        GetExpectedBoundingBoxForTestImage());
   expected_value.SetStringKey(kContextMenuElementTagName, "img");
 
   // Make sure the returned JSON does not have an 'href' key.
@@ -710,12 +641,6 @@
   expected_value.SetStringKey(kContextMenuElementSource, kImageSource);
   expected_value.SetStringKey(kContextMenuElementAlt, kImageAlt);
   expected_value.SetStringKey(kContextMenuElementReferrerPolicy, "default");
-  expected_value.SetDoubleKey(kContextMenuElementNaturalWidth,
-                              kImageNaturalWidth);
-  expected_value.SetDoubleKey(kContextMenuElementNaturalHeight,
-                              kImageNaturalHeight);
-  expected_value.SetKey(kContextMenuElementBoundingBox,
-                        GetExpectedBoundingBoxForTestImage());
   expected_value.SetStringKey(kContextMenuElementTagName, "img");
 
   // Make sure the returned JSON does not have an 'href' key.
@@ -742,10 +667,7 @@
   expected_value.SetStringKey(kContextMenuElementHyperlink, image_link);
   expected_value.SetStringKey(kContextMenuElementTagName, "a");
 
-  std::vector<const char*> ignored_keys;
-  ignored_keys.push_back(kContextMenuElementBoundingBox);
-
-  CheckElementResult(kPointOnImage, expected_value, ignored_keys);
+  CheckElementResult(kPointOnImage, expected_value);
 }
 
 #pragma mark - SVG shape links
@@ -762,10 +684,7 @@
   expected_value.SetStringKey(kContextMenuElementHyperlink, link);
   expected_value.SetStringKey(kContextMenuElementTagName, "a");
 
-  std::vector<const char*> ignored_keys;
-  ignored_keys.push_back(kContextMenuElementBoundingBox);
-
-  CheckElementResult(kPointOnSvgLink, expected_value, ignored_keys);
+  CheckElementResult(kPointOnSvgLink, expected_value);
 }
 
 // Tests that an SVG shape xlink returns details for the link.
@@ -780,10 +699,7 @@
   expected_value.SetStringKey(kContextMenuElementHyperlink, link);
   expected_value.SetStringKey(kContextMenuElementTagName, "a");
 
-  std::vector<const char*> ignored_keys;
-  ignored_keys.push_back(kContextMenuElementBoundingBox);
-
-  CheckElementResult(kPointOnSvgLink, expected_value, ignored_keys);
+  CheckElementResult(kPointOnSvgLink, expected_value);
 }
 
 // Tests that a point within an SVG element but outside a linked shape does not
@@ -799,7 +715,6 @@
   expected_value.SetStringKey(kContextMenuElementTagName, "P");
 
   std::vector<const char*> ignored_keys;
-  ignored_keys.push_back(kContextMenuElementBoundingBox);
   ignored_keys.push_back(kContextMenuElementTextOffset);
 
   CheckElementResult(kPointOutsideSvgLink, expected_value, ignored_keys);
@@ -881,12 +796,8 @@
   expected_value.SetStringKey(kContextMenuElementHyperlink, link);
   expected_value.SetStringKey(kContextMenuElementTagName, "a");
 
-  std::vector<const char*> ignored_keys;
-  ignored_keys.push_back(kContextMenuElementBoundingBox);
-
   // Link is at bottom of the page content.
-  CheckElementResult(CGPointMake(50.0, content_height - 100), expected_value,
-                     ignored_keys);
+  CheckElementResult(CGPointMake(50.0, content_height - 100), expected_value);
 }
 
 // Tests that __gCrWeb.findElementAtPoint finds a link inside shadow DOM
@@ -905,10 +816,7 @@
   expected_value.SetStringKey(kContextMenuElementHyperlink, link);
   expected_value.SetStringKey(kContextMenuElementTagName, "a");
 
-  std::vector<const char*> ignored_keys;
-  ignored_keys.push_back(kContextMenuElementBoundingBox);
-
-  CheckElementResult(kPointOnShadowDomLink, expected_value, ignored_keys);
+  CheckElementResult(kPointOnShadowDomLink, expected_value);
 }
 
 // Tests that a point within shadow DOM content but not on a link does not
@@ -926,7 +834,6 @@
   expected_value.SetStringKey(kContextMenuElementTagName, "DIV");
 
   std::vector<const char*> ignored_keys;
-  ignored_keys.push_back(kContextMenuElementBoundingBox);
   ignored_keys.push_back(kContextMenuElementTextOffset);
 
   CheckElementResult(kPointOutsideShadowDomLink, expected_value, ignored_keys);
@@ -948,10 +855,7 @@
   expected_value.SetStringKey(kContextMenuElementHyperlink, link);
   expected_value.SetStringKey(kContextMenuElementTagName, "a");
 
-  std::vector<const char*> ignored_keys;
-  ignored_keys.push_back(kContextMenuElementBoundingBox);
-
-  CheckElementResult(kPointOnLink, expected_value, ignored_keys);
+  CheckElementResult(kPointOnLink, expected_value);
 }
 
 // Tests that a callout information about a link is displayed when
@@ -972,10 +876,7 @@
   expected_value.SetStringKey(kContextMenuElementHyperlink, link);
   expected_value.SetStringKey(kContextMenuElementTagName, "a");
 
-  std::vector<const char*> ignored_keys;
-  ignored_keys.push_back(kContextMenuElementBoundingBox);
-
-  CheckElementResult(kPointOnLink, expected_value, ignored_keys);
+  CheckElementResult(kPointOnLink, expected_value);
 }
 
 // Tests that no callout information about a link is displayed when
@@ -996,7 +897,6 @@
   expected_value.SetStringKey(kContextMenuElementTagName, "P");
 
   std::vector<const char*> ignored_keys;
-  ignored_keys.push_back(kContextMenuElementBoundingBox);
   ignored_keys.push_back(kContextMenuElementTextOffset);
 
   CheckElementResult(kPointOnLink, expected_value, ignored_keys);
@@ -1019,7 +919,6 @@
   expected_value.SetStringKey(kContextMenuElementTagName, "P");
 
   std::vector<const char*> ignored_keys;
-  ignored_keys.push_back(kContextMenuElementBoundingBox);
   ignored_keys.push_back(kContextMenuElementTextOffset);
 
   CheckElementResult(kPointOnLink, expected_value, ignored_keys);
@@ -1044,10 +943,7 @@
   expected_value.SetStringKey(kContextMenuElementHyperlink, link);
   expected_value.SetStringKey(kContextMenuElementTagName, "a");
 
-  std::vector<const char*> ignored_keys;
-  ignored_keys.push_back(kContextMenuElementBoundingBox);
-
-  CheckElementResult(kPointOnLink, expected_value, ignored_keys);
+  CheckElementResult(kPointOnLink, expected_value);
 }
 
 }  // namespace web
diff --git a/ios/web/js_features/context_menu/context_menu_params.mm b/ios/web/js_features/context_menu/context_menu_params.mm
index 2b53a9e..9f3ef91 100644
--- a/ios/web/js_features/context_menu/context_menu_params.mm
+++ b/ios/web/js_features/context_menu/context_menu_params.mm
@@ -15,9 +15,6 @@
       tag_name(nil),
       referrer_policy(ReferrerPolicyDefault),
       location(CGPointZero),
-      natural_width(0.0),
-      natural_height(0.0),
-      bounding_box(CGRectZero),
       text_offset(0) {}
 
 ContextMenuParams::ContextMenuParams(const ContextMenuParams& other) = default;
diff --git a/ios/web/js_features/context_menu/context_menu_params_utils.mm b/ios/web/js_features/context_menu/context_menu_params_utils.mm
index 45784b4..36ee250 100644
--- a/ios/web/js_features/context_menu/context_menu_params_utils.mm
+++ b/ios/web/js_features/context_menu/context_menu_params_utils.mm
@@ -17,25 +17,6 @@
 
 namespace web {
 
-CGRect BoundingBoxFromBoundingBoxDictionary(const base::Value* boundingBox) {
-  absl::optional<double> x =
-      boundingBox->FindDoubleKey(kContextMenuElementBoundingBoxX);
-  absl::optional<double> y =
-      boundingBox->FindDoubleKey(kContextMenuElementBoundingBoxY);
-  absl::optional<double> width =
-      boundingBox->FindDoubleKey(kContextMenuElementBoundingBoxWidth);
-  absl::optional<double> height =
-      boundingBox->FindDoubleKey(kContextMenuElementBoundingBoxHeight);
-
-  if (x && y && width && height && width > 0.0 && height > 0.0) {
-    const double elementSize = *height * *width;
-    if (elementSize < kContextMenuMaxScreenshotSize) {
-      return CGRectMake(*x, *y, *width, *height);
-    }
-  }
-  return CGRectZero;
-}
-
 ContextMenuParams ContextMenuParamsFromElementDictionary(base::Value* element) {
   ContextMenuParams params;
   if (!element || !element->is_dict()) {
@@ -87,24 +68,6 @@
     params.text_offset = *text_offset;
   }
 
-  absl::optional<double> natural_width =
-      element->FindDoubleKey(web::kContextMenuElementNaturalWidth);
-  if (natural_width.has_value()) {
-    params.natural_width = *natural_width;
-  }
-
-  absl::optional<double> natural_height =
-      element->FindDoubleKey(web::kContextMenuElementNaturalHeight);
-  if (natural_height.has_value()) {
-    params.natural_height = *natural_height;
-  }
-
-  base::Value* bounding_box =
-      element->FindDictKey(web::kContextMenuElementBoundingBox);
-  if (bounding_box) {
-    params.bounding_box = BoundingBoxFromBoundingBoxDictionary(bounding_box);
-  }
-
   return params;
 }
 
diff --git a/ios/web/js_features/context_menu/context_menu_params_utils_unittest.mm b/ios/web/js_features/context_menu/context_menu_params_utils_unittest.mm
index 5b77c104..07ccca42 100644
--- a/ios/web/js_features/context_menu/context_menu_params_utils_unittest.mm
+++ b/ios/web/js_features/context_menu/context_menu_params_utils_unittest.mm
@@ -27,12 +27,6 @@
 const char kReferrerPolicy[] = "always";
 const char kLinkText[] = "link text";
 const char kAlt[] = "alt text";
-const double kNaturalWidth = 200.0;
-const double kNaturalHeight = 300.0;
-const double kBoundingBoxX = 10.0;
-const double kBoundingBoxY = 20.0;
-const double kBoundingBoxWidth = 50.0;
-const double kBoundingBoxHeight = 200.0;
 
 // Returns true if the |params| contain enough information to present a context
 // menu. (A valid url for either link_url or src_url must exist in the params.)
@@ -64,10 +58,6 @@
   EXPECT_NSEQ(params.text, nil);
   EXPECT_NSEQ(params.title_attribute, nil);
   EXPECT_NSEQ(params.alt_text, nil);
-  EXPECT_NEAR(params.natural_width, 0.0, DBL_EPSILON);
-  EXPECT_NEAR(params.natural_height, 0.0, DBL_EPSILON);
-  EXPECT_TRUE(CGRectIsEmpty(params.bounding_box));
-  EXPECT_EQ(params.screenshot, nil);
 }
 
 // Tests the parsing of the element NSDictionary.
@@ -79,19 +69,6 @@
   element_dict.SetStringKey(kContextMenuElementReferrerPolicy, kReferrerPolicy);
   element_dict.SetStringKey(kContextMenuElementInnerText, kLinkText);
   element_dict.SetStringKey(kContextMenuElementAlt, kAlt);
-  element_dict.SetDoubleKey(kContextMenuElementNaturalWidth, kNaturalWidth);
-  element_dict.SetDoubleKey(kContextMenuElementNaturalHeight, kNaturalHeight);
-  base::Value bounding_box_element_dict(base::Value::Type::DICTIONARY);
-  bounding_box_element_dict.SetDoubleKey(kContextMenuElementBoundingBoxX,
-                                         kBoundingBoxX);
-  bounding_box_element_dict.SetDoubleKey(kContextMenuElementBoundingBoxY,
-                                         kBoundingBoxY);
-  bounding_box_element_dict.SetDoubleKey(kContextMenuElementBoundingBoxWidth,
-                                         kBoundingBoxWidth);
-  bounding_box_element_dict.SetDoubleKey(kContextMenuElementBoundingBoxHeight,
-                                         kBoundingBoxHeight);
-  element_dict.SetKey(kContextMenuElementBoundingBox,
-                      std::move(bounding_box_element_dict));
   ContextMenuParams params =
       ContextMenuParamsFromElementDictionary(&element_dict);
 
@@ -106,14 +83,6 @@
 
   EXPECT_NSEQ(params.title_attribute, @(kTitle));
   EXPECT_NSEQ(params.alt_text, @(kAlt));
-
-  EXPECT_NEAR(params.natural_width, kNaturalWidth, DBL_EPSILON);
-  EXPECT_NEAR(params.natural_height, kNaturalHeight, DBL_EPSILON);
-
-  EXPECT_NEAR(params.bounding_box.origin.x, kBoundingBoxX, DBL_EPSILON);
-  EXPECT_NEAR(params.bounding_box.origin.y, kBoundingBoxY, DBL_EPSILON);
-  EXPECT_NEAR(params.bounding_box.size.width, kBoundingBoxWidth, DBL_EPSILON);
-  EXPECT_NEAR(params.bounding_box.size.height, kBoundingBoxHeight, DBL_EPSILON);
 }
 
 
diff --git a/ios/web/js_features/context_menu/resources/all_frames_context_menu.js b/ios/web/js_features/context_menu/resources/all_frames_context_menu.js
index 33b1893..c9044834 100644
--- a/ios/web/js_features/context_menu/resources/all_frames_context_menu.js
+++ b/ios/web/js_features/context_menu/resources/all_frames_context_menu.js
@@ -28,7 +28,6 @@
  *                     {@code referrerPolicy} The referrer policy to use for
  *                         navigations away from the current page.
  *                     {@code innerText} The inner text of the link.
- *                     {@code boundingBox} The bounding box of the element.
  *                   }.
  */
 var getResponseForLinkElement = function(element) {
@@ -37,7 +36,6 @@
     href: getElementHref_(element),
     referrerPolicy: getReferrerPolicy_(element),
     innerText: element.innerText,
-    boundingBox: getElementBoundingBox_(element)
   };
 };
 
@@ -50,15 +48,10 @@
  *                     {@code src} The src of the image.
  *                     {@code referrerPolicy} The referrer policy to use for
  *                         navigations away from the current page.
- *                     {@code boundingBox} The bounding box of the element.
  *                     {@code title} (optional) The title of the image, if one
  *                         exists.
  *                     {@code href} (optional) The URL of the link, if one
  *                         exists.
- *                     {@code naturalWidth} (optional) The natural width of
- *                         the image, if one exists.
- *                     {@code naturalHeight} (optional) The natural height of
- *                         the image, if one exists.
  *                   }.
  */
 var getResponseForImageElement = function(element, src) {
@@ -66,7 +59,6 @@
     tagName: 'img',
     src: src,
     referrerPolicy: getReferrerPolicy_(),
-    boundingBox: getElementBoundingBox_(element)
   };
   var parent = element.parentNode;
   // Copy the title, if any.
@@ -95,14 +87,6 @@
     }
     parent = parent.parentNode;
   }
-  // Copy the image natural width, if any.
-  if (element.naturalWidth) {
-    result.naturalWidth = element.naturalWidth;
-  }
-  // Copy the image natural height, if any.
-  if (element.naturalHeight) {
-    result.naturalHeight = element.naturalHeight;
-  }
   return result;
 };
 
@@ -123,7 +107,6 @@
 var getResponseForTextElement = function(element, x, y) {
   var result = {
     tagName: element.tagName,
-    boundingBox: getElementBoundingBox_(element),
   };
   // caretRangeFromPoint is custom WebKit method.
   if (document.caretRangeFromPoint) {
@@ -476,30 +459,6 @@
   return href;
 };
 
-/**
- * Returns the client bounding box of the given element.
- * @param {HTMLElement} element to retrieve the bounding box
- * @return {!Object} An object of the form {
- *                     {@code x} The x coordinate of the bounding box origin.
- *                     {@code y} The y coordinate of the bounding box origin.
- *                     {@code width} The width of the bounding box size.
- *                     {@code height} The height of the bounding box size.
- *                   }.
- */
-var getElementBoundingBox_ = function(element) {
-  var boundingBox = element.getBoundingClientRect();
-  if (boundingBox) {
-    return {
-      x: boundingBox.x,
-      y: boundingBox.y,
-      width: boundingBox.width,
-      height: boundingBox.height
-    };
-  }
-  else {
-    return null;
-  }
-};
 
 /**
  * Checks if the element is effectively transparent and should be skipped when
diff --git a/ios/web/public/ui/context_menu_params.h b/ios/web/public/ui/context_menu_params.h
index 1be10a5..ea07f667 100644
--- a/ios/web/public/ui/context_menu_params.h
+++ b/ios/web/public/ui/context_menu_params.h
@@ -57,18 +57,6 @@
   // The text for the "alt" attribute of an HTML img element. Can be null.
   NSString* alt_text;
 
-  // The natural width of the HTML img element. Can be null = 0.
-  double natural_width;
-
-  // The natural height of the HTML img element. Can be null = 0.
-  double natural_height;
-
-  // The client bounding box of the HTML element. Can be null = CGRectZero.
-  CGRect bounding_box;
-
-  // The screenshot of the HTML element. Can be null = nil.
-  UIImage* screenshot;
-
   // The offset in text where the tap occurs. Can be null = 0.
   double text_offset;
 };
diff --git a/ios/web/web_state/ui/crw_context_menu_controller.mm b/ios/web/web_state/ui/crw_context_menu_controller.mm
index e2fdf26..2e22e42 100644
--- a/ios/web/web_state/ui/crw_context_menu_controller.mm
+++ b/ios/web/web_state/ui/crw_context_menu_controller.mm
@@ -23,11 +23,6 @@
 namespace {
 
 const CGFloat kJavaScriptTimeout = 1;
-// The animation still looks good without the screenshot so the timeout is
-// smaller.
-const CGFloat kScreenshotTimeout = 0.3;
-const CGFloat kScreenshotInset = 2;
-const CGFloat kScreenshotCornerRadius = 10;
 
 // Wrapper around CFRunLoop() to help crash server put all crashes happening
 // while the loop is executed in the same bucket. Marked as `noinline` to
@@ -111,29 +106,7 @@
   }
   web::ContextMenuParams params = optionalParams.value();
 
-  // Converts javascript bounding box to webView bounding box.
-  CGRect screenshotBoundingBox = params.bounding_box;
-  if (!CGRectIsEmpty(screenshotBoundingBox)) {
-    screenshotBoundingBox =
-        [self webViewBoundingBoxFromElementBoundingBox:screenshotBoundingBox];
-  }
-
-  // Adding the screenshot view here, so it can be used in the delegate's
-  // methods.
-  if (base::FeatureList::IsEnabled(
-          web::features::kWebViewNativeContextMenuPhase2Screenshot) &&
-      base::FeatureList::IsEnabled(
-          web::features::kWebViewNativeContextMenuPhase2) &&
-      !CGRectIsEmpty(screenshotBoundingBox)) {
-    self.screenshotView =
-        [self fetchScreenshotViewAtBoundingBox:screenshotBoundingBox];
-    params.screenshot = self.screenshotView.image;
-    self.screenshotView.center =
-        CGPointMake(CGRectGetMidX(screenshotBoundingBox),
-                    CGRectGetMidY(screenshotBoundingBox));
-  } else {
-    self.screenshotView.center = location;
-  }
+  self.screenshotView.center = location;
 
   // Adding the screenshotView here so they can be used in the
   // delegate's methods. Will be removed if no menu is presented.
@@ -163,20 +136,26 @@
                           (UIContextMenuInteraction*)interaction
     previewForHighlightingMenuWithConfiguration:
         (UIContextMenuConfiguration*)configuration {
+  UIPreviewParameters* previewParameters = [[UIPreviewParameters alloc] init];
+  previewParameters.backgroundColor = UIColor.clearColor;
+
   return [[UITargetedPreview alloc] initWithView:self.screenshotView
-                                      parameters:[self previewParameters]];
+                                      parameters:previewParameters];
 }
 
 - (UITargetedPreview*)contextMenuInteraction:
                           (UIContextMenuInteraction*)interaction
     previewForDismissingMenuWithConfiguration:
         (UIContextMenuConfiguration*)configuration {
+  UIPreviewParameters* previewParameters = [[UIPreviewParameters alloc] init];
+  previewParameters.backgroundColor = UIColor.clearColor;
+
   // If the dismiss view is not attached to the view hierarchy, fallback to nil
   // to prevent app crashing. See crbug.com/1231888.
   UITargetedPreview* targetPreview =
       self.screenshotView.window
           ? [[UITargetedPreview alloc] initWithView:self.screenshotView
-                                         parameters:[self previewParameters]]
+                                         parameters:previewParameters]
           : nil;
   self.screenshotView = nil;
   return targetPreview;
@@ -205,21 +184,6 @@
 
 #pragma mark - Private
 
-// Returns preview parameters for highlight/dismiss previews.
-- (UIPreviewParameters*)previewParameters {
-  UIPreviewParameters* previewParameters = [[UIPreviewParameters alloc] init];
-  previewParameters.backgroundColor = UIColor.clearColor;
-  if (kScreenshotInset < self.screenshotView.frame.size.width &&
-      kScreenshotInset < self.screenshotView.frame.size.height) {
-    previewParameters.visiblePath = [UIBezierPath
-        bezierPathWithRoundedRect:CGRectInset(self.screenshotView.bounds,
-                                              kScreenshotInset,
-                                              kScreenshotInset)
-                     cornerRadius:kScreenshotCornerRadius];
-  }
-  return previewParameters;
-}
-
 // Prevents the web view gesture recognizer to get the touch events.
 - (void)cancelAllTouches {
   // All user gestures are handled by a subview of web view scroll view
@@ -289,89 +253,4 @@
   return resultParams;
 }
 
-// Converts HTMLElement bounding box to webView coordinates.
-// Returns CGRectZero if the converted bounding box exeeds the maximum allowed
-// size.
-- (CGRect)webViewBoundingBoxFromElementBoundingBox:(CGRect)elementBoundingBox {
-  CGRect boundingBox = elementBoundingBox;
-
-  // Viewport gives the correct top inset.
-  id<CRWViewportAdjustmentContainer> viewportAdjustmentContainer =
-      static_cast<id<CRWViewportAdjustmentContainer>>(self.webState->GetView());
-  UIView<CRWViewportAdjustment>* viewportAdjustmentView =
-      [viewportAdjustmentContainer fullscreenViewportAdjuster];
-  UIEdgeInsets viewportInsets = [viewportAdjustmentView viewportInsets];
-
-  // Bounding box is scaled to handle page zooming.
-  // ScrollView gives the correct left inset.
-  CGFloat zoomScale = self.webView.scrollView.zoomScale;
-  boundingBox.size.width *= zoomScale;
-  boundingBox.size.height *= zoomScale;
-  boundingBox.origin.x = boundingBox.origin.x * zoomScale +
-                         self.webView.scrollView.adjustedContentInset.left;
-  boundingBox.origin.y = boundingBox.origin.y * zoomScale + viewportInsets.top;
-
-  const double size = boundingBox.size.height * boundingBox.size.width;
-  if (size >= web::kContextMenuMaxScreenshotSize) {
-    return CGRectZero;
-  }
-
-  return boundingBox;
-}
-
-// Fetches a UIImageView containing a screenshot of webState at location of
-// |screenshotBoundingBox|. The screenshot image can be empty.
-- (UIImageView*)fetchScreenshotViewAtBoundingBox:(CGRect)screenshotBoundingBox {
-  // While traditionally using dispatch_async would be used here, we have to
-  // instead use CFRunLoop because dispatch_async blocks the thread. As this
-  // function is called by iOS when it detects the user's force touch, it is on
-  // the main thread and we cannot block that. CFRunLoop instead just loops on
-  // the main thread until the completion block is fired.
-  __block BOOL isRunLoopNested = NO;
-  __block BOOL screenshotCompleted = NO;
-  __block BOOL isRunLoopComplete = NO;
-
-  __block UIImageView* screenshotView = [[UIImageView alloc]
-      initWithFrame:CGRectMake(0, 0, screenshotBoundingBox.size.width,
-                               screenshotBoundingBox.size.height)];
-  screenshotView.backgroundColor = UIColor.clearColor;
-  self.webState->TakeSnapshot(
-      gfx::RectF(screenshotBoundingBox),
-      base::BindRepeating(^(const gfx::Image& image) {
-        screenshotCompleted = YES;
-        if (image.HasRepresentation(
-                gfx::Image::RepresentationType::kImageRepCocoaTouch)) {
-          screenshotView.image = image.ToUIImage();
-        }
-        if (isRunLoopNested) {
-          CFRunLoopStop(CFRunLoopGetCurrent());
-        }
-      }));
-
-  // Make sure to timeout in case the screenshot doesn't return in a timely
-  // manner. While this is executing, the scrolling on the page is frozen.
-  // Interacting with the page will force this method to return even before any
-  // of this code is called.
-  dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
-                               (int64_t)(kScreenshotTimeout * NSEC_PER_SEC)),
-                 dispatch_get_main_queue(), ^{
-                   if (!isRunLoopComplete) {
-                     CFRunLoopStop(CFRunLoopGetCurrent());
-                     screenshotCompleted = YES;
-                   }
-                 });
-
-  // CFRunLoopRun isn't necessary if javascript evaluation is completed by the
-  // time we reach this line.
-  if (!screenshotCompleted) {
-    isRunLoopNested = YES;
-    ContextMenuNestedCFRunLoop();
-    isRunLoopNested = NO;
-  }
-
-  isRunLoopComplete = YES;
-
-  return screenshotView;
-}
-
 @end
diff --git a/media/base/media_switches.cc b/media/base/media_switches.cc
index 7bca356..f3a15a8a 100644
--- a/media/base/media_switches.cc
+++ b/media/base/media_switches.cc
@@ -206,19 +206,12 @@
 const char kEnableLiveCaptionPrefForTesting[] =
     "enable-live-caption-pref-for-testing";
 
-#if BUILDFLAG(ENABLE_PLATFORM_HEVC)
-// Enables playback of clear (unencrypted) HEVC content for testing purposes.
-const char kEnableClearHevcForTesting[] = "enable-clear-hevc-for-testing";
-#endif
-
 #if BUILDFLAG(IS_CHROMEOS)
 // These are flags passed from ash-chrome to lacros-chrome that correspond to
 // buildflags for the platform we are running on. lacros-chrome only builds for
 // x86/arm differences, so we unconditionally build in the below features into
 // the relevant parts of lacros-chrome and then filter the functionality based
 // on these command line flags.
-MEDIA_EXPORT extern const char kLacrosEnablePlatformEncryptedHevc[] =
-    "lacros-enable-platform-encrypted-hevc";
 MEDIA_EXPORT extern const char kLacrosEnablePlatformHevc[] =
     "lacros-enable-platform-hevc";
 MEDIA_EXPORT extern const char kLacrosUseChromeosProtectedMedia[] =
diff --git a/media/base/media_switches.h b/media/base/media_switches.h
index ef7f048..247bdae9 100644
--- a/media/base/media_switches.h
+++ b/media/base/media_switches.h
@@ -88,17 +88,7 @@
 MEDIA_EXPORT extern const char kOverrideHardwareSecureCodecsForTesting[];
 MEDIA_EXPORT extern const char kEnableLiveCaptionPrefForTesting[];
 
-#if BUILDFLAG(ENABLE_PLATFORM_HEVC)
-// TODO(crbug/1311348): Remove this after Chrome clear HEVC lands and Chrome OS
-// is uprev'd to use that version and we then also land changes to tast-tests
-// that drop usage of this flag.
-MEDIA_EXPORT extern const char kEnableClearHevcForTesting[];
-#endif
-
 #if BUILDFLAG(IS_CHROMEOS)
-// TODO(crbug/1311348): Remove kLacrosEnablePlatformEncryptedHevc after Chrome
-// clear HEVC lands and Chrome OS is uprev'd to use that version for ash-chrome.
-MEDIA_EXPORT extern const char kLacrosEnablePlatformEncryptedHevc[];
 MEDIA_EXPORT extern const char kLacrosEnablePlatformHevc[];
 MEDIA_EXPORT extern const char kLacrosUseChromeosProtectedMedia[];
 MEDIA_EXPORT extern const char kLacrosUseChromeosProtectedAv1[];
diff --git a/net/base/address_list.cc b/net/base/address_list.cc
index b3c91d8..121bdc4c 100644
--- a/net/base/address_list.cc
+++ b/net/base/address_list.cc
@@ -116,19 +116,19 @@
 }
 
 base::Value AddressList::NetLogParams() const {
-  base::Value dict(base::Value::Type::DICTIONARY);
+  base::Value::Dict dict;
 
-  base::Value address_list(base::Value::Type::LIST);
+  base::Value::List address_list;
   for (const auto& ip_endpoint : *this)
     address_list.Append(ip_endpoint.ToString());
-  dict.SetKey("address_list", std::move(address_list));
+  dict.Set("address_list", std::move(address_list));
 
-  base::Value alias_list(base::Value::Type::LIST);
+  base::Value::List alias_list;
   for (const std::string& alias : dns_aliases_)
     alias_list.Append(alias);
-  dict.SetKey("aliases", std::move(alias_list));
+  dict.Set("aliases", std::move(alias_list));
 
-  return dict;
+  return base::Value(std::move(dict));
 }
 
 void AddressList::Deduplicate() {
diff --git a/net/base/backoff_entry_serializer_unittest.cc b/net/base/backoff_entry_serializer_unittest.cc
index 856c640..02e515a4 100644
--- a/net/base/backoff_entry_serializer_unittest.cc
+++ b/net/base/backoff_entry_serializer_unittest.cc
@@ -141,7 +141,7 @@
 
     // Check that the serialized backoff duration matches our expectation.
     const std::string& serialized_backoff_duration_string =
-        serialized.GetListDeprecated()[2].GetString();
+        serialized.GetList()[2].GetString();
     int64_t serialized_backoff_duration_us;
     EXPECT_TRUE(base::StringToInt64(serialized_backoff_duration_string,
                                     &serialized_backoff_duration_us));
@@ -174,7 +174,7 @@
 
   // Reach into the serialization and check the string-formatted release time.
   const std::string& serialized_release_time =
-      serialized.GetListDeprecated()[3].GetString();
+      serialized.GetList()[3].GetString();
   EXPECT_EQ(serialized_release_time, "0");
 
   // Test that |DeserializeFromValue| notices this zero-valued release time and
@@ -331,74 +331,74 @@
 }
 
 TEST(BackoffEntrySerializerTest, DeserializeUnknownVersion) {
-  base::Value serialized(base::Value::Type::LIST);
+  base::Value::List serialized;
   serialized.Append(0);       // Format version that never existed
   serialized.Append(0);       // Failure count
   serialized.Append(2.0);     // Backoff duration
   serialized.Append("1234");  // Absolute release time
 
   auto deserialized = BackoffEntrySerializer::DeserializeFromValue(
-      serialized, &base_policy, nullptr, kParseTime);
+      base::Value(std::move(serialized)), &base_policy, nullptr, kParseTime);
   ASSERT_FALSE(deserialized);
 }
 
 TEST(BackoffEntrySerializerTest, DeserializeVersion1) {
-  base::Value serialized(base::Value::Type::LIST);
+  base::Value::List serialized;
   serialized.Append(SerializationFormatVersion::kVersion1);
   serialized.Append(0);       // Failure count
   serialized.Append(2.0);     // Backoff duration in seconds as double
   serialized.Append("1234");  // Absolute release time
 
   auto deserialized = BackoffEntrySerializer::DeserializeFromValue(
-      serialized, &base_policy, nullptr, kParseTime);
+      base::Value(std::move(serialized)), &base_policy, nullptr, kParseTime);
   ASSERT_TRUE(deserialized);
 }
 
 TEST(BackoffEntrySerializerTest, DeserializeVersion2) {
-  base::Value serialized(base::Value::Type::LIST);
+  base::Value::List serialized;
   serialized.Append(SerializationFormatVersion::kVersion2);
   serialized.Append(0);       // Failure count
   serialized.Append("2000");  // Backoff duration
   serialized.Append("1234");  // Absolute release time
 
   auto deserialized = BackoffEntrySerializer::DeserializeFromValue(
-      serialized, &base_policy, nullptr, kParseTime);
+      base::Value(std::move(serialized)), &base_policy, nullptr, kParseTime);
   ASSERT_TRUE(deserialized);
 }
 
 TEST(BackoffEntrySerializerTest, DeserializeVersion2NegativeDuration) {
-  base::Value serialized(base::Value::Type::LIST);
+  base::Value::List serialized;
   serialized.Append(SerializationFormatVersion::kVersion2);
   serialized.Append(0);        // Failure count
   serialized.Append("-2000");  // Backoff duration
   serialized.Append("1234");   // Absolute release time
 
   auto deserialized = BackoffEntrySerializer::DeserializeFromValue(
-      serialized, &base_policy, nullptr, kParseTime);
+      base::Value(std::move(serialized)), &base_policy, nullptr, kParseTime);
   ASSERT_TRUE(deserialized);
 }
 
 TEST(BackoffEntrySerializerTest, DeserializeVersion1WrongDurationType) {
-  base::Value serialized(base::Value::Type::LIST);
+  base::Value::List serialized;
   serialized.Append(SerializationFormatVersion::kVersion1);
   serialized.Append(0);       // Failure count
   serialized.Append("2000");  // Backoff duration in seconds as double
   serialized.Append("1234");  // Absolute release time
 
   auto deserialized = BackoffEntrySerializer::DeserializeFromValue(
-      serialized, &base_policy, nullptr, kParseTime);
+      base::Value(std::move(serialized)), &base_policy, nullptr, kParseTime);
   ASSERT_FALSE(deserialized);
 }
 
 TEST(BackoffEntrySerializerTest, DeserializeVersion2WrongDurationType) {
-  base::Value serialized(base::Value::Type::LIST);
+  base::Value::List serialized;
   serialized.Append(SerializationFormatVersion::kVersion2);
   serialized.Append(0);       // Failure count
   serialized.Append(2.0);     // Backoff duration
   serialized.Append("1234");  // Absolute release time
 
   auto deserialized = BackoffEntrySerializer::DeserializeFromValue(
-      serialized, &base_policy, nullptr, kParseTime);
+      base::Value(std::move(serialized)), &base_policy, nullptr, kParseTime);
   ASSERT_FALSE(deserialized);
 }
 
diff --git a/net/base/connection_endpoint_metadata.cc b/net/base/connection_endpoint_metadata.cc
index 7930fa8..842c0e53 100644
--- a/net/base/connection_endpoint_metadata.cc
+++ b/net/base/connection_endpoint_metadata.cc
@@ -27,15 +27,15 @@
     ConnectionEndpointMetadata&&) = default;
 
 base::Value ConnectionEndpointMetadata::ToValue() const {
-  base::Value::DictStorage dict;
+  base::Value::Dict dict;
 
-  base::Value::ListStorage alpns_list;
+  base::Value::List alpns_list;
   for (const std::string& alpn : supported_protocol_alpns) {
-    alpns_list.emplace_back(alpn);
+    alpns_list.Append(alpn);
   }
-  dict.emplace(kSupportedProtocolAlpnsKey, std::move(alpns_list));
+  dict.Set(kSupportedProtocolAlpnsKey, std::move(alpns_list));
 
-  dict.emplace(kEchConfigListKey, base::Base64Encode(ech_config_list));
+  dict.Set(kEchConfigListKey, base::Base64Encode(ech_config_list));
 
   return base::Value(std::move(dict));
 }
@@ -43,21 +43,22 @@
 // static
 absl::optional<ConnectionEndpointMetadata>
 ConnectionEndpointMetadata::FromValue(const base::Value& value) {
-  if (!value.is_dict())
+  const base::Value::Dict* dict = value.GetIfDict();
+  if (!dict)
     return absl::nullopt;
 
-  const base::Value* alpns_value =
-      value.FindListKey(kSupportedProtocolAlpnsKey);
+  const base::Value::List* alpns_list =
+      dict->FindList(kSupportedProtocolAlpnsKey);
   const std::string* ech_config_list_value =
-      value.FindStringKey(kEchConfigListKey);
+      dict->FindString(kEchConfigListKey);
 
-  if (!alpns_value || !ech_config_list_value)
+  if (!alpns_list || !ech_config_list_value)
     return absl::nullopt;
 
   ConnectionEndpointMetadata metadata;
 
   std::vector<std::string> alpns;
-  for (const base::Value& value : alpns_value->GetListDeprecated()) {
+  for (const base::Value& value : *alpns_list) {
     if (!value.is_string())
       return absl::nullopt;
     metadata.supported_protocol_alpns.push_back(value.GetString());
diff --git a/net/base/logging_network_change_observer.cc b/net/base/logging_network_change_observer.cc
index 0a221b7ea..11beacd 100644
--- a/net/base/logging_network_change_observer.cc
+++ b/net/base/logging_network_change_observer.cc
@@ -41,25 +41,24 @@
 // like the default network, and the types of active networks.
 base::Value NetworkSpecificNetLogParams(
     NetworkChangeNotifier::NetworkHandle network) {
-  base::Value dict(base::Value::Type::DICTIONARY);
-  dict.SetIntKey("changed_network_handle", HumanReadableNetworkHandle(network));
-  dict.SetStringKey(
-      "changed_network_type",
-      NetworkChangeNotifier::ConnectionTypeToString(
-          NetworkChangeNotifier::GetNetworkConnectionType(network)));
-  dict.SetIntKey(
+  base::Value::Dict dict;
+  dict.Set("changed_network_handle", HumanReadableNetworkHandle(network));
+  dict.Set("changed_network_type",
+           NetworkChangeNotifier::ConnectionTypeToString(
+               NetworkChangeNotifier::GetNetworkConnectionType(network)));
+  dict.Set(
       "default_active_network_handle",
       HumanReadableNetworkHandle(NetworkChangeNotifier::GetDefaultNetwork()));
   NetworkChangeNotifier::NetworkList networks;
   NetworkChangeNotifier::GetConnectedNetworks(&networks);
   for (NetworkChangeNotifier::NetworkHandle active_network : networks) {
-    dict.SetStringKey(
+    dict.Set(
         "current_active_networks." +
             base::NumberToString(HumanReadableNetworkHandle(active_network)),
         NetworkChangeNotifier::ConnectionTypeToString(
             NetworkChangeNotifier::GetNetworkConnectionType(active_network)));
   }
-  return dict;
+  return base::Value(std::move(dict));
 }
 
 void NetLogNetworkSpecific(NetLog* net_log,
diff --git a/net/base/network_isolation_key.cc b/net/base/network_isolation_key.cc
index e6fc03ba..4ee3e9e 100644
--- a/net/base/network_isolation_key.cc
+++ b/net/base/network_isolation_key.cc
@@ -126,15 +126,16 @@
       SerializeSiteWithNonce(*top_frame_site_);
   if (!top_frame_value)
     return false;
-  *out_value = base::Value(base::Value::Type::LIST);
-  out_value->Append(std::move(*top_frame_value));
+  base::Value::List list;
+  list.Append(std::move(top_frame_value).value());
 
   absl::optional<std::string> frame_value =
       SerializeSiteWithNonce(*frame_site_);
   if (!frame_value)
     return false;
-  out_value->Append(std::move(*frame_value));
+  list.Append(std::move(frame_value).value());
 
+  *out_value = base::Value(std::move(list));
   return true;
 }
 
@@ -144,7 +145,7 @@
   if (!value.is_list())
     return false;
 
-  base::Value::ConstListView list = value.GetListDeprecated();
+  const base::Value::List& list = value.GetList();
   if (list.empty()) {
     *network_isolation_key = NetworkIsolationKey();
     return true;
diff --git a/net/base/network_isolation_key_unittest.cc b/net/base/network_isolation_key_unittest.cc
index 6f05b842..a2320dd 100644
--- a/net/base/network_isolation_key_unittest.cc
+++ b/net/base/network_isolation_key_unittest.cc
@@ -258,22 +258,20 @@
 }
 
 TEST_P(NetworkIsolationKeyTest, FromValueBadData) {
-  // Can't create these inline, since vector initialization lists require a
-  // copy, and base::Value has no copy operator, only move.
-  base::Value::ListStorage not_a_url_list;
-  not_a_url_list.emplace_back(base::Value("not-a-url"));
+  base::Value::List not_a_url_list;
+  not_a_url_list.Append("not-a-url");
 
-  base::Value::ListStorage transient_origin_list;
-  transient_origin_list.emplace_back(base::Value("data:text/html,transient"));
+  base::Value::List transient_origin_list;
+  transient_origin_list.Append("data:text/html,transient");
 
-  base::Value::ListStorage too_many_origins_list;
-  too_many_origins_list.emplace_back(base::Value("https://too/"));
-  too_many_origins_list.emplace_back(base::Value("https://many/"));
-  too_many_origins_list.emplace_back(base::Value("https://origins/"));
+  base::Value::List too_many_origins_list;
+  too_many_origins_list.Append("https://too/");
+  too_many_origins_list.Append("https://many/");
+  too_many_origins_list.Append("https://origins/");
 
   const base::Value kTestCases[] = {
-      base::Value(base::Value::Type::STRING),
-      base::Value(base::Value::Type::DICTIONARY),
+      base::Value(std::string()),
+      base::Value(base::Value::Dict()),
       base::Value(std::move(not_a_url_list)),
       base::Value(std::move(transient_origin_list)),
       base::Value(std::move(too_many_origins_list)),
@@ -285,11 +283,11 @@
     EXPECT_FALSE(NetworkIsolationKey::FromValue(test_case, &key)) << test_case;
   }
 
-  base::Value::ListStorage triple_key_list;
-  triple_key_list.emplace_back(base::Value("http://www.triple.com"));
-  triple_key_list.emplace_back(base::Value("http://www.key.com"));
+  base::Value::List triple_key_list;
+  triple_key_list.Append("http://www.triple.com");
+  triple_key_list.Append("http://www.key.com");
   NetworkIsolationKey key;
-  const auto triple_key_case = base::Value(std::move(triple_key_list));
+  base::Value triple_key_case(std::move(triple_key_list));
 
   // When double key is enabled top_level_site must equal frame_site.
   bool expect_fail_on_different_sites =
diff --git a/net/base/upload_data_stream.cc b/net/base/upload_data_stream.cc
index bd5aea1..cccaedb0 100644
--- a/net/base/upload_data_stream.cc
+++ b/net/base/upload_data_stream.cc
@@ -17,19 +17,19 @@
 base::Value NetLogInitEndInfoParams(int result,
                                     int total_size,
                                     bool is_chunked) {
-  base::Value dict(base::Value::Type::DICTIONARY);
+  base::Value::Dict dict;
 
-  dict.SetIntKey("net_error", result);
-  dict.SetIntKey("total_size", total_size);
-  dict.SetBoolKey("is_chunked", is_chunked);
-  return dict;
+  dict.Set("net_error", result);
+  dict.Set("total_size", total_size);
+  dict.Set("is_chunked", is_chunked);
+  return base::Value(std::move(dict));
 }
 
 base::Value CreateReadInfoParams(int current_position) {
-  base::Value dict(base::Value::Type::DICTIONARY);
+  base::Value::Dict dict;
 
-  dict.SetIntKey("current_position", current_position);
-  return dict;
+  dict.Set("current_position", current_position);
+  return base::Value(std::move(dict));
 }
 
 }  // namespace
diff --git a/net/cookies/cookie_monster_change_dispatcher.cc b/net/cookies/cookie_monster_change_dispatcher.cc
index d9e41f2..cf92836 100644
--- a/net/cookies/cookie_monster_change_dispatcher.cc
+++ b/net/cookies/cookie_monster_change_dispatcher.cc
@@ -183,7 +183,7 @@
 
   std::unique_ptr<Subscription> subscription = std::make_unique<Subscription>(
       weak_ptr_factory_.GetWeakPtr(), std::string(kGlobalDomainKey),
-      std::string(kGlobalNameKey), GURL(""), CookiePartitionKey::Todo(),
+      std::string(kGlobalNameKey), GURL(""), absl::nullopt,
       first_party_sets_enabled_, std::move(callback));
 
   LinkSubscription(subscription.get());
diff --git a/net/cookies/cookie_partition_key.h b/net/cookies/cookie_partition_key.h
index 60ff48b..4b920323 100644
--- a/net/cookies/cookie_partition_key.h
+++ b/net/cookies/cookie_partition_key.h
@@ -92,10 +92,6 @@
     return absl::make_optional(CookiePartitionKey(true));
   }
 
-  // Temporary method, used to mark the places where we need to supply the
-  // cookie partition key to CanonicalCookie::Create.
-  static absl::optional<CookiePartitionKey> Todo() { return absl::nullopt; }
-
   const SchemefulSite& site() const { return site_; }
 
   bool from_script() const { return from_script_; }
diff --git a/net/ssl/openssl_ssl_util.cc b/net/ssl/openssl_ssl_util.cc
index ca3987f..c5b7bfb 100644
--- a/net/ssl/openssl_ssl_util.cc
+++ b/net/ssl/openssl_ssl_util.cc
@@ -132,18 +132,18 @@
 base::Value NetLogOpenSSLErrorParams(int net_error,
                                      int ssl_error,
                                      const OpenSSLErrorInfo& error_info) {
-  base::DictionaryValue dict;
-  dict.SetInteger("net_error", net_error);
-  dict.SetInteger("ssl_error", ssl_error);
+  base::Value::Dict dict;
+  dict.Set("net_error", net_error);
+  dict.Set("ssl_error", ssl_error);
   if (error_info.error_code != 0) {
-    dict.SetInteger("error_lib", ERR_GET_LIB(error_info.error_code));
-    dict.SetInteger("error_reason", ERR_GET_REASON(error_info.error_code));
+    dict.Set("error_lib", ERR_GET_LIB(error_info.error_code));
+    dict.Set("error_reason", ERR_GET_REASON(error_info.error_code));
   }
   if (error_info.file != nullptr)
-    dict.SetString("file", error_info.file);
+    dict.Set("file", error_info.file);
   if (error_info.line != 0)
-    dict.SetInteger("line", error_info.line);
-  return std::move(dict);
+    dict.Set("line", error_info.line);
+  return base::Value(std::move(dict));
 }
 
 }  // namespace
diff --git a/services/network/cookie_manager.cc b/services/network/cookie_manager.cc
index 07c1fb8..a9113d9 100644
--- a/services/network/cookie_manager.cc
+++ b/services/network/cookie_manager.cc
@@ -284,15 +284,17 @@
       base::Unretained(listener_registration.get()));
 
   if (name) {
+    // TODO(https://crbug.com/1225444): Include the correct cookie partition
+    // key when attaching cookie change listeners to service workers.
     listener_registration->subscription =
         cookie_store_->GetChangeDispatcher().AddCallbackForCookie(
-            url, *name, net::CookiePartitionKey::Todo(),
-            std::move(cookie_change_callback));
+            url, *name, absl::nullopt, std::move(cookie_change_callback));
   } else {
+    // TODO(https://crbug.com/1225444): Include the correct cookie partition
+    // key when attaching cookie change listeners to service workers.
     listener_registration->subscription =
         cookie_store_->GetChangeDispatcher().AddCallbackForUrl(
-            url, net::CookiePartitionKey::Todo(),
-            std::move(cookie_change_callback));
+            url, absl::nullopt, std::move(cookie_change_callback));
   }
 
   listener_registration->listener.set_disconnect_handler(
diff --git a/services/viz/privileged/mojom/compositing/frame_sink_manager.mojom b/services/viz/privileged/mojom/compositing/frame_sink_manager.mojom
index a9a0e5a..3c495df 100644
--- a/services/viz/privileged/mojom/compositing/frame_sink_manager.mojom
+++ b/services/viz/privileged/mojom/compositing/frame_sink_manager.mojom
@@ -147,6 +147,17 @@
   Throttle(array<FrameSinkId> frame_sink_ids,
            mojo_base.mojom.TimeDelta interval);
 
+  // Throttles all current and future frame sinks to send BeginFrames at an
+  // interval at least as long as |interval|. Because there is a single viz
+  // process, which itself contains a single host frame sink manager, calling
+  // this multiple times from anywhere will apply the throttling described by
+  // the latest call.
+  StartThrottlingAllFrameSinks(mojo_base.mojom.TimeDelta interval);
+
+  // Disables the global throttling triggered by StartThrottlingAllFrameSinks().
+  // If throttling is already disabled, this has no effect.
+  StopThrottlingAllFrameSinks();
+
   // Takes a snapshot of |surface_id| or a newer surface with the same
   // FrameSinkId. The request will be queued up until such surface exists and is
   // reachable from the root surface.
diff --git a/storage/browser/blob/blob_registry_impl.cc b/storage/browser/blob/blob_registry_impl.cc
index 4f73ddcf..5285bab 100644
--- a/storage/browser/blob/blob_registry_impl.cc
+++ b/storage/browser/blob/blob_registry_impl.cc
@@ -635,7 +635,6 @@
   }
   if (!context_->registry().HasEntry(uuid)) {
     LOG(ERROR) << "Invalid UUID: " << uuid;
-    // TODO(mek): Log histogram, old code logs Storage.Blob.InvalidReference
     std::move(callback).Run();
     return;
   }
diff --git a/storage/browser/blob/blob_storage_context.cc b/storage/browser/blob/blob_storage_context.cc
index 9e2a2416..1a8fecf 100644
--- a/storage/browser/blob/blob_storage_context.cc
+++ b/storage/browser/blob/blob_storage_context.cc
@@ -17,7 +17,6 @@
 #include "base/location.h"
 #include "base/logging.h"
 #include "base/memory/ptr_util.h"
-#include "base/metrics/histogram_macros.h"
 #include "base/numerics/safe_conversions.h"
 #include "base/numerics/safe_math.h"
 #include "base/strings/stringprintf.h"
@@ -504,14 +503,6 @@
   BlobStatus status = entry->status_;
   DCHECK_NE(BlobStatus::DONE, status);
 
-  bool error = BlobStatusIsError(status);
-  UMA_HISTOGRAM_BOOLEAN("Storage.Blob.Broken", error);
-  if (error) {
-    UMA_HISTOGRAM_ENUMERATION("Storage.Blob.BrokenReason",
-                              static_cast<int>(status),
-                              (static_cast<int>(BlobStatus::LAST_ERROR) + 1));
-  }
-
   if (BlobStatusIsPending(entry->status_)) {
     for (const ItemCopyEntry& copy : entry->building_state_->copies) {
       // Our source item can be a file if it was a slice of an unpopulated file,
diff --git a/testing/buildbot/chromium.android.fyi.json b/testing/buildbot/chromium.android.fyi.json
index 689b726..31a7041 100644
--- a/testing/buildbot/chromium.android.fyi.json
+++ b/testing/buildbot/chromium.android.fyi.json
@@ -8155,15 +8155,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
+          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
+          "--client-outdir",
+          "../../weblayer_instrumentation_test_M101/out/Release",
           "--implementation-outdir",
           ".",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/SystemWebView.apk",
-          "--client-outdir",
-          "../../weblayer_instrumentation_test_M101/out/Release",
           "--client-version=101",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -8189,7 +8189,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M101",
-              "revision": "version:101.0.4951.69"
+              "revision": "version:101.0.4951.74"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -8665,15 +8665,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
+          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
           "--test-runner-outdir",
           ".",
           "--client-outdir",
           ".",
-          "--test-expectations",
-          "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
           "--implementation-outdir",
           "../../weblayer_instrumentation_test_M101/out/Release",
+          "--test-expectations",
+          "../../weblayer/browser/android/javatests/skew/expectations.txt",
           "--impl-version=101",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -8699,7 +8699,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M101",
-              "revision": "version:101.0.4951.69"
+              "revision": "version:101.0.4951.74"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
diff --git a/testing/buildbot/chromium.android.json b/testing/buildbot/chromium.android.json
index bb521f8c..6bef602 100644
--- a/testing/buildbot/chromium.android.json
+++ b/testing/buildbot/chromium.android.json
@@ -46129,15 +46129,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
+          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
+          "--client-outdir",
+          "../../weblayer_instrumentation_test_M101/out/Release",
           "--implementation-outdir",
           ".",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/SystemWebView.apk",
-          "--client-outdir",
-          "../../weblayer_instrumentation_test_M101/out/Release",
           "--client-version=101",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -46163,7 +46163,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M101",
-              "revision": "version:101.0.4951.69"
+              "revision": "version:101.0.4951.74"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -46639,15 +46639,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
+          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
           "--test-runner-outdir",
           ".",
           "--client-outdir",
           ".",
-          "--test-expectations",
-          "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
           "--implementation-outdir",
           "../../weblayer_instrumentation_test_M101/out/Release",
+          "--test-expectations",
+          "../../weblayer/browser/android/javatests/skew/expectations.txt",
           "--impl-version=101",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -46673,7 +46673,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M101",
-              "revision": "version:101.0.4951.69"
+              "revision": "version:101.0.4951.74"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -47153,15 +47153,15 @@
       {
         "args": [
           "--additional-apk=apks/ChromePublic.apk",
+          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
+          "--client-outdir",
+          "../../weblayer_instrumentation_test_M101/out/Release",
           "--implementation-outdir",
           ".",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/SystemWebView.apk",
-          "--client-outdir",
-          "../../weblayer_instrumentation_test_M101/out/Release",
           "--client-version=101",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -47187,7 +47187,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M101",
-              "revision": "version:101.0.4951.69"
+              "revision": "version:101.0.4951.74"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -47663,15 +47663,15 @@
       {
         "args": [
           "--additional-apk=apks/ChromePublic.apk",
+          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
           "--test-runner-outdir",
           ".",
           "--client-outdir",
           ".",
-          "--test-expectations",
-          "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
           "--implementation-outdir",
           "../../weblayer_instrumentation_test_M101/out/Release",
+          "--test-expectations",
+          "../../weblayer/browser/android/javatests/skew/expectations.txt",
           "--impl-version=101",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -47697,7 +47697,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M101",
-              "revision": "version:101.0.4951.69"
+              "revision": "version:101.0.4951.74"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -48245,15 +48245,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
+          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
+          "--client-outdir",
+          "../../weblayer_instrumentation_test_M101/out/Release",
           "--implementation-outdir",
           ".",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/SystemWebView.apk",
-          "--client-outdir",
-          "../../weblayer_instrumentation_test_M101/out/Release",
           "--client-version=101",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -48279,7 +48279,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M101",
-              "revision": "version:101.0.4951.69"
+              "revision": "version:101.0.4951.74"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -48755,15 +48755,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
+          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
           "--client-outdir",
           ".",
-          "--test-expectations",
-          "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/SystemWebView.apk",
           "--implementation-outdir",
           "../../weblayer_instrumentation_test_M101/out/Release",
+          "--test-expectations",
+          "../../weblayer/browser/android/javatests/skew/expectations.txt",
           "--impl-version=101",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -48789,7 +48789,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M101",
-              "revision": "version:101.0.4951.69"
+              "revision": "version:101.0.4951.74"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -49337,15 +49337,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
+          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
+          "--client-outdir",
+          "../../weblayer_instrumentation_test_M101/out/Release",
           "--implementation-outdir",
           ".",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/SystemWebView.apk",
-          "--client-outdir",
-          "../../weblayer_instrumentation_test_M101/out/Release",
           "--client-version=101",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -49371,7 +49371,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M101",
-              "revision": "version:101.0.4951.69"
+              "revision": "version:101.0.4951.74"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -49847,15 +49847,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
+          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
           "--client-outdir",
           ".",
-          "--test-expectations",
-          "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/SystemWebView.apk",
           "--implementation-outdir",
           "../../weblayer_instrumentation_test_M101/out/Release",
+          "--test-expectations",
+          "../../weblayer/browser/android/javatests/skew/expectations.txt",
           "--impl-version=101",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -49881,7 +49881,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M101",
-              "revision": "version:101.0.4951.69"
+              "revision": "version:101.0.4951.74"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
diff --git a/testing/buildbot/chromium.clang.json b/testing/buildbot/chromium.clang.json
index 2d910ce..01dfa8f 100644
--- a/testing/buildbot/chromium.clang.json
+++ b/testing/buildbot/chromium.clang.json
@@ -8942,7 +8942,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
         "test": "cast_runner_browsertests",
-        "test_id_prefix": "ninja://fuchsia/runners:cast_runner_browsertests/"
+        "test_id_prefix": "ninja://fuchsia_web/runners:cast_runner_browsertests/"
       },
       {
         "merge": {
@@ -8960,7 +8960,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
         "test": "cast_runner_integration_tests",
-        "test_id_prefix": "ninja://fuchsia/runners:cast_runner_integration_tests/"
+        "test_id_prefix": "ninja://fuchsia_web/runners:cast_runner_integration_tests/"
       },
       {
         "merge": {
@@ -8978,7 +8978,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
         "test": "cast_runner_unittests",
-        "test_id_prefix": "ninja://fuchsia/runners:cast_runner_unittests/"
+        "test_id_prefix": "ninja://fuchsia_web/runners:cast_runner_unittests/"
       },
       {
         "merge": {
@@ -9908,7 +9908,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
         "test": "web_runner_integration_tests",
-        "test_id_prefix": "ninja://fuchsia/runners:web_runner_integration_tests/"
+        "test_id_prefix": "ninja://fuchsia_web/runners:web_runner_integration_tests/"
       },
       {
         "merge": {
@@ -10490,7 +10490,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
         "test": "cast_runner_browsertests",
-        "test_id_prefix": "ninja://fuchsia/runners:cast_runner_browsertests/"
+        "test_id_prefix": "ninja://fuchsia_web/runners:cast_runner_browsertests/"
       },
       {
         "merge": {
@@ -10509,7 +10509,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
         "test": "cast_runner_integration_tests",
-        "test_id_prefix": "ninja://fuchsia/runners:cast_runner_integration_tests/"
+        "test_id_prefix": "ninja://fuchsia_web/runners:cast_runner_integration_tests/"
       },
       {
         "merge": {
@@ -10528,7 +10528,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
         "test": "cast_runner_unittests",
-        "test_id_prefix": "ninja://fuchsia/runners:cast_runner_unittests/"
+        "test_id_prefix": "ninja://fuchsia_web/runners:cast_runner_unittests/"
       },
       {
         "merge": {
@@ -11308,7 +11308,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
         "test": "web_runner_integration_tests",
-        "test_id_prefix": "ninja://fuchsia/runners:web_runner_integration_tests/"
+        "test_id_prefix": "ninja://fuchsia_web/runners:web_runner_integration_tests/"
       },
       {
         "merge": {
diff --git a/testing/buildbot/chromium.fuchsia.fyi.json b/testing/buildbot/chromium.fuchsia.fyi.json
index a15aebbb1..7d218bc00 100644
--- a/testing/buildbot/chromium.fuchsia.fyi.json
+++ b/testing/buildbot/chromium.fuchsia.fyi.json
@@ -251,7 +251,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
         "test": "cast_runner_browsertests",
-        "test_id_prefix": "ninja://fuchsia/runners:cast_runner_browsertests/"
+        "test_id_prefix": "ninja://fuchsia_web/runners:cast_runner_browsertests/"
       },
       {
         "merge": {
@@ -270,7 +270,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
         "test": "cast_runner_integration_tests",
-        "test_id_prefix": "ninja://fuchsia/runners:cast_runner_integration_tests/"
+        "test_id_prefix": "ninja://fuchsia_web/runners:cast_runner_integration_tests/"
       },
       {
         "merge": {
@@ -289,7 +289,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
         "test": "cast_runner_unittests",
-        "test_id_prefix": "ninja://fuchsia/runners:cast_runner_unittests/"
+        "test_id_prefix": "ninja://fuchsia_web/runners:cast_runner_unittests/"
       },
       {
         "merge": {
@@ -1078,7 +1078,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
         "test": "web_runner_integration_tests",
-        "test_id_prefix": "ninja://fuchsia/runners:web_runner_integration_tests/"
+        "test_id_prefix": "ninja://fuchsia_web/runners:web_runner_integration_tests/"
       },
       {
         "merge": {
@@ -1405,7 +1405,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
         "test": "cast_runner_browsertests",
-        "test_id_prefix": "ninja://fuchsia/runners:cast_runner_browsertests/"
+        "test_id_prefix": "ninja://fuchsia_web/runners:cast_runner_browsertests/"
       },
       {
         "merge": {
@@ -1423,7 +1423,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
         "test": "cast_runner_integration_tests",
-        "test_id_prefix": "ninja://fuchsia/runners:cast_runner_integration_tests/"
+        "test_id_prefix": "ninja://fuchsia_web/runners:cast_runner_integration_tests/"
       },
       {
         "merge": {
@@ -1441,7 +1441,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
         "test": "cast_runner_unittests",
-        "test_id_prefix": "ninja://fuchsia/runners:cast_runner_unittests/"
+        "test_id_prefix": "ninja://fuchsia_web/runners:cast_runner_unittests/"
       },
       {
         "merge": {
@@ -2380,7 +2380,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
         "test": "web_runner_integration_tests",
-        "test_id_prefix": "ninja://fuchsia/runners:web_runner_integration_tests/"
+        "test_id_prefix": "ninja://fuchsia_web/runners:web_runner_integration_tests/"
       },
       {
         "merge": {
@@ -2701,7 +2701,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
         "test": "cast_runner_browsertests",
-        "test_id_prefix": "ninja://fuchsia/runners:cast_runner_browsertests/"
+        "test_id_prefix": "ninja://fuchsia_web/runners:cast_runner_browsertests/"
       },
       {
         "merge": {
@@ -2719,7 +2719,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
         "test": "cast_runner_integration_tests",
-        "test_id_prefix": "ninja://fuchsia/runners:cast_runner_integration_tests/"
+        "test_id_prefix": "ninja://fuchsia_web/runners:cast_runner_integration_tests/"
       },
       {
         "merge": {
@@ -2737,7 +2737,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
         "test": "cast_runner_unittests",
-        "test_id_prefix": "ninja://fuchsia/runners:cast_runner_unittests/"
+        "test_id_prefix": "ninja://fuchsia_web/runners:cast_runner_unittests/"
       },
       {
         "merge": {
@@ -3667,7 +3667,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
         "test": "web_runner_integration_tests",
-        "test_id_prefix": "ninja://fuchsia/runners:web_runner_integration_tests/"
+        "test_id_prefix": "ninja://fuchsia_web/runners:web_runner_integration_tests/"
       },
       {
         "merge": {
diff --git a/testing/buildbot/chromium.fuchsia.json b/testing/buildbot/chromium.fuchsia.json
index 2ea21e78..aad12baf 100644
--- a/testing/buildbot/chromium.fuchsia.json
+++ b/testing/buildbot/chromium.fuchsia.json
@@ -251,7 +251,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
         "test": "cast_runner_browsertests",
-        "test_id_prefix": "ninja://fuchsia/runners:cast_runner_browsertests/"
+        "test_id_prefix": "ninja://fuchsia_web/runners:cast_runner_browsertests/"
       },
       {
         "merge": {
@@ -270,7 +270,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
         "test": "cast_runner_integration_tests",
-        "test_id_prefix": "ninja://fuchsia/runners:cast_runner_integration_tests/"
+        "test_id_prefix": "ninja://fuchsia_web/runners:cast_runner_integration_tests/"
       },
       {
         "merge": {
@@ -289,7 +289,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
         "test": "cast_runner_unittests",
-        "test_id_prefix": "ninja://fuchsia/runners:cast_runner_unittests/"
+        "test_id_prefix": "ninja://fuchsia_web/runners:cast_runner_unittests/"
       },
       {
         "merge": {
@@ -1069,7 +1069,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
         "test": "web_runner_integration_tests",
-        "test_id_prefix": "ninja://fuchsia/runners:web_runner_integration_tests/"
+        "test_id_prefix": "ninja://fuchsia_web/runners:web_runner_integration_tests/"
       },
       {
         "merge": {
@@ -1416,7 +1416,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
         "test": "cast_runner_browsertests",
-        "test_id_prefix": "ninja://fuchsia/runners:cast_runner_browsertests/"
+        "test_id_prefix": "ninja://fuchsia_web/runners:cast_runner_browsertests/"
       },
       {
         "merge": {
@@ -1434,7 +1434,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
         "test": "cast_runner_integration_tests",
-        "test_id_prefix": "ninja://fuchsia/runners:cast_runner_integration_tests/"
+        "test_id_prefix": "ninja://fuchsia_web/runners:cast_runner_integration_tests/"
       },
       {
         "merge": {
@@ -1452,7 +1452,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
         "test": "cast_runner_unittests",
-        "test_id_prefix": "ninja://fuchsia/runners:cast_runner_unittests/"
+        "test_id_prefix": "ninja://fuchsia_web/runners:cast_runner_unittests/"
       },
       {
         "merge": {
@@ -2382,7 +2382,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
         "test": "web_runner_integration_tests",
-        "test_id_prefix": "ninja://fuchsia/runners:web_runner_integration_tests/"
+        "test_id_prefix": "ninja://fuchsia_web/runners:web_runner_integration_tests/"
       },
       {
         "merge": {
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index c66efa71..7c4f096 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -19819,7 +19819,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
         "test": "cast_runner_browsertests",
-        "test_id_prefix": "ninja://fuchsia/runners:cast_runner_browsertests/"
+        "test_id_prefix": "ninja://fuchsia_web/runners:cast_runner_browsertests/"
       },
       {
         "args": [
@@ -19843,7 +19843,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
         "test": "cast_runner_integration_tests",
-        "test_id_prefix": "ninja://fuchsia/runners:cast_runner_integration_tests/"
+        "test_id_prefix": "ninja://fuchsia_web/runners:cast_runner_integration_tests/"
       },
       {
         "args": [
@@ -19867,7 +19867,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
         "test": "cast_runner_unittests",
-        "test_id_prefix": "ninja://fuchsia/runners:cast_runner_unittests/"
+        "test_id_prefix": "ninja://fuchsia_web/runners:cast_runner_unittests/"
       },
       {
         "args": [
@@ -21088,7 +21088,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
         "test": "web_runner_integration_tests",
-        "test_id_prefix": "ninja://fuchsia/runners:web_runner_integration_tests/"
+        "test_id_prefix": "ninja://fuchsia_web/runners:web_runner_integration_tests/"
       },
       {
         "args": [
@@ -21456,7 +21456,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
         "test": "cast_runner_browsertests",
-        "test_id_prefix": "ninja://fuchsia/runners:cast_runner_browsertests/"
+        "test_id_prefix": "ninja://fuchsia_web/runners:cast_runner_browsertests/"
       },
       {
         "merge": {
@@ -21476,7 +21476,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
         "test": "cast_runner_integration_tests",
-        "test_id_prefix": "ninja://fuchsia/runners:cast_runner_integration_tests/"
+        "test_id_prefix": "ninja://fuchsia_web/runners:cast_runner_integration_tests/"
       },
       {
         "merge": {
@@ -21496,7 +21496,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
         "test": "cast_runner_unittests",
-        "test_id_prefix": "ninja://fuchsia/runners:cast_runner_unittests/"
+        "test_id_prefix": "ninja://fuchsia_web/runners:cast_runner_unittests/"
       },
       {
         "merge": {
@@ -22526,7 +22526,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
         "test": "web_runner_integration_tests",
-        "test_id_prefix": "ninja://fuchsia/runners:web_runner_integration_tests/"
+        "test_id_prefix": "ninja://fuchsia_web/runners:web_runner_integration_tests/"
       },
       {
         "merge": {
diff --git a/testing/buildbot/filters/fuchsia.browser_tests.filter b/testing/buildbot/filters/fuchsia.browser_tests.filter
index 3fb6f52..ac262fa 100644
--- a/testing/buildbot/filters/fuchsia.browser_tests.filter
+++ b/testing/buildbot/filters/fuchsia.browser_tests.filter
@@ -108,13 +108,6 @@
 # Unexpected infobar hung_plugin
 -InfoBarUiTest.InvokeUi_multiple_infobars
 
-# TODO(crbug.com/1326968): [loopback_server.cc(887)] Loopback sync cannot read the persistent state
-# file (/tmp/.org.chromium.Chromium.JpoHmf/user_data/FakeSyncServer/profile.pb) with error
-# FILE_ERROR_NOT_FOUND Possibly broken because profile sign in isn't supported, although it could be
-# because /tmp isn't being created
--MetricsServiceUserDemographicsBrowserTest.AddSyncedUserBirthYearAndGenderToProtoData/0
--MetricsServiceUserDemographicsBrowserTest.AddSyncedUserBirthYearAndGenderToProtoData/1
-
 # TODO(crbug.com/1326969): Expects click to deselect text, but click seems like it might not be
 # hitting the right location
 -OmniboxPopupContentsViewTest.ClickOmnibox
diff --git a/testing/buildbot/filters/ozone-linux.wayland_browser_tests.filter b/testing/buildbot/filters/ozone-linux.wayland_browser_tests.filter
index b7d4d4b..2c76c89f 100644
--- a/testing/buildbot/filters/ozone-linux.wayland_browser_tests.filter
+++ b/testing/buildbot/filters/ozone-linux.wayland_browser_tests.filter
@@ -11,6 +11,8 @@
 -ChromeSitePerProcessTest.PopupWindowFocus
 -ExternalProtocolDialogBrowserTest.TestFocus
 -FolderUploadConfirmationViewTest.InitiallyFocusesCancel
+-JavaScriptTabModalDialogViewViewsBrowserTest.AlertDialogAccessibleNameDescriptionAndRole
+-JavaScriptTabModalDialogViewViewsBrowserTest.AlertDialogCloseButtonAccessibilityIgnored
 -PreservedWindowPlacement.Test
 -ProfileHelperTest.OpenNewWindowForProfile
 -TabHoverCardBubbleViewBrowserTest.WidgetNotVisibleOnMousePressAfterTabFocus
diff --git a/testing/buildbot/gn_isolate_map.pyl b/testing/buildbot/gn_isolate_map.pyl
index 8bf88109..d63b81a 100644
--- a/testing/buildbot/gn_isolate_map.pyl
+++ b/testing/buildbot/gn_isolate_map.pyl
@@ -327,19 +327,19 @@
     "type": "console_test_launcher",
   },
   "cast_runner_browsertests": {
-    "label": "//fuchsia/runners:cast_runner_browsertests",
+    "label": "//fuchsia_web/runners:cast_runner_browsertests",
     "type": "console_test_launcher",
   },
   "cast_runner_integration_tests": {
-    "label": "//fuchsia/runners:cast_runner_integration_tests",
+    "label": "//fuchsia_web/runners:cast_runner_integration_tests",
     "type": "console_test_launcher",
   },
   "cast_runner_pkg":{
-    "label": "//fuchsia/runners:cast_runner_pkg",
+    "label": "//fuchsia_web/runners:cast_runner_pkg",
     "type": "additonal_compile_target",
   },
   "cast_runner_unittests": {
-    "label": "//fuchsia/runners:cast_runner_unittests",
+    "label": "//fuchsia_web/runners:cast_runner_unittests",
     "type": "console_test_launcher",
   },
   "cast_audio_backend_unittests": {
@@ -2015,11 +2015,11 @@
     "type": "console_test_launcher",
   },
   "web_runner_integration_tests": {
-    "label": "//fuchsia/runners:web_runner_integration_tests",
+    "label": "//fuchsia_web/runners:web_runner_integration_tests",
     "type": "console_test_launcher",
   },
   "web_runner_pkg": {
-    "label": "//fuchsia/runners:web_runner_pkg",
+    "label": "//fuchsia_web/runners:web_runner_pkg",
     "type": "additonal_compile_target",
   },
   "webapk_client_junit_tests": {
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl
index cdba141..597f363 100644
--- a/testing/buildbot/variants.pyl
+++ b/testing/buildbot/variants.pyl
@@ -450,16 +450,16 @@
   },
   'WEBLAYER_10_AND_M_IMPL_SKEW_TESTS_NTH_MINUS_TWO_MILESTONE': {
     'args': [
+      '--webview-apk-path=apks/AOSP_SystemWebView.apk',
       '--test-runner-outdir',
       '.',
       '--client-outdir',
       '.',
-      '--test-expectations',
-      '../../weblayer/browser/android/javatests/skew/expectations.txt',
-      '--webview-apk-path=apks/AOSP_SystemWebView.apk',
       '--implementation-outdir',
       '../../weblayer_instrumentation_test_M101/out/Release',
-      '--impl-version=101'
+      '--test-expectations',
+      '../../weblayer/browser/android/javatests/skew/expectations.txt',
+      '--impl-version=101',
     ],
     'identifier': 'with_impl_from_101',
     'swarming': {
@@ -467,10 +467,10 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M101',
-          'revision': 'version:101.0.4951.69'
+          'revision': 'version:101.0.4951.74',
         }
-      ]
-    }
+      ],
+    },
   },
   'WEBLAYER_10_AND_M_IMPL_SKEW_TESTS_NTH_MINUS_THREE_MILESTONE': {
     'args': [
@@ -594,16 +594,16 @@
   },
   'WEBLAYER_IMPL_SKEW_TESTS_NTH_MINUS_TWO_MILESTONE': {
     'args': [
+      '--webview-apk-path=apks/SystemWebView.apk',
       '--test-runner-outdir',
       '.',
       '--client-outdir',
       '.',
-      '--test-expectations',
-      '../../weblayer/browser/android/javatests/skew/expectations.txt',
-      '--webview-apk-path=apks/SystemWebView.apk',
       '--implementation-outdir',
       '../../weblayer_instrumentation_test_M101/out/Release',
-      '--impl-version=101'
+      '--test-expectations',
+      '../../weblayer/browser/android/javatests/skew/expectations.txt',
+      '--impl-version=101',
     ],
     'identifier': 'with_impl_from_101',
     'swarming': {
@@ -611,10 +611,10 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M101',
-          'revision': 'version:101.0.4951.69'
+          'revision': 'version:101.0.4951.74',
         }
-      ]
-    }
+      ],
+    },
   },
   'WEBLAYER_IMPL_SKEW_TESTS_NTH_MINUS_THREE_MILESTONE': {
     'args': [
@@ -738,16 +738,16 @@
   },
   'WEBLAYER_CLIENT_SKEW_TESTS_NTH_MINUS_TWO_MILESTONE': {
     'args': [
+      '--webview-apk-path=apks/SystemWebView.apk',
       '--test-runner-outdir',
       '.',
+      '--client-outdir',
+      '../../weblayer_instrumentation_test_M101/out/Release',
       '--implementation-outdir',
       '.',
       '--test-expectations',
       '../../weblayer/browser/android/javatests/skew/expectations.txt',
-      '--webview-apk-path=apks/SystemWebView.apk',
-      '--client-outdir',
-      '../../weblayer_instrumentation_test_M101/out/Release',
-      '--client-version=101'
+      '--client-version=101',
     ],
     'identifier': 'with_client_from_101',
     'swarming': {
@@ -755,10 +755,10 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M101',
-          'revision': 'version:101.0.4951.69'
+          'revision': 'version:101.0.4951.74',
         }
-      ]
-    }
+      ],
+    },
   },
   'WEBLAYER_CLIENT_SKEW_TESTS_NTH_MINUS_THREE_MILESTONE': {
     'args': [
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 5dab1f3..6e86b56 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -543,6 +543,21 @@
             ]
         }
     ],
+    "ArcVmBroadcastPreAnrHandling": [
+        {
+            "platforms": [
+                "chromeos"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "ArcVmBroadcastPreAnrHandling"
+                    ]
+                }
+            ]
+        }
+    ],
     "ArcVmDalvikMemoryProfile": [
         {
             "platforms": [
@@ -1149,28 +1164,6 @@
             ]
         }
     ],
-    "AutofillIgnoreEarlyClicksOnPopup": [
-        {
-            "platforms": [
-                "chromeos",
-                "chromeos_lacros",
-                "linux",
-                "mac",
-                "windows"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled_500ms",
-                    "params": {
-                        "duration": "500ms"
-                    },
-                    "enable_features": [
-                        "AutofillIgnoreEarlyClicksOnPopup"
-                    ]
-                }
-            ]
-        }
-    ],
     "AutofillKeyboardAccessory": [
         {
             "platforms": [
diff --git a/third_party/blink/common/client_hints/client_hints.cc b/third_party/blink/common/client_hints/client_hints.cc
index fb72470..7de1dbd 100644
--- a/third_party/blink/common/client_hints/client_hints.cc
+++ b/third_party/blink/common/client_hints/client_hints.cc
@@ -120,10 +120,8 @@
     case network::mojom::WebClientHintsType::kSaveData:
     case network::mojom::WebClientHintsType::kUA:
     case network::mojom::WebClientHintsType::kUAMobile:
-      return true;
     case network::mojom::WebClientHintsType::kUAPlatform:
-      return base::FeatureList::IsEnabled(
-          features::kUACHPlatformEnabledByDefault);
+      return true;
     default:
       return false;
   }
diff --git a/third_party/blink/common/features.cc b/third_party/blink/common/features.cc
index b1ed2c9..dd4dcf6 100644
--- a/third_party/blink/common/features.cc
+++ b/third_party/blink/common/features.cc
@@ -1131,11 +1131,6 @@
 const base::Feature kUsePageViewportInLCP{"UsePageViewportInLCP",
                                           base::FEATURE_ENABLED_BY_DEFAULT};
 
-// Enable `Sec-CH-UA-Platform` client hint and request header to be sent by
-// default
-const base::Feature kUACHPlatformEnabledByDefault{
-    "UACHPlatformEnabledByDefault", base::FEATURE_ENABLED_BY_DEFAULT};
-
 // When enabled, allow dropping alpha on media streams for rendering sinks if
 // other sinks connected do not use alpha.
 const base::Feature kAllowDropAlphaForMediaStream{
diff --git a/third_party/blink/public/common/features.h b/third_party/blink/public/common/features.h
index be2e45e4..38be643c 100644
--- a/third_party/blink/public/common/features.h
+++ b/third_party/blink/public/common/features.h
@@ -513,9 +513,6 @@
 // heuristic where images occupying the full viewport are ignored.
 BLINK_COMMON_EXPORT extern const base::Feature kUsePageViewportInLCP;
 
-// Enable "Sec-CH-UA-Platform" client hint and request header for all requests
-BLINK_COMMON_EXPORT extern const base::Feature kUACHPlatformEnabledByDefault;
-
 // When enabled, allow dropping alpha on media streams for rendering sinks if
 // other sinks connected do not use alpha.
 BLINK_COMMON_EXPORT extern const base::Feature kAllowDropAlphaForMediaStream;
diff --git a/third_party/blink/public/devtools_protocol/browser_protocol.pdl b/third_party/blink/public/devtools_protocol/browser_protocol.pdl
index 0230f78..1c633e98 100644
--- a/third_party/blink/public/devtools_protocol/browser_protocol.pdl
+++ b/third_party/blink/public/devtools_protocol/browser_protocol.pdl
@@ -702,7 +702,7 @@
       InvalidHeader
 
   # Details for issues around "Attribution Reporting API" usage.
-  # Explainer: https://github.com/WICG/conversion-measurement-api
+  # Explainer: https://github.com/WICG/attribution-reporting-api
   type AttributionReportingIssueDetails extends object
     properties
       AttributionReportingIssueType violationType
@@ -767,7 +767,6 @@
       NotificationInsecureOrigin
       NotificationPermissionRequestedIframe
       ObsoleteWebRtcCipherSuite
-      PaymentRequestBasicCard
       PictureSourceSrc
       PrefixedCancelAnimationFrame
       PrefixedRequestAnimationFrame
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 1dd5fc6..f5c0869 100644
--- a/third_party/blink/public/mojom/conversions/attribution_data_host.mojom
+++ b/third_party/blink/public/mojom/conversions/attribution_data_host.mojom
@@ -13,7 +13,7 @@
 };
 
 // Filter data for selectively matching attribution sources and triggers.
-// See https://github.com/WICG/conversion-measurement-api/blob/main/EVENT.md#optional-attribution-filters
+// See https://github.com/WICG/attribution-reporting-api/blob/main/EVENT.md#optional-attribution-filters
 // for details.
 struct AttributionFilterData {
   // Map of filter name to a possibly empty set of values.
@@ -98,7 +98,7 @@
 
 // Represents a request from a reporting origin to trigger attribution on a
 // given site. See:
-// https://github.com/WICG/conversion-measurement-api/blob/main/EVENT.md#triggering-attribution
+// https://github.com/WICG/attribution-reporting-api/blob/main/EVENT.md#triggering-attribution
 struct AttributionTriggerData {
   // Origin that registered this trigger, used to determine which source this
   // trigger is associated with.
diff --git a/third_party/blink/renderer/core/BUILD.gn b/third_party/blink/renderer/core/BUILD.gn
index d461be9..4a79c311 100644
--- a/third_party/blink/renderer/core/BUILD.gn
+++ b/third_party/blink/renderer/core/BUILD.gn
@@ -1908,8 +1908,8 @@
   seed_corpus = "//third_party/blink/renderer/core/frame/attribution_src/attribution_source_registration_corpus"
 }
 
-fuzzer_test("attribution_event_trigger_data_fuzzer") {
-  sources = [ "frame/attribution_event_trigger_data_fuzzer.cc" ]
+fuzzer_test("attribution_trigger_registration_fuzzer") {
+  sources = [ "frame/attribution_trigger_registration_fuzzer.cc" ]
   deps = [
     ":core",
     "//testing/libfuzzer/proto:json_proto",
@@ -1917,29 +1917,5 @@
     "//third_party/blink/renderer/platform:blink_fuzzer_test_support",
     "//third_party/libprotobuf-mutator",
   ]
-  seed_corpus = "//third_party/blink/renderer/core/frame/attribution_src/attribution_event_trigger_data_corpus"
-}
-
-fuzzer_test("attribution_aggregatable_values_fuzzer") {
-  sources = [ "frame/attribution_aggregatable_values_fuzzer.cc" ]
-  deps = [
-    ":core",
-    "//testing/libfuzzer/proto:json_proto",
-    "//testing/libfuzzer/proto:json_proto_converter",
-    "//third_party/blink/renderer/platform:blink_fuzzer_test_support",
-    "//third_party/libprotobuf-mutator",
-  ]
-  seed_corpus = "//third_party/blink/renderer/core/frame/attribution_src/attribution_aggregatable_values_corpus"
-}
-
-fuzzer_test("attribution_aggregatable_trigger_data_fuzzer") {
-  sources = [ "frame/attribution_aggregatable_trigger_data_fuzzer.cc" ]
-  deps = [
-    ":core",
-    "//testing/libfuzzer/proto:json_proto",
-    "//testing/libfuzzer/proto:json_proto_converter",
-    "//third_party/blink/renderer/platform:blink_fuzzer_test_support",
-    "//third_party/libprotobuf-mutator",
-  ]
-  seed_corpus = "//third_party/blink/renderer/core/frame/attribution_src/attribution_aggregatable_trigger_data_corpus"
+  seed_corpus = "//third_party/blink/renderer/core/frame/attribution_src/attribution_trigger_registration_corpus"
 }
diff --git a/third_party/blink/renderer/core/css/affected_by_pseudo_test.cc b/third_party/blink/renderer/core/css/affected_by_pseudo_test.cc
index c167706..4523a33 100644
--- a/third_party/blink/renderer/core/css/affected_by_pseudo_test.cc
+++ b/third_party/blink/renderer/core/css/affected_by_pseudo_test.cc
@@ -36,7 +36,8 @@
     kSiblingsAffectedByHasForSiblingRelationship,
     kSiblingsAffectedByHasForSiblingDescendantRelationship,
     kAffectedByPseudoInHas,
-    kAncestorsOrSiblingsAffectedByHoverInHas
+    kAncestorsOrSiblingsAffectedByHoverInHas,
+    kAffectedByLogicalCombinationsInHas
   };
   void CheckAffectedByFlagsForHas(
       const char* element_id,
@@ -115,6 +116,11 @@
                      ->AncestorsOrSiblingsAffectedByHoverInHas();
         flag_name = "AncestorsOrSiblingsAffectedByHoverInHas";
         break;
+      case kAffectedByLogicalCombinationsInHas:
+        actual =
+            GetElementById(element_id)->AffectedByLogicalCombinationsInHas();
+        flag_name = "AffectedByLogicalCombinationsInHas";
+        break;
     }
     DCHECK(flag_name);
     if (iter.second == actual)
@@ -4233,4 +4239,82 @@
                 GetCSSPropertyColor()));
 }
 
+TEST_F(AffectedByPseudoTest, AffectedByLogicalCombinationsInHas) {
+  SetHtmlInnerHTML(R"HTML(
+    <style>
+      .a:has(:is(.b .c)) { color: green; }
+      .d:has(:is(.e)) { color: green; }
+    </style>
+    <div id=div1>
+      <div id=div11 class='a'>
+        <div id=div111>
+          <div id=div1111 class='c'></div>
+        </div>
+      </div>
+      <div id=div12 class='d'>
+        <div id=div121>
+          <div id=div1211></div>
+        </div>
+      </div>
+    </div>
+  )HTML");
+
+  UpdateAllLifecyclePhasesForTest();
+  CheckAffectedByFlagsForHas(
+      "div1", {{kAffectedBySubjectHas, false},
+               {kAffectedByLogicalCombinationsInHas, false},
+               {kAncestorsOrAncestorSiblingsAffectedByHas, false},
+               {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div11",
+                             {{kAffectedBySubjectHas, true},
+                              {kAffectedByLogicalCombinationsInHas, true},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div111",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div1111",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div12",
+                             {{kAffectedBySubjectHas, true},
+                              {kAffectedByLogicalCombinationsInHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div121",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+  CheckAffectedByFlagsForHas("div1211",
+                             {{kAffectedBySubjectHas, false},
+                              {kAffectedByNonSubjectHas, false},
+                              {kAncestorsOrAncestorSiblingsAffectedByHas, true},
+                              {kSiblingsAffectedByHas, false}});
+
+  unsigned start_count = GetStyleEngine().StyleForElementCount();
+  GetElementById("div11")->setAttribute(html_names::kClassAttr, "a b");
+  UpdateAllLifecyclePhasesForTest();
+  EXPECT_EQ(1U, GetStyleEngine().StyleForElementCount() - start_count);
+
+  start_count = GetStyleEngine().StyleForElementCount();
+  GetElementById("div11")->setAttribute(html_names::kClassAttr, "a");
+  UpdateAllLifecyclePhasesForTest();
+  EXPECT_EQ(1U, GetStyleEngine().StyleForElementCount() - start_count);
+
+  start_count = GetStyleEngine().StyleForElementCount();
+  GetElementById("div11")->setAttribute(html_names::kClassAttr, "a invalid");
+  UpdateAllLifecyclePhasesForTest();
+  EXPECT_EQ(0U, GetStyleEngine().StyleForElementCount() - start_count);
+
+  start_count = GetStyleEngine().StyleForElementCount();
+  GetElementById("div12")->setAttribute(html_names::kClassAttr, "d e");
+  UpdateAllLifecyclePhasesForTest();
+  EXPECT_EQ(0U, GetStyleEngine().StyleForElementCount() - start_count);
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/css_selector.cc b/third_party/blink/renderer/core/css/css_selector.cc
index b8125240..5cf4225 100644
--- a/third_party/blink/renderer/core/css/css_selector.cc
+++ b/third_party/blink/renderer/core/css/css_selector.cc
@@ -1063,7 +1063,12 @@
 
 void CSSSelector::SetContainsPseudoInsideHasPseudoClass() {
   CreateRareData();
-  data_.rare_data_->bits_.contains_pseudo_inside_has_pseudo_class_ = true;
+  data_.rare_data_->bits_.has_.contains_pseudo_ = true;
+}
+
+void CSSSelector::SetContainsComplexLogicalCombinationsInsideHasPseudoClass() {
+  CreateRareData();
+  data_.rare_data_->bits_.has_.contains_complex_logical_combinations_ = true;
 }
 
 static bool ValidateSubSelector(const CSSSelector* selector) {
diff --git a/third_party/blink/renderer/core/css/css_selector.h b/third_party/blink/renderer/core/css/css_selector.h
index 9d01bb76..90c67d8 100644
--- a/third_party/blink/renderer/core/css/css_selector.h
+++ b/third_party/blink/renderer/core/css/css_selector.h
@@ -359,8 +359,12 @@
     return has_rare_data_ ? data_.rare_data_->part_names_.get() : nullptr;
   }
   bool ContainsPseudoInsideHasPseudoClass() const {
-    return has_rare_data_ ? data_.rare_data_->bits_
-                                .contains_pseudo_inside_has_pseudo_class_
+    return has_rare_data_ ? data_.rare_data_->bits_.has_.contains_pseudo_
+                          : false;
+  }
+  bool ContainsComplexLogicalCombinationsInsideHasPseudoClass() const {
+    return has_rare_data_ ? data_.rare_data_->bits_.has_
+                                .contains_complex_logical_combinations_
                           : false;
   }
 
@@ -376,6 +380,7 @@
   void SetSelectorList(std::unique_ptr<CSSSelectorList>);
   void SetPartNames(std::unique_ptr<Vector<AtomicString>>);
   void SetContainsPseudoInsideHasPseudoClass();
+  void SetContainsComplexLogicalCombinationsInsideHasPseudoClass();
 
   void SetNth(int a, int b);
   bool MatchNth(unsigned count) const;
@@ -490,8 +495,14 @@
       AttributeMatchType
           attribute_match_;  // used for attribute selector (with value)
 
-      // Used for :has() with pseudos in its argument. e.g. :has(:hover)
-      bool contains_pseudo_inside_has_pseudo_class_;
+      struct {
+        // Used for :has() with pseudos in its argument. e.g. :has(:hover)
+        bool contains_pseudo_;
+
+        // Used for :has() with logical combinations (:is(), :where(), :not())
+        // containing complex selector in its argument. e.g. :has(:is(.a .b))
+        bool contains_complex_logical_combinations_;
+      } has_;
     } bits_;
     QualifiedName attribute_;  // used for attribute selector
     AtomicString argument_;    // Used for :contains, :lang, :nth-*
diff --git a/third_party/blink/renderer/core/css/parser/css_parser_selector.cc b/third_party/blink/renderer/core/css/parser/css_parser_selector.cc
index 737a646..451a92f 100644
--- a/third_party/blink/renderer/core/css/parser/css_parser_selector.cc
+++ b/third_party/blink/renderer/core/css/parser/css_parser_selector.cc
@@ -62,6 +62,11 @@
   selector_->SetContainsPseudoInsideHasPseudoClass();
 }
 
+void CSSParserSelector::
+    SetContainsComplexLogicalCombinationsInsideHasPseudoClass() {
+  selector_->SetContainsComplexLogicalCombinationsInsideHasPseudoClass();
+}
+
 void CSSParserSelector::AppendTagHistory(
     CSSSelector::RelationType relation,
     std::unique_ptr<CSSParserSelector> selector) {
diff --git a/third_party/blink/renderer/core/css/parser/css_parser_selector.h b/third_party/blink/renderer/core/css/parser/css_parser_selector.h
index 2e244c65..00a5929a 100644
--- a/third_party/blink/renderer/core/css/parser/css_parser_selector.h
+++ b/third_party/blink/renderer/core/css/parser/css_parser_selector.h
@@ -80,6 +80,7 @@
   void SetSelectorList(std::unique_ptr<CSSSelectorList>);
   void SetAtomics(std::unique_ptr<CSSSelectorList>);
   void SetContainsPseudoInsideHasPseudoClass();
+  void SetContainsComplexLogicalCombinationsInsideHasPseudoClass();
 
   bool IsHostPseudoSelector() const;
 
diff --git a/third_party/blink/renderer/core/css/parser/css_selector_parser.cc b/third_party/blink/renderer/core/css/parser/css_selector_parser.cc
index fe145e2..a2b52140 100644
--- a/third_party/blink/renderer/core/css/parser/css_selector_parser.cc
+++ b/third_party/blink/renderer/core/css/parser/css_selector_parser.cc
@@ -342,6 +342,10 @@
     previous_compound_flags |= ExtractCompoundFlags(*simple, context_->Mode());
 
   if (CSSSelector::RelationType combinator = ConsumeCombinator(range)) {
+    if (is_inside_has_argument_ &&
+        is_inside_logical_combination_in_has_argument_) {
+      found_complex_logical_combinations_in_has_argument_ = true;
+    }
     return ConsumePartialComplexSelector(range, combinator, std::move(selector),
                                          previous_compound_flags);
   }
@@ -864,6 +868,9 @@
     case CSSSelector::kPseudoIs: {
       DisallowPseudoElementsScope scope(this);
       base::AutoReset<bool> resist_namespace(&resist_default_namespace_, true);
+      base::AutoReset<bool> is_inside_logical_combination_in_has_argument(
+          &is_inside_logical_combination_in_has_argument_,
+          is_inside_has_argument_);
 
       std::unique_ptr<CSSSelectorList> selector_list =
           std::make_unique<CSSSelectorList>();
@@ -876,6 +883,9 @@
     case CSSSelector::kPseudoWhere: {
       DisallowPseudoElementsScope scope(this);
       base::AutoReset<bool> resist_namespace(&resist_default_namespace_, true);
+      base::AutoReset<bool> is_inside_logical_combination_in_has_argument(
+          &is_inside_logical_combination_in_has_argument_,
+          is_inside_has_argument_);
 
       std::unique_ptr<CSSSelectorList> selector_list =
           std::make_unique<CSSSelectorList>();
@@ -926,6 +936,8 @@
                                                    true);
       base::AutoReset<bool> found_pseudo_in_has_argument(
           &found_pseudo_in_has_argument_, false);
+      base::AutoReset<bool> found_complex_logical_combinations_in_has_argument(
+          &found_complex_logical_combinations_in_has_argument_, false);
 
       std::unique_ptr<CSSSelectorList> selector_list =
           std::make_unique<CSSSelectorList>();
@@ -936,11 +948,16 @@
       selector->SetSelectorList(std::move(selector_list));
       if (found_pseudo_in_has_argument_)
         selector->SetContainsPseudoInsideHasPseudoClass();
+      if (found_complex_logical_combinations_in_has_argument_)
+        selector->SetContainsComplexLogicalCombinationsInsideHasPseudoClass();
       return selector;
     }
     case CSSSelector::kPseudoNot: {
       DisallowPseudoElementsScope scope(this);
       base::AutoReset<bool> resist_namespace(&resist_default_namespace_, true);
+      base::AutoReset<bool> is_inside_logical_combination_in_has_argument(
+          &is_inside_logical_combination_in_has_argument_,
+          is_inside_has_argument_);
 
       std::unique_ptr<CSSSelectorList> selector_list =
           std::make_unique<CSSSelectorList>();
diff --git a/third_party/blink/renderer/core/css/parser/css_selector_parser.h b/third_party/blink/renderer/core/css/parser/css_selector_parser.h
index e1ab69fe..f6b6071 100644
--- a/third_party/blink/renderer/core/css/parser/css_selector_parser.h
+++ b/third_party/blink/renderer/core/css/parser/css_selector_parser.h
@@ -164,11 +164,17 @@
   // the default namespace is '*' while this flag is true.
   bool ignore_default_namespace_ = false;
 
-  // The 'found_pseudo_in_has_argument flag is true when we found any pseudo in
-  // ':has()' argument while parsing.
+  // The 'found_pseudo_in_has_argument_' flag is true when we found any pseudo
+  // in ':has()' argument while parsing.
   bool found_pseudo_in_has_argument_ = false;
   bool is_inside_has_argument_ = false;
 
+  // The 'found_complex_logical_combinations_in_has_argument_' flag is true when
+  // we found any logical combinations (:is(), :where(), :not()) containing
+  // complex selector in ':has()' argument while parsing.
+  bool found_complex_logical_combinations_in_has_argument_ = false;
+  bool is_inside_logical_combination_in_has_argument_ = false;
+
   class DisallowPseudoElementsScope {
     STACK_ALLOCATED();
 
diff --git a/third_party/blink/renderer/core/css/selector_checker.cc b/third_party/blink/renderer/core/css/selector_checker.cc
index 1e5ad9a..6d6b7f2 100644
--- a/third_party/blink/renderer/core/css/selector_checker.cc
+++ b/third_party/blink/renderer/core/css/selector_checker.cc
@@ -1521,6 +1521,9 @@
 
         if (selector.ContainsPseudoInsideHasPseudoClass())
           element.SetAffectedByPseudoInHas();
+
+        if (selector.ContainsComplexLogicalCombinationsInsideHasPseudoClass())
+          element.SetAffectedByLogicalCombinationsInHas();
       }
       return CheckPseudoHas(context, result);
     case CSSSelector::kPseudoRelativeLeftmost:
diff --git a/third_party/blink/renderer/core/css/style_engine.cc b/third_party/blink/renderer/core/css/style_engine.cc
index 7e37c93..418ccdf 100644
--- a/third_party/blink/renderer/core/css/style_engine.cc
+++ b/third_party/blink/renderer/core/css/style_engine.cc
@@ -984,7 +984,8 @@
 
 bool PossiblyAffectingHasState(Element& element) {
   return element.AncestorsOrAncestorSiblingsAffectedByHas() ||
-         element.GetSiblingsAffectedByHasFlags();
+         element.GetSiblingsAffectedByHasFlags() ||
+         element.AffectedByLogicalCombinationsInHas();
 }
 
 bool InsertionOrRemovalPossiblyAffectHasStateOfAncestorsOrAncestorSiblings(
@@ -1018,12 +1019,37 @@
 
 }  // namespace
 
-void StyleEngine::InvalidateAncestorsOrSiblingsAffectedByHasInternal(
+void StyleEngine::InvalidateElementAffectedByHas(Element& element,
+                                                 bool for_pseudo_change) {
+  if (for_pseudo_change && !element.AffectedByPseudoInHas())
+    return;
+
+  const ComputedStyle* style = element.GetComputedStyle();
+
+  if (style && style->AffectedBySubjectHas()) {
+    // TODO(blee@igalia.com) Need filtering for irrelevant elements.
+    // e.g. When we have '.a:has(.b) {}', '.c:has(.d) {}', mutation of class
+    // value 'd' can invalidate ancestor with class value 'a' because we
+    // don't have any filtering for this case.
+    element.SetNeedsStyleRecalc(
+        StyleChangeType::kLocalStyleChange,
+        StyleChangeReasonForTracing::Create(
+            blink::style_change_reason::kStyleInvalidator));
+  }
+
+  if (element.AffectedByNonSubjectHas()) {
+    InvalidationLists invalidation_lists;
+    GetRuleFeatureSet().CollectInvalidationSetsForPseudoClass(
+        invalidation_lists, element, CSSSelector::kPseudoHas);
+    pending_invalidations_.ScheduleInvalidationSetsForNode(invalidation_lists,
+                                                           element);
+  }
+}
+
+void StyleEngine::InvalidateAncestorsOrSiblingsAffectedByHas(
     Element* parent,
     Element* previous_sibling,
     bool for_pseudo_change) {
-  const RuleFeatureSet& features = GetRuleFeatureSet();
-
   bool traverse_ancestors = false;
   bool traverse_siblings = false;
   Element* element = previous_sibling ? previous_sibling : parent;
@@ -1034,28 +1060,7 @@
     traverse_ancestors |= element->AncestorsOrAncestorSiblingsAffectedByHas();
     traverse_siblings = element->GetSiblingsAffectedByHasFlags();
 
-    const ComputedStyle* style = element->GetComputedStyle();
-
-    if (!for_pseudo_change || element->AffectedByPseudoInHas()) {
-      if (style && style->AffectedBySubjectHas()) {
-        // TODO(blee@igalia.com) Need filtering for irrelevant elements.
-        // e.g. When we have '.a:has(.b) {}', '.c:has(.d) {}', mutation of class
-        // value 'd' can invalidate ancestor with class value 'a' because we
-        // don't have any filtering for this case.
-        element->SetNeedsStyleRecalc(
-            StyleChangeType::kLocalStyleChange,
-            StyleChangeReasonForTracing::Create(
-                blink::style_change_reason::kStyleInvalidator));
-      }
-
-      if (element->AffectedByNonSubjectHas()) {
-        InvalidationLists invalidation_lists;
-        features.CollectInvalidationSetsForPseudoClass(
-            invalidation_lists, *element, CSSSelector::kPseudoHas);
-        pending_invalidations_.ScheduleInvalidationSetsForNode(
-            invalidation_lists, *element);
-      }
-    }
+    InvalidateElementAffectedByHas(*element, for_pseudo_change);
 
     if (traverse_siblings) {
       previous_sibling = ElementTraversal::PreviousSibling(*element);
@@ -1087,8 +1092,8 @@
 void StyleEngine::InvalidateAncestorsOrSiblingsAffectedByHasForPseudoChange(
     Element* parent,
     Element* previous_sibling) {
-  InvalidateAncestorsOrSiblingsAffectedByHasInternal(
-      parent, previous_sibling, true /* for_pseudo_change */);
+  InvalidateAncestorsOrSiblingsAffectedByHas(parent, previous_sibling,
+                                             true /* for_pseudo_change */);
 }
 
 void StyleEngine::InvalidateAncestorsOrSiblingsAffectedByHas(
@@ -1105,8 +1110,16 @@
 void StyleEngine::InvalidateAncestorsOrSiblingsAffectedByHas(
     Element* parent,
     Element* previous_sibling) {
-  InvalidateAncestorsOrSiblingsAffectedByHasInternal(
-      parent, previous_sibling, false /* for_pseudo_change */);
+  InvalidateAncestorsOrSiblingsAffectedByHas(parent, previous_sibling,
+                                             false /* for_pseudo_change */);
+}
+
+void StyleEngine::InvalidateChangedElementAffectedByLogicalCombinationsInHas(
+    Element& changed_element,
+    bool for_pseudo_change) {
+  if (!changed_element.AffectedByLogicalCombinationsInHas())
+    return;
+  InvalidateElementAffectedByHas(changed_element, for_pseudo_change);
 }
 
 void StyleEngine::ClassChangedForElement(
@@ -1123,6 +1136,8 @@
     unsigned changed_size = changed_classes.size();
     for (unsigned i = 0; i < changed_size; ++i) {
       if (features.NeedsHasInvalidationForClass(changed_classes[i])) {
+        InvalidateChangedElementAffectedByLogicalCombinationsInHas(
+            element, /* for_pseudo_change */ false);
         InvalidateAncestorsOrSiblingsAffectedByHas(element);
         break;
       }
@@ -1216,8 +1231,11 @@
                                                            element);
   }
 
-  if (affecting_has_state)
+  if (affecting_has_state) {
+    InvalidateChangedElementAffectedByLogicalCombinationsInHas(
+        element, /* for_pseudo_change */ false);
     InvalidateAncestorsOrSiblingsAffectedByHas(element);
+  }
 }
 
 namespace {
@@ -1249,8 +1267,11 @@
   if (RuntimeEnabledFeatures::CSSPseudoHasEnabled() &&
       features.NeedsHasInvalidationForAttributeChange() &&
       PossiblyAffectingHasState(element)) {
-    if (features.NeedsHasInvalidationForAttribute(attribute_name))
+    if (features.NeedsHasInvalidationForAttribute(attribute_name)) {
+      InvalidateChangedElementAffectedByLogicalCombinationsInHas(
+          element, /* for_pseudo_change */ false);
       InvalidateAncestorsOrSiblingsAffectedByHas(element);
+    }
   }
 
   if (IsSubtreeAndSiblingsStyleDirty(element))
@@ -1283,6 +1304,8 @@
       PossiblyAffectingHasState(element)) {
     if ((!old_id.IsEmpty() && features.NeedsHasInvalidationForId(old_id)) ||
         (!new_id.IsEmpty() && features.NeedsHasInvalidationForId(new_id))) {
+      InvalidateChangedElementAffectedByLogicalCombinationsInHas(
+          element, /* for_pseudo_change */ false);
       InvalidateAncestorsOrSiblingsAffectedByHas(element);
     }
   }
@@ -1316,8 +1339,11 @@
       RuntimeEnabledFeatures::CSSPseudoHasEnabled() &&
       features.NeedsHasInvalidationForPseudoStateChange() &&
       PossiblyAffectingHasState(element)) {
-    if (features.NeedsHasInvalidationForPseudoClass(pseudo_type))
+    if (features.NeedsHasInvalidationForPseudoClass(pseudo_type)) {
+      InvalidateChangedElementAffectedByLogicalCombinationsInHas(
+          element, /* for_pseudo_change */ true);
       InvalidateAncestorsOrSiblingsAffectedByHasForPseudoChange(element);
+    }
   }
 
   if (!invalidate_descendants_or_siblings ||
diff --git a/third_party/blink/renderer/core/css/style_engine.h b/third_party/blink/renderer/core/css/style_engine.h
index f25bf55..9272fbc 100644
--- a/third_party/blink/renderer/core/css/style_engine.h
+++ b/third_party/blink/renderer/core/css/style_engine.h
@@ -722,10 +722,10 @@
   void RebuildFieldSetContainer(HTMLFieldSetElement& fieldset);
 
   // Invalidate ancestors or siblings affected by :has() state change
-  void InvalidateAncestorsOrSiblingsAffectedByHasInternal(
-      Element* parent,
-      Element* previous_sibling,
-      bool for_pseudo_change);
+  inline void InvalidateElementAffectedByHas(Element&, bool for_pseudo_change);
+  void InvalidateAncestorsOrSiblingsAffectedByHas(Element* parent,
+                                                  Element* previous_sibling,
+                                                  bool for_pseudo_change);
   inline void InvalidateAncestorsOrSiblingsAffectedByHas(
       Element& changed_element);
   void InvalidateAncestorsOrSiblingsAffectedByHas(Element* parent,
@@ -735,6 +735,10 @@
   void InvalidateAncestorsOrSiblingsAffectedByHasForPseudoChange(
       Element* parent,
       Element* previous_sibling);
+  // Invalidate changed element affected by logical combinations in :has()
+  inline void InvalidateChangedElementAffectedByLogicalCombinationsInHas(
+      Element& changed_element,
+      bool for_pseudo_change);
 
   Member<Document> document_;
 
diff --git a/third_party/blink/renderer/core/dom/element.cc b/third_party/blink/renderer/core/dom/element.cc
index e2d00bd..2ecc6ad 100644
--- a/third_party/blink/renderer/core/dom/element.cc
+++ b/third_party/blink/renderer/core/dom/element.cc
@@ -5566,6 +5566,16 @@
   EnsureElementRareData().SetAncestorsOrSiblingsAffectedByFocusVisibleInHas();
 }
 
+bool Element::AffectedByLogicalCombinationsInHas() const {
+  return HasRareData()
+             ? GetElementRareData()->AffectedByLogicalCombinationsInHas()
+             : false;
+}
+
+void Element::SetAffectedByLogicalCombinationsInHas() {
+  EnsureElementRareData().SetAffectedByLogicalCombinationsInHas();
+}
+
 bool Element::UpdateForceLegacyLayout(const ComputedStyle& new_style,
                                       const ComputedStyle* old_style) {
   // ::first-letter may cause structure discrepancies between DOM and layout
diff --git a/third_party/blink/renderer/core/dom/element.h b/third_party/blink/renderer/core/dom/element.h
index b614206..b5e9396 100644
--- a/third_party/blink/renderer/core/dom/element.h
+++ b/third_party/blink/renderer/core/dom/element.h
@@ -1111,6 +1111,8 @@
   void SetAncestorsOrSiblingsAffectedByFocusInHas();
   bool AncestorsOrSiblingsAffectedByFocusVisibleInHas() const;
   void SetAncestorsOrSiblingsAffectedByFocusVisibleInHas();
+  bool AffectedByLogicalCombinationsInHas() const;
+  void SetAffectedByLogicalCombinationsInHas();
 
   void SaveIntrinsicSize(ResizeObserverSize* size);
   const ResizeObserverSize* LastIntrinsicSize() const;
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 17ee1605..031d21b 100644
--- a/third_party/blink/renderer/core/dom/element_rare_data.h
+++ b/third_party/blink/renderer/core/dom/element_rare_data.h
@@ -226,6 +226,12 @@
     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;
+  }
 
   void Trace(blink::Visitor*) const;
 
@@ -481,6 +487,14 @@
   void SetAncestorsOrSiblingsAffectedByFocusVisibleInHas() {
     EnsureSuperRareData().SetAncestorsOrSiblingsAffectedByFocusVisibleInHas();
   }
+  bool AffectedByLogicalCombinationsInHas() const {
+    return super_rare_data_
+               ? super_rare_data_->AffectedByLogicalCombinationsInHas()
+               : false;
+  }
+  void SetAffectedByLogicalCombinationsInHas() {
+    EnsureSuperRareData().SetAffectedByLogicalCombinationsInHas();
+  }
 
   AccessibleNode* GetAccessibleNode() const {
     if (super_rare_data_)
diff --git a/third_party/blink/renderer/core/dom/has_invalidation_flags.h b/third_party/blink/renderer/core/dom/has_invalidation_flags.h
index bdc2262..58d8eba 100644
--- a/third_party/blink/renderer/core/dom/has_invalidation_flags.h
+++ b/third_party/blink/renderer/core/dom/has_invalidation_flags.h
@@ -189,6 +189,7 @@
   unsigned ancestors_or_siblings_affected_by_active_in_has : 1;
   unsigned ancestors_or_siblings_affected_by_focus_in_has : 1;
   unsigned ancestors_or_siblings_affected_by_focus_visible_in_has : 1;
+  unsigned affected_by_logical_combinations_in_has : 1;
 
   HasInvalidationFlags()
       : affected_by_non_subject_has(false),
diff --git a/third_party/blink/renderer/core/frame/attribution_aggregatable_trigger_data_fuzzer.cc b/third_party/blink/renderer/core/frame/attribution_aggregatable_trigger_data_fuzzer.cc
deleted file mode 100644
index cb294ba..0000000
--- a/third_party/blink/renderer/core/frame/attribution_aggregatable_trigger_data_fuzzer.cc
+++ /dev/null
@@ -1,35 +0,0 @@
-// 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 <stdlib.h>
-#include <iostream>
-#include <string>
-
-#include "testing/libfuzzer/proto/json.pb.h"
-#include "testing/libfuzzer/proto/json_proto_converter.h"
-#include "testing/libfuzzer/proto/lpm_interface.h"
-#include "third_party/blink/public/mojom/conversions/attribution_data_host.mojom-blink.h"
-#include "third_party/blink/renderer/core/frame/attribution_response_parsing.h"
-#include "third_party/blink/renderer/platform/testing/blink_fuzzer_test_support.h"
-#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
-#include "third_party/blink/renderer/platform/wtf/vector.h"
-
-namespace blink {
-
-DEFINE_PROTO_FUZZER(const json_proto::JsonValue& json_value) {
-  static BlinkFuzzerTestSupport test_support = BlinkFuzzerTestSupport();
-
-  json_proto::JsonProtoConverter converter;
-  std::string native_input = converter.Convert(json_value);
-
-  if (getenv("LPM_DUMP_NATIVE_INPUT"))
-    std::cout << native_input << std::endl;
-
-  const String input(native_input.c_str());
-  WTF::Vector<mojom::blink::AttributionAggregatableTriggerDataPtr> output;
-  attribution_response_parsing::ParseAttributionAggregatableTriggerData(input,
-                                                                        output);
-}
-
-}  // namespace blink
diff --git a/third_party/blink/renderer/core/frame/attribution_aggregatable_values_fuzzer.cc b/third_party/blink/renderer/core/frame/attribution_aggregatable_values_fuzzer.cc
deleted file mode 100644
index 72fe570..0000000
--- a/third_party/blink/renderer/core/frame/attribution_aggregatable_values_fuzzer.cc
+++ /dev/null
@@ -1,36 +0,0 @@
-// 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 <stdint.h>
-#include <stdlib.h>
-#include <iostream>
-#include <string>
-
-#include "testing/libfuzzer/proto/json.pb.h"
-#include "testing/libfuzzer/proto/json_proto_converter.h"
-#include "testing/libfuzzer/proto/lpm_interface.h"
-#include "third_party/blink/public/mojom/conversions/attribution_data_host.mojom-blink.h"
-#include "third_party/blink/renderer/core/frame/attribution_response_parsing.h"
-#include "third_party/blink/renderer/platform/testing/blink_fuzzer_test_support.h"
-#include "third_party/blink/renderer/platform/wtf/hash_map.h"
-#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
-
-namespace blink {
-
-DEFINE_PROTO_FUZZER(const json_proto::JsonValue& json_value) {
-  static BlinkFuzzerTestSupport test_support = BlinkFuzzerTestSupport();
-
-  json_proto::JsonProtoConverter converter;
-  std::string native_input = converter.Convert(json_value);
-
-  if (getenv("LPM_DUMP_NATIVE_INPUT"))
-    std::cout << native_input << std::endl;
-
-  const String input(native_input.c_str());
-  WTF::HashMap<String, uint32_t> output;
-  attribution_response_parsing::ParseAttributionAggregatableValues(input,
-                                                                   output);
-}
-
-}  // namespace blink
diff --git a/third_party/blink/renderer/core/frame/attribution_response_parsing.cc b/third_party/blink/renderer/core/frame/attribution_response_parsing.cc
index e391159..9750d79 100644
--- a/third_party/blink/renderer/core/frame/attribution_response_parsing.cc
+++ b/third_party/blink/renderer/core/frame/attribution_response_parsing.cc
@@ -76,13 +76,15 @@
   return true;
 }
 
+}  // namespace
+
 bool ParseAttributionFilterData(
-    JSONValue* value,
+    const JSONValue* value,
     mojom::blink::AttributionFilterData& filter_data) {
   if (!value)
     return true;
 
-  JSONObject* object = JSONObject::Cast(value);
+  const JSONObject* object = JSONObject::Cast(value);
   if (!object)
     return false;
 
@@ -105,14 +107,14 @@
   UMA_HISTOGRAM_COUNTS_100("Conversions.FiltersPerFilterData", num_filters);
 
   for (wtf_size_t i = 0; i < num_filters; ++i) {
-    JSONObject::Entry entry = object->at(i);
+    const JSONObject::Entry entry = object->at(i);
 
     if (entry.first.CharactersSizeInBytes() >
         kMaxBytesPerAttributionFilterString) {
       return false;
     }
 
-    JSONArray* array = JSONArray::Cast(entry.second);
+    const JSONArray* array = JSONArray::Cast(entry.second);
     if (!array)
       return false;
 
@@ -141,8 +143,6 @@
   return true;
 }
 
-}  // namespace
-
 bool ParseAggregationKeys(
     const JSONValue* json,
     WTF::HashMap<String, absl::uint128>& aggregation_keys) {
@@ -272,17 +272,14 @@
 }
 
 bool ParseEventTriggerData(
-    const String& json_string,
+    const JSONValue* json,
     WTF::Vector<mojom::blink::EventTriggerDataPtr>& event_trigger_data) {
-  // TODO(apaseltiner): Consider applying a max stack depth to this.
-  std::unique_ptr<JSONValue> json = ParseJSON(json_string);
-
-  // TODO(johnidel): Log a devtools issues if JSON parsing fails and on
-  // individual early exits below.
   if (!json)
-    return false;
+    return true;
 
-  JSONArray* array_value = JSONArray::Cast(json.get());
+  // TODO(apaseltiner): Log a devtools issues on individual early exits below.
+
+  const JSONArray* array_value = JSONArray::Cast(json);
   if (!array_value)
     return false;
 
@@ -351,24 +348,12 @@
   return true;
 }
 
-bool ParseFilters(const String& json_string,
-                  mojom::blink::AttributionFilterData& filter_data) {
-  // TODO(apaseltiner): Consider applying a max stack depth to this.
-  std::unique_ptr<JSONValue> json = ParseJSON(json_string);
-  if (!json)
-    return false;
-
-  return ParseAttributionFilterData(json.get(), filter_data);
-}
-
 bool ParseAttributionAggregatableTriggerData(
-    const String& json_string,
+    const JSONValue* json,
     WTF::Vector<mojom::blink::AttributionAggregatableTriggerDataPtr>&
         trigger_data) {
-  // TODO(apaseltiner): Consider applying a max stack depth to this.
-  std::unique_ptr<JSONValue> json = ParseJSON(json_string);
   if (!json)
-    return false;
+    return true;
 
   const int kExclusiveMaxHistogramValue = 101;
 
@@ -377,7 +362,7 @@
                 "Bump the version for histogram "
                 "Conversions.AggregatableTriggerDataLength");
 
-  const auto* array = JSONArray::Cast(json.get());
+  const auto* array = JSONArray::Cast(json);
   if (!array)
     return false;
 
@@ -444,14 +429,12 @@
 }
 
 bool ParseAttributionAggregatableValues(
-    const String& json_string,
+    const JSONValue* json,
     WTF::HashMap<String, uint32_t>& values) {
-  // TODO(apaseltiner): Consider applying a max stack depth to this.
-  std::unique_ptr<JSONValue> json = ParseJSON(json_string);
   if (!json)
-    return false;
+    return true;
 
-  const auto* object = JSONObject::Cast(json.get());
+  const auto* object = JSONObject::Cast(json);
   if (!object ||
       object->size() > kMaxAttributionAggregatableKeysPerSourceOrTrigger) {
     return false;
@@ -483,6 +466,52 @@
   return true;
 }
 
+bool ParseTriggerRegistrationHeader(
+    const String& json_string,
+    mojom::blink::AttributionTriggerData& trigger_data) {
+  std::unique_ptr<JSONValue> json = ParseJSON(json_string);
+  if (!json)
+    return false;
+
+  const JSONObject* object = JSONObject::Cast(json.get());
+  if (!object)
+    return false;
+
+  // Populate event triggers.
+  if (!ParseEventTriggerData(object->Get("event_trigger_data"),
+                             trigger_data.event_triggers)) {
+    return false;
+  }
+
+  trigger_data.filters = mojom::blink::AttributionFilterData::New();
+
+  if (!ParseAttributionFilterData(object->Get("filters"),
+                                  *trigger_data.filters)) {
+    return false;
+  }
+
+  trigger_data.aggregatable_trigger =
+      mojom::blink::AttributionAggregatableTrigger::New();
+
+  if (!ParseAttributionAggregatableTriggerData(
+          object->Get("aggregatable_trigger_data"),
+          trigger_data.aggregatable_trigger->trigger_data)) {
+    return false;
+  }
+
+  if (!ParseAttributionAggregatableValues(
+          object->Get("aggregatable_values"),
+          trigger_data.aggregatable_trigger->values)) {
+    return false;
+  }
+
+  String debug_key_string;
+  if (object->GetString("debug_key", &debug_key_string))
+    trigger_data.debug_key = ParseDebugKey(debug_key_string);
+
+  return true;
+}
+
 mojom::blink::AttributionTriggerDataPtr ParseAttributionTriggerData(
     const ResourceResponse& response) {
   auto trigger_data = mojom::blink::AttributionTriggerData::New();
@@ -494,48 +523,10 @@
     return nullptr;
   trigger_data->reporting_origin = std::move(reporting_origin);
 
-  // Populate event triggers.
-  const AtomicString& event_triggers_json = response.HttpHeaderField(
-      http_names::kAttributionReportingRegisterEventTrigger);
-  if (!event_triggers_json.IsNull() &&
-      !attribution_response_parsing::ParseEventTriggerData(
-          event_triggers_json, trigger_data->event_triggers)) {
+  const AtomicString& trigger_json = response.HttpHeaderField(
+      http_names::kAttributionReportingRegisterTrigger);
+  if (!ParseTriggerRegistrationHeader(trigger_json, *trigger_data))
     return nullptr;
-  }
-
-  trigger_data->filters = mojom::blink::AttributionFilterData::New();
-
-  const AtomicString& filter_json =
-      response.HttpHeaderField(http_names::kAttributionReportingFilters);
-  if (!filter_json.IsNull() && !attribution_response_parsing::ParseFilters(
-                                   filter_json, *trigger_data->filters)) {
-    return nullptr;
-  }
-
-  trigger_data->aggregatable_trigger =
-      mojom::blink::AttributionAggregatableTrigger::New();
-
-  const AtomicString& aggregatable_trigger_json = response.HttpHeaderField(
-      http_names::kAttributionReportingRegisterAggregatableTriggerData);
-  if (!aggregatable_trigger_json.IsNull() &&
-      !attribution_response_parsing::ParseAttributionAggregatableTriggerData(
-          aggregatable_trigger_json,
-          trigger_data->aggregatable_trigger->trigger_data)) {
-    return nullptr;
-  }
-
-  const AtomicString& aggregatable_values_json = response.HttpHeaderField(
-      http_names::kAttributionReportingRegisterAggregatableValues);
-  if (!aggregatable_values_json.IsNull() &&
-      !attribution_response_parsing::ParseAttributionAggregatableValues(
-          aggregatable_values_json,
-          trigger_data->aggregatable_trigger->values)) {
-    return nullptr;
-  }
-
-  trigger_data->debug_key =
-      attribution_response_parsing::ParseDebugKey(response.HttpHeaderField(
-          http_names::kAttributionReportingTriggerDebugKey));
 
   return trigger_data;
 }
diff --git a/third_party/blink/renderer/core/frame/attribution_response_parsing.h b/third_party/blink/renderer/core/frame/attribution_response_parsing.h
index 683c99ab1..7a39c08 100644
--- a/third_party/blink/renderer/core/frame/attribution_response_parsing.h
+++ b/third_party/blink/renderer/core/frame/attribution_response_parsing.h
@@ -25,8 +25,8 @@
 namespace attribution_response_parsing {
 
 // Helper functions to parse response headers. See details in the explainer.
-// https://github.com/WICG/conversion-measurement-api/blob/main/EVENT.md
-// https://github.com/WICG/conversion-measurement-api/blob/main/AGGREGATE.md
+// https://github.com/WICG/attribution-reporting-api/blob/main/EVENT.md
+// https://github.com/WICG/attribution-reporting-api/blob/main/AGGREGATE.md
 
 // Example JSON schema:
 // [{
@@ -52,6 +52,10 @@
     const String& json_string,
     mojom::blink::AttributionSourceData& source_data);
 
+CORE_EXPORT bool ParseTriggerRegistrationHeader(
+    const String& json_string,
+    mojom::blink::AttributionTriggerData& trigger_data);
+
 // Parses event trigger data header of the form:
 //
 // [{
@@ -62,7 +66,7 @@
 //
 // Returns whether parsing was successful.
 CORE_EXPORT bool ParseEventTriggerData(
-    const String& json_string,
+    const JSONValue* json,
     WTF::Vector<mojom::blink::EventTriggerDataPtr>& event_trigger_data);
 
 // Parses filter header of the form:
@@ -73,8 +77,9 @@
 // }
 //
 // Returns whether parsing was successful.
-CORE_EXPORT bool ParseFilters(const String& json_string,
-                              mojom::blink::AttributionFilterData& filter_data);
+CORE_EXPORT bool ParseAttributionFilterData(
+    const JSONValue* json,
+    mojom::blink::AttributionFilterData& filter_data);
 
 // Example JSON schema:
 // [{
@@ -88,7 +93,7 @@
 //
 // Returns whether parsing was successful.
 CORE_EXPORT bool ParseAttributionAggregatableTriggerData(
-    const String& json_string,
+    const JSONValue* json,
     WTF::Vector<mojom::blink::AttributionAggregatableTriggerDataPtr>&
         trigger_data);
 
@@ -100,7 +105,7 @@
 //
 // Returns whether parsing was successful.
 CORE_EXPORT bool ParseAttributionAggregatableValues(
-    const String& json_string,
+    const JSONValue* json,
     WTF::HashMap<String, uint32_t>& values);
 
 // Returns the attribution trigger data parsed from the response. Returns
diff --git a/third_party/blink/renderer/core/frame/attribution_response_parsing_test.cc b/third_party/blink/renderer/core/frame/attribution_response_parsing_test.cc
index 0bd4d77..d8b86700 100644
--- a/third_party/blink/renderer/core/frame/attribution_response_parsing_test.cc
+++ b/third_party/blink/renderer/core/frame/attribution_response_parsing_test.cc
@@ -184,22 +184,24 @@
 TEST(AttributionResponseParsingTest, ParseAttributionAggregatableTrigger) {
   const struct {
     String description;
-    String header;
+    std::unique_ptr<JSONValue> json;
     mojom::blink::AttributionAggregatableTriggerPtr expected;
   } kTestCases[] = {
-      {"Empty header", "", nullptr},
-      {"Invalid JSON", "{", nullptr},
-      {"Not an array", "{}", nullptr},
-      {"Element not a dictionary", "[123]", nullptr},
-      {"Missing source_keys field", R"([{"key_piece":"0x400"}])", nullptr},
-      {"source_keys not an array",
-       R"([{"key_piece":"0x400","source_keys":"key"}])", nullptr},
-      {"source_keys element not a string",
-       R"([{"key_piece":"0x400","source_keys":[123]}])"},
-      {"Missing key_piece field", R"([{"source_keys":["key"]}])", nullptr},
-      {"Invalid key", R"([{"key_piece":"0xG00","source_keys":["key"]}])",
+      {"Null", nullptr, AggregatableTriggerBuilder().Build()},
+      {"Not an array", ParseJSON(R"({})"), nullptr},
+      {"Element not a dictionary", ParseJSON(R"([123])"), nullptr},
+      {"Missing source_keys field", ParseJSON(R"([{"key_piece":"0x400"}])"),
        nullptr},
-      {"Valid trigger", R"([{"key_piece":"0x400","source_keys":["key"]}])",
+      {"source_keys not an array",
+       ParseJSON(R"([{"key_piece":"0x400","source_keys":"key"}])"), nullptr},
+      {"source_keys element not a string",
+       ParseJSON(R"([{"key_piece":"0x400","source_keys":[123]}])")},
+      {"Missing key_piece field", ParseJSON(R"([{"source_keys":["key"]}])"),
+       nullptr},
+      {"Invalid key",
+       ParseJSON(R"([{"key_piece":"0xG00","source_keys":["key"]}])"), nullptr},
+      {"Valid trigger",
+       ParseJSON(R"([{"key_piece":"0x400","source_keys":["key"]}])"),
        AggregatableTriggerBuilder()
            .AddTriggerData(
                mojom::blink::AttributionAggregatableTriggerData::New(
@@ -208,13 +210,12 @@
                    /*filters=*/mojom::blink::AttributionFilterData::New(),
                    /*not_filters=*/mojom::blink::AttributionFilterData::New()))
            .Build()},
-      {"Valid trigger with filters",
-       R"([{
+      {"Valid trigger with filters", ParseJSON(R"([{
          "key_piece": "0x400",
          "source_keys": ["key"],
          "filters": {"filter": ["value1"]},
          "not_filters": {"filter": ["value2"]}
-       }])",
+       }])"),
        AggregatableTriggerBuilder()
            .AddTriggerData(
                mojom::blink::AttributionAggregatableTriggerData::New(
@@ -230,8 +231,8 @@
                        .Build()))
            .Build()},
       {"Two valid trigger data",
-       R"([{"key_piece":"0x400","source_keys":["key1"]},
-           {"key_piece":"0xA80","source_keys":["key2"]}])",
+       ParseJSON(R"([{"key_piece":"0x400","source_keys":["key1"]},
+           {"key_piece":"0xA80","source_keys":["key2"]}])"),
        AggregatableTriggerBuilder()
            .AddTriggerData(
                mojom::blink::AttributionAggregatableTriggerData::New(
@@ -251,8 +252,8 @@
   for (const auto& test_case : kTestCases) {
     WTF::Vector<mojom::blink::AttributionAggregatableTriggerDataPtr>
         trigger_data;
-    bool valid =
-        ParseAttributionAggregatableTriggerData(test_case.header, trigger_data);
+    bool valid = ParseAttributionAggregatableTriggerData(test_case.json.get(),
+                                                         trigger_data);
     EXPECT_EQ(!test_case.expected.is_null(), valid) << test_case.description;
     if (test_case.expected) {
       EXPECT_EQ(test_case.expected->trigger_data, trigger_data)
@@ -270,10 +271,10 @@
     wtf_size_t key_count;
     wtf_size_t key_size;
 
-    String GetHeader() const {
+    std::unique_ptr<JSONArray> GetHeader() const {
       String key = GetKey();
 
-      JSONArray array;
+      auto array = std::make_unique<JSONArray>();
       for (wtf_size_t i = 0u; i < trigger_data_count; ++i) {
         auto object = std::make_unique<JSONObject>();
         object->SetString("key_piece", "0x1");
@@ -284,10 +285,10 @@
         }
         object->SetArray("source_keys", std::move(keys));
 
-        array.PushObject(std::move(object));
+        array->PushObject(std::move(object));
       }
 
-      return array.ToJSONString();
+      return array;
     }
 
     WTF::Vector<mojom::blink::AttributionAggregatableTriggerDataPtr>
@@ -327,10 +328,11 @@
   };
 
   for (const auto& test_case : kTestCases) {
+    std::unique_ptr<JSONArray> json = test_case.GetHeader();
     WTF::Vector<mojom::blink::AttributionAggregatableTriggerDataPtr>
         trigger_data;
-    bool valid = ParseAttributionAggregatableTriggerData(test_case.GetHeader(),
-                                                         trigger_data);
+    bool valid =
+        ParseAttributionAggregatableTriggerData(json.get(), trigger_data);
 
     EXPECT_EQ(test_case.valid, valid) << test_case.description;
     if (test_case.valid) {
@@ -343,26 +345,29 @@
 TEST(AttributionResponseParsingTest, ParseAttributionAggregatableValues) {
   const struct {
     String description;
-    String header;
+    std::unique_ptr<JSONValue> json;
     bool valid;
     WTF::HashMap<String, uint32_t> values;
   } kTestCases[] = {
-      {"Empty header", "", false, {}},
-      {"Invalid JSON", "{", false, {}},
-      {"Value not an integer", R"({"key":"1"})", false, {}},
-      {"Invalid value", R"({"key":-1})", false, {}},
-      {"Valid value", R"({"key":123})", true, {{"key", 123}}},
+      {"Null", nullptr, true, {}},
+      {"Value not an integer", ParseJSON(R"({"key":"1"})"), false, {}},
+      {"Invalid value", ParseJSON(R"({"key":-1})"), false, {}},
+      {"Valid value", ParseJSON(R"({"key":123})"), true, {{"key", 123}}},
       {"Two valid values",
-       R"({"key1":123,"key2":456})",
+       ParseJSON(R"({"key1":123,"key2":456})"),
        true,
        {{"key1", 123}, {"key2", 456}}},
-      {"Max valid value", R"({"key":65536})", true, {{"key", 65536}}},
-      {"Value out of range", R"({"key":65537})", false, {}},
+      {"Max valid value",
+       ParseJSON(R"({"key":65536})"),
+       true,
+       {{"key", 65536}}},
+      {"Value out of range", ParseJSON(R"({"key":65537})"), false, {}},
   };
 
   for (const auto& test_case : kTestCases) {
     WTF::HashMap<String, uint32_t> values;
-    bool valid = ParseAttributionAggregatableValues(test_case.header, values);
+    bool valid =
+        ParseAttributionAggregatableValues(test_case.json.get(), values);
     EXPECT_EQ(test_case.valid, valid) << test_case.description;
     if (test_case.valid)
       EXPECT_EQ(test_case.values, values) << test_case.description;
@@ -377,12 +382,12 @@
     wtf_size_t key_count;
     wtf_size_t key_size;
 
-    String GetHeader() const {
-      JSONObject object;
+    std::unique_ptr<JSONValue> GetHeader() const {
+      auto object = std::make_unique<JSONObject>();
       for (wtf_size_t i = 0u; i < key_count; ++i) {
-        object.SetInteger(GetKey(i), i + 1);
+        object->SetInteger(GetKey(i), i + 1);
       }
-      return object.ToJSONString();
+      return object;
     }
 
     WTF::HashMap<String, uint32_t> GetValues() const {
@@ -419,9 +424,9 @@
   };
 
   for (const auto& test_case : kTestCases) {
+    std::unique_ptr<JSONValue> json = test_case.GetHeader();
     WTF::HashMap<String, uint32_t> values;
-    bool valid =
-        ParseAttributionAggregatableValues(test_case.GetHeader(), values);
+    bool valid = ParseAttributionAggregatableValues(json.get(), values);
 
     EXPECT_EQ(test_case.valid, valid) << test_case.description;
     if (test_case.valid)
@@ -429,19 +434,19 @@
   }
 }
 
-TEST(AttributionResponseParsingTest, ParseFilters) {
+TEST(AttributionResponseParsingTest, ParseFilterData) {
   const auto make_filter_data_with_keys = [](wtf_size_t n) {
-    JSONObject root;
+    auto root = std::make_unique<JSONObject>();
     for (wtf_size_t i = 0; i < n; ++i) {
-      root.SetArray(String::Number(i), std::make_unique<JSONArray>());
+      root->SetArray(String::Number(i), std::make_unique<JSONArray>());
     }
-    return root.ToJSONString();
+    return root;
   };
 
   const auto make_filter_data_with_key_length = [](wtf_size_t n) {
-    JSONObject root;
-    root.SetArray(String(std::string(n, 'a')), std::make_unique<JSONArray>());
-    return root.ToJSONString();
+    auto root = std::make_unique<JSONObject>();
+    root->SetArray(String(std::string(n, 'a')), std::make_unique<JSONArray>());
+    return root;
   };
 
   const auto make_filter_data_with_values = [](wtf_size_t n) {
@@ -450,64 +455,64 @@
       array->PushString("x");
     }
 
-    JSONObject root;
-    root.SetArray("a", std::move(array));
-    return root.ToJSONString();
+    auto root = std::make_unique<JSONObject>();
+    root->SetArray("a", std::move(array));
+    return root;
   };
 
   const auto make_filter_data_with_value_length = [](wtf_size_t n) {
     auto array = std::make_unique<JSONArray>();
     array->PushString(String(std::string(n, 'a')));
 
-    JSONObject root;
-    root.SetArray("a", std::move(array));
-    return root.ToJSONString();
+    auto root = std::make_unique<JSONObject>();
+    root->SetArray("a", std::move(array));
+    return root;
   };
 
   const struct {
     String description;
-    String json;
+    std::unique_ptr<JSONValue> json;
     mojom::blink::AttributionFilterDataPtr expected;
   } kTestCases[] = {
       {
+          "Null",
+          nullptr,
+          AttributionFilterDataBuilder().Build(),
+      },
+      {
           "empty",
-          R"json({})json",
+          ParseJSON(R"json({})json"),
           AttributionFilterDataBuilder().Build(),
       },
       {
           "source_type",
-          R"json({"source_type": []})json",
+          ParseJSON(R"json({"source_type": []})json"),
           AttributionFilterDataBuilder().AddFilter("source_type", {}).Build(),
       },
       {
           "multiple",
-          R"json({
+          ParseJSON(R"json({
             "a": ["b"],
             "c": ["e", "d"]
-          })json",
+          })json"),
           AttributionFilterDataBuilder()
               .AddFilter("a", {"b"})
               .AddFilter("c", {"e", "d"})
               .Build(),
       },
       {
-          "invalid_json",
-          "!",
-          nullptr,
-      },
-      {
           "not_dictionary",
-          R"json(true)json",
+          ParseJSON(R"json(true)json"),
           nullptr,
       },
       {
           "value_not_array",
-          R"json({"a": true})json",
+          ParseJSON(R"json({"a": true})json"),
           nullptr,
       },
       {
           "array_element_not_string",
-          R"json({"a": [true]})json",
+          ParseJSON(R"json({"a": [true]})json"),
           nullptr,
       },
       {
@@ -535,7 +540,7 @@
   for (const auto& test_case : kTestCases) {
     mojom::blink::AttributionFilterData filter_data;
 
-    bool valid = ParseFilters(test_case.json, filter_data);
+    bool valid = ParseAttributionFilterData(test_case.json.get(), filter_data);
     EXPECT_EQ(valid, !test_case.expected.is_null()) << test_case.description;
 
     if (test_case.expected) {
@@ -544,25 +549,27 @@
   }
 
   {
+    std::unique_ptr<JSONValue> json = make_filter_data_with_keys(50);
     mojom::blink::AttributionFilterData filter_data;
-    EXPECT_TRUE(ParseFilters(make_filter_data_with_keys(50), filter_data));
+    EXPECT_TRUE(ParseAttributionFilterData(json.get(), filter_data));
   }
 
   {
+    std::unique_ptr<JSONValue> json = make_filter_data_with_key_length(25);
     mojom::blink::AttributionFilterData filter_data;
-    EXPECT_TRUE(
-        ParseFilters(make_filter_data_with_key_length(25), filter_data));
+    EXPECT_TRUE(ParseAttributionFilterData(json.get(), filter_data));
   }
 
   {
+    std::unique_ptr<JSONValue> json = make_filter_data_with_values(50);
     mojom::blink::AttributionFilterData filter_data;
-    EXPECT_TRUE(ParseFilters(make_filter_data_with_values(50), filter_data));
+    EXPECT_TRUE(ParseAttributionFilterData(json.get(), filter_data));
   }
 
   {
+    std::unique_ptr<JSONValue> json = make_filter_data_with_value_length(25);
     mojom::blink::AttributionFilterData filter_data;
-    EXPECT_TRUE(
-        ParseFilters(make_filter_data_with_value_length(25), filter_data));
+    EXPECT_TRUE(ParseAttributionFilterData(json.get(), filter_data));
   }
 }
 
@@ -891,55 +898,55 @@
 TEST(AttributionResponseParsingTest, ParseEventTriggerData) {
   const struct {
     String description;
-    String json;
+    std::unique_ptr<JSONValue> json;
     bool valid;
     Vector<mojom::blink::EventTriggerDataPtr> expected;
   } kTestCases[] = {
       {
-          "invalid_json",
-          "!",
-          false,
+          "Null",
+          nullptr,
+          true,
           {},
       },
       {
           "root_not_array",
-          R"json({})json",
+          ParseJSON(R"json({})json"),
           false,
           {},
       },
       {
           "empty",
-          R"json([])json",
+          ParseJSON(R"json([])json"),
           true,
           {},
       },
       {
           "too_many_values",
-          R"json([{},{},{},{},{},{},{},{},{},{},{}])json",
+          ParseJSON(R"json([{},{},{},{},{},{},{},{},{},{},{}])json"),
           false,
           {},
       },
       {
           "value_not_object",
-          R"json([123])json",
+          ParseJSON(R"json([123])json"),
           false,
           {},
       },
       {
           "missing_trigger_data",
-          R"json([{}])json",
+          ParseJSON(R"json([{}])json"),
           false,
           {},
       },
       {
           "trigger_data_not_string",
-          R"json([{"trigger_data": 1}])json",
+          ParseJSON(R"json([{"trigger_data": 1}])json"),
           false,
           {},
       },
       {
           "invalid_trigger_data",
-          R"json([{"trigger_data": "-5"}])json",
+          ParseJSON(R"json([{"trigger_data": "-5"}])json"),
           true,
           VectorBuilder<mojom::blink::EventTriggerDataPtr>()
               .Add(mojom::blink::EventTriggerData::New(
@@ -952,7 +959,7 @@
       },
       {
           "valid_trigger_data",
-          R"json([{"trigger_data": "5"}])json",
+          ParseJSON(R"json([{"trigger_data": "5"}])json"),
           true,
           VectorBuilder<mojom::blink::EventTriggerDataPtr>()
               .Add(mojom::blink::EventTriggerData::New(
@@ -965,11 +972,11 @@
       },
       {
           "multiple",
-          R"json([
+          ParseJSON(R"json([
             {"trigger_data": "5"},
             {"trigger_data": "3"},
             {"trigger_data": "4"}
-          ])json",
+          ])json"),
           true,
           VectorBuilder<mojom::blink::EventTriggerDataPtr>()
               .Add(mojom::blink::EventTriggerData::New(
@@ -994,10 +1001,10 @@
       },
       {
           "valid_priority",
-          R"json([{
+          ParseJSON(R"json([{
             "trigger_data": "5",
             "priority": "3"
-          }])json",
+          }])json"),
           true,
           VectorBuilder<mojom::blink::EventTriggerDataPtr>()
               .Add(mojom::blink::EventTriggerData::New(
@@ -1010,10 +1017,10 @@
       },
       {
           "priority_not_string",
-          R"json([{
+          ParseJSON(R"json([{
             "trigger_data": "5",
             "priority": 3
-          }])json",
+          }])json"),
           true,
           VectorBuilder<mojom::blink::EventTriggerDataPtr>()
               .Add(mojom::blink::EventTriggerData::New(
@@ -1026,10 +1033,10 @@
       },
       {
           "invalid_priority",
-          R"json([{
+          ParseJSON(R"json([{
             "trigger_data": "5",
             "priority": "abc"
-          }])json",
+          }])json"),
           true,
           VectorBuilder<mojom::blink::EventTriggerDataPtr>()
               .Add(mojom::blink::EventTriggerData::New(
@@ -1042,10 +1049,10 @@
       },
       {
           "valid_dedup_key",
-          R"json([{
+          ParseJSON(R"json([{
             "trigger_data": "5",
             "deduplication_key": "3"
-          }])json",
+          }])json"),
           true,
           VectorBuilder<mojom::blink::EventTriggerDataPtr>()
               .Add(mojom::blink::EventTriggerData::New(
@@ -1059,10 +1066,10 @@
       },
       {
           "dedup_key_not_string",
-          R"json([{
+          ParseJSON(R"json([{
             "trigger_data": "5",
             "deduplication_key": 3
-          }])json",
+          }])json"),
           true,
           VectorBuilder<mojom::blink::EventTriggerDataPtr>()
               .Add(mojom::blink::EventTriggerData::New(
@@ -1075,10 +1082,10 @@
       },
       {
           "invalid_dedup_Key",
-          R"json([{
+          ParseJSON(R"json([{
             "trigger_data": "5",
             "deduplication_key": "abc"
-          }])json",
+          }])json"),
           true,
           VectorBuilder<mojom::blink::EventTriggerDataPtr>()
               .Add(mojom::blink::EventTriggerData::New(
@@ -1091,10 +1098,10 @@
       },
       {
           "valid_filters",
-          R"json([{
+          ParseJSON(R"json([{
             "trigger_data": "5",
             "filters": {"source_type": ["navigation"]}
-          }])json",
+          }])json"),
           true,
           VectorBuilder<mojom::blink::EventTriggerDataPtr>()
               .Add(mojom::blink::EventTriggerData::New(
@@ -1110,19 +1117,19 @@
       },
       {
           "invalid_filters",
-          R"json([{
+          ParseJSON(R"json([{
             "trigger_data": "5",
             "filters": 1
-          }])json",
+          }])json"),
           false,
           {},
       },
       {
           "valid_not_filters",
-          R"json([{
+          ParseJSON(R"json([{
             "trigger_data": "5",
             "not_filters": {"source_type": ["navigation"]}
-          }])json",
+          }])json"),
           true,
           VectorBuilder<mojom::blink::EventTriggerDataPtr>()
               .Add(mojom::blink::EventTriggerData::New(
@@ -1138,10 +1145,10 @@
       },
       {
           "invalid_not_filters",
-          R"json([{
+          ParseJSON(R"json([{
             "trigger_data": "5",
             "not_filters": 1
-          }])json",
+          }])json"),
           false,
           {},
       },
@@ -1149,7 +1156,7 @@
 
   for (const auto& test_case : kTestCases) {
     Vector<mojom::blink::EventTriggerDataPtr> actual;
-    bool valid = ParseEventTriggerData(test_case.json, actual);
+    bool valid = ParseEventTriggerData(test_case.json.get(), actual);
     EXPECT_EQ(valid, test_case.valid) << test_case.description;
     EXPECT_EQ(actual, test_case.expected) << test_case.description;
   }
@@ -1162,9 +1169,9 @@
       array->PushString("x");
     }
 
-    JSONObject object;
-    object.SetArray("a", std::move(array));
-    return object.ToJSONString();
+    auto object = std::make_unique<JSONObject>();
+    object->SetArray("a", std::move(array));
+    return object;
   };
 
   const struct {
@@ -1178,8 +1185,9 @@
 
   for (const auto& test_case : kTestCases) {
     base::HistogramTester histograms;
+    std::unique_ptr<JSONValue> json = make_filter_data(test_case.size);
     mojom::blink::AttributionFilterData filter_data;
-    ParseFilters(make_filter_data(test_case.size), filter_data);
+    ParseAttributionFilterData(json.get(), filter_data);
     histograms.ExpectUniqueSample("Conversions.ValuesPerFilter", test_case.size,
                                   test_case.expected);
   }
@@ -1187,11 +1195,11 @@
 
 TEST(AttributionResponseParsingTest, FiltersSizeHistogram) {
   const auto make_filter_data = [](wtf_size_t n) {
-    JSONObject object;
+    auto object = std::make_unique<JSONObject>();
     for (wtf_size_t i = 0; i < n; ++i) {
-      object.SetArray(String::Number(i), std::make_unique<JSONArray>());
+      object->SetArray(String::Number(i), std::make_unique<JSONArray>());
     }
-    return object.ToJSONString();
+    return object;
   };
 
   const struct {
@@ -1205,8 +1213,9 @@
 
   for (const auto& test_case : kTestCases) {
     base::HistogramTester histograms;
+    std::unique_ptr<JSONValue> json = make_filter_data(test_case.size);
     mojom::blink::AttributionFilterData filter_data;
-    ParseFilters(make_filter_data(test_case.size), filter_data);
+    ParseAttributionFilterData(json.get(), filter_data);
     histograms.ExpectUniqueSample("Conversions.FiltersPerFilterData",
                                   test_case.size, test_case.expected);
   }
@@ -1242,14 +1251,14 @@
 
 TEST(AttributionResponseParsingTest, AggregatableTriggerDataHistogram) {
   const auto make_aggregatable_trigger_with_trigger_data = [](wtf_size_t n) {
-    JSONArray array;
+    auto array = std::make_unique<JSONArray>();
     for (wtf_size_t i = 0; i < n; ++i) {
       auto object = std::make_unique<JSONObject>();
       object->SetString("key_piece", "0x1");
       object->SetArray("source_keys", std::make_unique<JSONArray>());
-      array.PushObject(std::move(object));
+      array->PushObject(std::move(object));
     }
-    return array.ToJSONString();
+    return array;
   };
 
   const struct {
@@ -1263,11 +1272,11 @@
 
   for (const auto& test_case : kTestCases) {
     base::HistogramTester histograms;
+    std::unique_ptr<JSONValue> json =
+        make_aggregatable_trigger_with_trigger_data(test_case.size);
     WTF::Vector<mojom::blink::AttributionAggregatableTriggerDataPtr>
         trigger_data;
-    ParseAttributionAggregatableTriggerData(
-        make_aggregatable_trigger_with_trigger_data(test_case.size),
-        trigger_data);
+    ParseAttributionAggregatableTriggerData(json.get(), trigger_data);
     histograms.ExpectUniqueSample("Conversions.AggregatableTriggerDataLength",
                                   test_case.size, test_case.expected);
   }
diff --git a/third_party/blink/renderer/core/frame/attribution_src/attribution_aggregatable_trigger_data_corpus/all_params.textproto b/third_party/blink/renderer/core/frame/attribution_src/attribution_aggregatable_trigger_data_corpus/all_params.textproto
deleted file mode 100644
index bab1d54..0000000
--- a/third_party/blink/renderer/core/frame/attribution_src/attribution_aggregatable_trigger_data_corpus/all_params.textproto
+++ /dev/null
@@ -1,81 +0,0 @@
-array_value {
-  value {
-    object_value {
-      field {
-        name: "key_piece"
-        value {
-          string_value {
-            value: "0x1"
-          }
-        }
-      }
-      field {
-        name: "source_keys"
-        value {
-          array_value {
-            value {
-              string_value {
-                value: "a"
-              }
-            }
-          }
-        }
-      }
-      field {
-        name: "filters"
-        value {
-          object_value {
-            field {
-              name: "a"
-              value {
-                array_value {
-                  value {
-                    string_value {
-                      value: "x"
-                    }
-                  }
-                }
-              }
-            }
-            field {
-              name: "b"
-              value {
-                array_value {
-                  value {
-                    string_value {
-                      value: "y"
-                    }
-                  }
-                  value {
-                    string_value {
-                      value: "z"
-                    }
-                  }
-                }
-              }
-            }
-          }
-        }
-      }
-      field {
-        name: "not_filters"
-        value {
-          object_value {
-            field {
-              name: "c"
-              value {
-                array_value {
-                  value {
-                    string_value {
-                      value: "d"
-                    }
-                  }
-                }
-              }
-            }
-          }
-        }
-      }
-    }
-  }
-}
diff --git a/third_party/blink/renderer/core/frame/attribution_src/attribution_aggregatable_values_corpus/all_params.textproto b/third_party/blink/renderer/core/frame/attribution_src/attribution_aggregatable_values_corpus/all_params.textproto
deleted file mode 100644
index 558cea0b..0000000
--- a/third_party/blink/renderer/core/frame/attribution_src/attribution_aggregatable_values_corpus/all_params.textproto
+++ /dev/null
@@ -1,18 +0,0 @@
-object_value {
-  field {
-    name: "a"
-    value {
-      number_value {
-        value: 123
-      }
-    }
-  }
-  field {
-    name: "b"
-    value {
-      number_value {
-        value: 456
-      }
-    }
-  }
-}
diff --git a/third_party/blink/renderer/core/frame/attribution_src/attribution_event_trigger_data_corpus/all_params.textproto b/third_party/blink/renderer/core/frame/attribution_src/attribution_event_trigger_data_corpus/all_params.textproto
deleted file mode 100644
index 6b184be4..0000000
--- a/third_party/blink/renderer/core/frame/attribution_src/attribution_event_trigger_data_corpus/all_params.textproto
+++ /dev/null
@@ -1,97 +0,0 @@
-array_value {
-  value {
-    object_value {
-      field {
-        name: "trigger_data"
-        value {
-          string_value {
-            value: "1"
-          }
-        }
-      }
-      field {
-        name: "priority"
-        value {
-          string_value {
-            value: "2"
-          }
-        }
-      }
-      field {
-        name: "deduplication_key"
-        value {
-          string_value {
-            value: "3"
-          }
-        }
-      }
-      field {
-        name: "filters"
-        value {
-          object_value {
-            field {
-              name: "a"
-              value {
-                array_value {
-                  value {
-                    string_value {
-                      value: "x"
-                    }
-                  }
-                }
-              }
-            }
-            field {
-              name: "b"
-              value {
-                array_value {
-                  value {
-                    string_value {
-                      value: "y"
-                    }
-                  }
-                  value {
-                    string_value {
-                      value: "z"
-                    }
-                  }
-                }
-              }
-            }
-          }
-        }
-      }
-      field {
-        name: "not_filters"
-        value {
-          object_value {
-            field {
-              name: "a"
-              value {
-                array_value {
-                  value {
-                    string_value {
-                      value: "x"
-                    }
-                  }
-                }
-              }
-            }
-            field {
-              name: "c"
-              value {
-                array_value {
-                  value {
-                    string_value {
-                      value: "d"
-                    }
-                  }
-                }
-              }
-            }
-          }
-        }
-      }
-    }
-  }
-}
diff --git a/third_party/blink/renderer/core/frame/attribution_src/attribution_trigger_registration_corpus/all_params.textproto b/third_party/blink/renderer/core/frame/attribution_src/attribution_trigger_registration_corpus/all_params.textproto
new file mode 100644
index 0000000..c109a52
--- /dev/null
+++ b/third_party/blink/renderer/core/frame/attribution_src/attribution_trigger_registration_corpus/all_params.textproto
@@ -0,0 +1,257 @@
+object_value {
+  field {
+    name: "event_trigger_data"
+    value {
+      array_value {
+        value {
+          object_value {
+            field {
+              name: "trigger_data"
+              value {
+                string_value {
+                  value: "1"
+                }
+              }
+            }
+            field {
+              name: "priority"
+              value {
+                string_value {
+                  value: "2"
+                }
+              }
+            }
+            field {
+              name: "deduplication_key"
+              value {
+                string_value {
+                  value: "3"
+                }
+              }
+            }
+            field {
+              name: "filters"
+              value {
+                object_value {
+                  field {
+                    name: "a"
+                    value {
+                      array_value {
+                        value {
+                          string_value {
+                            value: "x"
+                          }
+                        }
+                      }
+                    }
+                  }
+                  field {
+                    name: "b"
+                    value {
+                      array_value {
+                        value {
+                          string_value {
+                            value: "y"
+                          }
+                        }
+                        value {
+                          string_value {
+                            value: "z"
+                          }
+                        }
+                      }
+                    }
+                  }
+                }
+              }
+            }
+            field {
+              name: "not_filters"
+              value {
+                object_value {
+                  field {
+                    name: "a"
+                    value {
+                      array_value {
+                        value {
+                          string_value {
+                            value: "x"
+                          }
+                        }
+                      }
+                    }
+                  }
+                  field {
+                    name: "c"
+                    value {
+                      array_value {
+                        value {
+                          string_value {
+                            value: "d"
+                          }
+                        }
+                      }
+                    }
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+  field {
+    name: "aggregatable_trigger_data"
+    value {
+      array_value {
+        value {
+          object_value {
+            field {
+              name: "key_piece"
+              value {
+                string_value {
+                  value: "0x1"
+                }
+              }
+            }
+            field {
+              name: "source_keys"
+              value {
+                array_value {
+                  value {
+                    string_value {
+                      value: "a"
+                    }
+                  }
+                }
+              }
+            }
+            field {
+              name: "filters"
+              value {
+                object_value {
+                  field {
+                    name: "a"
+                    value {
+                      array_value {
+                        value {
+                          string_value {
+                            value: "x"
+                          }
+                        }
+                      }
+                    }
+                  }
+                  field {
+                    name: "b"
+                    value {
+                      array_value {
+                        value {
+                          string_value {
+                            value: "y"
+                          }
+                        }
+                        value {
+                          string_value {
+                            value: "z"
+                          }
+                        }
+                      }
+                    }
+                  }
+                }
+              }
+            }
+            field {
+              name: "not_filters"
+              value {
+                object_value {
+                  field {
+                    name: "c"
+                    value {
+                      array_value {
+                        value {
+                          string_value {
+                            value: "d"
+                          }
+                        }
+                      }
+                    }
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+  field {
+    name: "aggregatable_values"
+    value {
+      object_value {
+        field {
+          name: "a"
+          value {
+            number_value {
+              value: 123
+            }
+          }
+        }
+        field {
+          name: "b"
+          value {
+            number_value {
+              value: 456
+            }
+          }
+        }
+      }
+    }
+  }
+  field {
+    name: "debug_key"
+    value {
+      string_value {
+        value: "789"
+      }
+    }
+  }
+  field {
+    name: "filters"
+    value {
+      object_value {
+        field {
+          name: "a"
+          value {
+            array_value {
+              value {
+                string_value {
+                  value: "x"
+                }
+              }
+            }
+          }
+        }
+        field {
+          name: "b"
+          value {
+            array_value {
+              value {
+                string_value {
+                  value: "y"
+                }
+              }
+              value {
+                string_value {
+                  value: "z"
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+}
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 48b2454..28ef1f1a 100644
--- a/third_party/blink/renderer/core/frame/attribution_src_loader.cc
+++ b/third_party/blink/renderer/core/frame/attribution_src_loader.cc
@@ -56,16 +56,6 @@
   kMaxValue = kFailed,
 };
 
-bool ContainsTriggerHeaders(const HTTPHeaderMap& headers) {
-  return headers.Contains(
-             http_names::kAttributionReportingRegisterEventTrigger) ||
-         (headers.Contains(
-              http_names::
-                  kAttributionReportingRegisterAggregatableTriggerData) &&
-          headers.Contains(
-              http_names::kAttributionReportingRegisterAggregatableValues));
-}
-
 void RecordAttributionSrcRequestStatus(AttributionSrcRequestStatus status) {
   base::UmaHistogramEnumeration("Conversions.AttributionSrcRequestStatus",
                                 status);
@@ -338,8 +328,10 @@
     return;
   }
 
-  if (!ContainsTriggerHeaders(response.HttpHeaderFields()))
+  if (!response.HttpHeaderFields().Contains(
+          http_names::kAttributionReportingRegisterTrigger)) {
     return;
+  }
 
   if (!CanRegisterAttribution(RegisterContext::kResourceTrigger,
                               response.CurrentRequestUrl(),
@@ -429,7 +421,8 @@
   // are present together.
   bool can_process_trigger =
       type_ == SrcType::kUndetermined || type_ == SrcType::kTrigger;
-  if (can_process_trigger && ContainsTriggerHeaders(headers)) {
+  if (can_process_trigger &&
+      headers.Contains(http_names::kAttributionReportingRegisterTrigger)) {
     type_ = SrcType::kTrigger;
     HandleTriggerRegistration(response);
   }
diff --git a/third_party/blink/renderer/core/frame/attribution_event_trigger_data_fuzzer.cc b/third_party/blink/renderer/core/frame/attribution_trigger_registration_fuzzer.cc
similarity index 85%
rename from third_party/blink/renderer/core/frame/attribution_event_trigger_data_fuzzer.cc
rename to third_party/blink/renderer/core/frame/attribution_trigger_registration_fuzzer.cc
index 3b89379..689a4594 100644
--- a/third_party/blink/renderer/core/frame/attribution_event_trigger_data_fuzzer.cc
+++ b/third_party/blink/renderer/core/frame/attribution_trigger_registration_fuzzer.cc
@@ -13,7 +13,6 @@
 #include "third_party/blink/renderer/core/frame/attribution_response_parsing.h"
 #include "third_party/blink/renderer/platform/testing/blink_fuzzer_test_support.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
-#include "third_party/blink/renderer/platform/wtf/vector.h"
 
 namespace blink {
 
@@ -27,8 +26,8 @@
     std::cout << native_input << std::endl;
 
   const String input(native_input.c_str());
-  WTF::Vector<mojom::blink::EventTriggerDataPtr> output;
-  attribution_response_parsing::ParseEventTriggerData(input, output);
+  mojom::blink::AttributionTriggerData output;
+  attribution_response_parsing::ParseTriggerRegistrationHeader(input, output);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/frame/deprecation/deprecation.cc b/third_party/blink/renderer/core/frame/deprecation/deprecation.cc
index 5eba69f..9246e0c 100644
--- a/third_party/blink/renderer/core/frame/deprecation/deprecation.cc
+++ b/third_party/blink/renderer/core/frame/deprecation/deprecation.cc
@@ -153,9 +153,6 @@
     case WebFeature::kObsoleteWebrtcTlsVersion:
       return DeprecationInfo::WithTranslation(
           feature, DeprecationIssueType::kObsoleteWebRtcCipherSuite);
-    case WebFeature::kPaymentRequestBasicCard:
-      return DeprecationInfo::WithTranslation(
-          feature, DeprecationIssueType::kPaymentRequestBasicCard);
     case WebFeature::kPictureSourceSrc:
       return DeprecationInfo::WithTranslation(
           feature, DeprecationIssueType::kPictureSourceSrc);
diff --git a/third_party/blink/renderer/core/inspector/inspector_audits_issue.cc b/third_party/blink/renderer/core/inspector/inspector_audits_issue.cc
index 9b17250..6152fc9 100644
--- a/third_party/blink/renderer/core/inspector/inspector_audits_issue.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_audits_issue.cc
@@ -521,10 +521,6 @@
       type =
           protocol::Audits::DeprecationIssueTypeEnum::ObsoleteWebRtcCipherSuite;
       break;
-    case DeprecationIssueType::kPaymentRequestBasicCard:
-      type =
-          protocol::Audits::DeprecationIssueTypeEnum::PaymentRequestBasicCard;
-      break;
     case DeprecationIssueType::kPictureSourceSrc:
       type = protocol::Audits::DeprecationIssueTypeEnum::PictureSourceSrc;
       break;
diff --git a/third_party/blink/renderer/core/inspector/inspector_audits_issue.h b/third_party/blink/renderer/core/inspector/inspector_audits_issue.h
index 6d740b1..536da35 100644
--- a/third_party/blink/renderer/core/inspector/inspector_audits_issue.h
+++ b/third_party/blink/renderer/core/inspector/inspector_audits_issue.h
@@ -65,7 +65,6 @@
   kNotificationInsecureOrigin,
   kNotificationPermissionRequestedIframe,
   kObsoleteWebRtcCipherSuite,
-  kPaymentRequestBasicCard,
   kPictureSourceSrc,
   kPrefixedCancelAnimationFrame,
   kPrefixedRequestAnimationFrame,
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 ac98ecc..e7bbc21 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
@@ -137,9 +137,7 @@
     {
       name: "ClientHintUAPlatform",
       permissions_policy_name: "ch-ua-platform",
-      default_value_behind_flag: [
-        ["UACHPlatformEnabledByDefault", "EnableForAll"],
-      ],
+      feature_default: "EnableForAll",
     },
     {
       name: "ClientHintUAModel",
diff --git a/third_party/blink/renderer/core/style/grid_area.h b/third_party/blink/renderer/core/style/grid_area.h
index 0798cd6..169fdc3 100644
--- a/third_party/blink/renderer/core/style/grid_area.h
+++ b/third_party/blink/renderer/core/style/grid_area.h
@@ -161,12 +161,12 @@
                                     : kLegacyGridMaxTracks;
     start_line_ =
         ClampTo<int>(start_line, -grid_max_tracks, grid_max_tracks - 1);
-    end_line_ = ClampTo<int>(end_line, -grid_max_tracks + 1, grid_max_tracks);
+    end_line_ = ClampTo<int>(end_line, start_line_ + 1, grid_max_tracks);
 
 #if DCHECK_IS_ON()
-    DCHECK_LT(start_line, end_line);
+    DCHECK_LT(start_line_, end_line_);
     if (type == kTranslatedDefinite)
-      DCHECK_GE(start_line, static_cast<T>(0));
+      DCHECK_GE(start_line_, 0);
 #endif
   }
 
diff --git a/third_party/blink/renderer/modules/indexeddb/idb_request_loader.cc b/third_party/blink/renderer/modules/indexeddb/idb_request_loader.cc
index 8ed28d92..f5898c21 100644
--- a/third_party/blink/renderer/modules/indexeddb/idb_request_loader.cc
+++ b/third_party/blink/renderer/modules/indexeddb/idb_request_loader.cc
@@ -6,7 +6,6 @@
 
 #include <algorithm>
 
-#include "base/metrics/histogram_functions.h"
 #include "third_party/blink/public/platform/task_type.h"
 #include "third_party/blink/renderer/core/dom/dom_exception.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
@@ -130,10 +129,6 @@
   DCHECK(file_reader_loading_);
   file_reader_loading_ = false;
 #endif  // DCHECK_IS_ON()
-
-  base::UmaHistogramSparse("Storage.Blob.IDBRequestLoader.ReadError",
-                           std::max(0, -loader_->GetNetError()));
-
   ReportError();
 }
 
diff --git a/third_party/blink/renderer/modules/mediastream/transferred_media_stream_track.cc b/third_party/blink/renderer/modules/mediastream/transferred_media_stream_track.cc
index a09eacf..7938b8a 100644
--- a/third_party/blink/renderer/modules/mediastream/transferred_media_stream_track.cc
+++ b/third_party/blink/renderer/modules/mediastream/transferred_media_stream_track.cc
@@ -200,6 +200,15 @@
   // Set up an EventPropagator helper to forward any events fired on track so
   // that they're re-dispatched to anything that's listening on this.
   event_propagator_ = MakeGarbageCollected<EventPropagator>(track, this);
+
+  // Observers may dispatch events which create and add new Observers. Such
+  // observers are added directly to the implementation track since track_ is
+  // now set.
+  for (auto observer : observers_) {
+    observer->TrackChangedState();
+    track_->AddObserver(observer);
+  }
+  observers_.clear();
 }
 
 void TransferredMediaStreamTrack::SetConstraints(
@@ -315,9 +324,9 @@
 void TransferredMediaStreamTrack::AddObserver(Observer* observer) {
   if (track_) {
     track_->AddObserver(observer);
+  } else {
+    observers_.insert(observer);
   }
-  // TODO(https://crbug.com/1288839): Save and forward to track_ once it's
-  // initialized.
 }
 
 TransferredMediaStreamTrack::EventPropagator::EventPropagator(
@@ -349,6 +358,7 @@
   visitor->Trace(track_);
   visitor->Trace(execution_context_);
   visitor->Trace(event_propagator_);
+  visitor->Trace(observers_);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/mediastream/transferred_media_stream_track.h b/third_party/blink/renderer/modules/mediastream/transferred_media_stream_track.h
index caacde00..7942b0f 100644
--- a/third_party/blink/renderer/modules/mediastream/transferred_media_stream_track.h
+++ b/third_party/blink/renderer/modules/mediastream/transferred_media_stream_track.h
@@ -127,6 +127,7 @@
   WeakMember<ExecutionContext> execution_context_;
   TransferredValues data_;
   Member<EventPropagator> event_propagator_;
+  HeapHashSet<WeakMember<MediaStreamTrack::Observer>> observers_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/mediastream/transferred_media_stream_track_test.cc b/third_party/blink/renderer/modules/mediastream/transferred_media_stream_track_test.cc
index 7621323..dd1e3f93f 100644
--- a/third_party/blink/renderer/modules/mediastream/transferred_media_stream_track_test.cc
+++ b/third_party/blink/renderer/modules/mediastream/transferred_media_stream_track_test.cc
@@ -17,6 +17,17 @@
 
 namespace {
 using testing::_;
+
+class TestObserver : public GarbageCollected<TestObserver>,
+                     public MediaStreamTrack::Observer {
+ public:
+  void TrackChangedState() override { observation_count_++; }
+  int ObservationCount() const { return observation_count_; }
+
+ private:
+  int observation_count_ = 0;
+};
+
 }  // namespace
 
 class TransferredMediaStreamTrackTest : public testing::Test {
@@ -172,4 +183,25 @@
   transferred_track_->applyConstraints(scope.GetScriptState(),
                                        MediaTrackConstraints::Create());
 }
+
+TEST_F(TransferredMediaStreamTrackTest, SetImplementationTriggersObservers) {
+  V8TestingScope scope;
+  CustomSetUp(scope);
+  TestObserver* testObserver = MakeGarbageCollected<TestObserver>();
+  transferred_track_->AddObserver(testObserver);
+  transferred_track_->SetImplementation(
+      MakeGarbageCollected<testing::NiceMock<MockMediaStreamTrack>>());
+  EXPECT_EQ(testObserver->ObservationCount(), 1);
+}
+
+TEST_F(TransferredMediaStreamTrackTest, ObserversAddedToImpl) {
+  V8TestingScope scope;
+  CustomSetUp(scope);
+  transferred_track_->AddObserver(MakeGarbageCollected<TestObserver>());
+  MockMediaStreamTrack* mock_impl =
+      MakeGarbageCollected<testing::NiceMock<MockMediaStreamTrack>>();
+  EXPECT_CALL(*mock_impl, AddObserver(_));
+  transferred_track_->SetImplementation(mock_impl);
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/payments/payment_request.cc b/third_party/blink/renderer/modules/payments/payment_request.cc
index e6a69e6..369fb2b 100644
--- a/third_party/blink/renderer/modules/payments/payment_request.cc
+++ b/third_party/blink/renderer/modules/payments/payment_request.cc
@@ -437,8 +437,6 @@
   if (RuntimeEnabledFeatures::PaymentRequestBasicCardEnabled(
           &execution_context) &&
       supported_method == "basic-card") {
-    Deprecation::CountDeprecation(&execution_context,
-                                  WebFeature::kPaymentRequestBasicCard);
     BasicCardHelper::ParseBasiccardData(input, output->supported_networks,
                                         exception_state);
   } else if (supported_method == kSecurePaymentConfirmationMethod &&
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_queue.cc b/third_party/blink/renderer/modules/webgpu/gpu_queue.cc
index e2a9933..24cb42b 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_queue.cc
+++ b/third_party/blink/renderer/modules/webgpu/gpu_queue.cc
@@ -398,18 +398,24 @@
     return;
   }
 
-  WGPUTextureFormat format = destination->texture()->Format();
-  size_t required_copy_size = 0;
-  if (!ComputeAndValidateRequiredBytesInCopy(
-          data_size, dawn_data_layout, dawn_write_size, format,
-          dawn_destination.aspect, &required_copy_size, device_)) {
-    return;
-  }
-
-  // Only send the data which is really required.
+  // Handle the data layout offset by offsetting the data pointer instead. This
+  // helps move less data between then renderer and GPU process (otherwise all
+  // the data from 0 to offset would be copied over as well).
   const void* data_ptr =
       static_cast<const uint8_t*>(data) + dawn_data_layout.offset;
   dawn_data_layout.offset = 0;
+  data_size -= dawn_data_layout.offset;
+
+  // Compute a tight upper bound of the number of bytes to send for this
+  // WriteTexture. This can be 0 for some cases that produce validation errors,
+  // but we don't create an error in Blink since Dawn can produce better error
+  // messages (and this is more up-to-spec because the errors must be created on
+  // the device timeline).
+  size_t data_size_upper_bound = EstimateWriteTextureBytesUpperBound(
+      dawn_data_layout, dawn_write_size, destination->texture()->Format(),
+      dawn_destination.aspect);
+  size_t required_copy_size = std::min(data_size, data_size_upper_bound);
+
   GetProcs().queueWriteTexture(GetHandle(), &dawn_destination, data_ptr,
                                required_copy_size, &dawn_data_layout,
                                &dawn_write_size);
diff --git a/third_party/blink/renderer/modules/webgpu/texture_utils.cc b/third_party/blink/renderer/modules/webgpu/texture_utils.cc
index 87cffc8c..96e7a1e 100644
--- a/third_party/blink/renderer/modules/webgpu/texture_utils.cc
+++ b/third_party/blink/renderer/modules/webgpu/texture_utils.cc
@@ -16,266 +16,229 @@
   uint32_t height;
 };
 
-bool ValidateFormatAndAspectForCopy(WGPUTextureFormat format,
-                                    WGPUTextureAspect aspect) {
-  switch (format) {
-    // For depth/stencil formats, see the valid format and aspect combinations
-    // for copy at https://gpuweb.github.io/gpuweb/#depth-formats
-    case WGPUTextureFormat_Stencil8:
-      return aspect != WGPUTextureAspect_DepthOnly;
-
-    case WGPUTextureFormat_Depth16Unorm:
-      return aspect != WGPUTextureAspect_StencilOnly;
-
-    case WGPUTextureFormat_Depth24Plus:
-    // Depth32Float is not copyable when it is used as copy dst in WriteTexture
-    case WGPUTextureFormat_Depth32Float:
-      return false;
-
-    case WGPUTextureFormat_Depth24PlusStencil8:
-    case WGPUTextureFormat_Depth24UnormStencil8:
-    // Depth aspect of Depth32FloatStencil8 is not copyable when it is used as
-    // copy dst in WriteTexture
-    case WGPUTextureFormat_Depth32FloatStencil8:
-      return aspect == WGPUTextureAspect_StencilOnly;
-
-    // These formats are not copyable in WriteTexture
-    case WGPUTextureFormat_R8BG8Biplanar420Unorm:
-    case WGPUTextureFormat_Force32:
-    case WGPUTextureFormat_Undefined:
-      return false;
-
-    default:
-      return aspect == WGPUTextureAspect_All;
-  }
-}
-
 TexelBlockInfo GetTexelBlockInfoForCopy(WGPUTextureFormat format,
                                         WGPUTextureAspect aspect) {
-  if (!ValidateFormatAndAspectForCopy(format, aspect)) {
-    return {0u, 0u, 0u};
-  }
+  constexpr TexelBlockInfo kInvalidTexelBlockInfo = {0, 0, 0};
 
-  switch (format) {
-    case WGPUTextureFormat_R8Unorm:
-    case WGPUTextureFormat_R8Snorm:
-    case WGPUTextureFormat_R8Uint:
-    case WGPUTextureFormat_R8Sint:
-      return {1u, 1u, 1u};
+  switch (aspect) {
+    case WGPUTextureAspect_All:
+      switch (format) {
+        case WGPUTextureFormat_R8Unorm:
+        case WGPUTextureFormat_R8Snorm:
+        case WGPUTextureFormat_R8Uint:
+        case WGPUTextureFormat_R8Sint:
+          return {1u, 1u, 1u};
 
-    case WGPUTextureFormat_R16Uint:
-    case WGPUTextureFormat_R16Sint:
-    case WGPUTextureFormat_R16Float:
-    case WGPUTextureFormat_RG8Unorm:
-    case WGPUTextureFormat_RG8Snorm:
-    case WGPUTextureFormat_RG8Uint:
-    case WGPUTextureFormat_RG8Sint:
-      return {2u, 1u, 1u};
+        case WGPUTextureFormat_R16Uint:
+        case WGPUTextureFormat_R16Sint:
+        case WGPUTextureFormat_R16Float:
+        case WGPUTextureFormat_RG8Unorm:
+        case WGPUTextureFormat_RG8Snorm:
+        case WGPUTextureFormat_RG8Uint:
+        case WGPUTextureFormat_RG8Sint:
+          return {2u, 1u, 1u};
 
-    case WGPUTextureFormat_R32Float:
-    case WGPUTextureFormat_R32Uint:
-    case WGPUTextureFormat_R32Sint:
-    case WGPUTextureFormat_RG16Uint:
-    case WGPUTextureFormat_RG16Sint:
-    case WGPUTextureFormat_RG16Float:
-    case WGPUTextureFormat_RGBA8Unorm:
-    case WGPUTextureFormat_RGBA8UnormSrgb:
-    case WGPUTextureFormat_RGBA8Snorm:
-    case WGPUTextureFormat_RGBA8Uint:
-    case WGPUTextureFormat_RGBA8Sint:
-    case WGPUTextureFormat_BGRA8Unorm:
-    case WGPUTextureFormat_BGRA8UnormSrgb:
-    case WGPUTextureFormat_RGB10A2Unorm:
-    case WGPUTextureFormat_RG11B10Ufloat:
-    case WGPUTextureFormat_RGB9E5Ufloat:
-      return {4u, 1u, 1u};
+        case WGPUTextureFormat_R32Float:
+        case WGPUTextureFormat_R32Uint:
+        case WGPUTextureFormat_R32Sint:
+        case WGPUTextureFormat_RG16Uint:
+        case WGPUTextureFormat_RG16Sint:
+        case WGPUTextureFormat_RG16Float:
+        case WGPUTextureFormat_RGBA8Unorm:
+        case WGPUTextureFormat_RGBA8UnormSrgb:
+        case WGPUTextureFormat_RGBA8Snorm:
+        case WGPUTextureFormat_RGBA8Uint:
+        case WGPUTextureFormat_RGBA8Sint:
+        case WGPUTextureFormat_BGRA8Unorm:
+        case WGPUTextureFormat_BGRA8UnormSrgb:
+        case WGPUTextureFormat_RGB10A2Unorm:
+        case WGPUTextureFormat_RG11B10Ufloat:
+        case WGPUTextureFormat_RGB9E5Ufloat:
+          return {4u, 1u, 1u};
 
-    case WGPUTextureFormat_RG32Float:
-    case WGPUTextureFormat_RG32Uint:
-    case WGPUTextureFormat_RG32Sint:
-    case WGPUTextureFormat_RGBA16Uint:
-    case WGPUTextureFormat_RGBA16Sint:
-    case WGPUTextureFormat_RGBA16Float:
-      return {8u, 1u, 1u};
+        case WGPUTextureFormat_RG32Float:
+        case WGPUTextureFormat_RG32Uint:
+        case WGPUTextureFormat_RG32Sint:
+        case WGPUTextureFormat_RGBA16Uint:
+        case WGPUTextureFormat_RGBA16Sint:
+        case WGPUTextureFormat_RGBA16Float:
+          return {8u, 1u, 1u};
 
-    case WGPUTextureFormat_RGBA32Float:
-    case WGPUTextureFormat_RGBA32Uint:
-    case WGPUTextureFormat_RGBA32Sint:
-      return {16u, 1u, 1u};
+        case WGPUTextureFormat_RGBA32Float:
+        case WGPUTextureFormat_RGBA32Uint:
+        case WGPUTextureFormat_RGBA32Sint:
+          return {16u, 1u, 1u};
 
-    case WGPUTextureFormat_Stencil8:
-      return {1u, 1u, 1u};
+        case WGPUTextureFormat_Depth16Unorm:
+          return {2u, 1u, 1u};
+        case WGPUTextureFormat_Stencil8:
+          return {1u, 1u, 1u};
 
-    case WGPUTextureFormat_Depth16Unorm:
-      return {2u, 1u, 1u};
+        case WGPUTextureFormat_BC1RGBAUnorm:
+        case WGPUTextureFormat_BC1RGBAUnormSrgb:
+        case WGPUTextureFormat_BC4RUnorm:
+        case WGPUTextureFormat_BC4RSnorm:
+          return {8u, 4u, 4u};
 
-    // Only stencil aspect is valid for WriteTexture
-    case WGPUTextureFormat_Depth24UnormStencil8:
-    case WGPUTextureFormat_Depth24PlusStencil8:
-    case WGPUTextureFormat_Depth32FloatStencil8:
-      return {1u, 1u, 1u};
+        case WGPUTextureFormat_BC2RGBAUnorm:
+        case WGPUTextureFormat_BC2RGBAUnormSrgb:
+        case WGPUTextureFormat_BC3RGBAUnorm:
+        case WGPUTextureFormat_BC3RGBAUnormSrgb:
+        case WGPUTextureFormat_BC5RGUnorm:
+        case WGPUTextureFormat_BC5RGSnorm:
+        case WGPUTextureFormat_BC6HRGBUfloat:
+        case WGPUTextureFormat_BC6HRGBFloat:
+        case WGPUTextureFormat_BC7RGBAUnorm:
+        case WGPUTextureFormat_BC7RGBAUnormSrgb:
+          return {16u, 4u, 4u};
 
-    case WGPUTextureFormat_BC1RGBAUnorm:
-    case WGPUTextureFormat_BC1RGBAUnormSrgb:
-    case WGPUTextureFormat_BC4RUnorm:
-    case WGPUTextureFormat_BC4RSnorm:
-      return {8u, 4u, 4u};
+        case WGPUTextureFormat_ETC2RGB8Unorm:
+        case WGPUTextureFormat_ETC2RGB8UnormSrgb:
+        case WGPUTextureFormat_ETC2RGB8A1Unorm:
+        case WGPUTextureFormat_ETC2RGB8A1UnormSrgb:
+        case WGPUTextureFormat_EACR11Unorm:
+        case WGPUTextureFormat_EACR11Snorm:
+          return {8u, 4u, 4u};
 
-    case WGPUTextureFormat_BC2RGBAUnorm:
-    case WGPUTextureFormat_BC2RGBAUnormSrgb:
-    case WGPUTextureFormat_BC3RGBAUnorm:
-    case WGPUTextureFormat_BC3RGBAUnormSrgb:
-    case WGPUTextureFormat_BC5RGUnorm:
-    case WGPUTextureFormat_BC5RGSnorm:
-    case WGPUTextureFormat_BC6HRGBUfloat:
-    case WGPUTextureFormat_BC6HRGBFloat:
-    case WGPUTextureFormat_BC7RGBAUnorm:
-    case WGPUTextureFormat_BC7RGBAUnormSrgb:
-      return {16u, 4u, 4u};
+        case WGPUTextureFormat_ETC2RGBA8Unorm:
+        case WGPUTextureFormat_ETC2RGBA8UnormSrgb:
+        case WGPUTextureFormat_EACRG11Unorm:
+        case WGPUTextureFormat_EACRG11Snorm:
+          return {16u, 4u, 4u};
 
-    case WGPUTextureFormat_ETC2RGB8Unorm:
-    case WGPUTextureFormat_ETC2RGB8UnormSrgb:
-    case WGPUTextureFormat_ETC2RGB8A1Unorm:
-    case WGPUTextureFormat_ETC2RGB8A1UnormSrgb:
-    case WGPUTextureFormat_EACR11Unorm:
-    case WGPUTextureFormat_EACR11Snorm:
-      return {8u, 4u, 4u};
+        case WGPUTextureFormat_ASTC4x4Unorm:
+        case WGPUTextureFormat_ASTC4x4UnormSrgb:
+          return {16u, 4u, 4u};
+        case WGPUTextureFormat_ASTC5x4Unorm:
+        case WGPUTextureFormat_ASTC5x4UnormSrgb:
+          return {16u, 5u, 4u};
+        case WGPUTextureFormat_ASTC5x5Unorm:
+        case WGPUTextureFormat_ASTC5x5UnormSrgb:
+          return {16u, 5u, 5u};
+        case WGPUTextureFormat_ASTC6x5Unorm:
+        case WGPUTextureFormat_ASTC6x5UnormSrgb:
+          return {16u, 6u, 5u};
+        case WGPUTextureFormat_ASTC6x6Unorm:
+        case WGPUTextureFormat_ASTC6x6UnormSrgb:
+          return {16u, 6u, 6u};
+        case WGPUTextureFormat_ASTC8x5Unorm:
+        case WGPUTextureFormat_ASTC8x5UnormSrgb:
+          return {16u, 8u, 5u};
+        case WGPUTextureFormat_ASTC8x6Unorm:
+        case WGPUTextureFormat_ASTC8x6UnormSrgb:
+          return {16u, 8u, 6u};
+        case WGPUTextureFormat_ASTC8x8Unorm:
+        case WGPUTextureFormat_ASTC8x8UnormSrgb:
+          return {16u, 8u, 8u};
+        case WGPUTextureFormat_ASTC10x5Unorm:
+        case WGPUTextureFormat_ASTC10x5UnormSrgb:
+          return {16u, 10u, 5u};
+        case WGPUTextureFormat_ASTC10x6Unorm:
+        case WGPUTextureFormat_ASTC10x6UnormSrgb:
+          return {16u, 10u, 6u};
+        case WGPUTextureFormat_ASTC10x8Unorm:
+        case WGPUTextureFormat_ASTC10x8UnormSrgb:
+          return {16u, 10u, 8u};
+        case WGPUTextureFormat_ASTC10x10Unorm:
+        case WGPUTextureFormat_ASTC10x10UnormSrgb:
+          return {16u, 10u, 10u};
+        case WGPUTextureFormat_ASTC12x10Unorm:
+        case WGPUTextureFormat_ASTC12x10UnormSrgb:
+          return {16u, 12u, 10u};
+        case WGPUTextureFormat_ASTC12x12Unorm:
+        case WGPUTextureFormat_ASTC12x12UnormSrgb:
+          return {16u, 12u, 12u};
 
-    case WGPUTextureFormat_ETC2RGBA8Unorm:
-    case WGPUTextureFormat_ETC2RGBA8UnormSrgb:
-    case WGPUTextureFormat_EACRG11Unorm:
-    case WGPUTextureFormat_EACRG11Snorm:
-      return {16u, 4u, 4u};
+        default:
+          return kInvalidTexelBlockInfo;
+      }
 
-    case WGPUTextureFormat_ASTC4x4Unorm:
-    case WGPUTextureFormat_ASTC4x4UnormSrgb:
-      return {16u, 4u, 4u};
-    case WGPUTextureFormat_ASTC5x4Unorm:
-    case WGPUTextureFormat_ASTC5x4UnormSrgb:
-      return {16u, 5u, 4u};
-    case WGPUTextureFormat_ASTC5x5Unorm:
-    case WGPUTextureFormat_ASTC5x5UnormSrgb:
-      return {16u, 5u, 5u};
-    case WGPUTextureFormat_ASTC6x5Unorm:
-    case WGPUTextureFormat_ASTC6x5UnormSrgb:
-      return {16u, 6u, 5u};
-    case WGPUTextureFormat_ASTC6x6Unorm:
-    case WGPUTextureFormat_ASTC6x6UnormSrgb:
-      return {16u, 6u, 6u};
-    case WGPUTextureFormat_ASTC8x5Unorm:
-    case WGPUTextureFormat_ASTC8x5UnormSrgb:
-      return {16u, 8u, 5u};
-    case WGPUTextureFormat_ASTC8x6Unorm:
-    case WGPUTextureFormat_ASTC8x6UnormSrgb:
-      return {16u, 8u, 6u};
-    case WGPUTextureFormat_ASTC8x8Unorm:
-    case WGPUTextureFormat_ASTC8x8UnormSrgb:
-      return {16u, 8u, 8u};
-    case WGPUTextureFormat_ASTC10x5Unorm:
-    case WGPUTextureFormat_ASTC10x5UnormSrgb:
-      return {16u, 10u, 5u};
-    case WGPUTextureFormat_ASTC10x6Unorm:
-    case WGPUTextureFormat_ASTC10x6UnormSrgb:
-      return {16u, 10u, 6u};
-    case WGPUTextureFormat_ASTC10x8Unorm:
-    case WGPUTextureFormat_ASTC10x8UnormSrgb:
-      return {16u, 10u, 8u};
-    case WGPUTextureFormat_ASTC10x10Unorm:
-    case WGPUTextureFormat_ASTC10x10UnormSrgb:
-      return {16u, 10u, 10u};
-    case WGPUTextureFormat_ASTC12x10Unorm:
-    case WGPUTextureFormat_ASTC12x10UnormSrgb:
-      return {16u, 12u, 10u};
-    case WGPUTextureFormat_ASTC12x12Unorm:
-    case WGPUTextureFormat_ASTC12x12UnormSrgb:
-      return {16u, 12u, 12u};
+    // Copies to depth/stencil aspects are fairly restricted, see
+    // https://gpuweb.github.io/gpuweb/#depth-formats so we only list
+    // combinations of format and aspects that can be copied to with a
+    // WriteTexture.
+    case WGPUTextureAspect_DepthOnly:
+      switch (format) {
+        case WGPUTextureFormat_Depth16Unorm:
+          return GetTexelBlockInfoForCopy(format, WGPUTextureAspect_All);
+
+        default:
+          return kInvalidTexelBlockInfo;
+      }
+
+    case WGPUTextureAspect_StencilOnly:
+      switch (format) {
+        case WGPUTextureFormat_Depth24UnormStencil8:
+        case WGPUTextureFormat_Depth24PlusStencil8:
+        case WGPUTextureFormat_Depth32FloatStencil8:
+          return {1u, 1u, 1u};
+
+        case WGPUTextureFormat_Stencil8:
+          return GetTexelBlockInfoForCopy(format, WGPUTextureAspect_All);
+
+        default:
+          return kInvalidTexelBlockInfo;
+      }
 
     default:
       NOTREACHED();
-      return {0u, 0u, 0u};
+      return kInvalidTexelBlockInfo;
   }
 }
 
 }  // anonymous namespace
 
-bool ComputeAndValidateRequiredBytesInCopy(size_t data_size,
-                                           WGPUTextureDataLayout layout,
+size_t EstimateWriteTextureBytesUpperBound(WGPUTextureDataLayout layout,
                                            WGPUExtent3D extent,
                                            WGPUTextureFormat format,
-                                           WGPUTextureAspect aspect,
-                                           size_t* required_copy_size,
-                                           GPUDevice* device) {
-  TexelBlockInfo blockInfo = GetTexelBlockInfoForCopy(format, aspect);
-  if (!blockInfo.byteSize) {
-    device->InjectError(
-        WGPUErrorType_Validation,
-        "Format, aspect or the combination are not valid for WriteTexture");
-    return false;
+                                           WGPUTextureAspect aspect) {
+  // Check for empty copies because of depth first so we can early out. Note
+  // that we can't early out because of height or width being 0 because padding
+  // images still need to be accounted for.
+  if (extent.depthOrArrayLayers == 0) {
+    return 0;
   }
 
+  TexelBlockInfo blockInfo = GetTexelBlockInfoForCopy(format, aspect);
+
+  // Unknown format/aspect combination will be validated by the GPU process
+  // again.
+  if (blockInfo.byteSize == 0) {
+    return 0;
+  }
+
+  // If the block size doesn't divide the extent, a validation error will be
+  // produced on the GPU process side so we don't need to guard against it.
   uint32_t widthInBlocks = extent.width / blockInfo.width;
   uint32_t heightInBlocks = extent.height / blockInfo.height;
-  size_t lastRowBytes = widthInBlocks * blockInfo.byteSize;
 
-  if (layout.bytesPerRow == WGPU_STRIDE_UNDEFINED &&
-      (heightInBlocks > 1 || extent.depthOrArrayLayers > 1)) {
-    device->InjectError(WGPUErrorType_Validation,
-                        "bytesPerRow must be specified");
-    return false;
-  }
-
-  if (layout.rowsPerImage == WGPU_STRIDE_UNDEFINED &&
-      extent.depthOrArrayLayers > 1) {
-    device->InjectError(WGPUErrorType_Validation,
-                        "rowsPerImage must be specified");
-    return false;
-  }
-
-  if (layout.bytesPerRow < lastRowBytes) {
-    device->InjectError(WGPUErrorType_Validation,
-                        "bytesPerRow in image data layout is too small");
-    return false;
-  }
-
-  if (layout.rowsPerImage < heightInBlocks) {
-    device->InjectError(WGPUErrorType_Validation,
-                        "rowsPerImage in image data layout is too small");
-    return false;
-  }
-
-  if (extent.depthOrArrayLayers == 0) {
-    *required_copy_size = 0;
-    return true;
-  }
-
+  // Use checked numerics even though the GPU process will guard against OOB
+  // because otherwise UBSan will complain about overflows. Note that if
+  // bytesPerRow or rowsPerImage are WGPU_COPY_STRIDE_UNDEFINED and used, the
+  // GPU process will also create a validation error because it means that they
+  // are used when copySize.height/depthOrArrayLayers > 1.
   base::CheckedNumeric<size_t> requiredBytesInCopy = 0;
+
+  // WebGPU requires that the padding bytes for images are counted, even if the
+  // copy is empty.
   if (extent.depthOrArrayLayers > 1) {
     requiredBytesInCopy = layout.bytesPerRow;
     requiredBytesInCopy *= layout.rowsPerImage;
     requiredBytesInCopy *= (extent.depthOrArrayLayers - 1);
   }
+
   if (heightInBlocks != 0) {
-    size_t lastImageBytes =
-        layout.bytesPerRow * (heightInBlocks - 1) + lastRowBytes;
+    base::CheckedNumeric<size_t> lastRowBytes = widthInBlocks;
+    lastRowBytes *= blockInfo.byteSize;
+
+    base::CheckedNumeric<size_t> lastImageBytes = layout.bytesPerRow;
+    lastImageBytes *= (heightInBlocks - 1);
+    lastImageBytes += lastRowBytes;
+
     requiredBytesInCopy += lastImageBytes;
   }
 
-  if (!requiredBytesInCopy.IsValid()) {
-    device->InjectError(WGPUErrorType_Validation,
-                        "Required copy size overflows");
-    return false;
-  }
-
-  *required_copy_size = requiredBytesInCopy.ValueOrDie();
-  DCHECK(data_size >= layout.offset);
-  if (*required_copy_size > data_size - layout.offset) {
-    device->InjectError(
-        WGPUErrorType_Validation,
-        "Required copy size for texture layout exceed data size with offset");
-    return false;
-  }
-
-  return true;
+  return requiredBytesInCopy.ValueOrDefault(0);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/webgpu/texture_utils.h b/third_party/blink/renderer/modules/webgpu/texture_utils.h
index a2a6262..14fcd190 100644
--- a/third_party/blink/renderer/modules/webgpu/texture_utils.h
+++ b/third_party/blink/renderer/modules/webgpu/texture_utils.h
@@ -9,13 +9,10 @@
 
 namespace blink {
 
-bool ComputeAndValidateRequiredBytesInCopy(size_t data_size,
-                                           WGPUTextureDataLayout layout,
+size_t EstimateWriteTextureBytesUpperBound(WGPUTextureDataLayout layout,
                                            WGPUExtent3D extent,
                                            WGPUTextureFormat format,
-                                           WGPUTextureAspect aspect,
-                                           size_t* required_copy_size,
-                                           GPUDevice* device);
+                                           WGPUTextureAspect aspect);
 
 }  // namespace blink
 
diff --git a/third_party/blink/renderer/platform/network/http_names.json5 b/third_party/blink/renderer/platform/network/http_names.json5
index 0cc8c46..35e86bfa 100644
--- a/third_party/blink/renderer/platform/network/http_names.json5
+++ b/third_party/blink/renderer/platform/network/http_names.json5
@@ -24,12 +24,8 @@
     "Access-Control-Request-Method",
     "Allow-CSP-From",
     "Attribution-Reporting-Eligible",
-    "Attribution-Reporting-Filters",
-    "Attribution-Reporting-Register-Aggregatable-Trigger-Data",
-    "Attribution-Reporting-Register-Aggregatable-Values",
-    "Attribution-Reporting-Register-Event-Trigger",
     "Attribution-Reporting-Register-Source",
-    "Attribution-Reporting-Trigger-Debug-Key",
+    "Attribution-Reporting-Register-Trigger",
     "Cache-Control",
     "Content-DPR",
     "Content-Disposition",
diff --git a/third_party/blink/renderer/platform/widget/compositing/android_webview/synchronous_layer_tree_frame_sink.cc b/third_party/blink/renderer/platform/widget/compositing/android_webview/synchronous_layer_tree_frame_sink.cc
index db18ae26..2b2f5b7 100644
--- a/third_party/blink/renderer/platform/widget/compositing/android_webview/synchronous_layer_tree_frame_sink.cc
+++ b/third_party/blink/renderer/platform/widget/compositing/android_webview/synchronous_layer_tree_frame_sink.cc
@@ -108,15 +108,9 @@
   void BindToClient(viz::OutputSurfaceClient* client) override {}
   void EnsureBackbuffer() override {}
   void DiscardBackbuffer() override {}
-  void BindFramebuffer() override {}
   void SwapBuffers(viz::OutputSurfaceFrame frame) override {}
   void Reshape(const ReshapeParams& params) override {}
-  uint32_t GetFramebufferCopyTextureFormat() override { return 0; }
   bool IsDisplayedAsOverlayPlane() const override { return false; }
-  unsigned GetOverlayTextureId() const override { return 0; }
-  bool HasExternalStencilTest() const override { return false; }
-  void ApplyExternalStencil() override {}
-  unsigned UpdateGpuFence() override { return 0; }
   void SetUpdateVSyncParametersCallback(
       viz::UpdateVSyncParametersCallback callback) override {}
   void SetDisplayTransformHint(gfx::OverlayTransform transform) override {}
diff --git a/third_party/blink/web_tests/FlagExpectations/force-renderer-accessibility b/third_party/blink/web_tests/FlagExpectations/force-renderer-accessibility
index 501c0855..b5f98f8 100644
--- a/third_party/blink/web_tests/FlagExpectations/force-renderer-accessibility
+++ b/third_party/blink/web_tests/FlagExpectations/force-renderer-accessibility
@@ -33,6 +33,7 @@
 external/wpt/html/browsers/browsing-the-web/back-forward-cache/service-worker-clients-matchall.https.html [ Skip ]
 external/wpt/html/browsers/browsing-the-web/back-forward-cache/service-worker-controlled-after-restore.https.html [ Skip ]
 external/wpt/html/browsers/browsing-the-web/back-forward-cache/service-worker-unregister.https.html [ Skip ]
+external/wpt/html/browsers/browsing-the-web/back-forward-cache/storage-events.html [ Skip ]
 external/wpt/html/browsers/browsing-the-web/back-forward-cache/timers.html [ Skip ]
 external/wpt/html/semantics/interactive-elements/the-dialog-element/inert-svg-hittest.html [ Skip ]
 external/wpt/inert/dynamic-inert-on-focused-element.html [ Skip ]
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 7555fcf..a1a21254 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -3516,7 +3516,6 @@
 crbug.com/626703 [ Mac11-arm64 ] virtual/webrtc-wpt-plan-b/external/wpt/webrtc/RTCPeerConnection-videoDetectorTest.html [ Skip Timeout ]
 crbug.com/626703 [ Linux ] external/wpt/media-capabilities/encodingInfo.any.worker.html [ Crash ]
 crbug.com/626703 [ Win10.20h2 ] external/wpt/media-capabilities/encodingInfo.any.worker.html [ Crash ]
-crbug.com/626703 [ Mac11-arm64 ] virtual/disable-ua-ch-platform/external/wpt/client-hints/accept-ch-stickiness/same-origin-navigation.https.html [ Crash ]
 crbug.com/626703 [ Mac11-arm64 ] virtual/forced-high-contrast-colors/external/wpt/forced-colors-mode/backplate/forced-colors-mode-backplate-01.html [ Crash ]
 crbug.com/626703 external/wpt/workers/interfaces/WorkerUtils/importScripts/blob-url.worker.html [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/input-events/input-events-get-target-ranges.html [ Failure Timeout ]
@@ -7089,10 +7088,6 @@
 crbug.com/1325307 fast/css-grid-layout/grid-auto-repeat-huge-grid-009.html [ Skip ]
 crbug.com/1325307 fast/css-grid-layout/grid-auto-repeat-huge-grid-010.html [ Skip ]
 
-# suppress geolocation failures
-crbug.com/1330988 http/tests/geolocation-api/geolocation-default-feature-policy.https.sub.html [ Failure ]
-crbug.com/1330988 http/tests/security/powerfulFeatureRestrictions/geolocation-on-secure-origin-in-secure-origin.html [ Failure ]
-
 # Sheriff 2022-05-30
 crbug.com/1330238 [ Mac ] http/tests/devtools/elements/styles-3/styles-computed-trace.js [ Failure Pass Timeout ]
 
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites
index 8598d39..ada0d2a 100644
--- a/third_party/blink/web_tests/VirtualTestSuites
+++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -1056,12 +1056,6 @@
     "args": ["--disable-features=DialogFocusNewSpecBehavior"]
   },
   {
-    "prefix": "disable-ua-ch-platform",
-    "platforms": ["Linux", "Mac", "Win"],
-    "bases": ["external/wpt/client-hints", "wpt_internal/client-hints"],
-    "args": ["--disable-features=UACHPlatformEnabledByDefault"]
-  },
-  {
     "prefix": "object-param-url",
     "platforms": ["Linux", "Mac", "Win"],
     "bases": ["external/wpt/html/semantics/embedded-content/the-object-element/object-param-url.html"],
diff --git a/third_party/blink/web_tests/external/wpt/credential-management/support/set_cookie.headers b/third_party/blink/web_tests/external/wpt/credential-management/support/set_cookie.headers
index ad111fc..8c233d1 100644
--- a/third_party/blink/web_tests/external/wpt/credential-management/support/set_cookie.headers
+++ b/third_party/blink/web_tests/external/wpt/credential-management/support/set_cookie.headers
@@ -1 +1 @@
-Set-Cookie: cookie=1; SameSite=None; Secure
+Set-Cookie: cookie=1; SameSite=Strict; Secure
diff --git a/third_party/blink/web_tests/fast/css-grid-layout/crash-large-positions.html b/third_party/blink/web_tests/fast/css-grid-layout/crash-large-positions.html
index 2c448e4..5ae7d218 100644
--- a/third_party/blink/web_tests/fast/css-grid-layout/crash-large-positions.html
+++ b/third_party/blink/web_tests/fast/css-grid-layout/crash-large-positions.html
@@ -5,11 +5,13 @@
 <script src="resources/grid-definitions-parsing-utils.js"></script>
 
 <div style="display: grid;">
-    <div id="item" style="grid-column-start: 5000000000; grid-column-end: -5000000000; grid-row-start: 5000000000; grid-row-end: -5000000000;"></div>
+    <div id="item1" style="grid-column-start: 5000000000; grid-column-end: -5000000000; grid-row-start: 5000000000; grid-row-end: -5000000000;"></div>
+    <div id="item2" style="grid-column: 10000000000000000000 / span 5; grid-row: span 5 / 10000000000000000000"></div>
 </div>
 
 <script>
  test(function() {
-     testGridPositionDefinitionsValues(document.getElementById("item"), "2.14748e+09", "-2.14748e+09", "2.14748e+09", "-2.14748e+09");
+     testGridPositionDefinitionsValues(document.getElementById("item1"), "2.14748e+09", "-2.14748e+09", "2.14748e+09", "-2.14748e+09");
+     testGridPositionDefinitionsValues(document.getElementById("item2"), "span 5", "2.14748e+09", "2.14748e+09", "span 5");
  }, "Test that setting and getting grid-column|row-start|end to huge values is properly clamped and does not make the renderer crash.");
 </script>
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/attribution-reporting/resources/register-trigger.php b/third_party/blink/web_tests/http/tests/inspector-protocol/attribution-reporting/resources/register-trigger.php
index ee0c4ff6..a34c4ff 100644
--- a/third_party/blink/web_tests/http/tests/inspector-protocol/attribution-reporting/resources/register-trigger.php
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/attribution-reporting/resources/register-trigger.php
@@ -1,3 +1,3 @@
 <?php
-header('Attribution-Reporting-Register-Event-Trigger: []');
+header('Attribution-Reporting-Register-Trigger: {}');
 ?>
diff --git a/third_party/blink/web_tests/http/tests/resources/geolocation-mock.js b/third_party/blink/web_tests/http/tests/resources/geolocation-mock.js
index 1e9a588..26eaa34f 100644
--- a/third_party/blink/web_tests/http/tests/resources/geolocation-mock.js
+++ b/third_party/blink/web_tests/http/tests/resources/geolocation-mock.js
@@ -112,6 +112,7 @@
         timestamp: {internalValue: 0n},
         errorMessage: "User has not allowed access to system location.",
         errorCode: Geoposition_ErrorCode.PERMISSION_DENIED,
+        errorTechnical: "",
       };
     }
 
@@ -139,9 +140,10 @@
     const timestamp =
         {internalValue: BigInt((new Date().getTime() + epochDeltaInMs) * 1000)};
     const errorMessage = '';
+    const errorTechnical = '';
     const valid = true;
     return {latitude, longitude, accuracy, altitude, altitudeAccuracy, heading,
-            speed, timestamp, errorMessage, valid};
+            speed, timestamp, errorMessage, valid, errorTechnical};
   }
 
   /**
@@ -166,6 +168,7 @@
       timestamp: {internalValue: 0n},
       errorMessage: message,
       errorCode: Geoposition_ErrorCode.POSITION_UNAVAILABLE,
+      errorTechnical: "",
     };
   }
 
diff --git a/third_party/blink/web_tests/platform/generic/external/wpt/css/selectors/invalidation/is-pseudo-containing-complex-in-has-expected.txt b/third_party/blink/web_tests/platform/generic/external/wpt/css/selectors/invalidation/is-pseudo-containing-complex-in-has-expected.txt
index 618cca7..221527e 100644
--- a/third_party/blink/web_tests/platform/generic/external/wpt/css/selectors/invalidation/is-pseudo-containing-complex-in-has-expected.txt
+++ b/third_party/blink/web_tests/platform/generic/external/wpt/css/selectors/invalidation/is-pseudo-containing-complex-in-has-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 476 tests; 408 PASS, 68 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 476 tests; 418 PASS, 58 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS [ .red:has(#descendant:is(.a_has_scope .b)) ] #has_scope.classList.add('red') : check matches (false)
 PASS [ .red:has(#descendant:is(.a_has_scope .b)) ] #has_scope.classList.add('red') : check #has_scope color
 PASS [ .red:has(#descendant:is(.a_has_scope .b)) ] #parent.classList.add('a_has_scope') : check matches (true)
@@ -7,7 +7,7 @@
 PASS [ .red:has(#descendant:is(.a_has_scope .b)) ] #parent.classList.remove('a_has_scope') : check matches (false)
 PASS [ .red:has(#descendant:is(.a_has_scope .b)) ] #parent.classList.remove('a_has_scope') : check #has_scope color
 PASS [ .red:has(#descendant:is(.a_has_scope .b)) ] #has_scope.classList.add('a_has_scope') : check matches (true)
-FAIL [ .red:has(#descendant:is(.a_has_scope .b)) ] #has_scope.classList.add('a_has_scope') : check #has_scope color assert_equals: expected "rgb(255, 0, 0)" but got "rgb(128, 128, 128)"
+PASS [ .red:has(#descendant:is(.a_has_scope .b)) ] #has_scope.classList.add('a_has_scope') : check #has_scope color
 PASS [ .red:has(#descendant:is(.a_has_scope .b)) ] #has_scope.classList.remove('a_has_scope') : check matches (false)
 PASS [ .red:has(#descendant:is(.a_has_scope .b)) ] #has_scope.classList.remove('a_has_scope') : check #has_scope color
 PASS [ .red:has(#descendant:is(.a_has_scope .b)) ] #child.classList.add('a_has_scope') : check matches (true)
@@ -23,7 +23,7 @@
 PASS [ .orangered:has(#descendant:is(.a_descendant .b)) #descendant ] #parent.classList.remove('a_descendant') : check matches (false)
 PASS [ .orangered:has(#descendant:is(.a_descendant .b)) #descendant ] #parent.classList.remove('a_descendant') : check #descendant color
 PASS [ .orangered:has(#descendant:is(.a_descendant .b)) #descendant ] #has_scope.classList.add('a_descendant') : check matches (true)
-FAIL [ .orangered:has(#descendant:is(.a_descendant .b)) #descendant ] #has_scope.classList.add('a_descendant') : check #descendant color assert_equals: expected "rgb(255, 69, 0)" but got "rgb(128, 128, 128)"
+PASS [ .orangered:has(#descendant:is(.a_descendant .b)) #descendant ] #has_scope.classList.add('a_descendant') : check #descendant color
 PASS [ .orangered:has(#descendant:is(.a_descendant .b)) #descendant ] #has_scope.classList.remove('a_descendant') : check matches (false)
 PASS [ .orangered:has(#descendant:is(.a_descendant .b)) #descendant ] #has_scope.classList.remove('a_descendant') : check #descendant color
 PASS [ .orangered:has(#descendant:is(.a_descendant .b)) #descendant ] #child.classList.add('a_descendant') : check matches (true)
@@ -39,7 +39,7 @@
 PASS [ .darkred:has(#descendant:is(.a_indirect_next .b)) ~ #indirect_next ] #parent.classList.remove('a_indirect_next') : check matches (false)
 PASS [ .darkred:has(#descendant:is(.a_indirect_next .b)) ~ #indirect_next ] #parent.classList.remove('a_indirect_next') : check #indirect_next color
 PASS [ .darkred:has(#descendant:is(.a_indirect_next .b)) ~ #indirect_next ] #has_scope.classList.add('a_indirect_next') : check matches (true)
-FAIL [ .darkred:has(#descendant:is(.a_indirect_next .b)) ~ #indirect_next ] #has_scope.classList.add('a_indirect_next') : check #indirect_next color assert_equals: expected "rgb(139, 0, 0)" but got "rgb(128, 128, 128)"
+PASS [ .darkred:has(#descendant:is(.a_indirect_next .b)) ~ #indirect_next ] #has_scope.classList.add('a_indirect_next') : check #indirect_next color
 PASS [ .darkred:has(#descendant:is(.a_indirect_next .b)) ~ #indirect_next ] #has_scope.classList.remove('a_indirect_next') : check matches (false)
 PASS [ .darkred:has(#descendant:is(.a_indirect_next .b)) ~ #indirect_next ] #has_scope.classList.remove('a_indirect_next') : check #indirect_next color
 PASS [ .darkred:has(#descendant:is(.a_indirect_next .b)) ~ #indirect_next ] #child.classList.add('a_indirect_next') : check matches (true)
@@ -55,7 +55,7 @@
 PASS [ .pink:has(#descendant:is(.a_indirect_next_child .b)) ~ #indirect_next #indirect_next_child ] #parent.classList.remove('a_indirect_next_child') : check matches (false)
 PASS [ .pink:has(#descendant:is(.a_indirect_next_child .b)) ~ #indirect_next #indirect_next_child ] #parent.classList.remove('a_indirect_next_child') : check #indirect_next_child color
 PASS [ .pink:has(#descendant:is(.a_indirect_next_child .b)) ~ #indirect_next #indirect_next_child ] #has_scope.classList.add('a_indirect_next_child') : check matches (true)
-FAIL [ .pink:has(#descendant:is(.a_indirect_next_child .b)) ~ #indirect_next #indirect_next_child ] #has_scope.classList.add('a_indirect_next_child') : check #indirect_next_child color assert_equals: expected "rgb(255, 192, 203)" but got "rgb(128, 128, 128)"
+PASS [ .pink:has(#descendant:is(.a_indirect_next_child .b)) ~ #indirect_next #indirect_next_child ] #has_scope.classList.add('a_indirect_next_child') : check #indirect_next_child color
 PASS [ .pink:has(#descendant:is(.a_indirect_next_child .b)) ~ #indirect_next #indirect_next_child ] #has_scope.classList.remove('a_indirect_next_child') : check matches (false)
 PASS [ .pink:has(#descendant:is(.a_indirect_next_child .b)) ~ #indirect_next #indirect_next_child ] #has_scope.classList.remove('a_indirect_next_child') : check #indirect_next_child color
 PASS [ .pink:has(#descendant:is(.a_indirect_next_child .b)) ~ #indirect_next #indirect_next_child ] #child.classList.add('a_indirect_next_child') : check matches (true)
@@ -239,7 +239,7 @@
 PASS [ .blue:has(~ #indirect_next:is(.p + .f_has_scope ~ .g)) ] insert/remove .f_has_scope before #previous) : (removal) check matches (false)
 PASS [ .blue:has(~ #indirect_next:is(.p + .f_has_scope ~ .g)) ] insert/remove .f_has_scope before #previous) : (removal) check #has_scope color
 PASS [ .blue:has(~ #indirect_next:is(.p + .f_has_scope ~ .g)) ] #has_scope.classList.add('f_has_scope') : check matches (true)
-FAIL [ .blue:has(~ #indirect_next:is(.p + .f_has_scope ~ .g)) ] #has_scope.classList.add('f_has_scope') : check #has_scope color assert_equals: expected "rgb(0, 0, 255)" but got "rgb(128, 128, 128)"
+PASS [ .blue:has(~ #indirect_next:is(.p + .f_has_scope ~ .g)) ] #has_scope.classList.add('f_has_scope') : check #has_scope color
 PASS [ .blue:has(~ #indirect_next:is(.p + .f_has_scope ~ .g)) ] #has_scope.classList.remove('f_has_scope') : check matches (false)
 PASS [ .blue:has(~ #indirect_next:is(.p + .f_has_scope ~ .g)) ] #has_scope.classList.remove('f_has_scope') : check #has_scope color
 PASS [ .blue:has(~ #indirect_next:is(.p + .f_has_scope ~ .g)) ] #direct_next.classList.add('f_has_scope') : check matches (true)
@@ -271,7 +271,7 @@
 PASS [ .skyblue:has(~ #indirect_next:is(.p + .f_descendant ~ .g)) #descendant ] insert/remove .f_descendant before #previous) : (removal) check matches (false)
 PASS [ .skyblue:has(~ #indirect_next:is(.p + .f_descendant ~ .g)) #descendant ] insert/remove .f_descendant before #previous) : (removal) check #descendant color
 PASS [ .skyblue:has(~ #indirect_next:is(.p + .f_descendant ~ .g)) #descendant ] #has_scope.classList.add('f_descendant') : check matches (true)
-FAIL [ .skyblue:has(~ #indirect_next:is(.p + .f_descendant ~ .g)) #descendant ] #has_scope.classList.add('f_descendant') : check #descendant color assert_equals: expected "rgb(135, 206, 235)" but got "rgb(128, 128, 128)"
+PASS [ .skyblue:has(~ #indirect_next:is(.p + .f_descendant ~ .g)) #descendant ] #has_scope.classList.add('f_descendant') : check #descendant color
 PASS [ .skyblue:has(~ #indirect_next:is(.p + .f_descendant ~ .g)) #descendant ] #has_scope.classList.remove('f_descendant') : check matches (false)
 PASS [ .skyblue:has(~ #indirect_next:is(.p + .f_descendant ~ .g)) #descendant ] #has_scope.classList.remove('f_descendant') : check #descendant color
 PASS [ .skyblue:has(~ #indirect_next:is(.p + .f_descendant ~ .g)) #descendant ] #direct_next.classList.add('f_descendant') : check matches (true)
@@ -303,7 +303,7 @@
 PASS [ .lightblue:has(~ #indirect_next:is(.p + .f_indirect_next ~ .g)) ~ #indirect_next ] insert/remove .f_indirect_next before #previous) : (removal) check matches (false)
 PASS [ .lightblue:has(~ #indirect_next:is(.p + .f_indirect_next ~ .g)) ~ #indirect_next ] insert/remove .f_indirect_next before #previous) : (removal) check #indirect_next color
 PASS [ .lightblue:has(~ #indirect_next:is(.p + .f_indirect_next ~ .g)) ~ #indirect_next ] #has_scope.classList.add('f_indirect_next') : check matches (true)
-FAIL [ .lightblue:has(~ #indirect_next:is(.p + .f_indirect_next ~ .g)) ~ #indirect_next ] #has_scope.classList.add('f_indirect_next') : check #indirect_next color assert_equals: expected "rgb(173, 216, 230)" but got "rgb(128, 128, 128)"
+PASS [ .lightblue:has(~ #indirect_next:is(.p + .f_indirect_next ~ .g)) ~ #indirect_next ] #has_scope.classList.add('f_indirect_next') : check #indirect_next color
 PASS [ .lightblue:has(~ #indirect_next:is(.p + .f_indirect_next ~ .g)) ~ #indirect_next ] #has_scope.classList.remove('f_indirect_next') : check matches (false)
 PASS [ .lightblue:has(~ #indirect_next:is(.p + .f_indirect_next ~ .g)) ~ #indirect_next ] #has_scope.classList.remove('f_indirect_next') : check #indirect_next color
 PASS [ .lightblue:has(~ #indirect_next:is(.p + .f_indirect_next ~ .g)) ~ #indirect_next ] #direct_next.classList.add('f_indirect_next') : check matches (true)
@@ -335,7 +335,7 @@
 PASS [ .darkblue:has(~ #indirect_next:is(.p + .f_indirect_next_child ~ .g)) ~ #indirect_next #indirect_next_child ] insert/remove .f_indirect_next_child before #previous) : (removal) check matches (false)
 PASS [ .darkblue:has(~ #indirect_next:is(.p + .f_indirect_next_child ~ .g)) ~ #indirect_next #indirect_next_child ] insert/remove .f_indirect_next_child before #previous) : (removal) check #indirect_next_child color
 PASS [ .darkblue:has(~ #indirect_next:is(.p + .f_indirect_next_child ~ .g)) ~ #indirect_next #indirect_next_child ] #has_scope.classList.add('f_indirect_next_child') : check matches (true)
-FAIL [ .darkblue:has(~ #indirect_next:is(.p + .f_indirect_next_child ~ .g)) ~ #indirect_next #indirect_next_child ] #has_scope.classList.add('f_indirect_next_child') : check #indirect_next_child color assert_equals: expected "rgb(0, 0, 139)" but got "rgb(128, 128, 128)"
+PASS [ .darkblue:has(~ #indirect_next:is(.p + .f_indirect_next_child ~ .g)) ~ #indirect_next #indirect_next_child ] #has_scope.classList.add('f_indirect_next_child') : check #indirect_next_child color
 PASS [ .darkblue:has(~ #indirect_next:is(.p + .f_indirect_next_child ~ .g)) ~ #indirect_next #indirect_next_child ] #has_scope.classList.remove('f_indirect_next_child') : check matches (false)
 PASS [ .darkblue:has(~ #indirect_next:is(.p + .f_indirect_next_child ~ .g)) ~ #indirect_next #indirect_next_child ] #has_scope.classList.remove('f_indirect_next_child') : check #indirect_next_child color
 PASS [ .darkblue:has(~ #indirect_next:is(.p + .f_indirect_next_child ~ .g)) ~ #indirect_next #indirect_next_child ] #direct_next.classList.add('f_indirect_next_child') : check matches (true)
@@ -459,11 +459,11 @@
 PASS [ .orange:has(#descendant:is(:is(.m, .n) .o)) ] #parent.classList.remove('n') : check matches (false)
 PASS [ .orange:has(#descendant:is(:is(.m, .n) .o)) ] #parent.classList.remove('n') : check #has_scope color
 PASS [ .orange:has(#descendant:is(:is(.m, .n) .o)) ] #has_scope.classList.add('m') : check matches (true)
-FAIL [ .orange:has(#descendant:is(:is(.m, .n) .o)) ] #has_scope.classList.add('m') : check #has_scope color assert_equals: expected "rgb(255, 165, 0)" but got "rgb(128, 128, 128)"
+PASS [ .orange:has(#descendant:is(:is(.m, .n) .o)) ] #has_scope.classList.add('m') : check #has_scope color
 PASS [ .orange:has(#descendant:is(:is(.m, .n) .o)) ] #has_scope.classList.remove('m') : check matches (false)
 PASS [ .orange:has(#descendant:is(:is(.m, .n) .o)) ] #has_scope.classList.remove('m') : check #has_scope color
 PASS [ .orange:has(#descendant:is(:is(.m, .n) .o)) ] #has_scope.classList.add('n') : check matches (true)
-FAIL [ .orange:has(#descendant:is(:is(.m, .n) .o)) ] #has_scope.classList.add('n') : check #has_scope color assert_equals: expected "rgb(255, 165, 0)" but got "rgb(128, 128, 128)"
+PASS [ .orange:has(#descendant:is(:is(.m, .n) .o)) ] #has_scope.classList.add('n') : check #has_scope color
 PASS [ .orange:has(#descendant:is(:is(.m, .n) .o)) ] #has_scope.classList.remove('n') : check matches (false)
 PASS [ .orange:has(#descendant:is(:is(.m, .n) .o)) ] #has_scope.classList.remove('n') : check #has_scope color
 PASS [ .orange:has(#descendant:is(:is(.m, .n) .o)) ] #child.classList.add('m') : check matches (true)
diff --git a/third_party/blink/web_tests/platform/generic/external/wpt/css/selectors/invalidation/not-pseudo-containing-complex-in-has-expected.txt b/third_party/blink/web_tests/platform/generic/external/wpt/css/selectors/invalidation/not-pseudo-containing-complex-in-has-expected.txt
index a6665c6..3c63a01 100644
--- a/third_party/blink/web_tests/platform/generic/external/wpt/css/selectors/invalidation/not-pseudo-containing-complex-in-has-expected.txt
+++ b/third_party/blink/web_tests/platform/generic/external/wpt/css/selectors/invalidation/not-pseudo-containing-complex-in-has-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 460 tests; 394 PASS, 66 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 460 tests; 404 PASS, 56 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS [ .red:has(#descendant:not(.a_has_scope .b)) ] #has_scope.classList.add('red') : check matches (true)
 PASS [ .red:has(#descendant:not(.a_has_scope .b)) ] #has_scope.classList.add('red') : check #has_scope color
 PASS [ .red:has(#descendant:not(.a_has_scope .b)) ] #parent.classList.add('a_has_scope') : check matches (false)
@@ -7,7 +7,7 @@
 PASS [ .red:has(#descendant:not(.a_has_scope .b)) ] #parent.classList.remove('a_has_scope') : check matches (true)
 PASS [ .red:has(#descendant:not(.a_has_scope .b)) ] #parent.classList.remove('a_has_scope') : check #has_scope color
 PASS [ .red:has(#descendant:not(.a_has_scope .b)) ] #has_scope.classList.add('a_has_scope') : check matches (false)
-FAIL [ .red:has(#descendant:not(.a_has_scope .b)) ] #has_scope.classList.add('a_has_scope') : check #has_scope color assert_equals: expected "rgb(128, 128, 128)" but got "rgb(255, 0, 0)"
+PASS [ .red:has(#descendant:not(.a_has_scope .b)) ] #has_scope.classList.add('a_has_scope') : check #has_scope color
 PASS [ .red:has(#descendant:not(.a_has_scope .b)) ] #has_scope.classList.remove('a_has_scope') : check matches (true)
 PASS [ .red:has(#descendant:not(.a_has_scope .b)) ] #has_scope.classList.remove('a_has_scope') : check #has_scope color
 PASS [ .red:has(#descendant:not(.a_has_scope .b)) ] #child.classList.add('a_has_scope') : check matches (false)
@@ -23,7 +23,7 @@
 PASS [ .orangered:has(#descendant:not(.a_descendant .b)) #descendant ] #parent.classList.remove('a_descendant') : check matches (true)
 PASS [ .orangered:has(#descendant:not(.a_descendant .b)) #descendant ] #parent.classList.remove('a_descendant') : check #descendant color
 PASS [ .orangered:has(#descendant:not(.a_descendant .b)) #descendant ] #has_scope.classList.add('a_descendant') : check matches (false)
-FAIL [ .orangered:has(#descendant:not(.a_descendant .b)) #descendant ] #has_scope.classList.add('a_descendant') : check #descendant color assert_equals: expected "rgb(128, 128, 128)" but got "rgb(255, 69, 0)"
+PASS [ .orangered:has(#descendant:not(.a_descendant .b)) #descendant ] #has_scope.classList.add('a_descendant') : check #descendant color
 PASS [ .orangered:has(#descendant:not(.a_descendant .b)) #descendant ] #has_scope.classList.remove('a_descendant') : check matches (true)
 PASS [ .orangered:has(#descendant:not(.a_descendant .b)) #descendant ] #has_scope.classList.remove('a_descendant') : check #descendant color
 PASS [ .orangered:has(#descendant:not(.a_descendant .b)) #descendant ] #child.classList.add('a_descendant') : check matches (false)
@@ -39,7 +39,7 @@
 PASS [ .darkred:has(#descendant:not(.a_indirect_next .b)) ~ #indirect_next ] #parent.classList.remove('a_indirect_next') : check matches (true)
 PASS [ .darkred:has(#descendant:not(.a_indirect_next .b)) ~ #indirect_next ] #parent.classList.remove('a_indirect_next') : check #indirect_next color
 PASS [ .darkred:has(#descendant:not(.a_indirect_next .b)) ~ #indirect_next ] #has_scope.classList.add('a_indirect_next') : check matches (false)
-FAIL [ .darkred:has(#descendant:not(.a_indirect_next .b)) ~ #indirect_next ] #has_scope.classList.add('a_indirect_next') : check #indirect_next color assert_equals: expected "rgb(128, 128, 128)" but got "rgb(139, 0, 0)"
+PASS [ .darkred:has(#descendant:not(.a_indirect_next .b)) ~ #indirect_next ] #has_scope.classList.add('a_indirect_next') : check #indirect_next color
 PASS [ .darkred:has(#descendant:not(.a_indirect_next .b)) ~ #indirect_next ] #has_scope.classList.remove('a_indirect_next') : check matches (true)
 PASS [ .darkred:has(#descendant:not(.a_indirect_next .b)) ~ #indirect_next ] #has_scope.classList.remove('a_indirect_next') : check #indirect_next color
 PASS [ .darkred:has(#descendant:not(.a_indirect_next .b)) ~ #indirect_next ] #child.classList.add('a_indirect_next') : check matches (false)
@@ -55,7 +55,7 @@
 PASS [ .pink:has(#descendant:not(.a_indirect_next_child .b)) ~ #indirect_next #indirect_next_child ] #parent.classList.remove('a_indirect_next_child') : check matches (true)
 PASS [ .pink:has(#descendant:not(.a_indirect_next_child .b)) ~ #indirect_next #indirect_next_child ] #parent.classList.remove('a_indirect_next_child') : check #indirect_next_child color
 PASS [ .pink:has(#descendant:not(.a_indirect_next_child .b)) ~ #indirect_next #indirect_next_child ] #has_scope.classList.add('a_indirect_next_child') : check matches (false)
-FAIL [ .pink:has(#descendant:not(.a_indirect_next_child .b)) ~ #indirect_next #indirect_next_child ] #has_scope.classList.add('a_indirect_next_child') : check #indirect_next_child color assert_equals: expected "rgb(128, 128, 128)" but got "rgb(255, 192, 203)"
+PASS [ .pink:has(#descendant:not(.a_indirect_next_child .b)) ~ #indirect_next #indirect_next_child ] #has_scope.classList.add('a_indirect_next_child') : check #indirect_next_child color
 PASS [ .pink:has(#descendant:not(.a_indirect_next_child .b)) ~ #indirect_next #indirect_next_child ] #has_scope.classList.remove('a_indirect_next_child') : check matches (true)
 PASS [ .pink:has(#descendant:not(.a_indirect_next_child .b)) ~ #indirect_next #indirect_next_child ] #has_scope.classList.remove('a_indirect_next_child') : check #indirect_next_child color
 PASS [ .pink:has(#descendant:not(.a_indirect_next_child .b)) ~ #indirect_next #indirect_next_child ] #child.classList.add('a_indirect_next_child') : check matches (false)
@@ -235,7 +235,7 @@
 PASS [ .blue:has(~ #indirect_next:not(.p + .f_has_scope ~ .g)) ] insert/remove .f_has_scope before #previous) : (removal) check matches (true)
 PASS [ .blue:has(~ #indirect_next:not(.p + .f_has_scope ~ .g)) ] insert/remove .f_has_scope before #previous) : (removal) check #has_scope color
 PASS [ .blue:has(~ #indirect_next:not(.p + .f_has_scope ~ .g)) ] #has_scope.classList.add('f_has_scope') : check matches (false)
-FAIL [ .blue:has(~ #indirect_next:not(.p + .f_has_scope ~ .g)) ] #has_scope.classList.add('f_has_scope') : check #has_scope color assert_equals: expected "rgb(128, 128, 128)" but got "rgb(0, 0, 255)"
+PASS [ .blue:has(~ #indirect_next:not(.p + .f_has_scope ~ .g)) ] #has_scope.classList.add('f_has_scope') : check #has_scope color
 PASS [ .blue:has(~ #indirect_next:not(.p + .f_has_scope ~ .g)) ] #has_scope.classList.remove('f_has_scope') : check matches (true)
 PASS [ .blue:has(~ #indirect_next:not(.p + .f_has_scope ~ .g)) ] #has_scope.classList.remove('f_has_scope') : check #has_scope color
 PASS [ .blue:has(~ #indirect_next:not(.p + .f_has_scope ~ .g)) ] #direct_next.classList.add('f_has_scope') : check matches (false)
@@ -259,7 +259,7 @@
 PASS [ .skyblue:has(~ #indirect_next:not(.p + .f_descendant ~ .g)) #descendant ] insert/remove .f_descendant before #previous) : (removal) check matches (true)
 PASS [ .skyblue:has(~ #indirect_next:not(.p + .f_descendant ~ .g)) #descendant ] insert/remove .f_descendant before #previous) : (removal) check #descendant color
 PASS [ .skyblue:has(~ #indirect_next:not(.p + .f_descendant ~ .g)) #descendant ] #has_scope.classList.add('f_descendant') : check matches (false)
-FAIL [ .skyblue:has(~ #indirect_next:not(.p + .f_descendant ~ .g)) #descendant ] #has_scope.classList.add('f_descendant') : check #descendant color assert_equals: expected "rgb(128, 128, 128)" but got "rgb(135, 206, 235)"
+PASS [ .skyblue:has(~ #indirect_next:not(.p + .f_descendant ~ .g)) #descendant ] #has_scope.classList.add('f_descendant') : check #descendant color
 PASS [ .skyblue:has(~ #indirect_next:not(.p + .f_descendant ~ .g)) #descendant ] #has_scope.classList.remove('f_descendant') : check matches (true)
 PASS [ .skyblue:has(~ #indirect_next:not(.p + .f_descendant ~ .g)) #descendant ] #has_scope.classList.remove('f_descendant') : check #descendant color
 PASS [ .skyblue:has(~ #indirect_next:not(.p + .f_descendant ~ .g)) #descendant ] #direct_next.classList.add('f_descendant') : check matches (false)
@@ -287,7 +287,7 @@
 PASS [ .lightblue:has(~ #indirect_next:not(.p + .f_indirect_next ~ .g)) ~ #indirect_next ] insert/remove .f_indirect_next before #previous) : (removal) check matches (true)
 PASS [ .lightblue:has(~ #indirect_next:not(.p + .f_indirect_next ~ .g)) ~ #indirect_next ] insert/remove .f_indirect_next before #previous) : (removal) check #indirect_next color
 PASS [ .lightblue:has(~ #indirect_next:not(.p + .f_indirect_next ~ .g)) ~ #indirect_next ] #has_scope.classList.add('f_indirect_next') : check matches (false)
-FAIL [ .lightblue:has(~ #indirect_next:not(.p + .f_indirect_next ~ .g)) ~ #indirect_next ] #has_scope.classList.add('f_indirect_next') : check #indirect_next color assert_equals: expected "rgb(128, 128, 128)" but got "rgb(173, 216, 230)"
+PASS [ .lightblue:has(~ #indirect_next:not(.p + .f_indirect_next ~ .g)) ~ #indirect_next ] #has_scope.classList.add('f_indirect_next') : check #indirect_next color
 PASS [ .lightblue:has(~ #indirect_next:not(.p + .f_indirect_next ~ .g)) ~ #indirect_next ] #has_scope.classList.remove('f_indirect_next') : check matches (true)
 PASS [ .lightblue:has(~ #indirect_next:not(.p + .f_indirect_next ~ .g)) ~ #indirect_next ] #has_scope.classList.remove('f_indirect_next') : check #indirect_next color
 PASS [ .lightblue:has(~ #indirect_next:not(.p + .f_indirect_next ~ .g)) ~ #indirect_next ] #direct_next.classList.add('f_indirect_next') : check matches (false)
@@ -319,7 +319,7 @@
 PASS [ .darkblue:has(~ #indirect_next:not(.p + .f_indirect_next_child ~ .g)) ~ #indirect_next #indirect_next_child ] insert/remove .f_indirect_next_child before #previous) : (removal) check matches (true)
 PASS [ .darkblue:has(~ #indirect_next:not(.p + .f_indirect_next_child ~ .g)) ~ #indirect_next #indirect_next_child ] insert/remove .f_indirect_next_child before #previous) : (removal) check #indirect_next_child color
 PASS [ .darkblue:has(~ #indirect_next:not(.p + .f_indirect_next_child ~ .g)) ~ #indirect_next #indirect_next_child ] #has_scope.classList.add('f_indirect_next_child') : check matches (false)
-FAIL [ .darkblue:has(~ #indirect_next:not(.p + .f_indirect_next_child ~ .g)) ~ #indirect_next #indirect_next_child ] #has_scope.classList.add('f_indirect_next_child') : check #indirect_next_child color assert_equals: expected "rgb(128, 128, 128)" but got "rgb(0, 0, 139)"
+PASS [ .darkblue:has(~ #indirect_next:not(.p + .f_indirect_next_child ~ .g)) ~ #indirect_next #indirect_next_child ] #has_scope.classList.add('f_indirect_next_child') : check #indirect_next_child color
 PASS [ .darkblue:has(~ #indirect_next:not(.p + .f_indirect_next_child ~ .g)) ~ #indirect_next #indirect_next_child ] #has_scope.classList.remove('f_indirect_next_child') : check matches (true)
 PASS [ .darkblue:has(~ #indirect_next:not(.p + .f_indirect_next_child ~ .g)) ~ #indirect_next #indirect_next_child ] #has_scope.classList.remove('f_indirect_next_child') : check #indirect_next_child color
 PASS [ .darkblue:has(~ #indirect_next:not(.p + .f_indirect_next_child ~ .g)) ~ #indirect_next #indirect_next_child ] #direct_next.classList.add('f_indirect_next_child') : check matches (false)
@@ -443,11 +443,11 @@
 PASS [ .orange:has(#descendant:not(.m:not(.n) .o)) ] #parent.classList.remove('m') : check matches (true)
 PASS [ .orange:has(#descendant:not(.m:not(.n) .o)) ] #parent.classList.remove('m') : check #has_scope color
 PASS [ .orange:has(#descendant:not(.m:not(.n) .o)) ] #has_scope.classList.add('m') : check matches (false)
-FAIL [ .orange:has(#descendant:not(.m:not(.n) .o)) ] #has_scope.classList.add('m') : check #has_scope color assert_equals: expected "rgb(128, 128, 128)" but got "rgb(255, 165, 0)"
+PASS [ .orange:has(#descendant:not(.m:not(.n) .o)) ] #has_scope.classList.add('m') : check #has_scope color
 PASS [ .orange:has(#descendant:not(.m:not(.n) .o)) ] #has_scope.classList.add('n') : check matches (true)
 PASS [ .orange:has(#descendant:not(.m:not(.n) .o)) ] #has_scope.classList.add('n') : check #has_scope color
 PASS [ .orange:has(#descendant:not(.m:not(.n) .o)) ] #has_scope.classList.remove('n') : check matches (false)
-FAIL [ .orange:has(#descendant:not(.m:not(.n) .o)) ] #has_scope.classList.remove('n') : check #has_scope color assert_equals: expected "rgb(128, 128, 128)" but got "rgb(255, 165, 0)"
+PASS [ .orange:has(#descendant:not(.m:not(.n) .o)) ] #has_scope.classList.remove('n') : check #has_scope color
 PASS [ .orange:has(#descendant:not(.m:not(.n) .o)) ] #has_scope.classList.remove('m') : check matches (true)
 PASS [ .orange:has(#descendant:not(.m:not(.n) .o)) ] #has_scope.classList.remove('m') : check #has_scope color
 PASS [ .orange:has(#descendant:not(.m:not(.n) .o)) ] #child.classList.add('m') : check matches (false)
diff --git a/third_party/blink/web_tests/platform/generic/virtual/disable-ua-ch-platform/external/wpt/client-hints/accept-ch-stickiness/cross-origin-iframe-redirect-with-fp-delegation.https-expected.txt b/third_party/blink/web_tests/platform/generic/virtual/disable-ua-ch-platform/external/wpt/client-hints/accept-ch-stickiness/cross-origin-iframe-redirect-with-fp-delegation.https-expected.txt
deleted file mode 100644
index 54ace634..0000000
--- a/third_party/blink/web_tests/platform/generic/virtual/disable-ua-ch-platform/external/wpt/client-hints/accept-ch-stickiness/cross-origin-iframe-redirect-with-fp-delegation.https-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL Iframe redirect with Feature Policy delegation got client hints according to expectations. assert_equals: message from opened frame expected "PASS" but got "PLATFORM"
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/platform/generic/virtual/disable-ua-ch-platform/external/wpt/client-hints/accept-ch-stickiness/cross-origin-subresource-redirect-with-fp-delegation.https-expected.txt b/third_party/blink/web_tests/platform/generic/virtual/disable-ua-ch-platform/external/wpt/client-hints/accept-ch-stickiness/cross-origin-subresource-redirect-with-fp-delegation.https-expected.txt
deleted file mode 100644
index cea54bbdd..0000000
--- a/third_party/blink/web_tests/platform/generic/virtual/disable-ua-ch-platform/external/wpt/client-hints/accept-ch-stickiness/cross-origin-subresource-redirect-with-fp-delegation.https-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL cross-origin subresource redirect with Feature Policy delegation got client hints according to expectations. assert_true: expected true got false
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/platform/generic/virtual/disable-ua-ch-platform/external/wpt/client-hints/accept-ch-stickiness/same-origin-navigation-redirect.https-expected.txt b/third_party/blink/web_tests/platform/generic/virtual/disable-ua-ch-platform/external/wpt/client-hints/accept-ch-stickiness/same-origin-navigation-redirect.https-expected.txt
deleted file mode 100644
index e20ba533..0000000
--- a/third_party/blink/web_tests/platform/generic/virtual/disable-ua-ch-platform/external/wpt/client-hints/accept-ch-stickiness/same-origin-navigation-redirect.https-expected.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-This is a testharness.js-based test.
-PASS redirect on navigation precondition: Test that the browser does not have client hints preferences cached
-FAIL redirect on navigation got client hints according to expectations. assert_equals: message from opened page expected "PASS" but got "PLATFORM"
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/platform/generic/virtual/disable-ua-ch-platform/external/wpt/client-hints/accept-ch-stickiness/same-origin-subresource-redirect-opted-in.https-expected.txt b/third_party/blink/web_tests/platform/generic/virtual/disable-ua-ch-platform/external/wpt/client-hints/accept-ch-stickiness/same-origin-subresource-redirect-opted-in.https-expected.txt
deleted file mode 100644
index dad4461..0000000
--- a/third_party/blink/web_tests/platform/generic/virtual/disable-ua-ch-platform/external/wpt/client-hints/accept-ch-stickiness/same-origin-subresource-redirect-opted-in.https-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL same-origin subresource redirect with opt-in got client hints according to expectations. assert_true: expected true got false
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/platform/generic/virtual/disable-ua-ch-platform/external/wpt/client-hints/accept-ch/feature-policy-navigation/feature-policy.https-expected.txt b/third_party/blink/web_tests/platform/generic/virtual/disable-ua-ch-platform/external/wpt/client-hints/accept-ch/feature-policy-navigation/feature-policy.https-expected.txt
deleted file mode 100644
index 77f8b35..0000000
--- a/third_party/blink/web_tests/platform/generic/virtual/disable-ua-ch-platform/external/wpt/client-hints/accept-ch/feature-policy-navigation/feature-policy.https-expected.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-This is a testharness.js-based test.
-FAIL Client hints loaded on cross-origin iframe request with feature policy. promise_test: Unhandled rejection with value: "FAIL sec-ch-ua-platform True None"
-PASS Client hints loaded on same-origin iframe request with feature policy.
-FAIL Client hints loaded on cross-origin iframe request with feature policy after attempting to set independently. promise_test: Unhandled rejection with value: "FAIL sec-ch-ua-platform True None"
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/platform/generic/virtual/disable-ua-ch-platform/external/wpt/client-hints/accept-ch/feature-policy-navigation/no-feature-policy.https-expected.txt b/third_party/blink/web_tests/platform/generic/virtual/disable-ua-ch-platform/external/wpt/client-hints/accept-ch/feature-policy-navigation/no-feature-policy.https-expected.txt
deleted file mode 100644
index e553f8d..0000000
--- a/third_party/blink/web_tests/platform/generic/virtual/disable-ua-ch-platform/external/wpt/client-hints/accept-ch/feature-policy-navigation/no-feature-policy.https-expected.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-This is a testharness.js-based test.
-FAIL Client hints not loaded on cross-origin iframe request with no feature policy. promise_test: Unhandled rejection with value: "FAIL sec-ch-ua-platform True None"
-PASS Client hints loaded on same-origin iframe request with no feature policy.
-FAIL Client hints loaded on cross-origin iframe request with allow list. promise_test: Unhandled rejection with value: "FAIL sec-ch-ua-platform True None"
-PASS Client hints loaded on same-origin iframe request with allow list.
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/platform/generic/virtual/disable-ua-ch-platform/external/wpt/client-hints/http-equiv-accept-ch-iframe.https-expected.txt b/third_party/blink/web_tests/platform/generic/virtual/disable-ua-ch-platform/external/wpt/client-hints/http-equiv-accept-ch-iframe.https-expected.txt
deleted file mode 100644
index d9b3330..0000000
--- a/third_party/blink/web_tests/platform/generic/virtual/disable-ua-ch-platform/external/wpt/client-hints/http-equiv-accept-ch-iframe.https-expected.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-This is a testharness.js-based test.
-FAIL Client hints loaded on same-origin iframe should include hints with a default permissions policy ofself and *, but the http-equiv meta tag has a bug and it doesn't impact iframes. promise_test: Unhandled rejection with value: "FAIL sec-ch-ua-platform True None"
-FAIL Client hints loaded on cross-origin iframe only include hints with a default permissions policy of *. promise_test: Unhandled rejection with value: "FAIL sec-ch-ua-platform True None"
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/platform/generic/virtual/disable-ua-ch-platform/external/wpt/client-hints/meta-name-accept-ch-iframe.https-expected.txt b/third_party/blink/web_tests/platform/generic/virtual/disable-ua-ch-platform/external/wpt/client-hints/meta-name-accept-ch-iframe.https-expected.txt
deleted file mode 100644
index 76274f94..0000000
--- a/third_party/blink/web_tests/platform/generic/virtual/disable-ua-ch-platform/external/wpt/client-hints/meta-name-accept-ch-iframe.https-expected.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-This is a testharness.js-based test.
-FAIL Client hints loaded on same-origin iframe include hints with a default permissions policy of self and *. promise_test: Unhandled rejection with value: "FAIL sec-ch-ua-platform True None"
-FAIL Client hints loaded on cross-origin iframe only include hints with a default permissions policy of *. promise_test: Unhandled rejection with value: "FAIL sec-ch-ua-platform True None"
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/platform/generic/virtual/disable-ua-ch-platform/external/wpt/client-hints/sandbox/iframe-csp.https-expected.txt b/third_party/blink/web_tests/platform/generic/virtual/disable-ua-ch-platform/external/wpt/client-hints/sandbox/iframe-csp.https-expected.txt
deleted file mode 100644
index 6786739..0000000
--- a/third_party/blink/web_tests/platform/generic/virtual/disable-ua-ch-platform/external/wpt/client-hints/sandbox/iframe-csp.https-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL CSP sandboxed iframe does not send client hint headers assert_equals: message from opened frame expected "PASS" but got "FAIL sec-ch-ua-platform True None"
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/platform/generic/virtual/disable-ua-ch-platform/external/wpt/client-hints/sandbox/iframe.https-expected.txt b/third_party/blink/web_tests/platform/generic/virtual/disable-ua-ch-platform/external/wpt/client-hints/sandbox/iframe.https-expected.txt
deleted file mode 100644
index f11947c..0000000
--- a/third_party/blink/web_tests/platform/generic/virtual/disable-ua-ch-platform/external/wpt/client-hints/sandbox/iframe.https-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL sandboxed iframe does not send client hint headers assert_equals: message from opened frame expected "PASS" but got "FAIL sec-ch-ua-platform True None"
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/virtual/disable-ua-ch-platform/README.md b/third_party/blink/web_tests/virtual/disable-ua-ch-platform/README.md
deleted file mode 100644
index 37eafa5..0000000
--- a/third_party/blink/web_tests/virtual/disable-ua-ch-platform/README.md
+++ /dev/null
@@ -1,2 +0,0 @@
-This virtual test suite ensures the UACHPlatformEnabledByDefault feature
-disables correctly.
diff --git a/third_party/freetype/README.chromium b/third_party/freetype/README.chromium
index 27ad761..e6aee63 100644
--- a/third_party/freetype/README.chromium
+++ b/third_party/freetype/README.chromium
@@ -1,7 +1,7 @@
 Name: FreeType
 URL: http://www.freetype.org/
-Version: VER-2-12-1-12-gd68579812
-Revision: d6857981239ea5f6e95cb4eb4402307f3527760a
+Version: VER-2-12-1-13-gc26872ed5
+Revision: c26872ed59cba3af2f407b5eefc92fcec92aa52b
 CPEPrefix: cpe:/a:freetype:freetype:2.11.1
 License: Custom license "inspired by the BSD, Artistic, and IJG (Independent
          JPEG Group) licenses"
diff --git a/tools/attribution_reporting/simulator_main.cc b/tools/attribution_reporting/simulator_main.cc
index dc692a1..d852684 100644
--- a/tools/attribution_reporting/simulator_main.cc
+++ b/tools/attribution_reporting/simulator_main.cc
@@ -88,10 +88,10 @@
 is described below in detail.
 
 Learn more about the Attribution Reporting API at
-https://github.com/WICG/conversion-measurement-api#attribution-reporting-api.
+https://github.com/WICG/attribution-reporting-api#attribution-reporting-api.
 
 Learn about the meaning of the input and output fields at
-https://github.com/WICG/conversion-measurement-api/blob/main/EVENT.md.
+https://github.com/WICG/attribution-reporting-api/blob/main/EVENT.md.
 
 Switches:
   --copy_input_to_output    - Optional. If present, the input is copied to the
diff --git a/tools/clang/scripts/build.py b/tools/clang/scripts/build.py
index a2ec9e5..9313a100 100755
--- a/tools/clang/scripts/build.py
+++ b/tools/clang/scripts/build.py
@@ -1000,7 +1000,6 @@
     if args.build_mac_arm:
       assert platform.machine() != 'arm64', 'build_mac_arm for cross build only'
       cmake_args += ['-DCMAKE_OSX_ARCHITECTURES=arm64',
-                     '-DLLVM_USE_HOST_TOOLS=ON',
                      '-DCMAKE_SYSTEM_NAME=Darwin']
 
   # The default LLVM_DEFAULT_TARGET_TRIPLE depends on the host machine.
diff --git a/tools/fuchsia/size_tests/BUILD.gn b/tools/fuchsia/size_tests/BUILD.gn
index 5b9d723..8a0ffad 100644
--- a/tools/fuchsia/size_tests/BUILD.gn
+++ b/tools/fuchsia/size_tests/BUILD.gn
@@ -9,6 +9,6 @@
 compute_fuchsia_package_sizes("fuchsia_sizes") {
   data_deps = [
     "//fuchsia/engine:web_engine",
-    "//fuchsia/runners:cast_runner_pkg",
+    "//fuchsia_web/runners:cast_runner_pkg",
   ]
 }
diff --git a/tools/fuchsia/size_tests/fyi_sizes.json b/tools/fuchsia/size_tests/fyi_sizes.json
index c061514..56be7f07 100644
--- a/tools/fuchsia/size_tests/fyi_sizes.json
+++ b/tools/fuchsia/size_tests/fyi_sizes.json
@@ -1,7 +1,7 @@
 {
   "far_files" : [
     "gen/fuchsia/engine/web_engine/web_engine.far",
-    "gen/fuchsia/runners/cast_runner/cast_runner.far"
+    "gen/fuchsia_web/runners/cast_runner/cast_runner.far"
   ],
   "far_total_name" : "chrome_fuchsia",
   "size_limits" : {
diff --git a/tools/fuchsia/size_tests/fyi_sizes_smoketest.json b/tools/fuchsia/size_tests/fyi_sizes_smoketest.json
index c061514..56be7f07 100644
--- a/tools/fuchsia/size_tests/fyi_sizes_smoketest.json
+++ b/tools/fuchsia/size_tests/fyi_sizes_smoketest.json
@@ -1,7 +1,7 @@
 {
   "far_files" : [
     "gen/fuchsia/engine/web_engine/web_engine.far",
-    "gen/fuchsia/runners/cast_runner/cast_runner.far"
+    "gen/fuchsia_web/runners/cast_runner/cast_runner.far"
   ],
   "far_total_name" : "chrome_fuchsia",
   "size_limits" : {
diff --git a/tools/fuchsia/size_tests/fyi_sizes_warning.json b/tools/fuchsia/size_tests/fyi_sizes_warning.json
index b349273..ee605199 100644
--- a/tools/fuchsia/size_tests/fyi_sizes_warning.json
+++ b/tools/fuchsia/size_tests/fyi_sizes_warning.json
@@ -1,7 +1,7 @@
 {
   "far_files" : [
     "gen/fuchsia/engine/web_engine/web_engine.far",
-    "gen/fuchsia/runners/cast_runner/cast_runner.far"
+    "gen/fuchsia_web/runners/cast_runner/cast_runner.far"
   ],
   "far_total_name" : "chrome_fuchsia",
   "size_limits" : {
diff --git a/tools/metrics/actions/actions.xml b/tools/metrics/actions/actions.xml
index 53f4055..a2c7a18 100644
--- a/tools/metrics/actions/actions.xml
+++ b/tools/metrics/actions/actions.xml
@@ -27864,6 +27864,15 @@
   </description>
 </action>
 
+<action name="Signin_Impression_FromNTPFeedTopPromo">
+  <owner>mrefaat@google.com</owner>
+  <owner>sczs@chromium.org</owner>
+  <owner>feed@chromium.org</owner>
+  <description>
+    Recorded when showing sign in entry in the NTP feed top section promo.
+  </description>
+</action>
+
 <action name="Signin_Impression_FromPasswordBubble" not_user_triggered="true">
   <owner>gogerald@chromium.org</owner>
   <description>
@@ -28002,6 +28011,18 @@
   </description>
 </action>
 
+<action name="Signin_ImpressionWithAccount_FromNTPFeedTopPromo"
+    not_user_triggered="true">
+  <owner>mrefaat@chromium.org</owner>
+  <owner>sczs@chromium.org</owner>
+  <owner>jlebel@chromium.org</owner>
+  <description>
+    Recorded when starting sign-in using the promo view, with a default account,
+    from NTP Feed top section
+    (signin_metrics::AccessPoint::ACCESS_POINT_NTP_FEED_TOP_PROMO).
+  </description>
+</action>
+
 <action name="Signin_ImpressionWithAccount_FromPasswordBubble"
     not_user_triggered="true">
   <owner>msarda@chromium.org</owner>
@@ -28113,6 +28134,18 @@
   </description>
 </action>
 
+<action name="Signin_ImpressionWithNoAccount_FromNTPFeedTopPromo"
+    not_user_triggered="true">
+  <owner>mrefaat@chromium.org</owner>
+  <owner>jlebel@chromium.org</owner>
+  <owner>sczs@chromium.org</owner>
+  <description>
+    Recorded when starting sign-in using the promo view, with no default
+    account, from NTP content suggestions
+    (signin_metrics::AccessPoint::ACCESS_POINT_NTP_FEED_TOP_PROMO).
+  </description>
+</action>
+
 <action name="Signin_ImpressionWithNoAccount_FromPasswordBubble"
     not_user_triggered="true">
   <owner>msarda@chromium.org</owner>
@@ -28431,6 +28464,15 @@
   </description>
 </action>
 
+<action name="Signin_Signin_FromNTPFeedTopPromo">
+  <owner>jlebel@chromium.org</owner>
+  <owner>mrefaat@chromium.org</owner>
+  <description>
+    Recorded on sign in start from access point
+    signin_metrics::AccessPoint::ACCESS_POINT_NTP_FEED_TOP_PROMO.
+  </description>
+</action>
+
 <action name="Signin_Signin_FromPasswordBubble">
   <owner>gogerald@chromium.org</owner>
   <description>
@@ -28775,6 +28817,16 @@
   </description>
 </action>
 
+<action name="Signin_SigninNewAccountExistingAccount_FromNTPFeedTopPromo">
+  <owner>mrefaat@chromium.org</owner>
+  <owner>jlebel@chromium.org</owner>
+  <description>
+    Recorded on sign in start from access point
+    signin_metrics::AccessPoint::ACCESS_POINT_NTP_FEED_TOP_PROMO, with a new
+    account, while Chrome already has other accounts.
+  </description>
+</action>
+
 <action name="Signin_SigninNewAccountExistingAccount_FromPasswordBubble">
   <owner>bsazonov@chromium.org</owner>
   <owner>msarda@chromium.org</owner>
@@ -28898,6 +28950,17 @@
   </description>
 </action>
 
+<action name="Signin_SigninNewAccountNoExistingAccount_FromNTPFeedTopPromo">
+  <owner>mrefaat@chromium.org</owner>
+  <owner>sczs@chromium.org</owner>
+  <owner>jlebel@chromium.org</owner>
+  <description>
+    Recorded on sign in start from access point
+    signin_metrics::AccessPoint::ACCESS_POINT_NTP_FEED_TOP_PROMO, with a new
+    account, while Chrome does not have other accounts.
+  </description>
+</action>
+
 <action name="Signin_SigninNewAccountNoExistingAccount_FromPasswordBubble">
   <owner>bsazonov@chromium.org</owner>
   <owner>msarda@chromium.org</owner>
@@ -29146,6 +29209,17 @@
   </description>
 </action>
 
+<action name="Signin_SigninNotDefault_FromNTPFeedTopPromo">
+  <owner>mrefaat@chromium.org</owner>
+  <owner>sczs@chromium.org</owner>
+  <owner>jlebel@chromium.org</owner>
+  <description>
+    Recorded on sign in start from access point
+    signin_metrics::AccessPoint::ACCESS_POINT_NTP_FEED_TOP_PROMO, using another
+    account than the default one.
+  </description>
+</action>
+
 <action name="Signin_SigninNotDefault_FromPasswordBubble">
   <owner>bsazonov@chromium.org</owner>
   <owner>msarda@chromium.org</owner>
@@ -29274,6 +29348,17 @@
   </description>
 </action>
 
+<action name="Signin_SigninWithDefault_FromNTPFeedTopPromo">
+  <owner>mrefaat@chromium.org</owner>
+  <owner>sczs@chromium.org</owner>
+  <owner>jlebel@chromium.org</owner>
+  <description>
+    Recorded on sign in start from access point
+    signin_metrics::AccessPoint::ACCESS_POINT_NTP_FEED_TOP_PROMO, using the
+    default account.
+  </description>
+</action>
+
 <action name="Signin_SigninWithDefault_FromPasswordBubble">
   <owner>bsazonov@chromium.org</owner>
   <owner>msarda@chromium.org</owner>
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 0528c3a..cd0306a 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -10790,16 +10790,6 @@
   <int value="1" label="Ignored due to multiple pages in the renderer process"/>
 </enum>
 
-<enum name="BlobBrokenReason">
-  <int value="0" label="Unknown"/>
-  <int value="1" label="There is not enough memory to store this blob"/>
-  <int value="2" label="File write failed"/>
-  <int value="3" label="Source died in transit"/>
-  <int value="4" label="Blob dereferenced while building"/>
-  <int value="5" label="Referenced blob broken"/>
-  <int value="6" label="Referenced file unavailable"/>
-</enum>
-
 <enum name="BlobBuildFromStreamResult">
   <int value="0" label="Success"/>
   <int value="1" label="Aborted while building"/>
@@ -55605,6 +55595,7 @@
       label="EnableBluetoothVerboseLogsForGooglers:disabled"/>
   <int value="-1491417046" label="enable-fullscreen-toolbar-reveal"/>
   <int value="-1491304576" label="ProgressBarThrottle:disabled"/>
+  <int value="-1490827023" label="EnableCbdSignOut:disabled"/>
   <int value="-1490298774" label="enable-captive-portal-bypass-proxy-option"/>
   <int value="-1490048536"
       label="PageVisibilityPageContentAnnotations:disabled"/>
@@ -57235,6 +57226,7 @@
   <int value="-454362199" label="HelpAppV2:disabled"/>
   <int value="-450976085"
       label="AutofillSaveCreditCardUsesImprovedMessaging:disabled"/>
+  <int value="-450917820" label="EnableCbdSignOut:enabled"/>
   <int value="-450100254" label="OverviewButton:enabled"/>
   <int value="-449465495" label="disable-browser-task-scheduler"/>
   <int value="-448929520" label="TranslateMessageUI:enabled"/>
@@ -80418,11 +80410,6 @@
   <int value="3" label="Last redirect in URL chain"/>
 </enum>
 
-<enum name="RefcountOperation">
-  <int value="0" label="Decrement"/>
-  <int value="1" label="Increment"/>
-</enum>
-
 <enum name="RefineActionUsage">
   <int value="0" label="Not used"/>
   <int value="1" label="Used on Search Suggestion in zero-prefix context"/>
@@ -86592,6 +86579,7 @@
   <int value="34" label="Enterprise sign-out coordinator"/>
   <int value="35" label="Signin intercept first run experience"/>
   <int value="36" label="Send-tab-to-self promo"/>
+  <int value="37" label="Sign-in promo on NTP feed top section."/>
 </enum>
 
 <enum name="SigninAccountEquality">
diff --git a/tools/metrics/histograms/metadata/android/histograms.xml b/tools/metrics/histograms/metadata/android/histograms.xml
index d69bc0f..b1caf616 100644
--- a/tools/metrics/histograms/metadata/android/histograms.xml
+++ b/tools/metrics/histograms/metadata/android/histograms.xml
@@ -3532,7 +3532,7 @@
 </histogram>
 
 <histogram name="Android.WebView.DarkMode.ForceDarkBehavior"
-    enum="WebViewForceDarkBehavior" expires_after="2022-07-24">
+    enum="WebViewForceDarkBehavior" expires_after="2023-06-01">
   <owner>michaelbai@chromium.org</owner>
   <owner>src/android_webview/OWNERS</owner>
   <summary>
@@ -3542,7 +3542,7 @@
 </histogram>
 
 <histogram name="Android.WebView.DarkMode.ForceDarkMode"
-    enum="WebViewForceDarkMode2" expires_after="2022-07-24">
+    enum="WebViewForceDarkMode2" expires_after="2023-06-01">
   <owner>michaelbai@chromium.org</owner>
   <owner>src/android_webview/OWNERS</owner>
   <summary>
@@ -3552,7 +3552,7 @@
 </histogram>
 
 <histogram name="Android.WebView.DarkMode.InDarkMode" enum="Boolean"
-    expires_after="2022-07-24">
+    expires_after="2023-06-01">
   <owner>michaelbai@chromium.org</owner>
   <owner>src/android_webview/OWNERS</owner>
   <summary>
@@ -3562,7 +3562,7 @@
 </histogram>
 
 <histogram name="Android.WebView.DarkMode.InDarkModeVsLightTheme"
-    enum="WebViewInDarkModeVsLightTheme" expires_after="2022-07-24">
+    enum="WebViewInDarkModeVsLightTheme" expires_after="2023-06-01">
   <owner>michaelbai@chromium.org</owner>
   <owner>src/android_webview/OWNERS</owner>
   <summary>
@@ -3572,7 +3572,7 @@
 </histogram>
 
 <histogram name="Android.WebView.DarkMode.InDarkModeVsNightMode"
-    enum="WebViewInDarkModeVsNightMode" expires_after="2022-07-24">
+    enum="WebViewInDarkModeVsNightMode" expires_after="2023-06-01">
   <owner>michaelbai@chromium.org</owner>
   <owner>src/android_webview/OWNERS</owner>
   <summary>
@@ -3582,7 +3582,7 @@
 </histogram>
 
 <histogram name="Android.WebView.DarkMode.LightTheme" enum="LightTheme"
-    expires_after="2022-07-24">
+    expires_after="2023-06-01">
   <owner>michaelbai@chromium.org</owner>
   <owner>src/android_webview/OWNERS</owner>
   <summary>
@@ -3592,7 +3592,7 @@
 </histogram>
 
 <histogram name="Android.WebView.DarkMode.NightMode" enum="NightMode"
-    expires_after="2022-07-24">
+    expires_after="2023-06-01">
   <owner>michaelbai@chromium.org</owner>
   <owner>src/android_webview/OWNERS</owner>
   <summary>
@@ -3603,7 +3603,7 @@
 </histogram>
 
 <histogram name="Android.WebView.DarkMode.NightModeVsLightTheme"
-    enum="WebViewNightModeVsLightTheme" expires_after="2022-07-24">
+    enum="WebViewNightModeVsLightTheme" expires_after="2023-06-01">
   <owner>michaelbai@chromium.org</owner>
   <owner>src/android_webview/OWNERS</owner>
   <summary>
@@ -3613,7 +3613,7 @@
 </histogram>
 
 <histogram name="Android.WebView.DarkMode.PageDarkenedAccordingToAppTheme"
-    enum="Boolean" expires_after="2022-06-24">
+    enum="Boolean" expires_after="2023-06-01">
   <owner>michaelbai@chromium.org</owner>
   <owner>src/android_webview/OWNERS</owner>
   <summary>
@@ -3625,7 +3625,7 @@
 </histogram>
 
 <histogram name="Android.WebView.DarkMode.PrefersDarkFromTheme"
-    enum="BooleanYesNo" expires_after="2022-07-24">
+    enum="BooleanYesNo" expires_after="2023-06-01">
   <owner>michaelbai@chromium.org</owner>
   <owner>src/android_webview/OWNERS</owner>
   <summary>
@@ -3636,7 +3636,7 @@
 </histogram>
 
 <histogram name="Android.WebView.DarkMode.PrimaryTextLuminanceVsLightTheme"
-    enum="WebViewPrimaryTextLuminanceVsLightTheme" expires_after="2022-07-24">
+    enum="WebViewPrimaryTextLuminanceVsLightTheme" expires_after="2023-06-01">
   <owner>michaelbai@chromium.org</owner>
   <owner>src/android_webview/OWNERS</owner>
   <summary>
@@ -3647,7 +3647,7 @@
 </histogram>
 
 <histogram name="Android.WebView.DarkMode.PrimaryTextLuminanceVsNightMode"
-    enum="WebViewPrimaryTextLuminanceVsNightMode" expires_after="2022-07-24">
+    enum="WebViewPrimaryTextLuminanceVsNightMode" expires_after="2023-06-01">
   <owner>michaelbai@chromium.org</owner>
   <owner>src/android_webview/OWNERS</owner>
   <summary>
@@ -3844,7 +3844,7 @@
 </histogram>
 
 <histogram name="Android.WebView.ForceDarkBehavior"
-    enum="WebViewForceDarkBehavior" expires_after="2022-06-03">
+    enum="WebViewForceDarkBehavior" expires_after="2023-06-01">
   <owner>peter@chromium.org</owner>
   <owner>src/android_webview/OWNERS</owner>
   <summary>
@@ -3854,7 +3854,7 @@
 </histogram>
 
 <histogram name="Android.WebView.ForceDarkMode" enum="WebViewForceDarkMode"
-    expires_after="2022-06-03">
+    expires_after="2023-06-01">
   <owner>peter@chromium.org</owner>
   <owner>src/android_webview/OWNERS</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/autofill/histograms.xml b/tools/metrics/histograms/metadata/autofill/histograms.xml
index d2e8890d..88a1380 100644
--- a/tools/metrics/histograms/metadata/autofill/histograms.xml
+++ b/tools/metrics/histograms/metadata/autofill/histograms.xml
@@ -3959,14 +3959,14 @@
 </histogram>
 
 <histogram name="Autofill.WebView.AutofillSession" enum="AutofillSessionStates"
-    expires_after="2022-06-08">
+    expires_after="2023-06-01">
   <owner>michaelbai@chromium.org</owner>
   <owner>src/android_webview/OWNERS</owner>
   <summary>Records the state of an autofill session.</summary>
 </histogram>
 
 <histogram name="Autofill.WebView.AwGIsCurrentService" enum="BooleanYesNo"
-    expires_after="2022-06-08">
+    expires_after="2023-06-01">
   <owner>michaelbai@chromium.org</owner>
   <owner>src/android_webview/OWNERS</owner>
   <summary>
@@ -3977,14 +3977,14 @@
 </histogram>
 
 <histogram name="Autofill.WebView.CreatedByActivityContext"
-    enum="BooleanEnabled" expires_after="2022-06-08">
+    enum="BooleanEnabled" expires_after="2023-06-01">
   <owner>michaelbai@chromium.org</owner>
   <owner>src/android_webview/OWNERS</owner>
   <summary>Whether the autofill is created by activity context.</summary>
 </histogram>
 
 <histogram name="Autofill.WebView.Enabled" enum="BooleanEnabled"
-    expires_after="2022-06-08">
+    expires_after="2023-06-01">
   <owner>michaelbai@chromium.org</owner>
   <owner>src/android_webview/OWNERS</owner>
   <summary>
@@ -3993,7 +3993,7 @@
 </histogram>
 
 <histogram name="Autofill.WebView.ServerPrediction.AwGSuggestionAvailability"
-    enum="AutofillAwGSuggestionAvailability" expires_after="2022-06-08">
+    enum="AutofillAwGSuggestionAvailability" expires_after="2023-06-01">
   <owner>michaelbai@chromium.org</owner>
   <owner>src/android_webview/OWNERS</owner>
   <summary>
@@ -4004,7 +4004,7 @@
 </histogram>
 
 <histogram name="Autofill.WebView.ServerPredicton.HasValidServerPrediction"
-    enum="BooleanYesNo" expires_after="2022-06-08">
+    enum="BooleanYesNo" expires_after="2023-06-01">
   <owner>michaelbai@chromium.org</owner>
   <owner>src/android_webview/OWNERS</owner>
   <summary>
@@ -4016,7 +4016,7 @@
 </histogram>
 
 <histogram name="Autofill.WebView.ServerPredicton.PredictionAvailability"
-    enum="AutofillServerPredictionAvailability" expires_after="2022-06-08">
+    enum="AutofillServerPredictionAvailability" expires_after="2023-06-01">
   <owner>michaelbai@chromium.org</owner>
   <owner>src/android_webview/OWNERS</owner>
   <summary>
@@ -4029,21 +4029,21 @@
 </histogram>
 
 <histogram name="Autofill.WebView.SubmissionSource"
-    enum="AutofillSubmissionSource" expires_after="2022-06-08">
+    enum="AutofillSubmissionSource" expires_after="2023-06-01">
   <owner>michaelbai@chromium.org</owner>
   <owner>src/android_webview/OWNERS</owner>
   <summary>Records the source of form submission.</summary>
 </histogram>
 
 <histogram name="Autofill.WebView.SuggestionTime" units="ms"
-    expires_after="2022-06-08">
+    expires_after="2023-06-01">
   <owner>michaelbai@chromium.org</owner>
   <owner>src/android_webview/OWNERS</owner>
   <summary>The time taken to display suggestion.</summary>
 </histogram>
 
 <histogram name="Autofill.WebView.UserChangedAutofilledField"
-    enum="BooleanEnabled" expires_after="2022-06-08">
+    enum="BooleanEnabled" expires_after="2023-06-01">
   <owner>michaelbai@chromium.org</owner>
   <owner>src/android_webview/OWNERS</owner>
   <summary>Whether the user changed autofilled field.</summary>
diff --git a/tools/metrics/histograms/metadata/mobile/histograms.xml b/tools/metrics/histograms/metadata/mobile/histograms.xml
index fc8f58d..79aefca 100644
--- a/tools/metrics/histograms/metadata/mobile/histograms.xml
+++ b/tools/metrics/histograms/metadata/mobile/histograms.xml
@@ -1112,6 +1112,41 @@
   </summary>
 </histogram>
 
+<histogram name="MobileSignInPromo.NTPFeedTop.ImpressionsTilDismiss"
+    units="impressions" expires_after="2023-06-01">
+  <owner>mrefaat@chromium.org</owner>
+  <owner>jlebel@chromium.org</owner>
+  <owner>chrome-signin-team@google.com</owner>
+  <summary>
+    Counts how many times the signin promo is implicitly dismissed (by leaving
+    the NTP) per impression of the NTP feed top section sign in promo.
+  </summary>
+</histogram>
+
+<histogram name="MobileSignInPromo.NTPFeedTop.ImpressionsTilSigninButtons"
+    units="impressions" expires_after="2023-06-01">
+  <owner>mrefaat@chromium.org</owner>
+  <owner>jlebel@chromium.org</owner>
+  <owner>chrome-signin-team@google.com</owner>
+  <summary>
+    Counts how many times one of the &quot;sign in&quot; buttons (any of the
+    signed-out &quot;Sign in to Chrome&quot; button, the &quot;Continue as
+    |name|&quot; button, or the &quot;Not |email|?&quot; button) is clicked per
+    impression of the NTP feed top section sign in promo.
+  </summary>
+</histogram>
+
+<histogram name="MobileSignInPromo.NTPFeedTop.ImpressionsTilXButton"
+    units="impressions" expires_after="2023-06-01">
+  <owner>mrefaat@chromium.org</owner>
+  <owner>jlebel@chromium.org</owner>
+  <owner>chrome-signin-team@google.com</owner>
+  <summary>
+    Counts how many times the explicit &quot;X&quot;-to-close button is clicked
+    per impression of the NTP feed top section sign in promo.
+  </summary>
+</histogram>
+
 <histogram name="MobileSignInPromo.SettingsManager.ImpressionsTilDismiss"
     units="impressions" expires_after="2023-04-01">
   <owner>jlebel@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/net/histograms.xml b/tools/metrics/histograms/metadata/net/histograms.xml
index 4ed61f443..a2458af 100644
--- a/tools/metrics/histograms/metadata/net/histograms.xml
+++ b/tools/metrics/histograms/metadata/net/histograms.xml
@@ -4672,12 +4672,14 @@
 </histogram>
 
 <histogram name="Net.SSLKeyLogFileUse" enum="SSLKeyLogFileAction"
-    expires_after="M92">
+    expires_after="M110">
   <owner>cthomp@chromium.org</owner>
-  <owner>security-enamel@chromium.org</owner>
+  <owner>trusty-transport@chromium.org</owner>
   <summary>
     Counts when the SSLKEYLOGFILE environment variable or --ssl-key-log-file
     command-line flag are set, and when they enable the SSLKeyLogger feature.
+
+    This histogram was temporarily expired from M92 until M104.
   </summary>
 </histogram>
 
diff --git a/tools/metrics/histograms/metadata/others/histograms.xml b/tools/metrics/histograms/metadata/others/histograms.xml
index aac2316..e3180a9 100644
--- a/tools/metrics/histograms/metadata/others/histograms.xml
+++ b/tools/metrics/histograms/metadata/others/histograms.xml
@@ -3640,7 +3640,7 @@
   <owner>csharrison@chromium.org</owner>
   <summary>
     Records the number of registered conversions
-    (https://github.com/WICG/conversion-measurement-api) on a given top level
+    (https://github.com/WICG/attribution-reporting-api) on a given top level
     page load. Recorded when the page navigates away or is otherwise closed.
     Only recorded for non-off-the-record profiles (OTR profiles have the feature
     disabled).
diff --git a/tools/metrics/histograms/metadata/safe_browsing/histograms.xml b/tools/metrics/histograms/metadata/safe_browsing/histograms.xml
index 44c79260..4414220 100644
--- a/tools/metrics/histograms/metadata/safe_browsing/histograms.xml
+++ b/tools/metrics/histograms/metadata/safe_browsing/histograms.xml
@@ -1973,16 +1973,6 @@
   </summary>
 </histogram>
 
-<histogram name="SafeBrowsing.TailoredSecurity.SyncPromptSkippedAlreadyEnabled"
-    enum="BooleanSkipped" expires_after="2022-10-04">
-  <owner>drubery@chromium.org</owner>
-  <owner>chrome-safebrowsing-alerts@google.com</owner>
-  <summary>
-    Records whether a sync prompt was skipped because the user had already
-    consented to Enhanced Safe Browsing.
-  </summary>
-</histogram>
-
 <histogram
     name="SafeBrowsing.TailoredSecurityConsented{Status}{PromptType}Outcome"
     enum="SafeBrowsingTailoredSecurityOutcome" expires_after="2022-11-22">
diff --git a/tools/metrics/histograms/metadata/search/histograms.xml b/tools/metrics/histograms/metadata/search/histograms.xml
index 708a242..0c48e49 100644
--- a/tools/metrics/histograms/metadata/search/histograms.xml
+++ b/tools/metrics/histograms/metadata/search/histograms.xml
@@ -154,7 +154,10 @@
 </histogram>
 
 <histogram name="Search.ContextualSearch.Ranker.NotSuppressed.ResultsSeen"
-    enum="ContextualSearchResultsSeen" expires_after="M77">
+    enum="ContextualSearchResultsSeen" expires_after="M104">
+  <obsolete>
+    Removed 06/2022
+  </obsolete>
   <owner>donnd@chromium.org</owner>
   <owner>twellington@chromium.org</owner>
   <summary>
@@ -164,7 +167,10 @@
 </histogram>
 
 <histogram name="Search.ContextualSearch.Ranker.Recorded" enum="Boolean"
-    expires_after="M77">
+    expires_after="M104">
+  <obsolete>
+    Removed 06/2022
+  </obsolete>
   <owner>donnd@chromium.org</owner>
   <owner>twellington@chromium.org</owner>
   <summary>
@@ -185,7 +191,10 @@
 </histogram>
 
 <histogram name="Search.ContextualSearch.Ranker.Suppressed" enum="Boolean"
-    expires_after="M85">
+    expires_after="M104">
+  <obsolete>
+    Removed 06/2022
+  </obsolete>
   <owner>donnd@chromium.org</owner>
   <owner>twellington@chromium.org</owner>
   <summary>
@@ -234,7 +243,10 @@
 </histogram>
 
 <histogram name="Search.ContextualSearch.Ranker.WasAbleToPredict"
-    enum="Boolean" expires_after="M77">
+    enum="Boolean" expires_after="M104">
+  <obsolete>
+    Removed 06/2022
+  </obsolete>
   <owner>donnd@chromium.org</owner>
   <owner>twellington@chromium.org</owner>
   <summary>
@@ -244,7 +256,10 @@
 </histogram>
 
 <histogram name="Search.ContextualSearch.Ranker.WouldSuppress.ResultsSeen"
-    enum="ContextualSearchResultsSeen" expires_after="M77">
+    enum="ContextualSearchResultsSeen" expires_after="M104">
+  <obsolete>
+    Removed 06/2022
+  </obsolete>
   <owner>donnd@chromium.org</owner>
   <owner>twellington@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/storage/histograms.xml b/tools/metrics/histograms/metadata/storage/histograms.xml
index cbd62fc..81064b3 100644
--- a/tools/metrics/histograms/metadata/storage/histograms.xml
+++ b/tools/metrics/histograms/metadata/storage/histograms.xml
@@ -224,38 +224,6 @@
   </summary>
 </histogram>
 
-<histogram name="Storage.Blob.Broken" enum="BooleanBroken" expires_after="M95">
-  <owner>mek@chromium.org</owner>
-  <owner>dmurph@chromium.org</owner>
-  <summary>
-    If a newly constructed blob is broken. See Storage.Blob.BrokenReason for a
-    the broken reasons.
-  </summary>
-</histogram>
-
-<histogram name="Storage.Blob.BrokenReason" enum="BlobBrokenReason"
-    expires_after="M95">
-  <owner>mek@chromium.org</owner>
-  <owner>dmurph@chromium.org</owner>
-  <summary>
-    The reason a blob is broken, reported only for broken blobs upon
-    construction. See Storage.Blob.Broken for the breakdown of blobs broken vs
-    unbroken.
-  </summary>
-</histogram>
-
-<histogram name="Storage.Blob.CleanupSuccess" enum="Boolean"
-    expires_after="M95">
-  <owner>mek@chromium.org</owner>
-  <owner>dmurph@chromium.org</owner>
-  <summary>
-    Recorded when the old blob storage directories are cleared. This occurs on
-    storage partition initialization, and is not recorded if there are no
-    directories to clear. The value indicates if the file operations were a
-    success.
-  </summary>
-</histogram>
-
 <histogram name="Storage.Blob.DataURLWorkerRegister" enum="Boolean"
     expires_after="M106">
   <owner>awillia@chromium.org</owner>
@@ -309,42 +277,7 @@
   <owner>dmurph@chromium.org</owner>
   <summary>
     The error code reported by the blob system while trying to read a blob in
-    the FileReaderLoader. Compare with the
-    Storage.Blob.IDBRequestLoader.ReadError histogram to eliminate IndexedDB
-    large-value blobs.
-  </summary>
-</histogram>
-
-<histogram name="Storage.Blob.IDBRequestLoader.ReadError" enum="NetErrorCodes"
-    expires_after="M95">
-  <owner>mek@chromium.org</owner>
-  <owner>dmurph@chromium.org</owner>
-  <summary>
-    The error code reported by the blob system while trying to read an IndexedDB
-    large-value blob in the IDBRequestLoader. These blobs are automatically
-    created in Blink when a website writes a large value to IndexedDB.
-  </summary>
-</histogram>
-
-<histogram name="Storage.Blob.InvalidReference" enum="RefcountOperation"
-    expires_after="M95">
-  <owner>mek@chromium.org</owner>
-  <owner>dmurph@chromium.org</owner>
-  <summary>
-    Counts the number of times we have an invalid refcount operation. An invalid
-    increment means the blob didn't exist, and an invalid decrement means we
-    don't have any record of the blob in our host.
-  </summary>
-</histogram>
-
-<histogram name="Storage.Blob.InvalidURLRegister" enum="RefcountOperation"
-    expires_after="M95">
-  <owner>mek@chromium.org</owner>
-  <owner>dmurph@chromium.org</owner>
-  <summary>
-    Counts the number of times we have an invalid url registration operation. An
-    invalid increment means the blob isn't in use by the host yet or the url is
-    already mapped. An invalid decrement means the url isn't registered.
+    the FileReaderLoader.
   </summary>
 </histogram>
 
diff --git a/tools/metrics/histograms/metadata/tab/histograms.xml b/tools/metrics/histograms/metadata/tab/histograms.xml
index 79a4c31..4097711d 100644
--- a/tools/metrics/histograms/metadata/tab/histograms.xml
+++ b/tools/metrics/histograms/metadata/tab/histograms.xml
@@ -2025,7 +2025,7 @@
 </histogram>
 
 <histogram name="Tabs.SavedTabLoadTime.{SavedTabMethod}.{SavedTabLoadResult}"
-    units="ms" expires_after="2022-07-11">
+    units="ms" expires_after="2023-05-30">
   <owner>davidjm@chromium.org</owner>
   <owner>nyquist@chromium.org</owner>
   <owner>dtrainor@chromium.org</owner>
diff --git a/tools/traffic_annotation/summary/annotations.xml b/tools/traffic_annotation/summary/annotations.xml
index e1001ca..f1410b1e 100644
--- a/tools/traffic_annotation/summary/annotations.xml
+++ b/tools/traffic_annotation/summary/annotations.xml
@@ -11,8 +11,8 @@
  <item id="accounts_image_fetcher" added_in_milestone="66" content_hash_code="02b53da6" os_list="linux,windows,chromeos,android" file_path="components/signin/internal/identity_manager/account_fetcher_service.cc" />
  <item id="adb_client_socket" added_in_milestone="65" content_hash_code="03607bec" os_list="linux,windows,chromeos" file_path="chrome/browser/devtools/device/adb/adb_client_socket.cc" />
  <item id="affiliation_lookup_by_hash" added_in_milestone="87" content_hash_code="026afe84" os_list="linux,windows,chromeos,android" file_path="components/password_manager/core/browser/site_affiliation/hash_affiliation_fetcher.cc" />
- <item id="aggregation_service_helper_keys" added_in_milestone="96" content_hash_code="04127781" os_list="linux,windows,chromeos,android" file_path="content/browser/aggregation_service/aggregation_service_network_fetcher_impl.cc" />
- <item id="aggregation_service_report" added_in_milestone="95" content_hash_code="02a9ab82" os_list="linux,windows,chromeos,android" file_path="content/browser/aggregation_service/aggregatable_report_sender.cc" />
+ <item id="aggregation_service_helper_keys" added_in_milestone="96" content_hash_code="006e2ea0" os_list="linux,windows,chromeos,android" file_path="content/browser/aggregation_service/aggregation_service_network_fetcher_impl.cc" />
+ <item id="aggregation_service_report" added_in_milestone="95" content_hash_code="037768da" os_list="linux,windows,chromeos,android" file_path="content/browser/aggregation_service/aggregatable_report_sender.cc" />
  <item id="android_device_manager_socket" added_in_milestone="65" content_hash_code="00623801" os_list="linux,windows,chromeos" file_path="chrome/browser/devtools/device/android_device_manager.cc" />
  <item id="android_web_socket" added_in_milestone="65" content_hash_code="00bbd661" os_list="linux,windows,chromeos" file_path="chrome/browser/devtools/device/android_web_socket.cc" />
  <item id="auction_downloader" added_in_milestone="92" content_hash_code="0713f212" os_list="linux,windows,chromeos,android" file_path="content/services/auction_worklet/auction_downloader.cc" />
diff --git a/ui/compositor/transform_recorder.cc b/ui/compositor/transform_recorder.cc
index faa4a52..a98e7a4 100644
--- a/ui/compositor/transform_recorder.cc
+++ b/ui/compositor/transform_recorder.cc
@@ -7,6 +7,7 @@
 #include "cc/paint/display_item_list.h"
 #include "cc/paint/paint_op_buffer.h"
 #include "ui/compositor/paint_context.h"
+#include "ui/gfx/geometry/transform.h"
 
 namespace ui {
 
diff --git a/ui/ozone/platform/x11/x11_window.cc b/ui/ozone/platform/x11/x11_window.cc
index 2a56617..7371f85c 100644
--- a/ui/ozone/platform/x11/x11_window.cc
+++ b/ui/ozone/platform/x11/x11_window.cc
@@ -34,6 +34,7 @@
 #include "ui/events/x/events_x_utils.h"
 #include "ui/events/x/x11_event_translation.h"
 #include "ui/gfx/geometry/skia_conversions.h"
+#include "ui/gfx/geometry/transform.h"
 #include "ui/gfx/image/image_skia_rep.h"
 #include "ui/gfx/x/x11_atom_cache.h"
 #include "ui/gfx/x/x11_path.h"
diff --git a/weblayer/browser/cookie_manager_impl.cc b/weblayer/browser/cookie_manager_impl.cc
index 9a2510f7..5e2352d6 100644
--- a/weblayer/browser/cookie_manager_impl.cc
+++ b/weblayer/browser/cookie_manager_impl.cc
@@ -211,9 +211,8 @@
 bool CookieManagerImpl::SetCookieInternal(const GURL& url,
                                           const std::string& value,
                                           SetCookieCallback callback) {
-  auto cc =
-      net::CanonicalCookie::Create(url, value, base::Time::Now(), absl::nullopt,
-                                   net::CookiePartitionKey::Todo());
+  auto cc = net::CanonicalCookie::Create(url, value, base::Time::Now(),
+                                         absl::nullopt, absl::nullopt);
   if (!cc) {
     return false;
   }