diff --git a/.vpython b/.vpython
index 55da4c6c..f36679b 100644
--- a/.vpython
+++ b/.vpython
@@ -55,7 +55,7 @@
 # //third_party/catapult/telemetry/telemetry/internal/util/external_modules.py
 wheel: <
   name: "infra/python/wheels/psutil/${vpython_platform}"
-  version: "version:5.2.2"
+  version: "version:5.8.0.chromium.2"
 >
 
 # Used by:
diff --git a/DEPS b/DEPS
index cceebea..b513a37 100644
--- a/DEPS
+++ b/DEPS
@@ -228,7 +228,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': '0909a47192c160ac67701c584573c3ed921cfacb',
+  'skia_revision': 'b05f03db32cabca4f7ccc0e357aac3825dda1ee8',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
@@ -240,7 +240,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': 'ff64d2c7e562f401bf174ba2030bc10583a4b307',
+  'angle_revision': 'ce6b0cd7fd71bd9da5c24f6696fc46724d5eeb89',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -299,7 +299,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': '3ef3f2c876b9f1a7f315126107252ae160c1bab8',
+  'catapult_revision': 'bd47f22ad262b4e1f5beef55fd52a93a992e8007',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -307,7 +307,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': '663d38164a832ee24620cb34f050eb6b1cf4d652',
+  'devtools_frontend_revision': '33571946d00ea2ebfbc03017636d519153893c00',
   # 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.
@@ -347,7 +347,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': '39c4fcbdd394850a0ab110587cd03f920eceb0b5',
+  'dawn_revision': '0d6da1072c684a85fd6747f82619ed9b1346a87e',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -391,11 +391,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'libcxxabi_revision':    'eed07007f8ed34369d80190423d51b483565c6fc',
+  'libcxxabi_revision':    '671803fd96051bfeb25e5665b4262e1f8a509bbf',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'libunwind_revision':    '7729bc924826c3cfd06da08abb45781016627b77',
+  'libunwind_revision':    '83f8edbca7fc9b34be334da52091905dc3cc0c4a',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -610,7 +610,7 @@
   },
 
   'src/ios/third_party/material_components_ios/src': {
-      'url': Var('chromium_git') + '/external/github.com/material-components/material-components-ios.git' + '@' + '8accfaa3379193df2d96fe4d6aa20d3989ce8e1d',
+      'url': Var('chromium_git') + '/external/github.com/material-components/material-components-ios.git' + '@' + 'a49890be988bdac2924060cc1c1f681e0dcfb20d',
       'condition': 'checkout_ios',
   },
 
@@ -1389,7 +1389,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + '91a6d3a9b815351984230325e644e263c00825f0',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'e0c4d9b9566c52974287aa1a6678e16f1949c6c4',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1582,7 +1582,7 @@
   'src/third_party/usrsctp/usrsctplib':
     Var('chromium_git') + '/external/github.com/sctplab/usrsctp' + '@' + '978003f36a3bc1e9fdeafae26dbfe825684b0a25',
 
-  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@b9a71ac6f94507562ad90d689a622bd4a70b42df',
+  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@20a966e2b2fd91e4a201201d6d65a3a734b59898',
 
   'src/third_party/vulkan_memory_allocator':
     Var('chromium_git') + '/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git' + '@' + 'f67d7fa397e83060b76a1ec53579116a0bbdff7a',
@@ -1618,10 +1618,10 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + 'ad1419557298bfa2829818c12ae3bca2795a7c8f',
 
   'src/third_party/webgpu-cts/src':
-    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '97d686891b20622fc5cb04b32665e9739adce068',
+    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '201435f657c0b3942418a34a8fe87d593fa433d8',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '8b18304e66524060eca390f143033ba51322b3a2',
+    Var('webrtc_git') + '/src.git' + '@' + 'e0fb45c6d47f72cc42289d560cad522de132fe7e',
 
   'src/third_party/libgifcodec':
      Var('skia_git') + '/libgifcodec' + '@'+  Var('libgifcodec_revision'),
@@ -1682,7 +1682,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@ad9d10a4762f54f93ae704108378ae2a695693b5',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@fcacc1476634c86684d9ba6a1d840eafe55bf10d',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/android_webview/browser/aw_contents_io_thread_client.cc b/android_webview/browser/aw_contents_io_thread_client.cc
index 1e74a63..f7318774 100644
--- a/android_webview/browser/aw_contents_io_thread_client.cc
+++ b/android_webview/browser/aw_contents_io_thread_client.cc
@@ -60,7 +60,7 @@
 
 IoThreadClientData::IoThreadClientData() : pending_association(false) {}
 
-typedef map<pair<int, int>, IoThreadClientData>
+typedef map<content::GlobalRenderFrameHostId, IoThreadClientData>
     RenderFrameHostToIoThreadClientType;
 
 typedef pair<base::flat_set<RenderFrameHost*>, IoThreadClientData>
@@ -74,16 +74,13 @@
 // therefore the FrameTreeNodeId should be removed).
 typedef map<int, HostsAndClientDataPair> FrameTreeNodeToIoThreadClientType;
 
-static pair<int, int> GetRenderFrameHostIdPair(RenderFrameHost* rfh) {
-  return pair<int, int>(rfh->GetProcess()->GetID(), rfh->GetRoutingID());
-}
-
 // RfhToIoThreadClientMap -----------------------------------------------------
 class RfhToIoThreadClientMap {
  public:
   static RfhToIoThreadClientMap* GetInstance();
-  void Set(pair<int, int> rfh_id, const IoThreadClientData& client);
-  bool Get(pair<int, int> rfh_id, IoThreadClientData* client);
+  void Set(content::GlobalRenderFrameHostId rfh_id,
+           const IoThreadClientData& client);
+  bool Get(content::GlobalRenderFrameHostId rfh_id, IoThreadClientData* client);
 
   bool Get(int frame_tree_node_id, IoThreadClientData* client);
 
@@ -114,13 +111,13 @@
   return g_instance_.Pointer();
 }
 
-void RfhToIoThreadClientMap::Set(pair<int, int> rfh_id,
+void RfhToIoThreadClientMap::Set(content::GlobalRenderFrameHostId rfh_id,
                                  const IoThreadClientData& client) {
   base::AutoLock lock(map_lock_);
   rfh_to_io_thread_client_[rfh_id] = client;
 }
 
-bool RfhToIoThreadClientMap::Get(pair<int, int> rfh_id,
+bool RfhToIoThreadClientMap::Get(content::GlobalRenderFrameHostId rfh_id,
                                  IoThreadClientData* client) {
   base::AutoLock lock(map_lock_);
   RenderFrameHostToIoThreadClientType::iterator iterator =
@@ -147,7 +144,7 @@
 void RfhToIoThreadClientMap::Set(RenderFrameHost* rfh,
                                  const IoThreadClientData& client) {
   int frame_tree_node_id = rfh->GetFrameTreeNodeId();
-  pair<int, int> rfh_id = GetRenderFrameHostIdPair(rfh);
+  content::GlobalRenderFrameHostId rfh_id = rfh->GetGlobalId();
   base::AutoLock lock(map_lock_);
 
   // If this FrameTreeNodeId already has an associated IoThreadClientData, add
@@ -166,7 +163,7 @@
 
 void RfhToIoThreadClientMap::Erase(RenderFrameHost* rfh) {
   int frame_tree_node_id = rfh->GetFrameTreeNodeId();
-  pair<int, int> rfh_id = GetRenderFrameHostIdPair(rfh);
+  content::GlobalRenderFrameHostId rfh_id = rfh->GetGlobalId();
   base::AutoLock lock(map_lock_);
   HostsAndClientDataPair& current_entry =
       frame_tree_node_to_io_thread_client_[frame_tree_node_id];
@@ -231,11 +228,10 @@
 
 // static
 std::unique_ptr<AwContentsIoThreadClient> AwContentsIoThreadClient::FromID(
-    int render_process_id,
-    int render_frame_id) {
-  pair<int, int> rfh_id(render_process_id, render_frame_id);
+    content::GlobalRenderFrameHostId render_frame_host_id) {
   IoThreadClientData client_data;
-  if (!RfhToIoThreadClientMap::GetInstance()->Get(rfh_id, &client_data))
+  if (!RfhToIoThreadClientMap::GetInstance()->Get(render_frame_host_id,
+                                                  &client_data))
     return nullptr;
 
   JNIEnv* env = AttachCurrentThread();
@@ -265,8 +261,10 @@
 void AwContentsIoThreadClient::SubFrameCreated(int render_process_id,
                                                int parent_render_frame_id,
                                                int child_render_frame_id) {
-  pair<int, int> parent_rfh_id(render_process_id, parent_render_frame_id);
-  pair<int, int> child_rfh_id(render_process_id, child_render_frame_id);
+  content::GlobalRenderFrameHostId parent_rfh_id(render_process_id,
+                                                 parent_render_frame_id);
+  content::GlobalRenderFrameHostId child_rfh_id(render_process_id,
+                                                child_render_frame_id);
   IoThreadClientData client_data;
   if (!RfhToIoThreadClientMap::GetInstance()->Get(parent_rfh_id,
                                                   &client_data)) {
@@ -283,7 +281,7 @@
   IoThreadClientData client_data;
   client_data.pending_association = true;
   RfhToIoThreadClientMap::GetInstance()->Set(
-      GetRenderFrameHostIdPair(web_contents->GetMainFrame()), client_data);
+      web_contents->GetMainFrame()->GetGlobalId(), client_data);
 }
 
 // static
diff --git a/android_webview/browser/aw_contents_io_thread_client.h b/android_webview/browser/aw_contents_io_thread_client.h
index 9f0e5c7..8fc44e59 100644
--- a/android_webview/browser/aw_contents_io_thread_client.h
+++ b/android_webview/browser/aw_contents_io_thread_client.h
@@ -16,6 +16,7 @@
 #include "base/macros.h"
 #include "base/task/post_task.h"
 #include "base/task/thread_pool.h"
+#include "content/public/browser/global_routing_id.h"
 
 namespace content {
 class WebContents;
@@ -84,11 +85,11 @@
   CacheMode GetCacheMode() const;
 
   // This will attempt to fetch the AwContentsIoThreadClient for the given
-  // |render_process_id|, |render_frame_id| pair.
+  // RenderFrameHost id.
   // This method can be called from any thread.
   // A null std::unique_ptr is a valid return value.
-  static std::unique_ptr<AwContentsIoThreadClient> FromID(int render_process_id,
-                                                          int render_frame_id);
+  static std::unique_ptr<AwContentsIoThreadClient> FromID(
+      content::GlobalRenderFrameHostId render_frame_host_id);
 
   // This map is useful when browser side navigations are enabled as
   // render_frame_ids will not be valid anymore for some of the navigations.
diff --git a/android_webview/browser/aw_cookie_access_policy.cc b/android_webview/browser/aw_cookie_access_policy.cc
index 844e421..5991d4c7 100644
--- a/android_webview/browser/aw_cookie_access_policy.cc
+++ b/android_webview/browser/aw_cookie_access_policy.cc
@@ -10,6 +10,7 @@
 #include "base/check_op.h"
 #include "base/no_destructor.h"
 #include "content/public/browser/browser_thread.h"
+#include "content/public/browser/global_routing_id.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/websocket_handshake_request_info.h"
 #include "net/base/net_errors.h"
@@ -49,11 +50,12 @@
     int render_frame_id,
     int frame_tree_node_id) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  const content::GlobalRenderFrameHostId rfh_id(render_process_id,
+                                                render_frame_id);
   std::unique_ptr<AwContentsIoThreadClient> io_thread_client =
       (frame_tree_node_id != content::RenderFrameHost::kNoFrameTreeNodeId)
           ? AwContentsIoThreadClient::FromID(frame_tree_node_id)
-          : AwContentsIoThreadClient::FromID(render_process_id,
-                                             render_frame_id);
+          : AwContentsIoThreadClient::FromID(rfh_id);
 
   if (!io_thread_client) {
     return false;
diff --git a/android_webview/browser/aw_feature_list_creator.cc b/android_webview/browser/aw_feature_list_creator.cc
index e49ae9a2..a26f5f1 100644
--- a/android_webview/browser/aw_feature_list_creator.cc
+++ b/android_webview/browser/aw_feature_list_creator.cc
@@ -23,6 +23,7 @@
 #include "base/callback_helpers.h"
 #include "base/command_line.h"
 #include "base/feature_list.h"
+#include "base/files/file_path.h"
 #include "base/path_service.h"
 #include "base/strings/string_split.h"
 #include "base/time/time.h"
diff --git a/android_webview/browser/aw_ssl_host_state_delegate.cc b/android_webview/browser/aw_ssl_host_state_delegate.cc
index 5aed7201c..4215619 100644
--- a/android_webview/browser/aw_ssl_host_state_delegate.cc
+++ b/android_webview/browser/aw_ssl_host_state_delegate.cc
@@ -61,13 +61,17 @@
   return false;
 }
 
-void AwSSLHostStateDelegate::AllowHttpForHost(const std::string& host) {
+void AwSSLHostStateDelegate::AllowHttpForHost(
+    const std::string& host,
+    content::WebContents* web_contents) {
   // Intentional no-op for Android WebView.
 }
 
-bool AwSSLHostStateDelegate::IsHttpAllowedForHost(const std::string& host) {
+bool AwSSLHostStateDelegate::IsHttpAllowedForHost(
+    const std::string& host,
+    content::WebContents* web_contents) {
   // Intentional no-op for Android WebView. Return value does not matter as
-  // HTTPS-Only Mode is not enabled on WebView.
+  // HTTPS-First Mode is not enabled on WebView.
   return false;
 }
 
diff --git a/android_webview/browser/aw_ssl_host_state_delegate.h b/android_webview/browser/aw_ssl_host_state_delegate.h
index b843cd6..9d44c44 100644
--- a/android_webview/browser/aw_ssl_host_state_delegate.h
+++ b/android_webview/browser/aw_ssl_host_state_delegate.h
@@ -73,9 +73,11 @@
                                  int child_id,
                                  InsecureContentType content_type) override;
 
-  // HTTPS-Only Mode is not implemented in Android Webview.
-  void AllowHttpForHost(const std::string& host) override;
-  bool IsHttpAllowedForHost(const std::string& host) override;
+  // HTTPS-First Mode is not implemented in Android Webview.
+  void AllowHttpForHost(const std::string& host,
+                        content::WebContents* web_contents) override;
+  bool IsHttpAllowedForHost(const std::string& host,
+                            content::WebContents* web_contents) override;
 
   // Revokes all SSL certificate error allow exceptions made by the user for
   // |host|.
diff --git a/android_webview/browser/metrics/aw_metrics_service_client.cc b/android_webview/browser/metrics/aw_metrics_service_client.cc
index b9c2e96..b527975 100644
--- a/android_webview/browser/metrics/aw_metrics_service_client.cc
+++ b/android_webview/browser/metrics/aw_metrics_service_client.cc
@@ -14,6 +14,7 @@
 #include "base/android/jni_android.h"
 #include "base/android/jni_string.h"
 #include "base/feature_list.h"
+#include "base/files/file_path.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/persistent_histogram_allocator.h"
 #include "base/no_destructor.h"
@@ -82,6 +83,13 @@
 
 AwMetricsServiceClient::~AwMetricsServiceClient() = default;
 
+void AwMetricsServiceClient::Initialize(PrefService* pref_service) {
+  // Pass an empty file path since the path is for the Extended Variations Safe
+  // Mode experiment and Android WebView is excluded from this experiment.
+  AndroidMetricsServiceClient::Initialize(/*user_data_dir=*/base::FilePath(),
+                                          pref_service);
+}
+
 int32_t AwMetricsServiceClient::GetProduct() {
   return metrics::ChromeUserMetricsExtension::ANDROID_WEBVIEW;
 }
diff --git a/android_webview/browser/metrics/aw_metrics_service_client.h b/android_webview/browser/metrics/aw_metrics_service_client.h
index 53d9b11..1299cff 100644
--- a/android_webview/browser/metrics/aw_metrics_service_client.h
+++ b/android_webview/browser/metrics/aw_metrics_service_client.h
@@ -21,6 +21,8 @@
 #include "content/public/browser/notification_observer.h"
 #include "content/public/browser/notification_registrar.h"
 
+class PrefService;
+
 namespace android_webview {
 
 namespace prefs {
@@ -150,6 +152,9 @@
   AwMetricsServiceClient(std::unique_ptr<Delegate> delegate);
   ~AwMetricsServiceClient() override;
 
+  // Initializes, but does not necessarily start, the MetricsService.
+  void Initialize(PrefService* pref_service);
+
   // metrics::MetricsServiceClient
   int32_t GetProduct() override;
 
diff --git a/android_webview/browser/safe_browsing/aw_url_checker_delegate_impl.cc b/android_webview/browser/safe_browsing/aw_url_checker_delegate_impl.cc
index de3b0de..917c1d8 100644
--- a/android_webview/browser/safe_browsing/aw_url_checker_delegate_impl.cc
+++ b/android_webview/browser/safe_browsing/aw_url_checker_delegate_impl.cc
@@ -30,6 +30,7 @@
 #include "components/security_interstitials/core/urls.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
+#include "content/public/browser/global_routing_id.h"
 #include "content/public/browser/navigation_entry.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/url_constants.h"
@@ -112,15 +113,16 @@
     int render_process_id,
     int render_frame_id,
     bool originated_from_service_worker) {
-  std::unique_ptr<AwContentsIoThreadClient> client;
+  const content::GlobalRenderFrameHostId rfh_id(render_process_id,
+                                                render_frame_id);
 
+  std::unique_ptr<AwContentsIoThreadClient> client;
   if (originated_from_service_worker) {
     client = AwContentsIoThreadClient::GetServiceWorkerIoThreadClient();
-  } else if (render_process_id == -1 || render_frame_id == -1) {
+  } else if (!rfh_id) {
     client = AwContentsIoThreadClient::FromID(frame_tree_node_id);
   } else {
-    client =
-        AwContentsIoThreadClient::FromID(render_process_id, render_frame_id);
+    client = AwContentsIoThreadClient::FromID(rfh_id);
   }
 
   // If Safe Browsing is disabled by the app, skip the check. Default to
diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd
index 9171087..155f187 100644
--- a/ash/ash_strings.grd
+++ b/ash/ash_strings.grd
@@ -3164,7 +3164,10 @@
         File feedback
       </message>
       <message name="IDS_ASH_QUICK_ANSWERS_SETTINGS_BUTTON_TOOLTIP_TEXT" desc="Tootip text for the settings-button in Quick-Answers related views.">
-        Settings
+        Quick answers settings
+      </message>
+      <message name="IDS_ASH_QUICK_ANSWERS_PHONETICS_BUTTON_TOOLTIP_TEXT" desc="Tootip text for the phonetics-button in Quick-Answers related views.">
+        Listen
       </message>
       <message name="IDS_ASH_QUICK_ANSWERS_DEFINITION_INTENT" desc="Display text of definition intent. This is used in the title of Quick Answers user consent dialog.">
         definition
@@ -3233,10 +3236,10 @@
         Info related to your selection available. Use Up arrow key to access.
       </message>
       <message name="IDS_ASH_QUICK_ANSWERS_VIEW_A11Y_INFO_DESC_TEMPLATE" desc="A11y description template for the Quick Answers view.">
-        <ph name="DESC_TEXT">$1<ex>Query texts</ex></ph>; Click the dialog to see result in Assistant.
+        <ph name="QUERY_TEXT">$1<ex>interested</ex></ph>; <ph name="RESULT_TEXT">$2<ex>showing curiosity or concern about something or someone; having a feeling of interest.</ex></ph>; Press Search plus Space to see result in Assistant.
       </message>
       <message name="IDS_ASH_QUICK_ANSWERS_VIEW_A11Y_INFO_DESC_TEMPLATE_V2" desc="A11y description template for the Quick Answers view (V2 version).">
-        <ph name="DESC_TEXT">$1<ex>Query texts</ex></ph>; Click the dialog to see result in Google Search.
+        <ph name="QUERY_TEXT">$1<ex>interested</ex></ph>; <ph name="RESULT_TEXT">$2<ex>showing curiosity or concern about something or someone; having a feeling of interest.</ex></ph>; Press Search plus Space to see result in Google Search.
       </message>
       <message name="IDS_ASH_QUICK_ANSWERS_VIEW_A11Y_NAME_TEXT" desc="A11y name text for the Quick Answers view.">
         Info related to your selection
diff --git a/ash/ash_strings_grd/IDS_ASH_QUICK_ANSWERS_PHONETICS_BUTTON_TOOLTIP_TEXT.png.sha1 b/ash/ash_strings_grd/IDS_ASH_QUICK_ANSWERS_PHONETICS_BUTTON_TOOLTIP_TEXT.png.sha1
new file mode 100644
index 0000000..111f8c4
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_QUICK_ANSWERS_PHONETICS_BUTTON_TOOLTIP_TEXT.png.sha1
@@ -0,0 +1 @@
+f07eedbd867859809d66f0ae1bd78d232ab91ccb
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_ASH_QUICK_ANSWERS_SETTINGS_BUTTON_TOOLTIP_TEXT.png.sha1 b/ash/ash_strings_grd/IDS_ASH_QUICK_ANSWERS_SETTINGS_BUTTON_TOOLTIP_TEXT.png.sha1
index 0a44add..3b48130 100644
--- a/ash/ash_strings_grd/IDS_ASH_QUICK_ANSWERS_SETTINGS_BUTTON_TOOLTIP_TEXT.png.sha1
+++ b/ash/ash_strings_grd/IDS_ASH_QUICK_ANSWERS_SETTINGS_BUTTON_TOOLTIP_TEXT.png.sha1
@@ -1 +1 @@
-1cd3d40f985dfce827937d37b5fa3c324b8e54a2
\ No newline at end of file
+7e7ffaae16e66eb6f34b189b3dd66e99c5bab5c8
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_ASH_QUICK_ANSWERS_VIEW_A11Y_INFO_DESC_TEMPLATE.png.sha1 b/ash/ash_strings_grd/IDS_ASH_QUICK_ANSWERS_VIEW_A11Y_INFO_DESC_TEMPLATE.png.sha1
index 2bbe767..138527afa 100644
--- a/ash/ash_strings_grd/IDS_ASH_QUICK_ANSWERS_VIEW_A11Y_INFO_DESC_TEMPLATE.png.sha1
+++ b/ash/ash_strings_grd/IDS_ASH_QUICK_ANSWERS_VIEW_A11Y_INFO_DESC_TEMPLATE.png.sha1
@@ -1 +1 @@
-a54a9781638401dc399f5aa506d1db54944abda7
\ No newline at end of file
+2b0bdaca7641cb3e5b9ca18d6b4ee8ce90c3740d
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_ASH_QUICK_ANSWERS_VIEW_A11Y_INFO_DESC_TEMPLATE_V2.png.sha1 b/ash/ash_strings_grd/IDS_ASH_QUICK_ANSWERS_VIEW_A11Y_INFO_DESC_TEMPLATE_V2.png.sha1
index 9be8830..7ece1b9 100644
--- a/ash/ash_strings_grd/IDS_ASH_QUICK_ANSWERS_VIEW_A11Y_INFO_DESC_TEMPLATE_V2.png.sha1
+++ b/ash/ash_strings_grd/IDS_ASH_QUICK_ANSWERS_VIEW_A11Y_INFO_DESC_TEMPLATE_V2.png.sha1
@@ -1 +1 @@
-eb21e2877b9c2fd3206e88eae3fb580037cd00cf
\ No newline at end of file
+2b0bdaca7641cb3e5b9ca18d6b4ee8ce90c3740d
diff --git a/ash/constants/ash_features.cc b/ash/constants/ash_features.cc
index b2ed0f45..351340a1 100644
--- a/ash/constants/ash_features.cc
+++ b/ash/constants/ash_features.cc
@@ -1040,6 +1040,10 @@
 const base::Feature kTelemetryExtension{"TelemetryExtension",
                                         base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Enables the Settings UI to show data usage for cellular networks.
+const base::Feature kTrafficCountersSettingsUi{
+    "TrafficCountersSettingsUi", base::FEATURE_ENABLED_BY_DEFAULT};
+
 // Enables trilinear filtering.
 const base::Feature kTrilinearFiltering{"TrilinearFiltering",
                                         base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/ash/constants/ash_features.h b/ash/constants/ash_features.h
index be145f81..b15ac5e 100644
--- a/ash/constants/ash_features.h
+++ b/ash/constants/ash_features.h
@@ -391,6 +391,8 @@
 extern const base::Feature kSystemProxyForSystemServices;
 COMPONENT_EXPORT(ASH_CONSTANTS) extern const base::Feature kTabClusterUI;
 COMPONENT_EXPORT(ASH_CONSTANTS) extern const base::Feature kTelemetryExtension;
+COMPONENT_EXPORT(ASH_CONSTANTS)
+extern const base::Feature kTrafficCountersSettingsUi;
 COMPONENT_EXPORT(ASH_CONSTANTS) extern const base::Feature kTrilinearFiltering;
 COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const base::Feature kUseBluetoothSystemInAsh;
diff --git a/ash/constants/ash_switches.cc b/ash/constants/ash_switches.cc
index 7f18235..ac28c07f 100644
--- a/ash/constants/ash_switches.cc
+++ b/ash/constants/ash_switches.cc
@@ -450,6 +450,10 @@
 // Whether to enable PSM (private set membership) queries.
 const char kEnterpriseEnablePsm[] = "enterprise-enable-psm";
 
+// Whether to use fake PSM RLWE client for testing purposes.
+const char kEnterpriseUseFakePsmRlweClient[] =
+    "enterprise-use-fake-psm-rlwe-client";
+
 // Enables the zero-touch enterprise enrollment flow.
 const char kEnterpriseEnableZeroTouchEnrollment[] =
     "enterprise-enable-zero-touch-enrollment";
diff --git a/ash/constants/ash_switches.h b/ash/constants/ash_switches.h
index 7b9fa164..8199939 100644
--- a/ash/constants/ash_switches.h
+++ b/ash/constants/ash_switches.h
@@ -149,6 +149,8 @@
 extern const char kEnterpriseEnableInitialEnrollment[];
 COMPONENT_EXPORT(ASH_CONSTANTS) extern const char kEnterpriseEnablePsm[];
 COMPONENT_EXPORT(ASH_CONSTANTS)
+extern const char kEnterpriseUseFakePsmRlweClient[];
+COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const char kEnterpriseEnableZeroTouchEnrollment[];
 COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const char kEnterpriseEnrollmentInitialModulus[];
diff --git a/ash/public/cpp/holding_space/holding_space_metrics.cc b/ash/public/cpp/holding_space/holding_space_metrics.cc
index d63cdd5..3937a1d 100644
--- a/ash/public/cpp/holding_space/holding_space_metrics.cc
+++ b/ash/public/cpp/holding_space/holding_space_metrics.cc
@@ -45,16 +45,22 @@
 // values are persisted to histograms so should remain unchanged.
 std::string ItemActionToString(ItemAction action) {
   switch (action) {
+    case ItemAction::kCancel:
+      return "Cancel";
     case ItemAction::kCopy:
       return "Copy";
     case ItemAction::kDrag:
       return "Drag";
     case ItemAction::kLaunch:
       return "Launch";
+    case ItemAction::kPause:
+      return "Pause";
     case ItemAction::kPin:
       return "Pin";
     case ItemAction::kRemove:
       return "Remove";
+    case ItemAction::kResume:
+      return "Resume";
     case ItemAction::kShowInFolder:
       return "ShowInFolder";
     case ItemAction::kUnpin:
diff --git a/ash/public/cpp/holding_space/holding_space_metrics.h b/ash/public/cpp/holding_space/holding_space_metrics.h
index 81b4be0c..1a8955f8b 100644
--- a/ash/public/cpp/holding_space/holding_space_metrics.h
+++ b/ash/public/cpp/holding_space/holding_space_metrics.h
@@ -72,7 +72,10 @@
   kShowInFolder = 4,
   kUnpin = 5,
   kRemove = 6,
-  kMaxValue = kRemove,
+  kCancel = 7,
+  kPause = 8,
+  kResume = 9,
+  kMaxValue = kResume,
 };
 
 // Records the specified `action` taken on a set of holding space `items`.
diff --git a/ash/quick_answers/ui/quick_answers_view.cc b/ash/quick_answers/ui/quick_answers_view.cc
index 2c68a63c..9335e24 100644
--- a/ash/quick_answers/ui/quick_answers_view.cc
+++ b/ash/quick_answers/ui/quick_answers_view.cc
@@ -507,6 +507,8 @@
       gfx::CreateVectorIcon(kSystemMenuVolumeHighIcon,
                             kPhoneticsAudioButtonSizeDip,
                             kPhoneticsAudioButtonColor));
+  phonetics_audio_button_->SetTooltipText(l10n_util::GetStringUTF16(
+      IDS_ASH_QUICK_ANSWERS_PHONETICS_BUTTON_TOOLTIP_TEXT));
 }
 
 void QuickAnswersView::AddAssistantIcon() {
@@ -594,14 +596,15 @@
       first_answer_view->children().front()->GetClassName() ==
           views::Label::kViewClassName;
   if (first_answer_is_single_label) {
-    // Update answer announcement.
+    // Update announcement.
+    auto* title_label = static_cast<Label*>(title_view->children().front());
     auto* answer_label =
         static_cast<Label*>(first_answer_view->children().front());
     GetViewAccessibility().OverrideDescription(l10n_util::GetStringFUTF8(
         chromeos::features::IsQuickAnswersV2Enabled()
             ? IDS_ASH_QUICK_ANSWERS_VIEW_A11Y_INFO_DESC_TEMPLATE_V2
             : IDS_ASH_QUICK_ANSWERS_VIEW_A11Y_INFO_DESC_TEMPLATE,
-        answer_label->GetText()));
+        title_label->GetText(), answer_label->GetText()));
   }
 
   // Add second row answer.
diff --git a/ash/quick_pair/pairing/BUILD.gn b/ash/quick_pair/pairing/BUILD.gn
index 82fa338..3f0293c 100644
--- a/ash/quick_pair/pairing/BUILD.gn
+++ b/ash/quick_pair/pairing/BUILD.gn
@@ -9,16 +9,10 @@
 
 source_set("pairing") {
   sources = [
-    "fast_pair/decrypted_passkey.cc",
-    "fast_pair/decrypted_passkey.h",
-    "fast_pair/decrypted_response.cc",
-    "fast_pair/decrypted_response.h",
     "fast_pair/fake_fast_pair_gatt_service_client.cc",
     "fast_pair/fake_fast_pair_gatt_service_client.h",
     "fast_pair/fast_pair_data_encryptor.cc",
     "fast_pair/fast_pair_data_encryptor.h",
-    "fast_pair/fast_pair_data_parser.cc",
-    "fast_pair/fast_pair_data_parser.h",
     "fast_pair/fast_pair_encryption.cc",
     "fast_pair/fast_pair_encryption.h",
     "fast_pair/fast_pair_gatt_service_client.h",
@@ -35,6 +29,7 @@
 
   deps = [
     "//ash/quick_pair/common",
+    "//ash/services/quick_pair/public/cpp",
     "//base",
     "//crypto:crypto",
     "//device/bluetooth",
@@ -63,7 +58,6 @@
 
   sources = [
     "fast_pair/fast_pair_data_encryptor_unittest.cc",
-    "fast_pair/fast_pair_data_parser_unittest.cc",
     "fast_pair/fast_pair_gatt_service_client_unittest.cc",
     "fast_pair/fast_pair_pairer_unittest.cc",
   ]
@@ -72,6 +66,7 @@
     ":pairing",
     ":test_support",
     "//ash/quick_pair/common",
+    "//ash/services/quick_pair/public/cpp",
     "//base",
     "//base/test:test_support",
     "//device/bluetooth",
diff --git a/ash/quick_pair/pairing/fast_pair/decrypted_passkey.cc b/ash/quick_pair/pairing/fast_pair/decrypted_passkey.cc
deleted file mode 100644
index be6bf2c..0000000
--- a/ash/quick_pair/pairing/fast_pair/decrypted_passkey.cc
+++ /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.
-
-#include "ash/quick_pair/pairing/fast_pair/decrypted_passkey.h"
-
-namespace ash {
-namespace quick_pair {
-
-DecryptedPasskey::DecryptedPasskey(
-    uint8_t message_type,
-    uint32_t passkey,
-    std::array<uint8_t, kDecryptedPasskeySaltByteSize> salt)
-    : message_type(message_type), passkey(passkey), salt(salt) {}
-
-DecryptedPasskey::DecryptedPasskey(DecryptedPasskey&&) = default;
-
-}  // namespace quick_pair
-}  // namespace ash
diff --git a/ash/quick_pair/pairing/fast_pair/decrypted_passkey.h b/ash/quick_pair/pairing/fast_pair/decrypted_passkey.h
deleted file mode 100644
index bf9d0e3..0000000
--- a/ash/quick_pair/pairing/fast_pair/decrypted_passkey.h
+++ /dev/null
@@ -1,44 +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 ASH_QUICK_PAIR_PAIRING_FAST_PAIR_DECRYPTED_PASSKEY_H_
-#define ASH_QUICK_PAIR_PAIRING_FAST_PAIR_DECRYPTED_PASSKEY_H_
-
-#include <stddef.h>
-#include <stdint.h>
-
-#include <array>
-
-#include "base/component_export.h"
-
-namespace {
-
-constexpr int kDecryptedPasskeySaltByteSize = 12;
-
-}  // namespace
-
-namespace ash {
-namespace quick_pair {
-
-// Thin class which is used by the higher level components of the Quick Pair
-// system to represent a decrypted account passkey.
-struct DecryptedPasskey {
-  DecryptedPasskey(uint8_t message_type,
-                   uint32_t passkey,
-                   std::array<uint8_t, kDecryptedPasskeySaltByteSize> salt);
-  DecryptedPasskey(const DecryptedPasskey&) = delete;
-  DecryptedPasskey(DecryptedPasskey&&);
-  DecryptedPasskey& operator=(const DecryptedPasskey&) = delete;
-  DecryptedPasskey& operator=(DecryptedPasskey&&) = delete;
-  ~DecryptedPasskey() = default;
-
-  const uint8_t message_type;
-  const uint32_t passkey;
-  const std::array<uint8_t, kDecryptedPasskeySaltByteSize> salt;
-};
-
-}  // namespace quick_pair
-}  // namespace ash
-
-#endif  // ASH_QUICK_PAIR_PAIRING_FAST_PAIR_DECRYPTED_PASSKEY_H_
diff --git a/ash/quick_pair/pairing/fast_pair/decrypted_response.cc b/ash/quick_pair/pairing/fast_pair/decrypted_response.cc
deleted file mode 100644
index 5864c4eb..0000000
--- a/ash/quick_pair/pairing/fast_pair/decrypted_response.cc
+++ /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.
-
-#include "ash/quick_pair/pairing/fast_pair/decrypted_response.h"
-
-namespace ash {
-namespace quick_pair {
-
-DecryptedResponse::DecryptedResponse(
-    uint8_t message_type,
-    std::array<uint8_t, kDecryptedResponseAddressByteSize> address_bytes,
-    std::array<uint8_t, kDecryptedResponseSaltByteSize> salt)
-    : message_type(message_type), address_bytes(address_bytes), salt(salt) {}
-
-DecryptedResponse::DecryptedResponse(DecryptedResponse&&) = default;
-
-}  // namespace quick_pair
-}  // namespace ash
diff --git a/ash/quick_pair/pairing/fast_pair/decrypted_response.h b/ash/quick_pair/pairing/fast_pair/decrypted_response.h
deleted file mode 100644
index e1d3226..0000000
--- a/ash/quick_pair/pairing/fast_pair/decrypted_response.h
+++ /dev/null
@@ -1,46 +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 ASH_QUICK_PAIR_PAIRING_FAST_PAIR_DECRYPTED_RESPONSE_H_
-#define ASH_QUICK_PAIR_PAIRING_FAST_PAIR_DECRYPTED_RESPONSE_H_
-
-#include <stddef.h>
-#include <stdint.h>
-
-#include <array>
-
-#include "base/component_export.h"
-
-namespace {
-
-constexpr int kDecryptedResponseAddressByteSize = 6;
-constexpr int kDecryptedResponseSaltByteSize = 9;
-
-}  // namespace
-
-namespace ash {
-namespace quick_pair {
-
-// Thin class which is used by the higher level components of the Quick Pair
-// system to represent a decrypted response.
-struct DecryptedResponse {
-  DecryptedResponse(
-      uint8_t message_type,
-      std::array<uint8_t, kDecryptedResponseAddressByteSize> address_bytes,
-      std::array<uint8_t, kDecryptedResponseSaltByteSize> salt);
-  DecryptedResponse(const DecryptedResponse&) = delete;
-  DecryptedResponse(DecryptedResponse&&);
-  DecryptedResponse& operator=(const DecryptedResponse&) = delete;
-  DecryptedResponse& operator=(DecryptedResponse&&) = delete;
-  ~DecryptedResponse() = default;
-
-  const uint8_t message_type;
-  const std::array<uint8_t, kDecryptedResponseAddressByteSize> address_bytes;
-  const std::array<uint8_t, kDecryptedResponseSaltByteSize> salt;
-};
-
-}  // namespace quick_pair
-}  // namespace ash
-
-#endif  // ASH_QUICK_PAIR_PAIRING_FAST_PAIR_DECRYPTED_RESPONSE_H_
diff --git a/ash/quick_pair/pairing/fast_pair/fast_pair_data_parser.cc b/ash/quick_pair/pairing/fast_pair/fast_pair_data_parser.cc
deleted file mode 100644
index 8211ee3..0000000
--- a/ash/quick_pair/pairing/fast_pair/fast_pair_data_parser.cc
+++ /dev/null
@@ -1,100 +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.
-
-#include "ash/quick_pair/pairing/fast_pair/fast_pair_data_parser.h"
-
-#include "crypto/openssl_util.h"
-#include "third_party/boringssl/src/include/openssl/aes.h"
-
-namespace {
-
-constexpr int kMessageTypeIndex = 0;
-constexpr int kResponseAddressStartIndex = 1;
-constexpr int kResponseSaltStartIndex = 7;
-constexpr uint8_t kKeybasedPairingResponseType = 0x01;
-constexpr uint8_t kSeekerPasskeyType = 0x02;
-constexpr uint8_t kProviderPasskeyType = 0x03;
-constexpr int kPasskeySaltStartIndex = 4;
-
-}  // namespace
-
-namespace ash {
-namespace quick_pair {
-
-FastPairDataParser::FastPairDataParser() {
-  crypto::EnsureOpenSSLInit();
-}
-
-FastPairDataParser::~FastPairDataParser() = default;
-
-absl::optional<DecryptedResponse> FastPairDataParser::ParseDecryptedResponse(
-    const std::array<uint8_t, kAesBlockByteSize>& aes_key_bytes,
-    const std::array<uint8_t, kEncryptedDataByteSize>&
-        encrypted_response_bytes) {
-  std::array<uint8_t, kEncryptedDataByteSize> decrypted_response_bytes =
-      DecryptBytes(aes_key_bytes, encrypted_response_bytes);
-
-  uint8_t message_type = decrypted_response_bytes[kMessageTypeIndex];
-
-  // If the message type index is not the expected fast pair message type, then
-  // this is not a valid fast pair response.
-  if (message_type != kKeybasedPairingResponseType) {
-    return absl::nullopt;
-  }
-
-  std::array<uint8_t, kDecryptedResponseAddressByteSize> address_bytes;
-  static_assert(kResponseSaltStartIndex - kResponseAddressStartIndex ==
-                    kDecryptedResponseAddressByteSize,
-                "");
-  std::copy(decrypted_response_bytes.begin() + kResponseAddressStartIndex,
-            decrypted_response_bytes.begin() + kResponseSaltStartIndex,
-            address_bytes.begin());
-
-  std::array<uint8_t, kDecryptedResponseSaltByteSize> salt;
-  static_assert(kEncryptedDataByteSize - kResponseSaltStartIndex ==
-                    kDecryptedResponseSaltByteSize,
-                "");
-  std::copy(decrypted_response_bytes.begin() + kResponseSaltStartIndex,
-            decrypted_response_bytes.end(), salt.begin());
-  return DecryptedResponse(message_type, address_bytes, salt);
-}
-
-absl::optional<DecryptedPasskey> FastPairDataParser::ParseDecryptedPasskey(
-    const std::array<uint8_t, kAesBlockByteSize>& aes_key_bytes,
-    const std::array<uint8_t, kEncryptedDataByteSize>&
-        encrypted_passkey_bytes) {
-  std::array<uint8_t, kEncryptedDataByteSize> decrypted_passkey_bytes =
-      DecryptBytes(aes_key_bytes, encrypted_passkey_bytes);
-  uint8_t message_type = decrypted_passkey_bytes[kMessageTypeIndex];
-  if (message_type != kSeekerPasskeyType &&
-      message_type != kProviderPasskeyType) {
-    return absl::nullopt;
-  }
-
-  uint32_t passkey = decrypted_passkey_bytes[3];
-  passkey += decrypted_passkey_bytes[2] << 8;
-  passkey += decrypted_passkey_bytes[1] << 16;
-
-  std::array<uint8_t, kDecryptedPasskeySaltByteSize> salt;
-  static_assert(kEncryptedDataByteSize - kPasskeySaltStartIndex ==
-                    kDecryptedPasskeySaltByteSize,
-                "");
-  std::copy(decrypted_passkey_bytes.begin() + kPasskeySaltStartIndex,
-            decrypted_passkey_bytes.end(), salt.begin());
-  return DecryptedPasskey(message_type, passkey, salt);
-}
-
-std::array<uint8_t, kEncryptedDataByteSize> FastPairDataParser::DecryptBytes(
-    const std::array<uint8_t, kAesBlockByteSize>& aes_key_bytes,
-    const std::array<uint8_t, kEncryptedDataByteSize>& encrypted_bytes) {
-  AES_KEY aes_key;
-  AES_set_decrypt_key(aes_key_bytes.data(), aes_key_bytes.size() * 8, &aes_key);
-  std::array<uint8_t, kEncryptedDataByteSize> decrypted_bytes;
-  static_assert(kEncryptedDataByteSize == AES_BLOCK_SIZE, "");
-  AES_decrypt(encrypted_bytes.data(), decrypted_bytes.data(), &aes_key);
-  return decrypted_bytes;
-}
-
-}  // namespace quick_pair
-}  // namespace ash
diff --git a/ash/quick_pair/pairing/fast_pair/fast_pair_data_parser.h b/ash/quick_pair/pairing/fast_pair/fast_pair_data_parser.h
deleted file mode 100644
index 5ff3cb7..0000000
--- a/ash/quick_pair/pairing/fast_pair/fast_pair_data_parser.h
+++ /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.
-
-#ifndef ASH_QUICK_PAIR_PAIRING_FAST_PAIR_FAST_PAIR_DATA_PARSER_H_
-#define ASH_QUICK_PAIR_PAIRING_FAST_PAIR_FAST_PAIR_DATA_PARSER_H_
-
-#include <stddef.h>
-#include <stdint.h>
-
-#include <array>
-#include <vector>
-
-#include "ash/quick_pair/pairing/fast_pair/decrypted_passkey.h"
-#include "ash/quick_pair/pairing/fast_pair/decrypted_response.h"
-#include "third_party/abseil-cpp/absl/types/optional.h"
-
-namespace {
-
-constexpr int kEncryptedDataByteSize = 16;
-constexpr int kAesBlockByteSize = 16;
-
-}  // namespace
-
-namespace ash {
-namespace quick_pair {
-
-// This parses the encrypted bytes from the Bluetooth device.
-class FastPairDataParser {
- public:
-  FastPairDataParser();
-  ~FastPairDataParser();
-  FastPairDataParser(const FastPairDataParser&) = delete;
-  FastPairDataParser& operator=(const FastPairDataParser&) = delete;
-
-  absl::optional<DecryptedResponse> ParseDecryptedResponse(
-      const std::array<uint8_t, kAesBlockByteSize>& aes_key_bytes,
-      const std::array<uint8_t, kEncryptedDataByteSize>&
-          encrypted_response_bytes);
-
-  absl::optional<DecryptedPasskey> ParseDecryptedPasskey(
-      const std::array<uint8_t, kAesBlockByteSize>& aes_key_bytes,
-      const std::array<uint8_t, kEncryptedDataByteSize>&
-          encrypted_passkey_bytes);
-
- private:
-  std::array<uint8_t, kEncryptedDataByteSize> DecryptBytes(
-      const std::array<uint8_t, kAesBlockByteSize>& aes_key_bytes,
-      const std::array<uint8_t, kEncryptedDataByteSize>& encrypted_bytes);
-};
-
-}  // namespace quick_pair
-}  // namespace ash
-
-#endif  // ASH_QUICK_PAIR_PAIRING_FAST_PAIR_FAST_PAIR_DATA_PARSER_H_
diff --git a/ash/quick_pair/pairing/fast_pair/fast_pair_data_parser_unittest.cc b/ash/quick_pair/pairing/fast_pair/fast_pair_data_parser_unittest.cc
deleted file mode 100644
index 54a1721..0000000
--- a/ash/quick_pair/pairing/fast_pair/fast_pair_data_parser_unittest.cc
+++ /dev/null
@@ -1,168 +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.
-
-#include "ash/quick_pair/pairing/fast_pair/fast_pair_data_parser.h"
-
-#include <stddef.h>
-
-#include "ash/quick_pair/pairing/fast_pair/decrypted_passkey.h"
-#include "ash/quick_pair/pairing/fast_pair/decrypted_response.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/abseil-cpp/absl/types/optional.h"
-#include "third_party/boringssl/src/include/openssl/aes.h"
-#include "third_party/boringssl/src/include/openssl/base.h"
-#include "third_party/boringssl/src/include/openssl/crypto.h"
-
-namespace {
-
-std::array<uint8_t, kAesBlockByteSize> aes_key_bytes = {
-    0xA0, 0xBA, 0xF0, 0xBB, 0x95, 0x1F, 0xF7, 0xB6,
-    0xCF, 0x5E, 0x3F, 0x45, 0x61, 0xC3, 0x32, 0x1D};
-
-std::array<uint8_t, kAesBlockByteSize> EncryptBytes(
-    const std::array<uint8_t, kAesBlockByteSize>& bytes) {
-  AES_KEY aes_key;
-  AES_set_encrypt_key(aes_key_bytes.data(), aes_key_bytes.size() * 8, &aes_key);
-  std::array<uint8_t, kAesBlockByteSize> encrypted_bytes;
-  AES_encrypt(bytes.data(), encrypted_bytes.data(), &aes_key);
-  return encrypted_bytes;
-}
-
-}  // namespace
-
-namespace ash {
-namespace quick_pair {
-
-class FastPairDataParserTest : public testing::Test {
- public:
-  void SetUp() override {
-    data_parser_ = std::make_unique<FastPairDataParser>();
-  }
-
-  void TearDown() override {}
-
-  FastPairDataParser& data_parser() { return *(data_parser_.get()); }
-
- private:
-  std::unique_ptr<FastPairDataParser> data_parser_;
-};
-
-TEST_F(FastPairDataParserTest, DecryptResponseUnsuccessfully) {
-  std::array<uint8_t, kAesBlockByteSize> response_bytes = {
-      /*message_type=*/0x02,
-      /*address_bytes=*/0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
-      /*salt=*/0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F};
-  std::array<uint8_t, kAesBlockByteSize> encrypted_bytes =
-      EncryptBytes(response_bytes);
-
-  absl::optional<DecryptedResponse> decrypted_response =
-      data_parser().ParseDecryptedResponse(aes_key_bytes, encrypted_bytes);
-
-  EXPECT_FALSE(decrypted_response.has_value());
-}
-
-TEST_F(FastPairDataParserTest, DecryptResponseSuccessfully) {
-  std::array<uint8_t, kAesBlockByteSize> response_bytes;
-
-  // Message type.
-  uint8_t message_type = 0x01;
-  response_bytes[0] = message_type;
-
-  // Address bytes.
-  std::array<uint8_t, 6> address_bytes = {0x02, 0x03, 0x04, 0x05, 0x06, 0x07};
-  std::copy(address_bytes.begin(), address_bytes.end(),
-            response_bytes.begin() + 1);
-
-  // Random salt
-  std::array<uint8_t, 9> salt = {0x08, 0x09, 0x0A, 0x0B,
-                                 0x0C, 0x0D, 0x0E, 0x0F};
-  std::copy(salt.begin(), salt.end(), response_bytes.begin() + 7);
-
-  std::array<uint8_t, kAesBlockByteSize> encrypted_bytes =
-      EncryptBytes(response_bytes);
-
-  absl::optional<DecryptedResponse> decrypted_response =
-      data_parser().ParseDecryptedResponse(aes_key_bytes, encrypted_bytes);
-
-  EXPECT_TRUE(decrypted_response.has_value());
-  EXPECT_EQ(decrypted_response->message_type, message_type);
-  EXPECT_EQ(decrypted_response->address_bytes, address_bytes);
-  EXPECT_EQ(decrypted_response->salt, salt);
-}
-
-TEST_F(FastPairDataParserTest, DecryptPasskeyUnsuccessfully) {
-  std::array<uint8_t, kAesBlockByteSize> passkey_bytes = {
-    /*message_type=*/0x04,
-    /*passkey=*/0x02, 0x03, 0x04,
-    /*salt=*/0x05, 0x06, 0x07, 0x08, 0x09, 0x0A,
-             0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x0E};
-  std::array<uint8_t, kAesBlockByteSize> encrypted_bytes =
-      EncryptBytes(passkey_bytes);
-
-  absl::optional<DecryptedPasskey> decrypted_passkey =
-      data_parser().ParseDecryptedPasskey(aes_key_bytes, encrypted_bytes);
-
-  EXPECT_FALSE(decrypted_passkey.has_value());
-}
-
-TEST_F(FastPairDataParserTest, DecryptSeekerPasskeySuccessfully) {
-  std::array<uint8_t, kAesBlockByteSize> passkey_bytes;
-  // Message type.
-  uint8_t message_type = 0x02;
-  passkey_bytes[0] = message_type;
-
-  // Passkey bytes.
-  uint32_t passkey = 5;
-  passkey_bytes[1] = passkey >> 16;
-  passkey_bytes[2] = passkey >> 8;
-  passkey_bytes[3] = passkey;
-
-  // Random salt
-  std::array<uint8_t, 12> salt = {0x08, 0x09, 0x0A, 0x08, 0x09, 0x0E,
-                                  0x0A, 0x0C, 0x0D, 0x0E, 0x05, 0x02};
-  std::copy(salt.begin(), salt.end(), passkey_bytes.begin() + 4);
-
-  std::array<uint8_t, kAesBlockByteSize> encrypted_bytes =
-      EncryptBytes(passkey_bytes);
-
-  absl::optional<DecryptedPasskey> decrypted_passkey =
-      data_parser().ParseDecryptedPasskey(aes_key_bytes, encrypted_bytes);
-
-  EXPECT_TRUE(decrypted_passkey.has_value());
-  EXPECT_EQ(decrypted_passkey->message_type, message_type);
-  EXPECT_EQ(decrypted_passkey->passkey, passkey);
-  EXPECT_EQ(decrypted_passkey->salt, salt);
-}
-
-TEST_F(FastPairDataParserTest, DecryptProviderPasskeySuccessfully) {
-  std::array<uint8_t, kAesBlockByteSize> passkey_bytes;
-  // Message type.
-  uint8_t message_type = 0x03;
-  passkey_bytes[0] = message_type;
-
-  // Passkey bytes.
-  uint32_t passkey = 5;
-  passkey_bytes[1] = passkey >> 16;
-  passkey_bytes[2] = passkey >> 8;
-  passkey_bytes[3] = passkey;
-
-  // Random salt
-  std::array<uint8_t, 12> salt = {0x08, 0x09, 0x0A, 0x08, 0x09, 0x0E,
-                                  0x0A, 0x0C, 0x0D, 0x0E, 0x05, 0x02};
-  std::copy(salt.begin(), salt.end(), passkey_bytes.begin() + 4);
-
-  std::array<uint8_t, kAesBlockByteSize> encrypted_bytes =
-      EncryptBytes(passkey_bytes);
-
-  absl::optional<DecryptedPasskey> decrypted_passkey =
-      data_parser().ParseDecryptedPasskey(aes_key_bytes, encrypted_bytes);
-
-  EXPECT_TRUE(decrypted_passkey.has_value());
-  EXPECT_EQ(decrypted_passkey->message_type, message_type);
-  EXPECT_EQ(decrypted_passkey->passkey, passkey);
-  EXPECT_EQ(decrypted_passkey->salt, salt);
-}
-
-}  // namespace quick_pair
-}  // namespace ash
diff --git a/ash/quick_pair/pairing/fast_pair/fast_pair_encryption.cc b/ash/quick_pair/pairing/fast_pair/fast_pair_encryption.cc
index c925f8c..18aa9ae 100644
--- a/ash/quick_pair/pairing/fast_pair/fast_pair_encryption.cc
+++ b/ash/quick_pair/pairing/fast_pair/fast_pair_encryption.cc
@@ -8,6 +8,7 @@
 #include "ash/quick_pair/pairing/fast_pair/fast_pair_encryption.h"
 
 #include "ash/quick_pair/pairing/fast_pair/fast_pair_key_pair.h"
+#include "ash/services/quick_pair/public/cpp/fast_pair_message_type.h"
 #include "third_party/boringssl/src/include/openssl/base.h"
 #include "third_party/boringssl/src/include/openssl/ec.h"
 #include "third_party/boringssl/src/include/openssl/ec_key.h"
@@ -17,6 +18,14 @@
 
 namespace {
 
+constexpr int kMessageTypeIndex = 0;
+constexpr int kResponseAddressStartIndex = 1;
+constexpr int kResponseSaltStartIndex = 7;
+constexpr uint8_t kKeybasedPairingResponseType = 0x01;
+constexpr uint8_t kSeekerPasskeyType = 0x02;
+constexpr uint8_t kProviderPasskeyType = 0x03;
+constexpr int kPasskeySaltStartIndex = 4;
+
 // Converts the public anti-spoofing key into an EC_Point.
 bssl::UniquePtr<EC_POINT> GetEcPointFromPublicAntiSpoofingKey(
     const bssl::UniquePtr<EC_GROUP>& ec_group,
@@ -42,6 +51,16 @@
                 static_cast<uint8_t*>(out));
 }
 
+std::array<uint8_t, kBlockByteSize> DecryptBytes(
+    const std::array<uint8_t, kBlockByteSize>& aes_key_bytes,
+    const std::array<uint8_t, kBlockByteSize>& encrypted_bytes) {
+  AES_KEY aes_key;
+  AES_set_decrypt_key(aes_key_bytes.data(), aes_key_bytes.size() * 8, &aes_key);
+  std::array<uint8_t, kBlockByteSize> decrypted_bytes;
+  AES_decrypt(encrypted_bytes.data(), decrypted_bytes.data(), &aes_key);
+  return decrypted_bytes;
+}
+
 }  // namespace
 
 namespace ash {
@@ -102,6 +121,66 @@
   return encrypted_bytes;
 }
 
+// Decrypts the encrypted response
+// (https://developers.google.com/nearby/fast-pair/spec#table1.4) and returns
+// the parsed decrypted response
+// (https://developers.google.com/nearby/fast-pair/spec#table1.3)
+absl::optional<DecryptedResponse> ParseDecryptedResponse(
+    const std::array<uint8_t, kBlockByteSize>& aes_key_bytes,
+    const std::array<uint8_t, kBlockByteSize>& encrypted_response_bytes) {
+  std::array<uint8_t, kBlockByteSize> decrypted_response_bytes =
+      DecryptBytes(aes_key_bytes, encrypted_response_bytes);
+
+  uint8_t message_type = decrypted_response_bytes[kMessageTypeIndex];
+
+  // If the message type index is not the expected fast pair message type, then
+  // this is not a valid fast pair response.
+  if (message_type != kKeybasedPairingResponseType) {
+    return absl::nullopt;
+  }
+
+  std::array<uint8_t, kDecryptedResponseAddressByteSize> address_bytes;
+  std::copy(decrypted_response_bytes.begin() + kResponseAddressStartIndex,
+            decrypted_response_bytes.begin() + kResponseSaltStartIndex,
+            address_bytes.begin());
+
+  std::array<uint8_t, kDecryptedResponseSaltByteSize> salt;
+  std::copy(decrypted_response_bytes.begin() + kResponseSaltStartIndex,
+            decrypted_response_bytes.end(), salt.begin());
+  return DecryptedResponse(FastPairMessageType::kKeyBasedPairingResponse,
+                           address_bytes, salt);
+}
+
+// Decrypts the encrypted passkey
+// (https://developers.google.com/nearby/fast-pair/spec#table2.1) and returns
+// the parsed decrypted passkey
+// (https://developers.google.com/nearby/fast-pair/spec#table2.2)
+absl::optional<DecryptedPasskey> ParseDecryptedPasskey(
+    const std::array<uint8_t, kBlockByteSize>& aes_key_bytes,
+    const std::array<uint8_t, kBlockByteSize>& encrypted_passkey_bytes) {
+  std::array<uint8_t, kBlockByteSize> decrypted_passkey_bytes =
+      DecryptBytes(aes_key_bytes, encrypted_passkey_bytes);
+
+  FastPairMessageType message_type;
+  if (decrypted_passkey_bytes[kMessageTypeIndex] == kSeekerPasskeyType) {
+    message_type = FastPairMessageType::kSeekersPasskey;
+  } else if (decrypted_passkey_bytes[kMessageTypeIndex] ==
+             kProviderPasskeyType) {
+    message_type = FastPairMessageType::kProvidersPasskey;
+  } else {
+    return absl::nullopt;
+  }
+
+  uint32_t passkey = decrypted_passkey_bytes[3];
+  passkey += decrypted_passkey_bytes[2] << 8;
+  passkey += decrypted_passkey_bytes[1] << 16;
+
+  std::array<uint8_t, kDecryptedPasskeySaltByteSize> salt;
+  std::copy(decrypted_passkey_bytes.begin() + kPasskeySaltStartIndex,
+            decrypted_passkey_bytes.end(), salt.begin());
+  return DecryptedPasskey(message_type, passkey, salt);
+}
+
 }  // namespace fast_pair_encryption
 }  // namespace quick_pair
 }  // namespace ash
diff --git a/ash/quick_pair/pairing/fast_pair/fast_pair_encryption.h b/ash/quick_pair/pairing/fast_pair/fast_pair_encryption.h
index dc82f2e4..01ce592 100644
--- a/ash/quick_pair/pairing/fast_pair/fast_pair_encryption.h
+++ b/ash/quick_pair/pairing/fast_pair/fast_pair_encryption.h
@@ -11,7 +11,10 @@
 #include <string>
 
 #include "ash/quick_pair/pairing/fast_pair/fast_pair_key_pair.h"
+#include "ash/services/quick_pair/public/cpp/decrypted_passkey.h"
+#include "ash/services/quick_pair/public/cpp/decrypted_response.h"
 #include "base/component_export.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/boringssl/src/include/openssl/aes.h"
 
 namespace {
@@ -33,6 +36,16 @@
     const std::array<uint8_t, kBlockByteSize>& aes_key_bytes,
     const std::array<uint8_t, kBlockByteSize>& bytes_to_encrypt);
 
+COMPONENT_EXPORT(QUICK_PAIR_PAIRING)
+absl::optional<DecryptedResponse> ParseDecryptedResponse(
+    const std::array<uint8_t, kBlockByteSize>& aes_key_bytes,
+    const std::array<uint8_t, kBlockByteSize>& encrypted_response_bytes);
+
+COMPONENT_EXPORT(QUICK_PAIR_PAIRING)
+absl::optional<DecryptedPasskey> ParseDecryptedPasskey(
+    const std::array<uint8_t, kBlockByteSize>& aes_key_bytes,
+    const std::array<uint8_t, kBlockByteSize>& encrypted_passkey_bytes);
+
 }  // namespace fast_pair_encryption
 }  // namespace quick_pair
 }  // namespace ash
diff --git a/ash/services/quick_pair/public/cpp/BUILD.gn b/ash/services/quick_pair/public/cpp/BUILD.gn
new file mode 100644
index 0000000..2526253
--- /dev/null
+++ b/ash/services/quick_pair/public/cpp/BUILD.gn
@@ -0,0 +1,18 @@
+# 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("//build/config/chromeos/ui_mode.gni")
+
+assert(is_chromeos_ash,
+       "Quick Pair protocols (e.g. Fast Pair) are ash-chrome only")
+
+source_set("cpp") {
+  sources = [
+    "decrypted_passkey.cc",
+    "decrypted_passkey.h",
+    "decrypted_response.cc",
+    "decrypted_response.h",
+    "fast_pair_message_type.h",
+  ]
+}
diff --git a/ash/services/quick_pair/public/cpp/decrypted_passkey.cc b/ash/services/quick_pair/public/cpp/decrypted_passkey.cc
new file mode 100644
index 0000000..e71aa24
--- /dev/null
+++ b/ash/services/quick_pair/public/cpp/decrypted_passkey.cc
@@ -0,0 +1,30 @@
+// 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.
+
+#include "ash/services/quick_pair/public/cpp/decrypted_passkey.h"
+
+namespace ash {
+namespace quick_pair {
+
+DecryptedPasskey::DecryptedPasskey() = default;
+
+DecryptedPasskey::DecryptedPasskey(
+    FastPairMessageType message_type,
+    uint32_t passkey,
+    std::array<uint8_t, kDecryptedPasskeySaltByteSize> salt)
+    : message_type(message_type), passkey(passkey), salt(salt) {}
+
+DecryptedPasskey::DecryptedPasskey(const DecryptedPasskey&) = default;
+
+DecryptedPasskey::DecryptedPasskey(DecryptedPasskey&&) = default;
+
+DecryptedPasskey& DecryptedPasskey::operator=(const DecryptedPasskey&) =
+    default;
+
+DecryptedPasskey& DecryptedPasskey::operator=(DecryptedPasskey&&) = default;
+
+DecryptedPasskey::~DecryptedPasskey() = default;
+
+}  // namespace quick_pair
+}  // namespace ash
diff --git a/ash/services/quick_pair/public/cpp/decrypted_passkey.h b/ash/services/quick_pair/public/cpp/decrypted_passkey.h
new file mode 100644
index 0000000..11b30cc1
--- /dev/null
+++ b/ash/services/quick_pair/public/cpp/decrypted_passkey.h
@@ -0,0 +1,45 @@
+// 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 ASH_SERVICES_QUICK_PAIR_PUBLIC_CPP_DECRYPTED_PASSKEY_H_
+#define ASH_SERVICES_QUICK_PAIR_PUBLIC_CPP_DECRYPTED_PASSKEY_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <array>
+
+#include "ash/services/quick_pair/public/cpp/fast_pair_message_type.h"
+
+namespace {
+
+constexpr int kDecryptedPasskeySaltByteSize = 12;
+
+}  // namespace
+
+namespace ash {
+namespace quick_pair {
+
+// Thin class which is used by the higher level components of the Quick Pair
+// system to represent a decrypted account passkey.
+struct DecryptedPasskey {
+  DecryptedPasskey();
+  DecryptedPasskey(FastPairMessageType message_type,
+                   uint32_t passkey,
+                   std::array<uint8_t, kDecryptedPasskeySaltByteSize> salt);
+  DecryptedPasskey(const DecryptedPasskey&);
+  DecryptedPasskey(DecryptedPasskey&&);
+  DecryptedPasskey& operator=(const DecryptedPasskey&);
+  DecryptedPasskey& operator=(DecryptedPasskey&&);
+  ~DecryptedPasskey();
+
+  FastPairMessageType message_type;
+  uint32_t passkey;
+  std::array<uint8_t, kDecryptedPasskeySaltByteSize> salt;
+};
+
+}  // namespace quick_pair
+}  // namespace ash
+
+#endif  // ASH_SERVICES_QUICK_PAIR_PUBLIC_CPP_DECRYPTED_PASSKEY_H_
diff --git a/ash/services/quick_pair/public/cpp/decrypted_response.cc b/ash/services/quick_pair/public/cpp/decrypted_response.cc
new file mode 100644
index 0000000..9c8ea015
--- /dev/null
+++ b/ash/services/quick_pair/public/cpp/decrypted_response.cc
@@ -0,0 +1,30 @@
+// 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.
+
+#include "ash/services/quick_pair/public/cpp/decrypted_response.h"
+
+namespace ash {
+namespace quick_pair {
+
+DecryptedResponse::DecryptedResponse() = default;
+
+DecryptedResponse::DecryptedResponse(
+    FastPairMessageType message_type,
+    std::array<uint8_t, kDecryptedResponseAddressByteSize> address_bytes,
+    std::array<uint8_t, kDecryptedResponseSaltByteSize> salt)
+    : message_type(message_type), address_bytes(address_bytes), salt(salt) {}
+
+DecryptedResponse::DecryptedResponse(const DecryptedResponse&) = default;
+
+DecryptedResponse::DecryptedResponse(DecryptedResponse&&) = default;
+
+DecryptedResponse& DecryptedResponse::operator=(const DecryptedResponse&) =
+    default;
+
+DecryptedResponse& DecryptedResponse::operator=(DecryptedResponse&&) = default;
+
+DecryptedResponse::~DecryptedResponse() = default;
+
+}  // namespace quick_pair
+}  // namespace ash
diff --git a/ash/services/quick_pair/public/cpp/decrypted_response.h b/ash/services/quick_pair/public/cpp/decrypted_response.h
new file mode 100644
index 0000000..22d998b
--- /dev/null
+++ b/ash/services/quick_pair/public/cpp/decrypted_response.h
@@ -0,0 +1,47 @@
+// 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 ASH_SERVICES_QUICK_PAIR_PUBLIC_CPP_DECRYPTED_RESPONSE_H_
+#define ASH_SERVICES_QUICK_PAIR_PUBLIC_CPP_DECRYPTED_RESPONSE_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <array>
+
+#include "ash/services/quick_pair/public/cpp/fast_pair_message_type.h"
+
+namespace {
+
+constexpr int kDecryptedResponseAddressByteSize = 6;
+constexpr int kDecryptedResponseSaltByteSize = 9;
+
+}  // namespace
+
+namespace ash {
+namespace quick_pair {
+
+// Thin class which is used by the higher level components of the Quick Pair
+// system to represent a decrypted response.
+struct DecryptedResponse {
+  DecryptedResponse();
+  DecryptedResponse(
+      FastPairMessageType message_type,
+      std::array<uint8_t, kDecryptedResponseAddressByteSize> address_bytes,
+      std::array<uint8_t, kDecryptedResponseSaltByteSize> salt);
+  DecryptedResponse(const DecryptedResponse&);
+  DecryptedResponse(DecryptedResponse&&);
+  DecryptedResponse& operator=(const DecryptedResponse&);
+  DecryptedResponse& operator=(DecryptedResponse&&);
+  ~DecryptedResponse();
+
+  FastPairMessageType message_type;
+  std::array<uint8_t, kDecryptedResponseAddressByteSize> address_bytes;
+  std::array<uint8_t, kDecryptedResponseSaltByteSize> salt;
+};
+
+}  // namespace quick_pair
+}  // namespace ash
+
+#endif  // ASH_SERVICES_QUICK_PAIR_PUBLIC_CPP_DECRYPTED_RESPONSE_H_
diff --git a/ash/services/quick_pair/public/cpp/fast_pair_message_type.h b/ash/services/quick_pair/public/cpp/fast_pair_message_type.h
new file mode 100644
index 0000000..2db1c575
--- /dev/null
+++ b/ash/services/quick_pair/public/cpp/fast_pair_message_type.h
@@ -0,0 +1,26 @@
+// 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 ASH_SERVICES_QUICK_PAIR_PUBLIC_CPP_FAST_PAIR_MESSAGE_TYPE_H_
+#define ASH_SERVICES_QUICK_PAIR_PUBLIC_CPP_FAST_PAIR_MESSAGE_TYPE_H_
+
+namespace ash {
+namespace quick_pair {
+
+// Type values for Fast Pair messages.
+enum class FastPairMessageType {
+  // Key-based Pairing Request.
+  kKeyBasedPairingRequest,
+  // Key-based Pairing Response.
+  kKeyBasedPairingResponse,
+  // Seeker's passkey.
+  kSeekersPasskey,
+  // Provider's passkey.
+  kProvidersPasskey,
+};
+
+}  // namespace quick_pair
+}  // namespace ash
+
+#endif  // ASH_SERVICES_QUICK_PAIR_PUBLIC_CPP_FAST_PAIR_MESSAGE_TYPE_H_
diff --git a/ash/services/quick_pair/public/mojom/BUILD.gn b/ash/services/quick_pair/public/mojom/BUILD.gn
index b305f42..8e89d636 100644
--- a/ash/services/quick_pair/public/mojom/BUILD.gn
+++ b/ash/services/quick_pair/public/mojom/BUILD.gn
@@ -15,4 +15,28 @@
   ]
 
   public_deps = [ "//sandbox/policy/mojom" ]
+
+  cpp_typemaps = [
+    {
+      types = [
+        {
+          mojom = "ash.quick_pair.mojom.DecryptedResponse"
+          cpp = "::ash::quick_pair::DecryptedResponse"
+        },
+        {
+          mojom = "ash.quick_pair.mojom.DecryptedPasskey"
+          cpp = "::ash::quick_pair::DecryptedPasskey"
+        },
+        {
+          mojom = "ash.quick_pair.mojom.MessageType"
+          cpp = "::ash::quick_pair::FastPairMessageType"
+        },
+      ]
+      traits_headers =
+          [ "//ash/services/quick_pair/public/mojom/fast_pair_traits.h" ]
+      traits_sources =
+          [ "//ash/services/quick_pair/public/mojom/fast_pair_traits.cc" ]
+      traits_public_deps = [ "//ash/services/quick_pair/public/cpp" ]
+    },
+  ]
 }
diff --git a/ash/services/quick_pair/public/mojom/fast_pair_traits.cc b/ash/services/quick_pair/public/mojom/fast_pair_traits.cc
new file mode 100644
index 0000000..e37fe03e
--- /dev/null
+++ b/ash/services/quick_pair/public/mojom/fast_pair_traits.cc
@@ -0,0 +1,91 @@
+// 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.
+
+#include "ash/services/quick_pair/public/mojom/fast_pair_traits.h"
+
+#include <algorithm>
+#include <vector>
+
+namespace mojo {
+
+// static
+bool StructTraits<DecryptedResponseDataView, DecryptedResponse>::Read(
+    DecryptedResponseDataView data,
+    DecryptedResponse* out) {
+  std::vector<uint8_t> address_bytes;
+  if (!data.ReadAddressBytes(&address_bytes) ||
+      address_bytes.size() != out->address_bytes.size())
+    return false;
+
+  std::vector<uint8_t> salt_bytes;
+  if (!data.ReadSalt(&salt_bytes) || salt_bytes.size() != out->salt.size())
+    return false;
+
+  if (!EnumTraits<MessageType, FastPairMessageType>::FromMojom(
+          data.message_type(), &out->message_type))
+    return false;
+
+  std::copy(address_bytes.begin(), address_bytes.end(),
+            out->address_bytes.begin());
+  std::copy(salt_bytes.begin(), salt_bytes.end(), out->salt.begin());
+
+  return true;
+}
+
+// static
+bool StructTraits<DecryptedPasskeyDataView, DecryptedPasskey>::Read(
+    DecryptedPasskeyDataView data,
+    DecryptedPasskey* out) {
+  std::vector<uint8_t> salt_bytes;
+  if (!data.ReadSalt(&salt_bytes) || salt_bytes.size() != out->salt.size())
+    return false;
+
+  if (!EnumTraits<MessageType, FastPairMessageType>::FromMojom(
+          data.message_type(), &out->message_type))
+    return false;
+
+  out->passkey = data.passkey();
+  std::copy(salt_bytes.begin(), salt_bytes.end(), out->salt.begin());
+
+  return true;
+}
+
+// static
+MessageType EnumTraits<MessageType, FastPairMessageType>::ToMojom(
+    FastPairMessageType input) {
+  switch (input) {
+    case FastPairMessageType::kKeyBasedPairingRequest:
+      return MessageType::kKeyBasedPairingRequest;
+    case FastPairMessageType::kKeyBasedPairingResponse:
+      return MessageType::kKeyBasedPairingResponse;
+    case FastPairMessageType::kSeekersPasskey:
+      return MessageType::kSeekersPasskey;
+    case FastPairMessageType::kProvidersPasskey:
+      return MessageType::kProvidersPasskey;
+  }
+}
+
+// static
+bool EnumTraits<MessageType, FastPairMessageType>::FromMojom(
+    MessageType input,
+    FastPairMessageType* out) {
+  switch (input) {
+    case MessageType::kKeyBasedPairingRequest:
+      *out = FastPairMessageType::kKeyBasedPairingRequest;
+      return true;
+    case MessageType::kKeyBasedPairingResponse:
+      *out = FastPairMessageType::kKeyBasedPairingResponse;
+      return true;
+    case MessageType::kSeekersPasskey:
+      *out = FastPairMessageType::kSeekersPasskey;
+      return true;
+    case MessageType::kProvidersPasskey:
+      *out = FastPairMessageType::kProvidersPasskey;
+      return true;
+  }
+
+  return false;
+}
+
+}  // namespace mojo
diff --git a/ash/services/quick_pair/public/mojom/fast_pair_traits.h b/ash/services/quick_pair/public/mojom/fast_pair_traits.h
new file mode 100644
index 0000000..2c6aa54
--- /dev/null
+++ b/ash/services/quick_pair/public/mojom/fast_pair_traits.h
@@ -0,0 +1,75 @@
+// 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 ASH_SERVICES_QUICK_PAIR_PUBLIC_MOJOM_FAST_PAIR_TRAITS_H_
+#define ASH_SERVICES_QUICK_PAIR_PUBLIC_MOJOM_FAST_PAIR_TRAITS_H_
+
+#include <algorithm>
+#include <cstdint>
+#include <vector>
+
+#include "ash/services/quick_pair/public/cpp/decrypted_passkey.h"
+#include "ash/services/quick_pair/public/cpp/decrypted_response.h"
+#include "ash/services/quick_pair/public/cpp/fast_pair_message_type.h"
+#include "ash/services/quick_pair/public/mojom/fast_pair_data_parser.mojom-shared.h"
+#include "mojo/public/cpp/bindings/enum_traits.h"
+#include "mojo/public/cpp/bindings/struct_traits.h"
+
+namespace mojo {
+
+namespace {
+using ash::quick_pair::DecryptedPasskey;
+using ash::quick_pair::DecryptedResponse;
+using ash::quick_pair::FastPairMessageType;
+using ash::quick_pair::mojom::DecryptedPasskeyDataView;
+using ash::quick_pair::mojom::DecryptedResponseDataView;
+using ash::quick_pair::mojom::MessageType;
+}  // namespace
+
+template <>
+class EnumTraits<MessageType, FastPairMessageType> {
+ public:
+  static MessageType ToMojom(FastPairMessageType input);
+  static bool FromMojom(MessageType input, FastPairMessageType* out);
+};
+
+template <>
+class StructTraits<DecryptedResponseDataView, DecryptedResponse> {
+ public:
+  static MessageType message_type(const DecryptedResponse& r) {
+    return EnumTraits<MessageType, FastPairMessageType>::ToMojom(
+        r.message_type);
+  }
+
+  static std::vector<uint8_t> address_bytes(const DecryptedResponse& r) {
+    return std::vector<uint8_t>(r.address_bytes.begin(), r.address_bytes.end());
+  }
+
+  static std::vector<uint8_t> salt(const DecryptedResponse& r) {
+    return std::vector<uint8_t>(r.salt.begin(), r.salt.end());
+  }
+
+  static bool Read(DecryptedResponseDataView data, DecryptedResponse* out);
+};
+
+template <>
+class StructTraits<DecryptedPasskeyDataView, DecryptedPasskey> {
+ public:
+  static MessageType message_type(const DecryptedPasskey& r) {
+    return EnumTraits<MessageType, FastPairMessageType>::ToMojom(
+        r.message_type);
+  }
+
+  static uint32_t passkey(const DecryptedPasskey& r) { return r.passkey; }
+
+  static std::vector<uint8_t> salt(const DecryptedPasskey& r) {
+    return std::vector<uint8_t>(r.salt.begin(), r.salt.end());
+  }
+
+  static bool Read(DecryptedPasskeyDataView data, DecryptedPasskey* out);
+};
+
+}  // namespace mojo
+
+#endif  // ASH_SERVICES_QUICK_PAIR_PUBLIC_MOJOM_FAST_PAIR_TRAITS_H_
diff --git a/ash/webui/common/resources/navigation_view_panel.js b/ash/webui/common/resources/navigation_view_panel.js
index 24c972e0..a3063480 100644
--- a/ash/webui/common/resources/navigation_view_panel.js
+++ b/ash/webui/common/resources/navigation_view_panel.js
@@ -131,11 +131,10 @@
    * @private
    */
   getPage_(item) {
-    const pageName = item.pageIs;
-    let pageComponent = this.shadowRoot.querySelector(`#${item.pageIs}`);
+    let pageComponent = this.shadowRoot.querySelector(`#${item.id}`);
 
     if (pageComponent === null) {
-      pageComponent = document.createElement(pageName);
+      pageComponent = document.createElement(item.pageIs);
       assert(pageComponent);
       pageComponent.setAttribute('id', item.id);
       pageComponent.setAttribute('class', 'view-content');
diff --git a/ash/webui/shortcut_customization_ui/resources/BUILD.gn b/ash/webui/shortcut_customization_ui/resources/BUILD.gn
index 2091ad4..af8ee2c 100644
--- a/ash/webui/shortcut_customization_ui/resources/BUILD.gn
+++ b/ash/webui/shortcut_customization_ui/resources/BUILD.gn
@@ -149,6 +149,7 @@
     ":accelerator_edit_dialog",
     ":shortcut_customization_fonts_css",
     ":shortcut_input",
+    ":shortcut_types",
     "//ash/webui/common/resources:navigation_view_panel",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
   ]
diff --git a/ash/webui/shortcut_customization_ui/resources/shortcut_customization_app.html b/ash/webui/shortcut_customization_ui/resources/shortcut_customization_app.html
index 00221b57..a17da2f 100644
--- a/ash/webui/shortcut_customization_ui/resources/shortcut_customization_app.html
+++ b/ash/webui/shortcut_customization_ui/resources/shortcut_customization_app.html
@@ -8,6 +8,6 @@
 <template is="dom-if" if="[[showEditDialog_]]" restamp>
   <accelerator-edit-dialog id="editDialog"
       description="[[dialogShortcutTitle_]]"
-      accelerators="[[dialogAccelerators_]]">
+      accelerator-infos="[[dialogAccelerators_]]">
   </accelerator-edit-dialog>
 </template>
\ No newline at end of file
diff --git a/ash/webui/shortcut_customization_ui/resources/shortcut_customization_app.js b/ash/webui/shortcut_customization_ui/resources/shortcut_customization_app.js
index 30bf235c..b5dd939 100644
--- a/ash/webui/shortcut_customization_ui/resources/shortcut_customization_app.js
+++ b/ash/webui/shortcut_customization_ui/resources/shortcut_customization_app.js
@@ -10,6 +10,7 @@
 
 import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
+import {AcceleratorInfo} from './shortcut_types.js';
 /**
  * @fileoverview
  * 'shortcut-customization-app' is the main landing page for the shortcut
@@ -33,7 +34,7 @@
       },
 
       /**
-       * @type {!Array<!Object>}
+       * @type {!Array<!AcceleratorInfo>}
        * @private
        */
       dialogAccelerators_: {
@@ -83,12 +84,13 @@
   }
 
   /**
-   * @param {!{description: string, accelerators: !Array<!Object>}} e
+   * @param {!{description: string, accelerators: !Array<!AcceleratorInfo>}} e
    * @private
    */
   showDialog_(e) {
     this.dialogShortcutTitle_ = e.description;
-    this.dialogAccelerators_ = /** @type {!Array<!Object>}*/(e.accelerators);
+    this.dialogAccelerators_ =
+        /** @type {!Array<!AcceleratorInfo>}*/(e.accelerators);
     this.showEditDialog_ = true;
   }
 
diff --git a/ash/wm/drag_details.cc b/ash/wm/drag_details.cc
index 9b1fcd9..83d65e3 100644
--- a/ash/wm/drag_details.cc
+++ b/ash/wm/drag_details.cc
@@ -67,13 +67,15 @@
     gfx::Rect* override_bounds = window->GetProperty(kRestoreBoundsOverrideKey);
     if (override_bounds && !override_bounds->IsEmpty()) {
       restore_bounds = *override_bounds;
-      ::wm::ConvertRectFromScreen(window->parent(), &restore_bounds);
+      wm::ConvertRectFromScreen(window->parent(), &restore_bounds);
     }
-  } else if (window_state->IsSnapped() || window_state->IsMaximized()) {
-    DCHECK(window_state->HasRestoreBounds());
-    restore_bounds = window_state->GetRestoreBoundsInParent();
-  } else if (window_state->IsNormalStateType() &&
-             window_state->HasRestoreBounds()) {
+  } else if (window_state->HasRestoreBounds() &&
+             (window_state->IsNormalOrSnapped() ||
+              window_state->IsMaximized())) {
+    // TODO(sammiequon): Snapped and maximized windows should always have
+    // restore bounds. This is currently not the case for lacros browser after
+    // closing and reopening, see https://crbug.com/1238928. DCHECK for restore
+    // bounds if the window is snapped or maximized after the bug is fixed.
     restore_bounds = window_state->GetRestoreBoundsInParent();
   }
   return restore_bounds;
@@ -84,7 +86,7 @@
 DragDetails::DragDetails(aura::Window* window,
                          const gfx::PointF& location,
                          int window_component,
-                         ::wm::WindowMoveSource source)
+                         wm::WindowMoveSource source)
     : initial_state_type(WindowState::Get(window)->GetStateType()),
       initial_bounds_in_parent(GetWindowInitialBoundsInParent(window)),
       restore_bounds_in_parent(
diff --git a/base/android/java/src/org/chromium/base/process_launcher/ChildProcessConnection.java b/base/android/java/src/org/chromium/base/process_launcher/ChildProcessConnection.java
index e6909d8..23e3aa74 100644
--- a/base/android/java/src/org/chromium/base/process_launcher/ChildProcessConnection.java
+++ b/base/android/java/src/org/chromium/base/process_launcher/ChildProcessConnection.java
@@ -28,7 +28,6 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -98,27 +97,15 @@
         return cl.toString() + cl.hashCode();
     }
 
-    // Global lock to protect all the fields that can be accessed outside launcher thread.
-    private static final Object sBindingStateLock = new Object();
-
-    @GuardedBy("sBindingStateLock")
-    private static final int[] sAllBindingStateCounts = new int[NUM_BINDING_STATES];
-
     // The last zygote PID metrics were recorded for.
     private static final AtomicInteger sLastRecordedZygotePid = new AtomicInteger();
 
-    @VisibleForTesting
-    static void resetBindingStateCountsForTesting() {
-        synchronized (sBindingStateLock) {
-            for (int i = 0; i < NUM_BINDING_STATES; ++i) {
-                sAllBindingStateCounts[i] = 0;
-            }
-        }
-    }
-
     // Only accessed on launcher thread.
     private static boolean sFallbackEnabled;
 
+    // Lock to protect all the fields that can be accessed outside launcher thread.
+    private final Object mBindingStateLock = new Object();
+
     private final Handler mLauncherHandler;
     private final Executor mLauncherExecutor;
     private ComponentName mServiceName;
@@ -214,30 +201,26 @@
     private boolean mUnbound;
 
     // Binding state of this connection.
-    @GuardedBy("sBindingStateLock")
+    @GuardedBy("mBindingStateLock")
     private @ChildBindingState int mBindingState;
 
     // Same as above except it no longer updates after |unbind()|.
-    @GuardedBy("sBindingStateLock")
+    @GuardedBy("mBindingStateLock")
     private @ChildBindingState int mBindingStateCurrentOrWhenDied;
 
     // Indicate |kill()| was called to intentionally kill this process.
-    @GuardedBy("sBindingStateLock")
+    @GuardedBy("mBindingStateLock")
     private boolean mKilledByUs;
 
-    // Copy of |sAllBindingStateCounts| at the time this is unbound.
-    @GuardedBy("sBindingStateLock")
-    private int[] mAllBindingStateCountsWhenDied;
-
     private MemoryPressureCallback mMemoryPressureCallback;
 
     // If the process threw an exception before entering the main loop, the exception
     // string is reported here.
-    @GuardedBy("sBindingStateLock")
+    @GuardedBy("mBindingStateLock")
     private String mExceptionInServiceDuringInit;
 
     // Whether the process exited cleanly or not.
-    @GuardedBy("sBindingStateLock")
+    @GuardedBy("mBindingStateLock")
     private boolean mCleanExit;
 
     public ChildProcessConnection(Context context, ComponentName serviceName,
@@ -460,7 +443,7 @@
         } catch (RemoteException e) {
             // Intentionally ignore since we are killing it anyway.
         }
-        synchronized (sBindingStateLock) {
+        synchronized (mBindingStateLock) {
             mKilledByUs = true;
         }
         notifyChildProcessDied();
@@ -559,14 +542,6 @@
         s.append(mModerateBinding.isBound() ? "M" : " ");
         s.append(mModerateWaiveCpuBinding.isBound() ? "C" : " ");
         s.append(mStrongBinding.isBound() ? "S" : " ");
-
-        synchronized (sBindingStateLock) {
-            s.append(" state:").append(mBindingState);
-            s.append(" counts:");
-            for (int i = 0; i < NUM_BINDING_STATES; ++i) {
-                s.append(sAllBindingStateCounts[i]).append(",");
-            }
-        }
         return s.toString();
     }
 
@@ -608,7 +583,7 @@
 
                 @Override
                 public void reportExceptionInInit(String exception) {
-                    synchronized (sBindingStateLock) {
+                    synchronized (mBindingStateLock) {
                         mExceptionInServiceDuringInit = exception;
                     }
                     mLauncherHandler.post(createUnbindRunnable());
@@ -616,7 +591,7 @@
 
                 @Override
                 public void reportCleanExit() {
-                    synchronized (sBindingStateLock) {
+                    synchronized (mBindingStateLock) {
                         mCleanExit = true;
                     }
                     mLauncherHandler.post(createUnbindRunnable());
@@ -738,11 +713,6 @@
         mModerateWaiveCpuBinding.unbindServiceConnection();
         updateBindingState();
 
-        synchronized (sBindingStateLock) {
-            mAllBindingStateCountsWhenDied =
-                    Arrays.copyOf(sAllBindingStateCounts, NUM_BINDING_STATES);
-        }
-
         if (mMemoryPressureCallback != null) {
             final MemoryPressureCallback callback = mMemoryPressureCallback;
             ThreadUtils.postOnUiThread(() -> MemoryPressureListener.removeCallback(callback));
@@ -884,7 +854,7 @@
         // WARNING: this method can be called from a thread other than the launcher thread.
         // Note that it returns the current waived bound only state and is racy. This not really
         // preventable without changing the caller's API, short of blocking.
-        synchronized (sBindingStateLock) {
+        synchronized (mBindingStateLock) {
             return mBindingStateCurrentOrWhenDied;
         }
     }
@@ -896,7 +866,7 @@
         // WARNING: this method can be called from a thread other than the launcher thread.
         // Note that it returns the current waived bound only state and is racy. This not really
         // preventable without changing the caller's API, short of blocking.
-        synchronized (sBindingStateLock) {
+        synchronized (mBindingStateLock) {
             return mKilledByUs;
         }
     }
@@ -905,7 +875,7 @@
      * @return true if the process exited cleanly.
      */
     public boolean hasCleanExit() {
-        synchronized (sBindingStateLock) {
+        synchronized (mBindingStateLock) {
             return mCleanExit;
         }
     }
@@ -915,36 +885,11 @@
      *         null otherwise.
      */
     public @Nullable String getExceptionDuringInit() {
-        synchronized (sBindingStateLock) {
+        synchronized (mBindingStateLock) {
             return mExceptionInServiceDuringInit;
         }
     }
 
-    /**
-     * Returns the binding state of remaining processes, excluding the current connection.
-     *
-     * If the current process is dead then returns the binding state of all processes when it died.
-     * Otherwise returns current state.
-     */
-    public int[] remainingBindingStateCountsCurrentOrWhenDied() {
-        // WARNING: this method can be called from a thread other than the launcher thread.
-        // Note that it returns the current waived bound only state and is racy. This not really
-        // preventable without changing the caller's API, short of blocking.
-        synchronized (sBindingStateLock) {
-            if (mAllBindingStateCountsWhenDied != null) {
-                return Arrays.copyOf(mAllBindingStateCountsWhenDied, NUM_BINDING_STATES);
-            }
-
-            int[] counts = Arrays.copyOf(sAllBindingStateCounts, NUM_BINDING_STATES);
-            // If current process is still bound then remove it from the counts.
-            if (mBindingState != ChildBindingState.UNBOUND) {
-                assert counts[mBindingState] > 0;
-                counts[mBindingState]--;
-            }
-            return counts;
-        }
-    }
-
     // Should be called any binding is bound or unbound.
     private void updateBindingState() {
         int newBindingState;
@@ -959,16 +904,7 @@
             newBindingState = ChildBindingState.WAIVED;
         }
 
-        synchronized (sBindingStateLock) {
-            if (newBindingState != mBindingState) {
-                if (mBindingState != ChildBindingState.UNBOUND) {
-                    assert sAllBindingStateCounts[mBindingState] > 0;
-                    sAllBindingStateCounts[mBindingState]--;
-                }
-                if (newBindingState != ChildBindingState.UNBOUND) {
-                    sAllBindingStateCounts[newBindingState]++;
-                }
-            }
+        synchronized (mBindingStateLock) {
             mBindingState = newBindingState;
             if (!mUnbound) {
                 mBindingStateCurrentOrWhenDied = mBindingState;
diff --git a/base/android/junit/src/org/chromium/base/process_launcher/ChildProcessConnectionTest.java b/base/android/junit/src/org/chromium/base/process_launcher/ChildProcessConnectionTest.java
index 5ef1099..55de119 100644
--- a/base/android/junit/src/org/chromium/base/process_launcher/ChildProcessConnectionTest.java
+++ b/base/android/junit/src/org/chromium/base/process_launcher/ChildProcessConnectionTest.java
@@ -4,7 +4,6 @@
 
 package org.chromium.base.process_launcher;
 
-import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -402,75 +401,6 @@
     }
 
     @Test
-    public void testBindingStateCounts() {
-        ChildProcessConnection.resetBindingStateCountsForTesting();
-        ChildProcessConnection connection0 = createDefaultTestConnection();
-        ChildServiceConnectionMock connectionMock0 = mFirstServiceConnection;
-        mFirstServiceConnection = null;
-        ChildProcessConnection connection1 = createDefaultTestConnection();
-        ChildServiceConnectionMock connectionMock1 = mFirstServiceConnection;
-        mFirstServiceConnection = null;
-        ChildProcessConnection connection2 = createDefaultTestConnection();
-        ChildServiceConnectionMock connectionMock2 = mFirstServiceConnection;
-        mFirstServiceConnection = null;
-
-        assertArrayEquals(
-                connection0.remainingBindingStateCountsCurrentOrWhenDied(), new int[] {0, 0, 0, 0});
-
-        connection0.start(false /* useStrongBinding */, null /* serviceCallback */);
-        assertArrayEquals(
-                connection0.remainingBindingStateCountsCurrentOrWhenDied(), new int[] {0, 0, 0, 0});
-
-        connection1.start(true /* useStrongBinding */, null /* serviceCallback */);
-        assertArrayEquals(
-                connection0.remainingBindingStateCountsCurrentOrWhenDied(), new int[] {0, 0, 0, 1});
-
-        connection2.start(false /* useStrongBinding */, null /* serviceCallback */);
-        assertArrayEquals(
-                connection0.remainingBindingStateCountsCurrentOrWhenDied(), new int[] {0, 0, 1, 1});
-
-        Binder binder0 = new Binder();
-        Binder binder1 = new Binder();
-        Binder binder2 = new Binder();
-        binder0.attachInterface(mIChildProcessService, IChildProcessService.class.getName());
-        binder1.attachInterface(mIChildProcessService, IChildProcessService.class.getName());
-        binder2.attachInterface(mIChildProcessService, IChildProcessService.class.getName());
-        connectionMock0.notifyServiceConnected(binder0);
-        connectionMock1.notifyServiceConnected(binder1);
-        connectionMock2.notifyServiceConnected(binder2);
-        ShadowLooper.runUiThreadTasks();
-
-        // Add and remove moderate binding works as expected.
-        connection2.removeModerateBinding(false);
-        assertArrayEquals(
-                connection0.remainingBindingStateCountsCurrentOrWhenDied(), new int[] {0, 1, 0, 1});
-        connection2.addModerateBinding(false);
-        assertArrayEquals(
-                connection0.remainingBindingStateCountsCurrentOrWhenDied(), new int[] {0, 0, 1, 1});
-
-        // Add and remove strong binding works as expected.
-        connection0.addStrongBinding();
-        assertArrayEquals(
-                connection0.remainingBindingStateCountsCurrentOrWhenDied(), new int[] {0, 0, 1, 1});
-        connection0.removeStrongBinding();
-        assertArrayEquals(
-                connection0.remainingBindingStateCountsCurrentOrWhenDied(), new int[] {0, 0, 1, 1});
-
-        // Stopped connection should no longe update.
-        connection0.stop();
-        assertArrayEquals(
-                connection0.remainingBindingStateCountsCurrentOrWhenDied(), new int[] {0, 0, 1, 1});
-        assertArrayEquals(
-                connection1.remainingBindingStateCountsCurrentOrWhenDied(), new int[] {0, 0, 1, 0});
-
-        connection2.removeModerateBinding(false);
-        assertArrayEquals(
-                connection0.remainingBindingStateCountsCurrentOrWhenDied(), new int[] {0, 0, 1, 1});
-        assertArrayEquals(
-                connection1.remainingBindingStateCountsCurrentOrWhenDied(), new int[] {0, 1, 0, 0});
-    }
-
-    @Test
     public void testUpdateGroupImportanceSmoke() throws RemoteException {
         ChildProcessConnection connection = createDefaultTestConnection();
         connection.start(false /* useStrongBinding */, null /* serviceCallback */);
diff --git a/base/threading/thread_restrictions.h b/base/threading/thread_restrictions.h
index 088e966..67f44e5 100644
--- a/base/threading/thread_restrictions.h
+++ b/base/threading/thread_restrictions.h
@@ -148,6 +148,8 @@
 class BlockingMethodCaller;
 namespace system {
 class StatisticsProviderImpl;
+bool IsCoreSchedulingAvailable();
+int NumberOfProcessorsForCoreScheduling();
 }
 }
 namespace chrome_browser_net {
@@ -448,8 +450,10 @@
   friend class weblayer::ProfileImpl;
   friend class weblayer::WebLayerPathProvider;
 
-  friend bool PathProviderWin(int, FilePath*);
   friend Profile* ::GetLastProfileMac();  // crbug.com/1176734
+  friend bool PathProviderWin(int, FilePath*);
+  friend bool chromeos::system::IsCoreSchedulingAvailable();
+  friend int chromeos::system::NumberOfProcessorsForCoreScheduling();
 
   ScopedAllowBlocking(const Location& from_here = Location::Current());
   ~ScopedAllowBlocking();
diff --git a/cc/OWNERS b/cc/OWNERS
index 97558ed31..2d8d597 100644
--- a/cc/OWNERS
+++ b/cc/OWNERS
@@ -48,6 +48,7 @@
 
 # metrics
 sadrul@chromium.org
+behdadb@chromium.org
 
 # paint
 sunnyps@chromium.org
diff --git a/chrome/android/features/tab_ui/java/res/drawable-v21/fake_search_box_bg_incognito.xml b/chrome/android/features/tab_ui/java/res/drawable-v21/fake_search_box_bg_incognito.xml
index 895fb81..210134d 100644
--- a/chrome/android/features/tab_ui/java/res/drawable-v21/fake_search_box_bg_incognito.xml
+++ b/chrome/android/features/tab_ui/java/res/drawable-v21/fake_search_box_bg_incognito.xml
@@ -8,6 +8,7 @@
     android:color="@color/toolbar_text_box_background_incognito" >
 
     <item
+        android:id="@+id/fake_search_box_bg_shape"
         android:drawable="@drawable/fake_search_box_text_box_bg_incognito"/>
 
 </ripple>
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksView.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksView.java
index 0561f41..5cd6749 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksView.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksView.java
@@ -7,6 +7,9 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.RippleDrawable;
 import android.os.Build;
 import android.util.AttributeSet;
 import android.view.View;
@@ -16,6 +19,7 @@
 import android.widget.FrameLayout;
 import android.widget.TextView;
 
+import androidx.annotation.ColorInt;
 import androidx.annotation.Nullable;
 import androidx.appcompat.content.res.AppCompatResources;
 import androidx.core.view.ViewCompat;
@@ -164,8 +168,24 @@
         mHeaderView.setBackgroundColor(backgroundColor);
 
         mSearchBoxCoordinator.setIncognitoMode(isIncognito);
-        mSearchBoxCoordinator.setBackground(AppCompatResources.getDrawable(mContext,
-                isIncognito ? R.drawable.fake_search_box_bg_incognito : R.drawable.ntp_search_box));
+        Drawable searchBackground = AppCompatResources.getDrawable(mContext,
+                isIncognito ? R.drawable.fake_search_box_bg_incognito : R.drawable.ntp_search_box);
+        if (searchBackground instanceof RippleDrawable) {
+            Drawable shapeDrawable = ((RippleDrawable) searchBackground)
+                                             .findDrawableByLayerId(R.id.fake_search_box_bg_shape);
+            if (shapeDrawable != null) {
+                @ColorInt
+                int searchBackgroundColor = isIncognito
+                        ? getResources().getColor(R.color.toolbar_text_box_background_incognito)
+                        : ChromeColors.getSurfaceColor(
+                                mContext, R.dimen.toolbar_text_box_elevation);
+                shapeDrawable.mutate();
+                // TODO(https://crbug.com/1239289): Change back to #setTint once our min API level
+                // is 23.
+                shapeDrawable.setColorFilter(searchBackgroundColor, PorterDuff.Mode.SRC_IN);
+            }
+        }
+        mSearchBoxCoordinator.setBackground(searchBackground);
         int hintTextColor = isIncognito
                 ? ApiCompatibilityUtils.getColor(resources, R.color.locationbar_light_hint_text)
                 : ApiCompatibilityUtils.getColor(resources, R.color.locationbar_dark_hint_text);
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinator.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinator.java
index f51cd2d3..b72b5a96 100644
--- a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinator.java
+++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinator.java
@@ -703,7 +703,7 @@
 
         mScrollViewForPolicy = new PolicyScrollView(mActivity);
         mScrollViewForPolicy.setBackgroundColor(
-                MaterialColors.getColor(mActivity, R.attr.default_bg_color, TAG));
+                MaterialColors.getColor(mActivity, R.attr.default_bg_color_dynamic, TAG));
         mScrollViewForPolicy.setVerticalScrollBarEnabled(false);
 
         // Make scroll view focusable so that it is the next focusable view when the url bar clears
diff --git a/chrome/android/java/res/values/styles.xml b/chrome/android/java/res/values/styles.xml
index 9aaa11e..6ac05ef1 100644
--- a/chrome/android/java/res/values/styles.xml
+++ b/chrome/android/java/res/values/styles.xml
@@ -331,7 +331,7 @@
     <style name="TabBarLineShadow">
         <item name="android:layout_width">match_parent</item>
         <item name="android:layout_height">1dp</item>
-        <item name="android:src">@color/hairline_stroke_color</item>
+        <item name="android:src">@color/divider_line_bg_color</item>
         <item name="android:scaleType">fitXY</item>
     </style>
     <style name="TextAppearance.UpdateMenuItem">
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/WebsitePermissionsFetcherTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/WebsitePermissionsFetcherTest.java
index 458964a9..8016da06 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/WebsitePermissionsFetcherTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/WebsitePermissionsFetcherTest.java
@@ -493,7 +493,7 @@
         // If the ContentSettingsType.NUM_TYPES value changes *and* a new value has been exposed on
         // Android, then please update this code block to include a test for your new type.
         // Otherwise, just update count in the assert.
-        Assert.assertEquals(71, ContentSettingsType.NUM_TYPES);
+        Assert.assertEquals(72, ContentSettingsType.NUM_TYPES);
         websitePreferenceBridge.addContentSettingException(
                 new ContentSettingException(ContentSettingsType.COOKIES, googleOrigin,
                         ContentSettingValues.DEFAULT, preferenceSource));
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/webauth/Fido2CredentialRequestTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/webauth/Fido2CredentialRequestTest.java
index 8026e91..cb46183 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/webauth/Fido2CredentialRequestTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/webauth/Fido2CredentialRequestTest.java
@@ -38,6 +38,7 @@
 import org.chromium.blink.mojom.AuthenticatorStatus;
 import org.chromium.blink.mojom.GetAssertionAuthenticatorResponse;
 import org.chromium.blink.mojom.MakeCredentialAuthenticatorResponse;
+import org.chromium.blink.mojom.PaymentOptions;
 import org.chromium.blink.mojom.PublicKeyCredentialCreationOptions;
 import org.chromium.blink.mojom.PublicKeyCredentialParameters;
 import org.chromium.blink.mojom.PublicKeyCredentialRequestOptions;
@@ -295,9 +296,10 @@
 
         @Override
         protected void getAssertion(PublicKeyCredentialRequestOptions options,
-                RenderFrameHost frameHost, Origin origin, GetAssertionResponseCallback callback,
-                FidoErrorResponseCallback errorCallback) {
-            mRequest.handleGetAssertionRequest(options, frameHost, origin, callback, errorCallback);
+                RenderFrameHost frameHost, Origin origin, PaymentOptions payment,
+                GetAssertionResponseCallback callback, FidoErrorResponseCallback errorCallback) {
+            mRequest.handleGetAssertionRequest(
+                    options, frameHost, origin, payment, callback, errorCallback);
         }
 
         @Override
@@ -741,7 +743,7 @@
         mWindowAndroid.setResponseIntent(Fido2ApiTestHelper.createSuccessfulGetAssertionIntent());
         TestThreadUtils.runOnUiThreadBlocking(() -> mRequest.setWindowForTesting(mWindowAndroid));
 
-        mRequest.handleGetAssertionRequest(mRequestOptions, mFrameHost, mOrigin,
+        mRequest.handleGetAssertionRequest(mRequestOptions, mFrameHost, mOrigin, /*payment=*/null,
                 (responseStatus, response)
                         -> mCallback.onSignResponse(responseStatus, response),
                 errorStatus -> mCallback.onError(errorStatus));
@@ -757,7 +759,7 @@
         TestThreadUtils.runOnUiThreadBlocking(() -> mRequest.setWindowForTesting(mWindowAndroid));
 
         mRequestOptions.userVerificationMethods = true;
-        mRequest.handleGetAssertionRequest(mRequestOptions, mFrameHost, mOrigin,
+        mRequest.handleGetAssertionRequest(mRequestOptions, mFrameHost, mOrigin, /*payment=*/null,
                 (responseStatus, response)
                         -> mCallback.onSignResponse(responseStatus, response),
                 errorStatus -> mCallback.onError(errorStatus));
@@ -775,7 +777,7 @@
         TestThreadUtils.runOnUiThreadBlocking(() -> mRequest.setWindowForTesting(mWindowAndroid));
 
         mRequestOptions.userVerificationMethods = true;
-        mRequest.handleGetAssertionRequest(mRequestOptions, mFrameHost, mOrigin,
+        mRequest.handleGetAssertionRequest(mRequestOptions, mFrameHost, mOrigin, /*payment=*/null,
                 (responseStatus, response)
                         -> mCallback.onSignResponse(responseStatus, response),
                 errorStatus -> mCallback.onError(errorStatus));
@@ -791,7 +793,7 @@
         mWindowAndroid.setCancelableIntentSuccess(false);
         TestThreadUtils.runOnUiThreadBlocking(() -> mRequest.setWindowForTesting(mWindowAndroid));
 
-        mRequest.handleGetAssertionRequest(mRequestOptions, mFrameHost, mOrigin,
+        mRequest.handleGetAssertionRequest(mRequestOptions, mFrameHost, mOrigin, /*payment=*/null,
                 (responseStatus, response)
                         -> mCallback.onSignResponse(responseStatus, response),
                 errorStatus -> mCallback.onError(errorStatus));
@@ -808,7 +810,7 @@
         mWindowAndroid.setResponseIntent(new Intent());
         TestThreadUtils.runOnUiThreadBlocking(() -> mRequest.setWindowForTesting(mWindowAndroid));
 
-        mRequest.handleGetAssertionRequest(mRequestOptions, mFrameHost, mOrigin,
+        mRequest.handleGetAssertionRequest(mRequestOptions, mFrameHost, mOrigin, /*payment=*/null,
                 (responseStatus, response)
                         -> mCallback.onSignResponse(responseStatus, response),
                 errorStatus -> mCallback.onError(errorStatus));
@@ -824,7 +826,7 @@
         // Don't set an intent to be returned at all.
         TestThreadUtils.runOnUiThreadBlocking(() -> mRequest.setWindowForTesting(mWindowAndroid));
 
-        mRequest.handleGetAssertionRequest(mRequestOptions, mFrameHost, mOrigin,
+        mRequest.handleGetAssertionRequest(mRequestOptions, mFrameHost, mOrigin, /*payment=*/null,
                 (responseStatus, response)
                         -> mCallback.onSignResponse(responseStatus, response),
                 errorStatus -> mCallback.onError(errorStatus));
@@ -840,7 +842,7 @@
         mWindowAndroid.setResultCode(Activity.RESULT_CANCELED);
         TestThreadUtils.runOnUiThreadBlocking(() -> mRequest.setWindowForTesting(mWindowAndroid));
 
-        mRequest.handleGetAssertionRequest(mRequestOptions, mFrameHost, mOrigin,
+        mRequest.handleGetAssertionRequest(mRequestOptions, mFrameHost, mOrigin, /*payment=*/null,
                 (responseStatus, response)
                         -> mCallback.onSignResponse(responseStatus, response),
                 errorStatus -> mCallback.onError(errorStatus));
@@ -857,7 +859,7 @@
         mWindowAndroid.setResultCode(Activity.RESULT_FIRST_USER);
         TestThreadUtils.runOnUiThreadBlocking(() -> mRequest.setWindowForTesting(mWindowAndroid));
 
-        mRequest.handleGetAssertionRequest(mRequestOptions, mFrameHost, mOrigin,
+        mRequest.handleGetAssertionRequest(mRequestOptions, mFrameHost, mOrigin, /*payment=*/null,
                 (responseStatus, response)
                         -> mCallback.onSignResponse(responseStatus, response),
                 errorStatus -> mCallback.onError(errorStatus));
@@ -874,7 +876,7 @@
                 ErrorCode.UNKNOWN_ERR, "Low level error 0x6a80"));
         TestThreadUtils.runOnUiThreadBlocking(() -> mRequest.setWindowForTesting(mWindowAndroid));
 
-        mRequest.handleGetAssertionRequest(mRequestOptions, mFrameHost, mOrigin,
+        mRequest.handleGetAssertionRequest(mRequestOptions, mFrameHost, mOrigin, /*payment=*/null,
                 (responseStatus, response)
                         -> mCallback.onSignResponse(responseStatus, response),
                 errorStatus -> mCallback.onError(errorStatus));
@@ -892,7 +894,7 @@
         mWindowAndroid.setResponseIntent(Fido2ApiTestHelper.createSuccessfulGetAssertionIntent());
         TestThreadUtils.runOnUiThreadBlocking(() -> mRequest.setWindowForTesting(mWindowAndroid));
 
-        mRequest.handleGetAssertionRequest(mRequestOptions, mFrameHost, mOrigin,
+        mRequest.handleGetAssertionRequest(mRequestOptions, mFrameHost, mOrigin, /*payment=*/null,
                 (responseStatus, response)
                         -> mCallback.onSignResponse(responseStatus, response),
                 errorStatus -> mCallback.onError(errorStatus));
@@ -1094,7 +1096,7 @@
         mWindowAndroid.setResponseIntent(Fido2ApiTestHelper.createErrorIntent(
                 ErrorCode.NOT_ALLOWED_ERR, "Authentication request must have non-empty allowList"));
         TestThreadUtils.runOnUiThreadBlocking(() -> mRequest.setWindowForTesting(mWindowAndroid));
-        mRequest.handleGetAssertionRequest(mRequestOptions, mFrameHost, mOrigin,
+        mRequest.handleGetAssertionRequest(mRequestOptions, mFrameHost, mOrigin, /*payment=*/null,
                 (responseStatus, response)
                         -> mCallback.onSignResponse(responseStatus, response),
                 errorStatus -> mCallback.onError(errorStatus));
@@ -1116,7 +1118,7 @@
                 Fido2ApiTestHelper.createErrorIntent(ErrorCode.NOT_ALLOWED_ERR,
                         "Request doesn't have a valid list of allowed credentials."));
         TestThreadUtils.runOnUiThreadBlocking(() -> mRequest.setWindowForTesting(mWindowAndroid));
-        mRequest.handleGetAssertionRequest(mRequestOptions, mFrameHost, mOrigin,
+        mRequest.handleGetAssertionRequest(mRequestOptions, mFrameHost, mOrigin, /*payment=*/null,
                 (responseStatus, response)
                         -> mCallback.onSignResponse(responseStatus, response),
                 errorStatus -> mCallback.onError(errorStatus));
@@ -1149,7 +1151,7 @@
         mWindowAndroid.setResponseIntent(Fido2ApiTestHelper.createErrorIntent(
                 ErrorCode.CONSTRAINT_ERR, "The device is not secured with any screen lock"));
         TestThreadUtils.runOnUiThreadBlocking(() -> mRequest.setWindowForTesting(mWindowAndroid));
-        mRequest.handleGetAssertionRequest(mRequestOptions, mFrameHost, mOrigin,
+        mRequest.handleGetAssertionRequest(mRequestOptions, mFrameHost, mOrigin, /*payment=*/null,
                 (responseStatus, response)
                         -> mCallback.onSignResponse(responseStatus, response),
                 errorStatus -> mCallback.onError(errorStatus));
@@ -1183,7 +1185,7 @@
         mWindowAndroid.setResponseIntent(
                 Fido2ApiTestHelper.createErrorIntent(ErrorCode.valueOf(errorCodeName), errorMsg));
         TestThreadUtils.runOnUiThreadBlocking(() -> mRequest.setWindowForTesting(mWindowAndroid));
-        mRequest.handleGetAssertionRequest(mRequestOptions, mFrameHost, mOrigin,
+        mRequest.handleGetAssertionRequest(mRequestOptions, mFrameHost, mOrigin, /*payment=*/null,
                 (responseStatus, response)
                         -> mCallback.onSignResponse(responseStatus, response),
                 errorStatus -> mCallback.onError(errorStatus));
@@ -1217,7 +1219,7 @@
         mWindowAndroid.setResponseIntent(
                 Fido2ApiTestHelper.createErrorIntent(ErrorCode.valueOf(errorCodeName), null));
         TestThreadUtils.runOnUiThreadBlocking(() -> mRequest.setWindowForTesting(mWindowAndroid));
-        mRequest.handleGetAssertionRequest(mRequestOptions, mFrameHost, mOrigin,
+        mRequest.handleGetAssertionRequest(mRequestOptions, mFrameHost, mOrigin, /*payment=*/null,
                 (responseStatus, response)
                         -> mCallback.onSignResponse(responseStatus, response),
                 errorStatus -> mCallback.onError(errorStatus));
@@ -1239,9 +1241,9 @@
 
         mMockWebContents.setLastCommittedUrl(new GURL("https://www.chromium.org/pay"));
 
-        mRequestOptions.payment = Fido2ApiTestHelper.createPaymentOptions();
+        PaymentOptions payment = Fido2ApiTestHelper.createPaymentOptions();
         mRequestOptions.challenge = new byte[3];
-        mRequest.handleGetAssertionRequest(mRequestOptions, mFrameHost, mOrigin,
+        mRequest.handleGetAssertionRequest(mRequestOptions, mFrameHost, mOrigin, payment,
                 (responseStatus, response)
                         -> mCallback.onSignResponse(responseStatus, response),
                 errorStatus -> mCallback.onError(errorStatus));
@@ -1265,9 +1267,9 @@
 
         mMockWebContents.setLastCommittedUrl(new GURL("https://www.chromium.org/pay"));
 
-        mRequestOptions.payment = Fido2ApiTestHelper.createPaymentOptions();
+        PaymentOptions payment = Fido2ApiTestHelper.createPaymentOptions();
         mRequestOptions.challenge = new byte[3];
-        mRequest.handleGetAssertionRequest(mRequestOptions, mFrameHost, mOrigin,
+        mRequest.handleGetAssertionRequest(mRequestOptions, mFrameHost, mOrigin, payment,
                 (responseStatus, response)
                         -> mCallback.onSignResponse(responseStatus, response),
                 errorStatus -> mCallback.onError(errorStatus));
diff --git a/chrome/app/chrome_main_delegate.cc b/chrome/app/chrome_main_delegate.cc
index 73d78a39..f3e4c8f 100644
--- a/chrome/app/chrome_main_delegate.cc
+++ b/chrome/app/chrome_main_delegate.cc
@@ -1170,7 +1170,7 @@
   crash_keys::SetCrashKeysFromCommandLine(command_line);
 
 #if BUILDFLAG(ENABLE_PDF)
-  MaybeInitializeGDI();
+  MaybePatchGdiGetFontData();
 #endif
 }
 
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 850ccbe..f0fee1d3 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -1864,6 +1864,7 @@
     "//base",
     "//chrome/common",
     "//chrome/common:buildflags",
+    "//chrome/common:constants",
     "//chrome/services/file_util/public/mojom",
     "//components/account_id",
     "//components/autofill/core/browser",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 19623ab8..144e456 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -7493,6 +7493,13 @@
      FEATURE_VALUE_TYPE(
          password_manager::features::kPasswordsAccountStorageRevisedOptInFlow)},
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+    {"traffic-counters-settings-ui",
+     flag_descriptions::kTrafficCountersSettingsUiName,
+     flag_descriptions::kTrafficCountersSettingsUiDescription, kOsCrOS,
+     FEATURE_VALUE_TYPE(ash::features::kTrafficCountersSettingsUi)},
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
     // NOTE: Adding a new flag requires adding a corresponding entry to enum
     // "LoginCustomFlags" in tools/metrics/histograms/enums.xml. See "Flag
     // Histograms" in tools/metrics/histograms/README.md (run the
diff --git a/chrome/browser/ash/accessibility/select_to_speak_browsertest.cc b/chrome/browser/ash/accessibility/select_to_speak_browsertest.cc
index 4ef3ae6..1cb74f8 100644
--- a/chrome/browser/ash/accessibility/select_to_speak_browsertest.cc
+++ b/chrome/browser/ash/accessibility/select_to_speak_browsertest.cc
@@ -82,6 +82,13 @@
     ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL));
   }
 
+  void SetUpInProcessBrowserTestFixture() override {
+    // TODO (leileilei@google.com): Provide a way to disable the pop up dialog.
+    // Disable kEnhancedNetworkVoices To avoid its pop up dialog.
+    scoped_feature_list_.InitAndDisableFeature(
+        ::features::kEnhancedNetworkVoices);
+  }
+
   test::SpeechMonitor sm_;
   std::unique_ptr<ui::test::EventGenerator> generator_;
   std::unique_ptr<SystemTrayTestApi> tray_test_api_;
@@ -155,6 +162,7 @@
   }
 
  private:
+  base::test::ScopedFeatureList scoped_feature_list_;
   scoped_refptr<content::MessageLoopRunner> loop_runner_;
   scoped_refptr<content::MessageLoopRunner> tray_loop_runner_;
   base::WeakPtrFactory<SelectToSpeakTest> weak_ptr_factory_{this};
@@ -598,19 +606,7 @@
   sm_.Replay();
 }
 
-/* Test fixture enabling navigation control */
-class SelectToSpeakTestWithNavigationControl : public SelectToSpeakTest {
- public:
-  void SetUpInProcessBrowserTestFixture() override {
-    scoped_feature_list_.InitAndEnableFeature(
-        ::features::kSelectToSpeakNavigationControl);
-  }
-
- private:
-  base::test::ScopedFeatureList scoped_feature_list_;
-};
-
-IN_PROC_BROWSER_TEST_F(SelectToSpeakTestWithNavigationControl,
+IN_PROC_BROWSER_TEST_F(SelectToSpeakTest,
                        SelectToSpeakDoesNotDismissTrayBubble) {
   // Open tray bubble menu.
   tray_test_api_->ShowBubble();
diff --git a/chrome/browser/ash/file_manager/file_manager_browsertest_base.cc b/chrome/browser/ash/file_manager/file_manager_browsertest_base.cc
index 01e3cff..c9d5bac0 100644
--- a/chrome/browser/ash/file_manager/file_manager_browsertest_base.cc
+++ b/chrome/browser/ash/file_manager/file_manager_browsertest_base.cc
@@ -97,7 +97,6 @@
 #include "components/prefs/pref_service.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/navigation_handle.h"
-#include "content/public/browser/notification_service.h"
 #include "content/public/browser/storage_partition.h"
 #include "content/public/common/content_switches.h"
 #include "content/public/test/browser_test_utils.h"
@@ -105,11 +104,12 @@
 #include "content/public/test/test_navigation_observer.h"
 #include "content/public/test/test_utils.h"
 #include "extensions/browser/api/test/test_api.h"
+#include "extensions/browser/api/test/test_api_observer.h"
+#include "extensions/browser/api/test/test_api_observer_registry.h"
 #include "extensions/browser/app_window/app_window.h"
 #include "extensions/browser/app_window/app_window_registry.h"
 #include "extensions/browser/event_router.h"
 #include "extensions/browser/extension_function_registry.h"
-#include "extensions/browser/notification_types.h"
 #include "extensions/common/api/test.h"
 #include "google_apis/common/test_util.h"
 #include "google_apis/drive/drive_api_parser.h"
@@ -506,23 +506,27 @@
 };
 
 // Listens for chrome.test messages: PASS, FAIL, and SendMessage.
-class FileManagerTestMessageListener : public content::NotificationObserver {
+class FileManagerTestMessageListener : public extensions::TestApiObserver {
  public:
   struct Message {
-    int type;
+    enum class Completion {
+      kNone,
+      kPass,
+      kFail,
+    };
+
+    Completion completion;
     std::string message;
     scoped_refptr<extensions::TestSendMessageFunction> function;
   };
 
   FileManagerTestMessageListener() {
-    registrar_.Add(this, extensions::NOTIFICATION_EXTENSION_TEST_PASSED,
-                   content::NotificationService::AllSources());
-    registrar_.Add(this, extensions::NOTIFICATION_EXTENSION_TEST_FAILED,
-                   content::NotificationService::AllSources());
-    registrar_.Add(this, extensions::NOTIFICATION_EXTENSION_TEST_MESSAGE,
-                   content::NotificationService::AllSources());
+    test_api_observation_.Observe(
+        extensions::TestApiObserverRegistry::GetInstance());
   }
 
+  ~FileManagerTestMessageListener() override = default;
+
   Message GetNextMessage() {
     DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
@@ -538,37 +542,38 @@
     return next;
   }
 
-  void Observe(int type,
-               const content::NotificationSource& source,
-               const content::NotificationDetails& details) override {
-    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ private:
+  // extensions::TestApiObserver:
+  void OnTestPassed(content::BrowserContext* browser_context) override {
+    test_complete_ = true;
+    QueueMessage({Message::Completion::kPass, std::string(), nullptr});
+  }
+  void OnTestFailed(content::BrowserContext* browser_context,
+                    const std::string& message) override {
+    test_complete_ = true;
+    QueueMessage({Message::Completion::kFail, message, nullptr});
+  }
+  bool OnTestMessage(extensions::TestSendMessageFunction* function,
+                     const std::string& message) override {
+    // crbug.com/668680
+    EXPECT_FALSE(test_complete_) << "LATE MESSAGE: " << message;
+    QueueMessage({Message::Completion::kNone, message, function});
+    return true;
+  }
 
-    Message message{type, std::string(), nullptr};
-    if (type == extensions::NOTIFICATION_EXTENSION_TEST_PASSED) {
-      test_complete_ = true;
-    } else if (type == extensions::NOTIFICATION_EXTENSION_TEST_FAILED) {
-      message.message = *content::Details<std::string>(details).ptr();
-      test_complete_ = true;
-    } else if (type == extensions::NOTIFICATION_EXTENSION_TEST_MESSAGE) {
-      message.message = *content::Details<std::string>(details).ptr();
-      using SendMessage = content::Source<extensions::TestSendMessageFunction>;
-      message.function = SendMessage(source).ptr();
-      using WillReply = content::Details<std::pair<std::string, bool*>>;
-      *WillReply(details).ptr()->second = true;  // crbug.com/668680
-      CHECK(!test_complete_) << "LATE MESSAGE: " << message.message;
-    }
-
+  void QueueMessage(const Message& message) {
     messages_.push_back(message);
     if (quit_closure_) {
       std::move(quit_closure_).Run();
     }
   }
 
- private:
   bool test_complete_ = false;
   base::OnceClosure quit_closure_;
   base::circular_deque<Message> messages_;
-  content::NotificationRegistrar registrar_;
+  base::ScopedObservation<extensions::TestApiObserverRegistry,
+                          extensions::TestApiObserver>
+      test_api_observation_{this};
 
   DISALLOW_COPY_AND_ASSIGN(FileManagerTestMessageListener);
 };
@@ -1982,9 +1987,12 @@
   while (true) {
     auto message = listener.GetNextMessage();
 
-    if (message.type == extensions::NOTIFICATION_EXTENSION_TEST_PASSED)
+    if (message.completion ==
+        FileManagerTestMessageListener::Message::Completion::kPass) {
       return;  // Test PASSED.
-    if (message.type == extensions::NOTIFICATION_EXTENSION_TEST_FAILED) {
+    }
+    if (message.completion ==
+        FileManagerTestMessageListener::Message::Completion::kFail) {
       ADD_FAILURE() << message.message;
       return;  // Test FAILED.
     }
diff --git a/chrome/browser/ash/login/easy_unlock/easy_unlock_service.cc b/chrome/browser/ash/login/easy_unlock/easy_unlock_service.cc
index ea79c6f5..a1736f4 100644
--- a/chrome/browser/ash/login/easy_unlock/easy_unlock_service.cc
+++ b/chrome/browser/ash/login/easy_unlock/easy_unlock_service.cc
@@ -699,4 +699,11 @@
   tpm_key_checked_ = true;
 }
 
+std::string EasyUnlockService::GetLastRemoteStatusUnlockForLogging() {
+  if (proximity_auth_system_) {
+    return proximity_auth_system_->GetLastRemoteStatusUnlockForLogging();
+  }
+  return std::string();
+}
+
 }  // namespace ash
diff --git a/chrome/browser/ash/login/easy_unlock/easy_unlock_service.h b/chrome/browser/ash/login/easy_unlock/easy_unlock_service.h
index 7786264..cf31d2b 100644
--- a/chrome/browser/ash/login/easy_unlock/easy_unlock_service.h
+++ b/chrome/browser/ash/login/easy_unlock/easy_unlock_service.h
@@ -160,6 +160,12 @@
     return &proximity_auth_client_;
   }
 
+  // The last value emitted to the SmartLock.GetRemoteStatus.Unlock(.Failure)
+  // metrics. Helps to understand whether/why not Smart Lock was an available
+  // choice for unlock. Returns the empty string if the ProximityAuthSystem or
+  // the UnlockManager is uninitialized.
+  std::string GetLastRemoteStatusUnlockForLogging();
+
  protected:
   EasyUnlockService(Profile* profile,
                     secure_channel::SecureChannelClient* secure_channel_client);
diff --git a/chrome/browser/ash/login/enrollment/auto_enrollment_controller.cc b/chrome/browser/ash/login/enrollment/auto_enrollment_controller.cc
index 915fe75..0f787d9 100644
--- a/chrome/browser/ash/login/enrollment/auto_enrollment_controller.cc
+++ b/chrome/browser/ash/login/enrollment/auto_enrollment_controller.cc
@@ -368,6 +368,12 @@
 }
 
 // static
+bool AutoEnrollmentController::ShouldUseFakePsmRlweClient() {
+  return base::CommandLine::ForCurrentProcess()->HasSwitch(
+      switches::kEnterpriseUseFakePsmRlweClient);
+}
+
+// static
 bool AutoEnrollmentController::IsEnabled() {
   return IsFREEnabled() || IsInitialEnrollmentEnabled();
 }
diff --git a/chrome/browser/ash/login/enrollment/auto_enrollment_controller.h b/chrome/browser/ash/login/enrollment/auto_enrollment_controller.h
index 90a59b4..7013b03f 100644
--- a/chrome/browser/ash/login/enrollment/auto_enrollment_controller.h
+++ b/chrome/browser/ash/login/enrollment/auto_enrollment_controller.h
@@ -105,6 +105,10 @@
   // command-line flags.
   static bool IsPsmEnabled();
 
+  // Returns true if it is determined to use the fake PSM RLWE client based on
+  // command-line flags.
+  static bool ShouldUseFakePsmRlweClient();
+
   // Returns whether the FRE auto-enrollment check is required. When
   // kCheckEnrollmentKey VPD entry is present, it is explicitly stating whether
   // the forced re-enrollment is required or not. Otherwise, for backward
diff --git a/chrome/browser/ash/policy/enrollment/private_membership/OWNERS b/chrome/browser/ash/policy/enrollment/private_membership/OWNERS
new file mode 100644
index 0000000..4a248a696
--- /dev/null
+++ b/chrome/browser/ash/policy/enrollment/private_membership/OWNERS
@@ -0,0 +1 @@
+amraboelkher@chromium.org
diff --git a/chrome/browser/ash/policy/enrollment/private_membership/private_membership_rlwe_client.h b/chrome/browser/ash/policy/enrollment/private_membership/private_membership_rlwe_client.h
new file mode 100644
index 0000000..1a74e92
--- /dev/null
+++ b/chrome/browser/ash/policy/enrollment/private_membership/private_membership_rlwe_client.h
@@ -0,0 +1,64 @@
+// 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 CHROME_BROWSER_ASH_POLICY_ENROLLMENT_PRIVATE_MEMBERSHIP_PRIVATE_MEMBERSHIP_RLWE_CLIENT_H_
+#define CHROME_BROWSER_ASH_POLICY_ENROLLMENT_PRIVATE_MEMBERSHIP_PRIVATE_MEMBERSHIP_RLWE_CLIENT_H_
+
+#include <memory>
+#include <vector>
+
+#include "third_party/private_membership/src/private_membership_rlwe.pb.h"
+#include "third_party/shell-encryption/src/statusor.h"
+
+namespace private_membership {
+namespace rlwe {
+class MembershipResponseMap;
+}  // namespace rlwe
+}  // namespace private_membership
+
+namespace policy {
+
+// Interface for the Private Membership RLWE Client, allowing to replace the
+// private membership RLWE client library with a fake for tests.
+class PrivateMembershipRlweClient {
+ public:
+  class Factory {
+   public:
+    virtual ~Factory() = default;
+    // Creates a client for the Private Membership RLWE protocol. It will be
+    // created for |plaintext_ids| with use case as |use_case|.
+    virtual ::rlwe::StatusOr<std::unique_ptr<PrivateMembershipRlweClient>>
+    Create(private_membership::rlwe::RlweUseCase use_case,
+           const std::vector<private_membership::rlwe::RlwePlaintextId>&
+               plaintext_ids) = 0;
+  };
+
+  virtual ~PrivateMembershipRlweClient() = default;
+
+  // Creates a request proto for the first phase of the protocol.
+  virtual ::rlwe::StatusOr<
+      private_membership::rlwe::PrivateMembershipRlweOprfRequest>
+  CreateOprfRequest() = 0;
+
+  // Creates a request proto for the second phase of the protocol.
+  virtual ::rlwe::StatusOr<
+      private_membership::rlwe::PrivateMembershipRlweQueryRequest>
+  CreateQueryRequest(
+      const private_membership::rlwe::PrivateMembershipRlweOprfResponse&
+          oprf_response) = 0;
+
+  // Processes the query response from the server and returns the membership
+  // response map.
+  //
+  // Keys of the returned map corresponds to the original plaintext ids supplied
+  // to the client when it was created.
+  virtual ::rlwe::StatusOr<private_membership::rlwe::MembershipResponseMap>
+  ProcessResponse(
+      const private_membership::rlwe::PrivateMembershipRlweQueryResponse&
+          query_response) = 0;
+};
+
+}  // namespace policy
+
+#endif  // CHROME_BROWSER_ASH_POLICY_ENROLLMENT_PRIVATE_MEMBERSHIP_PRIVATE_MEMBERSHIP_RLWE_CLIENT_H_
diff --git a/chrome/browser/ash/policy/enrollment/private_membership/private_membership_rlwe_client_impl.cc b/chrome/browser/ash/policy/enrollment/private_membership/private_membership_rlwe_client_impl.cc
new file mode 100644
index 0000000..af386452e
--- /dev/null
+++ b/chrome/browser/ash/policy/enrollment/private_membership/private_membership_rlwe_client_impl.cc
@@ -0,0 +1,66 @@
+// 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.
+
+#include "chrome/browser/ash/policy/enrollment/private_membership/private_membership_rlwe_client_impl.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/check.h"
+#include "chrome/browser/ash/policy/enrollment/private_membership/private_membership_rlwe_client.h"
+#include "third_party/private_membership/src/membership_response_map.h"
+#include "third_party/private_membership/src/private_membership_rlwe.pb.h"
+#include "third_party/private_membership/src/private_membership_rlwe_client.h"
+#include "third_party/shell-encryption/src/statusor.h"
+
+namespace psm_rlwe = private_membership::rlwe;
+
+namespace policy {
+
+PrivateMembershipRlweClientImpl::FactoryImpl::FactoryImpl() = default;
+
+::rlwe::StatusOr<std::unique_ptr<PrivateMembershipRlweClient>>
+PrivateMembershipRlweClientImpl::FactoryImpl::Create(
+    psm_rlwe::RlweUseCase use_case,
+    const std::vector<psm_rlwe::RlwePlaintextId>& plaintext_ids) {
+  auto status_or_client =
+      psm_rlwe::PrivateMembershipRlweClient::Create(use_case, plaintext_ids);
+  if (!status_or_client.ok()) {
+    return absl::InvalidArgumentError(status_or_client.status().message());
+  }
+
+  return absl::WrapUnique<PrivateMembershipRlweClient>(
+      new PrivateMembershipRlweClientImpl(std::move(status_or_client).value()));
+}
+
+PrivateMembershipRlweClientImpl::FactoryImpl::~FactoryImpl() = default;
+
+PrivateMembershipRlweClientImpl::~PrivateMembershipRlweClientImpl() = default;
+
+::rlwe::StatusOr<psm_rlwe::PrivateMembershipRlweOprfRequest>
+PrivateMembershipRlweClientImpl::CreateOprfRequest() {
+  return psm_rlwe_client_->CreateOprfRequest();
+}
+
+::rlwe::StatusOr<psm_rlwe::PrivateMembershipRlweQueryRequest>
+PrivateMembershipRlweClientImpl::CreateQueryRequest(
+    const psm_rlwe::PrivateMembershipRlweOprfResponse& oprf_response) {
+  return psm_rlwe_client_->CreateQueryRequest(oprf_response);
+}
+
+::rlwe::StatusOr<psm_rlwe::MembershipResponseMap>
+PrivateMembershipRlweClientImpl::ProcessResponse(
+    const psm_rlwe::PrivateMembershipRlweQueryResponse& query_response) {
+  return psm_rlwe_client_->ProcessResponse(query_response);
+}
+
+PrivateMembershipRlweClientImpl::PrivateMembershipRlweClientImpl(
+    std::unique_ptr<psm_rlwe::PrivateMembershipRlweClient> psm_rlwe_client)
+    : psm_rlwe_client_(std::move(psm_rlwe_client)) {
+  DCHECK(psm_rlwe_client_);
+}
+
+}  // namespace policy
diff --git a/chrome/browser/ash/policy/enrollment/private_membership/private_membership_rlwe_client_impl.h b/chrome/browser/ash/policy/enrollment/private_membership/private_membership_rlwe_client_impl.h
new file mode 100644
index 0000000..e234261
--- /dev/null
+++ b/chrome/browser/ash/policy/enrollment/private_membership/private_membership_rlwe_client_impl.h
@@ -0,0 +1,80 @@
+// 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 CHROME_BROWSER_ASH_POLICY_ENROLLMENT_PRIVATE_MEMBERSHIP_PRIVATE_MEMBERSHIP_RLWE_CLIENT_IMPL_H_
+#define CHROME_BROWSER_ASH_POLICY_ENROLLMENT_PRIVATE_MEMBERSHIP_PRIVATE_MEMBERSHIP_RLWE_CLIENT_IMPL_H_
+
+#include "chrome/browser/ash/policy/enrollment/private_membership/private_membership_rlwe_client.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "third_party/private_membership/src/private_membership_rlwe.pb.h"
+#include "third_party/shell-encryption/src/statusor.h"
+
+namespace private_membership {
+namespace rlwe {
+class PrivateMembershipRlweClient;
+class MembershipResponseMap;
+}  // namespace rlwe
+}  // namespace private_membership
+
+namespace policy {
+
+class PrivateMembershipRlweClientImpl : public PrivateMembershipRlweClient {
+ public:
+  // A factory that creates |PrivateMembershipRlweClientImpl|s.
+  class FactoryImpl : public Factory {
+   public:
+    FactoryImpl();
+
+    // FactoryImpl is neither copyable nor copy assignable.
+    FactoryImpl(const FactoryImpl&) = delete;
+    FactoryImpl& operator=(const FactoryImpl&) = delete;
+
+    ~FactoryImpl() override;
+
+    // Creates PSM RLWE client that generates and holds a randomly generated
+    // key.
+    ::rlwe::StatusOr<std::unique_ptr<PrivateMembershipRlweClient>> Create(
+        private_membership::rlwe::RlweUseCase use_case,
+        const std::vector<private_membership::rlwe::RlwePlaintextId>&
+            plaintext_ids) override;
+  };
+
+  // PrivateMembershipRlweClientImpl is neither copyable nor copy assignable.
+  PrivateMembershipRlweClientImpl(const PrivateMembershipRlweClientImpl&) =
+      delete;
+  PrivateMembershipRlweClientImpl& operator=(
+      const PrivateMembershipRlweClientImpl&) = delete;
+
+  ~PrivateMembershipRlweClientImpl() override;
+
+  // Delegates all function calls into PrivateMembershipRlweClient by
+  // |psm_rlwe_client_|.
+
+  ::rlwe::StatusOr<private_membership::rlwe::PrivateMembershipRlweOprfRequest>
+  CreateOprfRequest() override;
+  ::rlwe::StatusOr<private_membership::rlwe::PrivateMembershipRlweQueryRequest>
+  CreateQueryRequest(
+      const private_membership::rlwe::PrivateMembershipRlweOprfResponse&
+          oprf_response) override;
+  ::rlwe::StatusOr<private_membership::rlwe::MembershipResponseMap>
+  ProcessResponse(
+      const private_membership::rlwe::PrivateMembershipRlweQueryResponse&
+          query_response) override;
+
+ private:
+  explicit PrivateMembershipRlweClientImpl(
+      std::unique_ptr<private_membership::rlwe::PrivateMembershipRlweClient>
+          psm_rlwe_client);
+
+  const std::unique_ptr<private_membership::rlwe::PrivateMembershipRlweClient>
+      psm_rlwe_client_;
+};
+
+}  // namespace policy
+
+#endif  // CHROME_BROWSER_ASH_POLICY_ENROLLMENT_PRIVATE_MEMBERSHIP_PRIVATE_MEMBERSHIP_RLWE_CLIENT_IMPL_H_
diff --git a/chrome/browser/ash/policy/enrollment/private_membership/testing_private_membership_rlwe_client.cc b/chrome/browser/ash/policy/enrollment/private_membership/testing_private_membership_rlwe_client.cc
new file mode 100644
index 0000000..e7be721
--- /dev/null
+++ b/chrome/browser/ash/policy/enrollment/private_membership/testing_private_membership_rlwe_client.cc
@@ -0,0 +1,76 @@
+// 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.
+
+#include "chrome/browser/ash/policy/enrollment/private_membership/testing_private_membership_rlwe_client.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/check.h"
+#include "chrome/browser/ash/policy/enrollment/private_membership/private_membership_rlwe_client.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "third_party/private_membership/src/membership_response_map.h"
+#include "third_party/private_membership/src/private_membership_rlwe.pb.h"
+#include "third_party/private_membership/src/private_membership_rlwe_client.h"
+#include "third_party/shell-encryption/src/statusor.h"
+
+namespace psm_rlwe = private_membership::rlwe;
+
+namespace policy {
+
+TestingPrivateMembershipRlweClient::FactoryImpl::FactoryImpl(
+    const std::string& ec_cipher_key,
+    const std::string& seed,
+    std::vector<private_membership::rlwe::RlwePlaintextId>
+        plaintext_testing_ids)
+    : ec_cipher_key_(ec_cipher_key),
+      seed_(seed),
+      plaintext_testing_ids_(std::move(plaintext_testing_ids)) {}
+
+::rlwe::StatusOr<std::unique_ptr<PrivateMembershipRlweClient>>
+TestingPrivateMembershipRlweClient::FactoryImpl::Create(
+    psm_rlwe::RlweUseCase use_case,
+    const std::vector<psm_rlwe::RlwePlaintextId>& plaintext_ids) {
+  auto status_or_client =
+      psm_rlwe::PrivateMembershipRlweClient::CreateForTesting(
+          use_case, plaintext_testing_ids_, ec_cipher_key_, seed_);
+  if (!status_or_client.ok()) {
+    return absl::InvalidArgumentError(status_or_client.status().message());
+  }
+  return absl::WrapUnique<PrivateMembershipRlweClient>(
+      new TestingPrivateMembershipRlweClient(
+          std::move(status_or_client).value()));
+}
+
+TestingPrivateMembershipRlweClient::FactoryImpl::~FactoryImpl() = default;
+
+TestingPrivateMembershipRlweClient::~TestingPrivateMembershipRlweClient() =
+    default;
+
+::rlwe::StatusOr<psm_rlwe::PrivateMembershipRlweOprfRequest>
+TestingPrivateMembershipRlweClient::CreateOprfRequest() {
+  return psm_rlwe_client_->CreateOprfRequest();
+}
+
+::rlwe::StatusOr<psm_rlwe::PrivateMembershipRlweQueryRequest>
+TestingPrivateMembershipRlweClient::CreateQueryRequest(
+    const psm_rlwe::PrivateMembershipRlweOprfResponse& oprf_response) {
+  return psm_rlwe_client_->CreateQueryRequest(oprf_response);
+}
+
+::rlwe::StatusOr<psm_rlwe::MembershipResponseMap>
+TestingPrivateMembershipRlweClient::ProcessResponse(
+    const psm_rlwe::PrivateMembershipRlweQueryResponse& query_response) {
+  return psm_rlwe_client_->ProcessResponse(query_response);
+}
+
+TestingPrivateMembershipRlweClient::TestingPrivateMembershipRlweClient(
+    std::unique_ptr<psm_rlwe::PrivateMembershipRlweClient> psm_rlwe_client)
+    : psm_rlwe_client_(std::move(psm_rlwe_client)) {
+  DCHECK(psm_rlwe_client_);
+}
+
+}  // namespace policy
diff --git a/chrome/browser/ash/policy/enrollment/private_membership/testing_private_membership_rlwe_client.h b/chrome/browser/ash/policy/enrollment/private_membership/testing_private_membership_rlwe_client.h
new file mode 100644
index 0000000..ad328290
--- /dev/null
+++ b/chrome/browser/ash/policy/enrollment/private_membership/testing_private_membership_rlwe_client.h
@@ -0,0 +1,95 @@
+// 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 CHROME_BROWSER_ASH_POLICY_ENROLLMENT_PRIVATE_MEMBERSHIP_TESTING_PRIVATE_MEMBERSHIP_RLWE_CLIENT_H_
+#define CHROME_BROWSER_ASH_POLICY_ENROLLMENT_PRIVATE_MEMBERSHIP_TESTING_PRIVATE_MEMBERSHIP_RLWE_CLIENT_H_
+
+#include "chrome/browser/ash/policy/enrollment/private_membership/private_membership_rlwe_client.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "third_party/private_membership/src/private_membership_rlwe.pb.h"
+#include "third_party/shell-encryption/src/statusor.h"
+
+namespace private_membership {
+namespace rlwe {
+class PrivateMembershipRlweClient;
+class MembershipResponseMap;
+}  // namespace rlwe
+}  // namespace private_membership
+
+namespace policy {
+
+class TestingPrivateMembershipRlweClient : public PrivateMembershipRlweClient {
+ public:
+  // A factory that creates |TestingPrivateMembershipRlweClient|s.
+  class FactoryImpl : public Factory {
+   public:
+    // TODO(crbug.com/1239329): Remove |plaintext_ids| from the factory
+    // constructor, and create a delegate for PSM ID.
+    FactoryImpl(const std::string& ec_cipher_key,
+                const std::string& seed,
+                std::vector<private_membership::rlwe::RlwePlaintextId>
+                    plaintext_testing_ids);
+
+    // FactoryImpl is neither copyable nor copy assignable.
+    FactoryImpl(const FactoryImpl&) = delete;
+    FactoryImpl& operator=(const FactoryImpl&) = delete;
+
+    ~FactoryImpl() override;
+
+    // Creates PSM RLWE client for testing with a given cipher key
+    // |ec_cipher_key_| and deterministic PRNG |seed_|.
+    // Note: |plaintext_ids| value will be ignored while creating the client,
+    // and |plaintext_testing_ids_| member will be used instead for testing.
+    ::rlwe::StatusOr<std::unique_ptr<PrivateMembershipRlweClient>> Create(
+        private_membership::rlwe::RlweUseCase use_case,
+        const std::vector<private_membership::rlwe::RlwePlaintextId>&
+            plaintext_ids) override;
+
+   private:
+    // The following members are used to create the PSM RLWE client for testing.
+
+    const std::string ec_cipher_key_;
+    const std::string seed_;
+    const std::vector<private_membership::rlwe::RlwePlaintextId>
+        plaintext_testing_ids_;
+  };
+
+  // TestingPrivateMembershipRlweClient is neither copyable nor copy assignable.
+  TestingPrivateMembershipRlweClient(
+      const TestingPrivateMembershipRlweClient&) = delete;
+  TestingPrivateMembershipRlweClient& operator=(
+      const TestingPrivateMembershipRlweClient&) = delete;
+
+  ~TestingPrivateMembershipRlweClient() override;
+
+  // Delegates all function calls into PrivateMembershipRlweClient by
+  // |psm_rlwe_client_|.
+
+  ::rlwe::StatusOr<private_membership::rlwe::PrivateMembershipRlweOprfRequest>
+  CreateOprfRequest() override;
+  ::rlwe::StatusOr<private_membership::rlwe::PrivateMembershipRlweQueryRequest>
+  CreateQueryRequest(
+      const private_membership::rlwe::PrivateMembershipRlweOprfResponse&
+          oprf_response) override;
+  ::rlwe::StatusOr<private_membership::rlwe::MembershipResponseMap>
+  ProcessResponse(
+      const private_membership::rlwe::PrivateMembershipRlweQueryResponse&
+          query_response) override;
+
+ private:
+  explicit TestingPrivateMembershipRlweClient(
+      std::unique_ptr<private_membership::rlwe::PrivateMembershipRlweClient>
+          psm_rlwe_client);
+
+  const std::unique_ptr<private_membership::rlwe::PrivateMembershipRlweClient>
+      psm_rlwe_client_;
+};
+
+}  // namespace policy
+
+#endif  // CHROME_BROWSER_ASH_POLICY_ENROLLMENT_PRIVATE_MEMBERSHIP_TESTING_PRIVATE_MEMBERSHIP_RLWE_CLIENT_H_
diff --git a/chrome/browser/autofill/content_autofill_driver_browsertest.cc b/chrome/browser/autofill/content_autofill_driver_browsertest.cc
index 333f1b77..bca671b 100644
--- a/chrome/browser/autofill/content_autofill_driver_browsertest.cc
+++ b/chrome/browser/autofill/content_autofill_driver_browsertest.cc
@@ -26,6 +26,7 @@
 #include "content/public/common/url_constants.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
+#include "content/public/test/prerender_test_util.h"
 #include "content/public/test/test_utils.h"
 #include "net/dns/mock_host_resolver.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
@@ -68,8 +69,16 @@
 class ContentAutofillDriverBrowserTest : public InProcessBrowserTest,
                                          public content::WebContentsObserver {
  public:
-  ContentAutofillDriverBrowserTest() {}
-  ~ContentAutofillDriverBrowserTest() override {}
+  ContentAutofillDriverBrowserTest()
+      : prerender_helper_(
+            base::BindRepeating(&ContentAutofillDriverBrowserTest::web_contents,
+                                base::Unretained(this))) {}
+  ~ContentAutofillDriverBrowserTest() override = default;
+
+  void SetUp() override {
+    prerender_helper_.SetUp(embedded_test_server());
+    InProcessBrowserTest::SetUp();
+  }
 
   void SetUpOnMainThread() override {
     autofill_client_ =
@@ -156,6 +165,14 @@
     return *autofill_client_.get();
   }
 
+  content::test::PrerenderTestHelper& prerender_helper() {
+    return prerender_helper_;
+  }
+
+  content::WebContents* web_contents() {
+    return browser()->tab_strip_model()->GetActiveWebContents();
+  }
+
  protected:
   base::OnceClosure web_contents_hidden_callback_;
   base::OnceClosure nav_entry_committed_callback_;
@@ -163,6 +180,7 @@
   base::OnceClosure subframe_navigation_callback_;
 
   std::unique_ptr<testing::NiceMock<MockAutofillClient>> autofill_client_;
+  content::test::PrerenderTestHelper prerender_helper_;
 };
 
 IN_PROC_BROWSER_TEST_F(ContentAutofillDriverBrowserTest,
@@ -202,6 +220,31 @@
 }
 
 IN_PROC_BROWSER_TEST_F(ContentAutofillDriverBrowserTest,
+                       PrerenderNavigationDoesntHideAutofillPopup) {
+  GURL initial_url =
+      embedded_test_server()->GetURL("/autofill/autofill_test_form.html");
+  GURL prerender_url = embedded_test_server()->GetURL("/empty.html");
+  prerender_helper().NavigatePrimaryPage(initial_url);
+
+  int host_id = content::RenderFrameHost::kNoFrameTreeNodeId;
+
+  {
+    EXPECT_CALL(autofill_client(),
+                HideAutofillPopup(PopupHidingReason::kNavigation))
+        .Times(0);
+    host_id = prerender_helper().AddPrerender(prerender_url);
+  }
+
+  EXPECT_CALL(autofill_client(),
+              HideAutofillPopup(PopupHidingReason::kNavigation))
+      .Times(testing::AtLeast(1));
+
+  content::test::PrerenderHostObserver host_observer(*web_contents(), host_id);
+  prerender_helper().NavigatePrimaryPage(prerender_url);
+  EXPECT_TRUE(host_observer.was_activated());
+}
+
+IN_PROC_BROWSER_TEST_F(ContentAutofillDriverBrowserTest,
                        SubframeNavigationDoesntHideAutofillPopup) {
   // Main frame is on a.com, iframe is on b.com.
   GURL url = embedded_test_server()->GetURL(
diff --git a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc
index bee9618..5f70314a 100644
--- a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc
+++ b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc
@@ -179,6 +179,10 @@
 #include "device/fido/mac/credential_store.h"
 #endif  // defined(OS_MAC)
 
+#if defined(OS_WIN)
+#include "chrome/browser/media/cdm_pref_service_helper.h"
+#endif  // defined(OS_WIN)
+
 using base::UserMetricsAction;
 using content::BrowserContext;
 using content::BrowserThread;
@@ -1118,8 +1122,14 @@
 #if defined(OS_ANDROID)
     cdm::MediaDrmStorageImpl::ClearMatchingLicenses(
         prefs, delete_begin_, delete_end, nullable_filter,
-        CreateTaskCompletionClosure(TracingDataType::kDrmLicenses));
+        CreateTaskCompletionClosure(TracingDataType::kCdmLicenses));
 #endif  // defined(OS_ANDROID);
+
+#if defined(OS_WIN)
+    CdmPrefServiceHelper::ClearCdmPreferenceData(
+        prefs, delete_begin, delete_end, nullable_filter,
+        CreateTaskCompletionClosure(TracingDataType::kCdmLicenses));
+#endif  // defined(OS_WIN)
   }
 
   //////////////////////////////////////////////////////////////////////////////
diff --git a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.h b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.h
index 513a64c..c40c069 100644
--- a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.h
+++ b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.h
@@ -116,7 +116,7 @@
     kExploreSites = 25,
     kLegacyStrikes = 26,
     kWebrtcEventLogs = 27,
-    kDrmLicenses = 28,
+    kCdmLicenses = 28,
     kHostCache = 29,
     kTpmAttestationKeys = 30,
     kStrikes = 31,
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 93aa8c5..aabd3cf 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -2178,6 +2178,9 @@
     "../ash/policy/enrollment/enrollment_requisition_manager.h",
     "../ash/policy/enrollment/fake_auto_enrollment_client.cc",
     "../ash/policy/enrollment/fake_auto_enrollment_client.h",
+    "../ash/policy/enrollment/private_membership/private_membership_rlwe_client.h",
+    "../ash/policy/enrollment/private_membership/private_membership_rlwe_client_impl.cc",
+    "../ash/policy/enrollment/private_membership/private_membership_rlwe_client_impl.h",
     "../ash/policy/enrollment/tpm_enrollment_key_signing_service.cc",
     "../ash/policy/enrollment/tpm_enrollment_key_signing_service.h",
     "../ash/policy/external_data/cloud_external_data_manager_base.cc",
@@ -4043,6 +4046,8 @@
     "../ash/policy/enrollment/account_status_check_fetcher_unittest.cc",
     "../ash/policy/enrollment/auto_enrollment_client_impl_unittest.cc",
     "../ash/policy/enrollment/device_cloud_policy_initializer_unittest.cc",
+    "../ash/policy/enrollment/private_membership/testing_private_membership_rlwe_client.cc",
+    "../ash/policy/enrollment/private_membership/testing_private_membership_rlwe_client.h",
     "../ash/policy/enrollment/tpm_enrollment_key_signing_service_unittest.cc",
     "../ash/policy/external_data/cloud_external_data_manager_base_unittest.cc",
     "../ash/policy/external_data/cloud_external_data_policy_observer_unittest.cc",
diff --git a/chrome/browser/content_creation/notes/internal/android/java/src/org/chromium/chrome/browser/content_creation/notes/NoteCreationDialog.java b/chrome/browser/content_creation/notes/internal/android/java/src/org/chromium/chrome/browser/content_creation/notes/NoteCreationDialog.java
index cf5c04c..a093c3b 100644
--- a/chrome/browser/content_creation/notes/internal/android/java/src/org/chromium/chrome/browser/content_creation/notes/NoteCreationDialog.java
+++ b/chrome/browser/content_creation/notes/internal/android/java/src/org/chromium/chrome/browser/content_creation/notes/NoteCreationDialog.java
@@ -11,6 +11,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewGroup.MarginLayoutParams;
+import android.view.accessibility.AccessibilityEvent;
 import android.widget.Button;
 import android.widget.ImageView;
 import android.widget.TextView;
@@ -118,7 +119,14 @@
     public void createRecyclerViews(ModelList carouselItems) {
         RecyclerView noteCarousel = mContentView.findViewById(R.id.note_carousel);
 
-        SimpleRecyclerViewAdapter adapter = new SimpleRecyclerViewAdapter(carouselItems);
+        SimpleRecyclerViewAdapter adapter = new SimpleRecyclerViewAdapter(carouselItems) {
+            @Override
+            public void onBindViewHolder(
+                    SimpleRecyclerViewAdapter.ViewHolder holder, int position) {
+                holder.itemView.setTag(position);
+                super.onBindViewHolder(holder, position);
+            }
+        };
         adapter.registerType(NoteProperties.NOTE_VIEW_TYPE,
                 new LayoutViewBuilder(R.layout.carousel_item), this::bindCarouselItem);
         noteCarousel.setAdapter(adapter);
@@ -206,6 +214,21 @@
 
         setPadding(model.get(NoteProperties.IS_FIRST), model.get(NoteProperties.IS_LAST),
                 parent.findViewById(R.id.item));
+
+        carouselItemView.setAccessibilityDelegate(new View.AccessibilityDelegate() {
+            @Override
+            public void onPopulateAccessibilityEvent(View host, AccessibilityEvent event) {
+                int position;
+                switch (event.getEventType()) {
+                    case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED:
+                        mSelectedItemIndex = (Integer) host.getTag();
+                        centerCurrentNote();
+                        break;
+                }
+
+                super.onPopulateAccessibilityEvent(host, event);
+            }
+        });
     }
 
     // Adjust the padding for carousel items so that:
diff --git a/chrome/browser/extensions/BUILD.gn b/chrome/browser/extensions/BUILD.gn
index 3c3358a..26d14bbd 100644
--- a/chrome/browser/extensions/BUILD.gn
+++ b/chrome/browser/extensions/BUILD.gn
@@ -949,10 +949,28 @@
   ]
 
   if (is_chromeos_ash) {
+    sources += [
+      "api/printing/print_job_controller.cc",
+      "api/printing/print_job_controller.h",
+      "api/printing/printer_capabilities_provider.cc",
+      "api/printing/printer_capabilities_provider.h",
+    ]
     deps += [
       "//chromeos/resources:media_app_bundle_resources_grit",
       "//ui/display/manager",
     ]
+    if (use_cups) {
+      sources += [
+        "api/printing/print_job_submitter.cc",
+        "api/printing/print_job_submitter.h",
+        "api/printing/printing_api.cc",
+        "api/printing/printing_api.h",
+        "api/printing/printing_api_handler.cc",
+        "api/printing/printing_api_handler.h",
+        "api/printing/printing_api_utils.cc",
+        "api/printing/printing_api_utils.h",
+      ]
+    }
   }
 
   if (is_chromeos) {
@@ -970,25 +988,11 @@
       "api/platform_keys/platform_keys_api.h",
       "api/platform_keys/verify_trust_api.cc",
       "api/platform_keys/verify_trust_api.h",
-      "api/printing/print_job_controller.cc",
-      "api/printing/print_job_controller.h",
       "clipboard_extension_helper_chromeos.cc",
       "clipboard_extension_helper_chromeos.h",
       "system_display/system_display_serialization.cc",
       "system_display/system_display_serialization.h",
     ]
-    if (use_cups) {
-      sources += [
-        "api/printing/print_job_submitter.cc",
-        "api/printing/print_job_submitter.h",
-        "api/printing/printing_api.cc",
-        "api/printing/printing_api.h",
-        "api/printing/printing_api_handler.cc",
-        "api/printing/printing_api_handler.h",
-        "api/printing/printing_api_utils.cc",
-        "api/printing/printing_api_utils.h",
-      ]
-    }
     deps += [
       "//chromeos/crosapi/cpp",
       "//chromeos/crosapi/mojom",
diff --git a/chrome/browser/extensions/api/certificate_provider/certificate_provider_apitest.cc b/chrome/browser/extensions/api/certificate_provider/certificate_provider_apitest.cc
index 11c6ad3..f43a031c 100644
--- a/chrome/browser/extensions/api/certificate_provider/certificate_provider_apitest.cc
+++ b/chrome/browser/extensions/api/certificate_provider/certificate_provider_apitest.cc
@@ -21,6 +21,7 @@
 #include "base/memory/scoped_refptr.h"
 #include "base/path_service.h"
 #include "base/run_loop.h"
+#include "base/scoped_observation.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
@@ -42,9 +43,6 @@
 #include "components/policy/core/common/policy_map.h"
 #include "components/policy/core/common/policy_types.h"
 #include "components/policy/policy_constants.h"
-#include "content/public/browser/notification_details.h"
-#include "content/public/browser/notification_observer.h"
-#include "content/public/browser/notification_source.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/test/browser_test.h"
@@ -52,10 +50,11 @@
 #include "content/public/test/test_navigation_observer.h"
 #include "content/public/test/test_utils.h"
 #include "crypto/rsa_private_key.h"
+#include "extensions/browser/api/test/test_api_observer.h"
+#include "extensions/browser/api/test/test_api_observer_registry.h"
 #include "extensions/browser/disable_reason.h"
 #include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_system.h"
-#include "extensions/browser/notification_types.h"
 #include "extensions/browser/process_manager.h"
 #include "extensions/browser/test_extension_registry_observer.h"
 #include "extensions/common/extension.h"
@@ -175,8 +174,28 @@
   return base::ToLowerASCII(base::HexEncode(hash, base::kSHA1Length));
 }
 
-class CertificateProviderApiTest : public extensions::ExtensionApiTest,
-                                   public content::NotificationObserver {
+// Generates a gtest failure whenever extension JS reports failure.
+class JsFailureObserver : public extensions::TestApiObserver {
+ public:
+  JsFailureObserver() {
+    test_api_observation_.Observe(
+        extensions::TestApiObserverRegistry::GetInstance());
+  }
+  ~JsFailureObserver() override = default;
+
+  void OnTestFailed(content::BrowserContext* browser_context,
+                    const std::string& message) override {
+    ADD_FAILURE() << "Received failure notification from the JS side: "
+                  << message;
+  }
+
+ private:
+  base::ScopedObservation<extensions::TestApiObserverRegistry,
+                          extensions::TestApiObserver>
+      test_api_observation_{this};
+};
+
+class CertificateProviderApiTest : public extensions::ExtensionApiTest {
  public:
   CertificateProviderApiTest() {}
 
@@ -194,9 +213,7 @@
 
     // Observe all assertion failures in the JS code, even those that happen
     // when there's no active `ResultCatcher`.
-    notification_registrar_.Add(this,
-                                extensions::NOTIFICATION_EXTENSION_TEST_FAILED,
-                                content::NotificationService::AllSources());
+    js_failure_observer_ = std::make_unique<JsFailureObserver>();
 
     // Set up the AutoSelectCertificateForUrls policy to avoid the client
     // certificate selection dialog.
@@ -281,14 +298,6 @@
  private:
   const char* const kClientCertUrl = "/client-cert";
 
-  // content::NotificationObserver:
-  void Observe(int type,
-               const content::NotificationSource&,
-               const content::NotificationDetails&) override {
-    DCHECK_EQ(type, extensions::NOTIFICATION_EXTENSION_TEST_FAILED);
-    ADD_FAILURE() << "Received failure notification from the JS side";
-  }
-
   std::unique_ptr<net::test_server::HttpResponse> OnHttpsServerRequested(
       const net::test_server::HttpRequest& request) const {
     if (request.relative_url != kClientCertUrl)
@@ -304,7 +313,7 @@
     return response;
   }
 
-  content::NotificationRegistrar notification_registrar_;
+  std::unique_ptr<JsFailureObserver> js_failure_observer_;
   std::unique_ptr<net::EmbeddedTestServer> https_server_;
 };
 
diff --git a/chrome/browser/extensions/api/printing/fake_print_job_controller.cc b/chrome/browser/extensions/api/printing/fake_print_job_controller.cc
index 7d5a5b93..9c76b6b3 100644
--- a/chrome/browser/extensions/api/printing/fake_print_job_controller.cc
+++ b/chrome/browser/extensions/api/printing/fake_print_job_controller.cc
@@ -8,66 +8,75 @@
 #include <utility>
 
 #include "base/callback.h"
-#include "base/containers/flat_set.h"
 #include "base/strings/utf_string_conversions.h"
-#include "chrome/browser/chrome_notification_types.h"
-#include "chrome/browser/extensions/api/printing/printing_api_handler.h"
-#include "chrome/browser/printing/print_job.h"
+#include "chrome/browser/chromeos/printing/cups_print_job.h"
+#include "chrome/browser/chromeos/printing/cups_print_job_manager.h"
+#include "chrome/browser/chromeos/printing/cups_printers_manager.h"
+#include "chrome/browser/chromeos/printing/history/print_job_info_proto_conversions.h"
+#include "chrome/browser/chromeos/printing/test_cups_print_job_manager.h"
 #include "chromeos/printing/printer_configuration.h"
-#include "content/public/browser/notification_details.h"
-#include "content/public/browser/notification_source.h"
 #include "printing/metafile_skia.h"
 #include "printing/print_settings.h"
-#include "printing/printed_document.h"
-#include "testing/gtest/include/gtest/gtest.h"
 
 namespace extensions {
 
-FakePrintJobController::FakePrintJobController() = default;
+FakePrintJobController::FakePrintJobController(
+    chromeos::TestCupsPrintJobManager* print_job_manager,
+    chromeos::CupsPrintersManager* printers_manager)
+    : print_job_manager_(print_job_manager),
+      printers_manager_(printers_manager) {}
 
 FakePrintJobController::~FakePrintJobController() = default;
 
-void FakePrintJobController::CreatePrintJob(
-    const std::string& printer_id,
-    int job_id,
-    const std::string& extension_id,
-    crosapi::mojom::PrintJob::Source source) const {
-  auto job = base::MakeRefCounted<printing::PrintJob>();
-  job->SetSource(source, extension_id);
-  auto settings = std::make_unique<printing::PrintSettings>();
-  settings->set_device_name(base::UTF8ToUTF16(printer_id));
-  auto document = base::MakeRefCounted<printing::PrintedDocument>(
-      std::move(settings), std::u16string(), 0);
-  auto details = base::MakeRefCounted<printing::JobEventDetails>(
-      printing::JobEventDetails::DOC_DONE, job_id, document.get());
-  // Normally, PrintingAPIHandler observes the DOC_DONE notification.
-  handler_->Observe(chrome::NOTIFICATION_PRINT_JOB_EVENT,
-                    content::Source<printing::PrintJob>(job.get()),
-                    content::Details<printing::JobEventDetails>(details.get()));
-}
-
 void FakePrintJobController::StartPrintJob(
     const std::string& extension_id,
     std::unique_ptr<printing::MetafileSkia> metafile,
     std::unique_ptr<printing::PrintSettings> settings,
     StartPrintJobCallback callback) {
-  std::string printer_id = base::UTF16ToUTF8(settings->device_name());
-  CreatePrintJob(printer_id, ++job_id_, extension_id,
-                 crosapi::mojom::PrintJob::Source::EXTENSION);
-  std::string cups_id = PrintingAPIHandler::CreateUniqueId(printer_id, job_id_);
-  EXPECT_TRUE(print_jobs_.contains(cups_id));
-  std::move(callback).Run(std::make_unique<std::string>(std::move(cups_id)));
+  absl::optional<chromeos::Printer> printer =
+      printers_manager_->GetPrinter(base::UTF16ToUTF8(settings->device_name()));
+  if (!printer) {
+    std::move(callback).Run(nullptr);
+    return;
+  }
+
+  // Create and start new CupsPrintJob.
+  job_id_++;
+  auto print_job = std::make_unique<chromeos::CupsPrintJob>(
+      *printer, job_id_, base::UTF16ToUTF8(settings->title()),
+      /*total_page_number=*/1, printing::PrintJob::Source::EXTENSION,
+      extension_id, chromeos::PrintSettingsToProto(*settings));
+  print_job_manager_->CreatePrintJob(print_job.get());
+  print_job_manager_->StartPrintJob(print_job.get());
+  std::string print_job_unique_id = print_job->GetUniqueId();
+  jobs_[print_job_unique_id] = std::move(print_job);
+
+  std::move(callback).Run(std::make_unique<std::string>(print_job_unique_id));
 }
 
-void FakePrintJobController::OnPrintJobCreated(const std::string& extension_id,
-                                               const std::string& job_id) {
-  EXPECT_FALSE(print_jobs_.contains(job_id));
-  print_jobs_.insert(job_id);
+bool FakePrintJobController::CancelPrintJob(const std::string& job_id) {
+  auto it = jobs_.find(job_id);
+  if (it == jobs_.end())
+    return false;
+  print_job_manager_->CancelPrintJob(it->second.get());
+  return true;
 }
 
+void FakePrintJobController::OnPrintJobCreated(
+    const std::string& extension_id,
+    const std::string& job_id,
+    base::WeakPtr<chromeos::CupsPrintJob> cups_job) {}
+
 void FakePrintJobController::OnPrintJobFinished(const std::string& job_id) {
-  EXPECT_TRUE(print_jobs_.contains(job_id));
-  print_jobs_.erase(job_id);
+  jobs_.erase(job_id);
+}
+
+chromeos::CupsPrintJob* FakePrintJobController::GetCupsPrintJob(
+    const std::string& job_id) {
+  auto it = jobs_.find(job_id);
+  if (it == jobs_.end())
+    return nullptr;
+  return it->second.get();
 }
 
 }  // namespace extensions
diff --git a/chrome/browser/extensions/api/printing/fake_print_job_controller.h b/chrome/browser/extensions/api/printing/fake_print_job_controller.h
index 97ff03e..537a4ea 100644
--- a/chrome/browser/extensions/api/printing/fake_print_job_controller.h
+++ b/chrome/browser/extensions/api/printing/fake_print_job_controller.h
@@ -5,45 +5,47 @@
 #ifndef CHROME_BROWSER_EXTENSIONS_API_PRINTING_FAKE_PRINT_JOB_CONTROLLER_H_
 #define CHROME_BROWSER_EXTENSIONS_API_PRINTING_FAKE_PRINT_JOB_CONTROLLER_H_
 
-#include "base/containers/flat_set.h"
+#include "base/containers/flat_map.h"
 #include "chrome/browser/extensions/api/printing/print_job_controller.h"
-#include "chromeos/crosapi/mojom/local_printer.mojom.h"
+
+namespace chromeos {
+class CupsPrintersManager;
+class TestCupsPrintJobManager;
+}  // namespace chromeos
 
 namespace extensions {
 
-class PrintingAPIHandler;
-
 // Fake print job controller which doesn't send print jobs to actual printing
 // pipeline.
 // It's used in unit and API integration tests.
 class FakePrintJobController : public PrintJobController {
  public:
-  FakePrintJobController();
+  FakePrintJobController(chromeos::TestCupsPrintJobManager* print_job_manager,
+                         chromeos::CupsPrintersManager* printers_manager);
   ~FakePrintJobController() override;
 
-  void set_handler(PrintingAPIHandler* handler) { handler_ = handler; }
-
-  const base::flat_set<std::string>& print_jobs() const { return print_jobs_; }
-
-  void CreatePrintJob(const std::string& printer_id,
-                      int job_id,
-                      const std::string& extension_id,
-                      crosapi::mojom::PrintJob::Source source) const;
-
   // PrintJobController:
   void StartPrintJob(const std::string& extension_id,
                      std::unique_ptr<printing::MetafileSkia> metafile,
                      std::unique_ptr<printing::PrintSettings> settings,
                      StartPrintJobCallback callback) override;
-  void OnPrintJobCreated(const std::string& extension_id,
-                         const std::string& job_id) override;
+  bool CancelPrintJob(const std::string& job_id) override;
+  void OnPrintJobCreated(
+      const std::string& extension_id,
+      const std::string& job_id,
+      base::WeakPtr<chromeos::CupsPrintJob> cups_job) override;
   void OnPrintJobFinished(const std::string& job_id) override;
 
- private:
-  PrintingAPIHandler* handler_ = nullptr;
+  // Helper method to be used in tests to access ongoing print jobs.
+  chromeos::CupsPrintJob* GetCupsPrintJob(const std::string& job_id);
 
-  // Stores ids for ongoing print jobs.
-  base::flat_set<std::string> print_jobs_;
+ private:
+  // Not owned by FakePrintJobController.
+  chromeos::TestCupsPrintJobManager* print_job_manager_;
+  chromeos::CupsPrintersManager* printers_manager_;
+
+  // Stores ongoing print jobs as a mapping from job id to CupsPrintJob.
+  base::flat_map<std::string, std::unique_ptr<chromeos::CupsPrintJob>> jobs_;
 
   // Current job id.
   int job_id_ = 0;
diff --git a/chrome/browser/extensions/api/printing/print_job_controller.cc b/chrome/browser/extensions/api/printing/print_job_controller.cc
index 7590aa2..f2aada00 100644
--- a/chrome/browser/extensions/api/printing/print_job_controller.cc
+++ b/chrome/browser/extensions/api/printing/print_job_controller.cc
@@ -8,12 +8,11 @@
 
 #include "base/bind.h"
 #include "base/callback.h"
-#include "base/check.h"
 #include "base/containers/flat_map.h"
-#include "base/containers/flat_set.h"
 #include "base/containers/queue.h"
 #include "base/memory/scoped_refptr.h"
-#include "build/chromeos_buildflags.h"
+#include "chrome/browser/chromeos/printing/cups_print_job.h"
+#include "chrome/browser/chromeos/printing/cups_print_job_manager.h"
 #include "chrome/browser/printing/print_job.h"
 #include "chrome/browser/printing/printer_query.h"
 #include "content/public/browser/browser_task_traits.h"
@@ -56,7 +55,8 @@
 // This class lives on UI thread.
 class PrintJobControllerImpl : public PrintJobController {
  public:
-  PrintJobControllerImpl();
+  explicit PrintJobControllerImpl(
+      chromeos::CupsPrintJobManager* print_job_manager);
   ~PrintJobControllerImpl() override;
 
   // PrintJobController:
@@ -65,9 +65,13 @@
                      std::unique_ptr<printing::PrintSettings> settings,
                      StartPrintJobCallback callback) override;
 
+  bool CancelPrintJob(const std::string& job_id) override;
+
   // Moves print job pointer to |print_jobs_map_| and resolves corresponding
-  void OnPrintJobCreated(const std::string& extension_id,
-                         const std::string& job_id) override;
+  void OnPrintJobCreated(
+      const std::string& extension_id,
+      const std::string& job_id,
+      base::WeakPtr<chromeos::CupsPrintJob> cups_job) override;
 
   // Removes print job pointer from |print_jobs_map_| as the job is finished.
   void OnPrintJobFinished(const std::string& job_id) override;
@@ -98,10 +102,20 @@
   base::flat_map<std::string, base::queue<JobState>> extension_pending_jobs_;
 
   // Stores mapping from job id to printing::PrintJob.
-  // This is needed to hold the PrintJob pointer.
+  // This is needed to hold PrintJob pointer and correct handle CancelJob()
+  // requests.
   base::flat_map<std::string, scoped_refptr<printing::PrintJob>>
       print_jobs_map_;
 
+  // Stores mapping from job id to chromeos::CupsPrintJob.
+  base::flat_map<std::string, base::WeakPtr<chromeos::CupsPrintJob>>
+      cups_print_jobs_map_;
+
+  // PrintingAPIHandler (which owns PrintJobController) depends on
+  // CupsPrintJobManagerFactory, so |print_job_manager_| outlives
+  // PrintJobController.
+  chromeos::CupsPrintJobManager* const print_job_manager_;
+
   base::WeakPtrFactory<PrintJobControllerImpl> weak_ptr_factory_{this};
 };
 
@@ -117,7 +131,9 @@
 
 PrintJobControllerImpl::JobState::~JobState() = default;
 
-PrintJobControllerImpl::PrintJobControllerImpl() = default;
+PrintJobControllerImpl::PrintJobControllerImpl(
+    chromeos::CupsPrintJobManager* print_job_manager)
+    : print_job_manager_(print_job_manager) {}
 
 PrintJobControllerImpl::~PrintJobControllerImpl() = default;
 
@@ -157,40 +173,55 @@
   job->StartPrinting();
 }
 
-void PrintJobControllerImpl::OnPrintJobCreated(const std::string& extension_id,
-                                               const std::string& job_id) {
+bool PrintJobControllerImpl::CancelPrintJob(const std::string& job_id) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
-  DCHECK(!print_jobs_map_.contains(job_id));
+  auto it = cups_print_jobs_map_.find(job_id);
+  if (it == cups_print_jobs_map_.end() || !it->second)
+    return false;
+  print_job_manager_->CancelPrintJob(it->second.get());
+  return true;
+}
+
+void PrintJobControllerImpl::OnPrintJobCreated(
+    const std::string& extension_id,
+    const std::string& job_id,
+    base::WeakPtr<chromeos::CupsPrintJob> cups_job) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+  DCHECK(!cups_print_jobs_map_.contains(job_id));
+  cups_print_jobs_map_[job_id] = cups_job;
 
   auto it = extension_pending_jobs_.find(extension_id);
-  if (it == extension_pending_jobs_.end() || it->second.empty()) {
-    LOG(ERROR) << "Could not find print job for extension " << extension_id;
+  if (it == extension_pending_jobs_.end())
     return;
-  }
   auto& pending_jobs = it->second;
-  // We need to move corresponding scoped refptr of PrintJob from queue
-  // of pending pointers to global map. We can't drop it as the print job is
-  // not completed yet so we should not destruct it.
-  print_jobs_map_[job_id] = pending_jobs.front().job;
-  // The job is submitted to CUPS so we have to resolve the first callback
-  // in the corresponding queue.
-  auto callback = std::move(pending_jobs.front().callback);
-  pending_jobs.pop();
-  if (pending_jobs.empty())
-    extension_pending_jobs_.erase(it);
-  std::move(callback).Run(std::make_unique<std::string>(job_id));
+  if (!pending_jobs.empty()) {
+    // We need to move corresponding scoped refptr of PrintJob from queue
+    // of pending pointers to global map. We can't drop it as the print job is
+    // not completed yet so we should not destruct it.
+    print_jobs_map_[job_id] = pending_jobs.front().job;
+    // The job is submitted to CUPS so we have to resolve the first callback
+    // in the corresponding queue.
+    auto callback = std::move(pending_jobs.front().callback);
+    pending_jobs.pop();
+    if (pending_jobs.empty())
+      extension_pending_jobs_.erase(it);
+    std::move(callback).Run(std::make_unique<std::string>(job_id));
+  }
 }
 
 void PrintJobControllerImpl::OnPrintJobFinished(const std::string& job_id) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
   print_jobs_map_.erase(job_id);
+  cups_print_jobs_map_.erase(job_id);
 }
 
 // static
-std::unique_ptr<PrintJobController> PrintJobController::Create() {
-  return std::make_unique<PrintJobControllerImpl>();
+std::unique_ptr<PrintJobController> PrintJobController::Create(
+    chromeos::CupsPrintJobManager* print_job_manager) {
+  return std::make_unique<PrintJobControllerImpl>(print_job_manager);
 }
 
 }  // namespace extensions
diff --git a/chrome/browser/extensions/api/printing/print_job_controller.h b/chrome/browser/extensions/api/printing/print_job_controller.h
index d5173d8..bd6d26a 100644
--- a/chrome/browser/extensions/api/printing/print_job_controller.h
+++ b/chrome/browser/extensions/api/printing/print_job_controller.h
@@ -11,6 +11,11 @@
 #include "base/callback_forward.h"
 #include "base/memory/weak_ptr.h"
 
+namespace chromeos {
+class CupsPrintJob;
+class CupsPrintJobManager;
+}  // namespace chromeos
+
 namespace printing {
 class MetafileSkia;
 class PrintSettings;
@@ -18,15 +23,16 @@
 
 namespace extensions {
 
-// This class is responsible for sending print jobs in the printing pipeline.
-// It should be used by API handler as the entry point of actual printing
-// pipeline.
+// This class is responsible for sending print jobs in the printing pipeline and
+// cancelling them. It should be used by API handler as the entry point of
+// actual printing pipeline.
 class PrintJobController {
  public:
   using StartPrintJobCallback =
       base::OnceCallback<void(std::unique_ptr<std::string> job_id)>;
 
-  static std::unique_ptr<PrintJobController> Create();
+  static std::unique_ptr<PrintJobController> Create(
+      chromeos::CupsPrintJobManager* print_job_manager);
 
   PrintJobController() = default;
   virtual ~PrintJobController() = default;
@@ -39,9 +45,15 @@
                              std::unique_ptr<printing::PrintSettings> settings,
                              StartPrintJobCallback callback) = 0;
 
+  // Returns false if there is no active print job with specified id.
+  // Returns true otherwise.
+  virtual bool CancelPrintJob(const std::string& job_id) = 0;
+
   // This should be called when CupsPrintJobManager created CupsPrintJob.
-  virtual void OnPrintJobCreated(const std::string& extension_id,
-                                 const std::string& job_id) = 0;
+  virtual void OnPrintJobCreated(
+      const std::string& extension_id,
+      const std::string& job_id,
+      base::WeakPtr<chromeos::CupsPrintJob> cups_job) = 0;
 
   // This should be called when CupsPrintJob is finished (it could be either
   // completed, failed or cancelled).
diff --git a/chrome/browser/extensions/api/printing/print_job_submitter.cc b/chrome/browser/extensions/api/printing/print_job_submitter.cc
index f266659..d5f6f69 100644
--- a/chrome/browser/extensions/api/printing/print_job_submitter.cc
+++ b/chrome/browser/extensions/api/printing/print_job_submitter.cc
@@ -12,9 +12,9 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/threading/sequenced_task_runner_handle.h"
 #include "base/values.h"
-#include "build/chromeos_buildflags.h"
 #include "chrome/browser/chromeos/printing/cups_printers_manager.h"
 #include "chrome/browser/extensions/api/printing/print_job_controller.h"
+#include "chrome/browser/extensions/api/printing/printer_capabilities_provider.h"
 #include "chrome/browser/extensions/api/printing/printing_api_utils.h"
 #include "chrome/browser/printing/printing_service.h"
 #include "chrome/browser/profiles/profile.h"
@@ -22,7 +22,6 @@
 #include "chrome/browser/ui/native_window_tracker.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/services/printing/public/mojom/pdf_flattener.mojom.h"
-#include "chromeos/crosapi/mojom/local_printer.mojom.h"
 #include "chromeos/printing/printer_configuration.h"
 #include "components/prefs/pref_service.h"
 #include "content/public/browser/browser_context.h"
@@ -36,12 +35,6 @@
 #include "ui/gfx/image/image.h"
 #include "ui/gfx/image/image_skia.h"
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-#include "chrome/browser/ash/crosapi/local_printer_ash.h"
-#elif BUILDFLAG(IS_CHROMEOS_LACROS)
-#include "chromeos/lacros/lacros_service.h"
-#endif
-
 namespace extensions {
 
 namespace {
@@ -87,24 +80,20 @@
 PrintJobSubmitter::PrintJobSubmitter(
     gfx::NativeWindow native_window,
     content::BrowserContext* browser_context,
+    chromeos::CupsPrintersManager* printers_manager,
+    PrinterCapabilitiesProvider* printer_capabilities_provider,
     PrintJobController* print_job_controller,
     mojo::Remote<printing::mojom::PdfFlattener>* pdf_flattener,
     scoped_refptr<const extensions::Extension> extension,
-    api::printing::SubmitJobRequest request,
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-    int local_printer_version,
-#endif
-    crosapi::mojom::LocalPrinter* local_printer)
+    api::printing::SubmitJobRequest request)
     : native_window_(native_window),
       browser_context_(browser_context),
+      printers_manager_(printers_manager),
+      printer_capabilities_provider_(printer_capabilities_provider),
       print_job_controller_(print_job_controller),
       pdf_flattener_(pdf_flattener),
       extension_(extension),
-      request_(std::move(request)),
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-      local_printer_version_(local_printer_version),
-#endif
-      local_printer_(local_printer) {
+      request_(std::move(request)) {
   DCHECK(extension);
   if (native_window)
     native_window_tracker_ = NativeWindowTracker::Create(native_window);
@@ -141,31 +130,26 @@
 }
 
 void PrintJobSubmitter::CheckPrinter() {
-  if (!local_printer_) {
-    LOG(ERROR)
-        << "Local printer not available (PrintJobSubmitter::CheckPrinter()";
-    CheckCapabilitiesCompatibility(nullptr);
+  absl::optional<chromeos::Printer> printer =
+      printers_manager_->GetPrinter(request_.job.printer_id);
+  if (!printer) {
+    FireErrorCallback(kInvalidPrinterId);
     return;
   }
-  local_printer_->GetCapability(
+  printer_name_ = base::UTF8ToUTF16(printer->display_name());
+  printer_capabilities_provider_->GetPrinterCapabilities(
       request_.job.printer_id,
       base::BindOnce(&PrintJobSubmitter::CheckCapabilitiesCompatibility,
                      weak_ptr_factory_.GetWeakPtr()));
 }
 
 void PrintJobSubmitter::CheckCapabilitiesCompatibility(
-    crosapi::mojom::CapabilitiesResponsePtr caps) {
-  if (!caps) {
-    FireErrorCallback(kInvalidPrinterId);
-    return;
-  }
-  printer_name_ = base::UTF8ToUTF16(caps->basic_info->name);
-  if (!caps->capabilities) {
+    absl::optional<printing::PrinterSemanticCapsAndDefaults> capabilities) {
+  if (!capabilities) {
     FireErrorCallback(kPrinterUnavailable);
     return;
   }
-  if (!CheckSettingsAndCapabilitiesCompatibility(*settings_,
-                                                 *caps->capabilities)) {
+  if (!CheckSettingsAndCapabilitiesCompatibility(*settings_, *capabilities)) {
     FireErrorCallback(kUnsupportedTicket);
     return;
   }
diff --git a/chrome/browser/extensions/api/printing/print_job_submitter.h b/chrome/browser/extensions/api/printing/print_job_submitter.h
index 7bc3216..7d05234 100644
--- a/chrome/browser/extensions/api/printing/print_job_submitter.h
+++ b/chrome/browser/extensions/api/printing/print_job_submitter.h
@@ -13,7 +13,6 @@
 #include "base/memory/read_only_shared_memory_region.h"
 #include "base/memory/weak_ptr.h"
 #include "chrome/common/extensions/api/printing.h"
-#include "chromeos/crosapi/mojom/local_printer.mojom-forward.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "ui/gfx/native_widget_types.h"
@@ -22,6 +21,10 @@
 class ReadOnlySharedMemoryRegion;
 }  // namespace base
 
+namespace chromeos {
+class CupsPrintersManager;
+}  // namespace chromeos
+
 namespace content {
 class BrowserContext;
 }  // namespace content
@@ -34,6 +37,7 @@
 namespace mojom {
 class PdfFlattener;
 }  // namespace mojom
+struct PrinterSemanticCapsAndDefaults;
 class PrintSettings;
 }  // namespace printing
 
@@ -42,6 +46,7 @@
 namespace extensions {
 
 class Extension;
+class PrinterCapabilitiesProvider;
 class PrintJobController;
 
 // Handles chrome.printing.submitJob() API request including parsing job
@@ -59,14 +64,12 @@
 
   PrintJobSubmitter(gfx::NativeWindow native_window,
                     content::BrowserContext* browser_context,
+                    chromeos::CupsPrintersManager* printers_manager,
+                    PrinterCapabilitiesProvider* printer_capabilities_provider,
                     PrintJobController* print_job_controller,
                     mojo::Remote<printing::mojom::PdfFlattener>* pdf_flattener,
                     scoped_refptr<const extensions::Extension> extension,
-                    api::printing::SubmitJobRequest request,
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-                    int local_printer_version,
-#endif
-                    crosapi::mojom::LocalPrinter* local_printer);
+                    api::printing::SubmitJobRequest request);
   ~PrintJobSubmitter();
 
   // Only one call to Start() should happen at a time.
@@ -79,8 +82,6 @@
   static base::AutoReset<bool> SkipConfirmationDialogForTesting();
 
  private:
-  friend class PrintingAPIHandler;
-
   bool CheckContentType() const;
 
   bool CheckPrintTicket();
@@ -88,7 +89,7 @@
   void CheckPrinter();
 
   void CheckCapabilitiesCompatibility(
-      crosapi::mojom::CapabilitiesResponsePtr capabilities);
+      absl::optional<printing::PrinterSemanticCapsAndDefaults> capabilities);
 
   void ReadDocumentData();
 
@@ -118,6 +119,8 @@
   std::unique_ptr<NativeWindowTracker> native_window_tracker_;
 
   // These objects are owned by PrintingAPIHandler.
+  chromeos::CupsPrintersManager* const printers_manager_;
+  PrinterCapabilitiesProvider* const printer_capabilities_provider_;
   PrintJobController* const print_job_controller_;
   mojo::Remote<printing::mojom::PdfFlattener>* const pdf_flattener_;
 
@@ -131,10 +134,6 @@
   // This is cleared after the request is handled (successfully or not).
   SubmitJobCallback callback_;
 
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-  const int local_printer_version_;
-#endif
-  crosapi::mojom::LocalPrinter* const local_printer_;
   base::WeakPtrFactory<PrintJobSubmitter> weak_ptr_factory_{this};
 };
 
diff --git a/chrome/browser/extensions/api/printing/printer_capabilities_provider.cc b/chrome/browser/extensions/api/printing/printer_capabilities_provider.cc
new file mode 100644
index 0000000..8d8bffda
--- /dev/null
+++ b/chrome/browser/extensions/api/printing/printer_capabilities_provider.cc
@@ -0,0 +1,114 @@
+// 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 "chrome/browser/extensions/api/printing/printer_capabilities_provider.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/task/post_task.h"
+#include "base/task/task_traits.h"
+#include "base/task/thread_pool.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/chromeos/printing/cups_printers_manager.h"
+#include "chromeos/printing/printer_configuration.h"
+#include "printing/backend/print_backend.h"
+
+namespace extensions {
+
+namespace {
+
+constexpr int kMaxPrintersCount = 20;
+
+absl::optional<printing::PrinterSemanticCapsAndDefaults>
+FetchCapabilitiesOnBlockingTaskRunner(const std::string& printer_id) {
+  scoped_refptr<printing::PrintBackend> backend(
+      printing::PrintBackend::CreateInstance(
+          g_browser_process->GetApplicationLocale()));
+  printing::PrinterSemanticCapsAndDefaults capabilities;
+  if (backend->GetPrinterSemanticCapsAndDefaults(printer_id, &capabilities) !=
+      printing::mojom::ResultCode::kSuccess) {
+    LOG(WARNING) << "Failed to get capabilities for " << printer_id;
+    return absl::nullopt;
+  }
+  return capabilities;
+}
+
+}  // namespace
+
+PrinterCapabilitiesProvider::PrinterCapabilitiesProvider(
+    chromeos::CupsPrintersManager* printers_manager,
+    std::unique_ptr<chromeos::PrinterConfigurer> printer_configurer)
+    : printers_manager_(printers_manager),
+      printer_configurer_(std::move(printer_configurer)),
+      printer_capabilities_cache_(kMaxPrintersCount) {}
+
+PrinterCapabilitiesProvider::~PrinterCapabilitiesProvider() = default;
+
+void PrinterCapabilitiesProvider::GetPrinterCapabilities(
+    const std::string& printer_id,
+    GetPrinterCapabilitiesCallback callback) {
+  absl::optional<chromeos::Printer> printer =
+      printers_manager_->GetPrinter(printer_id);
+  if (!printer) {
+    base::SequencedTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE, base::BindOnce(std::move(callback), absl::nullopt));
+    return;
+  }
+
+  if (!printers_manager_->IsPrinterInstalled(*printer)) {
+    printer_configurer_->SetUpPrinter(
+        *printer,
+        base::BindOnce(&PrinterCapabilitiesProvider::OnPrinterInstalled,
+                       weak_ptr_factory_.GetWeakPtr(), *printer,
+                       std::move(callback)));
+    return;
+  }
+
+  auto capabilities = printer_capabilities_cache_.Get(printer->id());
+  if (capabilities == printer_capabilities_cache_.end()) {
+    FetchCapabilities(printer->id(), std::move(callback));
+    return;
+  }
+  base::SequencedTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(std::move(callback), capabilities->second));
+}
+
+void PrinterCapabilitiesProvider::OnPrinterInstalled(
+    const chromeos::Printer& printer,
+    GetPrinterCapabilitiesCallback callback,
+    chromeos::PrinterSetupResult result) {
+  if (result != chromeos::kSuccess) {
+    base::SequencedTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE, base::BindOnce(std::move(callback), absl::nullopt));
+    return;
+  }
+  printers_manager_->PrinterInstalled(printer, /*is_automatic=*/true);
+  FetchCapabilities(printer.id(), std::move(callback));
+}
+
+void PrinterCapabilitiesProvider::FetchCapabilities(
+    const std::string& printer_id,
+    GetPrinterCapabilitiesCallback callback) {
+  base::ThreadPool::PostTaskAndReplyWithResult(
+      FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
+      base::BindOnce(&FetchCapabilitiesOnBlockingTaskRunner, printer_id),
+      base::BindOnce(&PrinterCapabilitiesProvider::OnCapabilitiesFetched,
+                     weak_ptr_factory_.GetWeakPtr(), printer_id,
+                     std::move(callback)));
+}
+
+void PrinterCapabilitiesProvider::OnCapabilitiesFetched(
+    const std::string& printer_id,
+    GetPrinterCapabilitiesCallback callback,
+    absl::optional<printing::PrinterSemanticCapsAndDefaults> capabilities) {
+  if (capabilities.has_value())
+    printer_capabilities_cache_.Put(printer_id, capabilities.value());
+  base::SequencedTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(std::move(callback), std::move(capabilities)));
+}
+
+}  // namespace extensions
diff --git a/chrome/browser/extensions/api/printing/printer_capabilities_provider.h b/chrome/browser/extensions/api/printing/printer_capabilities_provider.h
new file mode 100644
index 0000000..bc6ad94
--- /dev/null
+++ b/chrome/browser/extensions/api/printing/printer_capabilities_provider.h
@@ -0,0 +1,70 @@
+// 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 CHROME_BROWSER_EXTENSIONS_API_PRINTING_PRINTER_CAPABILITIES_PROVIDER_H_
+#define CHROME_BROWSER_EXTENSIONS_API_PRINTING_PRINTER_CAPABILITIES_PROVIDER_H_
+
+#include <memory>
+#include <string>
+
+#include "base/callback_forward.h"
+#include "base/containers/mru_cache.h"
+#include "base/memory/weak_ptr.h"
+#include "chrome/browser/chromeos/printing/printer_configurer.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace chromeos {
+class CupsPrintersManager;
+class Printer;
+}  // namespace chromeos
+
+namespace printing {
+struct PrinterSemanticCapsAndDefaults;
+}  // namespace printing
+
+namespace extensions {
+
+// Provides the capabilities for printers installed in Chrome OS.
+// Is used for chrome.printing API handling.
+class PrinterCapabilitiesProvider {
+ public:
+  using GetPrinterCapabilitiesCallback = base::OnceCallback<void(
+      absl::optional<printing::PrinterSemanticCapsAndDefaults> capabilities)>;
+
+  PrinterCapabilitiesProvider(
+      chromeos::CupsPrintersManager* printers_manager,
+      std::unique_ptr<chromeos::PrinterConfigurer> printer_configurer);
+  ~PrinterCapabilitiesProvider();
+
+  void GetPrinterCapabilities(const std::string& printer_id,
+                              GetPrinterCapabilitiesCallback callback);
+
+ private:
+  using PrinterCapabilitiesCache =
+      base::MRUCache<std::string, printing::PrinterSemanticCapsAndDefaults>;
+
+  void OnPrinterInstalled(const chromeos::Printer& printer,
+                          GetPrinterCapabilitiesCallback callback,
+                          chromeos::PrinterSetupResult result);
+
+  void FetchCapabilities(const std::string& printer_id,
+                         GetPrinterCapabilitiesCallback callback);
+
+  void OnCapabilitiesFetched(
+      const std::string& printer_id,
+      GetPrinterCapabilitiesCallback callback,
+      absl::optional<printing::PrinterSemanticCapsAndDefaults> capabilities);
+
+  chromeos::CupsPrintersManager* const printers_manager_;
+  std::unique_ptr<chromeos::PrinterConfigurer> printer_configurer_;
+
+  // Stores mapping from printer id to cached printer capabilities.
+  PrinterCapabilitiesCache printer_capabilities_cache_;
+
+  base::WeakPtrFactory<PrinterCapabilitiesProvider> weak_ptr_factory_{this};
+};
+
+}  // namespace extensions
+
+#endif  // CHROME_BROWSER_EXTENSIONS_API_PRINTING_PRINTER_CAPABILITIES_PROVIDER_H_
diff --git a/chrome/browser/extensions/api/printing/printing_api.cc b/chrome/browser/extensions/api/printing/printing_api.cc
index 8dd951f..b5fb2f4 100644
--- a/chrome/browser/extensions/api/printing/printing_api.cc
+++ b/chrome/browser/extensions/api/printing/printing_api.cc
@@ -10,7 +10,6 @@
 #include "base/values.h"
 #include "chrome/browser/extensions/api/printing/printing_api_handler.h"
 #include "chrome/browser/extensions/chrome_extension_function_details.h"
-#include "extensions/browser/extension_function.h"
 #include "extensions/browser/quota_service.h"
 
 namespace extensions {
@@ -70,19 +69,11 @@
   return RespondNow(NoArguments());
 }
 
-PrintingGetPrintersFunction::PrintingGetPrintersFunction() = default;
 PrintingGetPrintersFunction::~PrintingGetPrintersFunction() = default;
 
 ExtensionFunction::ResponseAction PrintingGetPrintersFunction::Run() {
-  PrintingAPIHandler::Get(browser_context())
-      ->GetPrinters(
-          base::BindOnce(&PrintingGetPrintersFunction::OnPrintersReady, this));
-  return RespondLater();
-}
-
-void PrintingGetPrintersFunction::OnPrintersReady(
-    std::vector<api::printing::Printer> printers) {
-  Respond(ArgumentList(api::printing::GetPrinters::Results::Create(printers)));
+  return RespondNow(ArgumentList(api::printing::GetPrinters::Results::Create(
+      PrintingAPIHandler::Get(browser_context())->GetPrinters())));
 }
 
 PrintingGetPrinterInfoFunction::~PrintingGetPrinterInfoFunction() = default;
diff --git a/chrome/browser/extensions/api/printing/printing_api.h b/chrome/browser/extensions/api/printing/printing_api.h
index 160dfbd..f07eacf2 100644
--- a/chrome/browser/extensions/api/printing/printing_api.h
+++ b/chrome/browser/extensions/api/printing/printing_api.h
@@ -47,9 +47,6 @@
 };
 
 class PrintingGetPrintersFunction : public ExtensionFunction {
- public:
-  PrintingGetPrintersFunction();
-
  protected:
   ~PrintingGetPrintersFunction() override;
 
@@ -57,7 +54,6 @@
   ResponseAction Run() override;
 
  private:
-  void OnPrintersReady(std::vector<api::printing::Printer> printers);
   DECLARE_EXTENSION_FUNCTION("printing.getPrinters", PRINTING_GETPRINTERS)
 };
 
diff --git a/chrome/browser/extensions/api/printing/printing_api_handler.cc b/chrome/browser/extensions/api/printing/printing_api_handler.cc
index bd60d3e41..6282ee3 100644
--- a/chrome/browser/extensions/api/printing/printing_api_handler.cc
+++ b/chrome/browser/extensions/api/printing/printing_api_handler.cc
@@ -7,52 +7,30 @@
 #include <utility>
 
 #include "base/bind.h"
-#include "base/callback.h"
-#include "base/callback_helpers.h"
-#include "base/check.h"
-#include "base/check_op.h"
-#include "base/containers/contains.h"
 #include "base/location.h"
+#include "base/memory/ptr_util.h"
 #include "base/no_destructor.h"
-#include "base/sequenced_task_runner.h"
-#include "base/strings/stringprintf.h"
-#include "base/strings/utf_string_conversions.h"
 #include "base/task/post_task.h"
-#include "base/task_runner.h"
 #include "base/threading/sequenced_task_runner_handle.h"
-#include "build/chromeos_buildflags.h"
-#include "chrome/browser/chrome_notification_types.h"
+#include "chrome/browser/ash/profiles/profile_helper.h"
+#include "chrome/browser/chromeos/printing/cups_print_job.h"
+#include "chrome/browser/chromeos/printing/cups_printers_manager.h"
 #include "chrome/browser/chromeos/printing/cups_wrapper.h"
+#include "chrome/browser/chromeos/printing/printer_configurer.h"
 #include "chrome/browser/chromeos/printing/printer_error_codes.h"
 #include "chrome/browser/extensions/api/printing/printing_api_utils.h"
-#include "chrome/browser/printing/print_job.h"
 #include "chrome/browser/printing/print_preview_sticky_settings.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/common/pref_names.h"
-#include "chromeos/crosapi/mojom/local_printer.mojom.h"
+#include "chromeos/printing/printer_configuration.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
 #include "components/printing/common/cloud_print_cdd_conversion.h"
 #include "content/public/browser/browser_context.h"
-#include "content/public/browser/browser_thread.h"
-#include "content/public/browser/notification_details.h"
-#include "content/public/browser/notification_observer.h"
-#include "content/public/browser/notification_registrar.h"
-#include "content/public/browser/notification_service.h"
-#include "content/public/browser/notification_source.h"
 #include "extensions/browser/event_router.h"
 #include "extensions/browser/extension_registry.h"
+#include "printing/backend/cups_jobs.h"
 #include "printing/backend/print_backend.h"
-#include "printing/print_settings.h"
-#include "printing/printed_document.h"
-
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-#include "chrome/browser/ash/crosapi/crosapi_ash.h"
-#include "chrome/browser/ash/crosapi/crosapi_manager.h"
-#include "chrome/browser/ash/crosapi/local_printer_ash.h"
-#elif BUILDFLAG(IS_CHROMEOS_LACROS)
-#include "chromeos/lacros/lacros_service.h"
-#endif
 
 namespace extensions {
 
@@ -69,75 +47,57 @@
     content::BrowserContext* browser_context,
     EventRouter* event_router,
     ExtensionRegistry* extension_registry,
+    chromeos::CupsPrintJobManager* print_job_manager,
+    chromeos::CupsPrintersManager* printers_manager,
     std::unique_ptr<PrintJobController> print_job_controller,
-    std::unique_ptr<chromeos::CupsWrapper> cups_wrapper,
-    crosapi::mojom::LocalPrinter* local_printer) {
-  auto handler = std::make_unique<PrintingAPIHandler>(
-      browser_context, event_router, extension_registry,
-      std::move(print_job_controller), std::move(cups_wrapper), local_printer);
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-  handler->local_printer_version_ = crosapi::mojom::LocalPrinter::Version_;
-#endif
-  return handler;
+    std::unique_ptr<chromeos::PrinterConfigurer> printer_configurer,
+    std::unique_ptr<chromeos::CupsWrapper> cups_wrapper) {
+  return base::WrapUnique(new PrintingAPIHandler(
+      browser_context, event_router, extension_registry, print_job_manager,
+      printers_manager, std::move(print_job_controller),
+      std::move(printer_configurer), std::move(cups_wrapper)));
 }
 
 PrintingAPIHandler::PrintingAPIHandler(content::BrowserContext* browser_context)
-    : PrintingAPIHandler(browser_context,
-                         EventRouter::Get(browser_context),
-                         ExtensionRegistry::Get(browser_context),
-                         PrintJobController::Create(),
-                         chromeos::CupsWrapper::Create()) {
-  registrar_.Add(this, chrome::NOTIFICATION_PRINT_JOB_EVENT,
-                 content::NotificationService::AllSources());
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-  DCHECK(crosapi::CrosapiManager::IsInitialized());
-  local_printer_ =
-      crosapi::CrosapiManager::Get()->crosapi_ash()->local_printer_ash();
-#elif BUILDFLAG(IS_CHROMEOS_LACROS)
-  chromeos::LacrosService* service = chromeos::LacrosService::Get();
-  if (!service->IsAvailable<crosapi::mojom::LocalPrinter>()) {
-    LOG(ERROR) << "Local printer not available (PrintJobControllerImpl)";
-    return;
-  }
-  local_printer_ = service->GetRemote<crosapi::mojom::LocalPrinter>().get();
-  local_printer_version_ =
-      service->GetInterfaceVersion(crosapi::mojom::LocalPrinter::Uuid_);
-  if (local_printer_version_ <
-      int{crosapi::mojom::LocalPrinter::MethodMinVersions::
-              kAddPrintJobObserverMinVersion}) {
-    LOG(WARNING) << "Ash LocalPrinter version " << local_printer_version_
-                 << " does not support AddExtensionPrintJobObserver().";
-    return;
-  }
-#endif
-  local_printer_->AddPrintJobObserver(
-      receiver_.BindNewPipeAndPassRemoteWithVersion(),
-      crosapi::mojom::PrintJobSource::kExtension, base::DoNothing());
-}
+    : PrintingAPIHandler(
+          browser_context,
+          EventRouter::Get(browser_context),
+          ExtensionRegistry::Get(browser_context),
+          chromeos::CupsPrintJobManagerFactory::GetForBrowserContext(
+              browser_context),
+          chromeos::CupsPrintersManagerFactory::GetForBrowserContext(
+              browser_context),
+          PrintJobController::Create(
+              chromeos::CupsPrintJobManagerFactory::GetForBrowserContext(
+                  browser_context)),
+          chromeos::PrinterConfigurer::Create(
+              Profile::FromBrowserContext(browser_context)),
+          chromeos::CupsWrapper::Create()) {}
 
 PrintingAPIHandler::PrintingAPIHandler(
     content::BrowserContext* browser_context,
     EventRouter* event_router,
     ExtensionRegistry* extension_registry,
+    chromeos::CupsPrintJobManager* print_job_manager,
+    chromeos::CupsPrintersManager* printers_manager,
     std::unique_ptr<PrintJobController> print_job_controller,
-    std::unique_ptr<chromeos::CupsWrapper> cups_wrapper,
-    crosapi::mojom::LocalPrinter* local_printer)
+    std::unique_ptr<chromeos::PrinterConfigurer> printer_configurer,
+    std::unique_ptr<chromeos::CupsWrapper> cups_wrapper)
     : browser_context_(browser_context),
       event_router_(event_router),
       extension_registry_(extension_registry),
+      print_job_manager_(print_job_manager),
+      printers_manager_(printers_manager),
       print_job_controller_(std::move(print_job_controller)),
-      cups_wrapper_(std::move(cups_wrapper)),
-      local_printer_(local_printer) {}
+      printer_capabilities_provider_(printers_manager,
+                                     std::move(printer_configurer)),
+      cups_wrapper_(std::move(cups_wrapper)) {
+  print_job_manager_observation_.Observe(print_job_manager_);
+}
 
 PrintingAPIHandler::~PrintingAPIHandler() = default;
 
 // static
-std::string PrintingAPIHandler::CreateUniqueId(const std::string& printer_id,
-                                               int job_id) {
-  return base::StringPrintf("%s%d", printer_id.c_str(), job_id);
-}
-
-// static
 BrowserContextKeyedAPIFactory<PrintingAPIHandler>*
 PrintingAPIHandler::GetFactoryInstance() {
   static base::NoDestructor<BrowserContextKeyedAPIFactory<PrintingAPIHandler>>
@@ -163,12 +123,9 @@
     std::unique_ptr<api::printing::SubmitJob::Params> params,
     PrintJobSubmitter::SubmitJobCallback callback) {
   auto print_job_submitter = std::make_unique<PrintJobSubmitter>(
-      native_window, browser_context_, print_job_controller_.get(),
-      &pdf_flattener_, std::move(extension), std::move(params->request),
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-      local_printer_version_,
-#endif
-      local_printer_);
+      native_window, browser_context_, printers_manager_,
+      &printer_capabilities_provider_, print_job_controller_.get(),
+      &pdf_flattener_, std::move(extension), std::move(params->request));
   PrintJobSubmitter* print_job_submitter_ptr = print_job_submitter.get();
   print_job_submitter_ptr->Start(base::BindOnce(
       &PrintingAPIHandler::OnPrintJobSubmitted, weak_ptr_factory_.GetWeakPtr(),
@@ -181,8 +138,6 @@
     absl::optional<api::printing::SubmitJobStatus> status,
     std::unique_ptr<std::string> job_id,
     absl::optional<std::string> error) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-
   base::SequencedTaskRunnerHandle::Get()->PostTask(
       FROM_HERE,
       base::BindOnce(std::move(callback), status, std::move(job_id), error));
@@ -191,56 +146,22 @@
 absl::optional<std::string> PrintingAPIHandler::CancelJob(
     const std::string& extension_id,
     const std::string& job_id) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-
-  auto it = print_jobs_.find(job_id);
-
+  auto it = print_jobs_extension_ids_.find(job_id);
   // If there was no print job with specified id sent by the extension return
   // an error.
-  if (it == print_jobs_.end() || it->second.extension_id != extension_id) {
+  if (it == print_jobs_extension_ids_.end() || it->second != extension_id)
     return kNoActivePrintJobWithIdError;
-  }
 
-  if (!local_printer_)
-    return "Local printer not available";
+  // If we can't cancel the print job (e.g. it's in terminated state) return an
+  // error.
+  if (!print_job_controller_->CancelPrintJob(job_id))
+    return kNoActivePrintJobWithIdError;
 
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-  if (local_printer_version_ <
-      int{crosapi::mojom::LocalPrinter::MethodMinVersions::
-              kCancelPrintJobMinVersion}) {
-    LOG(WARNING) << "Ash LocalPrinter version " << local_printer_version_
-                 << " does not support CancelPrintJob().";
-    return "Ash local printer interface does not support CancelPrintJob()";
-  }
-#endif
-
-  local_printer_->CancelPrintJob(it->second.printer_id, it->second.job_id,
-                                 base::DoNothing());
+  // Return no error otherwise.
   return absl::nullopt;
 }
 
-void PrintingAPIHandler::GetPrinters(GetPrintersCallback callback) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-
-  if (!local_printer_) {
-    LOG(ERROR) << "Local printer interface not available "
-                  "(PrintingAPIHandler::GetPrinters()";
-    base::SequencedTaskRunnerHandle::Get()->PostTask(
-        FROM_HERE, base::BindOnce(std::move(callback),
-                                  std::vector<api::printing::Printer>{}));
-    return;
-  }
-
-  local_printer_->GetPrinters(
-      base::BindOnce(&PrintingAPIHandler::OnPrintersRetrieved,
-                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
-}
-
-void PrintingAPIHandler::OnPrintersRetrieved(
-    GetPrintersCallback callback,
-    std::vector<crosapi::mojom::LocalDestinationInfoPtr> data) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-
+std::vector<api::printing::Printer> PrintingAPIHandler::GetPrinters() {
   PrefService* prefs =
       Profile::FromBrowserContext(browser_context_)->GetPrefs();
 
@@ -253,68 +174,63 @@
   base::flat_map<std::string, int> recently_used_ranks =
       sticky_settings->GetPrinterRecentlyUsedRanks();
 
-  std::vector<api::printing::Printer> printers;
-  printers.reserve(data.size());
-  for (const crosapi::mojom::LocalDestinationInfoPtr& ptr : data) {
-    printers.push_back(
-        PrinterToIdl(*ptr, default_printer_rules, recently_used_ranks));
+  static constexpr chromeos::PrinterClass kPrinterClasses[] = {
+      chromeos::PrinterClass::kEnterprise,
+      chromeos::PrinterClass::kSaved,
+      chromeos::PrinterClass::kAutomatic,
+  };
+  std::vector<api::printing::Printer> all_printers;
+  for (auto printer_class : kPrinterClasses) {
+    const std::vector<chromeos::Printer>& printers =
+        printers_manager_->GetPrinters(printer_class);
+    for (const auto& printer : printers) {
+      all_printers.emplace_back(
+          PrinterToIdl(printer, default_printer_rules, recently_used_ranks));
+    }
   }
-  base::SequencedTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, base::BindOnce(std::move(callback), std::move(printers)));
+  return all_printers;
 }
 
 void PrintingAPIHandler::GetPrinterInfo(const std::string& printer_id,
                                         GetPrinterInfoCallback callback) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-
-  if (!local_printer_) {
-    LOG(ERROR)
-        << "Local printer not available (PrintingAPIHandler::GetPrinterInfo)";
-    OnPrinterCapabilitiesRetrieved(std::string(), std::move(callback), nullptr);
-    return;
-  }
-  local_printer_->GetCapability(
-      printer_id,
-      base::BindOnce(&PrintingAPIHandler::OnPrinterCapabilitiesRetrieved,
-                     weak_ptr_factory_.GetWeakPtr(), printer_id,
-                     std::move(callback)));
-}
-
-void PrintingAPIHandler::OnPrinterCapabilitiesRetrieved(
-    const std::string& printer_id,
-    GetPrinterInfoCallback callback,
-    crosapi::mojom::CapabilitiesResponsePtr caps) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-
-  if (!caps) {
+  if (!printers_manager_->GetPrinter(printer_id)) {
     base::SequencedTaskRunnerHandle::Get()->PostTask(
         FROM_HERE,
         base::BindOnce(std::move(callback), /*capabilities=*/absl::nullopt,
                        /*status=*/absl::nullopt, kInvalidPrinterIdError));
     return;
   }
-  if (!caps->capabilities) {
+  printer_capabilities_provider_.GetPrinterCapabilities(
+      printer_id, base::BindOnce(&PrintingAPIHandler::GetPrinterStatus,
+                                 weak_ptr_factory_.GetWeakPtr(), printer_id,
+                                 std::move(callback)));
+}
+
+void PrintingAPIHandler::GetPrinterStatus(
+    const std::string& printer_id,
+    GetPrinterInfoCallback callback,
+    absl::optional<printing::PrinterSemanticCapsAndDefaults> capabilities) {
+  if (!capabilities.has_value()) {
     base::SequencedTaskRunnerHandle::Get()->PostTask(
         FROM_HERE,
         base::BindOnce(std::move(callback), /*capabilities=*/absl::nullopt,
-                       /*status=*/api::printing::PRINTER_STATUS_UNREACHABLE,
+                       api::printing::PRINTER_STATUS_UNREACHABLE,
                        /*error=*/absl::nullopt));
     return;
   }
+  base::Value capabilities_value =
+      cloud_print::PrinterSemanticCapsAndDefaultsToCdd(capabilities.value());
   cups_wrapper_->QueryCupsPrinterStatus(
       printer_id,
       base::BindOnce(&PrintingAPIHandler::OnPrinterStatusRetrieved,
                      weak_ptr_factory_.GetWeakPtr(), std::move(callback),
-                     cloud_print::PrinterSemanticCapsAndDefaultsToCdd(
-                         *caps->capabilities)));
+                     std::move(capabilities_value)));
 }
 
 void PrintingAPIHandler::OnPrinterStatusRetrieved(
     GetPrinterInfoCallback callback,
     base::Value capabilities,
     std::unique_ptr<::printing::PrinterStatus> printer_status) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-
   if (!printer_status) {
     base::SequencedTaskRunnerHandle::Get()->PostTask(
         FROM_HERE, base::BindOnce(std::move(callback), std::move(capabilities),
@@ -336,91 +252,72 @@
   print_job_controller_ = std::move(print_job_controller);
 }
 
-void PrintingAPIHandler::Observe(int type,
-                                 const content::NotificationSource& source,
-                                 const content::NotificationDetails& details) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  DCHECK_EQ(chrome::NOTIFICATION_PRINT_JOB_EVENT, type);
-
-  content::Source<printing::PrintJob> job(source);
-  content::Details<printing::JobEventDetails> job_details(details);
-  if (job->source() != crosapi::mojom::PrintJob::Source::EXTENSION ||
-      job_details->type() != printing::JobEventDetails::DOC_DONE ||
-      !extension_registry_->enabled_extensions().Contains(job->source_id())) {
+void PrintingAPIHandler::OnPrintJobCreated(
+    base::WeakPtr<chromeos::CupsPrintJob> job) {
+  DispatchJobStatusChangedEvent(api::printing::JOB_STATUS_PENDING, job);
+  if (!job || job->source() != printing::PrintJob::Source::EXTENSION)
     return;
-  }
+  const std::string& extension_id = job->source_id();
+  const std::string& job_id = job->GetUniqueId();
+  print_jobs_extension_ids_[job_id] = extension_id;
+  print_job_controller_->OnPrintJobCreated(extension_id, job_id, job);
+}
 
-  std::string printer_id =
-      base::UTF16ToUTF8(job_details->document()->settings().device_name());
-  std::string cups_id = CreateUniqueId(printer_id, job_details->job_id());
-  DCHECK(!base::Contains(print_jobs_, cups_id));
-  print_jobs_[cups_id] =
-      PrintJobInfo{printer_id, job_details->job_id(), job->source_id()};
-  print_job_controller_->OnPrintJobCreated(job->source_id(), cups_id);
+void PrintingAPIHandler::OnPrintJobStarted(
+    base::WeakPtr<chromeos::CupsPrintJob> job) {
+  DispatchJobStatusChangedEvent(api::printing::JOB_STATUS_IN_PROGRESS, job);
+}
+
+void PrintingAPIHandler::OnPrintJobDone(
+    base::WeakPtr<chromeos::CupsPrintJob> job) {
+  DispatchJobStatusChangedEvent(api::printing::JOB_STATUS_PRINTED, job);
+  FinishJob(job);
+}
+
+void PrintingAPIHandler::OnPrintJobError(
+    base::WeakPtr<chromeos::CupsPrintJob> job) {
+  DispatchJobStatusChangedEvent(api::printing::JOB_STATUS_FAILED, job);
+  FinishJob(job);
+}
+
+void PrintingAPIHandler::OnPrintJobCancelled(
+    base::WeakPtr<chromeos::CupsPrintJob> job) {
+  DispatchJobStatusChangedEvent(api::printing::JOB_STATUS_CANCELED, job);
+  FinishJob(job);
+}
+
+void PrintingAPIHandler::DispatchJobStatusChangedEvent(
+    api::printing::JobStatus job_status,
+    base::WeakPtr<chromeos::CupsPrintJob> job) {
+  if (!job || job->source() != printing::PrintJob::Source::EXTENSION)
+    return;
+
   auto event =
       std::make_unique<Event>(events::PRINTING_ON_JOB_STATUS_CHANGED,
                               api::printing::OnJobStatusChanged::kEventName,
                               api::printing::OnJobStatusChanged::Create(
-                                  cups_id, api::printing::JOB_STATUS_PENDING));
-  event_router_->DispatchEventToExtension(job->source_id(), std::move(event));
+                                  job->GetUniqueId(), job_status));
+
+  if (extension_registry_->enabled_extensions().Contains(job->source_id()))
+    event_router_->DispatchEventToExtension(job->source_id(), std::move(event));
 }
 
-void PrintingAPIHandler::OnPrintJobUpdate(
-    const std::string& printer_id,
-    unsigned int job_id,
-    crosapi::mojom::PrintJobStatus status) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-
-  bool done = true;
-  api::printing::JobStatus job_status;
-  switch (status) {
-    case crosapi::mojom::PrintJobStatus::kStarted:
-      job_status = api::printing::JOB_STATUS_IN_PROGRESS;
-      done = false;
-      break;
-    case crosapi::mojom::PrintJobStatus::kDone:
-      job_status = api::printing::JOB_STATUS_PRINTED;
-      break;
-    case crosapi::mojom::PrintJobStatus::kError:
-      job_status = api::printing::JOB_STATUS_FAILED;
-      break;
-    case crosapi::mojom::PrintJobStatus::kCancelled:
-      job_status = api::printing::JOB_STATUS_CANCELED;
-      break;
-    default:  // crosapi::mojom::PrintJobStatus::kCreated
-      return;
-  }
-
-  std::string cups_id = CreateUniqueId(printer_id, job_id);
-  auto it = print_jobs_.find(cups_id);
-  if (it == print_jobs_.end())
+void PrintingAPIHandler::FinishJob(base::WeakPtr<chromeos::CupsPrintJob> job) {
+  if (!job || job->source() != printing::PrintJob::Source::EXTENSION)
     return;
-  const std::string& extension_id = it->second.extension_id;
-
-  if (extension_registry_->enabled_extensions().Contains(extension_id)) {
-    auto event = std::make_unique<Event>(
-        events::PRINTING_ON_JOB_STATUS_CHANGED,
-        api::printing::OnJobStatusChanged::kEventName,
-        api::printing::OnJobStatusChanged::Create(cups_id, job_status));
-    event_router_->DispatchEventToExtension(extension_id, std::move(event));
-  }
-
-  if (done) {
-    print_jobs_.erase(it);
-    print_job_controller_->OnPrintJobFinished(cups_id);
-  }
+  const std::string& job_id = job->GetUniqueId();
+  print_jobs_extension_ids_.erase(job_id);
+  print_job_controller_->OnPrintJobFinished(job_id);
 }
 
 template <>
 KeyedService*
 BrowserContextKeyedAPIFactory<PrintingAPIHandler>::BuildServiceInstanceFor(
     content::BrowserContext* context) const {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-
   Profile* profile = Profile::FromBrowserContext(context);
   // We do not want an instance of PrintingAPIHandler on the lock screen.
   // This will lead to multiple printing notifications.
-  if (!profile->IsRegularProfile()) {
+  if (!chromeos::ProfileHelper::IsRegularProfile(profile)) {
     return nullptr;
   }
   return new PrintingAPIHandler(context);
diff --git a/chrome/browser/extensions/api/printing/printing_api_handler.h b/chrome/browser/extensions/api/printing/printing_api_handler.h
index 6877ddc..fdf0ce6 100644
--- a/chrome/browser/extensions/api/printing/printing_api_handler.h
+++ b/chrome/browser/extensions/api/printing/printing_api_handler.h
@@ -9,21 +9,19 @@
 #include <string>
 #include <vector>
 
-#include "base/callback.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "base/scoped_observation.h"
-#include "build/chromeos_buildflags.h"
+#include "chrome/browser/chromeos/printing/cups_print_job_manager.h"
+#include "chrome/browser/chromeos/printing/cups_print_job_manager_factory.h"
+#include "chrome/browser/chromeos/printing/cups_printers_manager_factory.h"
 #include "chrome/browser/extensions/api/printing/print_job_controller.h"
 #include "chrome/browser/extensions/api/printing/print_job_submitter.h"
+#include "chrome/browser/extensions/api/printing/printer_capabilities_provider.h"
 #include "chrome/common/extensions/api/printing.h"
 #include "chrome/services/printing/public/mojom/pdf_flattener.mojom.h"
-#include "chromeos/crosapi/mojom/local_printer.mojom.h"
-#include "content/public/browser/notification_observer.h"
-#include "content/public/browser/notification_registrar.h"
 #include "extensions/browser/browser_context_keyed_api_factory.h"
 #include "extensions/browser/event_router_factory.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/gfx/native_widget_types.h"
@@ -33,6 +31,7 @@
 namespace chromeos {
 class CupsWrapper;
 class Printer;
+class PrinterConfigurer;
 }  // namespace chromeos
 
 namespace content {
@@ -40,6 +39,7 @@
 }  // namespace content
 
 namespace printing {
+struct PrinterSemanticCapsAndDefaults;
 struct PrinterStatus;
 }  // namespace printing
 
@@ -48,20 +48,15 @@
 class PrintJobSubmitter;
 class ExtensionRegistry;
 
-// Handles chrome.printing API functions calls, observes NotificationService,
-// and generates OnJobStatusChanged() events of chrome.printing API.
-// The callback function is never run directly - it is posted to
-// base::SequencedTaskRunnerHandle::Get().
+// Handles chrome.printing API functions calls, observes CupsPrintJobManager and
+// generates OnJobStatusChanged() events of chrome.printing API.
 class PrintingAPIHandler : public BrowserContextKeyedAPI,
-                           public crosapi::mojom::PrintJobObserver,
-                           public content::NotificationObserver {
+                           public chromeos::CupsPrintJobManager::Observer {
  public:
   using SubmitJobCallback = base::OnceCallback<void(
       absl::optional<api::printing::SubmitJobStatus> status,
       std::unique_ptr<std::string> job_id,
       absl::optional<std::string> error)>;
-  using GetPrintersCallback =
-      base::OnceCallback<void(std::vector<api::printing::Printer>)>;
   using GetPrinterInfoCallback = base::OnceCallback<void(
       absl::optional<base::Value> capabilities,
       absl::optional<api::printing::PrinterStatus> status,
@@ -71,27 +66,15 @@
       content::BrowserContext* browser_context,
       EventRouter* event_router,
       ExtensionRegistry* extension_registry,
+      chromeos::CupsPrintJobManager* print_job_manager,
+      chromeos::CupsPrintersManager* printers_manager,
       std::unique_ptr<PrintJobController> print_job_controller,
-      std::unique_ptr<chromeos::CupsWrapper> cups_wrapper,
-      crosapi::mojom::LocalPrinter* local_printer);
+      std::unique_ptr<chromeos::PrinterConfigurer> printer_configurer,
+      std::unique_ptr<chromeos::CupsWrapper> cups_wrapper);
 
   explicit PrintingAPIHandler(content::BrowserContext* browser_context);
-  PrintingAPIHandler(content::BrowserContext* browser_context,
-                     EventRouter* event_router,
-                     ExtensionRegistry* extension_registry,
-                     std::unique_ptr<PrintJobController> print_job_controller,
-                     std::unique_ptr<chromeos::CupsWrapper> cups_wrapper,
-                     crosapi::mojom::LocalPrinter* local_printer = nullptr);
-  PrintingAPIHandler(const PrintingAPIHandler&) = delete;
-  PrintingAPIHandler& operator=(const PrintingAPIHandler&) = delete;
   ~PrintingAPIHandler() override;
 
-  static std::string CreateUniqueId(const std::string& printer_id, int job_id);
-
-  // NotificationObserver:
-  void Observe(int type,
-               const content::NotificationSource& source,
-               const content::NotificationDetails& details) override;
   // BrowserContextKeyedAPI:
   static BrowserContextKeyedAPIFactory<PrintingAPIHandler>*
   GetFactoryInstance();
@@ -99,16 +82,6 @@
   // Returns the current instance for |browser_context|.
   static PrintingAPIHandler* Get(content::BrowserContext* browser_context);
 
-  // Print jobs should be registered before OnPrintJobUpdate() is called.
-  void RegisterPrintJob(const std::string& printer_id,
-                        int job_id,
-                        const std::string& extension_id);
-
-  // crosapi::mojom::PrintJobObserver:
-  void OnPrintJobUpdate(const std::string& printer_id,
-                        unsigned int job_id,
-                        crosapi::mojom::PrintJobStatus status) override;
-
   // Register the printing API preference with the |registry|.
   static void RegisterProfilePrefs(PrefRegistrySimple* registry);
 
@@ -126,7 +99,7 @@
   absl::optional<std::string> CancelJob(const std::string& extension_id,
                                         const std::string& job_id);
 
-  void GetPrinters(GetPrintersCallback callback);
+  std::vector<api::printing::Printer> GetPrinters();
 
   void GetPrinterInfo(const std::string& printer_id,
                       GetPrinterInfoCallback callback);
@@ -138,11 +111,15 @@
   // Needed for BrowserContextKeyedAPI implementation.
   friend class BrowserContextKeyedAPIFactory<PrintingAPIHandler>;
 
-  struct PrintJobInfo {
-    std::string printer_id;
-    int job_id;
-    std::string extension_id;
-  };
+  PrintingAPIHandler(
+      content::BrowserContext* browser_context,
+      EventRouter* event_router,
+      ExtensionRegistry* extension_registry,
+      chromeos::CupsPrintJobManager* print_job_manager,
+      chromeos::CupsPrintersManager* printers_manager,
+      std::unique_ptr<PrintJobController> print_job_controller,
+      std::unique_ptr<chromeos::PrinterConfigurer> printer_configurer,
+      std::unique_ptr<chromeos::CupsWrapper> cups_wrapper);
 
   // This is needed to save ownership of |print_job_submitter| object which
   // could be destructed because of asynchronous work otherwise.
@@ -153,22 +130,28 @@
       std::unique_ptr<std::string> job_id,
       absl::optional<std::string> error);
 
-  void OnPrintersRetrieved(
-      GetPrintersCallback callback,
-      std::vector<crosapi::mojom::LocalDestinationInfoPtr> data);
-
-  // GetPrinterInfo() calls this function.
-  void OnPrinterCapabilitiesRetrieved(
+  void GetPrinterStatus(
       const std::string& printer_id,
       GetPrinterInfoCallback callback,
-      crosapi::mojom::CapabilitiesResponsePtr caps);
+      absl::optional<printing::PrinterSemanticCapsAndDefaults> capabilities);
 
-  // OnPrinterCapabilitiesRetrieved() calls this function.
   void OnPrinterStatusRetrieved(
       GetPrinterInfoCallback callback,
       base::Value capabilities,
       std::unique_ptr<::printing::PrinterStatus> printer_status);
 
+  // CupsPrintJobManager::Observer:
+  void OnPrintJobCreated(base::WeakPtr<chromeos::CupsPrintJob> job) override;
+  void OnPrintJobStarted(base::WeakPtr<chromeos::CupsPrintJob> job) override;
+  void OnPrintJobDone(base::WeakPtr<chromeos::CupsPrintJob> job) override;
+  void OnPrintJobError(base::WeakPtr<chromeos::CupsPrintJob> job) override;
+  void OnPrintJobCancelled(base::WeakPtr<chromeos::CupsPrintJob> job) override;
+
+  void DispatchJobStatusChangedEvent(api::printing::JobStatus job_status,
+                                     base::WeakPtr<chromeos::CupsPrintJob> job);
+
+  void FinishJob(base::WeakPtr<chromeos::CupsPrintJob> job);
+
   // BrowserContextKeyedAPI:
   static const bool kServiceIsNULLWhileTesting = true;
   static const char* service_name() { return "PrintingAPIHandler"; }
@@ -176,25 +159,28 @@
   content::BrowserContext* const browser_context_;
   EventRouter* const event_router_;
   ExtensionRegistry* const extension_registry_;
+
+  chromeos::CupsPrintJobManager* print_job_manager_;
+  chromeos::CupsPrintersManager* const printers_manager_;
   std::unique_ptr<PrintJobController> print_job_controller_;
+  PrinterCapabilitiesProvider printer_capabilities_provider_;
   std::unique_ptr<chromeos::CupsWrapper> cups_wrapper_;
-  content::NotificationRegistrar registrar_;
 
   // Remote interface used to flatten a PDF.
   mojo::Remote<printing::mojom::PdfFlattener> pdf_flattener_;
 
-  // Stores mapping from job id to PrintJobInfo object.
-  // This is needed to cancel print jobs.
-  base::flat_map<std::string, PrintJobInfo> print_jobs_;
+  // Stores mapping from job id to the extension id.
+  // This is needed to disallow extensions to cancel jobs initiated by other
+  // extensions.
+  base::flat_map<std::string, std::string> print_jobs_extension_ids_;
 
-  crosapi::mojom::LocalPrinter* local_printer_;
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-  int local_printer_version_ = 0;
-#endif
-
-  mojo::Receiver<crosapi::mojom::PrintJobObserver> receiver_{this};
+  base::ScopedObservation<chromeos::CupsPrintJobManager,
+                          chromeos::CupsPrintJobManager::Observer>
+      print_job_manager_observation_{this};
 
   base::WeakPtrFactory<PrintingAPIHandler> weak_ptr_factory_{this};
+
+  DISALLOW_COPY_AND_ASSIGN(PrintingAPIHandler);
 };
 
 template <>
@@ -202,6 +188,8 @@
   static void DeclareFactoryDependencies(
       BrowserContextKeyedAPIFactory<PrintingAPIHandler>* factory) {
     factory->DependsOn(EventRouterFactory::GetInstance());
+    factory->DependsOn(chromeos::CupsPrintJobManagerFactory::GetInstance());
+    factory->DependsOn(chromeos::CupsPrintersManagerFactory::GetInstance());
   }
 };
 
diff --git a/chrome/browser/extensions/api/printing/printing_api_utils.cc b/chrome/browser/extensions/api/printing/printing_api_utils.cc
index b126713..454b1e0e 100644
--- a/chrome/browser/extensions/api/printing/printing_api_utils.cc
+++ b/chrome/browser/extensions/api/printing/printing_api_utils.cc
@@ -7,12 +7,10 @@
 #include <algorithm>
 #include <memory>
 #include <utility>
-#include <vector>
 
 #include "base/containers/contains.h"
 #include "base/json/json_reader.h"
 #include "base/values.h"
-#include "chromeos/crosapi/mojom/local_printer.mojom.h"
 #include "chromeos/printing/printer_configuration.h"
 #include "components/cloud_devices/common/cloud_device_description.h"
 #include "components/cloud_devices/common/printer_description.h"
@@ -32,16 +30,27 @@
 constexpr char kIdPattern[] = "idPattern";
 constexpr char kNamePattern[] = "namePattern";
 
+idl::PrinterSource PrinterSourceToIdl(chromeos::Printer::Source source) {
+  switch (source) {
+    case chromeos::Printer::Source::SRC_USER_PREFS:
+      return idl::PRINTER_SOURCE_USER;
+    case chromeos::Printer::Source::SRC_POLICY:
+      return idl::PRINTER_SOURCE_POLICY;
+  }
+  NOTREACHED();
+  return idl::PRINTER_SOURCE_USER;
+}
+
 bool DoesPrinterMatchDefaultPrinterRules(
-    const crosapi::mojom::LocalDestinationInfo& printer,
+    const chromeos::Printer& printer,
     const absl::optional<DefaultPrinterRules>& rules) {
   if (!rules.has_value())
     return false;
   return (rules->kind.empty() || rules->kind == kLocal) &&
          (rules->id_pattern.empty() ||
-          RE2::FullMatch(printer.id, rules->id_pattern)) &&
+          RE2::FullMatch(printer.id(), rules->id_pattern)) &&
          (rules->name_pattern.empty() ||
-          RE2::FullMatch(printer.name, rules->name_pattern));
+          RE2::FullMatch(printer.display_name(), rules->name_pattern));
 }
 
 }  // namespace
@@ -73,21 +82,18 @@
 }
 
 idl::Printer PrinterToIdl(
-    const crosapi::mojom::LocalDestinationInfo& printer,
+    const chromeos::Printer& printer,
     const absl::optional<DefaultPrinterRules>& default_printer_rules,
     const base::flat_map<std::string, int>& recently_used_ranks) {
   idl::Printer idl_printer;
-  idl_printer.id = printer.id;
-  idl_printer.name = printer.name;
-  idl_printer.description = printer.description;
-  if (printer.uri)
-    idl_printer.uri = *printer.uri;
-  idl_printer.source = printer.configured_via_policy
-                           ? idl::PRINTER_SOURCE_POLICY
-                           : idl::PRINTER_SOURCE_USER;
+  idl_printer.id = printer.id();
+  idl_printer.name = printer.display_name();
+  idl_printer.description = printer.description();
+  idl_printer.uri = printer.uri().GetNormalized(true /*always_print_port*/);
+  idl_printer.source = PrinterSourceToIdl(printer.source());
   idl_printer.is_default =
       DoesPrinterMatchDefaultPrinterRules(printer, default_printer_rules);
-  auto it = recently_used_ranks.find(printer.id);
+  auto it = recently_used_ranks.find(printer.id());
   if (it != recently_used_ranks.end())
     idl_printer.recently_used_rank = std::make_unique<int>(it->second);
   else
@@ -116,7 +122,7 @@
     case chromeos::PrinterErrorCode::STOPPED:
       return idl::PRINTER_STATUS_STOPPED;
     default:
-      break;
+      return idl::PRINTER_STATUS_GENERIC_ISSUE;
   }
   return idl::PRINTER_STATUS_GENERIC_ISSUE;
 }
diff --git a/chrome/browser/extensions/api/printing/printing_api_utils.h b/chrome/browser/extensions/api/printing/printing_api_utils.h
index 7a31439..ee072b9 100644
--- a/chrome/browser/extensions/api/printing/printing_api_utils.h
+++ b/chrome/browser/extensions/api/printing/printing_api_utils.h
@@ -7,12 +7,10 @@
 
 #include <memory>
 #include <string>
-#include <vector>
 
 #include "base/containers/flat_map.h"
 #include "chrome/browser/chromeos/printing/printer_error_codes.h"
 #include "chrome/common/extensions/api/printing.h"
-#include "chromeos/crosapi/mojom/local_printer.mojom.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace base {
@@ -43,7 +41,7 @@
     const std::string& default_destination_selection_rules);
 
 api::printing::Printer PrinterToIdl(
-    const crosapi::mojom::LocalDestinationInfo& printer,
+    const chromeos::Printer& printer,
     const absl::optional<DefaultPrinterRules>& default_printer_rules,
     const base::flat_map<std::string, int>& recently_used_ranks);
 
diff --git a/chrome/browser/extensions/api/printing/printing_apitest.cc b/chrome/browser/extensions/api/printing/printing_apitest.cc
index e535ca02..b1250f0 100644
--- a/chrome/browser/extensions/api/printing/printing_apitest.cc
+++ b/chrome/browser/extensions/api/printing/printing_apitest.cc
@@ -3,14 +3,13 @@
 // found in the LICENSE file.
 
 #include "base/bind.h"
-#include "chrome/browser/ash/crosapi/crosapi_manager.h"
 #include "chrome/browser/chromeos/printing/cups_print_job_manager_factory.h"
 #include "chrome/browser/chromeos/printing/cups_printers_manager_factory.h"
 #include "chrome/browser/chromeos/printing/printer_configurer.h"
 #include "chrome/browser/chromeos/printing/test_cups_print_job_manager.h"
 #include "chrome/browser/chromeos/printing/test_cups_printers_manager.h"
 #include "chrome/browser/chromeos/printing/test_printer_configurer.h"
-#include "chrome/browser/extensions/api/printing/fake_print_job_controller_ash.h"
+#include "chrome/browser/extensions/api/printing/fake_print_job_controller.h"
 #include "chrome/browser/extensions/api/printing/printing_api.h"
 #include "chrome/browser/extensions/api/printing/printing_api_handler.h"
 #include "chrome/browser/extensions/extension_apitest.h"
@@ -105,14 +104,9 @@
   void AddAvailablePrinter(
       const std::string& printer_id,
       std::unique_ptr<printing::PrinterSemanticCapsAndDefaults> capabilities) {
-    GetPrintersManager()->AddPrinter(chromeos::Printer(printer_id),
+    chromeos::Printer printer = chromeos::Printer(printer_id);
+    GetPrintersManager()->AddPrinter(printer,
                                      chromeos::PrinterClass::kEnterprise);
-    chromeos::CupsPrinterStatus status(printer_id);
-    status.AddStatusReason(
-        chromeos::CupsPrinterStatus::CupsPrinterStatusReason::Reason::
-            kPrinterUnreachable,
-        chromeos::CupsPrinterStatus::CupsPrinterStatusReason::Severity::kError);
-    GetPrintersManager()->SetPrinterStatus(status);
     test_print_backend_->AddValidPrinter(printer_id, std::move(capabilities),
                                          nullptr);
   }
@@ -159,10 +153,10 @@
   ASSERT_TRUE(StartEmbeddedTestServer());
 
   AddAvailablePrinter(kId, ConstructPrinterCapabilities());
-  PrintingAPIHandler* handler = PrintingAPIHandler::Get(browser()->profile());
-  handler->SetPrintJobControllerForTesting(
-      std::make_unique<FakePrintJobControllerAsh>(GetPrintJobManager(),
-                                                  GetPrintersManager()));
+  PrintingAPIHandler::Get(browser()->profile())
+      ->SetPrintJobControllerForTesting(
+          std::make_unique<FakePrintJobController>(GetPrintJobManager(),
+                                                   GetPrintersManager()));
   base::AutoReset<bool> skip_confirmation_dialog_reset(
       PrintJobSubmitter::SkipConfirmationDialogForTesting());
 
@@ -176,10 +170,10 @@
   ASSERT_TRUE(StartEmbeddedTestServer());
 
   AddAvailablePrinter(kId, ConstructPrinterCapabilities());
-  PrintingAPIHandler* handler = PrintingAPIHandler::Get(browser()->profile());
-  handler->SetPrintJobControllerForTesting(
-      std::make_unique<FakePrintJobControllerAsh>(GetPrintJobManager(),
-                                                  GetPrintersManager()));
+  PrintingAPIHandler::Get(browser()->profile())
+      ->SetPrintJobControllerForTesting(
+          std::make_unique<FakePrintJobController>(GetPrintJobManager(),
+                                                   GetPrintersManager()));
   base::AutoReset<bool> skip_confirmation_dialog_reset(
       PrintJobSubmitter::SkipConfirmationDialogForTesting());
 
diff --git a/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_api_unittest.cc b/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_api_unittest.cc
index 684c309..08e03b288 100644
--- a/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_api_unittest.cc
+++ b/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_api_unittest.cc
@@ -36,6 +36,8 @@
 
 namespace {
 
+using testing::NiceMock;
+
 std::unique_ptr<base::Value> RunGetReferrerChainFunction(Browser* browser,
                                                          int tab_id) {
   scoped_refptr<SafeBrowsingPrivateGetReferrerChainFunction> function(
@@ -70,9 +72,14 @@
 }  // namespace
 
 class SafeBrowsingPrivateApiUnitTest : public ExtensionServiceTestBase {
+ public:
+  SafeBrowsingPrivateApiUnitTest(SafeBrowsingPrivateApiUnitTest&) = delete;
+  SafeBrowsingPrivateApiUnitTest& operator=(SafeBrowsingPrivateApiUnitTest&) =
+      delete;
+
  protected:
-  SafeBrowsingPrivateApiUnitTest() {}
-  ~SafeBrowsingPrivateApiUnitTest() override {}
+  SafeBrowsingPrivateApiUnitTest() = default;
+  ~SafeBrowsingPrivateApiUnitTest() override = default;
 
   Browser* browser() { return browser_.get(); }
 
@@ -82,8 +89,6 @@
 
   std::unique_ptr<TestBrowserWindow> browser_window_;
   std::unique_ptr<Browser> browser_;
-
-  DISALLOW_COPY_AND_ASSIGN(SafeBrowsingPrivateApiUnitTest);
 };
 
 void SafeBrowsingPrivateApiUnitTest::SetUp() {
@@ -98,15 +103,15 @@
 
   PasswordStoreFactory::GetInstance()->SetTestingFactoryAndUse(
       profile(),
-      base::BindRepeating(
-          &password_manager::BuildPasswordStore<
-              content::BrowserContext, password_manager::MockPasswordStore>));
+      base::BindRepeating(&password_manager::BuildPasswordStore<
+                          content::BrowserContext,
+                          NiceMock<password_manager::MockPasswordStore>>));
 
   AccountPasswordStoreFactory::GetInstance()->SetTestingFactoryAndUse(
       profile(),
-      base::BindRepeating(
-          &password_manager::BuildPasswordStore<
-              content::BrowserContext, password_manager::MockPasswordStore>));
+      base::BindRepeating(&password_manager::BuildPasswordStore<
+                          content::BrowserContext,
+                          NiceMock<password_manager::MockPasswordStore>>));
 
   // Initialize Safe Browsing service.
   safe_browsing::TestSafeBrowsingServiceFactory sb_service_factory;
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index d3fcd6c..0c42ae6 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -5333,6 +5333,11 @@
     "expiry_milestone": 85
   },
   {
+    "name": "traffic-counters-settings-ui",
+    "owners": ["khegde", "stevenjb"],
+    "expiry_milestone": 95
+  },
+  {
     "name": "translate-assist-content",
     "owners": [ "jds", "basiaz@google.com", "chrome-language@google.com" ],
     "expiry_milestone": 94
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index dacf17c..5c14a85 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -4935,6 +4935,10 @@
     "If enabled, the user can calibrate the touch screen displays in "
     "chrome://settings/display.";
 
+const char kTrafficCountersSettingsUiName[] = "Traffic Counters Settings UI";
+const char kTrafficCountersSettingsUiDescription[] =
+    "If enabled, the SettingsUI will show data usage for cellular networks";
+
 const char kUseFakeDeviceForMediaStreamName[] = "Use fake video capture device";
 const char kUseFakeDeviceForMediaStreamDescription[] =
     "Forces Chrome to use a fake video capture device (a rolling pacman with a "
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index fb96591..b93261d6 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -2842,6 +2842,9 @@
 extern const char kTouchscreenCalibrationName[];
 extern const char kTouchscreenCalibrationDescription[];
 
+extern const char kTrafficCountersSettingsUiName[];
+extern const char kTrafficCountersSettingsUiDescription[];
+
 extern const char kUiDevToolsName[];
 extern const char kUiDevToolsDescription[];
 
diff --git a/chrome/browser/media/cdm_document_service_impl_test.cc b/chrome/browser/media/cdm_document_service_impl_test.cc
index 6c376abb..db6a4b1 100644
--- a/chrome/browser/media/cdm_document_service_impl_test.cc
+++ b/chrome/browser/media/cdm_document_service_impl_test.cc
@@ -7,10 +7,12 @@
 #include <memory>
 
 #include "base/json/values_util.h"
+#include "base/run_loop.h"
 #include "base/test/gmock_callback_support.h"
 #include "base/test/mock_callback.h"
 #include "base/unguessable_token.h"
 #include "base/values.h"
+#include "chrome/browser/media/cdm_pref_service_helper.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
 #include "chrome/test/base/testing_browser_process.h"
@@ -191,4 +193,99 @@
   ASSERT_FALSE(cdm_preference_data->client_token.has_value());
 }
 
+// Check that we can clear the CDM preferences. `GetCdmPreferenceData()` should
+// return a new origin_id after the clearing operation.
+TEST_F(CdmDocumentServiceImplTest, ClearCdmPreferenceData) {
+  const auto kOrigin = url::Origin::Create(GURL(kTestOrigin));
+
+  NavigateToUrlAndCreateCdmDocumentService(GURL(kTestOrigin));
+  base::UnguessableToken origin_id = GetCdmPreferenceData()->origin_id;
+
+  base::Time start = base::Time::Now() - base::TimeDelta::FromHours(1);
+  base::Time end;  // null time
+
+  base::RunLoop loop1;
+
+  // With the filter returning false, the origin id should not be destroyed.
+  CdmPrefServiceHelper::ClearCdmPreferenceData(
+      user_prefs::UserPrefs::Get(
+          web_contents()->GetMainFrame()->GetBrowserContext()),
+      start, end, base::BindRepeating([](const GURL& url) { return false; }),
+      loop1.QuitClosure());
+
+  loop1.Run();
+  base::UnguessableToken same_origin_id = GetCdmPreferenceData()->origin_id;
+  ASSERT_EQ(origin_id, same_origin_id);
+
+  base::RunLoop loop2;
+
+  CdmPrefServiceHelper::ClearCdmPreferenceData(
+      user_prefs::UserPrefs::Get(
+          web_contents()->GetMainFrame()->GetBrowserContext()),
+      start, end, base::BindRepeating([](const GURL& url) { return true; }),
+      loop2.QuitClosure());
+
+  loop2.Run();
+
+  base::UnguessableToken new_origin_id = GetCdmPreferenceData()->origin_id;
+  ASSERT_NE(origin_id, new_origin_id);
+}
+
+// Check that we only clear the CDM preference that were set between start and
+// end.
+TEST_F(CdmDocumentServiceImplTest, ClearCdmPreferenceDataWrongTime) {
+  const auto kOrigin = url::Origin::Create(GURL(kTestOrigin));
+
+  NavigateToUrlAndCreateCdmDocumentService(GURL(kTestOrigin));
+  base::UnguessableToken origin_id = GetCdmPreferenceData()->origin_id;
+
+  base::Time start = base::Time::Now() - base::TimeDelta::FromHours(4);
+  base::Time end = start - base::TimeDelta::FromHours(2);
+
+  auto null_filter = base::RepeatingCallback<bool(const GURL&)>();
+
+  base::RunLoop loop;
+
+  CdmPrefServiceHelper::ClearCdmPreferenceData(
+      user_prefs::UserPrefs::Get(
+          web_contents()->GetMainFrame()->GetBrowserContext()),
+      start, end, null_filter, loop.QuitClosure());
+
+  loop.Run();
+
+  base::UnguessableToken new_origin_id = GetCdmPreferenceData()->origin_id;
+  ASSERT_EQ(origin_id, new_origin_id);
+}
+
+TEST_F(CdmDocumentServiceImplTest, ClearCdmPreferenceDataNullFilter) {
+  const auto kOrigin = url::Origin::Create(GURL(kTestOrigin));
+
+  NavigateToUrlAndCreateCdmDocumentService(GURL(kTestOrigin));
+  base::UnguessableToken origin_id_1 = GetCdmPreferenceData()->origin_id;
+
+  NavigateToUrlAndCreateCdmDocumentService(GURL(kTestOrigin2));
+  base::UnguessableToken origin_id_2 = GetCdmPreferenceData()->origin_id;
+
+  base::Time start = base::Time::Now() - base::TimeDelta::FromHours(1);
+  base::Time end;  // null time
+
+  auto null_filter = base::RepeatingCallback<bool(const GURL&)>();
+
+  base::RunLoop loop;
+
+  CdmPrefServiceHelper::ClearCdmPreferenceData(
+      user_prefs::UserPrefs::Get(
+          web_contents()->GetMainFrame()->GetBrowserContext()),
+      start, end, null_filter, loop.QuitClosure());
+
+  loop.Run();
+
+  base::UnguessableToken new_origin_id = GetCdmPreferenceData()->origin_id;
+  ASSERT_NE(origin_id_2, new_origin_id);
+
+  NavigateToUrlAndCreateCdmDocumentService(GURL(kTestOrigin));
+  new_origin_id = GetCdmPreferenceData()->origin_id;
+  ASSERT_NE(origin_id_1, new_origin_id);
+}
+
 }  // namespace content
diff --git a/chrome/browser/media/cdm_pref_service_helper.cc b/chrome/browser/media/cdm_pref_service_helper.cc
index ee1a361c..98bc4f8 100644
--- a/chrome/browser/media/cdm_pref_service_helper.cc
+++ b/chrome/browser/media/cdm_pref_service_helper.cc
@@ -26,6 +26,12 @@
 const char kClientToken[] = "client_token";
 const char kClientTokenCreationTime[] = "client_token_creation_time";
 
+bool TimeIsBetween(const base::Time& time,
+                   const base::Time& start,
+                   const base::Time& end) {
+  return time >= start && (end.is_null() || time <= end);
+}
+
 // Data stored in the kMediaCdmOriginData Pref dictionary.
 // {
 //     $origin_string: {
@@ -159,6 +165,58 @@
   registry->RegisterDictionaryPref(prefs::kMediaCdmOriginData);
 }
 
+// Removes the CDM preference data from origin dict if the session's creation
+// time falls in [`start`, `end`] and `filter` returns true on its origin.
+// `start` can be null, which would indicate that we should delete everything
+// since the beginning of time. `end` can also be null, in which case we can
+// just ignore it. If only `client_token_creation_time` falls between `start`
+// and `end`, we only clear that field. If `origin_id_creation_time` falls
+// between `start` and `end`, we clear the whole entry.
+void CdmPrefServiceHelper::ClearCdmPreferenceData(
+    PrefService* user_prefs,
+    base::Time start,
+    base::Time end,
+    const base::RepeatingCallback<bool(const GURL&)>& filter,
+    base::OnceClosure complete_cb) {
+  DVLOG(1) << __func__ << " From [" << start << ", " << end << "]";
+
+  DictionaryPrefUpdate update(user_prefs, prefs::kMediaCdmOriginData);
+
+  std::vector<std::string> origins_to_delete;
+  for (auto key_value : update->DictItems()) {
+    const std::string& origin = key_value.first;
+
+    // Null filter indicates that we should delete everything.
+    if (filter && !filter.Run(GURL(origin)))
+      continue;
+
+    const base::Value& origin_dict = key_value.second;
+    if (!origin_dict.is_dict()) {
+      DVLOG(ERROR) << "Could not parse the preference data. Removing entry.";
+      origins_to_delete.push_back(origin);
+      continue;
+    }
+
+    std::unique_ptr<CdmData> cdm_data = CdmData::FromDictValue(origin_dict);
+
+    if (TimeIsBetween(cdm_data->origin_id_creation_time(), start, end)) {
+      DVLOG(1) << "Clearing cdm pref data for " << origin;
+      origins_to_delete.push_back(origin);
+    } else if (TimeIsBetween(cdm_data->client_token_creation_time(), start,
+                             end)) {
+      key_value.second.RemoveKey(kClientToken);
+      key_value.second.RemoveKey(kClientTokenCreationTime);
+    }
+  }
+
+  // Remove CDM preference data.
+  for (const auto& origin_str : origins_to_delete)
+    update->RemoveKey(origin_str);
+
+  std::move(complete_cb).Run();
+  DVLOG(1) << __func__ << "Done removing CDM preference data";
+}
+
 std::unique_ptr<media::CdmPreferenceData>
 CdmPrefServiceHelper::GetCdmPreferenceData(PrefService* user_prefs,
                                            const url::Origin& cdm_origin) {
diff --git a/chrome/browser/media/cdm_pref_service_helper.h b/chrome/browser/media/cdm_pref_service_helper.h
index e65c7db..c9917e4 100644
--- a/chrome/browser/media/cdm_pref_service_helper.h
+++ b/chrome/browser/media/cdm_pref_service_helper.h
@@ -5,8 +5,11 @@
 #ifndef CHROME_BROWSER_MEDIA_CDM_PREF_SERVICE_HELPER_H_
 #define CHROME_BROWSER_MEDIA_CDM_PREF_SERVICE_HELPER_H_
 
+#include "base/callback.h"
+#include "base/time/time.h"
 #include "media/cdm/cdm_preference_data.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
+#include "url/gurl.h"
 #include "url/origin.h"
 
 class PrefService;
@@ -25,6 +28,12 @@
   ~CdmPrefServiceHelper();
 
   static void RegisterProfilePrefs(PrefRegistrySimple* registry);
+  static void ClearCdmPreferenceData(
+      PrefService* user_prefs,
+      base::Time start,
+      base::Time end,
+      const base::RepeatingCallback<bool(const GURL&)>& filter,
+      base::OnceClosure complete_cb);
 
   // Gets the CDM preference data associated with the current origin. If no
   // preference data exist for the current origin, an entry is created with a
diff --git a/chrome/browser/metrics/chrome_metrics_service_client_unittest.cc b/chrome/browser/metrics/chrome_metrics_service_client_unittest.cc
index c34f1c95..b501572 100644
--- a/chrome/browser/metrics/chrome_metrics_service_client_unittest.cc
+++ b/chrome/browser/metrics/chrome_metrics_service_client_unittest.cc
@@ -54,7 +54,7 @@
     testing::Test::SetUp();
     metrics::MetricsService::RegisterPrefs(prefs_.registry());
     metrics_state_manager_ = metrics::MetricsStateManager::Create(
-        &prefs_, &enabled_state_provider_, std::wstring(),
+        &prefs_, &enabled_state_provider_, std::wstring(), base::FilePath(),
         base::BindRepeating(
             &ChromeMetricsServiceClientTest::FakeStoreClientInfoBackup,
             base::Unretained(this)),
diff --git a/chrome/browser/metrics/chrome_metrics_services_manager_client.cc b/chrome/browser/metrics/chrome_metrics_services_manager_client.cc
index 1e42df6..99743fe 100644
--- a/chrome/browser/metrics/chrome_metrics_services_manager_client.cc
+++ b/chrome/browser/metrics/chrome_metrics_services_manager_client.cc
@@ -11,6 +11,8 @@
 #include "base/check_op.h"
 #include "base/command_line.h"
 #include "base/feature_list.h"
+#include "base/files/file_path.h"
+#include "base/path_service.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/task/post_task.h"
 #include "base/task/thread_pool.h"
@@ -23,6 +25,7 @@
 #include "chrome/browser/metrics/variations/ui_string_overrider_factory.h"
 #include "chrome/browser/net/system_network_context_manager.h"
 #include "chrome/browser/ui/browser_otr_state.h"
+#include "chrome/common/chrome_paths.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/installer/util/google_update_settings.h"
 #include "components/metrics/enabled_state_provider.h"
@@ -262,9 +265,11 @@
 ChromeMetricsServicesManagerClient::GetMetricsStateManager() {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   if (!metrics_state_manager_) {
+    base::FilePath user_data_dir;
+    base::PathService::Get(chrome::DIR_USER_DATA, &user_data_dir);
     metrics_state_manager_ = metrics::MetricsStateManager::Create(
         local_state_, enabled_state_provider_.get(), GetRegistryBackupKey(),
-        base::BindRepeating(&PostStoreMetricsClientInfo),
+        user_data_dir, base::BindRepeating(&PostStoreMetricsClientInfo),
         base::BindRepeating(&GoogleUpdateSettings::LoadMetricsClientInfo));
   }
   return metrics_state_manager_.get();
diff --git a/chrome/browser/metrics/desktop_session_duration/desktop_session_metrics_provider.cc b/chrome/browser/metrics/desktop_session_duration/desktop_session_metrics_provider.cc
index 408e2fa4..e3646d3f 100644
--- a/chrome/browser/metrics/desktop_session_duration/desktop_session_metrics_provider.cc
+++ b/chrome/browser/metrics/desktop_session_duration/desktop_session_metrics_provider.cc
@@ -25,7 +25,7 @@
 }  // namespace
 
 std::unique_ptr<MetricsProvider> CreateDesktopSessionMetricsProvider() {
-  return std::make_unique<MetricsProvider>();
+  return std::make_unique<DesktopSessionMetricsProvider>();
 }
 
 }  // namespace metrics
diff --git a/chrome/browser/metrics/extensions_metrics_provider_unittest.cc b/chrome/browser/metrics/extensions_metrics_provider_unittest.cc
index 7f31e0d..c719d66 100644
--- a/chrome/browser/metrics/extensions_metrics_provider_unittest.cc
+++ b/chrome/browser/metrics/extensions_metrics_provider_unittest.cc
@@ -11,6 +11,7 @@
 #include <string>
 
 #include "base/bind.h"
+#include "base/files/file_path.h"
 #include "base/test/task_environment.h"
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/extensions/extension_service_test_base.h"
@@ -134,7 +135,7 @@
   std::unique_ptr<metrics::MetricsStateManager> metrics_state_manager(
       metrics::MetricsStateManager::Create(
           &local_state, &enabled_state_provider, std::wstring(),
-          base::BindRepeating(&StoreNoClientInfoBackup),
+          base::FilePath(), base::BindRepeating(&StoreNoClientInfoBackup),
           base::BindRepeating(&ReturnNoBackup)));
   TestExtensionsMetricsProvider extension_metrics(metrics_state_manager.get());
   extension_metrics.ProvideSystemProfileMetrics(&system_profile);
diff --git a/chrome/browser/payments/secure_payment_confirmation_browsertest.cc b/chrome/browser/payments/secure_payment_confirmation_browsertest.cc
index 12396527..f7220af 100644
--- a/chrome/browser/payments/secure_payment_confirmation_browsertest.cc
+++ b/chrome/browser/payments/secure_payment_confirmation_browsertest.cc
@@ -11,6 +11,7 @@
 
 #include "base/command_line.h"
 #include "base/files/file_util.h"
+#include "base/json/json_reader.h"
 #include "base/memory/ref_counted.h"
 #include "base/path_service.h"
 #include "base/strings/strcat.h"
@@ -19,6 +20,7 @@
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/threading/thread_restrictions.h"
+#include "base/values.h"
 #include "build/build_config.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/test/payments/payment_request_platform_browsertest_base.h"
@@ -41,6 +43,7 @@
 #include "net/test/embedded_test_server/http_request.h"
 #include "net/test/embedded_test_server/http_response.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/mojom/payments/payment_handler_host.mojom.h"
 
 namespace payments {
@@ -823,14 +826,34 @@
   test_controller()->SetHasAuthenticator(true);
   confirm_payment_ = true;
   // EvalJs waits for JavaScript promise to resolve.
-  EXPECT_EQ(
-      "success",
+  std::string response =
       content::EvalJs(
           GetActiveWebContents(),
           content::JsReplace(
               "postToIframe($1, $2);",
               https_server()->GetURL("c.com", "/iframe_receiver.html").spec(),
-              credentialIdentifier)));
+              credentialIdentifier))
+          .ExtractString();
+
+  ASSERT_EQ(std::string::npos, response.find("Error"));
+  absl::optional<base::Value> value = base::JSONReader::Read(response);
+  ASSERT_TRUE(value.has_value());
+  ASSERT_TRUE(value->is_dict());
+
+  std::string* type = value->FindStringKey("type");
+  ASSERT_NE(nullptr, type) << response;
+  EXPECT_EQ("payment.get", *type);
+
+  // TODO(https://crbug.com/1210488): Origin must be "c.com", i.e.,
+  // the URL retrieved from `http_server()->GetURL("c.com", "/")`.
+  std::string* origin = value->FindStringKey("origin");
+  ASSERT_NE(nullptr, origin) << response;
+  EXPECT_EQ(GURL("https://a.com"), GURL(*origin));
+
+  // TODO(https://crbug.com/1210488): "crossOrigin" must be true.
+  absl::optional<bool> cross_origin = value->FindBoolKey("crossOrigin");
+  ASSERT_TRUE(cross_origin.has_value()) << response;
+  EXPECT_FALSE(cross_origin.value());
 
   int expected_enroll_histogram_value_ =
       (GetParam() == APIVersion::kApiV3) ? 0 : 1;
diff --git a/chrome/browser/performance_manager/decorators/frozen_frame_aggregator_unittest.cc b/chrome/browser/performance_manager/decorators/frozen_frame_aggregator_unittest.cc
index 67be550..0c0a7c6 100644
--- a/chrome/browser/performance_manager/decorators/frozen_frame_aggregator_unittest.cc
+++ b/chrome/browser/performance_manager/decorators/frozen_frame_aggregator_unittest.cc
@@ -84,10 +84,9 @@
     EXPECT_EQ(LifecycleState::kFrozen, page_node_.get()->lifecycle_state());
   }
 
-  TestNodeWrapper<FrameNodeImpl> CreateFrame(FrameNodeImpl* parent_frame_node,
-                                             int frame_tree_node_id) {
+  TestNodeWrapper<FrameNodeImpl> CreateFrame(FrameNodeImpl* parent_frame_node) {
     return CreateFrameNodeAutoId(process_node_.get(), page_node_.get(),
-                                 parent_frame_node, frame_tree_node_id);
+                                 parent_frame_node);
   }
 
   FrozenFrameAggregator* ffa_;
@@ -106,7 +105,7 @@
   ExpectNoProcessData();
 
   // Add a main frame.
-  auto f0 = CreateFrame(nullptr, 0);
+  auto f0 = CreateFrame(nullptr);
   ExpectNoProcessData();
 
   // Make the frame current.
@@ -125,7 +124,7 @@
   ExpectProcessData(1, 1);
 
   // Create a child frame for the first page hosted in the second process.
-  auto f1 = CreateFrameNodeAutoId(proc2.get(), page_node_.get(), f0.get(), 1);
+  auto f1 = CreateFrameNodeAutoId(proc2.get(), page_node_.get(), f0.get());
   ExpectProcessData(1, 1);
 
   // Immediately make it current.
@@ -145,7 +144,7 @@
   ExpectProcessData(1, 0);
 
   // Create a main frame in the second page, but that's in the first process.
-  auto f2 = CreateFrameNodeAutoId(process_node_.get(), page2.get(), nullptr, 2);
+  auto f2 = CreateFrameNodeAutoId(process_node_.get(), page2.get(), nullptr);
   ExpectProcessData(1, 0);
 
   // Freeze the main frame in the second page.
@@ -189,7 +188,7 @@
   ExpectRunning();
 
   // Add a non-current frame.
-  auto f0 = CreateFrame(nullptr, 0);
+  auto f0 = CreateFrame(nullptr);
   ExpectPageData(0, 0);
   ExpectRunning();
 
@@ -209,7 +208,7 @@
   ExpectRunning();
 
   // Add a child frame.
-  auto f1 = CreateFrame(f0.get(), 1);
+  auto f1 = CreateFrame(f0.get());
   ExpectPageData(1, 0);
   ExpectRunning();
 
@@ -235,7 +234,7 @@
   ExpectRunning();
 
   // Create a third frame.
-  auto f1a = CreateFrame(f0.get(), 1);
+  auto f1a = CreateFrame(f0.get());
   ExpectPageData(2, 0);
   ExpectRunning();
 
diff --git a/chrome/browser/performance_manager/observers/isolation_context_metrics_unittest.cc b/chrome/browser/performance_manager/observers/isolation_context_metrics_unittest.cc
index 362491f..2d9d325a 100644
--- a/chrome/browser/performance_manager/observers/isolation_context_metrics_unittest.cc
+++ b/chrome/browser/performance_manager/observers/isolation_context_metrics_unittest.cc
@@ -83,8 +83,8 @@
       int32_t site_instance_id,
       FrameNodeImpl* parent_frame_node = nullptr) {
     return CreateNode<FrameNodeImpl>(
-        process_node, page_node, parent_frame_node, 0 /* frame_tree_node_id */,
-        ++next_render_frame_id_, blink::LocalFrameToken(),
+        process_node, page_node, parent_frame_node, ++next_render_frame_id_,
+        blink::LocalFrameToken(),
         content::BrowsingInstanceId(browsing_instance_id),
         content::SiteInstanceId(site_instance_id));
   }
diff --git a/chrome/browser/performance_manager/policies/policy_features.cc b/chrome/browser/performance_manager/policies/policy_features.cc
index 2b10ece..2a2cb0f0 100644
--- a/chrome/browser/performance_manager/policies/policy_features.cc
+++ b/chrome/browser/performance_manager/policies/policy_features.cc
@@ -72,6 +72,10 @@
 const base::FeatureParam<bool> kTrimArcVmOnCriticalPressure = {
     &kTrimArcVmOnMemoryPressure, "TrimArcVmOnCriticalPressure", false};
 
+const base::FeatureParam<bool> kTrimArcVmOnFirstMemoryPressureAfterArcVmBoot = {
+    &kTrimArcVmOnMemoryPressure, "TrimArcVmOnFirstMemoryPressureAfterArcVmBoot",
+    false};
+
 // Specifies the minimum amount of time a parent frame node must be invisible
 // before considering the process node for working set trim.
 const base::FeatureParam<int> kNodeInvisibileTimeSec = {
@@ -118,6 +122,8 @@
   params.arcvm_inactivity_time = kArcVmInactivityTimeMs.Get();
   params.arcvm_trim_backoff_time = kArcVmTrimBackoffTimeMs.Get();
   params.trim_arcvm_on_critical_pressure = kTrimArcVmOnCriticalPressure.Get();
+  params.trim_arcvm_on_first_memory_pressure_after_arcvm_boot =
+      kTrimArcVmOnFirstMemoryPressureAfterArcVmBoot.Get();
 
   return params;
 }
diff --git a/chrome/browser/performance_manager/policies/policy_features.h b/chrome/browser/performance_manager/policies/policy_features.h
index b86dd73b..1e05197 100644
--- a/chrome/browser/performance_manager/policies/policy_features.h
+++ b/chrome/browser/performance_manager/policies/policy_features.h
@@ -92,6 +92,12 @@
 // regardless of the user's interactions with ARCVM.
 extern const base::FeatureParam<bool> kTrimArcVmOnCriticalPressure;
 
+// If true then we will trim ARCVM's crosvm once on the first moderate (or
+// critical though unlikely) memory pressure after ARCVM boot. The trimming is
+// done regardless of the user's interactions with ARCVM.
+extern const base::FeatureParam<bool>
+    kTrimArcVmOnFirstMemoryPressureAfterArcVmBoot;
+
 struct TrimOnMemoryPressureParams {
   TrimOnMemoryPressureParams();
   TrimOnMemoryPressureParams(const TrimOnMemoryPressureParams&);
@@ -117,6 +123,7 @@
   base::TimeDelta arcvm_inactivity_time;
   base::TimeDelta arcvm_trim_backoff_time;
   bool trim_arcvm_on_critical_pressure = false;
+  bool trim_arcvm_on_first_memory_pressure_after_arcvm_boot = false;
 };
 
 #if BUILDFLAG(USE_TCMALLOC)
diff --git a/chrome/browser/performance_manager/policies/working_set_trimmer_policy_arcvm.cc b/chrome/browser/performance_manager/policies/working_set_trimmer_policy_arcvm.cc
index 41f911a7..0ee5a61e 100644
--- a/chrome/browser/performance_manager/policies/working_set_trimmer_policy_arcvm.cc
+++ b/chrome/browser/performance_manager/policies/working_set_trimmer_policy_arcvm.cc
@@ -12,9 +12,9 @@
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "components/arc/arc_browser_context_keyed_service_factory_base.h"
+#include "components/arc/arc_service_manager.h"
 #include "components/arc/arc_util.h"
 #include "components/exo/wm_helper.h"
-#include "components/session_manager/core/session_manager.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_thread.h"
 
@@ -52,14 +52,9 @@
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   DCHECK(arc::IsArcVmEnabled()) << "This is only for ARCVM builds";
 
-  // Ask SessionManager to notify when the user has signed in. In case the user
-  // has already signed in, call OnUserSessionStarted() now.
-  if (session_manager::SessionManager::Get()->IsSessionStarted())
-    OnUserSessionStarted(/*is_primary_user=*/true);
-  else
-    session_manager::SessionManager::Get()->AddObserver(this);
-
   auto* arc_session_manager = arc::ArcSessionManager::Get();
+  // ArcSessionManager is created very early in
+  // ChromeBrowserMainPartsChromeos::PreMainMessageLoopRun().
   DCHECK(arc_session_manager);
   arc_session_manager->AddObserver(this);
 
@@ -70,6 +65,16 @@
         wm::ActivationChangeObserver::ActivationReason::ACTIVATION_CLIENT,
         helper->GetActiveWindow(), /*lost_active=*/nullptr);
   }
+
+  // If app() is already connected to the AppInstance in the guest, the
+  // OnConnectionReady() function is synchronously called before returning
+  // from AddObserver. See components/arc/session/connection_holder.h for
+  // more details, especially its AddObserver() function.
+  auto* arc_service_manager = arc::ArcServiceManager::Get();
+  // ArcServiceManager and objects owned by the manager are created very early
+  // in ChromeBrowserMainPartsChromeos::PreMainMessageLoopRun() too.
+  DCHECK(arc_service_manager);
+  arc_service_manager->arc_bridge_service()->app()->AddObserver(this);
 }
 
 WorkingSetTrimmerPolicyArcVm::~WorkingSetTrimmerPolicyArcVm() {
@@ -82,38 +87,33 @@
         arc::ArcMetricsService::GetForBrowserContext(context);
     if (metrics_service)
       metrics_service->RemoveUserInteractionObserver(this);
-
-    auto* boot_phase_monitor_bridge =
-        arc::ArcBootPhaseMonitorBridge::GetForBrowserContext(context);
-    if (boot_phase_monitor_bridge)
-      boot_phase_monitor_bridge->RemoveObserver(this);
   }
 
+  auto* arc_service_manager = arc::ArcServiceManager::Get();
+  if (arc_service_manager)
+    arc_service_manager->arc_bridge_service()->app()->RemoveObserver(this);
+
   if (exo::WMHelper::HasInstance())
     exo::WMHelper::GetInstance()->RemoveActivationObserver(this);
 
   auto* arc_session_manager = arc::ArcSessionManager::Get();
   if (arc_session_manager)
     arc_session_manager->RemoveObserver(this);
-
-  auto* session_manager = session_manager::SessionManager::Get();
-  if (session_manager)
-    session_manager->RemoveObserver(this);
 }
 
 bool WorkingSetTrimmerPolicyArcVm::IsEligibleForReclaim(
-    const base::TimeDelta& arcvm_inactivity_time) {
+    const base::TimeDelta& arcvm_inactivity_time,
+    bool trim_once_after_arcvm_boot) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  if (!is_boot_complete_and_connected_)
+    return false;
+  if (!trimmed_at_boot_ && trim_once_after_arcvm_boot) {
+    trimmed_at_boot_ = true;
+    return true;
+  }
   const bool is_inactive =
       (base::TimeTicks::Now() - last_user_interaction_) > arcvm_inactivity_time;
-  return is_boot_complete_ && !is_focused_ && is_inactive;
-}
-
-void WorkingSetTrimmerPolicyArcVm::OnBootCompleted() {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  is_boot_complete_ = true;
-  // Now the user is able to interact with ARCVM. Reset the value.
-  last_user_interaction_ = base::TimeTicks::Now();
+  return !is_focused_ && is_inactive;
 }
 
 void WorkingSetTrimmerPolicyArcVm::OnUserInteraction(
@@ -125,11 +125,25 @@
 void WorkingSetTrimmerPolicyArcVm::OnArcSessionStopped(
     arc::ArcStopReason stop_reason) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  is_boot_complete_ = false;
+  is_boot_complete_and_connected_ = false;
+  trimmed_at_boot_ = false;
 }
+
 void WorkingSetTrimmerPolicyArcVm::OnArcSessionRestarting() {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  is_boot_complete_ = false;
+  is_boot_complete_and_connected_ = false;
+  trimmed_at_boot_ = false;
+}
+
+void WorkingSetTrimmerPolicyArcVm::OnConnectionReady() {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  is_boot_complete_and_connected_ = true;
+  // Now the user is able to interact with ARCVM. Reset the value.
+  last_user_interaction_ = base::TimeTicks::Now();
+  if (!observing_user_interactions_) {
+    StartObservingUserInteractions();
+    observing_user_interactions_ = true;
+  }
 }
 
 void WorkingSetTrimmerPolicyArcVm::OnWindowActivated(
@@ -145,25 +159,21 @@
   }
 }
 
-void WorkingSetTrimmerPolicyArcVm::OnUserSessionStarted(bool is_primary_user) {
+void WorkingSetTrimmerPolicyArcVm::StartObservingUserInteractions() {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  if (!is_primary_user)
-    return;
 
   content::BrowserContext* context =
       context_for_testing_ ? context_for_testing_ : GetContext();
   DCHECK(context);
 
-  // ArcBootPhaseMonitorBridge and ArcMetricsService are created when the
-  // primary user profile is created. In OnUserSessionStarted(), they always
-  // exist.
-  auto* boot_phase_monitor_bridge =
-      arc::ArcBootPhaseMonitorBridge::GetForBrowserContext(context);
-  DCHECK(boot_phase_monitor_bridge);
-  boot_phase_monitor_bridge->AddObserver(this);
-
+  // ArcMetricsService is created in OnPrimaryUserProfilePrepared() in
+  // ArcServiceLauncher which also initializes objects that are needed to start
+  // ARCVM e.g. ArcSessionManager. As long as the function is called after ARCVM
+  // is started, e.g. from OnConnectionReady(), the DCHECK below should never
+  // fail.
   auto* metrics_service = arc::ArcMetricsService::GetForBrowserContext(context);
   DCHECK(metrics_service);
+  DCHECK(!observing_user_interactions_);
   metrics_service->AddUserInteractionObserver(this);
 }
 
diff --git a/chrome/browser/performance_manager/policies/working_set_trimmer_policy_arcvm.h b/chrome/browser/performance_manager/policies/working_set_trimmer_policy_arcvm.h
index 3a537d58..dabce41 100644
--- a/chrome/browser/performance_manager/policies/working_set_trimmer_policy_arcvm.h
+++ b/chrome/browser/performance_manager/policies/working_set_trimmer_policy_arcvm.h
@@ -8,11 +8,12 @@
 #include "base/memory/memory_pressure_listener.h"
 #include "base/no_destructor.h"
 #include "base/time/time.h"
-#include "chrome/browser/ash/arc/boot_phase_monitor/arc_boot_phase_monitor_bridge.h"
 #include "chrome/browser/ash/arc/session/arc_session_manager_observer.h"
 #include "chrome/browser/performance_manager/policies/working_set_trimmer_policy_chromeos.h"
 #include "components/arc/metrics/arc_metrics_service.h"
-#include "components/session_manager/core/session_manager_observer.h"
+#include "components/arc/mojom/app.mojom.h"
+#include "components/arc/session/arc_bridge_service.h"
+#include "components/arc/session/connection_holder.h"
 #include "ui/wm/public/activation_change_observer.h"
 
 namespace aura {
@@ -31,11 +32,10 @@
 // have to be called on the UI thread.
 class WorkingSetTrimmerPolicyArcVm
     : public WorkingSetTrimmerPolicyChromeOS::ArcVmDelegate,
-      public arc::ArcBootPhaseMonitorBridge::Observer,
       public arc::ArcMetricsService::UserInteractionObserver,
       public arc::ArcSessionManagerObserver,
-      public wm::ActivationChangeObserver,
-      public session_manager::SessionManagerObserver {
+      public arc::ConnectionObserver<arc::mojom::AppInstance>,
+      public wm::ActivationChangeObserver {
  public:
   // Gets an instance of WorkingSetTrimmerPolicyArcVm.
   static WorkingSetTrimmerPolicyArcVm* Get();
@@ -52,11 +52,8 @@
   ~WorkingSetTrimmerPolicyArcVm() override;
 
   // WorkingSetTrimmerPolicyChromeOS::ArcVmDelegate overrides.
-  bool IsEligibleForReclaim(
-      const base::TimeDelta& arcvm_inactivity_time) override;
-
-  // ArcBootPhaseMonitorBridge::Observer overrides.
-  void OnBootCompleted() override;
+  bool IsEligibleForReclaim(const base::TimeDelta& arcvm_inactivity_time,
+                            bool trim_once_after_arcvm_boot) override;
 
   // ArcMetricsService::UserInteractionObserver overrides.
   void OnUserInteraction(arc::UserInteractionType type) override;
@@ -65,26 +62,35 @@
   void OnArcSessionStopped(arc::ArcStopReason stop_reason) override;
   void OnArcSessionRestarting() override;
 
+  // arc::ConnectionObserver<arc::mojom::AppInstance> overrides.
+  void OnConnectionReady() override;
+
   // wm::ActivationChangeObserver overrides.
   void OnWindowActivated(wm::ActivationChangeObserver::ActivationReason reason,
                          aura::Window* gained_active,
                          aura::Window* lost_active) override;
 
-  // session_manager::SessionManagerObserver overrides.
-  void OnUserSessionStarted(bool is_primary_user) override;
-
  private:
   friend class base::NoDestructor<WorkingSetTrimmerPolicyArcVm>;
   WorkingSetTrimmerPolicyArcVm();
 
+  void StartObservingUserInteractions();
+
   content::BrowserContext* context_for_testing_ = nullptr;
 
-  // True if ARCVM has already been fully booted.
-  bool is_boot_complete_ = false;
+  // True if ARCVM has already been fully booted and app.mojom connection is
+  // established.
+  bool is_boot_complete_and_connected_ = false;
   // True if ARCVM window is currently focused.
   bool is_focused_ = false;
   // The time of the last user interacted with ARCVM.
   base::TimeTicks last_user_interaction_;
+
+  // True if IsEligibleForReclaim() has already returned true for the single
+  // trim that happens after boot when `trim_once_after_arcvm_boot` is set.
+  bool trimmed_at_boot_ = false;
+  // True if observing the user's interactions with ARCVM via ArcMetricsService.
+  bool observing_user_interactions_ = false;
 };
 
 }  // namespace policies
diff --git a/chrome/browser/performance_manager/policies/working_set_trimmer_policy_arcvm_unittest.cc b/chrome/browser/performance_manager/policies/working_set_trimmer_policy_arcvm_unittest.cc
index 73e05d9f..978280b 100644
--- a/chrome/browser/performance_manager/policies/working_set_trimmer_policy_arcvm_unittest.cc
+++ b/chrome/browser/performance_manager/policies/working_set_trimmer_policy_arcvm_unittest.cc
@@ -94,50 +94,50 @@
 
 // Tests that IsEligibleForReclaim() returns false initially.
 TEST_F(WorkingSetTrimmerPolicyArcVmTest, InitialState) {
-  EXPECT_FALSE(trimmer()->IsEligibleForReclaim(GetInterval()));
+  EXPECT_FALSE(trimmer()->IsEligibleForReclaim(GetInterval(), false));
 }
 
 // Tests that IsEligibleForReclaim() returns false right after boot completion
 // but true after the period.
 TEST_F(WorkingSetTrimmerPolicyArcVmTest, BootComplete) {
-  trimmer()->OnBootCompleted();
-  EXPECT_FALSE(trimmer()->IsEligibleForReclaim(GetInterval()));
+  trimmer()->OnConnectionReady();
+  EXPECT_FALSE(trimmer()->IsEligibleForReclaim(GetInterval(), false));
   FastForwardBy(GetInterval());
   FastForwardBy(base::TimeDelta::FromSeconds(1));
-  EXPECT_TRUE(trimmer()->IsEligibleForReclaim(GetInterval()));
+  EXPECT_TRUE(trimmer()->IsEligibleForReclaim(GetInterval(), false));
 }
 
 // Tests that IsEligibleForReclaim() returns false right after user
 // interaction.
 TEST_F(WorkingSetTrimmerPolicyArcVmTest, UserInteraction) {
-  trimmer()->OnBootCompleted();
+  trimmer()->OnConnectionReady();
   FastForwardBy(GetInterval());
   FastForwardBy(base::TimeDelta::FromSeconds(1));
-  EXPECT_TRUE(trimmer()->IsEligibleForReclaim(GetInterval()));
+  EXPECT_TRUE(trimmer()->IsEligibleForReclaim(GetInterval(), false));
   trimmer()->OnUserInteraction(
       arc::UserInteractionType::APP_STARTED_FROM_LAUNCHER);
-  EXPECT_FALSE(trimmer()->IsEligibleForReclaim(GetInterval()));
+  EXPECT_FALSE(trimmer()->IsEligibleForReclaim(GetInterval(), false));
   FastForwardBy(GetInterval());
   FastForwardBy(base::TimeDelta::FromSeconds(1));
-  EXPECT_TRUE(trimmer()->IsEligibleForReclaim(GetInterval()));
+  EXPECT_TRUE(trimmer()->IsEligibleForReclaim(GetInterval(), false));
 }
 
 // Tests that IsEligibleForReclaim() returns false when ARCVM is no longer
 // running.
 TEST_F(WorkingSetTrimmerPolicyArcVmTest, ArcVmNotRunning) {
-  trimmer()->OnBootCompleted();
+  trimmer()->OnConnectionReady();
   FastForwardBy(GetInterval());
   FastForwardBy(base::TimeDelta::FromSeconds(1));
-  EXPECT_TRUE(trimmer()->IsEligibleForReclaim(GetInterval()));
+  EXPECT_TRUE(trimmer()->IsEligibleForReclaim(GetInterval(), false));
   trimmer()->OnArcSessionRestarting();
-  EXPECT_FALSE(trimmer()->IsEligibleForReclaim(GetInterval()));
+  EXPECT_FALSE(trimmer()->IsEligibleForReclaim(GetInterval(), false));
 
-  trimmer()->OnBootCompleted();
+  trimmer()->OnConnectionReady();
   FastForwardBy(GetInterval());
   FastForwardBy(base::TimeDelta::FromSeconds(1));
-  EXPECT_TRUE(trimmer()->IsEligibleForReclaim(GetInterval()));
+  EXPECT_TRUE(trimmer()->IsEligibleForReclaim(GetInterval(), false));
   trimmer()->OnArcSessionStopped(arc::ArcStopReason::CRASH);
-  EXPECT_FALSE(trimmer()->IsEligibleForReclaim(GetInterval()));
+  EXPECT_FALSE(trimmer()->IsEligibleForReclaim(GetInterval(), false));
 }
 
 // Tests that IsEligibleForReclaim() returns false when ARCVM is focused.
@@ -158,40 +158,66 @@
       chrome_window, nullptr);
 
   // After boot, ARCVM becomes eligible to reclaim.
-  trimmer()->OnBootCompleted();
+  trimmer()->OnConnectionReady();
   FastForwardBy(GetInterval());
   FastForwardBy(base::TimeDelta::FromSeconds(1));
-  EXPECT_TRUE(trimmer()->IsEligibleForReclaim(GetInterval()));
+  EXPECT_TRUE(trimmer()->IsEligibleForReclaim(GetInterval(), false));
 
   // ARCVM window is focused. ARCVM is ineligible to reclaim now.
   trimmer()->OnWindowActivated(
       wm::ActivationChangeObserver::ActivationReason::INPUT_EVENT, arc_window,
       chrome_window);
-  EXPECT_FALSE(trimmer()->IsEligibleForReclaim(GetInterval()));
+  EXPECT_FALSE(trimmer()->IsEligibleForReclaim(GetInterval(), false));
   FastForwardBy(GetInterval());
   FastForwardBy(base::TimeDelta::FromSeconds(1));
-  EXPECT_FALSE(trimmer()->IsEligibleForReclaim(GetInterval()));
+  EXPECT_FALSE(trimmer()->IsEligibleForReclaim(GetInterval(), false));
 
   // ARCVM window is unfocused. ARCVM becomes eligible to reclaim after the
   // period.
   trimmer()->OnWindowActivated(
       wm::ActivationChangeObserver::ActivationReason::INPUT_EVENT,
       chrome_window, arc_window);
-  EXPECT_FALSE(trimmer()->IsEligibleForReclaim(GetInterval()));
+  EXPECT_FALSE(trimmer()->IsEligibleForReclaim(GetInterval(), false));
   FastForwardBy(GetInterval());
   FastForwardBy(base::TimeDelta::FromSeconds(1));
-  EXPECT_TRUE(trimmer()->IsEligibleForReclaim(GetInterval()));
+  EXPECT_TRUE(trimmer()->IsEligibleForReclaim(GetInterval(), false));
 }
 
-// Tests that OnUserSessionStarted() does not crash. This is mainly for better
-// test coverage.
-TEST_F(WorkingSetTrimmerPolicyArcVmTest, OnUserSessionStarted) {
-  trimmer()->OnUserSessionStarted(/*is_primary_user=*/true);
+// Tests that IsEligibleForReclaim(.., true) returns true right after boot
+// completion.
+TEST_F(WorkingSetTrimmerPolicyArcVmTest, TrimOnBootComplete) {
+  EXPECT_FALSE(trimmer()->IsEligibleForReclaim(GetInterval(), true));
+  trimmer()->OnConnectionReady();
+  // IsEligibleForReclaim() returns true after boot completion (but only once.)
+  EXPECT_TRUE(trimmer()->IsEligibleForReclaim(GetInterval(), true));
+  EXPECT_FALSE(trimmer()->IsEligibleForReclaim(GetInterval(), true));
+
+  FastForwardBy(GetInterval());
+  FastForwardBy(base::TimeDelta::FromSeconds(1));
+  // After the interval, the function returns true again.
+  EXPECT_TRUE(trimmer()->IsEligibleForReclaim(GetInterval(), true));
 }
 
-// Tests the same but with is_primary_user=false.
-TEST_F(WorkingSetTrimmerPolicyArcVmTest, OnUserSessionStarted_NonPrimary) {
-  trimmer()->OnUserSessionStarted(/*is_primary_user=*/false);
+// Tests that IsEligibleForReclaim(.., true) returns true for each ARCVM boot.
+TEST_F(WorkingSetTrimmerPolicyArcVmTest, TrimOnBootCompleteAfterArcVmRestart) {
+  EXPECT_FALSE(trimmer()->IsEligibleForReclaim(GetInterval(), true));
+  trimmer()->OnConnectionReady();
+  EXPECT_TRUE(trimmer()->IsEligibleForReclaim(GetInterval(), true));
+  EXPECT_FALSE(trimmer()->IsEligibleForReclaim(GetInterval(), true));
+
+  trimmer()->OnArcSessionRestarting();
+  EXPECT_FALSE(trimmer()->IsEligibleForReclaim(GetInterval(), true));
+  trimmer()->OnConnectionReady();
+  // After ARCVM restart, the functions returns true again.
+  EXPECT_TRUE(trimmer()->IsEligibleForReclaim(GetInterval(), true));
+  EXPECT_FALSE(trimmer()->IsEligibleForReclaim(GetInterval(), true));
+
+  // Tests the same with OnArcSessionStopped().
+  trimmer()->OnArcSessionStopped(arc::ArcStopReason::CRASH);
+  EXPECT_FALSE(trimmer()->IsEligibleForReclaim(GetInterval(), true));
+  trimmer()->OnConnectionReady();
+  EXPECT_TRUE(trimmer()->IsEligibleForReclaim(GetInterval(), true));
+  EXPECT_FALSE(trimmer()->IsEligibleForReclaim(GetInterval(), true));
 }
 
 }  // namespace
diff --git a/chrome/browser/performance_manager/policies/working_set_trimmer_policy_chromeos.cc b/chrome/browser/performance_manager/policies/working_set_trimmer_policy_chromeos.cc
index 855bfb6..6823733 100644
--- a/chrome/browser/performance_manager/policies/working_set_trimmer_policy_chromeos.cc
+++ b/chrome/browser/performance_manager/policies/working_set_trimmer_policy_chromeos.cc
@@ -339,7 +339,9 @@
       (level == base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL);
   const bool need_reclaim =
       force_reclaim ||
-      arcvm_delegate->IsEligibleForReclaim(params.arcvm_inactivity_time);
+      arcvm_delegate->IsEligibleForReclaim(
+          params.arcvm_inactivity_time,
+          params.trim_arcvm_on_first_memory_pressure_after_arcvm_boot);
   PerformanceManager::CallOnGraph(
       FROM_HERE,
       base::BindOnce(&WorkingSetTrimmerPolicyChromeOS::OnTrimArcVmProcesses,
diff --git a/chrome/browser/performance_manager/policies/working_set_trimmer_policy_chromeos.h b/chrome/browser/performance_manager/policies/working_set_trimmer_policy_chromeos.h
index 7ab1e79..5fb3912 100644
--- a/chrome/browser/performance_manager/policies/working_set_trimmer_policy_chromeos.h
+++ b/chrome/browser/performance_manager/policies/working_set_trimmer_policy_chromeos.h
@@ -44,8 +44,11 @@
     // Returns true when ARCVM has been idle for more than
     // |arcvm_inactivity_time| and therefore is safe to reclaim its memory.
     // The function is called only on the UI thread.
+    // If |trim_once_after_arcvm_boot| is true, the function returns true when
+    // the function is called for the first time after ARCVM boot.
     virtual bool IsEligibleForReclaim(
-        const base::TimeDelta& arcvm_inactivity_time) = 0;
+        const base::TimeDelta& arcvm_inactivity_time,
+        bool trim_once_after_arcvm_boot) = 0;
   };
 
   ~WorkingSetTrimmerPolicyChromeOS() override;
diff --git a/chrome/browser/performance_manager/policies/working_set_trimmer_policy_chromeos_unittest.cc b/chrome/browser/performance_manager/policies/working_set_trimmer_policy_chromeos_unittest.cc
index 9752da0..c1b1661b 100644
--- a/chrome/browser/performance_manager/policies/working_set_trimmer_policy_chromeos_unittest.cc
+++ b/chrome/browser/performance_manager/policies/working_set_trimmer_policy_chromeos_unittest.cc
@@ -54,8 +54,8 @@
   ScopedTestArcVmDelegate& operator=(const ScopedTestArcVmDelegate&) = delete;
 
   // WorkingSetTrimmerPolicyChromeOS::ArcVmDelegate overrides:
-  bool IsEligibleForReclaim(
-      const base::TimeDelta& arcvm_inactivity_time) override {
+  bool IsEligibleForReclaim(const base::TimeDelta& arcvm_inactivity_time,
+                            bool trim_once_after_arcvm_boot) override {
     return eligible_;
   }
 
@@ -85,6 +85,7 @@
     params().arcvm_inactivity_time = base::TimeDelta::Min();
     params().arcvm_trim_backoff_time = base::TimeDelta::Min();
     params().trim_arcvm_on_critical_pressure = false;
+    params().trim_arcvm_on_first_memory_pressure_after_arcvm_boot = false;
 
     // Setup some default invocations.
     ON_CALL(*this, OnMemoryPressure(_))
diff --git a/chrome/browser/policy/cbcm_invalidations_initializer.cc b/chrome/browser/policy/cbcm_invalidations_initializer.cc
index 9cf25c7..504af97a 100644
--- a/chrome/browser/policy/cbcm_invalidations_initializer.cc
+++ b/chrome/browser/policy/cbcm_invalidations_initializer.cc
@@ -139,8 +139,17 @@
     return;
   }
 
-  // No need to get a refresh token if there is one present already.
-  if (!DeviceOAuth2TokenServiceFactory::Get()->RefreshTokenIsAvailable()) {
+  // If there's no invalidations service active yet, now's the time to start it.
+  // It will be notified when the service account for is ready to be used.
+  if (!delegate_->IsInvalidationsServiceStarted())
+    delegate_->StartInvalidations();
+
+  // If there's no refresh token when a policy has a service account, or the
+  // service account in the policy doesn't match the one in the token service,
+  // the service account has to be initialized to the one in the policy.
+  if (!DeviceOAuth2TokenServiceFactory::Get()->RefreshTokenIsAvailable() ||
+      DeviceOAuth2TokenServiceFactory::Get()->GetRobotAccountId() !=
+          CoreAccountId::FromEmail(account_email)) {
     // If this feature is enabled, we need to ensure the device service
     // account is initialized and fetch auth codes to exchange for a refresh
     // token. Creating this object starts that process and the callback will
@@ -154,11 +163,6 @@
                 ? delegate_->GetURLLoaderFactory()
                 : g_browser_process->system_network_context_manager()
                       ->GetSharedURLLoaderFactory());
-  } else if (!delegate_->IsInvalidationsServiceStarted()) {
-    // There's already a refresh token available but invalidations aren't
-    // running yet which means this is browser startup and the refresh token was
-    // retrieved from local storage. It's OK to start invalidations now.
-    delegate_->StartInvalidations();
   }
 }
 
@@ -166,8 +170,11 @@
     const std::string& account_email,
     bool success) {
   account_initializer_helper_.reset();
-  if (success)
-    delegate_->StartInvalidations();
+  if (!success) {
+    DVLOG(1)
+        << "There was an error initializing the service account with email: "
+        << account_email;
+  }
 }
 
 }  // namespace policy
diff --git a/chrome/browser/policy/cbcm_invalidations_initializer_unittest.cc b/chrome/browser/policy/cbcm_invalidations_initializer_unittest.cc
index d5e5496..7a199cdf 100644
--- a/chrome/browser/policy/cbcm_invalidations_initializer_unittest.cc
+++ b/chrome/browser/policy/cbcm_invalidations_initializer_unittest.cc
@@ -25,7 +25,10 @@
 
 namespace {
 
-static const char kRefreshToken[] = "refresh_token";
+static const char kFirstRefreshToken[] = "first_refresh_token";
+static const char kSecondRefreshToken[] = "second_refresh_token";
+static const char kFirstAccessToken[] = "first_access_token";
+static const char kSecondAccessToken[] = "second_access_token";
 static const char kServiceAccountEmail[] = "service_account@example.com";
 static const char kOtherServiceAccountEmail[] =
     "other_service_account@example.com";
@@ -77,14 +80,19 @@
 
   FakeCloudPolicyClient* policy_client() { return &mock_policy_client_; }
 
+  TestingPrefServiceSimple* testing_local_state() {
+    return &testing_local_state_;
+  }
+
   network::TestURLLoaderFactory* test_url_loader_factory() {
     return &test_url_loader_factory_;
   }
 
-  std::string MakeTokensFromAuthCodesResponse() {
+  std::string MakeTokensFromAuthCodesResponse(const std::string& refresh_token,
+                                              const std::string& access_token) {
     base::DictionaryValue dict;
-    dict.SetString("access_token", "access_token");
-    dict.SetString("refresh_token", kRefreshToken);
+    dict.SetString("access_token", access_token);
+    dict.SetString("refresh_token", refresh_token);
     dict.SetInteger("expires_in", 9999);
 
     std::string json;
@@ -134,7 +142,7 @@
   DeviceOAuth2TokenServiceFactory::Get()->SetServiceAccountEmail(
       kServiceAccountEmail);
   DeviceOAuth2TokenServiceFactory::Get()->SetAndSaveRefreshToken(
-      kRefreshToken,
+      kFirstRefreshToken,
       base::BindRepeating(&CBCMInvalidationsInitializerTest::
                               RefreshTokenSavedCallbackExpectSuccess,
                           base::Unretained(this)));
@@ -150,7 +158,8 @@
   EXPECT_TRUE(IsInvalidationsServiceStarted());
 }
 
-TEST_F(CBCMInvalidationsInitializerTest, InvalidationsStartAfterTokenFetched) {
+TEST_F(CBCMInvalidationsInitializerTest,
+       InvalidationsStartIfRefreshTokenAbsent) {
   CBCMInvalidationsInitializer initializer(this);
 
   EXPECT_FALSE(IsInvalidationsServiceStarted());
@@ -160,11 +169,11 @@
   EXPECT_EQ(GaiaUrls::GetInstance()->oauth2_token_url().spec(),
             test_url_loader_factory()->GetPendingRequest(0)->request.url);
 
-  EXPECT_FALSE(IsInvalidationsServiceStarted());
+  EXPECT_TRUE(IsInvalidationsServiceStarted());
 
   EXPECT_TRUE(test_url_loader_factory()->SimulateResponseForPendingRequest(
       GaiaUrls::GetInstance()->oauth2_token_url().spec(),
-      MakeTokensFromAuthCodesResponse()));
+      MakeTokensFromAuthCodesResponse(kFirstRefreshToken, kFirstAccessToken)));
 
   EXPECT_TRUE(IsInvalidationsServiceStarted());
 }
@@ -179,7 +188,7 @@
 
   EXPECT_TRUE(test_url_loader_factory()->SimulateResponseForPendingRequest(
       GaiaUrls::GetInstance()->oauth2_token_url().spec(),
-      MakeTokensFromAuthCodesResponse()));
+      MakeTokensFromAuthCodesResponse(kFirstRefreshToken, kFirstAccessToken)));
 
   EXPECT_TRUE(IsInvalidationsServiceStarted());
   EXPECT_EQ(0, test_url_loader_factory()->NumPending());
@@ -193,32 +202,99 @@
 }
 
 TEST_F(CBCMInvalidationsInitializerTest,
-       InvalidationsDontStartTwiceWhenTokenFetchRaces) {
+       CanHandleServiceAccountChangedAfterFetchingInSameSession) {
   CBCMInvalidationsInitializer initializer(this);
 
   EXPECT_FALSE(IsInvalidationsServiceStarted());
 
+  // Simulate that a policy sets a service account and triggers a fetch.
   initializer.OnServiceAccountSet(policy_client(), kServiceAccountEmail);
+  EXPECT_TRUE(IsInvalidationsServiceStarted());
   EXPECT_EQ(1, test_url_loader_factory()->NumPending());
   EXPECT_EQ(GaiaUrls::GetInstance()->oauth2_token_url().spec(),
             test_url_loader_factory()->GetPendingRequest(0)->request.url);
-
-  EXPECT_FALSE(IsInvalidationsServiceStarted());
-
-  // Trying to set the service account again when a request is already pending
-  // cancels the old request and starts a new one
-  initializer.OnServiceAccountSet(policy_client(), kOtherServiceAccountEmail);
-  EXPECT_EQ(1, test_url_loader_factory()->NumPending());
-
-  EXPECT_FALSE(IsInvalidationsServiceStarted());
-
   EXPECT_TRUE(test_url_loader_factory()->SimulateResponseForPendingRequest(
       GaiaUrls::GetInstance()->oauth2_token_url().spec(),
-      MakeTokensFromAuthCodesResponse()));
+      MakeTokensFromAuthCodesResponse(kFirstRefreshToken, kFirstAccessToken)));
+
+  EXPECT_EQ(0, test_url_loader_factory()->NumPending());
+  EXPECT_TRUE(
+      DeviceOAuth2TokenServiceFactory::Get()->RefreshTokenIsAvailable());
+  EXPECT_EQ(CoreAccountId::FromEmail(kServiceAccountEmail),
+            DeviceOAuth2TokenServiceFactory::Get()->GetRobotAccountId());
+  std::string first_refresh_token =
+      testing_local_state()->GetString(kCBCMServiceAccountRefreshToken);
+
+  // Simulate that a policy comes in with a different service account. This
+  // should trigger a re-initialization of the service account.
+  initializer.OnServiceAccountSet(policy_client(), kOtherServiceAccountEmail);
+  EXPECT_EQ(1, test_url_loader_factory()->NumPending());
+  EXPECT_TRUE(IsInvalidationsServiceStarted());
+  EXPECT_TRUE(test_url_loader_factory()->SimulateResponseForPendingRequest(
+      GaiaUrls::GetInstance()->oauth2_token_url().spec(),
+      MakeTokensFromAuthCodesResponse(kSecondRefreshToken,
+                                      kSecondAccessToken)));
 
   EXPECT_EQ(1, num_invalidations_started());
+  EXPECT_TRUE(
+      DeviceOAuth2TokenServiceFactory::Get()->RefreshTokenIsAvailable());
+  // Now a different refresh token and email should be present. The token
+  // themselves aren't validated because they're encrypted. Verifying that it
+  // changed is sufficient.
   EXPECT_EQ(CoreAccountId::FromEmail(kOtherServiceAccountEmail),
             DeviceOAuth2TokenServiceFactory::Get()->GetRobotAccountId());
+  EXPECT_NE(first_refresh_token,
+            testing_local_state()->GetString(kCBCMServiceAccountRefreshToken));
+}
+
+TEST_F(CBCMInvalidationsInitializerTest,
+       CanHandleServiceAccountChangedWhenAccountPresentOnStartup) {
+  CBCMInvalidationsInitializer initializer(this);
+
+  // Set up the token service as if there was already a service account set up
+  // on start up.
+  DeviceOAuth2TokenServiceFactory::Get()->SetServiceAccountEmail(
+      kServiceAccountEmail);
+  DeviceOAuth2TokenServiceFactory::Get()->SetAndSaveRefreshToken(
+      kFirstRefreshToken,
+      base::BindRepeating(&CBCMInvalidationsInitializerTest::
+                              RefreshTokenSavedCallbackExpectSuccess,
+                          base::Unretained(this)));
+
+  EXPECT_EQ(1, num_refresh_tokens_saved());
+  EXPECT_TRUE(
+      DeviceOAuth2TokenServiceFactory::Get()->RefreshTokenIsAvailable());
+  std::string first_refresh_token =
+      testing_local_state()->GetString(kCBCMServiceAccountRefreshToken);
+
+  EXPECT_FALSE(IsInvalidationsServiceStarted());
+  // On first policy store load, this will be called and invalidations started.
+  initializer.OnServiceAccountSet(policy_client(), kServiceAccountEmail);
+  EXPECT_EQ(0, test_url_loader_factory()->NumPending());
+  EXPECT_TRUE(IsInvalidationsServiceStarted());
+  // The same refresh token should be present in local state.
+  EXPECT_EQ(first_refresh_token,
+            testing_local_state()->GetString(kCBCMServiceAccountRefreshToken));
+
+  // Simulate that a new policy is fetched with a different service account.
+  // This should result in a gaia call for the service account initialization.
+  initializer.OnServiceAccountSet(policy_client(), kOtherServiceAccountEmail);
+  EXPECT_EQ(1, test_url_loader_factory()->NumPending());
+  EXPECT_TRUE(test_url_loader_factory()->SimulateResponseForPendingRequest(
+      GaiaUrls::GetInstance()->oauth2_token_url().spec(),
+      MakeTokensFromAuthCodesResponse(kSecondRefreshToken,
+                                      kSecondAccessToken)));
+
+  EXPECT_TRUE(IsInvalidationsServiceStarted());
+  EXPECT_TRUE(
+      DeviceOAuth2TokenServiceFactory::Get()->RefreshTokenIsAvailable());
+  // Now a different refresh token and email should be present. The token
+  // themselves aren't validated because they're encrypted. Verifying that it
+  // changed is sufficient.
+  EXPECT_EQ(CoreAccountId::FromEmail(kOtherServiceAccountEmail),
+            DeviceOAuth2TokenServiceFactory::Get()->GetRobotAccountId());
+  EXPECT_NE(first_refresh_token,
+            testing_local_state()->GetString(kCBCMServiceAccountRefreshToken));
 }
 
 }  // namespace policy
diff --git a/chrome/browser/policy/configuration_policy_handler_list_factory.cc b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
index 9795a5d..fc7a52e 100644
--- a/chrome/browser/policy/configuration_policy_handler_list_factory.cc
+++ b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
@@ -1279,6 +1279,18 @@
     base::Value::Type::BOOLEAN },
 #endif  // defined(OS_WIN) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
 
+#if defined(OS_WIN) || defined(OS_MAC)
+  { key::kPrintPdfAsImageAvailability,
+    prefs::kPrintPdfAsImageAvailability,
+    base::Value::Type::BOOLEAN },
+#endif  // defined(OS_WIN) || defined(OS_MAC)
+
+#if BUILDFLAG(ENABLE_PRINT_PREVIEW)
+    { key::kPrintRasterizePdfDpi,
+      prefs::kPrintRasterizePdfDpi,
+      base::Value::Type::INTEGER },
+#endif  // BUILDFLAGS(ENABLE_PRINT_PREVIEW)
+
 #if !defined(OS_ANDROID) && !defined(OS_CHROMEOS)
   { key::kNativeMessagingUserLevelHosts,
     extensions::pref_names::kNativeMessagingUserLevelHosts,
@@ -1731,11 +1743,6 @@
   handlers->AddHandler(std::make_unique<extensions::ExtensionListPolicyHandler>(
       key::kAttestationExtensionAllowlist,
       prefs::kAttestationExtensionAllowlist, false));
-#if defined(USE_CUPS)
-  handlers->AddHandler(std::make_unique<extensions::ExtensionListPolicyHandler>(
-      key::kPrintingAPIExtensionsAllowlist,
-      prefs::kPrintingAPIExtensionsAllowlist, /*allow_wildcards=*/false));
-#endif  // defined(USE_CUPS)
 #else  // defined(OS_CHROMEOS)
   std::vector<std::unique_ptr<ConfigurationPolicyHandler>>
       signin_legacy_policies;
@@ -2002,6 +2009,11 @@
       SimpleSchemaValidatingPolicyHandler::RECOMMENDED_PROHIBITED,
       SimpleSchemaValidatingPolicyHandler::MANDATORY_ALLOWED));
   handlers->AddHandler(std::make_unique<LacrosAvailabilityPolicyHandler>());
+#if defined(USE_CUPS)
+  handlers->AddHandler(std::make_unique<extensions::ExtensionListPolicyHandler>(
+      key::kPrintingAPIExtensionsAllowlist,
+      prefs::kPrintingAPIExtensionsAllowlist, /*allow_wildcards=*/false));
+#endif  // defined(USE_CUPS)
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 // On most platforms, there is a legacy policy
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index 8fadd3a..bd7243805 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -259,10 +259,7 @@
 
 #if defined(OS_CHROMEOS)
 #include "chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api.h"
-#if defined(USE_CUPS)
-#include "chrome/browser/extensions/api/printing/printing_api_handler.h"
-#endif  // defined(USE_CUPS)
-#endif  // defined(OS_CHROMEOS)
+#endif
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "ash/components/audio/audio_devices_pref_handler_impl.h"
@@ -378,6 +375,10 @@
 #include "components/quirks/quirks_manager.h"
 #include "extensions/browser/api/lock_screen_data/lock_screen_item_storage.h"
 
+#if defined(USE_CUPS)
+#include "chrome/browser/extensions/api/printing/printing_api_handler.h"
+#endif
+
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 #if defined(OS_MAC)
@@ -1244,9 +1245,6 @@
 
 #if defined(OS_CHROMEOS)
   extensions::platform_keys::RegisterProfilePrefs(registry);
-#if defined(USE_CUPS)
-  extensions::PrintingAPIHandler::RegisterProfilePrefs(registry);
-#endif  // defined(USE_CUPS)
 #endif  // defined(OS_CHROMEOS)
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
@@ -1301,6 +1299,9 @@
   ash::UserImageSyncObserver::RegisterProfilePrefs(registry);
   crostini::prefs::RegisterProfilePrefs(registry);
   ash::attestation::TpmChallengeKey::RegisterProfilePrefs(registry);
+#if defined(USE_CUPS)
+  extensions::PrintingAPIHandler::RegisterProfilePrefs(registry);
+#endif
   flags_ui::PrefServiceFlagsStorage::RegisterProfilePrefs(registry);
   guest_os::prefs::RegisterProfilePrefs(registry);
   lock_screen_apps::StateController::RegisterProfilePrefs(registry);
diff --git a/chrome/browser/printing/print_view_manager_base.cc b/chrome/browser/printing/print_view_manager_base.cc
index 34e26cab..e4cacbf 100644
--- a/chrome/browser/printing/print_view_manager_base.cc
+++ b/chrome/browser/printing/print_view_manager_base.cc
@@ -61,6 +61,7 @@
 #if BUILDFLAG(ENABLE_PRINT_PREVIEW)
 #include "chrome/browser/printing/print_error_dialog.h"
 #include "chrome/browser/printing/print_view_manager.h"
+#include "components/prefs/pref_service.h"
 #endif
 
 #if defined(OS_WIN) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
@@ -673,6 +674,16 @@
     return;
   }
 
+  content::BrowserContext* context =
+      web_contents() ? web_contents()->GetBrowserContext() : nullptr;
+  PrefService* prefs =
+      context ? Profile::FromBrowserContext(context)->GetPrefs() : nullptr;
+  if (prefs && prefs->HasPrefPath(prefs::kPrintRasterizePdfDpi)) {
+    int value = prefs->GetInteger(prefs::kPrintRasterizePdfDpi);
+    if (value > 0)
+      job_settings.SetIntKey(kSettingRasterizePdfDpi, value);
+  }
+
   content::RenderFrameHost* render_frame_host = GetCurrentTargetFrame();
   auto callback_wrapper =
       base::BindOnce(&PrintViewManagerBase::UpdatePrintSettingsReply,
diff --git a/chrome/browser/privacy_budget/privacy_budget_browsertest.cc b/chrome/browser/privacy_budget/privacy_budget_browsertest.cc
index 9022eb19..1ad854f 100644
--- a/chrome/browser/privacy_budget/privacy_budget_browsertest.cc
+++ b/chrome/browser/privacy_budget/privacy_budget_browsertest.cc
@@ -273,8 +273,8 @@
               }));
 }
 
-// TODO(crbug.com/1238940): Test is flaky on Win.
-#if defined(OS_WIN)
+// TODO(crbug.com/1238940, crbug.com/1238859): Test is flaky on Win and Android.
+#if defined(OS_WIN) || defined(OS_ANDROID)
 #define MAYBE_CanvasToBlobDifferentDocument \
   DISABLED_CanvasToBlobDifferentDocument
 #else
diff --git a/chrome/browser/profiles/profile_impl.cc b/chrome/browser/profiles/profile_impl.cc
index 886e388..677206f 100644
--- a/chrome/browser/profiles/profile_impl.cc
+++ b/chrome/browser/profiles/profile_impl.cc
@@ -422,9 +422,15 @@
   registry->RegisterBooleanPref(prefs::kPrintPreviewDisabled, false);
   registry->RegisterStringPref(
       prefs::kPrintPreviewDefaultDestinationSelectionRules, std::string());
+#if defined(OS_WIN) || defined(OS_MAC)
+  registry->RegisterBooleanPref(prefs::kPrintPdfAsImageAvailability, false);
+#endif
 #if defined(OS_WIN) && BUILDFLAG(ENABLE_PRINTING)
   registry->RegisterIntegerPref(prefs::kPrintRasterizationMode, 0);
 #endif
+#if BUILDFLAG(ENABLE_PRINT_PREVIEW)
+  registry->RegisterIntegerPref(prefs::kPrintRasterizePdfDpi, 0);
+#endif
 
   registry->RegisterBooleanPref(prefs::kForceEphemeralProfiles, false);
   registry->RegisterBooleanPref(prefs::kEnableMediaRouter, true);
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_keystroke_selection_test.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_keystroke_selection_test.js
index d53e662..dabffa7 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_keystroke_selection_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_keystroke_selection_test.js
@@ -25,6 +25,8 @@
       await importModule(
           'SelectToSpeakConstants',
           '/select_to_speak/select_to_speak_constants.js');
+      selectToSpeak.prefsManager_.enhancedVoicesDialogShown_ = true;
+
       runTest();
     })();
   }
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_mouse_selection_test.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_mouse_selection_test.js
index d7f9b151..10c9c93d 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_mouse_selection_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_mouse_selection_test.js
@@ -31,6 +31,8 @@
       await importModule(
           'SelectToSpeakConstants',
           '/select_to_speak/select_to_speak_constants.js');
+      selectToSpeak.prefsManager_.enhancedVoicesDialogShown_ = true;
+
       runTest();
     })();
   }
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_navigation_control_test.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_navigation_control_test.js
index 46f3eaed..4d2f4b1 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_navigation_control_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_navigation_control_test.js
@@ -36,6 +36,8 @@
       await importModule(
           'SelectToSpeakConstants',
           '/select_to_speak/select_to_speak_constants.js');
+      selectToSpeak.prefsManager_.enhancedVoicesDialogShown_ = true;
+
       runTest();
     })();
   }
diff --git a/chrome/browser/resources/chromeos/audio/BUILD.gn b/chrome/browser/resources/chromeos/audio/BUILD.gn
index 9e55b4e..492a6ba 100644
--- a/chrome/browser/resources/chromeos/audio/BUILD.gn
+++ b/chrome/browser/resources/chromeos/audio/BUILD.gn
@@ -13,6 +13,7 @@
     "audio_player.ts",
     "device_page.ts",
     "device_table.ts",
+    "feedback_page.ts",
     "input_page.ts",
     "output_page.ts",
     "page.ts",
@@ -42,6 +43,7 @@
     "audio.mojom-webui.js",
     "device_table.ts",
     "device_page.ts",
+    "feedback_page.ts",
     "input_page.ts",
     "output_page.ts",
     "page.ts",
diff --git a/chrome/browser/resources/chromeos/audio/audio.css b/chrome/browser/resources/chromeos/audio/audio.css
index 9f349a7..1a757a76 100644
--- a/chrome/browser/resources/chromeos/audio/audio.css
+++ b/chrome/browser/resources/chromeos/audio/audio.css
@@ -46,6 +46,62 @@
   font-weight: normal;
 }
 
+/* Device Page */
+#devices {
+  margin: auto;
+  width: 70%;
+}
+#warning-banner {
+  background: rgb(255,255,102);
+  position: relative;
+}
+
+#warning-msg {
+  padding: 10px;
+}
+
+.feedback-btn {
+  background: none;
+  border: none;
+  color: blue;
+  font-size: 120%;
+  font-weight: bold;
+  padding: 0;
+  position: absolute;
+  text-decoration: underline;
+}
+
+.banner-feedback {
+  right: 645px;
+  top: 28px;
+}
+
+#test-buttons {
+  margin: auto;
+  position: relative;
+}
+
+#device-not-present {
+  position: absolute;
+}
+
+#input-btn {
+  font-weight: bold;
+  position: absolute;
+  top: 40px;
+}
+
+#output-btn {
+  font-weight: bold;
+  position: absolute;
+  top: 70px;
+}
+
+#no-device-feedback {
+  left: 300px;
+  top: 15px;
+}
+
 /* Output Page */
 #output-explainer {
   margin: auto;
@@ -171,3 +227,44 @@
   border: none;
   font-size: 120%;
 }
+
+/*Feedback Page*/
+#feedback {
+  margin: auto;
+  width: 50%;
+}
+
+#feedback-explainer {
+  margin-top: 30px;
+}
+
+.instruction {
+  margin-top: -15px;
+  padding-inline-start: 17px;
+}
+
+.feedback-list {
+  margin-bottom: 20px;
+}
+
+.feedback-step {
+  margin-bottom: 30px;
+}
+
+#copy-btn {
+  position: absolute;
+}
+
+#input-replay {
+  margin-top: 10px;
+}
+
+#audio-info {
+  margin-inline-start: 55px;
+}
+
+#download-btn {
+  bottom: 20px;
+  position: relative;
+  right: 5px;
+}
diff --git a/chrome/browser/resources/chromeos/audio/audio.html b/chrome/browser/resources/chromeos/audio/audio.html
index ed1cd6f..7dc2778 100644
--- a/chrome/browser/resources/chromeos/audio/audio.html
+++ b/chrome/browser/resources/chromeos/audio/audio.html
@@ -8,19 +8,37 @@
 </head>
 <body>
   <section id="devices" hidden>
+    <div id = "device-table-explainer">
+      <h>Device Table</h>
+      <p>Please check if your intended device is displayed on the table.
+        If your device is not active, select the intended device from the
+        system tray. Then, select "Test Input Device" or "Test Output Device"
+        depending on which kind of device you are having issue with.
+      </p>
+    </div>
+    <div id="warning-banner" hidden>
+      <p id="warning-msg"></p>
+      <button id ="banner-feedback" class="feedback-btn">File feedback</button>
+    </div>
     <div id = "deviceTable">
     </div>
-    <button id="output-btn">Test Output Devices</button>
-    <button id="input-btn">Test Input Devices</button>
+    <div id="test-buttons">
+      <p id="device-not-present">Do not see your audio device on the table?</p>
+      <button id="no-device-feedback" class="feedback-btn">
+        File feedback
+      </button>
+      <button id="output-btn">Test Output Device</button>
+      <button id="input-btn">Test Input Device</button>
+    </div>
   </section>
   <section id="output" hidden>
     <div id = "output-explainer">
       <h>Test Output Devices</h>
-      <p>Please play through each audio files, 
+      <p>Please play through each audio files,
         and then indicate whether you could hear each audio clearly.</p>
       <p>playing from: <b id = "active-output"></b></p>
-      <p class = "advice-label">please go back to the device table 
-        if this is not the intended device.</p>
+      <p class = "advice-label">please select your device from the system
+        tray if this is not the intended device.</p>
     </div>
     <div id = "audio-players">
     </div>
@@ -29,14 +47,14 @@
     <div id = "input-explainer">
       <h>Test Input Devices</h>
       <p>Please click the record button and say something.
-        Observe if you can see audio frequency from both channels, 
-        and then listen to the playback and indicate if you can hear 
+        Observe if you can see audio frequency from both channels,
+        and then listen to the playback and indicate if you can hear
         the recording clearly.</p>
-      <p class="advice-label">For example, you could say: "I'm just testing 
+      <p class="advice-label">For example, you could say: "I'm just testing
         my audio input device."</p>
       <p>Input from: <b id = "active-input"></b></p>
-      <p class="advice-label">please go back to the device table if this is not 
-        the intended device.</p>
+      <p class="advice-label">please select your device from the system
+        tray if this is not the intended device.</p>
     </div>
     <div id = 'channels'>
       <p>
@@ -63,6 +81,48 @@
       </div>
     </div>
   </section>
+  <section id="feedback" hidden>
+    <div id = "feedback-explainer">
+      <h>Feedback</h>
+      <p>To help us get to your issue as quickly as possible,
+        please follow the steps below and submit a feedback report.</p>
+      <div class="feedback-step">
+        <p class="feedback-list">1. Copy and download:</p>
+        <p class="instruction">Copy the below audio information, and then
+          download the sample recording if you created one.</p>
+        <button id="copy-btn">copy</button>
+        <textarea id="audio-info" rows="10" cols="40" disabled=true></textarea>
+        <div id="input-replay" hidden>
+          <a id="download-btn">download</a>
+          <audio id="test-input-audio" src="" controls=true>
+            This browser does not support the audio element.
+          </audio>
+        </div>
+      </div>
+      <div class="feedback-step">
+        <p class="feedback-list">2. Reproduce audio issue:</p>
+        <p class="instruction">Navigate to the page or application
+          that you are having audio issue with and reproduce the audio issue.
+          For example, if you cannot hear the audio output from an Android app
+          installed on your chromebook, navigate to the app, play the intended
+          audio, then come back to this page.</p>
+        <p class="instruction"><b>Note: Do not close the page or application.
+          </b> Keep the audio on and follow the next step of submitting
+          feedback with the audio playing or recording from the page or
+          application causing the issue.</p>
+      </div>
+      <div class="feedback-step">
+        <p class="feedback-list">3. Submit feedback:</p>
+        <p class="instruction">Click on "Submit feedback",
+          paste the copied audio information and attach the
+          downloaded recording file if one exists. Then, fill
+          in additional information according to the text you
+          just pasted.
+        </p>
+        <button id="submit-btn">Submit feedback</button>
+      </div>
+    </div>
+  </section>
   <template id="deviceTable-template">
     <table>
       <thead>
diff --git a/chrome/browser/resources/chromeos/audio/audio.ts b/chrome/browser/resources/chromeos/audio/audio.ts
index ae8a7b7..d7ffba0 100644
--- a/chrome/browser/resources/chromeos/audio/audio.ts
+++ b/chrome/browser/resources/chromeos/audio/audio.ts
@@ -3,7 +3,9 @@
 // found in the LICENSE file.
 
 import {$} from 'chrome://resources/js/util.m.js';
+
 import {DevicePage} from './device_page.js';
+import {FeedbackPage} from './feedback_page.js';
 import {InputPage} from './input_page.js';
 import {OutputPage} from './output_page.js';
 import {PageNavigator} from './page.js';
@@ -14,9 +16,11 @@
   const devicePage = DevicePage.getInstance();
   const outputPage = OutputPage.getInstance();
   const inputPage = InputPage.getInstance();
+  const feedbackPage = FeedbackPage.getInstance();
   pageNavigator.addPage(devicePage);
   pageNavigator.addPage(outputPage);
   pageNavigator.addPage(inputPage);
+  pageNavigator.addPage(feedbackPage);
   window.addEventListener('hashchange', function() {
     const pageName = window.location.hash.substr(1);
     if ($(pageName)) {
diff --git a/chrome/browser/resources/chromeos/audio/audio_player.ts b/chrome/browser/resources/chromeos/audio/audio_player.ts
index 7bb2e8e..493ac70 100644
--- a/chrome/browser/resources/chromeos/audio/audio_player.ts
+++ b/chrome/browser/resources/chromeos/audio/audio_player.ts
@@ -4,6 +4,7 @@
 
 import {$} from 'chrome://resources/js/util.m.js';
 import {OutputPage} from './output_page.js';
+import {PageNavigator} from './page.js';
 
 export class AudioPlayer extends HTMLElement {
   private current: string;
@@ -64,12 +65,17 @@
       });
     }
     const yesLink = this.createButton('yes', 'Yes');
+    const noLink = this.createButton('no', 'No');
+
     yesLink.addEventListener('click', () => {
+      yesLink.style.color = 'blue';
+      noLink.style.color = 'black';
       this.handleResponse(true);
     });
 
-    const noLink = this.createButton('no', 'No');
     noLink.addEventListener('click', () => {
+      noLink.style.color = 'blue';
+      yesLink.style.color = 'black';
       this.handleResponse(false);
     });
   }
@@ -96,6 +102,8 @@
     OutputPage.getInstance().setOutputMapEntry(this.current, response);
     if (this.next) {
       this.handleNext();
+    } else {
+      PageNavigator.getInstance().showPage('feedback');
     }
   }
 
diff --git a/chrome/browser/resources/chromeos/audio/device_page.ts b/chrome/browser/resources/chromeos/audio/device_page.ts
index 2f5a0648..2982211 100644
--- a/chrome/browser/resources/chromeos/audio/device_page.ts
+++ b/chrome/browser/resources/chromeos/audio/device_page.ts
@@ -5,7 +5,7 @@
 import {DeviceTable} from './device_table.js';
 import {InputPage} from './input_page.js';
 import {OutputPage} from './output_page.js';
-import {Page} from './page.js';
+import {Page, PageNavigator} from './page.js';
 
 export interface DeviceMap {
   [id: number]: DeviceData;
@@ -22,8 +22,17 @@
     this.deviceTable = new DeviceTable();
     $('deviceTable').appendChild(this.deviceTable);
     this.setUpAudioDevices();
+    this.setUpButtons();
   }
 
+  setUpButtons() {
+    $('banner-feedback').addEventListener('click', () => {
+      PageNavigator.getInstance().showPage('feedback');
+    });
+    $('no-device-feedback').addEventListener('click', () => {
+      PageNavigator.getInstance().showPage('feedback');
+    });
+  }
   setUpAudioDevices() {
     this.router.updateDeviceInfo.addListener(this.updateDeviceInfo.bind(this));
     this.router.updateDeviceVolume.addListener(
diff --git a/chrome/browser/resources/chromeos/audio/device_table.ts b/chrome/browser/resources/chromeos/audio/device_table.ts
index 6a57606..67467df7f 100644
--- a/chrome/browser/resources/chromeos/audio/device_table.ts
+++ b/chrome/browser/resources/chromeos/audio/device_table.ts
@@ -23,6 +23,8 @@
   setDevices(deviceMap: DeviceMap) {
     this.devices = deviceMap;
     this.redrawTable();
+    this.checkInactiveDevice();
+    this.checkMutedDevice();
   }
 
   setDeviceVolume(nodeId: number, volume: number) {
@@ -30,7 +32,7 @@
       (this.devices[nodeId] as DeviceData).volumeGainPercent = volume;
       const row = <HTMLTableRowElement>$(String(nodeId));
       if (row && row.cells[3]) {
-        row.cells[3].textContent = String(volume);
+        row.cells[3].textContent = String(volume) + '%';
       }
     }
   }
@@ -43,14 +45,19 @@
         row.cells[4].textContent = String(isMuted);
       }
     }
+    this.checkMutedDevice();
   }
 
   redrawTable() {
     this.removeChild(this.tbody);
     this.tbody = this.createTBody();
     this.appendChild(this.tbody);
-    for (const device of Object.values(this.devices)) {
-      this.addRow(device as DeviceData);
+    for (const item of Object.values(this.devices)) {
+      const device = item as DeviceData;
+      if (!(device.type === 'POST_MIX_LOOPBACK' ||
+            device.type === 'POST_DSP_LOOPBACK')) {
+        this.addRow(device);
+      }
     }
   }
 
@@ -66,8 +73,53 @@
     newType.appendChild(document.createTextNode(device.type));
     newActive.appendChild(document.createTextNode(String(device.isActive)));
     newVolume.appendChild(
-        document.createTextNode(String(device.volumeGainPercent)));
+        document.createTextNode(String(device.volumeGainPercent) + '%'));
     newMuted.appendChild(document.createTextNode(String(device.isMuted)));
   }
+
+  checkMutedDevice() {
+    var hasMuted = false;
+    for (const item of Object.values(this.devices)) {
+      const device = item as DeviceData;
+      if (device.isMuted) {
+        hasMuted = true;
+      }
+    }
+    if (hasMuted) {
+      this.updateWarningBanner('muted');
+    } else {
+      $('warning-banner').hidden = true;
+    }
+  }
+
+  checkInactiveDevice() {
+    var allInactive = true;
+    for (const item of Object.values(this.devices)) {
+      const device = item as DeviceData;
+      if (device.isActive) {
+        allInactive = false;
+      }
+    }
+    if (allInactive) {
+      this.updateWarningBanner('inactive');
+    } else {
+      $('warning-banner').hidden = true;
+    }
+  }
+
+  updateWarningBanner(reason: string) {
+    $('warning-banner').hidden = false;
+    if (reason === 'inactive') {
+      $('warning-msg').innerHTML =
+          'Warning: There is no detected active audio device. ' +
+          'Have a device connected but cannot see it on the table or marked as active?';
+    }
+    if (reason === 'muted') {
+      $('warning-msg').innerHTML =
+          'Warning: One or more of your active devices is muted. ' +
+          'Please try unmuting it by toggling the audio setting. ' +
+          'Have unmuted the device but still see it marked as muted?';
+    }
+  }
 }
 customElements.define('device-table', DeviceTable, {extends: 'table'});
diff --git a/chrome/browser/resources/chromeos/audio/feedback_page.ts b/chrome/browser/resources/chromeos/audio/feedback_page.ts
new file mode 100644
index 0000000..cb2ea3c
--- /dev/null
+++ b/chrome/browser/resources/chromeos/audio/feedback_page.ts
@@ -0,0 +1,89 @@
+import {$} from 'chrome://resources/js/util.m.js';
+
+import {AudioBroker} from './audio_broker.js';
+import {InputPage} from './input_page.js';
+import {OutputPage} from './output_page.js';
+import {Page} from './page.js';
+
+interface feedbackObject {
+  [key: string]: any
+}
+
+export class FeedbackPage extends Page {
+  private audioInfoString: string = '';
+  private inputFeedbackMap: Map<string, null|string>;
+  private outputFeedbackMap: Map<string, null|boolean>;
+  constructor() {
+    super('feedback');
+    this.inputFeedbackMap = InputPage.getInstance().testInputFeedback;
+    this.outputFeedbackMap = OutputPage.getInstance().testOutputFeedback;
+    this.registerButtons();
+  }
+
+  showPage() {
+    super.showPage();
+    this.updateAudioInfo();
+    this.updateDownloadButton();
+  }
+
+  registerButtons() {
+    $('copy-btn').addEventListener('click', () => {
+      navigator.clipboard.writeText(this.audioInfoString);
+    });
+    $('submit-btn').addEventListener('click', () => {
+      AudioBroker.getInstance().handler.openFeedbackDialog();
+    });
+  }
+
+  updateDownloadButton() {
+    if (this.inputFeedbackMap.has('audioUrl')) {
+      const url = this.inputFeedbackMap.get('audioUrl');
+      if (url) {
+        const downloadBtn = <HTMLAnchorElement>$('download-btn');
+        const inputAudio = <HTMLAudioElement>$('test-input-audio');
+        inputAudio.src = url;
+        downloadBtn.href = url;
+        downloadBtn.download =
+            'test_input_' + new Date().toISOString() + '.wav';
+        $('input-replay').hidden = false;
+      }
+    }
+  }
+
+  updateAudioInfo() {
+    var audioInfoJson: feedbackObject = {};
+
+    const inputFeedbackObject = this.mapToObject(this.inputFeedbackMap);
+    const outputFeedbackObject = this.mapToObject(this.outputFeedbackMap);
+
+    audioInfoJson['inputFeedback'] = inputFeedbackObject;
+    audioInfoJson['outputFeedback'] = outputFeedbackObject;
+    const infoString = JSON.stringify(audioInfoJson);
+    const guidedQuestions = `#cros-audio \n
+    1. What is the app/website that you are having
+    audio issue with (please give url or app name): \n
+    2. Describe your audio device in detail: \n
+    3. Any specific behavior you notice during the testing process?: \n
+    4. audio info: `;
+    this.audioInfoString = guidedQuestions + infoString;
+    (<HTMLTextAreaElement>$('audio-info')).value = this.audioInfoString;
+  }
+
+  mapToObject(map: Map<string, any>) {
+    const tempObject: feedbackObject = {};
+    map.forEach((value: any, key: string) => {
+      tempObject[key] = value;
+    });
+    console.log(tempObject);
+    return tempObject;
+  }
+
+  static getInstance() {
+    if (instance === null) {
+      instance = new FeedbackPage();
+    }
+    return instance;
+  }
+}
+
+let instance: FeedbackPage|null = null;
\ No newline at end of file
diff --git a/chrome/browser/resources/chromeos/audio/input_page.ts b/chrome/browser/resources/chromeos/audio/input_page.ts
index e00f1d19..a07ad7c 100644
--- a/chrome/browser/resources/chromeos/audio/input_page.ts
+++ b/chrome/browser/resources/chromeos/audio/input_page.ts
@@ -1,7 +1,7 @@
 import {$} from 'chrome://resources/js/util.m.js';
 
 import {AudioBroker} from './audio_broker.js';
-import {Page} from './page.js';
+import {Page, PageNavigator} from './page.js';
 
 
 export class InputPage extends Page {
@@ -23,7 +23,7 @@
     this.recordClicked = false;
     this.intervalId = null;
     this.testInputFeedback =
-        new Map([['AudioUrl', null], ['Can Hear Clearly', null]]);
+        new Map([['audioUrl', null], ['Can Hear Clearly', null]]);
     this.setUpButtons();
   }
 
@@ -226,9 +226,11 @@
   setUpButtons() {
     $('input-yes').addEventListener('click', () => {
       this.testInputFeedback.set('Can Hear Clearly', 'true');
+      PageNavigator.getInstance().showPage('feedback');
     });
     $('input-no').addEventListener('click', () => {
       this.testInputFeedback.set('Can Hear Clearly', 'false');
+      PageNavigator.getInstance().showPage('feedback');
     });
   }
 
diff --git a/chrome/browser/resources/chromeos/audio/output_page.ts b/chrome/browser/resources/chromeos/audio/output_page.ts
index 5efcf869..741e627 100644
--- a/chrome/browser/resources/chromeos/audio/output_page.ts
+++ b/chrome/browser/resources/chromeos/audio/output_page.ts
@@ -11,7 +11,7 @@
 ];
 
 export class OutputPage extends Page {
-  private testOutputFeedback: Map<string, null|boolean>;
+  testOutputFeedback: Map<string, null|boolean>;
   constructor() {
     super('output');
     this.testOutputFeedback = new Map();
diff --git a/chrome/browser/resources/new_tab_page/BUILD.gn b/chrome/browser/resources/new_tab_page/BUILD.gn
index 743722b..a8c8e464 100644
--- a/chrome/browser/resources/new_tab_page/BUILD.gn
+++ b/chrome/browser/resources/new_tab_page/BUILD.gn
@@ -391,6 +391,7 @@
   grdp_files = [
     "$target_gen_dir/icons/resources.grdp",
     "$target_gen_dir/modules/cart/icons/resources.grdp",
+    "$target_gen_dir/modules/recipes_v2/icons/resources.grdp",
     "$target_gen_dir/new_tab_page_mojo_resources.grdp",
     "$target_gen_dir/promo_browser_command_mojo_resources.grdp",
     "$target_gen_dir/realbox/icons/resources.grdp",
@@ -409,6 +410,7 @@
     ":build_task_module_mojo_grdp",
     "icons:build_grdp",
     "modules/cart/icons:build_grdp",
+    "modules/recipes_v2/icons:build_grdp",
     "realbox:build_realbox_mojo_grdp",
     "realbox/icons:build_grdp",
   ]
diff --git a/chrome/browser/resources/new_tab_page/modules/cart_v2/BUILD.gn b/chrome/browser/resources/new_tab_page/modules/cart_v2/BUILD.gn
index 2451533..7b82595 100644
--- a/chrome/browser/resources/new_tab_page/modules/cart_v2/BUILD.gn
+++ b/chrome/browser/resources/new_tab_page/modules/cart_v2/BUILD.gn
@@ -8,6 +8,7 @@
 js_library("module") {
   deps = [
     "..:module_descriptor",
+    "..:module_header",
     "../..:i18n_setup",
     "//chrome/browser/resources/new_tab_page/modules/cart:chrome_cart_proxy",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
diff --git a/chrome/browser/resources/new_tab_page/modules/drive_v2/BUILD.gn b/chrome/browser/resources/new_tab_page/modules/drive_v2/BUILD.gn
index e61c7c69..9bc88100 100644
--- a/chrome/browser/resources/new_tab_page/modules/drive_v2/BUILD.gn
+++ b/chrome/browser/resources/new_tab_page/modules/drive_v2/BUILD.gn
@@ -9,6 +9,7 @@
   deps = [
     "..:info_dialog",
     "..:module_descriptor",
+    "..:module_header",
     "../..:i18n_setup",
     "//chrome/browser/resources/new_tab_page/modules/drive:drive_module_proxy",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
diff --git a/chrome/browser/resources/new_tab_page/modules/recipes_v2/BUILD.gn b/chrome/browser/resources/new_tab_page/modules/recipes_v2/BUILD.gn
index 056a98f..9b14e88c 100644
--- a/chrome/browser/resources/new_tab_page/modules/recipes_v2/BUILD.gn
+++ b/chrome/browser/resources/new_tab_page/modules/recipes_v2/BUILD.gn
@@ -8,6 +8,7 @@
 js_library("module") {
   deps = [
     "..:module_descriptor",
+    "..:module_header",
     "//chrome/browser/resources/new_tab_page/modules/task_module:task_module_handler_proxy",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/cr_elements/cr_auto_img",
diff --git a/chrome/browser/resources/new_tab_page/modules/recipes_v2/icons/BUILD.gn b/chrome/browser/resources/new_tab_page/modules/recipes_v2/icons/BUILD.gn
new file mode 100644
index 0000000..027a8ebd
--- /dev/null
+++ b/chrome/browser/resources/new_tab_page/modules/recipes_v2/icons/BUILD.gn
@@ -0,0 +1,13 @@
+# 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("//ui/webui/resources/tools/generate_grd.gni")
+
+generate_grd("build_grdp") {
+  grd_prefix = "recipes"
+  out_grd = "$target_gen_dir/resources.grdp"
+  input_files = [ "recipes_logo.svg" ]
+  input_files_base_dir = rebase_path(".", "//")
+  resource_path_prefix = "modules/recipes_v2/icons"
+}
diff --git a/chrome/browser/resources/new_tab_page/modules/recipes_v2/icons/recipes_logo.svg b/chrome/browser/resources/new_tab_page/modules/recipes_v2/icons/recipes_logo.svg
new file mode 100644
index 0000000..75704e92
--- /dev/null
+++ b/chrome/browser/resources/new_tab_page/modules/recipes_v2/icons/recipes_logo.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#455A64"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M16 6v8h3v8h2V2c-2.76 0-5 2.24-5 4zm-5 3H9V2H7v7H5V2H3v7c0 2.21 1.79 4 4 4v9h2v-9c2.21 0 4-1.79 4-4V2h-2v7z"/></svg>
\ No newline at end of file
diff --git a/chrome/browser/resources/new_tab_page/modules/recipes_v2/module.html b/chrome/browser/resources/new_tab_page/modules/recipes_v2/module.html
index 51f8f3c..8d3c958 100644
--- a/chrome/browser/resources/new_tab_page/modules/recipes_v2/module.html
+++ b/chrome/browser/resources/new_tab_page/modules/recipes_v2/module.html
@@ -20,6 +20,9 @@
     flex-direction: row;
   }
 </style>
+<ntp-module-header icon-src="modules/recipes_v2/icons/recipes_logo.svg">
+  [[task.title]]
+</ntp-module-header>
 <div id="moduleContent">
   <div id="recipes">
     <template is="dom-repeat" id="recipesRepeat" items="[[task.taskItems]]">
diff --git a/chrome/browser/resources/new_tab_page/modules/recipes_v2/module.js b/chrome/browser/resources/new_tab_page/modules/recipes_v2/module.js
index 8962aaa..153b9fc 100644
--- a/chrome/browser/resources/new_tab_page/modules/recipes_v2/module.js
+++ b/chrome/browser/resources/new_tab_page/modules/recipes_v2/module.js
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import '../module_header.js';
 import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {loadTimeData} from '../../i18n_setup.js';
diff --git a/chrome/browser/resources/new_tab_page/new_tab_page.js b/chrome/browser/resources/new_tab_page/new_tab_page.js
index b878882..b4a92cd 100644
--- a/chrome/browser/resources/new_tab_page/new_tab_page.js
+++ b/chrome/browser/resources/new_tab_page/new_tab_page.js
@@ -32,8 +32,8 @@
 // <if expr="not is_official_build">
 export {photosDescriptor} from './modules/photos/module.js';
 export {PhotosProxy} from './modules/photos/photos_module_proxy.js';
-export {recipeTasksDescriptor as recipeTasksV2Descriptor} from './modules/recipes_v2/module.js';
 // </if>
+export {recipeTasksDescriptor as recipeTasksV2Descriptor} from './modules/recipes_v2/module.js';
 export {recipeTasksDescriptor, shoppingTasksDescriptor} from './modules/task_module/module.js';
 export {TaskModuleHandlerProxy} from './modules/task_module/task_module_handler_proxy.js';
 export {NewTabPageProxy} from './new_tab_page_proxy.js';
diff --git a/chrome/browser/resources/print_preview/data/model.js b/chrome/browser/resources/print_preview/data/model.js
index 3a462df..8e262ae 100644
--- a/chrome/browser/resources/print_preview/data/model.js
+++ b/chrome/browser/resources/print_preview/data/model.js
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 import {assert} from 'chrome://resources/js/assert.m.js';
-import {isMac, isWindows} from 'chrome://resources/js/cr.m.js';
+import {isChromeOS, isLacros, isLinux, isMac, isWindows} from 'chrome://resources/js/cr.m.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 import {PromiseResolver} from 'chrome://resources/js/promise_resolver.m.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
@@ -112,6 +112,7 @@
  *   color: (PolicyEntry | undefined),
  *   duplex: (PolicyEntry | undefined),
  *   pin: (PolicyEntry | undefined),
+ *   printPdfAsImageAvailability: (PolicyEntry | undefined),
  * }}
  */
 export let PolicySettings;
@@ -791,8 +792,7 @@
         !this.documentSettings.isFromArc && this.isHeaderFooterAvailable_());
     this.setSettingPath_(
         'rasterize.available',
-        !this.documentSettings.isFromArc &&
-            !this.documentSettings.isModifiable && !isWindows && !isMac);
+        !this.documentSettings.isFromArc && this.isRasterizeAvailable_());
     this.setSettingPath_(
         'otherOptions.available',
         this.settings.cssBackground.available ||
@@ -849,6 +849,38 @@
         this.margins.get(CustomMarginsOrientation.BOTTOM) > 0;
   }
 
+  /** @private */
+  updateRasterizeAvailable_() {
+    // Need document settings to know if source is PDF.
+    if (this.documentSettings === undefined) {
+      return;
+    }
+
+    this.setSettingPath_('rasterize.available', this.isRasterizeAvailable_());
+  }
+
+  /**
+   * @return {boolean} Whether the rasterization setting should be available.
+   * @private
+   */
+  isRasterizeAvailable_() {
+    // Only a possibility for PDFs.  Always available for PDFs on Linux and
+    // ChromeOS.crbug.com/675798
+    let available =
+        !!this.documentSettings && !this.documentSettings.isModifiable;
+
+    // <if expr="is_win or is_macosx">
+    // Availability on Windows or macOS depends upon policy.
+    if (!available || !this.policySettings_) {
+      return false;
+    }
+    const policy = this.policySettings_['printPdfAsImageAvailability'];
+    available = policy !== undefined && policy.value;
+    // </if>
+
+    return available;
+  }
+
   /**
    * @param {?CddCapabilities} caps The printer capabilities.
    * @private
@@ -1136,6 +1168,15 @@
         }
         break;
       }
+      case 'printPdfAsImageAvailability': {
+        const value = allowedMode !== undefined ? allowedMode : defaultMode;
+        if (value !== undefined) {
+          this.setPolicySetting_(
+              settingName, value, /*managed=*/ false,
+              /*applyOnDestinationUpdate=*/ false);
+        }
+        break;
+      }
       default:
         break;
     }
@@ -1177,6 +1218,16 @@
       this.configurePolicySetting_(settingName, allowedMode, defaultMode);
     });
     // </if>
+    // <if expr="is_win or is_macosx">
+    if (policies['printPdfAsImageAvailability']) {
+      if (!this.policySettings_) {
+        this.policySettings_ = {};
+      }
+      const allowedMode = policies['printPdfAsImageAvailability'].allowedMode;
+      this.configurePolicySetting_(
+          'printPdfAsImageAvailability', allowedMode, /*defaultMode=*/ false);
+    }
+    // </if>
   }
 
   applyStickySettings() {
@@ -1276,6 +1327,16 @@
           continue;
         }
         // </if>
+        // <if expr="is_win or is_macosx">
+        if (settingName === 'printPdfAsImageAvailability') {
+          this.updateRasterizeAvailable_();
+          if (this.settings.rasterize.available) {
+            // If rasterize is available then otherOptions must be available.
+            this.setSettingPath_('otherOptions.available', true);
+          }
+          continue;
+        }
+        // </if>
         if (policy.value !== undefined && !policy.applyOnDestinationUpdate) {
           this.setSetting(settingName, policy.value, true);
           if (policy.managed) {
diff --git a/chrome/browser/resources/read_later/side_panel/bookmark_folder.html b/chrome/browser/resources/read_later/side_panel/bookmark_folder.html
index 7e21088d..3d56f65 100644
--- a/chrome/browser/resources/read_later/side_panel/bookmark_folder.html
+++ b/chrome/browser/resources/read_later/side_panel/bookmark_folder.html
@@ -1,5 +1,6 @@
 <style include="read-later-shared-style">
   :host {
+    --row-height: 40px;
     display: block;
     user-select: none;
     white-space: nowrap;
@@ -12,23 +13,24 @@
     border: none;
     box-sizing: border-box;
     color: currentColor;
+    content-visibility: auto;
     display: grid;
     grid-template-areas: 'arrow icon title';
     grid-template-columns: 20px 20px auto;
-    height: 40px;
-    line-height: 40px;
+    height: var(--row-height);
+    line-height: var(--row-height);
     padding-inline-start: calc(var(--node-depth) * 17px);
     text-align: start;
     width: 100%;
   }
 
-  .row:hover,
+  .row:not([empty]):hover,
   .row:focus-visible:focus {
     background-color: var(--cr-hover-background-color);
     outline: none;
   }
 
-  .row:active {
+  .row:not([empty]):active {
     background-color: var(--cr-active-background-color);
   }
 
@@ -83,26 +85,33 @@
     color: currentColor;
     text-decoration: none;
   }
-</style>
-<button class="row" on-click="onFolderClick_" tabindex="0"
-    on-contextmenu="onFolderContextMenu_">
-  <div id="arrow" hidden$="[[!folder.children.length]]">
-    <cr-icon-button
-        id="arrowIcon"
-        iron-icon="cr:arrow-drop-down"
-        open$="[[open_]]"
-        tabindex="-1">
-    </cr-icon-button>
-  </div>
-  <div id="folderIcon" class="icon"></div>
-  <div class="title">[[folder.title]]</div>
-</button>
 
-<div id="children" role="group">
-  <template is="dom-if" if="[[open_]]" restamp>
-    <template is="dom-repeat" items="[[folder.children]]">
+  #children {
+    --node-depth: var(--child-depth);
+    min-height: calc(var(--child-count, 0) * var(--row-height));
+  }
+</style>
+<div id="container" role="treeitem" aria-expanded="[[getAriaExpanded_(open_)]]">
+  <button id="folder" class="row"
+      empty$="[[!folder.children.length]]"
+      on-click="onFolderClick_" on-contextmenu="onFolderContextMenu_">
+    <div id="arrow" hidden$="[[!folder.children.length]]">
+      <cr-icon-button
+          id="arrowIcon"
+          iron-icon="cr:arrow-drop-down"
+          open$="[[open_]]"
+          tabindex="-1">
+      </cr-icon-button>
+    </div>
+    <div id="folderIcon" class="icon"></div>
+    <div class="title">[[folder.title]]</div>
+  </button>
+
+<template is="dom-if" if="[[open_]]" restamp>
+  <div id="children" role="group">
+    <template is="dom-repeat" items="[[folder.children]]" initial-count="20">
       <template is="dom-if" if="[[!item.url]]" restamp>
-        <bookmark-folder role="treeitem" folder="[[item]]"
+        <bookmark-folder folder="[[item]]"
             depth="[[childDepth_]]"
             open-folders="[[openFolders]]">
         </bookmark-folder>
@@ -121,5 +130,5 @@
         </a>
       </template>
     </template>
-  </template>
-</div>
\ No newline at end of file
+  </div>
+</template>
diff --git a/chrome/browser/resources/read_later/side_panel/bookmark_folder.ts b/chrome/browser/resources/read_later/side_panel/bookmark_folder.ts
index e40e1482..b14f5c2 100644
--- a/chrome/browser/resources/read_later/side_panel/bookmark_folder.ts
+++ b/chrome/browser/resources/read_later/side_panel/bookmark_folder.ts
@@ -73,8 +73,24 @@
   openFolders: string[];
   private bookmarksApi_: BookmarksApiProxy = BookmarksApiProxy.getInstance();
 
+  static get observers() {
+    return [
+      'onChildrenLengthChanged_(folder.children.length)',
+    ];
+  }
+
+  private getAriaExpanded_(): string|undefined {
+    if (!this.folder.children || this.folder.children.length === 0) {
+      // Remove the attribute for empty folders that cannot be expanded.
+      return undefined;
+    }
+
+    return this.open_ ? 'true' : 'false';
+  }
+
   private onBookmarkClick_(event: RepeaterMouseEvent) {
     event.preventDefault();
+    event.stopPropagation();
     this.bookmarksApi_.openBookmark(event.model.item.url!, this.depth, {
       middleButton: event.type === 'auxclick',
       altKey: event.altKey,
@@ -86,12 +102,14 @@
 
   private onBookmarkContextMenu_(event: RepeaterMouseEvent) {
     event.preventDefault();
+    event.stopPropagation();
     this.bookmarksApi_.showContextMenu(
         event.model.item.id, event.clientX, event.clientY);
   }
 
   private onFolderContextMenu_(event: MouseEvent) {
     event.preventDefault();
+    event.stopPropagation();
     this.bookmarksApi_.showContextMenu(
         this.folder.id, event.clientX, event.clientY);
   }
@@ -100,13 +118,21 @@
     return getFaviconForPageURL(url, false);
   }
 
+  private onChildrenLengthChanged_() {
+    this.style.setProperty(
+        '--child-count', this.folder.children!.length.toString());
+  }
+
   private onDepthChanged_() {
     this.childDepth_ = this.depth + 1;
     this.style.setProperty('--node-depth', `${this.depth}`);
-    this.$.children.style.setProperty('--node-depth', `${this.childDepth_}`);
+    this.style.setProperty('--child-depth', `${this.childDepth_}`);
   }
 
-  private onFolderClick_() {
+  private onFolderClick_(event: Event) {
+    event.preventDefault();
+    event.stopPropagation();
+
     if (!this.folder.children || this.folder.children.length === 0) {
       // No reason to open if there are no children to show.
       return;
diff --git a/chrome/browser/resources/read_later/side_panel/bookmarks_list.html b/chrome/browser/resources/read_later/side_panel/bookmarks_list.html
index eb325be..e24c162 100644
--- a/chrome/browser/resources/read_later/side_panel/bookmarks_list.html
+++ b/chrome/browser/resources/read_later/side_panel/bookmarks_list.html
@@ -1,5 +1,4 @@
 <template is="dom-repeat" items="[[folders_]]">
-  <bookmark-folder role="treeitem" folder="[[item]]"
-      open-folders="[[openFolders_]]">
+  <bookmark-folder folder="[[item]]" open-folders="[[openFolders_]]">
   </bookmark-folder>
 </template>
\ No newline at end of file
diff --git a/chrome/browser/resources/settings/BUILD.gn b/chrome/browser/resources/settings/BUILD.gn
index bad0412..08fef8f 100644
--- a/chrome/browser/resources/settings/BUILD.gn
+++ b/chrome/browser/resources/settings/BUILD.gn
@@ -171,7 +171,7 @@
   } else {
     if (!is_chromeos_lacros) {
       in_files += [
-        "default_browser_page/default_browser_browser_proxy.js",
+        "default_browser_page/default_browser_browser_proxy.ts",
         "system_page/system_page_browser_proxy.ts",
       ]
     }
@@ -288,8 +288,8 @@
     "search_page/search_page.ts",
     "settings_main/settings_main.js",
     "settings_page/settings_animated_pages.js",
-    "settings_page/settings_section.js",
-    "settings_page/settings_subpage.js",
+    "settings_page/settings_section.ts",
+    "settings_page/settings_subpage.ts",
     "settings_ui/settings_ui.js",
     "settings_page_css.ts",
     "settings_shared_css.ts",
@@ -338,7 +338,7 @@
 
   if (!is_chromeos) {
     in_files += [
-      "default_browser_page/default_browser_page.js",
+      "default_browser_page/default_browser_page.ts",
       "system_page/system_page.ts",
     ]
   }
@@ -397,10 +397,6 @@
     deps += [ "languages_page:closure_compile" ]
   }
 
-  if (!is_chromeos) {
-    deps += [ "default_browser_page:closure_compile" ]
-  }
-
   if (is_win) {
     deps += [
       "chrome_cleanup_page:closure_compile",
@@ -486,7 +482,6 @@
 }
 
 js_library("hats_browser_proxy") {
-  deps = [ "//ui/webui/resources/js:cr.m" ]
 }
 
 js_library("lazy_load") {
@@ -525,11 +520,9 @@
 }
 
 js_library("metrics_browser_proxy") {
-  deps = [ "//ui/webui/resources/js:cr.m" ]
 }
 
 js_library("open_window_proxy") {
-  deps = [ "//ui/webui/resources/js:cr.m" ]
 }
 
 js_library("page_visibility") {
@@ -795,8 +788,8 @@
     "settings_page_css.ts",
     "settings_page/main_page_mixin.js",
     "settings_page/settings_animated_pages.js",
-    "settings_page/settings_section.js",
-    "settings_page/settings_subpage.js",
+    "settings_page/settings_section.ts",
+    "settings_page/settings_subpage.ts",
     "settings_routes.js",
     "settings_shared_css.ts",
     "settings_ui/settings_ui.js",
@@ -856,8 +849,8 @@
   } else {
     if (!is_chromeos_lacros) {
       in_files += [
-        "default_browser_page/default_browser_browser_proxy.js",
-        "default_browser_page/default_browser_page.js",
+        "default_browser_page/default_browser_browser_proxy.ts",
+        "default_browser_page/default_browser_page.ts",
         "system_page/system_page_browser_proxy.ts",
         "system_page/system_page.ts",
       ]
diff --git a/chrome/browser/resources/settings/autofill_page/BUILD.gn b/chrome/browser/resources/settings/autofill_page/BUILD.gn
index 1af2350..0ff27d9 100644
--- a/chrome/browser/resources/settings/autofill_page/BUILD.gn
+++ b/chrome/browser/resources/settings/autofill_page/BUILD.gn
@@ -259,7 +259,6 @@
 }
 
 js_library("password_manager_proxy") {
-  deps = [ "//ui/webui/resources/js:cr.m" ]
   externs_list =
       chrome_extension_public_externs + [ "$externs_path/passwords_private.js" ]
 }
diff --git a/chrome/browser/resources/settings/autofill_page/password_manager_proxy.js b/chrome/browser/resources/settings/autofill_page/password_manager_proxy.js
index bcd3acd3..a7a4745d 100644
--- a/chrome/browser/resources/settings/autofill_page/password_manager_proxy.js
+++ b/chrome/browser/resources/settings/autofill_page/password_manager_proxy.js
@@ -7,8 +7,6 @@
  * chrome.passwordsPrivate which facilitates testing.
  */
 
-import {addSingletonGetter} from 'chrome://resources/js/cr.m.js';
-
 /**
  * Interface for all callbacks to the password API.
  * @interface
@@ -631,6 +629,17 @@
         'PasswordManager.BulkCheck.PasswordCheckReferrer', referrer,
         PasswordManagerProxy.PasswordCheckReferrer.COUNT);
   }
+
+  /** @return {!PasswordManagerProxy} */
+  static getInstance() {
+    return instance || (instance = new PasswordManagerImpl());
+  }
+
+  /** @param {!PasswordManagerProxy} obj */
+  static setInstance(obj) {
+    instance = obj;
+  }
 }
 
-addSingletonGetter(PasswordManagerImpl);
+/** @type {?PasswordManagerProxy} */
+let instance = null;
diff --git a/chrome/browser/resources/settings/chrome_cleanup_page/chrome_cleanup_proxy.js b/chrome/browser/resources/settings/chrome_cleanup_page/chrome_cleanup_proxy.js
index 021dd599..1d02de1 100644
--- a/chrome/browser/resources/settings/chrome_cleanup_page/chrome_cleanup_proxy.js
+++ b/chrome/browser/resources/settings/chrome_cleanup_page/chrome_cleanup_proxy.js
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 // clang-format off
-import {addSingletonGetter, sendWithPromise} from 'chrome://resources/js/cr.m.js';
+import {sendWithPromise} from 'chrome://resources/js/cr.m.js';
 // clang-format on
 
 /** @interface */
@@ -105,6 +105,17 @@
   getItemsToRemovePluralString(numItems) {
     return sendWithPromise('getItemsToRemovePluralString', numItems);
   }
+
+  /** @return {!ChromeCleanupProxy} */
+  static getInstance() {
+    return instance || (instance = new ChromeCleanupProxyImpl());
+  }
+
+  /** @param {!ChromeCleanupProxy} obj */
+  static setInstance(obj) {
+    instance = obj;
+  }
 }
 
-addSingletonGetter(ChromeCleanupProxyImpl);
+/** @type {?ChromeCleanupProxy} */
+let instance = null;
diff --git a/chrome/browser/resources/settings/chromeos/BUILD.gn b/chrome/browser/resources/settings/chromeos/BUILD.gn
index d6d87f0..31d79b5 100644
--- a/chrome/browser/resources/settings/chromeos/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/BUILD.gn
@@ -282,6 +282,8 @@
     "lifetime_browser_proxy.js",
     "setting_id_param_util.js",
     "settings_page_css.js",
+    "settings_page/settings_section.js",
+    "settings_page/settings_subpage.js",
     "settings_shared_css.js",
     "settings_vars_css.js",
   ]
@@ -547,8 +549,6 @@
     "privacy_page/secure_dns_input.js",
     "privacy_page/personalization_options.js",
     "settings_page/settings_animated_pages.js",
-    "settings_page/settings_section.js",
-    "settings_page/settings_subpage.js",
   ]
 }
 
diff --git a/chrome/browser/resources/settings/chromeos/crostini_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/crostini_page/BUILD.gn
index 2e6ba39..e605e52 100644
--- a/chrome/browser/resources/settings/chromeos/crostini_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/crostini_page/BUILD.gn
@@ -148,7 +148,6 @@
     "../guest_os:guest_os_shared_usb_devices",
     "../localized_link:localized_link",
     "//chrome/browser/resources/settings/settings_page:settings_animated_pages",
-    "//chrome/browser/resources/settings/settings_page:settings_subpage",
     "//ui/webui/resources/cr_elements/cr_button:cr_button.m",
     "//ui/webui/resources/cr_elements/cr_icon_button:cr_icon_button.m",
     "//ui/webui/resources/cr_elements/policy:cr_policy_indicator.m",
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/internet_page/BUILD.gn
index 63ad6c8..b6ffab6 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/internet_page/BUILD.gn
@@ -153,7 +153,6 @@
     "//chrome/browser/resources/settings/chromeos:os_route.m",
     "//chrome/browser/resources/settings/prefs:prefs",
     "//chrome/browser/resources/settings/settings_page:settings_animated_pages",
-    "//chrome/browser/resources/settings/settings_page:settings_subpage",
     "//third_party/polymer/v3_0/components-chromium/iron-icon:iron-icon",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/cr_components/chromeos/cellular_setup:cellular_types.m",
diff --git a/chrome/browser/resources/settings/chromeos/os_languages_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_languages_page/BUILD.gn
index c4051b37..13d5016 100644
--- a/chrome/browser/resources/settings/chromeos/os_languages_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_languages_page/BUILD.gn
@@ -187,7 +187,6 @@
     "../..:router",
     "../../languages_page:languages",
     "../../settings_page:settings_animated_pages",
-    "../../settings_page:settings_subpage",
     "//ui/webui/resources/js:assert.m",
     "//ui/webui/resources/js:cr.m",
     "//ui/webui/resources/js:i18n_behavior.m",
diff --git a/chrome/browser/resources/settings/chromeos/os_search_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_search_page/BUILD.gn
index 2bd9618..4d353b1 100644
--- a/chrome/browser/resources/settings/chromeos/os_search_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_search_page/BUILD.gn
@@ -26,7 +26,6 @@
     "//chrome/browser/resources/settings/chromeos/os_search_page:search_engines_browser_proxy",
     "//chrome/browser/resources/settings/controls:extension_controlled_indicator",
     "//chrome/browser/resources/settings/settings_page:settings_animated_pages",
-    "//chrome/browser/resources/settings/settings_page:settings_subpage",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/cr_elements/cr_link_row:cr_link_row",
     "//ui/webui/resources/cr_elements/policy:cr_policy_pref_indicator.m",
@@ -55,7 +54,6 @@
     "//chrome/browser/resources/settings:router",
     "//chrome/browser/resources/settings/chromeos:deep_linking_behavior.m",
     "//chrome/browser/resources/settings/chromeos:os_route.m",
-    "//chrome/browser/resources/settings/settings_page:settings_subpage",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/cr_elements/cr_link_row:cr_link_row",
     "//ui/webui/resources/js:cr.m",
@@ -70,7 +68,6 @@
     "//chrome/browser/resources/settings:router",
     "//chrome/browser/resources/settings/chromeos:deep_linking_behavior.m",
     "//chrome/browser/resources/settings/chromeos:os_route.m",
-    "//chrome/browser/resources/settings/settings_page:settings_subpage",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/cr_elements/cr_link_row:cr_link_row",
     "//ui/webui/resources/js:cr.m",
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_settings_page/BUILD.gn
index 8f90c77..c6252a5 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_settings_page/BUILD.gn
@@ -25,7 +25,6 @@
     "..:os_page_visibility.m",
     "..:os_route.m",
     "../..:router",
-    "../../settings_page:settings_section",
     "../bluetooth_page:bluetooth_page",
     "../crostini_page:crostini_page.m",
     "../device_page:device_page.m",
@@ -54,7 +53,6 @@
   sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/os_settings_page/main_page_behavior.m.js" ]
   deps = [
     "../..:router",
-    "../../settings_page:settings_section",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/js:assert.m",
     "//ui/webui/resources/js:util.m",
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_page/main_page_behavior.js b/chrome/browser/resources/settings/chromeos/os_settings_page/main_page_behavior.js
index 05374bf..9927c45c 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_page/main_page_behavior.js
+++ b/chrome/browser/resources/settings/chromeos/os_settings_page/main_page_behavior.js
@@ -7,7 +7,6 @@
 // #import {beforeNextRender} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 // #import {ensureLazyLoaded} from '../ensure_lazy_loaded.m.js';
 // #import {Route, Router, MinimumRoutes} from '../../router.js';
-// #import {SettingsSectionElement} from '../../settings_page/settings_section.js';
 // clang-format on
 
 cr.define('settings', function() {
@@ -122,7 +121,7 @@
      * 'hide-container' event is fired (necessary to avoid flashing). Callers
      * are responsible for firing a 'show-container' event.
      * @param {!settings.Route} route
-     * @return {!Promise<!SettingsSectionElement>}
+     * @return {!Promise<!HTMLElement>}
      * @private
      */
     ensureSectionForRoute_(route) {
@@ -346,13 +345,13 @@
      * ensureSectionForRoute_() which force-renders the section as needed.
      * Helper function to get a section from the local DOM.
      * @param {string} section Section name of the element to get.
-     * @return {?SettingsSectionElement}
+     * @return {?HTMLElement}
      */
     getSection(section) {
       if (!section) {
         return null;
       }
-      return /** @type {?SettingsSectionElement} */ (
+      return /** @type {?HTMLElement} */ (
           this.$$(`settings-section[section="${section}"]`));
     },
   };
diff --git a/chrome/browser/resources/settings/default_browser_page/BUILD.gn b/chrome/browser/resources/settings/default_browser_page/BUILD.gn
index 4a12a67..43fb427 100644
--- a/chrome/browser/resources/settings/default_browser_page/BUILD.gn
+++ b/chrome/browser/resources/settings/default_browser_page/BUILD.gn
@@ -2,31 +2,8 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import("//third_party/closure_compiler/compile_js.gni")
 import("//tools/polymer/html_to_js.gni")
-import("../settings.gni")
-
-js_type_check("closure_compile") {
-  is_polymer3 = true
-  closure_flags = settings_closure_flags
-  deps = [
-    ":default_browser_browser_proxy",
-    ":default_browser_page",
-  ]
-}
-
-js_library("default_browser_browser_proxy") {
-  deps = [ "//ui/webui/resources/js:cr.m" ]
-}
-
-js_library("default_browser_page") {
-  deps = [
-    ":default_browser_browser_proxy",
-    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
-    "//ui/webui/resources/js:web_ui_listener_behavior.m",
-  ]
-}
 
 html_to_js("web_components") {
-  js_files = [ "default_browser_page.js" ]
+  js_files = [ "default_browser_page.ts" ]
 }
diff --git a/chrome/browser/resources/settings/default_browser_page/default_browser_browser_proxy.js b/chrome/browser/resources/settings/default_browser_page/default_browser_browser_proxy.js
deleted file mode 100644
index ed9869f..0000000
--- a/chrome/browser/resources/settings/default_browser_page/default_browser_browser_proxy.js
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright 2016 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 A helper object used from the "Default Browser" section
- * to interact with the browser.
- */
-
-// clang-format off
-import {addSingletonGetter, sendWithPromise} from 'chrome://resources/js/cr.m.js';
-// clang-format on
-
-/**
- * @typedef {{
- *   canBeDefault: boolean,
- *   isDefault: boolean,
- *   isDisabledByPolicy: boolean,
- *   isUnknownError: boolean,
- * }};
- */
-export let DefaultBrowserInfo;
-
-/** @interface */
-export class DefaultBrowserBrowserProxy {
-  /**
-   * Get the initial DefaultBrowserInfo and begin sending updates to
-   * 'settings.updateDefaultBrowserState'.
-   * @return {!Promise<!DefaultBrowserInfo>}
-   */
-  requestDefaultBrowserState() {}
-
-  /*
-   * Try to set the current browser as the default browser. The new status of
-   * the settings will be sent to 'settings.updateDefaultBrowserState'.
-   */
-  setAsDefaultBrowser() {}
-}
-
-/** @implements {DefaultBrowserBrowserProxy} */
-export class DefaultBrowserBrowserProxyImpl {
-  /** @override */
-  requestDefaultBrowserState() {
-    return sendWithPromise('requestDefaultBrowserState');
-  }
-
-  /** @override */
-  setAsDefaultBrowser() {
-    chrome.send('setAsDefaultBrowser');
-  }
-}
-
-addSingletonGetter(DefaultBrowserBrowserProxyImpl);
diff --git a/chrome/browser/resources/settings/default_browser_page/default_browser_browser_proxy.ts b/chrome/browser/resources/settings/default_browser_page/default_browser_browser_proxy.ts
new file mode 100644
index 0000000..5dd156f
--- /dev/null
+++ b/chrome/browser/resources/settings/default_browser_page/default_browser_browser_proxy.ts
@@ -0,0 +1,52 @@
+// Copyright 2016 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 A helper object used from the "Default Browser" section
+ * to interact with the browser.
+ */
+
+// clang-format off
+import {sendWithPromise} from 'chrome://resources/js/cr.m.js';
+// clang-format on
+
+export type DefaultBrowserInfo = {
+  canBeDefault: boolean; isDefault: boolean; isDisabledByPolicy: boolean;
+  isUnknownError: boolean;
+}
+
+export interface DefaultBrowserBrowserProxy {
+  /**
+   * Get the initial DefaultBrowserInfo and begin sending updates to
+   * 'settings.updateDefaultBrowserState'.
+   */
+  requestDefaultBrowserState(): Promise<DefaultBrowserInfo>;
+
+  /*
+   * Try to set the current browser as the default browser. The new status of
+   * the settings will be sent to 'settings.updateDefaultBrowserState'.
+   */
+  setAsDefaultBrowser(): void;
+}
+
+export class DefaultBrowserBrowserProxyImpl implements
+    DefaultBrowserBrowserProxy {
+  requestDefaultBrowserState() {
+    return sendWithPromise('requestDefaultBrowserState');
+  }
+
+  setAsDefaultBrowser() {
+    chrome.send('setAsDefaultBrowser');
+  }
+
+  static getInstance(): DefaultBrowserBrowserProxy {
+    return instance || (instance = new DefaultBrowserBrowserProxyImpl());
+  }
+
+  static setInstance(obj: DefaultBrowserBrowserProxy) {
+    instance = obj;
+  }
+}
+
+let instance: DefaultBrowserBrowserProxy|null = null;
diff --git a/chrome/browser/resources/settings/default_browser_page/default_browser_page.js b/chrome/browser/resources/settings/default_browser_page/default_browser_page.ts
similarity index 74%
rename from chrome/browser/resources/settings/default_browser_page/default_browser_page.js
rename to chrome/browser/resources/settings/default_browser_page/default_browser_page.ts
index ec01679..2930317 100644
--- a/chrome/browser/resources/settings/default_browser_page/default_browser_page.js
+++ b/chrome/browser/resources/settings/default_browser_page/default_browser_page.ts
@@ -13,21 +13,15 @@
 import '../icons.js';
 import '../settings_shared_css.js';
 
-import {WebUIListenerBehavior, WebUIListenerBehaviorInterface} from 'chrome://resources/js/web_ui_listener_behavior.m.js';
+import {WebUIListenerBehavior} from 'chrome://resources/js/web_ui_listener_behavior.m.js';
 import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {DefaultBrowserBrowserProxy, DefaultBrowserBrowserProxyImpl, DefaultBrowserInfo} from './default_browser_browser_proxy.js';
 
-
-/**
- * @constructor
- * @extends {PolymerElement}
- * @implements {WebUIListenerBehaviorInterface}
- */
 const SettingsDefaultBrowserPageElementBase =
-    mixinBehaviors([WebUIListenerBehavior], PolymerElement);
+    mixinBehaviors([WebUIListenerBehavior], PolymerElement) as
+    {new (): PolymerElement & WebUIListenerBehavior};
 
-/** @polymer */
 class SettingsDefaultBrowserPageElement extends
     SettingsDefaultBrowserPageElementBase {
   static get is() {
@@ -40,30 +34,20 @@
 
   static get properties() {
     return {
-      /** @private */
       isDefault_: Boolean,
-
-      /** @private */
       isSecondaryInstall_: Boolean,
-
-      /** @private */
       isUnknownError_: Boolean,
-
-      /** @private */
       maySetDefaultBrowser_: Boolean,
-
     };
   }
 
-  /** @override */
-  constructor() {
-    super();
+  private isDefault_: boolean;
+  private isSecondaryInstall_: boolean;
+  private isUnknownError_: boolean;
+  private maySetDefaultBrowser_: boolean;
+  private browserProxy_: DefaultBrowserBrowserProxy =
+      DefaultBrowserBrowserProxyImpl.getInstance();
 
-    /** @private {!DefaultBrowserBrowserProxy} */
-    this.browserProxy_ = DefaultBrowserBrowserProxyImpl.getInstance();
-  }
-
-  /** @override */
   ready() {
     super.ready();
 
@@ -75,11 +59,7 @@
         this.updateDefaultBrowserState_.bind(this));
   }
 
-  /**
-   * @param {!DefaultBrowserInfo} defaultBrowserState
-   * @private
-   */
-  updateDefaultBrowserState_(defaultBrowserState) {
+  private updateDefaultBrowserState_(defaultBrowserState: DefaultBrowserInfo) {
     this.isDefault_ = false;
     this.isSecondaryInstall_ = false;
     this.isUnknownError_ = false;
@@ -98,8 +78,7 @@
     }
   }
 
-  /** @private */
-  onSetDefaultBrowserTap_() {
+  private onSetDefaultBrowserTap_() {
     this.browserProxy_.setAsDefaultBrowser();
   }
 }
diff --git a/chrome/browser/resources/settings/hats_browser_proxy.js b/chrome/browser/resources/settings/hats_browser_proxy.js
index 3feec1f7..4f3d9b6 100644
--- a/chrome/browser/resources/settings/hats_browser_proxy.js
+++ b/chrome/browser/resources/settings/hats_browser_proxy.js
@@ -4,10 +4,6 @@
 
 /** @fileoverview Handles Happiness Tracking Surveys for the settings pages. */
 
-// clang-format on
-import {addSingletonGetter} from 'chrome://resources/js/cr.m.js';
-// clang-format off
-
 /**
  * All Trust & Safety based interactions which may result in a HaTS survey.
  *
@@ -37,6 +33,17 @@
   trustSafetyInteractionOccurred(interaction) {
     chrome.send('trustSafetyInteractionOccurred', [interaction]);
   }
+
+  /** @return {!HatsBrowserProxy} */
+  static getInstance() {
+    return instance || (instance = new HatsBrowserProxyImpl());
+  }
+
+  /** @param {!HatsBrowserProxy} obj */
+  static setInstance(obj) {
+    instance = obj;
+  }
 }
 
-addSingletonGetter(HatsBrowserProxyImpl);
+/** @type {?HatsBrowserProxy} */
+let instance = null;
diff --git a/chrome/browser/resources/settings/metrics_browser_proxy.js b/chrome/browser/resources/settings/metrics_browser_proxy.js
index 5d8f5f1..7a2851b 100644
--- a/chrome/browser/resources/settings/metrics_browser_proxy.js
+++ b/chrome/browser/resources/settings/metrics_browser_proxy.js
@@ -4,10 +4,6 @@
 
 /** @fileoverview Handles metrics for the settings pages. */
 
-// clang-format off
-import {addSingletonGetter} from 'chrome://resources/js/cr.m.js';
-// clang-format on
-
 /**
  * Contains all possible recorded interactions across privacy settings pages.
  *
@@ -157,6 +153,17 @@
       SafeBrowsingInteractions.COUNT
     ]);
   }
+
+  /** @return {!MetricsBrowserProxy} */
+  static getInstance() {
+    return instance || (instance = new MetricsBrowserProxyImpl());
+  }
+
+  /** @param {!MetricsBrowserProxy} obj */
+  static setInstance(obj) {
+    instance = obj;
+  }
 }
 
-addSingletonGetter(MetricsBrowserProxyImpl);
+/** @type {?MetricsBrowserProxy} */
+let instance = null;
diff --git a/chrome/browser/resources/settings/open_window_proxy.js b/chrome/browser/resources/settings/open_window_proxy.js
index 7f73130..1c7b5b9 100644
--- a/chrome/browser/resources/settings/open_window_proxy.js
+++ b/chrome/browser/resources/settings/open_window_proxy.js
@@ -7,8 +7,6 @@
  * the browser.
  */
 
-import {addSingletonGetter} from 'chrome://resources/js/cr.m.js';
-
 /** @interface */
 export class OpenWindowProxy {
   /**
@@ -24,6 +22,17 @@
   openURL(url) {
     window.open(url);
   }
+
+  /** @return {!OpenWindowProxy} */
+  static getInstance() {
+    return instance || (instance = new OpenWindowProxyImpl());
+  }
+
+  /** @param {!OpenWindowProxy} obj */
+  static setInstance(obj) {
+    instance = obj;
+  }
 }
 
-addSingletonGetter(OpenWindowProxyImpl);
+/** @type {?OpenWindowProxy} */
+let instance = null;
diff --git a/chrome/browser/resources/settings/settings_page/BUILD.gn b/chrome/browser/resources/settings/settings_page/BUILD.gn
index 0f2895c..e676af4 100644
--- a/chrome/browser/resources/settings/settings_page/BUILD.gn
+++ b/chrome/browser/resources/settings/settings_page/BUILD.gn
@@ -12,14 +12,11 @@
   deps = [
     ":main_page_mixin",
     ":settings_animated_pages",
-    ":settings_section",
-    ":settings_subpage",
   ]
 }
 
 js_library("main_page_mixin") {
   deps = [
-    ":settings_section",
     "..:route",
     "..:router",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
@@ -30,7 +27,6 @@
 
 js_library("settings_animated_pages") {
   deps = [
-    ":settings_subpage",
     "..:router",
     "..:setting_id_param_util",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
@@ -40,33 +36,10 @@
   ]
 }
 
-js_library("settings_section") {
-  deps = [
-    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
-  ]
-}
-
-js_library("settings_subpage") {
-  deps = [
-    "..:router",
-    "..:setting_id_param_util",
-    "//third_party/polymer/v3_0/components-chromium/iron-resizable-behavior:iron-resizable-behavior",
-    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
-    "//ui/webui/resources/cr_elements:find_shortcut_behavior",
-    "//ui/webui/resources/cr_elements/cr_search_field:cr_search_field",
-    "//ui/webui/resources/js:assert.m",
-    "//ui/webui/resources/js:event_tracker.m",
-    "//ui/webui/resources/js:i18n_behavior.m",
-    "//ui/webui/resources/js:load_time_data.m",
-    "//ui/webui/resources/js:util.m",
-    "//ui/webui/resources/js/cr/ui:focus_without_ink.m",
-  ]
-}
-
 html_to_js("web_components") {
   js_files = [
     "settings_animated_pages.js",
-    "settings_section.js",
-    "settings_subpage.js",
+    "settings_section.ts",
+    "settings_subpage.ts",
   ]
 }
diff --git a/chrome/browser/resources/settings/settings_page/main_page_mixin.js b/chrome/browser/resources/settings/settings_page/main_page_mixin.js
index 7d7b7a07e..15e912e 100644
--- a/chrome/browser/resources/settings/settings_page/main_page_mixin.js
+++ b/chrome/browser/resources/settings/settings_page/main_page_mixin.js
@@ -10,7 +10,6 @@
 import {loadTimeData} from '../i18n_setup.js';
 import {routes} from '../route.js';
 import {MinimumRoutes, Route, Router} from '../router.js';
-import {SettingsSectionElement} from './settings_section.js';
 // clang-format on
 
   /**
@@ -192,7 +191,7 @@
        * 'hide-container' event is fired (necessary to avoid flashing). Callers
        * are responsible for firing a 'show-container' event.
        * @param {!Route} route
-       * @return {!Promise<!SettingsSectionElement>}
+       * @return {!Promise<!HTMLElement>}
        * @private
        */
       ensureSectionForRoute_(route) {
@@ -227,7 +226,7 @@
        * fired (necessary to avoid flashing). Callers are responsible for firing
        * a 'show-container' event.
        * @param {!Route} route
-       * @return {!Promise<!Array<!SettingsSectionElement>>}
+       * @return {!Promise<!Array<!HTMLElement>>}
        * @private
        */
       ensureSectionsForRoute_(route) {
@@ -568,19 +567,19 @@
        * ensureSectionForRoute_() which force-renders the section as needed.
        * Helper function to get a section from the local DOM.
        * @param {string} section Section name of the element to get.
-       * @return {?SettingsSectionElement}
+       * @return {?HTMLElement}
        */
       getSection(section) {
         if (!section) {
           return null;
         }
-        return /** @type {?SettingsSectionElement} */ (
+        return /** @type {?HTMLElement} */ (
             this.$$(`settings-section[section="${section}"]`));
       }
 
       /*
        * @param {string} sectionName Section name of the element to get.
-       * @return {!Array<!SettingsSectionElement>}
+       * @return {!Array<!HTMLElement>}
        */
       querySettingsSections_(sectionName) {
         const result = [];
diff --git a/chrome/browser/resources/settings/settings_page/settings_animated_pages.js b/chrome/browser/resources/settings/settings_page/settings_animated_pages.js
index 95872c46..a0e99d6c 100644
--- a/chrome/browser/resources/settings/settings_page/settings_animated_pages.js
+++ b/chrome/browser/resources/settings/settings_page/settings_animated_pages.js
@@ -109,7 +109,8 @@
     //     focused (further below in this function).
     if (this.previousRoute_ &&
         !Router.getInstance().lastRouteChangeWasPopstate()) {
-      const subpage = this.querySelector('settings-subpage.iron-selected');
+      const subpage = /** @type {{focusBackButton: Function}} */ (
+          this.querySelector('settings-subpage.iron-selected'));
       if (subpage) {
         subpage.focusBackButton();
         return;
diff --git a/chrome/browser/resources/settings/settings_page/settings_section.js b/chrome/browser/resources/settings/settings_page/settings_section.ts
similarity index 82%
rename from chrome/browser/resources/settings/settings_page/settings_section.js
rename to chrome/browser/resources/settings/settings_page/settings_section.ts
index 5e46476..ffb0b62 100644
--- a/chrome/browser/resources/settings/settings_page/settings_section.js
+++ b/chrome/browser/resources/settings/settings_page/settings_section.ts
@@ -60,20 +60,23 @@
     };
   }
 
+  section: string;
+  pageTitle: string;
+  hiddenBySearch: boolean;
+
   /**
    * Get the value to which to set the aria-hidden attribute of the section
    * heading.
-   * @return {boolean|string} A return value of false will not add aria-hidden
-   *    while aria-hidden requires a string of 'true' to be hidden as per aria
-   *    specs. This function ensures we have the right return type.
-   * @private
+   * @return A return value of false will not add aria-hidden while aria-hidden
+   *    requires a string of 'true' to be hidden as per aria specs. This
+   *    function ensures we have the right return type.
    */
-  getTitleHiddenStatus_() {
+  private getTitleHiddenStatus_(): boolean|string {
     return this.pageTitle ? false : 'true';
   }
 
   focus() {
-    this.shadowRoot.querySelector('.title').focus();
+    this.shadowRoot!.querySelector<HTMLElement>('.title')!.focus();
   }
 }
 
diff --git a/chrome/browser/resources/settings/settings_page/settings_subpage.js b/chrome/browser/resources/settings/settings_page/settings_subpage.ts
similarity index 71%
rename from chrome/browser/resources/settings/settings_page/settings_subpage.js
rename to chrome/browser/resources/settings/settings_page/settings_subpage.ts
index 080a411..bc7c7baf 100644
--- a/chrome/browser/resources/settings/settings_page/settings_subpage.js
+++ b/chrome/browser/resources/settings/settings_page/settings_subpage.ts
@@ -16,29 +16,30 @@
 import '../settings_shared_css.js';
 
 import {CrSearchFieldElement} from '//resources/cr_elements/cr_search_field/cr_search_field.js';
-import {FindShortcutBehavior, FindShortcutBehaviorInterface} from '//resources/cr_elements/find_shortcut_behavior.js';
+import {FindShortcutBehavior} from '//resources/cr_elements/find_shortcut_behavior.js';
 import {assert} from '//resources/js/assert.m.js';
 import {focusWithoutInk} from '//resources/js/cr/ui/focus_without_ink.m.js';
-import {I18nBehavior, I18nBehaviorInterface} from '//resources/js/i18n_behavior.m.js';
+import {I18nBehavior} from '//resources/js/i18n_behavior.m.js';
 import {listenOnce} from '//resources/js/util.m.js';
 import {IronResizableBehavior} from '//resources/polymer/v3_0/iron-resizable-behavior/iron-resizable-behavior.js';
 import {afterNextRender, html, mixinBehaviors, PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 import {EventTracker} from 'chrome://resources/js/event_tracker.m.js';
 
 import {loadTimeData} from '../i18n_setup.js';
-import {RouteObserverMixin, Router} from '../router.js';
+import {Route, RouteObserverMixin, Router} from '../router.js';
 import {getSettingIdParameter} from '../setting_id_param_util.js';
 
+interface SettingsSubpageElement {
+  $: {
+    closeButton: HTMLElement,
+  };
+}
 
-/**
- * @constructor
- * @extends {PolymerElement}
- * @implements {I18nBehaviorInterface}
- * @implements {FindShortcutBehaviorInterface}
- */
-const SettingsSubpageElementBase = mixinBehaviors(
-    [FindShortcutBehavior, I18nBehavior, IronResizableBehavior],
-    RouteObserverMixin(PolymerElement));
+const SettingsSubpageElementBase =
+    mixinBehaviors(
+        [FindShortcutBehavior, I18nBehavior, IronResizableBehavior],
+        RouteObserverMixin(PolymerElement)) as
+    {new (): PolymerElement & FindShortcutBehavior & I18nBehavior};
 
 /** @polymer */
 class SettingsSubpageElement extends SettingsSubpageElementBase {
@@ -46,10 +47,6 @@
     return 'settings-subpage';
   }
 
-  static get template() {
-    return html`{__html_template__}`;
-  }
-
   static get properties() {
     return {
       pageTitle: String,
@@ -94,7 +91,6 @@
        * Indicates which element triggers this subpage. Used by the searching
        * algorithm to show search bubbles. It is |null| for subpages that are
        * skipped during searching.
-       * @type {?HTMLElement}
        */
       associatedControl: {
         type: Object,
@@ -109,7 +105,6 @@
         value: false,
       },
 
-      /** @private */
       active_: {
         type: Boolean,
         value: false,
@@ -118,20 +113,27 @@
     };
   }
 
+  pageTitle: string;
+  titleIcon: string;
+  learnMoreUrl: string;
+  searchLabel: string;
+  searchTerm: string;
+  showSpinner: boolean;
+  spinnerTitle: string;
+  hideCloseButton: boolean;
+  associatedControl: HTMLElement|null;
+  preserveSearchTerm: boolean;
+  private active_: boolean;
+  private lastActiveValue_: boolean = false;
+  private eventTracker_: EventTracker|null = null;
+
   constructor() {
     super();
 
-    /** @private {boolean} */
-    this.lastActiveValue_ = false;
-
     // Override FindShortcutBehavior property.
     this.findShortcutListenOnAttach = false;
-
-    /** @private {?EventTracker} */
-    this.eventTracker_ = null;
   }
 
-  /** @override */
   connectedCallback() {
     super.connectedCallback();
 
@@ -143,53 +145,40 @@
     }
   }
 
-  /** @override */
   disconnectedCallback() {
     super.disconnectedCallback();
 
-    if (this.searchLabel) {
+    if (this.eventTracker_) {
       // |searchLabel| should not change dynamically.
       this.eventTracker_.removeAll();
     }
   }
 
-  /**
-   * @return {!Promise<!CrSearchFieldElement>}
-   * @private
-   */
-  getSearchField_() {
-    let searchField = this.shadowRoot.querySelector('cr-search-field');
+  private getSearchField_(): Promise<CrSearchFieldElement> {
+    let searchField = this.shadowRoot!.querySelector('cr-search-field');
     if (searchField) {
       return Promise.resolve(searchField);
     }
 
     return new Promise(resolve => {
       listenOnce(this, 'dom-change', () => {
-        searchField = this.shadowRoot.querySelector('cr-search-field');
-        resolve(assert(searchField));
+        searchField = this.shadowRoot!.querySelector('cr-search-field');
+        resolve(assert(searchField!));
       });
     });
   }
 
-  /**
-   * Restore search field value from URL search param
-   * @private
-   */
-  restoreSearchInput_() {
-    const searchField = this.shadowRoot.querySelector('cr-search-field');
-    if (assert(searchField)) {
-      const urlSearchQuery =
-          Router.getInstance().getQueryParameters().get('searchSubpage') || '';
-      this.searchTerm = urlSearchQuery;
-      searchField.setValue(urlSearchQuery);
-    }
+  /** Restore search field value from URL search param */
+  private restoreSearchInput_() {
+    const searchField = this.shadowRoot!.querySelector('cr-search-field')!;
+    const urlSearchQuery =
+        Router.getInstance().getQueryParameters().get('searchSubpage') || '';
+    this.searchTerm = urlSearchQuery;
+    searchField.setValue(urlSearchQuery);
   }
 
-  /**
-   * Preserve search field value to URL search param
-   * @private
-   */
-  preserveSearchInput_() {
+  /** Preserve search field value to URL search param */
+  private preserveSearchInput_() {
     const query = this.searchTerm;
     const searchParams = query.length > 0 ?
         new URLSearchParams('searchSubpage=' + encodeURIComponent(query)) :
@@ -206,8 +195,7 @@
     afterNextRender(this, () => focusWithoutInk(this.$.closeButton));
   }
 
-  /** @protected */
-  currentRouteChanged(newRoute, oldRoute) {
+  currentRouteChanged(newRoute: Route, oldRoute: Route|null) {
     this.active_ = this.getAttribute('route-path') === newRoute.path;
     if (this.active_ && this.searchLabel && this.preserveSearchTerm) {
       this.getSearchField_().then(() => this.restoreSearchInput_());
@@ -223,8 +211,7 @@
     }
   }
 
-  /** @private */
-  onActiveChanged_() {
+  private onActiveChanged_() {
     if (this.lastActiveValue_ === this.active_) {
       return;
     }
@@ -239,7 +226,7 @@
       return;
     }
 
-    const searchField = this.shadowRoot.querySelector('cr-search-field');
+    const searchField = this.shadowRoot!.querySelector('cr-search-field');
     if (searchField) {
       searchField.setValue('');
     }
@@ -251,27 +238,21 @@
     }
   }
 
-  /**
-   * Clear the value of the search field.
-   * @param {!Event} e
-   */
-  onClearSubpageSearch_(e) {
+  /** Clear the value of the search field. */
+  private onClearSubpageSearch_(e: Event) {
     e.stopPropagation();
-    this.shadowRoot.querySelector('cr-search-field').setValue('');
+    this.shadowRoot!.querySelector('cr-search-field')!.setValue('');
   }
 
-  /** @private */
-  onBackClick_() {
+  private onBackClick_() {
     Router.getInstance().navigateToPreviousRoute();
   }
 
-  /** @private */
-  onHelpClick_() {
+  private onHelpClick_() {
     window.open(this.learnMoreUrl);
   }
 
-  /** @private */
-  onSearchChanged_(e) {
+  private onSearchChanged_(e: CustomEvent<string>) {
     if (this.searchTerm === e.detail) {
       return;
     }
@@ -282,29 +263,31 @@
     }
   }
 
-  /** @private */
-  getBackButtonAriaLabel_() {
+  private getBackButtonAriaLabel_() {
     return this.i18n('subpageBackButtonAriaLabel', this.pageTitle);
   }
 
-  /** @private */
-  getBackButtonAriaRoleDescription_() {
+  private getBackButtonAriaRoleDescription_() {
     return this.i18n('subpageBackButtonAriaRoleDescription', this.pageTitle);
   }
 
   // Override FindShortcutBehavior methods.
-  handleFindShortcut(modalContextOpen) {
+  handleFindShortcut(modalContextOpen: boolean) {
     if (modalContextOpen) {
       return false;
     }
-    this.shadowRoot.querySelector('cr-search-field').getSearchInput().focus();
+    this.shadowRoot!.querySelector('cr-search-field')!.getSearchInput().focus();
     return true;
   }
 
   // Override FindShortcutBehavior methods.
   searchInputHasFocus() {
-    const field = this.shadowRoot.querySelector('cr-search-field');
-    return field.getSearchInput() === field.shadowRoot.activeElement;
+    const field = this.shadowRoot!.querySelector('cr-search-field')!;
+    return field.getSearchInput() === field.shadowRoot!.activeElement;
+  }
+
+  static get template() {
+    return html`{__html_template__}`;
   }
 }
 
diff --git a/chrome/browser/resources/whats_new/whats_new_app.ts b/chrome/browser/resources/whats_new/whats_new_app.ts
index a0ada609..a60ab01 100644
--- a/chrome/browser/resources/whats_new/whats_new_app.ts
+++ b/chrome/browser/resources/whats_new/whats_new_app.ts
@@ -6,12 +6,25 @@
 import './whats_new_error_page.js';
 import './strings.m.js';
 
+import {EventTracker} from 'chrome://resources/js/event_tracker.m.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 import {html, microTask, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
+import {ClickInfo, Command} from './promo_browser_command.mojom-webui.js';
 import {WhatsNewCommandProxy} from './whats_new_command_proxy.js';
 import {WhatsNewProxyImpl} from './whats_new_proxy.js';
 
+type CommandData = {
+  commandId: number,
+  clickInfo: ClickInfo,
+};
+
+// TODO (https://www.crbug.com/1219381): Add some additional parameters so
+// that we can filter the messages a bit better.
+type BrowserCommandMessageData = {
+  data: CommandData,
+};
+
 export class WhatsNewAppElement extends PolymerElement {
   static get is() {
     return 'whats-new-app';
@@ -26,6 +39,7 @@
 
   private showErrorPage_: boolean = false;
   private url_: string = '';
+  private eventTracker_: EventTracker = new EventTracker();
 
   connectedCallback() {
     super.connectedCallback();
@@ -39,6 +53,37 @@
       }
 
       this.url_ = isAutoOpen ? url.concat('?latest=true') : url;
+      this.eventTracker_.add(
+          window, 'message',
+          event => this.handleMessage_(event as MessageEvent));
+    });
+  }
+
+  disconnectedCallback() {
+    super.disconnectedCallback();
+    this.eventTracker_.removeAll();
+  }
+
+  private handleMessage_(event: MessageEvent) {
+    const {data, origin} = event;
+    const iframeUrl = new URL(this.url_);
+    if (!data || origin !== iframeUrl.origin) {
+      return;
+    }
+
+    const commandData = (data as BrowserCommandMessageData).data;
+
+    const commandId = Object.values(Command).includes(commandData.commandId) ?
+        commandData.commandId :
+        Command.kUnknownCommand;
+
+    const handler = WhatsNewCommandProxy.getInstance().handler;
+    handler.canExecuteCommand(commandId).then(({canExecute}) => {
+      if (canExecute) {
+        handler.executeCommand(commandId, commandData.clickInfo);
+      } else {
+        console.warn('Received invalid command: ' + commandId);
+      }
     });
   }
 
diff --git a/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service.cc b/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service.cc
index 76940cb..3756760b 100644
--- a/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service.cc
+++ b/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service.cc
@@ -57,15 +57,13 @@
 }
 
 bool ChromeEnterpriseRealTimeUrlLookupService::CanAttachReferrerChain() const {
-  // Referrer chain is currently not supported for enterprise users.
-  return false;
+  return base::FeatureList::IsEnabled(
+      kRealTimeUrlLookupReferrerChainForEnterprise);
 }
 
 int ChromeEnterpriseRealTimeUrlLookupService::GetReferrerUserGestureLimit()
     const {
-  NOTREACHED()
-      << "Referrer chain is currently not supported for enterprise users.";
-  return 0;
+  return 2;
 }
 
 bool ChromeEnterpriseRealTimeUrlLookupService::CanCheckSubresourceURL() const {
@@ -145,8 +143,8 @@
 
 double ChromeEnterpriseRealTimeUrlLookupService::
     GetMinAllowedTimestampForReferrerChains() const {
-  NOTREACHED()
-      << "Referrer chain is currently not supported for enterprise users.";
+  // Enterprise URL lookup is enabled at startup and managed by the admin, so
+  // all referrer URLs should be included in the referrer chain.
   return 0;
 }
 
diff --git a/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service_factory.cc b/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service_factory.cc
index 3b35fa1..269c800 100644
--- a/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service_factory.cc
+++ b/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service_factory.cc
@@ -11,6 +11,7 @@
 #include "chrome/browser/safe_browsing/advanced_protection_status_manager.h"
 #include "chrome/browser/safe_browsing/advanced_protection_status_manager_factory.h"
 #include "chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service.h"
+#include "chrome/browser/safe_browsing/safe_browsing_navigation_observer_manager_factory.h"
 #include "chrome/browser/safe_browsing/safe_browsing_service.h"
 #include "chrome/browser/safe_browsing/user_population.h"
 #include "chrome/browser/safe_browsing/verdict_cache_manager_factory.h"
@@ -47,6 +48,7 @@
           BrowserContextDependencyManager::GetInstance()) {
   DependsOn(VerdictCacheManagerFactory::GetInstance());
   DependsOn(enterprise_connectors::ConnectorsServiceFactory::GetInstance());
+  DependsOn(SafeBrowsingNavigationObserverManagerFactory::GetInstance());
 }
 
 KeyedService*
@@ -70,9 +72,8 @@
       base::BindRepeating(&safe_browsing::GetUserPopulation, profile),
       enterprise_connectors::ConnectorsServiceFactory::GetForBrowserContext(
           profile),
-      // Referrer chain provider is set to nullptr for enterprise because
-      // it is currently not supported for enterprise users.
-      /*referrer_chain_provider=*/nullptr);
+      SafeBrowsingNavigationObserverManagerFactory::GetForBrowserContext(
+          profile));
 }
 
 }  // namespace safe_browsing
diff --git a/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service_unittest.cc b/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service_unittest.cc
index e04c0bc..6684c87 100644
--- a/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service_unittest.cc
+++ b/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service_unittest.cc
@@ -7,6 +7,7 @@
 #include "base/bind.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/mock_callback.h"
+#include "base/test/scoped_feature_list.h"
 #include "chrome/browser/enterprise/connectors/connectors_service.h"
 #include "chrome/browser/policy/dm_token_utils.h"
 #include "chrome/browser/safe_browsing/user_population.h"
@@ -17,6 +18,7 @@
 #include "components/safe_browsing/core/browser/referrer_chain_provider.h"
 #include "components/safe_browsing/core/browser/sync/sync_utils.h"
 #include "components/safe_browsing/core/browser/verdict_cache_manager.h"
+#include "components/safe_browsing/core/common/features.h"
 #include "components/safe_browsing/core/common/proto/csd.pb.h"
 #include "components/safe_browsing/core/common/safe_browsing_prefs.h"
 #include "components/sync/driver/test_sync_service.h"
@@ -30,6 +32,9 @@
 #include "testing/platform_test.h"
 
 using ::testing::_;
+using testing::DoAll;
+using testing::Return;
+using testing::SetArgPointee;
 
 namespace safe_browsing {
 
@@ -200,16 +205,20 @@
 
 TEST_F(ChromeEnterpriseRealTimeUrlLookupServiceTest,
        TestStartLookup_RequestWithDmToken) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(
+      kRealTimeUrlLookupReferrerChainForEnterprise);
   GURL url("http://example.test/");
   SetUpRTLookupResponse(RTLookupResponse::ThreatInfo::DANGEROUS,
                         RTLookupResponse::ThreatInfo::SOCIAL_ENGINEERING, 60,
                         "example.test/",
                         RTLookupResponse::ThreatInfo::COVERING_MATCH);
   SetDMTokenForTesting(policy::DMToken::CreateValidTokenForTesting("dm_token"));
-  // Referrer chain is currently disabled for enterprise requests.
+  ReferrerChain returned_referrer_chain;
   EXPECT_CALL(*referrer_chain_provider_,
               IdentifyReferrerChainByPendingEventURL(_, _, _))
-      .Times(0);
+      .WillOnce(DoAll(SetArgPointee<2>(returned_referrer_chain),
+                      Return(ReferrerChainProvider::SUCCESS)));
 
   base::MockCallback<RTLookupResponseCallback> response_callback;
   enterprise_rt_service()->StartLookup(
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service_unittest.cc b/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service_unittest.cc
index e1f8d42..c549fd37 100644
--- a/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service_unittest.cc
+++ b/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service_unittest.cc
@@ -34,6 +34,7 @@
 
 using ::testing::_;
 using ::testing::Invoke;
+using ::testing::NiceMock;
 using ::testing::Return;
 using ::testing::SaveArg;
 
@@ -126,7 +127,7 @@
       : task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME),
         fake_factory_(true, enterprise_connectors::ContentAnalysisResponse()) {
     MultipartUploadRequest::RegisterFactoryForTests(&fake_factory_);
-    auto fcm_service = std::make_unique<MockBinaryFCMService>();
+    auto fcm_service = std::make_unique<NiceMock<MockBinaryFCMService>>();
     fcm_service_ = fcm_service.get();
 
     // Since we have mocked the MultipartUploadRequest, we don't need a
@@ -188,7 +189,7 @@
       BinaryUploadService::Result* scanning_result,
       enterprise_connectors::ContentAnalysisResponse* scanning_response,
       bool is_app) {
-    auto request = std::make_unique<MockRequest>(
+    auto request = std::make_unique<NiceMock<MockRequest>>(
         base::BindOnce(
             [](BinaryUploadService::Result* target_result,
                enterprise_connectors::ContentAnalysisResponse* target_response,
diff --git a/chrome/browser/sessions/tab_restore_service_browsertest.cc b/chrome/browser/sessions/tab_restore_service_browsertest.cc
index 9e9ccb2..d04aa82 100644
--- a/chrome/browser/sessions/tab_restore_service_browsertest.cc
+++ b/chrome/browser/sessions/tab_restore_service_browsertest.cc
@@ -13,6 +13,7 @@
 #include "chrome/browser/ui/browser_commands.h"
 #include "chrome/browser/ui/browser_window.h"
 #include "chrome/browser/ui/web_applications/test/web_app_browsertest_util.h"
+#include "chrome/browser/web_applications/components/web_app_utils.h"
 #include "chrome/browser/web_applications/system_web_apps/test/test_system_web_app_installation.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/ui_test_utils.h"
@@ -27,7 +28,7 @@
             web_app::TestSystemWebAppInstallation::
                 SetUpTabbedMultiWindowApp()) {
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
-    web_app::WebAppProvider::EnableSystemWebAppsInLacrosForTesting();
+    web_app::EnableSystemWebAppsInLacrosForTesting();
 #endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
   }
 
diff --git a/chrome/browser/ssl/https_defaulted_callbacks.cc b/chrome/browser/ssl/https_defaulted_callbacks.cc
index d771ae6..f79863b 100644
--- a/chrome/browser/ssl/https_defaulted_callbacks.cc
+++ b/chrome/browser/ssl/https_defaulted_callbacks.cc
@@ -51,7 +51,8 @@
       static_cast<StatefulSSLHostStateDelegate*>(
           profile->GetSSLHostStateDelegate());
   bool is_allowlisted =
-      state && state->IsHttpAllowedForHost(handle->GetURL().host());
+      state && state->IsHttpAllowedForHost(handle->GetURL().host(),
+                                           handle->GetWebContents());
 
   return is_upgraded && !is_allowlisted;
 }
diff --git a/chrome/browser/ssl/https_only_mode_browsertest.cc b/chrome/browser/ssl/https_only_mode_browsertest.cc
index e37f668..3ea821e9 100644
--- a/chrome/browser/ssl/https_only_mode_browsertest.cc
+++ b/chrome/browser/ssl/https_only_mode_browsertest.cc
@@ -13,6 +13,7 @@
 #include "chrome/common/pref_names.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "components/prefs/pref_service.h"
+#include "components/security_interstitials/content/stateful_ssl_host_state_delegate.h"
 #include "components/strings/grit/components_strings.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
@@ -516,3 +517,68 @@
                 .query(),
             "p=first_mode");
 }
+
+// Tests that if the user bypasses the HTTPS-First Mode interstitial, and then
+// later the server fixes their HTTPS support and the user successfully connects
+// over HTTPS, the allowlist entry is cleared (so HFM will kick in again for
+// that site).
+IN_PROC_BROWSER_TEST_F(HttpsOnlyModeBrowserTest, BadHttpsFollowedByGoodHttps) {
+  GURL http_url = http_server()->GetURL("foo.com", "/close-socket");
+  GURL bad_https_url = https_server()->GetURL("foo.com", "/close-socket");
+  GURL good_https_url = https_server()->GetURL("foo.com", "/ssl/google.html");
+
+  ASSERT_EQ(http_url.host(), bad_https_url.host());
+  ASSERT_EQ(bad_https_url.host(), good_https_url.host());
+
+  auto* tab = browser()->tab_strip_model()->GetActiveWebContents();
+  auto* profile = Profile::FromBrowserContext(tab->GetBrowserContext());
+  auto* state = static_cast<StatefulSSLHostStateDelegate*>(
+      profile->GetSSLHostStateDelegate());
+
+  // First check that main frame requests revoke the decision.
+
+  // Navigate to `http_url`, which will get upgraded to `bad_https_url`.
+  EXPECT_FALSE(content::NavigateToURL(tab, http_url));
+
+  ASSERT_TRUE(
+      chrome_browser_interstitials::IsShowingHttpsFirstModeInterstitial(tab));
+  ProceedThroughInterstitial(tab);
+  EXPECT_TRUE(state->HasAllowException(http_url.host(), tab));
+
+  EXPECT_TRUE(content::NavigateToURL(tab, good_https_url));
+  EXPECT_FALSE(state->HasAllowException(http_url.host(), tab));
+
+  // Rarely, an open connection with the bad cert might be reused for the next
+  // navigation, which is supposed to show an interstitial. Close open
+  // connections to ensure a fresh connection (and certificate validation) for
+  // the next navigation. See https://crbug.com/1150592. A deeper fix for this
+  // issue would be to unify certificate bypass logic which is currently split
+  // between the net stack and content layer; see https://crbug.com/488043.
+  // See also: SSLUITest.BadCertFollowedByGoodCert.
+  state->RevokeUserAllowExceptionsHard(http_url.host());
+
+  // Now check that subresource requests revoke the decision.
+
+  // Navigate to `http_url`, which will get upgraded to `bad_https_url`.
+  EXPECT_FALSE(content::NavigateToURL(tab, http_url));
+
+  ASSERT_TRUE(
+      chrome_browser_interstitials::IsShowingHttpsFirstModeInterstitial(tab));
+  ProceedThroughInterstitial(tab);
+  EXPECT_TRUE(state->HasAllowException(http_url.host(), tab));
+
+  // Load "logo.gif" as an image on the page.
+  GURL image = https_server()->GetURL("foo.com", "/ssl/google_files/logo.gif");
+  bool result = false;
+  EXPECT_TRUE(ExecuteScriptAndExtractBool(
+      tab,
+      std::string("var img = document.createElement('img');img.src ='") +
+          image.spec() +
+          "';img.onload=function() { "
+          "window.domAutomationController.send(true); };"
+          "document.body.appendChild(img);",
+      &result));
+  EXPECT_TRUE(result);
+
+  EXPECT_FALSE(state->HasAllowException(http_url.host(), tab));
+}
diff --git a/chrome/browser/ssl/https_only_mode_controller_client.cc b/chrome/browser/ssl/https_only_mode_controller_client.cc
index 9b4d78f..4d86c5fd 100644
--- a/chrome/browser/ssl/https_only_mode_controller_client.cc
+++ b/chrome/browser/ssl/https_only_mode_controller_client.cc
@@ -51,7 +51,7 @@
           profile->GetSSLHostStateDelegate());
   // StatefulSSLHostStateDelegate can be null during tests.
   if (state) {
-    state->AllowHttpForHost(request_url_.host());
+    state->AllowHttpForHost(request_url_.host(), web_contents_);
   }
   auto* tab_helper = HttpsOnlyModeTabHelper::FromWebContents(web_contents_);
   tab_helper->set_is_navigation_fallback(true);
diff --git a/chrome/browser/ssl/https_only_mode_upgrade_interceptor.cc b/chrome/browser/ssl/https_only_mode_upgrade_interceptor.cc
index 3574fa3..7f5aeb6d 100644
--- a/chrome/browser/ssl/https_only_mode_upgrade_interceptor.cc
+++ b/chrome/browser/ssl/https_only_mode_upgrade_interceptor.cc
@@ -116,8 +116,8 @@
         static_cast<StatefulSSLHostStateDelegate*>(
             profile->GetSSLHostStateDelegate());
     // StatefulSSLHostStateDelegate can be null during tests.
-    if (state &&
-        state->IsHttpAllowedForHost(tentative_resource_request.url.host())) {
+    if (state && state->IsHttpAllowedForHost(
+                     tentative_resource_request.url.host(), web_contents)) {
       std::move(callback).Run({});
       return;
     }
diff --git a/chrome/browser/ssl/ssl_browsertest.cc b/chrome/browser/ssl/ssl_browsertest.cc
index 7b840b3..7c327e9e 100644
--- a/chrome/browser/ssl/ssl_browsertest.cc
+++ b/chrome/browser/ssl/ssl_browsertest.cc
@@ -1119,7 +1119,16 @@
 // Tests that the NavigationEntry gets marked as active mixed content,
 // even if there is a certificate error. Regression test for
 // https://crbug.com/593950.
-IN_PROC_BROWSER_TEST_F(SSLUITest, TestBrokenHTTPSWithActiveInsecureContent) {
+// TODO(crbug.com/1239347): Flaky on Mac.
+#if defined(OS_MAC)
+#define MAYBE_TestBrokenHTTPSWithActiveInsecureContent \
+  DISABLED_TestBrokenHTTPSWithActiveInsecureContent
+#else
+#define MAYBE_TestBrokenHTTPSWithActiveInsecureContent \
+  TestBrokenHTTPSWithActiveInsecureContent
+#endif
+IN_PROC_BROWSER_TEST_F(SSLUITest,
+                       MAYBE_TestBrokenHTTPSWithActiveInsecureContent) {
   ASSERT_TRUE(https_server_expired_.Start());
 
   WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
@@ -3424,15 +3433,11 @@
     : public testing::WithParamInterface<SSLUIWorkerFetchTestType>,
       public SSLUITestBase {
  public:
-  SSLUIWorkerFetchTest() {
-    EXPECT_TRUE(tmp_dir_.CreateUniqueTempDir());
-  }
+  SSLUIWorkerFetchTest() { EXPECT_TRUE(tmp_dir_.CreateUniqueTempDir()); }
 
   ~SSLUIWorkerFetchTest() override {}
 
-  void SetUpOnMainThread() override {
-    SSLUITestBase::SetUpOnMainThread();
-  }
+  void SetUpOnMainThread() override { SSLUITestBase::SetUpOnMainThread(); }
 
   void SetUpCommandLine(base::CommandLine* command_line) override {
     SSLUITestBase::SetUpCommandLine(command_line);
@@ -3722,8 +3727,8 @@
 // with allow_running_insecure_content = true.
 // Flaky. See https://crbug.com/1145674.
 IN_PROC_BROWSER_TEST_P(
-        SSLUIWorkerFetchTest,
-        DISABLED_MixedContentSettings_AllowRunningInsecureContent) {
+    SSLUIWorkerFetchTest,
+    DISABLED_MixedContentSettings_AllowRunningInsecureContent) {
   ChromeContentBrowserClientForMixedContentTest browser_client;
   ScopedContentBrowserClientSetting setting(browser_client);
 
diff --git a/chrome/browser/ssl/stateful_ssl_host_state_delegate_test.cc b/chrome/browser/ssl/stateful_ssl_host_state_delegate_test.cc
index 146868b..5162f6f 100644
--- a/chrome/browser/ssl/stateful_ssl_host_state_delegate_test.cc
+++ b/chrome/browser/ssl/stateful_ssl_host_state_delegate_test.cc
@@ -124,6 +124,38 @@
       state->QueryPolicy(kExampleHost, *cert, net::ERR_CERT_DATE_INVALID, tab));
 }
 
+// Tests the expected behavior of calling IsHttpAllowedForHost on the
+// SSLHostStateDelegate class after various HTTP decisions have been made.
+IN_PROC_BROWSER_TEST_F(StatefulSSLHostStateDelegateTest, HttpAllowlisting) {
+  auto* tab = browser()->tab_strip_model()->GetActiveWebContents();
+  auto* profile = Profile::FromBrowserContext(tab->GetBrowserContext());
+  auto* state = profile->GetSSLHostStateDelegate();
+
+  // Verify that all three of the hosts are not allowlisted before any action
+  // has been taken.
+  EXPECT_FALSE(state->IsHttpAllowedForHost(kWWWGoogleHost, tab));
+  EXPECT_FALSE(state->IsHttpAllowedForHost(kGoogleHost, tab));
+  EXPECT_FALSE(state->IsHttpAllowedForHost(kExampleHost, tab));
+
+  // Simulate a user decision to allow HTTP for kWWWGoogleHost.
+  state->AllowHttpForHost(kWWWGoogleHost, tab);
+
+  // Verify that only kWWWGoogleHost is allowed and that the other two hosts
+  // being tested are not.
+  EXPECT_TRUE(state->IsHttpAllowedForHost(kWWWGoogleHost, tab));
+  EXPECT_FALSE(state->IsHttpAllowedForHost(kGoogleHost, tab));
+  EXPECT_FALSE(state->IsHttpAllowedForHost(kExampleHost, tab));
+
+  // Simulate a user decision to allow HTTP for kExampleHost.
+  state->AllowHttpForHost(kExampleHost, tab);
+
+  // Verify that both kWWWGoogleHost and kExampleHost have allow exceptions
+  // while kGoogleHost still does not.
+  EXPECT_TRUE(state->IsHttpAllowedForHost(kWWWGoogleHost, tab));
+  EXPECT_FALSE(state->IsHttpAllowedForHost(kGoogleHost, tab));
+  EXPECT_TRUE(state->IsHttpAllowedForHost(kExampleHost, tab));
+}
+
 // HasPolicyAndRevoke unit tests the expected behavior of calling
 // HasAllowException before and after calling RevokeUserAllowExceptions on the
 // SSLHostStateDelegate class.
@@ -160,10 +192,21 @@
   EXPECT_FALSE(state->HasAllowException(kGoogleHost, tab));
   state->RevokeUserAllowExceptions(kGoogleHost);
   EXPECT_FALSE(state->HasAllowException(kGoogleHost, tab));
+
+  // Simulate a user decision to allow HTTP for kExampleHost.
+  EXPECT_FALSE(state->IsHttpAllowedForHost(kExampleHost, tab));
+  state->AllowHttpForHost(kExampleHost, tab);
+
+  // Verify that revoking for kExampleHost clears both the cert decision and the
+  // HTTP decision.
+  EXPECT_TRUE(state->HasAllowException(kExampleHost, tab));
+  EXPECT_TRUE(state->IsHttpAllowedForHost(kExampleHost, tab));
+  state->RevokeUserAllowExceptions(kExampleHost);
+  EXPECT_FALSE(state->HasAllowException(kExampleHost, tab));
 }
 
 // Clear unit tests the expected behavior of calling Clear to forget all cert
-// decision state on the SSLHostStateDelegate class.
+// decision state and HTTP allowlist state on the SSLHostStateDelegate class.
 IN_PROC_BROWSER_TEST_F(StatefulSSLHostStateDelegateTest, Clear) {
   scoped_refptr<net::X509Certificate> cert = GetOkCert();
   content::WebContents* tab =
@@ -203,6 +246,32 @@
   EXPECT_EQ(
       content::SSLHostStateDelegate::DENIED,
       state->QueryPolicy(kGoogleHost, *cert, net::ERR_CERT_DATE_INVALID, tab));
+
+  // Simulate a user decision to allow HTTP for kWWWGoogleHost and for
+  // kExampleHost.
+  state->AllowHttpForHost(kWWWGoogleHost, tab);
+  state->AllowHttpForHost(kExampleHost, tab);
+
+  EXPECT_TRUE(state->HasAllowException(kWWWGoogleHost, tab));
+  EXPECT_TRUE(state->HasAllowException(kExampleHost, tab));
+
+  // Clear data for kWWWGoogleHost. kExampleHost will not be modified.
+  state->Clear(base::BindRepeating(&CStrStringMatcher,
+                                   base::Unretained(kWWWGoogleHost)));
+
+  EXPECT_FALSE(state->HasAllowException(kWWWGoogleHost, tab));
+  EXPECT_TRUE(state->HasAllowException(kExampleHost, tab));
+
+  // Do a full clear, then make sure that both kWWWGoogleHost and kExampleHost,
+  // which had a decision made, and kGoogleHost, which was untouched, do not
+  // have HTTP allowlist entries.
+  state->Clear(base::RepeatingCallback<bool(const std::string&)>());
+  EXPECT_FALSE(state->HasAllowException(kWWWGoogleHost, tab));
+  EXPECT_FALSE(state->IsHttpAllowedForHost(kWWWGoogleHost, tab));
+  EXPECT_FALSE(state->HasAllowException(kExampleHost, tab));
+  EXPECT_FALSE(state->IsHttpAllowedForHost(kExampleHost, tab));
+  EXPECT_FALSE(state->HasAllowException(kGoogleHost, tab));
+  EXPECT_FALSE(state->IsHttpAllowedForHost(kGoogleHost, tab));
 }
 
 // DidHostRunInsecureContent unit tests the expected behavior of calling
@@ -508,6 +577,58 @@
                                          incognito_tab));
 }
 
+IN_PROC_BROWSER_TEST_F(IncognitoSSLHostStateDelegateTest,
+                       PRE_AfterRestartHttp) {
+  auto* tab = browser()->tab_strip_model()->GetActiveWebContents();
+  auto* profile = Profile::FromBrowserContext(tab->GetBrowserContext());
+  auto* state = profile->GetSSLHostStateDelegate();
+
+  // Add an HTTP exception to the profile and then verify that it still exists
+  // in the incognito profile.
+  state->AllowHttpForHost(kWWWGoogleHost, tab);
+
+  auto* incognito_browser = CreateIncognitoBrowser(profile);
+  auto* incognito_tab =
+      incognito_browser->tab_strip_model()->GetActiveWebContents();
+
+  auto* incognito = profile->GetPrimaryOTRProfile(/*create_if_needed=*/true);
+  auto* incognito_state = incognito->GetSSLHostStateDelegate();
+  EXPECT_TRUE(
+      incognito_state->IsHttpAllowedForHost(kExampleHost, incognito_tab));
+
+  // Add an HTTP exception to the incognito profile. It will be checked after
+  // restart that this exception does not exist. Note the different host than
+  // above thus mapping to a second exception. Also validate that it was not
+  // added as an exception to the regular profile.
+  incognito_state->AllowHttpForHost(kGoogleHost, incognito_tab);
+  EXPECT_FALSE(state->IsHttpAllowedForHost(kGoogleHost, tab));
+}
+
+// AfterRestartHttp ensures that any HTTP decisions made in an incognito profile
+// are forgetten after a session restart.
+IN_PROC_BROWSER_TEST_F(IncognitoSSLHostStateDelegateTest, AfterRestartHttp) {
+  auto* tab = browser()->tab_strip_model()->GetActiveWebContents();
+  auto* profile = Profile::FromBrowserContext(tab->GetBrowserContext());
+  auto* state = profile->GetSSLHostStateDelegate();
+
+  // Verify that the exception added before restart to the regular
+  // (non-incognito) profile still exists and was not cleared after the
+  // incognito session ended.
+  EXPECT_TRUE(state->IsHttpAllowedForHost(kWWWGoogleHost, tab));
+
+  auto* incognito_browser = CreateIncognitoBrowser(profile);
+  auto* incognito_tab =
+      incognito_browser->tab_strip_model()->GetActiveWebContents();
+
+  auto* incognito = profile->GetPrimaryOTRProfile(/*create_if_needed=*/true);
+  auto* incognito_state = incognito->GetSSLHostStateDelegate();
+
+  // Verify that the exception added before restart to the incognito profile was
+  // cleared when the incognito session ended.
+  EXPECT_FALSE(
+      incognito_state->IsHttpAllowedForHost(kGoogleHost, incognito_tab));
+}
+
 // Tests the default certificate memory, which is one week.
 class DefaultMemorySSLHostStateDelegateTest
     : public StatefulSSLHostStateDelegateTest {};
@@ -543,7 +664,7 @@
   clock->SetNow(base::Time::NowFromSystemTime());
 
   // This should only pass if the cert was allowed before the test was restart
-  // and thus has now been rememebered across browser restarts.
+  // and thus has now been remembered across browser restarts.
   EXPECT_EQ(content::SSLHostStateDelegate::ALLOWED,
             state->QueryPolicy(kWWWGoogleHost, *cert,
                                net::ERR_CERT_DATE_INVALID, tab));
@@ -569,10 +690,56 @@
                                net::ERR_CERT_DATE_INVALID, tab));
 }
 
+IN_PROC_BROWSER_TEST_F(DefaultMemorySSLHostStateDelegateTest,
+                       PRE_AfterRestartHttp) {
+  auto* tab = browser()->tab_strip_model()->GetActiveWebContents();
+  auto* profile = Profile::FromBrowserContext(tab->GetBrowserContext());
+  auto* state = profile->GetSSLHostStateDelegate();
+
+  state->AllowHttpForHost(kWWWGoogleHost, tab);
+  EXPECT_TRUE(state->IsHttpAllowedForHost(kWWWGoogleHost, tab));
+}
+
+IN_PROC_BROWSER_TEST_F(DefaultMemorySSLHostStateDelegateTest,
+                       AfterRestartHttp) {
+  auto* tab = browser()->tab_strip_model()->GetActiveWebContents();
+  auto* profile = Profile::FromBrowserContext(tab->GetBrowserContext());
+  auto* state = profile->GetSSLHostStateDelegate();
+
+  // `chrome_state` takes ownership of this clock.
+  auto clock = std::make_unique<base::SimpleTestClock>();
+  auto* clock_ptr = clock.get();
+  auto* chrome_state = static_cast<StatefulSSLHostStateDelegate*>(state);
+  chrome_state->SetClockForTesting(std::move(clock));
+
+  // Start the clock at standard system time.
+  clock_ptr->SetNow(base::Time::NowFromSystemTime());
+
+  // This should only pass if HTTP was allowed before the test was restarted
+  // and thus has now been remembered across browser restarts.
+  EXPECT_TRUE(state->IsHttpAllowedForHost(kWWWGoogleHost, tab));
+
+  // Simulate the clock advancing by one day, which is less than the expiration
+  // length.
+  clock_ptr->Advance(base::TimeDelta::FromSeconds(kDeltaOneDayInSeconds + 1));
+
+  // HTTP should still be allowed because the default expiration length
+  // has not passed yet.
+  EXPECT_TRUE(state->IsHttpAllowedForHost(kWWWGoogleHost, tab));
+
+  // Now simulate the clock advancing by one week, which is past the expiration
+  // point.
+  clock_ptr->Advance(base::TimeDelta::FromSeconds(kDeltaOneWeekInSeconds -
+                                                  kDeltaOneDayInSeconds + 1));
+
+  // HTTP should no longer be allowed because the specified delta has passed.
+  EXPECT_FALSE(state->IsHttpAllowedForHost(kWWWGoogleHost, tab));
+}
+
 // The same test as StatefulSSLHostStateDelegateTest.QueryPolicyExpired but now
 // applied to a browser context that expires based on time, not restart. This
 // unit tests to make sure that if a certificate decision has expired, the
-// return value from QueryPolicy returns the correct vaule.
+// return value from QueryPolicy returns the correct value.
 IN_PROC_BROWSER_TEST_F(DefaultMemorySSLHostStateDelegateTest,
                        QueryPolicyExpired) {
   scoped_refptr<net::X509Certificate> cert = GetOkCert();
@@ -612,6 +779,39 @@
                                net::ERR_CERT_DATE_INVALID, tab));
 }
 
+// Tests that if an HTTP allowlist decision has expired, then the return value
+// from IsHttpAllowedForHost returns false. Similar to the test
+// DefaultMemorySSLHostStateDelegateTest.AfterRestartHttp but does not involve
+// restarting the browser.
+IN_PROC_BROWSER_TEST_F(DefaultMemorySSLHostStateDelegateTest,
+                       HttpDecisionExpires) {
+  auto* tab = browser()->tab_strip_model()->GetActiveWebContents();
+  auto* profile = Profile::FromBrowserContext(tab->GetBrowserContext());
+  auto* state = profile->GetSSLHostStateDelegate();
+
+  // `chrome_state` takes ownership of this clock.
+  auto clock = std::make_unique<base::SimpleTestClock>();
+  auto* clock_ptr = clock.get();
+  auto* chrome_state = static_cast<StatefulSSLHostStateDelegate*>(state);
+  chrome_state->SetClockForTesting(std::move(clock));
+
+  // Start the clock at standard system time.
+  clock_ptr->SetNow(base::Time::NowFromSystemTime());
+
+  // The host has never been seen before, so it should not be allowlisted.
+  EXPECT_FALSE(state->IsHttpAllowedForHost(kWWWGoogleHost, tab));
+
+  // Allowlist HTTP for the host.
+  state->AllowHttpForHost(kWWWGoogleHost, tab);
+  EXPECT_TRUE(state->IsHttpAllowedForHost(kWWWGoogleHost, tab));
+
+  // Simulate the clock advancing by one week, the default expiration time.
+  clock_ptr->Advance(base::TimeDelta::FromSeconds(kDeltaOneWeekInSeconds + 1));
+
+  // The decision expiration time has come, so this should now return false.
+  EXPECT_FALSE(state->IsHttpAllowedForHost(kWWWGoogleHost, tab));
+}
+
 // Tests to make sure that if the user deletes their browser history, SSL
 // exceptions will be deleted as well.
 class RemoveBrowsingHistorySSLHostStateDelegateTest
@@ -649,6 +849,19 @@
       state->QueryPolicy(kGoogleHost, *cert, net::ERR_CERT_DATE_INVALID, tab));
 }
 
+IN_PROC_BROWSER_TEST_F(RemoveBrowsingHistorySSLHostStateDelegateTest,
+                       DeleteHistoryClearsHttpAllowlistDecision) {
+  auto* tab = browser()->tab_strip_model()->GetActiveWebContents();
+  auto* profile = Profile::FromBrowserContext(tab->GetBrowserContext());
+  auto* state = profile->GetSSLHostStateDelegate();
+
+  // Add an exception for HTTP on this host. Then remove the last hour's worth
+  // of browsing history and verify that the exception has been deleted.
+  state->AllowHttpForHost(kExampleHost, tab);
+  RemoveAndWait(profile);
+  EXPECT_FALSE(state->IsHttpAllowedForHost(kExampleHost, tab));
+}
+
 // Tests to make sure that localhost certificate errors are treated as
 // normal errors or ignored, depending on whether the
 // kAllowInsecureLocalhost flag is set.
@@ -737,3 +950,45 @@
                                net::ERR_CERT_DATE_INVALID, tab));
   EXPECT_FALSE(state->HasAllowException(kWWWGoogleHost, tab));
 }
+
+// Tests that HTTP warning decisions are isolated by storage partition. In
+// particular, clicking through an HTTP warning in a <webview> in a Chrome
+// App shouldn't affect normal browsing.
+IN_PROC_BROWSER_TEST_F(StatefulSSLHostStateDelegateExtensionTest,
+                       StoragePartitionIsolationHttp) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+
+  // Launch a Chrome app.
+  content::WebContents* tab =
+      browser()->tab_strip_model()->GetActiveWebContents();
+  Profile* profile = Profile::FromBrowserContext(tab->GetBrowserContext());
+  extensions::ChromeTestExtensionLoader loader(profile);
+  const extensions::Extension* app =
+      LoadAndLaunchApp(test_data_dir_.AppendASCII("platform_apps")
+                           .AppendASCII("web_view")
+                           .AppendASCII("simple"));
+  ASSERT_TRUE(app);
+  auto app_windows =
+      extensions::AppWindowRegistry::Get(profile)->GetAppWindowsForApp(
+          app->id());
+  ASSERT_EQ(1u, app_windows.size());
+  // Wait for the app's guest WebContents to load.
+  guest_view::TestGuestViewManager* guest_manager =
+      static_cast<guest_view::TestGuestViewManager*>(
+          guest_view::TestGuestViewManager::FromBrowserContext(profile));
+  content::WebContents* guest = guest_manager->WaitForSingleGuestCreated();
+  guest_manager->WaitUntilAttached(guest);
+
+  // Store an HTTP exception for the guest.
+  content::SSLHostStateDelegate* state = profile->GetSSLHostStateDelegate();
+  state->AllowHttpForHost(kWWWGoogleHost, guest);
+  EXPECT_TRUE(state->IsHttpAllowedForHost(kWWWGoogleHost, guest));
+  EXPECT_TRUE(state->HasAllowException(kWWWGoogleHost, guest));
+
+  // Navigate to a non-app page and test that the exception is not carried over.
+  ui_test_utils::NavigateToURL(
+      browser(), embedded_test_server()->GetURL(
+                     "/extensions/isolated_apps/non_app/main.html"));
+  EXPECT_FALSE(state->IsHttpAllowedForHost(kWWWGoogleHost, tab));
+  EXPECT_FALSE(state->HasAllowException(kWWWGoogleHost, tab));
+}
diff --git a/chrome/browser/ui/android/toolbar/java/res/drawable-v21/ntp_search_box.xml b/chrome/browser/ui/android/toolbar/java/res/drawable-v21/ntp_search_box.xml
index 041bf2a..e3cbd2a 100644
--- a/chrome/browser/ui/android/toolbar/java/res/drawable-v21/ntp_search_box.xml
+++ b/chrome/browser/ui/android/toolbar/java/res/drawable-v21/ntp_search_box.xml
@@ -8,6 +8,7 @@
     android:color="@color/modern_grey_400" >
 
     <item
+        android:id="@+id/fake_search_box_bg_shape"
         android:drawable="@drawable/modern_toolbar_text_box_background"/>
 
 </ripple>
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java
index 283be0f..98d17317 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java
@@ -16,6 +16,7 @@
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Point;
+import android.graphics.PorterDuff;
 import android.graphics.Rect;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
@@ -392,7 +393,10 @@
         Drawable drawable = context.getDrawable(
                 R.drawable.modern_toolbar_text_box_background_with_primary_color);
         drawable.mutate();
-        drawable.setTint(ChromeColors.getSurfaceColor(context, R.dimen.toolbar_text_box_elevation));
+        final int tint = ChromeColors.getSurfaceColor(context, R.dimen.toolbar_text_box_elevation);
+        // TODO(https://crbug.com/1239289): Change back to #setTint once our min API level is 23.
+        drawable.setColorFilter(tint, PorterDuff.Mode.SRC_IN);
+
         return drawable;
     }
 
@@ -402,7 +406,8 @@
     private void updateModernLocationBarColor(int color) {
         if (mCurrentLocationBarColor == color) return;
         mCurrentLocationBarColor = color;
-        mLocationBarBackground.setTint(color);
+        // TODO(https://crbug.com/1239289): Change back to #setTint once our min API level is 23.
+        mLocationBarBackground.setColorFilter(color, PorterDuff.Mode.SRC_IN);
     }
 
     /**
diff --git a/chrome/browser/ui/ash/holding_space/holding_space_keyed_service.cc b/chrome/browser/ui/ash/holding_space/holding_space_keyed_service.cc
index 257248c..554a7edb 100644
--- a/chrome/browser/ui/ash/holding_space/holding_space_keyed_service.cc
+++ b/chrome/browser/ui/ash/holding_space/holding_space_keyed_service.cc
@@ -310,20 +310,35 @@
 
 void HoldingSpaceKeyedService::CancelItem(const HoldingSpaceItem* item) {
   // Currently it is only possible to cancel download type items.
-  if (HoldingSpaceItem::IsDownload(item->type()) && downloads_delegate_)
-    downloads_delegate_->Cancel(item);
+  if (!HoldingSpaceItem::IsDownload(item->type()) || !downloads_delegate_)
+    return;
+
+  holding_space_metrics::RecordItemAction(
+      {item}, holding_space_metrics::ItemAction::kCancel);
+
+  downloads_delegate_->Cancel(item);
 }
 
 void HoldingSpaceKeyedService::PauseItem(const HoldingSpaceItem* item) {
   // Currently it is only possible to pause download type items.
-  if (HoldingSpaceItem::IsDownload(item->type()) && downloads_delegate_)
-    downloads_delegate_->Pause(item);
+  if (!HoldingSpaceItem::IsDownload(item->type()) || !downloads_delegate_)
+    return;
+
+  holding_space_metrics::RecordItemAction(
+      {item}, holding_space_metrics::ItemAction::kPause);
+
+  downloads_delegate_->Pause(item);
 }
 
 void HoldingSpaceKeyedService::ResumeItem(const HoldingSpaceItem* item) {
   // Currently it is only possible to resume download type items.
-  if (HoldingSpaceItem::IsDownload(item->type()) && downloads_delegate_)
-    downloads_delegate_->Resume(item);
+  if (!HoldingSpaceItem::IsDownload(item->type()) || !downloads_delegate_)
+    return;
+
+  holding_space_metrics::RecordItemAction(
+      {item}, holding_space_metrics::ItemAction::kResume);
+
+  downloads_delegate_->Resume(item);
 }
 
 bool HoldingSpaceKeyedService::OpenItemWhenComplete(
diff --git a/chrome/browser/ui/search_engines/template_url_table_model.cc b/chrome/browser/ui/search_engines/template_url_table_model.cc
index 9f5a55e..64e6f20a 100644
--- a/chrome/browser/ui/search_engines/template_url_table_model.cc
+++ b/chrome/browser/ui/search_engines/template_url_table_model.cc
@@ -43,11 +43,8 @@
     } else if (template_url->type() == TemplateURL::OMNIBOX_API_EXTENSION) {
       extension_entries.push_back(template_url);
     } else if (OmniboxFieldTrial::IsActiveSearchEnginesEnabled() &&
-               (!template_url->safe_for_autoreplace() ||
-                template_url->usage_count() > 0)) {
-      // An entry is "active" if it has ever been used or manually
-      // added/modified. |safe_for_autoreplace| is false if the entry has been
-      // modified.
+               (template_url->is_active() ==
+                TemplateURLData::ActiveStatus::kTrue)) {
       active_entries.push_back(template_url);
     } else {
       other_entries.push_back(template_url);
diff --git a/chrome/browser/ui/tab_helpers.cc b/chrome/browser/ui/tab_helpers.cc
index a8b5f69..a9b64e23 100644
--- a/chrome/browser/ui/tab_helpers.cc
+++ b/chrome/browser/ui/tab_helpers.cc
@@ -104,7 +104,7 @@
 #include "components/infobars/content/content_infobar_manager.h"
 #include "components/javascript_dialogs/tab_modal_dialog_manager.h"
 #include "components/offline_pages/buildflags/buildflags.h"
-#include "components/optimization_guide/content/browser/page_content_annotations_web_contents_helper.h"
+#include "components/optimization_guide/content/browser/page_content_annotations_web_contents_observer.h"
 #include "components/optimization_guide/core/optimization_guide_features.h"
 #include "components/password_manager/core/browser/password_manager.h"
 #include "components/performance_manager/public/decorators/tab_properties_decorator.h"
@@ -293,7 +293,7 @@
       page_content_annotations_service =
           PageContentAnnotationsServiceFactory::GetForProfile(profile);
   if (page_content_annotations_service) {
-    optimization_guide::PageContentAnnotationsWebContentsHelper::
+    optimization_guide::PageContentAnnotationsWebContentsObserver::
         CreateForWebContents(web_contents, page_content_annotations_service);
   }
   OutOfMemoryReporter::CreateForWebContents(web_contents);
diff --git a/chrome/browser/ui/views/global_media_controls/media_notification_footer_view.cc b/chrome/browser/ui/views/global_media_controls/media_notification_footer_view.cc
index fda47a0..27089a4 100644
--- a/chrome/browser/ui/views/global_media_controls/media_notification_footer_view.cc
+++ b/chrome/browser/ui/views/global_media_controls/media_notification_footer_view.cc
@@ -150,8 +150,10 @@
 }
 
 void MediaNotificationFooterView::Layout() {
-  if (!overflow_button_)
+  if (!overflow_button_) {
+    views::View::Layout();
     return;
+  }
 
   overflow_button_->SetVisible(false);
   if (GetPreferredSize().width() > GetContentsBounds().width())
diff --git a/chrome/browser/ui/views/page_info/page_info_bubble_view.cc b/chrome/browser/ui/views/page_info/page_info_bubble_view.cc
index 71967fc..5e6642a 100644
--- a/chrome/browser/ui/views/page_info/page_info_bubble_view.cc
+++ b/chrome/browser/ui/views/page_info/page_info_bubble_view.cc
@@ -542,14 +542,17 @@
       ->SetEnabledColor(views::style::GetColor(
           *this, views::style::CONTEXT_DIALOG_TITLE, text_style));
 
+  // The "re-enable warnings" button is shown if the user has bypassed SSL
+  // error interstitials or HTTP warning interstitials (with HTTPS-First Mode
+  // enabled) on this site.
+  if (identity_info.show_ssl_decision_revoke_button) {
+    header_->AddResetDecisionsLabel(base::BindRepeating(
+        &PageInfoBubbleView::ResetDecisionsClicked, base::Unretained(this)));
+  }
+
   if (identity_info.certificate) {
     certificate_ = identity_info.certificate;
 
-    if (identity_info.show_ssl_decision_revoke_button) {
-      header_->AddResetDecisionsLabel(base::BindRepeating(
-          &PageInfoBubbleView::ResetDecisionsClicked, base::Unretained(this)));
-    }
-
     // Show information about the page's certificate.
     // The text of link to the Certificate Viewer varies depending on the
     // validity of the Certificate.
diff --git a/chrome/browser/ui/views/page_info/page_info_security_content_view.cc b/chrome/browser/ui/views/page_info/page_info_security_content_view.cc
index cb11ff1..0562fde8 100644
--- a/chrome/browser/ui/views/page_info/page_info_security_content_view.cc
+++ b/chrome/browser/ui/views/page_info/page_info_security_content_view.cc
@@ -48,15 +48,19 @@
       security_description->details,
       base::BindRepeating(&PageInfoSecurityContentView::SecurityDetailsClicked,
                           base::Unretained(this)));
+
+  // The "re-enable warnings" button is shown if the user has bypassed SSL
+  // error interstitials or HTTP warning interstitials (with HTTPS-First Mode
+  // enabled) on this site.
+  if (identity_info.show_ssl_decision_revoke_button) {
+    security_view_->AddResetDecisionsLabel(
+        base::BindRepeating(&PageInfoSecurityContentView::ResetDecisionsClicked,
+                            base::Unretained(this)));
+  }
+
   if (identity_info.certificate) {
     certificate_ = identity_info.certificate;
 
-    if (identity_info.show_ssl_decision_revoke_button) {
-      security_view_->AddResetDecisionsLabel(base::BindRepeating(
-          &PageInfoSecurityContentView::ResetDecisionsClicked,
-          base::Unretained(this)));
-    }
-
     // Show information about the page's certificate.
     // The text of link to the Certificate Viewer varies depending on the
     // validity of the Certificate.
diff --git a/chrome/browser/ui/views/sharing_hub/sharing_hub_bubble_view_impl.cc b/chrome/browser/ui/views/sharing_hub/sharing_hub_bubble_view_impl.cc
index d561e0e..e447e6c 100644
--- a/chrome/browser/ui/views/sharing_hub/sharing_hub_bubble_view_impl.cc
+++ b/chrome/browser/ui/views/sharing_hub/sharing_hub_bubble_view_impl.cc
@@ -79,6 +79,10 @@
   }
 }
 
+std::u16string SharingHubBubbleViewImpl::GetAccessibleWindowTitle() const {
+  return l10n_util::GetStringUTF16(IDS_SHARING_HUB_TOOLTIP);
+}
+
 void SharingHubBubbleViewImpl::OnPaint(gfx::Canvas* canvas) {
   views::BubbleDialogDelegateView::OnPaint(canvas);
 }
diff --git a/chrome/browser/ui/views/sharing_hub/sharing_hub_bubble_view_impl.h b/chrome/browser/ui/views/sharing_hub/sharing_hub_bubble_view_impl.h
index 342b7f52..1e177e25 100644
--- a/chrome/browser/ui/views/sharing_hub/sharing_hub_bubble_view_impl.h
+++ b/chrome/browser/ui/views/sharing_hub/sharing_hub_bubble_view_impl.h
@@ -44,6 +44,7 @@
   void WindowClosing() override;
 
   // LocationBarBubbleDelegateView:
+  std::u16string GetAccessibleWindowTitle() const override;
   void OnPaint(gfx::Canvas* canvas) override;
 
   // Shows the bubble view.
diff --git a/chrome/browser/ui/views/tabs/tab_hover_card_bubble_view.cc b/chrome/browser/ui/views/tabs/tab_hover_card_bubble_view.cc
index dec8eca1..3f77727 100644
--- a/chrome/browser/ui/views/tabs/tab_hover_card_bubble_view.cc
+++ b/chrome/browser/ui/views/tabs/tab_hover_card_bubble_view.cc
@@ -5,11 +5,13 @@
 #include "chrome/browser/ui/views/tabs/tab_hover_card_bubble_view.h"
 
 #include <algorithm>
+#include <ios>
 #include <memory>
 #include <string>
 
 #include "base/containers/mru_cache.h"
-#include "base/cxx17_backports.h"
+#include "base/i18n/break_iterator.h"
+#include "base/i18n/char_iterator.h"
 #include "base/metrics/field_trial_params.h"
 #include "base/metrics/histogram_macros.h"
 #include "build/build_config.h"
@@ -39,7 +41,10 @@
 #include "ui/gfx/geometry/rounded_corners_f.h"
 #include "ui/gfx/image/image_skia.h"
 #include "ui/gfx/paint_vector_icon.h"
+#include "ui/gfx/selection_model.h"
 #include "ui/gfx/text_constants.h"
+#include "ui/gfx/text_elider.h"
+#include "ui/gfx/text_utils.h"
 #include "ui/native_theme/native_theme.h"
 #include "ui/resources/grit/ui_resources.h"
 #include "ui/views/accessibility/view_accessibility.h"
@@ -60,6 +65,7 @@
 #endif
 
 namespace {
+
 // Maximum number of lines that a title label occupies.
 constexpr int kHoverCardTitleMaxLines = 2;
 
@@ -143,8 +149,197 @@
 BEGIN_METADATA(SolidLabel, views::Label)
 END_METADATA
 
+// Label that exposes the CreateRenderText() method, so that we can use
+// TabHoverCardBubbleView::FilenameElider to do a two-line elision of
+// filenames.
+class RenderTextFactoryLabel : public views::Label {
+ public:
+  using Label::CreateRenderText;
+  using Label::Label;
+};
+
 }  // namespace
 
+// TabHoverCardBubbleView::FilenameElider:
+// ----------------------------------------------------------
+
+TabHoverCardBubbleView::FilenameElider::FilenameElider(
+    std::unique_ptr<gfx::RenderText> render_text)
+    : render_text_(std::move(render_text)) {}
+
+TabHoverCardBubbleView::FilenameElider::~FilenameElider() = default;
+
+std::u16string TabHoverCardBubbleView::FilenameElider::Elide(
+    const std::u16string& text,
+    const gfx::Rect& display_rect) const {
+  render_text_->SetText(text);
+  return ElideImpl(GetLineLengths(display_rect));
+}
+
+TabHoverCardBubbleView::FilenameElider::LineLengths
+TabHoverCardBubbleView::FilenameElider::GetLineLengths(
+    const gfx::Rect& display_rect) const {
+  const std::u16string text = render_text_->text();
+  render_text_->SetMaxLines(0);
+  render_text_->SetMultiline(false);
+  render_text_->SetWhitespaceElision(true);
+  render_text_->SetDisplayRect(display_rect);
+
+  // Set our temporary RenderText to the unelided text and elide the start of
+  // the string to give us a guess at where the second line of the label
+  // should start.
+  render_text_->SetElideBehavior(gfx::ElideBehavior::ELIDE_HEAD);
+  const std::u16string tentative_second_line = render_text_->GetDisplayText();
+
+  // If there is no elision, then the text will fit on a single line and
+  // there's nothing to do.
+  if (tentative_second_line == text)
+    return LineLengths(text.length(), text.length());
+
+  // If there's not enough space to display even a single character, there is
+  // also nothing to do; the result needs to be empty.
+  if (tentative_second_line.empty())
+    return LineLengths(0, 0);
+
+  LineLengths result;
+
+  // Since we truncated, expect the string to start with ellipsis, then
+  // calculate the length of the string sans ellipsis.
+  DCHECK_EQ(gfx::kEllipsisUTF16[0], tentative_second_line[0]);
+
+  // Elision is still a little flaky, so we'll make sure we didn't stop in the
+  // middle of a grapheme. The +1 is to move past the ellipsis which is not
+  // part of the original string.
+  size_t pos = text.length() - tentative_second_line.length() + 1;
+  if (!render_text_->IsGraphemeBoundary(pos))
+    pos = render_text_->IndexOfAdjacentGrapheme(pos, gfx::CURSOR_FORWARD);
+  result.second = text.length() - pos;
+
+  // Calculate the first line by aggressively truncating the text. This may
+  // cut the string somewhere other than a word boundary, but for very long
+  // filenames, it's probably best to fit as much of the name on the card as
+  // possible, even if we sacrifice a small amount of readability.
+  render_text_->SetElideBehavior(gfx::ElideBehavior::TRUNCATE);
+  result.first = render_text_->GetDisplayText().length();
+
+  // Handle the case where we ended up in the middle of a grapheme.
+  if (!render_text_->IsGraphemeBoundary(result.first)) {
+    result.first = render_text_->IndexOfAdjacentGrapheme(result.first,
+                                                         gfx::CURSOR_BACKWARD);
+  }
+
+  return result;
+}
+
+std::u16string TabHoverCardBubbleView::FilenameElider::ElideImpl(
+    TabHoverCardBubbleView::FilenameElider::LineLengths line_lengths) const {
+  const std::u16string& text = render_text_->text();
+
+  // Validate the inputs. All of these are base assumptions.
+  DCHECK_LE(line_lengths.first, text.length());
+  DCHECK_LE(line_lengths.second, text.length());
+  DCHECK(render_text_->IsGraphemeBoundary(line_lengths.first));
+  DCHECK(render_text_->IsGraphemeBoundary(text.length() - line_lengths.second));
+
+  // If the entire text fits on a single line, use it as-is.
+  if (line_lengths.first == text.length() ||
+      line_lengths.second == text.length()) {
+    return text;
+  }
+
+  // If no characters will fit on one of the lines, return an empty string.
+  if (line_lengths.first == 0 || line_lengths.second == 0)
+    return std::u16string();
+
+  // Let's figure out where to actually start the second line. Strings that
+  // are too long for one line but fit on two lines tend to create some
+  // overlap between the first and second line, so take the maximum of the
+  // second line cut and the end of the first line.
+  const size_t second_line_cut = text.length() - line_lengths.second;
+  size_t cut_point = std::max(second_line_cut, line_lengths.first);
+
+  // We got the whole line if the cut point is the character immediately
+  // after the first line cuts off (otherwise we've truncated and need to
+  // show an ellipsis in the final string).
+  const bool is_whole_string = (cut_point == line_lengths.first);
+
+  // If we got the whole thing and there is a file extension and the dot
+  // separating the extension from the rest of the filename is in the
+  // overlapped region (i.e. the region that could go on either the first or
+  // second line) then we should preferably put the line break immediately
+  // before the extension. This solves some rendering problems when the
+  // filename is in an RTL language but the extension is in Latin characters.
+  // (We'll actually catch the case where the lines exactly match up as well
+  // because it will result in better formatting later.)
+  bool adjusted_cut_point = false;
+  if (is_whole_string && cut_point >= second_line_cut) {
+    // Note that this could theoretically cause a problem if there is a
+    // combining diacritic with the dot that precedes it but that's not
+    // typically how filenames work.
+    const size_t dot_pos = text.find_last_of(u'.');
+    if (dot_pos != std::u16string::npos && dot_pos >= second_line_cut &&
+        dot_pos <= cut_point) {
+      cut_point = dot_pos;
+      adjusted_cut_point = true;
+    }
+  }
+
+  // TODO(dfried): possibly handle the case where we chop a section with bidi
+  // delimiters out or split it between lines.
+
+  // If we didn't put the extension on its own line, eliminate whitespace
+  // from the start of the second line (it looks weird).
+  if (!adjusted_cut_point) {
+    cut_point =
+        gfx::FindValidBoundaryAfter(text, cut_point, /*trim_whitespace =*/true);
+  }
+
+  // Reassemble the string. Start with the first line up to `cut_point` or the
+  // end of the line, whichever comes sooner.
+  std::u16string result =
+      text.substr(0, std::min(line_lengths.first, cut_point));
+  result.push_back(u'\n');
+
+  // If we're starting the second line with a file extension hint that the
+  // directionality of the text might change by using an FSI mark. Allowing
+  // the renderer to re-infer RTL-ness produces much better results in text
+  // rendering when an RTL filename has an ASCII extension.
+  //
+  // TODO(dfried): Currently we do put an FSI before an ellipsis; this
+  // results in the ellipsis being placed with the text that immediately
+  // follows it (making the point of elision more obvious). If the text
+  // following the cut is LTR it goes on the left, and if the text is RTL it
+  // goes on the right. Reconsider if/how we should set text direction
+  // following an ellipsis:
+  // - No FSI would cause the ellipsis to align with the preceding rather
+  //   than the following text. It would provide a bit more visual continuity
+  //   between lines, but might be confusing as to where the text picks back
+  //   up (as the next character might be on the opposite side of the line).
+  // - We could preserve elided directionality markers, but they could end up
+  //   aligning the ellipsis with text that is not present at all on the
+  //   label.
+  // - We could also force direction to match the start of the first line for
+  //   consistency but that could result in an ellipsis that matches neither
+  //   the preceding nor following text.
+  //
+  // TODO(dfried): move these declarations to rtl.h alongside e.g.
+  // base::i18n::kRightToLeftMark
+  constexpr char16_t kFirstStrongIsolateMark = u'\u2068';
+  constexpr char16_t kPopDirectionalIsolateMark = u'\u2069';
+  if (adjusted_cut_point || !is_whole_string)
+    result += kFirstStrongIsolateMark;
+  if (!is_whole_string)
+    result.push_back(gfx::kEllipsisUTF16[0]);
+  result.append(text.substr(cut_point));
+  // If we added an FSI, we should bracket it with a PDI.
+  if (adjusted_cut_point || !is_whole_string)
+    result += kPopDirectionalIsolateMark;
+  return result;
+}
+
+// TabHoverCardBubbleView::FadeLabel:
+// ----------------------------------------------------------
+
 // This view overlays and fades out an old version of the text of a label,
 // while displaying the new text underneath. It is used to fade out the old
 // value of the title and domain labels on the hover card when the tab switches
@@ -152,7 +347,7 @@
 class TabHoverCardBubbleView::FadeLabel : public views::View {
  public:
   FadeLabel(int context, int num_lines) {
-    primary_label_ = AddChildView(std::make_unique<views::Label>(
+    primary_label_ = AddChildView(std::make_unique<RenderTextFactoryLabel>(
         std::u16string(), context, views::style::STYLE_PRIMARY));
     primary_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
     primary_label_->SetVerticalAlignment(gfx::ALIGN_TOP);
@@ -201,6 +396,14 @@
 
   std::u16string GetText() const { return primary_label_->GetText(); }
 
+  // Returns a version of the text that's middle-elided on two lines.
+  std::u16string TruncateFilenameToTwoLines(const std::u16string& text) const {
+    FilenameElider elider(primary_label_->CreateRenderText());
+    gfx::Rect text_rect = primary_label_->GetContentsBounds();
+    text_rect.Inset(-gfx::ShadowValue::GetMargin(primary_label_->GetShadows()));
+    return elider.Elide(text, text_rect);
+  }
+
  protected:
   // views::View:
   gfx::Size GetMaximumSize() const override {
@@ -223,21 +426,18 @@
 
  private:
   static void SetMultilineParams(views::Label* label, bool is_filename) {
-    if (is_filename) {
-      label->SetMultiLine(false);
-      label->SetElideBehavior(gfx::ELIDE_MIDDLE);
-    } else {
-      label->SetElideBehavior(gfx::ELIDE_TAIL);
-      label->SetMultiLine(true);
-    }
+    label->SetElideBehavior(is_filename ? gfx::NO_ELIDE : gfx::ELIDE_TAIL);
   }
 
-  views::Label* primary_label_;
+  RenderTextFactoryLabel* primary_label_;
   SolidLabel* label_fading_out_;
   absl::optional<bool> was_filename_;
   double percent_ = 1.0;
 };
 
+// TabHoverCardBubbleView::ThumbnailView:
+// ----------------------------------------------------------
+
 // Represents the preview image on the hover card. Allows for a new image to be
 // faded in over the old image.
 class TabHoverCardBubbleView::ThumbnailView
@@ -468,6 +668,9 @@
   ImageType image_type_ = ImageType::kNone;
 };
 
+// TabHoverCardBubbleView:
+// ----------------------------------------------------------
+
 // static
 constexpr base::TimeDelta TabHoverCardBubbleView::kHoverCardSlideDuration;
 
@@ -621,6 +824,7 @@
   bool is_filename = false;
   if (domain_url.SchemeIsFile()) {
     is_filename = true;
+    title = title_label_->TruncateFilenameToTwoLines(title);
     domain = l10n_util::GetStringUTF16(IDS_HOVER_CARD_FILE_URL_SOURCE);
   } else {
     if (domain_url.SchemeIsBlob()) {
diff --git a/chrome/browser/ui/views/tabs/tab_hover_card_bubble_view.h b/chrome/browser/ui/views/tabs/tab_hover_card_bubble_view.h
index 87052f25..78a28ea 100644
--- a/chrome/browser/ui/views/tabs/tab_hover_card_bubble_view.h
+++ b/chrome/browser/ui/views/tabs/tab_hover_card_bubble_view.h
@@ -5,7 +5,10 @@
 #ifndef CHROME_BROWSER_UI_VIEWS_TABS_TAB_HOVER_CARD_BUBBLE_VIEW_H_
 #define CHROME_BROWSER_UI_VIEWS_TABS_TAB_HOVER_CARD_BUBBLE_VIEW_H_
 
+#include <memory>
 #include <string>
+#include <utility>
+
 #include "base/callback_list.h"
 #include "base/scoped_observation.h"
 #include "base/time/time.h"
@@ -23,6 +26,8 @@
 
 namespace gfx {
 class ImageSkia;
+class Rect;
+class RenderText;
 }
 
 class Tab;
@@ -33,6 +38,64 @@
   static constexpr base::TimeDelta kHoverCardSlideDuration =
       base::TimeDelta::FromMilliseconds(200);
 
+  // Helper class used to elide local filenames with a RenderText object that
+  // is provided with the correct setup and formatting.
+  class FilenameElider {
+   public:
+    using LineLengths = std::pair<size_t, size_t>;
+
+    explicit FilenameElider(std::unique_ptr<gfx::RenderText> render_text);
+    ~FilenameElider();
+
+    // Returns the elided text. Equivalent to:
+    //   Elide(GetLineLengths(display_rect))
+    // See those methods for a detailed description.
+    std::u16string Elide(const std::u16string& text,
+                         const gfx::Rect& display_rect) const;
+
+   private:
+    friend class TabHoverCardBubbleViewFilenameEliderTest;
+
+    // Given the current text and a rectangle to display text in, returns the
+    // maximum length in characters of the first and second lines.
+    //
+    // The first value is the number of characters from the beginning of the
+    // text that will fit on the line. The second value is the number of
+    // characters from the end of the text that will fit on a line, minus
+    // enough space to insert an ellipsis.
+    //
+    // Note that the sum of the two values may be greater than the length of
+    // the text. Both segments are guaranteed to end at grapheme boundaries.
+    LineLengths GetLineLengths(const gfx::Rect& display_rect) const;
+
+    // Returns a string formatted for two-line elision given the last string
+    // passed to SetText() and the maximum extent of the first and second
+    // lines. The resulting string will either be the original text (if it fits
+    // on one line) or the first line, followed by a newline, an ellipsis, and
+    // the second line. The cut points passed in must be at grapheme
+    // boundaries.
+    //
+    // If the two lines overlap (that is, if the line lengths sum to more than
+    // the length of the original text), an optimum breakpoint will be chosen
+    // to insert the newline:
+    //  * If possible, the extension (and if it's an image, the image
+    //    dimensions) will be placed alone on the second line.
+    //  * Otherwise, as many characters as possible will be placed on the first
+    //    line.
+    // TODO(dfried): consider optimizing to break at natural breaks: spaces,
+    // punctuation, etc.
+    //
+    // Note that if the extension is isolated on the second line or an ellipsis
+    // is inserted, the second line will be marked as a bidirectional isolate,
+    // so that its direction is determined by the leading text on the line
+    // rather than whatever is "left over" from the first line. We find this
+    // produces a much more visually appealing and less confusing result than
+    // inheriting the preceding directionality.
+    std::u16string ElideImpl(LineLengths line_lengths) const;
+
+    std::unique_ptr<gfx::RenderText> render_text_;
+  };
+
   METADATA_HEADER(TabHoverCardBubbleView);
   explicit TabHoverCardBubbleView(Tab* tab);
   TabHoverCardBubbleView(const TabHoverCardBubbleView&) = delete;
diff --git a/chrome/browser/ui/views/tabs/tab_hover_card_bubble_view_unittest.cc b/chrome/browser/ui/views/tabs/tab_hover_card_bubble_view_unittest.cc
new file mode 100644
index 0000000..333526af8
--- /dev/null
+++ b/chrome/browser/ui/views/tabs/tab_hover_card_bubble_view_unittest.cc
@@ -0,0 +1,276 @@
+// 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.
+
+#include "chrome/browser/ui/views/tabs/tab_hover_card_bubble_view.h"
+
+#include <ostream>
+#include <string>
+
+#include "base/test/task_environment.h"
+#include "testing/gtest/include/gtest/gtest-param-test.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/render_text.h"
+
+class TabHoverCardBubbleViewFilenameEliderTest {
+ protected:
+  using FilenameElider = TabHoverCardBubbleView::FilenameElider;
+
+  static constexpr float kGlyphWidth = 10.0;
+  static constexpr float kGlyphHeight = 10.0;
+
+  static std::unique_ptr<gfx::RenderText> CreateRenderText(
+      const std::u16string& text) {
+    auto render_text = gfx::RenderText::CreateRenderText();
+    render_text->set_glyph_width_for_test(kGlyphWidth);
+    render_text->set_glyph_height_for_test(kGlyphHeight);
+    if (!text.empty())
+      render_text->SetText(text);
+    return render_text;
+  }
+
+  static gfx::Rect GetTextRect(int num_chars_per_line) {
+    // Bump up the width to almost large enough to hold an extra character in
+    // order to avoid some quirkiness with RenderText even when there's a fixed
+    // glyph width.
+    return gfx::Rect(kGlyphWidth * (num_chars_per_line + 0.9f),
+                     kGlyphHeight * 2);
+  }
+
+  static FilenameElider::LineLengths GetLineLengths(const std::u16string& text,
+                                                    int num_chars_per_line) {
+    FilenameElider elider(CreateRenderText(text));
+    return elider.GetLineLengths(GetTextRect(num_chars_per_line));
+  }
+
+  static std::u16string ElideImpl(const std::u16string& text,
+                                  size_t max_first_line_length,
+                                  size_t max_second_line_length) {
+    FilenameElider elider(CreateRenderText(text));
+    return elider.ElideImpl(FilenameElider::LineLengths{
+        max_first_line_length, max_second_line_length});
+  }
+
+ private:
+  // Required for loading fallback fonts, as fallback font loading needs to
+  // happen on the UI thread.
+  base::test::TaskEnvironment task_environment_{
+      base::test::TaskEnvironment::MainThreadType::UI};
+};
+
+#define EM_SPACE u"\u2003"
+#define COMBINING_CIRCUMFLEX u"\u0302"
+
+#define MEDICAL_SYMBOL_EMOJI u"\u2695\uFE0F"
+#define ZERO_WIDTH_JOINER u"\u200D"
+#define MAN_EMOJI u"\U0001F468"
+#define MEDIUM_SKIN_TONE_MODIFIER u"\U0001F3FD"
+#define MALE_HEALTH_WORKER_MEDIUM_SKIN_TONE \
+  MAN_EMOJI MEDIUM_SKIN_TONE_MODIFIER ZERO_WIDTH_JOINER MEDICAL_SYMBOL_EMOJI
+
+#define BIDIFY(a, b) a u"\n\u2068" b u"\u2069"
+#define ELLIPSIZE(a, b) BIDIFY(a, u"\u2026" b)
+
+struct ElideImplTestParams {
+  const char16_t* const text;
+  const size_t max_first_line_length;
+  const size_t max_second_line_length;
+  const char16_t* const expected;
+  const char* const comment;
+};
+
+void PrintTo(const ElideImplTestParams& params, ::std::ostream* os) {
+  *os << params.comment << " (\"" << params.text << "\", "
+      << params.max_first_line_length << ", " << params.max_second_line_length
+      << ")";
+}
+
+const ElideImplTestParams kElideImplTestParams[]{
+    {u"", 0, 0, u"", "Zero-length string yields empty result."},
+    {u"abcd", 0, 0, u"", "Zero-length lines yield empty results."},
+    {u"abcd", 4, 0, u"abcd",
+     "First line is all text yields full text, even if second is empty."},
+    {u"abcd", 0, 4, u"abcd",
+     "Second line is all text yields full text, even if first is empty."},
+    {u"abcd", 4, 1, u"abcd", "First line is all text yields full text."},
+    {u"abcd", 1, 4, u"abcd", "Second line is all text yields full text."},
+    {u"abcd", 1, 1, ELLIPSIZE(u"a", u"d"),
+     "Gap between first and second lines."},
+    {u"abcd", 2, 2, u"ab\ncd", "No gap between first and second lines."},
+    {u"abcd", 3, 3, u"abc\nd", "Overlap between first and second lines."},
+    {u"abc.def", 3, 3, ELLIPSIZE(u"abc", u"def"), "Extension dot is cut out."},
+    {u"abc.def", 4, 3, u"abc.\ndef",
+     "Extension whole string dot is not moved."},
+    {u"abc.def", 4, 4, BIDIFY(u"abc", u".def"),
+     "Extension overlap dot moved to second line."},
+    {u"abc.def", 6, 6, BIDIFY(u"abc", u".def"),
+     "Extension overlap dot moved to second line (2)."},
+    {u"abc def", 3, 4, u"abc\ndef", "Whitespace after break is elided (1)."},
+    {u"abc\t" EM_SPACE u"def", 3, 4, ELLIPSIZE(u"abc", u"def"),
+     "Whitespace after break is elided (2)."},
+    {u"abc\t" EM_SPACE u"def", 3, 5, u"abc\ndef",
+     "Whitespace after break is elided (3)."},
+    {u"abc def", 2, 4, ELLIPSIZE(u"ab", u"def"),
+     "Whitespace after ellipsis is elided (1)."},
+    {u"abc\t" EM_SPACE u"def", 2, 4, ELLIPSIZE(u"ab", u"def"),
+     "Whitespace after ellipsis is elided (2)."},
+    {u"abc\t" EM_SPACE u"def", 2, 5, ELLIPSIZE(u"ab", u"def"),
+     "Whitespace after ellipsis is elided (3)."},
+    {u"abco" COMBINING_CIRCUMFLEX u"def", 3, 5,
+     u"abc\no" COMBINING_CIRCUMFLEX u"def",
+     "Cut before combining characters does not elide characters."},
+    {u"abco" COMBINING_CIRCUMFLEX u"def", 5, 3,
+     u"abco" COMBINING_CIRCUMFLEX u"\ndef",
+     "Cut after combining characters does not elide characters."},
+    {u"abc" MAN_EMOJI u"def", 3, 5, u"abc\n" MAN_EMOJI u"def",
+     "Cut before four-byte emoji does not elide emoji."},
+    {u"abc" MAN_EMOJI u"def", 5, 3, u"abc" MAN_EMOJI u"\ndef",
+     "Cut after four-byte emoji does not elide emoji."},
+    {u"abc" MAN_EMOJI MAN_EMOJI MAN_EMOJI u"def", 3, 3,
+     ELLIPSIZE(u"abc", u"def"), "Cut around multiple emoji."},
+    {u"abc" MAN_EMOJI MAN_EMOJI MAN_EMOJI u"def", 5, 3,
+     ELLIPSIZE(u"abc" MAN_EMOJI, u"def"),
+     "Cut the end of a sequence of emoji."},
+    {u"abc" MAN_EMOJI MAN_EMOJI MAN_EMOJI u"def", 5, 5,
+     ELLIPSIZE(u"abc" MAN_EMOJI, MAN_EMOJI u"def"),
+     "Cut the middle of a sequence of emoji."},
+    {u"abc" MEDICAL_SYMBOL_EMOJI u"def", 3, 5,
+     u"abc\n" MEDICAL_SYMBOL_EMOJI u"def",
+     "Cut before two-character emoji does not elide emoji."},
+    {u"abc" MEDICAL_SYMBOL_EMOJI u"def", 5, 3,
+     u"abc" MEDICAL_SYMBOL_EMOJI u"\ndef",
+     "Cut after two-character emoji does not elide emoji."},
+    {u"abc" MAN_EMOJI MEDIUM_SKIN_TONE_MODIFIER u"def", 3, 7,
+     u"abc\n" MAN_EMOJI MEDIUM_SKIN_TONE_MODIFIER u"def",
+     "Cut before modified emoji does not elide emoji."},
+    {u"abc" MAN_EMOJI MEDIUM_SKIN_TONE_MODIFIER u"def", 7, 3,
+     u"abc" MAN_EMOJI MEDIUM_SKIN_TONE_MODIFIER u"\ndef",
+     "Cut after modified emoji does not elide emoji."},
+    {u"abc" MALE_HEALTH_WORKER_MEDIUM_SKIN_TONE u"def", 3, 10,
+     u"abc\n" MALE_HEALTH_WORKER_MEDIUM_SKIN_TONE u"def",
+     "Cut before compound emoji does not elide emoji."},
+    {u"abc" MALE_HEALTH_WORKER_MEDIUM_SKIN_TONE u"def", 10, 3,
+     u"abc" MALE_HEALTH_WORKER_MEDIUM_SKIN_TONE u"\ndef",
+     "Cut after compound emoji does not elide emoji."},
+    {u"abco" COMBINING_CIRCUMFLEX u" def", 3, 4, ELLIPSIZE(u"abc", u"def"),
+     "Eliminates whitespace after elided combining character."},
+};
+
+class TabHoverCardBubbleViewFilenameEliderElideImplTest
+    : public TabHoverCardBubbleViewFilenameEliderTest,
+      public testing::TestWithParam<ElideImplTestParams> {};
+
+INSTANTIATE_TEST_SUITE_P(,
+                         TabHoverCardBubbleViewFilenameEliderElideImplTest,
+                         testing::ValuesIn(kElideImplTestParams));
+
+TEST_P(TabHoverCardBubbleViewFilenameEliderElideImplTest, ElideImpl) {
+  const ElideImplTestParams& params = GetParam();
+  EXPECT_EQ(std::u16string(params.expected),
+            ElideImpl(params.text, params.max_first_line_length,
+                      params.max_second_line_length));
+}
+
+struct ElideTestParams {
+  const char16_t* const text;
+  const int chars_per_line;
+  const size_t expected_first_line_length;
+  const size_t expected_second_line_length;
+  const char16_t* const elided;
+  const char* const comment;
+};
+
+void PrintTo(const ElideTestParams& params, ::std::ostream* os) {
+  *os << params.comment << " (\"" << params.text << "\", "
+      << params.chars_per_line << ")";
+}
+
+const ElideTestParams kElideTestParams[]{
+    {u"", 1, 0, 0, u"", "Empty string results in zero-length lines."},
+    {u"abc", 3, 3, 3, u"abc",
+     "Length equal to width gives full length for each line."},
+    {u"abc", 4, 3, 3, u"abc",
+     "Length less than width gives full length for each line."},
+    {u"abcde", 3, 3, 2, u"abc\nde",
+     "Maximum length of lines without ellipsis results in perfect match."},
+    {u"abcdef", 3, 3, 2, ELLIPSIZE(u"abc", u"ef"),
+     "Length equal to width still leaves space for ellipsis on second line."},
+    {u"abcdefg", 3, 3, 2, ELLIPSIZE(u"abc", u"fg"),
+     "Length much greater than width still leaves space for ellipsis on second "
+     "line."},
+    {u"abc", 0, 0, 0, u"", "No available width results in zero length."},
+    {u"abco" COMBINING_CIRCUMFLEX u"efg", 4, 5, 3,
+     "abco" COMBINING_CIRCUMFLEX u"\nefg",
+     "First line ends with combining character."},
+    {u"abco" COMBINING_CIRCUMFLEX u"efg", 3, 3, 2, ELLIPSIZE(u"abc", u"fg"),
+     "Combining character fully elided between lines."},
+    {u"abc" MAN_EMOJI u"efg", 4, 5, 3, u"abc" MAN_EMOJI u"\nefg",
+     "First line ends with four-byte emoji."},
+    {u"abc" MAN_EMOJI u"efg", 3, 3, 2, ELLIPSIZE(u"abc", u"fg"),
+     "Four-byte emoji fully elided between lines."},
+    {u"abc" MAN_EMOJI MAN_EMOJI MAN_EMOJI u"efg", 3, 3, 2,
+     ELLIPSIZE(u"abc", u"fg"), "Elide around a sequence of emoji."},
+    {u"abc" MAN_EMOJI MAN_EMOJI MAN_EMOJI u"efg", 4, 5, 3,
+     ELLIPSIZE(u"abc" MAN_EMOJI, u"efg"), "Elide around a sequence of emoji."},
+    {u"abc" MAN_EMOJI MAN_EMOJI MAN_EMOJI u"efg", 5, 7, 5,
+     u"abc" MAN_EMOJI MAN_EMOJI u"\n" MAN_EMOJI u"efg",
+     "Line break in middle of sequence."},
+    {u"abc" MEDICAL_SYMBOL_EMOJI u"efg", 4, 5, 3,
+     u"abc" MEDICAL_SYMBOL_EMOJI u"\nefg",
+     "First line ends with two-character emoji."},
+    {u"abc" MEDICAL_SYMBOL_EMOJI u"efg", 3, 3, 2, ELLIPSIZE(u"abc", u"fg"),
+     "Two-character emoji fully elided between lines."},
+    {u"abc" MAN_EMOJI MEDIUM_SKIN_TONE_MODIFIER u"efg", 4, 7, 3,
+     u"abc" MAN_EMOJI MEDIUM_SKIN_TONE_MODIFIER u"\nefg",
+     "First line ends with modified emoji."},
+    {u"abc" MAN_EMOJI MEDIUM_SKIN_TONE_MODIFIER u"efg", 3, 3, 2,
+     ELLIPSIZE(u"abc", u"fg"), "Modified emoji fully elided between lines."},
+    {u"abc" MALE_HEALTH_WORKER_MEDIUM_SKIN_TONE u"efg", 4, 10, 3,
+     u"abc" MALE_HEALTH_WORKER_MEDIUM_SKIN_TONE u"\nefg",
+     "First line ends with joined emoji, full string returned."},
+    {u"abc" MALE_HEALTH_WORKER_MEDIUM_SKIN_TONE u"efgh", 4, 10, 3,
+     ELLIPSIZE(u"abc" MALE_HEALTH_WORKER_MEDIUM_SKIN_TONE, u"fgh"),
+     "First line ends with joined emoji, string cut in middle (1)."},
+    {u"abc" MALE_HEALTH_WORKER_MEDIUM_SKIN_TONE u"defghi", 5, 11, 4,
+     ELLIPSIZE(u"abc" MALE_HEALTH_WORKER_MEDIUM_SKIN_TONE u"d", u"fghi"),
+     "First line ends with joined emoji, string cut in middle (2)."},
+    {u"abc" MALE_HEALTH_WORKER_MEDIUM_SKIN_TONE u"efg", 3, 3, 2,
+     ELLIPSIZE(u"abc", u"fg"), "Joined emoji fully elided between lines."},
+    {u"abcde" MALE_HEALTH_WORKER_MEDIUM_SKIN_TONE
+         MALE_HEALTH_WORKER_MEDIUM_SKIN_TONE u"fg",
+     4, 4, 9, ELLIPSIZE(u"abcd", MALE_HEALTH_WORKER_MEDIUM_SKIN_TONE u"fg"),
+     "Joined emoji in sequence; first emoji is elided but not second."},
+    // These test the combined function of the Elide() method, including the
+    // intelligent overlapping and positioning of lines and extensions.
+    {u"abcdef", 5, 5, 4, u"abcde\nf", "Wrap at last possible location."},
+    {u"abcde.fgh", 6, 6, 5, BIDIFY(u"abcde", u".fgh"),
+     "Entire extension placed on second line (1)."},
+    {u"abcde.fgh", 5, 5, 4, BIDIFY(u"abcde", u".fgh"),
+     "Entire extension placed on second line (2)."},
+    {u"abc.fgh", 4, 4, 3, u"abc.\nfgh", "Force break after period."},
+    {u"abcde.jpg (100x100)", 15, 15, 14, BIDIFY(u"abcde", u".jpg (100x100)"),
+     "Extension plus size put on second line."}};
+
+class TabHoverCardBubbleViewFilenameEliderGetLineLengthsTest
+    : public TabHoverCardBubbleViewFilenameEliderTest,
+      public testing::TestWithParam<ElideTestParams> {};
+
+INSTANTIATE_TEST_SUITE_P(,
+                         TabHoverCardBubbleViewFilenameEliderGetLineLengthsTest,
+                         testing::ValuesIn(kElideTestParams));
+
+TEST_P(TabHoverCardBubbleViewFilenameEliderGetLineLengthsTest, GetLineLengths) {
+  const ElideTestParams& params = GetParam();
+  auto result = GetLineLengths(params.text, params.chars_per_line);
+  EXPECT_EQ(params.expected_first_line_length, result.first)
+      << "Text length is " << std::u16string(params.text).length();
+  EXPECT_EQ(params.expected_second_line_length, result.second);
+}
+
+TEST_P(TabHoverCardBubbleViewFilenameEliderGetLineLengthsTest, Elide) {
+  const ElideTestParams& params = GetParam();
+  FilenameElider elider(CreateRenderText(std::u16string()));
+  EXPECT_EQ(std::u16string(params.elided),
+            elider.Elide(params.text, GetTextRect(params.chars_per_line)));
+}
diff --git a/chrome/browser/ui/views/toolbar/chrome_labs_bubble_view.cc b/chrome/browser/ui/views/toolbar/chrome_labs_bubble_view.cc
index 0549af123..86418cfa 100644
--- a/chrome/browser/ui/views/toolbar/chrome_labs_bubble_view.cc
+++ b/chrome/browser/ui/views/toolbar/chrome_labs_bubble_view.cc
@@ -148,16 +148,14 @@
 }  // namespace
 
 // static
-void ChromeLabsBubbleView::Show(views::View* anchor_view,
+void ChromeLabsBubbleView::Show(ChromeLabsButton* anchor_view,
                                 Browser* browser,
                                 const ChromeLabsBubbleViewModel* model,
                                 bool user_is_chromeos_owner) {
 #if BUILDFLAG(IS_CHROMEOS_ASH)
-  if (static_cast<ChromeLabsButton*>(anchor_view)->GetAshOwnerCheckTimer()) {
+  if (anchor_view->GetAshOwnerCheckTimer()) {
     UmaHistogramMediumTimes("Toolbar.ChromeLabs.AshOwnerCheckTime",
-                            static_cast<ChromeLabsButton*>(anchor_view)
-                                ->GetAshOwnerCheckTimer()
-                                ->Elapsed());
+                            anchor_view->GetAshOwnerCheckTimer()->Elapsed());
   }
 #endif
   g_chrome_labs_bubble = new ChromeLabsBubbleView(anchor_view, browser, model,
@@ -184,7 +182,7 @@
 }
 
 ChromeLabsBubbleView::ChromeLabsBubbleView(
-    views::View* anchor_view,
+    ChromeLabsButton* anchor_view,
     Browser* browser,
     const ChromeLabsBubbleViewModel* model,
     bool user_is_chromeos_owner)
@@ -258,6 +256,9 @@
   // experiments to show. Therefore ChromeLabsBubble should not be created.
   DCHECK(menu_item_container_->children().size() >= 1);
 
+  // Hide dot indicator once bubble has been opened.
+  anchor_view->HideDotIndicator();
+
   restart_prompt_ = AddChildView(std::make_unique<ChromeLabsFooter>(this));
   restart_prompt_->SetVisible(about_flags::IsRestartNeededToCommitChanges());
 }
diff --git a/chrome/browser/ui/views/toolbar/chrome_labs_bubble_view.h b/chrome/browser/ui/views/toolbar/chrome_labs_bubble_view.h
index c1134af..8988e70 100644
--- a/chrome/browser/ui/views/toolbar/chrome_labs_bubble_view.h
+++ b/chrome/browser/ui/views/toolbar/chrome_labs_bubble_view.h
@@ -15,6 +15,7 @@
 #include "ui/views/layout/flex_layout_view.h"
 
 class Browser;
+class ChromeLabsButton;
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 class Profile;
 #endif
@@ -23,7 +24,7 @@
 class ChromeLabsBubbleView : public views::BubbleDialogDelegateView {
  public:
   METADATA_HEADER(ChromeLabsBubbleView);
-  static void Show(views::View* anchor_view,
+  static void Show(ChromeLabsButton* anchor_view,
                    Browser* browser,
                    const ChromeLabsBubbleViewModel* model,
                    bool user_is_chromeos_owner);
@@ -43,7 +44,7 @@
   bool IsRestartPromptVisibleForTesting();
 
  private:
-  ChromeLabsBubbleView(views::View* anchor_view,
+  ChromeLabsBubbleView(ChromeLabsButton* anchor_view,
                        Browser* browser,
                        const ChromeLabsBubbleViewModel* model,
                        bool user_is_chromeos_owner);
diff --git a/chrome/browser/ui/views/toolbar/chrome_labs_button.cc b/chrome/browser/ui/views/toolbar/chrome_labs_button.cc
index 7ab8cbb..798e00b 100644
--- a/chrome/browser/ui/views/toolbar/chrome_labs_button.cc
+++ b/chrome/browser/ui/views/toolbar/chrome_labs_button.cc
@@ -4,10 +4,14 @@
 
 #include "chrome/browser/ui/views/toolbar/chrome_labs_button.h"
 
+#include "base/ranges/algorithm.h"
 #include "base/timer/elapsed_timer.h"
 #include "chrome/app/vector_icons/vector_icons.h"
+#include "chrome/browser/browser_process.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/themes/theme_properties.h"
 #include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/toolbar/chrome_labs_prefs.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/browser/ui/views/toolbar/chrome_labs_bubble_view.h"
 #include "chrome/browser/ui/views/toolbar/chrome_labs_utils.h"
@@ -17,6 +21,7 @@
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/views/accessibility/view_accessibility.h"
 #include "ui/views/controls/button/button_controller.h"
+#include "ui/views/controls/dot_indicator.h"
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "ash/constants/ash_switches.h"
@@ -39,6 +44,8 @@
       views::ButtonController::NotifyAction::kOnPress);
   GetViewAccessibility().OverrideRole(ax::mojom::Role::kPopUpButton);
   GetViewAccessibility().OverrideHasPopup(ax::mojom::HasPopup::kDialog);
+  new_experiments_indicator_ = views::DotIndicator::Install(image());
+  UpdateDotIndicator();
 }
 
 ChromeLabsButton::~ChromeLabsButton() {
@@ -46,6 +53,37 @@
   ChromeLabsBubbleView::Hide();
 }
 
+void ChromeLabsButton::Layout() {
+  ToolbarButton::Layout();
+  gfx::Rect dot_rect(8, 8);
+  if (ui::TouchUiController::Get()->touch_ui()) {
+    dot_rect = ScaleToEnclosingRect(
+        dot_rect, float{kDefaultTouchableIconSize} / kDefaultIconSize);
+  }
+  dot_rect.set_origin(image()->GetImageBounds().bottom_right() -
+                      dot_rect.bottom_right().OffsetFromOrigin());
+  new_experiments_indicator_->SetBoundsRect(dot_rect);
+}
+
+void ChromeLabsButton::OnThemeChanged() {
+  ToolbarButton::OnThemeChanged();
+
+  // We don't always have a theme provider (ui tests, for example).
+  const ui::ThemeProvider* theme_provider = GetThemeProvider();
+  if (!theme_provider)
+    return;
+
+  new_experiments_indicator_->SetColor(
+      /*dot_color=*/GetNativeTheme()->GetSystemColor(
+          ui::NativeTheme::kColorId_ProminentButtonColor),
+      /*border_color=*/theme_provider->GetColor(
+          ThemeProperties::COLOR_TOOLBAR));
+}
+
+void ChromeLabsButton::HideDotIndicator() {
+  new_experiments_indicator_->Hide();
+}
+
 void ChromeLabsButton::ButtonPressed() {
   // On Chrome OS if we are still waiting for IsOwnerAsync to return abort
   // button clicks.
@@ -96,6 +134,41 @@
                              /*user_is_chromeos_owner=*/false);
 }
 
+void ChromeLabsButton::UpdateDotIndicator() {
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  DictionaryPrefUpdate update(
+      browser_view_->browser()->profile()->GetPrefs(),
+      chrome_labs_prefs::kChromeLabsNewBadgeDictAshChrome);
+#else
+  DictionaryPrefUpdate update(g_browser_process->local_state(),
+                              chrome_labs_prefs::kChromeLabsNewBadgeDict);
+#endif
+
+  base::DictionaryValue* new_badge_prefs = update.Get();
+
+  std::vector<std::string> lab_internal_names;
+  const std::vector<LabInfo>& all_labs = model_->GetLabInfo();
+
+  bool should_show_dot_indicator = base::ranges::any_of(
+      all_labs.begin(), all_labs.end(), [new_badge_prefs](const LabInfo& lab) {
+        if (!new_badge_prefs->HasKey(lab.internal_name))
+          return false;
+        int new_badge_pref_value;
+        new_badge_prefs->GetInteger(lab.internal_name, &new_badge_pref_value);
+        // Show the dot indicator if new experiments have not been seen yet.
+        if (new_badge_pref_value ==
+            chrome_labs_prefs::kChromeLabsNewExperimentPrefValue) {
+          return true;
+        }
+        return false;
+      });
+
+  if (should_show_dot_indicator)
+    new_experiments_indicator_->Show();
+  else
+    new_experiments_indicator_->Hide();
+}
+
 // static
 bool ChromeLabsButton::ShouldShowButton(const ChromeLabsBubbleViewModel* model,
                                         Profile* profile) {
diff --git a/chrome/browser/ui/views/toolbar/chrome_labs_button.h b/chrome/browser/ui/views/toolbar/chrome_labs_button.h
index 40c440c..e2f314c0 100644
--- a/chrome/browser/ui/views/toolbar/chrome_labs_button.h
+++ b/chrome/browser/ui/views/toolbar/chrome_labs_button.h
@@ -8,6 +8,7 @@
 #include "chrome/browser/ui/views/toolbar/chrome_labs_bubble_view_model.h"
 #include "chrome/browser/ui/views/toolbar/toolbar_button.h"
 #include "ui/base/metadata/metadata_header_macros.h"
+#include "ui/views/controls/dot_indicator.h"
 
 namespace base {
 class ElapsedTimer;
@@ -25,6 +26,12 @@
   ChromeLabsButton& operator=(const ChromeLabsButton&) = delete;
   ~ChromeLabsButton() override;
 
+  // ToolbarButton:
+  void Layout() override;
+  void OnThemeChanged() override;
+
+  void HideDotIndicator();
+
   static bool ShouldShowButton(const ChromeLabsBubbleViewModel* model,
                                Profile* profile);
 
@@ -38,9 +45,15 @@
   }
 #endif
 
+  bool GetDotIndicatorVisibilityForTesting() const {
+    return new_experiments_indicator_->GetVisible();
+  }
+
  private:
   void ButtonPressed();
 
+  void UpdateDotIndicator();
+
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   // Measures elapsed between when IsOwnerAsync is called and the callback
   // passed into IsOwnerAsnc is called. The callback will be called after
@@ -57,6 +70,8 @@
 #endif
 
   const ChromeLabsBubbleViewModel* model_;
+
+  views::DotIndicator* new_experiments_indicator_;
 };
 
 #endif  // CHROME_BROWSER_UI_VIEWS_TOOLBAR_CHROME_LABS_BUTTON_H_
diff --git a/chrome/browser/ui/views/toolbar/chrome_labs_button_unittest.cc b/chrome/browser/ui/views/toolbar/chrome_labs_button_unittest.cc
index a39f3a3..752ee2fc 100644
--- a/chrome/browser/ui/views/toolbar/chrome_labs_button_unittest.cc
+++ b/chrome/browser/ui/views/toolbar/chrome_labs_button_unittest.cc
@@ -15,6 +15,7 @@
 #include "chrome/browser/unexpire_flags.h"
 #include "components/flags_ui/feature_entry_macros.h"
 #include "ui/events/event_utils.h"
+#include "ui/views/controls/dot_indicator.h"
 #include "ui/views/test/button_test_api.h"
 #include "ui/views/test/widget_test.h"
 
@@ -135,6 +136,17 @@
   EXPECT_FALSE(browser_view()->toolbar()->chrome_labs_button()->GetVisible());
 }
 
+TEST_F(ChromeLabsButtonTest, DotIndicatorTest) {
+  ChromeLabsButton* chrome_labs_button =
+      browser_view()->toolbar()->chrome_labs_button();
+  EXPECT_TRUE(chrome_labs_button->GetDotIndicatorVisibilityForTesting());
+  ui::MouseEvent e(ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(),
+                   ui::EventTimeForNow(), 0, 0);
+  views::test::ButtonTestApi test_api(chrome_labs_button);
+  test_api.NotifyClick(e);
+  EXPECT_FALSE(chrome_labs_button->GetDotIndicatorVisibilityForTesting());
+}
+
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 
 class ChromeLabsButtonTestSafeMode : public ChromeLabsButtonTest {
diff --git a/chrome/browser/ui/views/toolbar/chrome_labs_item_view.cc b/chrome/browser/ui/views/toolbar/chrome_labs_item_view.cc
index 78bffabf..ec177008 100644
--- a/chrome/browser/ui/views/toolbar/chrome_labs_item_view.cc
+++ b/chrome/browser/ui/views/toolbar/chrome_labs_item_view.cc
@@ -15,6 +15,7 @@
 #include "chrome/browser/ui/views/chrome_layout_provider.h"
 #include "chrome/browser/ui/views/chrome_typography.h"
 #include "chrome/browser/ui/views/toolbar/chrome_labs_bubble_view_model.h"
+#include "chrome/browser/ui/views/toolbar/chrome_labs_utils.h"
 #include "chrome/browser/ui/views/user_education/new_badge_label.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/prefs/scoped_user_pref_update.h"
@@ -47,6 +48,7 @@
       /* extra_diagnostics=*/std::string());
 }
 
+// Returns the number of days since epoch (1970-01-01) in the local timezone.
 uint32_t GetCurrentDay() {
   base::TimeDelta delta = base::Time::Now() - base::Time::UnixEpoch();
   return base::saturated_cast<uint32_t>(delta.InDays());
@@ -262,12 +264,11 @@
     // show the new badge.
     new_badge_prefs->SetInteger(lab.internal_name, GetCurrentDay());
     return true;
-  } else {
-    int days_elapsed = GetCurrentDay() - start_day;
-    // Show the new badge for 7 days. If the users sets the clock such that the
-    // current day is now before |start_day| don’t show the new badge.
-    return (days_elapsed < 7) && (days_elapsed >= 0);
   }
+  int days_elapsed = GetCurrentDay() - start_day;
+  // Show the new badge for 7 days. If the users sets the clock such that the
+  // current day is now before |start_day| don’t show the new badge.
+  return (days_elapsed < 7) && (days_elapsed >= 0);
 }
 
 BEGIN_METADATA(ChromeLabsItemView, views::View)
diff --git a/chrome/browser/ui/views/toolbar/chrome_labs_utils.cc b/chrome/browser/ui/views/toolbar/chrome_labs_utils.cc
index a10c3dd..76bc9a6 100644
--- a/chrome/browser/ui/views/toolbar/chrome_labs_utils.cc
+++ b/chrome/browser/ui/views/toolbar/chrome_labs_utils.cc
@@ -6,6 +6,7 @@
 #include "base/containers/contains.h"
 #include "chrome/browser/about_flags.h"
 #include "chrome/browser/browser_process.h"
+#include "chrome/browser/flag_descriptions.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/toolbar/chrome_labs_prefs.h"
 #include "chrome/common/channel_info.h"
@@ -60,7 +61,11 @@
   std::vector<std::string> lab_internal_names;
   const std::vector<LabInfo>& all_labs = model->GetLabInfo();
   for (const auto& lab : all_labs) {
-    if (IsChromeLabsFeatureValid(lab, profile)) {
+    // Tab Scrolling was added before new badge logic and is not a new
+    // experiment. Adding it to |new_badge_prefs| will falsely indicate a new
+    // experiment for the button’s dot indicator.
+    if (IsChromeLabsFeatureValid(lab, profile) &&
+        (lab.internal_name != flag_descriptions::kScrollableTabStripFlagId)) {
       lab_internal_names.push_back(lab.internal_name);
       if (!new_badge_prefs->HasKey(lab.internal_name)) {
         new_badge_prefs->SetInteger(
diff --git a/chrome/browser/ui/web_applications/app_browser_controller_browsertest.cc b/chrome/browser/ui/web_applications/app_browser_controller_browsertest.cc
index 72f11810..edb4ff5 100644
--- a/chrome/browser/ui/web_applications/app_browser_controller_browsertest.cc
+++ b/chrome/browser/ui/web_applications/app_browser_controller_browsertest.cc
@@ -23,6 +23,7 @@
 #include "chrome/browser/ui/web_applications/system_web_app_ui_utils.h"
 #include "chrome/browser/ui/web_applications/test/web_app_browsertest_util.h"
 #include "chrome/browser/web_applications/components/web_app_constants.h"
+#include "chrome/browser/web_applications/components/web_app_utils.h"
 #include "chrome/browser/web_applications/components/web_application_info.h"
 #include "chrome/browser/web_applications/system_web_apps/system_web_app_manager.h"
 #include "chrome/browser/web_applications/system_web_apps/test/test_system_web_app_installation.h"
@@ -94,7 +95,7 @@
       : test_system_web_app_installation_(
             TestSystemWebAppInstallation::SetUpTabbedMultiWindowApp()) {
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
-    WebAppProvider::EnableSystemWebAppsInLacrosForTesting();
+    EnableSystemWebAppsInLacrosForTesting();
 #endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
   }
   AppBrowserControllerBrowserTest(const AppBrowserControllerBrowserTest&) =
diff --git a/chrome/browser/ui/web_applications/test/system_web_app_interactive_uitest.cc b/chrome/browser/ui/web_applications/test/system_web_app_interactive_uitest.cc
index 0ca8aa32..85b0bcd 100644
--- a/chrome/browser/ui/web_applications/test/system_web_app_interactive_uitest.cc
+++ b/chrome/browser/ui/web_applications/test/system_web_app_interactive_uitest.cc
@@ -26,6 +26,7 @@
 #include "chrome/browser/ui/web_applications/app_browser_controller.h"
 #include "chrome/browser/ui/web_applications/system_web_app_ui_utils.h"
 #include "chrome/browser/ui/web_applications/test/web_app_browsertest_util.h"
+#include "chrome/browser/web_applications/components/web_app_utils.h"
 #include "chrome/browser/web_applications/system_web_apps/test/system_web_app_browsertest_base.h"
 #include "chrome/browser/web_applications/system_web_apps/test/test_system_web_app_installation.h"
 #include "chrome/browser/web_applications/web_app_tab_helper.h"
@@ -64,7 +65,7 @@
   SystemWebAppLinkCaptureBrowserTest()
       : SystemWebAppManagerBrowserTest(/*install_mock*/ false) {
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
-    WebAppProvider::EnableSystemWebAppsInLacrosForTesting();
+    EnableSystemWebAppsInLacrosForTesting();
 #endif
     maybe_installation_ =
         TestSystemWebAppInstallation::SetUpAppThatCapturesNavigation();
diff --git a/chrome/browser/ui/webui/chromeos/audio/audio.mojom b/chrome/browser/ui/webui/chromeos/audio/audio.mojom
index 68fa0ff..aa32a568 100644
--- a/chrome/browser/ui/webui/chromeos/audio/audio.mojom
+++ b/chrome/browser/ui/webui/chromeos/audio/audio.mojom
@@ -28,12 +28,17 @@
 interface PageHandler {
     // Request to get audio device information from the browser side.
     GetAudioDeviceInfo();
+
     // Request to get active output device name from the browser side.
     // Return nullopt if active output device does not exist.
     GetActiveOutputDeviceName() => (string? device_name);
+
     // Request to get active input device name from the browser side.
     // Return nullopt if active input device does not exist.
     GetActiveInputDeviceName() => (string? device_name);
+
+    // Request to open Chrome feedback dialog.
+    OpenFeedbackDialog();
 };
 
 // Interface for the WebUI page.
diff --git a/chrome/browser/ui/webui/chromeos/audio/audio_handler.cc b/chrome/browser/ui/webui/chromeos/audio/audio_handler.cc
index 219804d..f793613 100644
--- a/chrome/browser/ui/webui/chromeos/audio/audio_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/audio/audio_handler.cc
@@ -6,6 +6,9 @@
 #include <tuple>
 #include <utility>
 
+#include "chrome/browser/ui/browser_commands.h"
+#include "chrome/browser/ui/browser_finder.h"
+#include "chrome/browser/ui/chrome_pages.h"
 #include "chrome/browser/ui/webui/chromeos/audio/audio_handler.h"
 
 namespace chromeos {
@@ -49,6 +52,11 @@
   }
 }
 
+void AudioHandler::OpenFeedbackDialog() {
+  chrome::OpenFeedbackDialog(chrome::FindBrowserWithActiveWindow(),
+                             chrome::kFeedbackSourceMdSettingsAboutPage);
+}
+
 void AudioHandler::OnAudioNodesChanged() {
   UpdateAudioDeviceInfo();
 }
diff --git a/chrome/browser/ui/webui/chromeos/audio/audio_handler.h b/chrome/browser/ui/webui/chromeos/audio/audio_handler.h
index 69b393e..de86c5e 100644
--- a/chrome/browser/ui/webui/chromeos/audio/audio_handler.h
+++ b/chrome/browser/ui/webui/chromeos/audio/audio_handler.h
@@ -35,6 +35,8 @@
   void GetActiveInputDeviceName(
       GetActiveInputDeviceNameCallback callback) override;
 
+  void OpenFeedbackDialog() override;
+
   void OnAudioNodesChanged() override;
 
   void OnOutputNodeVolumeChanged(uint64_t node_id, int volume) override;
diff --git a/chrome/browser/ui/webui/history_clusters/history_clusters_handler.cc b/chrome/browser/ui/webui/history_clusters/history_clusters_handler.cc
index 27e1ed4..a36dc655 100644
--- a/chrome/browser/ui/webui/history_clusters/history_clusters_handler.cc
+++ b/chrome/browser/ui/webui/history_clusters/history_clusters_handler.cc
@@ -78,15 +78,42 @@
   return visit_mojom;
 }
 
+absl::optional<mojom::SearchQueryPtr> SearchQueryToMojom(
+    const std::string& search_query,
+    const TemplateURLService* template_url_service) {
+  TemplateURLRef::SearchTermsArgs search_terms_args(
+      base::UTF8ToUTF16(search_query));
+  const SearchTermsData& search_terms_data =
+      template_url_service->search_terms_data();
+  const TemplateURL* default_search_provider =
+      template_url_service->GetDefaultSearchProvider();
+  if (!default_search_provider ||
+      !default_search_provider->url_ref().SupportsReplacement(
+          search_terms_data)) {
+    return absl::nullopt;
+  }
+
+  auto search_query_mojom = mojom::SearchQuery::New();
+  search_query_mojom->query = search_query;
+  search_query_mojom->url =
+      GURL(default_search_provider->url_ref().ReplaceSearchTerms(
+          search_terms_args, search_terms_data));
+  return search_query_mojom;
+}
+
 void ServiceResultToMojom(
+    Profile* profile,
     base::OnceCallback<
         void(const absl::optional<base::Time>& continuation_max_time,
              std::vector<mojom::ClusterPtr> cluster_mojoms)> callback,
     HistoryClustersService::QueryClustersResult result) {
+  const TemplateURLService* template_url_service =
+      TemplateURLServiceFactory::GetForProfile(profile);
   std::vector<mojom::ClusterPtr> clusters_mojoms;
   for (const auto& cluster : result.clusters) {
     auto cluster_mojom = mojom::Cluster::New();
     cluster_mojom->id = cluster.cluster_id;
+    std::set<std::string> related_searches;  // Keeps track of unique searches.
     for (const auto& visit : cluster.scored_annotated_visits) {
       if (cluster_mojom->visits.empty()) {
         // First visit is always the top visit.
@@ -109,7 +136,25 @@
         // duplicates too.
         top_visit->related_visits.push_back(std::move(visit_mojom));
       }
+
+      auto& top_visit = cluster_mojom->visits.front();
+      // The top visit's related searches are the set of related searches across
+      // all the visits in the order they are encountered.
+      for (const auto& search_query :
+           visit.annotated_visit.content_annotations.related_searches) {
+        if (!related_searches.insert(search_query).second) {
+          continue;
+        }
+
+        auto search_query_mojom =
+            SearchQueryToMojom(search_query, template_url_service);
+        if (search_query_mojom) {
+          top_visit->related_searches.emplace_back(
+              std::move(*search_query_mojom));
+        }
+      }
     }
+
     clusters_mojoms.emplace_back(std::move(cluster_mojom));
   }
 
@@ -174,7 +219,8 @@
         HistoryClustersServiceFactory::GetForBrowserContext(profile_);
     history_clusters_service->QueryClusters(
         query, end_time, max_count,
-        base::BindOnce(&ServiceResultToMojom, std::move(result_callback)),
+        base::BindOnce(&ServiceResultToMojom, profile_,
+                       std::move(result_callback)),
         &query_task_tracker_);
   } else {
 #if defined(CHROME_BRANDED)
@@ -306,7 +352,7 @@
       BookmarkModelFactory::GetForBrowserContext(profile_);
 
   // Keep track of related searches in this cluster.
-  std::set<std::u16string> related_searches;
+  std::set<std::string> related_searches;
 
   // Keep track of visits in this cluster.
   std::vector<mojom::URLVisitPtr> visits;
@@ -358,7 +404,7 @@
       visit->annotations.push_back(mojom::Annotation::kSearchResultsPage);
 
       // Also offer this as a related search query.
-      related_searches.insert(normalized_search_query);
+      related_searches.insert(base::UTF16ToUTF8(normalized_search_query));
     }
 
     // Check if the URL is in a tab group.
@@ -419,17 +465,12 @@
       cluster_mojom->visits.push_back(std::move(visit));
 
       for (auto& search_query : related_searches) {
-        const std::string search_query_utf8 = base::UTF16ToUTF8(search_query);
-        TemplateURLRef::SearchTermsArgs search_terms_args(search_query);
-        const TemplateURLRef& search_url_ref =
-            default_search_provider->url_ref();
-        const std::string& search_url = search_url_ref.ReplaceSearchTerms(
-            search_terms_args, search_terms_data);
-        auto search_query_mojom = mojom::SearchQuery::New();
-        search_query_mojom->query = search_query_utf8;
-        search_query_mojom->url = GURL(search_url);
-        cluster_mojom->visits[0]->related_searches.push_back(
-            std::move(search_query_mojom));
+        auto search_query_mojom =
+            SearchQueryToMojom(search_query, template_url_service);
+        if (search_query_mojom) {
+          cluster_mojom->visits[0]->related_searches.push_back(
+              std::move(*search_query_mojom));
+        }
       }
     } else {
       // The rest of the visits will related visits of the first one. Only the
diff --git a/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc b/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc
index 3dafb79..e5621f0 100644
--- a/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc
+++ b/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc
@@ -17,12 +17,10 @@
 #include "chrome/browser/new_tab_page/modules/task_module/task_module_handler.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/search/background/ntp_custom_background_service_factory.h"
-#include "chrome/browser/search/instant_service.h"
-#include "chrome/browser/search/instant_service_factory.h"
 #include "chrome/browser/search_engines/template_url_service_factory.h"
 #include "chrome/browser/search_provider_logos/logo_service_factory.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
-#include "chrome/browser/themes/theme_service.h"
+#include "chrome/browser/themes/theme_properties.h"
 #include "chrome/browser/themes/theme_service_factory.h"
 #include "chrome/browser/ui/webui/cr_components/most_visited/most_visited_handler.h"
 #include "chrome/browser/ui/webui/customize_themes/chrome_customize_themes_handler.h"
@@ -328,15 +326,18 @@
       most_visited_page_factory_receiver_(this),
       browser_command_factory_receiver_(this),
       profile_(Profile::FromWebUI(web_ui)),
-      instant_service_(InstantServiceFactory::GetForProfile(profile_)),
+      theme_service_(ThemeServiceFactory::GetForProfile(profile_)),
+      ntp_custom_background_service_(
+          NtpCustomBackgroundServiceFactory::GetForProfile(profile_)),
       web_contents_(web_ui->GetWebContents()),
       // We initialize navigation_start_time_ to a reasonable value to account
       // for the unlikely case where the NewTabPageHandler is created before we
       // received the DidStartNavigation event.
       navigation_start_time_(base::Time::Now()) {
   auto* source = CreateNewTabPageUiHtmlSource(profile_, navigation_start_time_);
-  source->AddBoolean("customBackgroundDisabledByPolicy",
-                     instant_service_->IsCustomBackgroundDisabledByPolicy());
+  source->AddBoolean(
+      "customBackgroundDisabledByPolicy",
+      ntp_custom_background_service_->IsCustomBackgroundDisabledByPolicy());
   source->AddBoolean(
       "modulesVisibleManagedByPolicy",
       profile_->GetPrefs()->IsManagedPreference(prefs::kNtpModulesVisible));
@@ -365,15 +366,24 @@
       base::BindRepeating(&NewTabPageUI::OnTilesVisibilityPrefChanged,
                           weak_ptr_factory_.GetWeakPtr()));
 
-  instant_service_->AddObserver(this);
-  instant_service_->UpdateNtpTheme();
+  // Store basic theme info in load time data to make the background color and
+  // background image available as soon as the page loads to prevent a potential
+  // white flicker.
+
+  // Load time data is cached across page reloads. Listen for theme changes so
+  // that theme info is up-to-date when reloading.
+  theme_service_observation_.Observe(theme_service_);
+  ntp_custom_background_service_observation_.Observe(
+      ntp_custom_background_service_);
+
+  // Populates the load time data with basic theme info.
+  OnThemeChanged();
+  OnCustomBackgroundImageUpdated();
 }
 
 WEB_UI_CONTROLLER_TYPE_IMPL(NewTabPageUI)
 
-NewTabPageUI::~NewTabPageUI() {
-  instant_service_->RemoveObserver(this);
-}
+NewTabPageUI::~NewTabPageUI() = default;
 
 // static
 bool NewTabPageUI::IsNewTabPageOrigin(const GURL& url) {
@@ -495,8 +505,7 @@
   DCHECK(pending_page.is_valid());
   page_handler_ = std::make_unique<NewTabPageHandler>(
       std::move(pending_page_handler), std::move(pending_page), profile_,
-      NtpCustomBackgroundServiceFactory::GetForProfile(profile_),
-      ThemeServiceFactory::GetForProfile(profile_),
+      ntp_custom_background_service_, theme_service_,
       LogoServiceFactory::GetForProfile(profile_),
       &ThemeService::GetThemeProviderForProfile(profile_), web_contents_,
       navigation_start_time_);
@@ -537,13 +546,41 @@
   most_visited_page_handler_->SetShortcutsVisible(IsShortcutsVisible());
 }
 
-void NewTabPageUI::NtpThemeChanged(const NtpTheme& theme) {
-  // Load time data is cached across page reloads. Update the background color
-  // here to prevent a white flicker on page reload.
-  UpdateBackgroundColor(theme);
+void NewTabPageUI::OnThemeChanged() {
+  std::unique_ptr<base::DictionaryValue> update(new base::DictionaryValue);
+  auto background_color =
+      ThemeService::GetThemeProviderForProfile(profile_).GetColor(
+          ThemeProperties::COLOR_NTP_BACKGROUND);
+  update->SetString(
+      "backgroundColor",
+      base::StringPrintf("#%02X%02X%02X", SkColorGetR(background_color),
+                         SkColorGetG(background_color),
+                         SkColorGetB(background_color)));
+  content::WebUIDataSource::Update(profile_, chrome::kChromeUINewTabPageHost,
+                                   std::move(update));
 }
 
-void NewTabPageUI::MostVisitedInfoChanged(const InstantMostVisitedInfo& info) {}
+void NewTabPageUI::OnCustomBackgroundImageUpdated() {
+  std::unique_ptr<base::DictionaryValue> update(new base::DictionaryValue);
+  url::RawCanonOutputT<char> encoded_url;
+  auto custom_background_url =
+      (ntp_custom_background_service_
+           ? ntp_custom_background_service_->GetCustomBackground()
+           : absl::optional<CustomBackground>())
+          .value_or(CustomBackground())
+          .custom_background_url;
+  url::EncodeURIComponent(custom_background_url.spec().c_str(),
+                          custom_background_url.spec().size(), &encoded_url);
+  update->SetString("backgroundImageUrl",
+                    std::string(encoded_url.data(), encoded_url.length()));
+  content::WebUIDataSource::Update(profile_, chrome::kChromeUINewTabPageHost,
+                                   std::move(update));
+}
+
+void NewTabPageUI::OnNtpCustomBackgroundServiceShuttingDown() {
+  ntp_custom_background_service_observation_.Reset();
+  ntp_custom_background_service_ = nullptr;
+}
 
 void NewTabPageUI::DidStartNavigation(
     content::NavigationHandle* navigation_handle) {
@@ -571,24 +608,6 @@
   }
 }
 
-void NewTabPageUI::UpdateBackgroundColor(const NtpTheme& theme) {
-  std::unique_ptr<base::DictionaryValue> update(new base::DictionaryValue);
-  auto background_color = theme.background_color;
-  update->SetString(
-      "backgroundColor",
-      base::StringPrintf("#%02X%02X%02X", SkColorGetR(background_color),
-                         SkColorGetG(background_color),
-                         SkColorGetB(background_color)));
-  url::RawCanonOutputT<char> encoded_url;
-  url::EncodeURIComponent(theme.custom_background_url.spec().c_str(),
-                          theme.custom_background_url.spec().size(),
-                          &encoded_url);
-  update->SetString("backgroundImageUrl",
-                    std::string(encoded_url.data(), encoded_url.length()));
-  content::WebUIDataSource::Update(profile_, chrome::kChromeUINewTabPageHost,
-                                   std::move(update));
-}
-
 bool NewTabPageUI::IsCustomLinksEnabled() const {
   return !profile_->GetPrefs()->GetBoolean(ntp_prefs::kNtpUseMostVisitedTiles);
 }
diff --git a/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.h b/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.h
index bde0b3dc..9ade4445 100644
--- a/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.h
+++ b/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.h
@@ -11,11 +11,15 @@
 #include "chrome/browser/new_tab_page/modules/photos/photos.mojom.h"
 #include "chrome/browser/new_tab_page/modules/task_module/task_module.mojom.h"
 #include "chrome/browser/promo_browser_command/promo_browser_command.mojom.h"
-#include "chrome/browser/search/instant_service_observer.h"
 #if !defined(OFFICIAL_BUILD)
 #include "chrome/browser/ui/webui/new_tab_page/foo/foo.mojom.h"  // nogncheck crbug.com/1125897
 #endif
 #include "base/memory/weak_ptr.h"
+#include "base/scoped_observation.h"
+#include "chrome/browser/search/background/ntp_custom_background_service.h"
+#include "chrome/browser/search/background/ntp_custom_background_service_observer.h"
+#include "chrome/browser/themes/theme_service.h"
+#include "chrome/browser/themes/theme_service_observer.h"
 #include "chrome/browser/ui/webui/new_tab_page/new_tab_page.mojom.h"
 #include "chrome/browser/ui/webui/realbox/realbox.mojom-forward.h"
 #include "components/prefs/pref_change_registrar.h"
@@ -43,7 +47,6 @@
 class FooHandler;
 #endif
 class GURL;
-class InstantService;
 class MostVisitedHandler;
 class NewTabPageHandler;
 class PrefRegistrySimple;
@@ -62,7 +65,8 @@
       public customize_themes::mojom::CustomizeThemesHandlerFactory,
       public most_visited::mojom::MostVisitedPageHandlerFactory,
       public promo_browser_command::mojom::CommandHandlerFactory,
-      public InstantServiceObserver,
+      public ThemeServiceObserver,
+      public NtpCustomBackgroundServiceObserver,
       content::WebContentsObserver {
  public:
   explicit NewTabPageUI(content::WebUI* web_ui);
@@ -162,19 +166,17 @@
       mojo::PendingReceiver<most_visited::mojom::MostVisitedPageHandler>
           pending_page_handler) override;
 
-  // InstantServiceObserver:
-  void NtpThemeChanged(const NtpTheme& theme) override;
-  void MostVisitedInfoChanged(const InstantMostVisitedInfo& info) override;
+  // ThemeServiceObserver:
+  void OnThemeChanged() override;
+
+  // NtpCustomBackgroundServiceObserver:
+  void OnCustomBackgroundImageUpdated() override;
+  void OnNtpCustomBackgroundServiceShuttingDown() override;
 
   // content::WebContentsObserver:
   void DidStartNavigation(
       content::NavigationHandle* navigation_handle) override;
 
-  // Updates the load time data with the current theme's background color. That
-  // way the background color is available as soon as the page loads and we
-  // prevent a potential white flicker.
-  void UpdateBackgroundColor(const NtpTheme& theme);
-
   bool IsCustomLinksEnabled() const;
   bool IsShortcutsVisible() const;
 
@@ -202,7 +204,13 @@
 #endif
   std::unique_ptr<CartHandler> cart_handler_;
   Profile* profile_;
-  InstantService* instant_service_;
+  ThemeService* theme_service_;
+  NtpCustomBackgroundService* ntp_custom_background_service_;
+  base::ScopedObservation<ThemeService, ThemeServiceObserver>
+      theme_service_observation_{this};
+  base::ScopedObservation<NtpCustomBackgroundService,
+                          NtpCustomBackgroundServiceObserver>
+      ntp_custom_background_service_observation_{this};
   content::WebContents* web_contents_;
   // Time the NTP started loading. Used for logging the WebUI NTP's load
   // performance.
diff --git a/chrome/browser/ui/webui/print_preview/print_preview_handler.cc b/chrome/browser/ui/webui/print_preview/print_preview_handler.cc
index db2533e..cf10516 100644
--- a/chrome/browser/ui/webui/print_preview/print_preview_handler.cc
+++ b/chrome/browser/ui/webui/print_preview/print_preview_handler.cc
@@ -201,6 +201,12 @@
 // mounted.
 const char kIsDriveMounted[] = "isDriveMounted";
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
+#if defined(OS_WIN) || defined(OS_MAC)
+// Name of a dictionary pref holding the policy value for whether the
+// "Print as image" option should be available to the user in the Print Preview
+// for a PDF job.
+const char kPrintPdfAsImageAvailability[] = "printPdfAsImageAvailability";
+#endif  // defined(OS_WIN) || defined(OS_MAC)
 
 // Get the print job settings dictionary from |json_str|.
 // Returns |base::Value()| on failure.
@@ -383,6 +389,19 @@
   if (!paper_size_policy.DictEmpty())
     policies.SetKey(kMediaSize, std::move(paper_size_policy));
 
+#if defined(OS_WIN) || defined(OS_MAC)
+  base::Value print_as_image_available_for_pdf_policy(
+      base::Value::Type::DICTIONARY);
+  if (prefs.HasPrefPath(prefs::kPrintPdfAsImageAvailability)) {
+    print_as_image_available_for_pdf_policy.SetBoolKey(
+        kAllowedMode, prefs.GetBoolean(prefs::kPrintPdfAsImageAvailability));
+  }
+  if (!print_as_image_available_for_pdf_policy.DictEmpty()) {
+    policies.SetKey(kPrintPdfAsImageAvailability,
+                    std::move(print_as_image_available_for_pdf_policy));
+  }
+#endif  // defined(OS_WIN) || defined(OS_MAC)
+
   return policies;
 }
 #endif  // defined(OS_CHROMEOS)
diff --git a/chrome/browser/ui/webui/settings/site_settings_helper.cc b/chrome/browser/ui/webui/settings/site_settings_helper.cc
index 3adb355..bf5f79be 100644
--- a/chrome/browser/ui/webui/settings/site_settings_helper.cc
+++ b/chrome/browser/ui/webui/settings/site_settings_helper.cc
@@ -160,6 +160,7 @@
     {ContentSettingsType::FEDERATED_IDENTITY_SHARING, nullptr},
     {ContentSettingsType::FEDERATED_IDENTITY_REQUEST, nullptr},
     {ContentSettingsType::JAVASCRIPT_JIT, nullptr},
+    {ContentSettingsType::HTTP_ALLOWED, nullptr},
 };
 
 static_assert(base::size(kContentSettingsTypeGroupNames) ==
diff --git a/chrome/browser/web_applications/components/web_app_utils.h b/chrome/browser/web_applications/components/web_app_utils.h
index d062373..cfbd12f 100644
--- a/chrome/browser/web_applications/components/web_app_utils.h
+++ b/chrome/browser/web_applications/components/web_app_utils.h
@@ -8,6 +8,7 @@
 #include <string>
 #include <vector>
 
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/web_applications/components/web_app_id.h"
 #include "components/services/app_service/public/cpp/file_handler.h"
 
@@ -33,6 +34,8 @@
 bool AreWebAppsEnabled(const Profile* profile);
 // Is user allowed to install web apps from UI:
 bool AreWebAppsUserInstallable(Profile* profile);
+// Can system web apps be installed:
+bool AreSystemWebAppsSupported();
 
 // Get BrowserContext to use for a WebApp KeyedService creation.
 content::BrowserContext* GetBrowserContextForWebApps(
@@ -105,6 +108,12 @@
     const GURL& url,
     bool* found_multiple = nullptr);
 
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+// Enables System Web Apps so we can test SWA features in Lacros, even we don't
+// have actual SWAs in Lacros.
+void EnableSystemWebAppsInLacrosForTesting();
+#endif
+
 }  // namespace web_app
 
 #endif  // CHROME_BROWSER_WEB_APPLICATIONS_COMPONENTS_WEB_APP_UTILS_H_
diff --git a/chrome/browser/web_applications/system_web_apps/test/system_web_app_browsertest_base.cc b/chrome/browser/web_applications/system_web_apps/test/system_web_app_browsertest_base.cc
index 8241d25..3ce3dcd7 100644
--- a/chrome/browser/web_applications/system_web_apps/test/system_web_app_browsertest_base.cc
+++ b/chrome/browser/web_applications/system_web_apps/test/system_web_app_browsertest_base.cc
@@ -12,6 +12,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/web_applications/system_web_app_ui_utils.h"
+#include "chrome/browser/web_applications/components/web_app_utils.h"
 #include "components/services/app_service/public/cpp/types_util.h"
 #include "content/public/test/test_navigation_observer.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -21,7 +22,7 @@
 
 SystemWebAppBrowserTestBase::SystemWebAppBrowserTestBase(bool install_mock) {
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
-  WebAppProvider::EnableSystemWebAppsInLacrosForTesting();
+  EnableSystemWebAppsInLacrosForTesting();
 #endif
 }
 
diff --git a/chrome/browser/web_applications/web_app_provider.cc b/chrome/browser/web_applications/web_app_provider.cc
index 2373cea..db6275b 100644
--- a/chrome/browser/web_applications/web_app_provider.cc
+++ b/chrome/browser/web_applications/web_app_provider.cc
@@ -9,6 +9,7 @@
 #include "base/bind.h"
 #include "base/feature_list.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/web_applications/components/externally_installed_web_app_prefs.h"
 #include "chrome/browser/web_applications/components/install_bounce_metric.h"
@@ -46,10 +47,6 @@
 
 namespace {
 
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-static bool g_enable_system_web_apps_in_lacros_for_testing = false;
-#endif
-
 WebAppProvider::OsIntegrationManagerFactory
     g_os_integration_manager_factory_for_testing = nullptr;
 
@@ -62,21 +59,11 @@
 
 // static
 WebAppProvider* WebAppProvider::GetForSystemWebApps(Profile* profile) {
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-  return g_enable_system_web_apps_in_lacros_for_testing
-             ? WebAppProviderFactory::GetForProfile(profile)
-             : nullptr;
-#else
-  return WebAppProviderFactory::GetForProfile(profile);
-#endif
-}
+  if (!AreSystemWebAppsSupported())
+    return nullptr;
 
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-// static
-void WebAppProvider::EnableSystemWebAppsInLacrosForTesting() {
-  g_enable_system_web_apps_in_lacros_for_testing = true;
+  return WebAppProviderFactory::GetForProfile(profile);
 }
-#endif
 
 // static
 WebAppProvider* WebAppProvider::GetForWebApps(Profile* profile) {
diff --git a/chrome/browser/web_applications/web_app_provider.h b/chrome/browser/web_applications/web_app_provider.h
index cae267f..3992071 100644
--- a/chrome/browser/web_applications/web_app_provider.h
+++ b/chrome/browser/web_applications/web_app_provider.h
@@ -10,7 +10,6 @@
 
 #include "base/memory/weak_ptr.h"
 #include "base/one_shot_event.h"
-#include "build/chromeos_buildflags.h"
 #include "chrome/browser/web_applications/components/web_app_id.h"
 #include "chrome/browser/web_applications/externally_managed_app_manager.h"
 #include "chrome/browser/web_applications/web_app_registrar.h"
@@ -78,13 +77,6 @@
   // non-system Web Apps.
   static WebAppProvider* GetForLocalApps(Profile* profile);
 
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-  // Enables System Web Apps WebAppProvider so we can test SWA features in
-  // Lacros, even we don't have actual SWAs in Lacros. After calling this,
-  // GetForSystemWebApps will return a valid WebAppProvider in Lacros.
-  static void EnableSystemWebAppsInLacrosForTesting();
-#endif
-
   static WebAppProvider* GetForWebContents(content::WebContents* web_contents);
 
   using OsIntegrationManagerFactory =
diff --git a/chrome/browser/web_applications/web_app_utils.cc b/chrome/browser/web_applications/web_app_utils.cc
index eb8aeef7..5fe3c252 100644
--- a/chrome/browser/web_applications/web_app_utils.cc
+++ b/chrome/browser/web_applications/web_app_utils.cc
@@ -9,7 +9,6 @@
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "build/build_config.h"
-#include "build/chromeos_buildflags.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/web_applications/web_app_provider.h"
 #include "chrome/browser/web_applications/web_app_registrar.h"
@@ -26,6 +25,12 @@
 #include "components/user_manager/user_manager.h"
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
+namespace {
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+bool g_enable_system_web_apps_in_lacros_for_testing = false;
+#endif
+}  // namespace
+
 namespace web_app {
 
 constexpr base::FilePath::CharType kManifestResourcesDirectoryName[] =
@@ -66,6 +71,14 @@
          !profile->IsOffTheRecord();
 }
 
+bool AreSystemWebAppsSupported() {
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+  if (!g_enable_system_web_apps_in_lacros_for_testing)
+    return false;
+#endif
+  return true;
+}
+
 content::BrowserContext* GetBrowserContextForWebApps(
     content::BrowserContext* context) {
   // Use original profile to create only one KeyedService instance.
@@ -239,4 +252,10 @@
       l10n_util::GetStringUTF8(IDS_WEB_APP_FILE_HANDLING_LIST_SEPARATOR)));
 }
 
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+void EnableSystemWebAppsInLacrosForTesting() {
+  g_enable_system_web_apps_in_lacros_for_testing = true;
+}
+#endif
+
 }  // namespace web_app
diff --git a/chrome/child/pdf_child_init.cc b/chrome/child/pdf_child_init.cc
index ee0c4d20..a89911d 100644
--- a/chrome/child/pdf_child_init.cc
+++ b/chrome/child/pdf_child_init.cc
@@ -49,7 +49,7 @@
 
 }  // namespace
 
-void MaybeInitializeGDI() {
+void MaybePatchGdiGetFontData() {
 #if defined(OS_WIN)
   // Only patch utility processes which explicitly need GDI.
   sandbox::policy::SandboxType service_sandbox_type =
@@ -59,6 +59,11 @@
       service_sandbox_type == sandbox::policy::SandboxType::kPpapi ||
       service_sandbox_type == sandbox::policy::SandboxType::kPrintCompositor ||
       service_sandbox_type == sandbox::policy::SandboxType::kPdfConversion;
+
+  // TODO(crbug.com/1239330): Only apply to renderers containing PDF content.
+  if (service_sandbox_type == sandbox::policy::SandboxType::kRenderer)
+    need_gdi = true;
+
   if (!need_gdi)
     return;
 
@@ -69,8 +74,8 @@
   HMODULE module = CURRENT_MODULE();
 #endif  // defined(COMPONENT_BUILD)
 
-  // Need to patch GetFontData() for font loading to work correctly. This can be
-  // removed once PDFium switches to use Skia. https://crbug.com/pdfium/11
+  // Need to patch GetFontData() for font loading to work correctly.
+  // TODO(crbug.com/pdfium/11): Can be removed once PDFium switches to use Skia.
   static base::NoDestructor<base::win::IATPatchFunction> patch_get_font_data;
   patch_get_font_data->PatchFromModule(
       module, "gdi32.dll", "GetFontData",
diff --git a/chrome/child/pdf_child_init.h b/chrome/child/pdf_child_init.h
index 051a5d97..30f6b0eb 100644
--- a/chrome/child/pdf_child_init.h
+++ b/chrome/child/pdf_child_init.h
@@ -11,7 +11,7 @@
 #error "PDF must be enabled"
 #endif
 
-// Initializes child-process specific code for the PDF module.
-void MaybeInitializeGDI();
+// Possibly patches GDI's `GetFontData()` for use by PDFium.
+void MaybePatchGdiGetFontData();
 
 #endif  // CHROME_CHILD_PDF_CHILD_INIT_H_
diff --git a/chrome/common/extensions/api/_permission_features.json b/chrome/common/extensions/api/_permission_features.json
index b1c2e216f..58f4f12 100644
--- a/chrome/common/extensions/api/_permission_features.json
+++ b/chrome/common/extensions/api/_permission_features.json
@@ -672,7 +672,7 @@
   },
   "printing": {
     "channel": "stable",
-    "platforms": ["chromeos", "lacros"],
+    "platforms": ["chromeos"],
     "extension_types": ["extension"]
   },
   "printingMetrics": {
diff --git a/chrome/common/extensions/api/api_sources.gni b/chrome/common/extensions/api/api_sources.gni
index f1c3093..542629d 100644
--- a/chrome/common/extensions/api/api_sources.gni
+++ b/chrome/common/extensions/api/api_sources.gni
@@ -74,7 +74,7 @@
   schema_sources_ += [ "processes.idl" ]
 }
 
-if (is_chromeos) {
+if (is_chromeos_ash || is_chromeos_lacros) {
   schema_sources_ += [
     "enterprise_device_attributes.idl",
     "enterprise_networking_attributes.idl",
@@ -83,10 +83,6 @@
     "platform_keys_internal.idl",
     "platform_keys.idl",
   ]
-
-  if (use_cups) {
-    schema_sources_ += [ "printing.idl" ]
-  }
 }
 
 if (is_chromeos_ash) {
@@ -116,7 +112,10 @@
     "wm_desks_private.idl",
   ]
   if (use_cups) {
-    schema_sources_ += [ "printing_metrics.idl" ]
+    schema_sources_ += [
+      "printing.idl",
+      "printing_metrics.idl",
+    ]
   }
 } else if (is_linux || is_chromeos || is_win) {
   schema_sources_ += [ "input_ime.json" ]
diff --git a/chrome/common/extensions/api/printing.idl b/chrome/common/extensions/api/printing.idl
index f301c8b..310408d5 100644
--- a/chrome/common/extensions/api/printing.idl
+++ b/chrome/common/extensions/api/printing.idl
@@ -4,7 +4,7 @@
 
 // Use the <code>chrome.printing</code> API to send print jobs to printers
 // installed on Chromebook.
-[platforms=("chromeos", "lacros"),
+[platforms=("chromeos"),
 implemented_in="chrome/browser/extensions/api/printing/printing_api.h"]
 namespace printing {
   dictionary SubmitJobRequest {
diff --git a/chrome/common/pref_names.cc b/chrome/common/pref_names.cc
index 6fad87c..f97d76c 100644
--- a/chrome/common/pref_names.cc
+++ b/chrome/common/pref_names.cc
@@ -12,6 +12,7 @@
 #include "chrome/common/pref_font_webkit_names.h"
 #include "extensions/buildflags/buildflags.h"
 #include "ppapi/buildflags/buildflags.h"
+#include "printing/buildflags/buildflags.h"
 
 namespace prefs {
 
@@ -1342,6 +1343,19 @@
 const char kPrintPreviewDefaultDestinationSelectionRules[] =
     "printing.default_destination_selection_rules";
 
+#if defined(OS_WIN) || defined(OS_MAC)
+// Boolean controlling whether the "Print as image" option should be available
+// in Print Preview when printing a PDF.
+const char kPrintPdfAsImageAvailability[] =
+    "printing.print_pdf_as_image_availability";
+#endif
+
+#if BUILDFLAG(ENABLE_PRINT_PREVIEW)
+// An integer resolution to use for DPI when rasterizing PDFs with "Print to
+// image".
+const char kPrintRasterizePdfDpi[] = "printing.rasterize_pdf_dpi";
+#endif
+
 #if defined(OS_WIN) && BUILDFLAG(ENABLE_PRINTING)
 // An integer pref that holds the rasterization mode to use when printing.
 const char kPrintRasterizationMode[] = "printing.rasterization_mode";
diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h
index 863fdc4..ffd3876d 100644
--- a/chrome/common/pref_names.h
+++ b/chrome/common/pref_names.h
@@ -438,6 +438,14 @@
 extern const char kPrintPreviewDisabled[];
 extern const char kPrintPreviewDefaultDestinationSelectionRules[];
 
+#if defined(OS_WIN) || defined(OS_MAC)
+extern const char kPrintPdfAsImageAvailability[];
+#endif
+
+#if BUILDFLAG(ENABLE_PRINT_PREVIEW)
+extern const char kPrintRasterizePdfDpi[];
+#endif
+
 #if defined(OS_WIN) && BUILDFLAG(ENABLE_PRINTING)
 extern const char kPrintRasterizationMode[];
 #endif
diff --git a/chrome/renderer/autofill/form_autofill_browsertest.cc b/chrome/renderer/autofill/form_autofill_browsertest.cc
index 89416da..59f2dda 100644
--- a/chrome/renderer/autofill/form_autofill_browsertest.cc
+++ b/chrome/renderer/autofill/form_autofill_browsertest.cc
@@ -5345,8 +5345,8 @@
   WebLocalFrame* frame = GetMainFrame();
   ASSERT_NE(nullptr, frame);
 
-  control_elements = GetUnownedAutofillableFormFieldElements(
-      frame->GetDocument().All(), &fieldsets);
+  control_elements =
+      GetUnownedAutofillableFormFieldElements(frame->GetDocument(), &fieldsets);
   ASSERT_EQ(3U, control_elements.size());
   ASSERT_EQ(2U, fieldsets.size());
 
@@ -5410,8 +5410,8 @@
   WebLocalFrame* frame = GetMainFrame();
   ASSERT_NE(nullptr, frame);
 
-  control_elements = GetUnownedAutofillableFormFieldElements(
-      frame->GetDocument().All(), &fieldsets);
+  control_elements =
+      GetUnownedAutofillableFormFieldElements(frame->GetDocument(), &fieldsets);
   ASSERT_EQ(3U, control_elements.size());
   ASSERT_EQ(1U, fieldsets.size());
 
@@ -5464,8 +5464,8 @@
   WebLocalFrame* frame = GetMainFrame();
   ASSERT_NE(nullptr, frame);
 
-  control_elements = GetUnownedAutofillableFormFieldElements(
-      frame->GetDocument().All(), &fieldsets);
+  control_elements =
+      GetUnownedAutofillableFormFieldElements(frame->GetDocument(), &fieldsets);
   ASSERT_TRUE(control_elements.empty());
   ASSERT_TRUE(fieldsets.empty());
 
@@ -5489,8 +5489,8 @@
   WebLocalFrame* frame = GetMainFrame();
   ASSERT_NE(nullptr, frame);
 
-  control_elements = GetUnownedAutofillableFormFieldElements(
-      frame->GetDocument().All(), &fieldsets);
+  control_elements =
+      GetUnownedAutofillableFormFieldElements(frame->GetDocument(), &fieldsets);
   ASSERT_FALSE(control_elements.empty());
   ASSERT_TRUE(fieldsets.empty());
 
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index a4f4693..0cdde09 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -3414,8 +3414,8 @@
         sources += [
           "../browser/chromeos/extensions/printing_metrics/print_job_finished_event_dispatcher_apitest.cc",
           "../browser/chromeos/extensions/printing_metrics/printing_metrics_apitest.cc",
-          "../browser/extensions/api/printing/fake_print_job_controller_ash.cc",
-          "../browser/extensions/api/printing/fake_print_job_controller_ash.h",
+          "../browser/extensions/api/printing/fake_print_job_controller.cc",
+          "../browser/extensions/api/printing/fake_print_job_controller.h",
           "../browser/extensions/api/printing/printing_apitest.cc",
         ]
         deps += [ "//printing:test_support" ]
@@ -4790,15 +4790,6 @@
       "chromeos/printing/fake_local_printer_chromeos.cc",
       "chromeos/printing/fake_local_printer_chromeos.h",
     ]
-
-    if (use_cups) {
-      sources += [
-        "../browser/chromeos/printing/test_cups_wrapper.cc",
-        "../browser/chromeos/printing/test_cups_wrapper.h",
-        "../browser/extensions/api/printing/fake_print_job_controller.cc",
-        "../browser/extensions/api/printing/fake_print_job_controller.h",
-      ]
-    }
   }
 
   if (is_chromeos_ash) {
@@ -6790,13 +6781,6 @@
       ]
     }
 
-    if (is_chromeos && use_cups) {
-      sources += [
-        "../browser/extensions/api/printing/printing_api_handler_unittest.cc",
-        "../browser/extensions/api/printing/printing_api_utils_unittest.cc",
-      ]
-    }
-
     if (is_chromeos_ash) {
       sources += [
         "../browser/ash/login/easy_unlock/easy_unlock_auth_attempt_unittest.cc",
@@ -7428,6 +7412,7 @@
       "../browser/ui/views/tabs/fake_base_tab_strip_controller.h",
       "../browser/ui/views/tabs/overflow_view_unittest.cc",
       "../browser/ui/views/tabs/stacked_tab_strip_layout_unittest.cc",
+      "../browser/ui/views/tabs/tab_hover_card_bubble_view_unittest.cc",
       "../browser/ui/views/tabs/tab_hover_card_controller_unittest.cc",
       "../browser/ui/views/tabs/tab_hover_card_metrics_unittest.cc",
       "../browser/ui/views/tabs/tab_strip_layout_unittest.cc",
diff --git a/chrome/test/data/policy/policy_test_cases.json b/chrome/test/data/policy/policy_test_cases.json
index 3ad7884..ba0fc2d 100644
--- a/chrome/test/data/policy/policy_test_cases.json
+++ b/chrome/test/data/policy/policy_test_cases.json
@@ -637,6 +637,41 @@
       }
     ]
   },
+  "PrintPdfAsImageAvailability": {
+    "os": [
+      "win",
+      "mac"
+    ],
+    "policy_pref_mapping_tests": [
+      {
+        "policies": {
+          "PrintPdfAsImageAvailability": true
+        },
+        "prefs": {
+          "printing.print_pdf_as_image_availability": {}
+        }
+      }
+    ]
+  },
+  "PrintRasterizePdfDpi": {
+    "os": [
+      "win",
+      "linux",
+      "mac",
+      "chromeos_ash",
+      "chromeos_lacros"
+    ],
+    "policy_pref_mapping_tests": [
+      {
+        "policies": {
+          "PrintRasterizePdfDpi": 300
+        },
+        "prefs": {
+          "printing.rasterize_pdf_dpi": {}
+        }
+      }
+    ]
+  },
   "PrinterTypeDenyList": {
     "os": [
       "win",
diff --git a/chrome/test/data/webui/chromeos/shortcut_customization/shortcut_customization_test.js b/chrome/test/data/webui/chromeos/shortcut_customization/shortcut_customization_test.js
index f926fd5..ff3d04c 100644
--- a/chrome/test/data/webui/chromeos/shortcut_customization/shortcut_customization_test.js
+++ b/chrome/test/data/webui/chromeos/shortcut_customization/shortcut_customization_test.js
@@ -4,11 +4,13 @@
 
 import {getShortcutProvider, setShortcutProviderForTesting} from 'chrome://shortcut-customization/mojo_interface_provider.js';
 import {ShortcutCustomizationAppElement} from 'chrome://shortcut-customization/shortcut_customization_app.js';
-import {ShortcutProviderInterface} from 'chrome://shortcut-customization/shortcut_types.js';
+import {AcceleratorInfo, Modifier, ShortcutProviderInterface} from 'chrome://shortcut-customization/shortcut_types.js';
 
 import {assertEquals, assertFalse, assertTrue} from '../../chai_assert.js';
 import {flushTasks} from '../../test_util.m.js';
 
+import {CreateDefaultAccelerator} from './shortcut_customization_test_util.js';
+
 export function shortcutCustomizationAppTest() {
   /** @type {?ShortcutCustomizationAppElement} */
   let page = null;
@@ -32,18 +34,25 @@
 
   test('DialogOpensOnEvent', async () => {
     await flushTasks();
+
     // The edit dialog should not be stamped and visible.
     let editDialog = page.shadowRoot.querySelector('#editDialog');
     assertFalse(!!editDialog);
 
     const nav = page.shadowRoot.querySelector('navigation-view-panel');
 
+    /** @type {!AcceleratorInfo} */
+    const acceleratorInfo = CreateDefaultAccelerator(
+        Modifier.SHIFT,
+        /*key=*/ 67,
+        /*key_display=*/ 'c');
+
     // Simulate the trigger event to display the dialog.
     nav.dispatchEvent(new CustomEvent('show-edit-dialog', {
       bubbles: true,
       composed: true,
       detail: /**@type {!Object}*/ (
-          {description: 'test', accelerators: [{modifiers: 1 << 1, key: 'c'}]})
+          {description: 'test', accelerators: [acceleratorInfo]})
     }));
     await flushTasks();
 
diff --git a/chrome/test/data/webui/print_preview/model_settings_availability_test.js b/chrome/test/data/webui/print_preview/model_settings_availability_test.js
index 4f09ce82..e491ce6 100644
--- a/chrome/test/data/webui/print_preview/model_settings_availability_test.js
+++ b/chrome/test/data/webui/print_preview/model_settings_availability_test.js
@@ -4,7 +4,7 @@
 
 import {Destination, DestinationConnectionStatus, DestinationOrigin, DestinationType, DuplexType, Margins, MarginsType, PrintPreviewModelElement, Size} from 'chrome://print/print_preview.js';
 import {assert} from 'chrome://resources/js/assert.m.js';
-import {isChromeOS, isLacros, isMac, isWindows} from 'chrome://resources/js/cr.m.js';
+import {isChromeOS, isLacros, isLinux} from 'chrome://resources/js/cr.m.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 
 import {assertEquals, assertFalse, assertTrue} from '../chai_assert.js';
@@ -553,12 +553,15 @@
   });
 
   test('rasterize', function() {
-    assertFalse(model.settings.rasterize.available);
-
-    // Available on non-Windows and Mac for PDFs.
+    // Availability for PDFs varies depening upon OS.
+    // Windows and macOS depend on policy - see policy_test.js for their
+    // testing coverage.
     model.set('documentSettings.isModifiable', false);
-    assertEquals(!isWindows && !isMac, model.settings.rasterize.available);
-    assertFalse(model.settings.rasterize.setFromUi);
+    if (isChromeOS || isLacros || isLinux) {
+      // Always available for PDFs on Linux and ChromeOS
+      assertTrue(model.settings.rasterize.available);
+      assertFalse(model.settings.rasterize.setFromUi);
+    }
 
     // Unavailable for ARC.
     model.set('documentSettings.isFromArc', true);
diff --git a/chrome/test/data/webui/print_preview/policy_test.js b/chrome/test/data/webui/print_preview/policy_test.js
index 1fd6bc2e..063cfa98 100644
--- a/chrome/test/data/webui/print_preview/policy_test.js
+++ b/chrome/test/data/webui/print_preview/policy_test.js
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {BackgroundGraphicsModeRestriction, DuplexMode, NativeLayerImpl, PluginProxyImpl, PrintPreviewAppElement, PrintPreviewPluralStringProxyImpl} from 'chrome://print/print_preview.js';
+import {BackgroundGraphicsModeRestriction, DuplexMode, getInstance, NativeLayerImpl, PluginProxyImpl, PrintPreviewAppElement, PrintPreviewPluralStringProxyImpl} from 'chrome://print/print_preview.js';
 // <if expr="chromeos or lacros">
 import {ColorModeRestriction, DuplexModeRestriction, PinModeRestriction} from 'chrome://print/print_preview.js';
 // </if>
@@ -28,6 +28,7 @@
   ColorPolicy: 'color policy',
   DuplexPolicy: 'duplex policy',
   PinPolicy: 'pin policy',
+  PrintPdfAsImageAvailability: 'print as image available for PDF policy'
 };
 
 class PolicyTestPluralStringProxy extends TestPluralStringProxy {
@@ -85,12 +86,14 @@
    * @param {string} serializedSettingName Name of the serialized setting.
    * @param {*} allowedMode Allowed value for the given setting.
    * @param {*} defaultMode Default value for the given setting.
+   * @param {boolean} isPdf If settings are for previewing a PDF.
    * @return {!Promise} A Promise that resolves once initial settings are done
    *     loading.
    */
   function doAllowedDefaultModePolicySetup(
-      settingName, serializedSettingName, allowedMode, defaultMode) {
-    const initialSettings = getDefaultInitialSettings();
+      settingName, serializedSettingName, allowedMode, defaultMode,
+      isPdf = false) {
+    const initialSettings = getDefaultInitialSettings(isPdf);
 
     if (allowedMode !== undefined || defaultMode !== undefined) {
       const policy = {};
@@ -670,4 +673,70 @@
     }
   });
   // </if>
+
+  // <if expr="is_win or is_macosx">
+  // Tests different scenarios of PDF print as image option policy.
+  // The otherOptions section will be visible for non-PDF cases, and only for
+  // PDF when the policy explicitly allows print as image.
+  test(assert(policy_tests.TestNames.PrintPdfAsImageAvailability), async () => {
+    const tests = [
+      {
+        // No policies with modifiable content.
+        allowedMode: undefined,
+        isPdf: false,
+        otherOptionsHidden: false,
+        expectedAvailable: false,
+      },
+      {
+        // No policies with PDF content.
+        allowedMode: undefined,
+        isPdf: true,
+        otherOptionsHidden: true,
+        expectedAvailable: false,
+      },
+      {
+        // Explicitly restrict "Print as image" option for modifiable content.
+        allowedMode: false,
+        isPdf: false,
+        otherOptionsHidden: false,
+        expectedAvailable: false,
+      },
+      {
+        // Explicitly restrict "Print as image" option for PDF content.
+        allowedMode: false,
+        isPdf: true,
+        otherOptionsHidden: true,
+        expectedAvailable: false,
+      },
+      {
+        // Explicitly enable "Print as image" option for modifiable content.
+        allowedMode: true,
+        isPdf: false,
+        otherOptionsHidden: false,
+        expectedAvailable: false,
+      },
+      {
+        // Explicitly enable "Print as image" option for PDF content.
+        allowedMode: true,
+        isPdf: true,
+        otherOptionsHidden: false,
+        expectedAvailable: true,
+      },
+    ];
+    for (const subtestParams of tests) {
+      await doAllowedDefaultModePolicySetup(
+          'printPdfAsImageAvailability', 'isRasterizeEnabled',
+          subtestParams.allowedMode,
+          /*defaultMode=*/ undefined, /*isPdf=*/ subtestParams.isPdf);
+      toggleMoreSettings();
+      const otherSettingsSection =
+          page.shadowRoot.querySelector('print-preview-sidebar')
+              .$$('print-preview-other-options-settings');
+      const rasterize = getInstance().getSetting('rasterize');
+      expectEquals(
+          subtestParams.otherOptionsHidden, otherSettingsSection.hidden);
+      expectEquals(subtestParams.expectedAvailable, rasterize.available);
+    }
+  });
+  // </if>
 });
diff --git a/chrome/test/data/webui/print_preview/preview_generation_test.js b/chrome/test/data/webui/print_preview/preview_generation_test.js
index 5c07467..d37cc23 100644
--- a/chrome/test/data/webui/print_preview/preview_generation_test.js
+++ b/chrome/test/data/webui/print_preview/preview_generation_test.js
@@ -575,8 +575,9 @@
   });
 
   /**
-   * Validate changing the rasterize setting updates the preview. Only runs
-   * on Linux and CrOS as setting is not available on other platforms.
+   * Validate changing the rasterize setting updates the preview.  Setting is
+   * always available on Linux and CrOS.  Availability on Windows and macOS
+   * depends upon policy (see policy_test.js).
    */
   test(assert(preview_generation_test.TestNames.Rasterize), function() {
     // Set PDF document so setting is available.
diff --git a/chrome/test/data/webui/print_preview/print_preview_test_utils.js b/chrome/test/data/webui/print_preview/print_preview_test_utils.js
index 5ef74389..cf4575f 100644
--- a/chrome/test/data/webui/print_preview/print_preview_test_utils.js
+++ b/chrome/test/data/webui/print_preview/print_preview_test_utils.js
@@ -10,16 +10,19 @@
 import {Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 import {eventToPromise} from '../test_util.m.js';
 
-/** @return {!NativeInitialSettings} */
-export function getDefaultInitialSettings() {
+/**
+ * @param {boolean} is_pdf
+ * @return {!NativeInitialSettings}
+ */
+export function getDefaultInitialSettings(is_pdf = false) {
   return {
     isInKioskAutoPrintMode: false,
     isInAppKioskMode: false,
     pdfPrinterDisabled: false,
     thousandsDelimiter: ',',
     decimalDelimiter: '.',
-    previewIsPdf: false,
-    previewModifiable: true,
+    previewIsPdf: is_pdf,
+    previewModifiable: !is_pdf,
     documentTitle: 'title',
     documentHasSelection: true,
     shouldPrintSelectionOnly: false,
diff --git a/chrome/test/data/webui/print_preview/print_preview_ui_browsertest.js b/chrome/test/data/webui/print_preview/print_preview_ui_browsertest.js
index 7168b75..be6826a 100644
--- a/chrome/test/data/webui/print_preview/print_preview_ui_browsertest.js
+++ b/chrome/test/data/webui/print_preview/print_preview_ui_browsertest.js
@@ -249,6 +249,12 @@
 });
 GEN('#endif');
 
+GEN('#if defined(OS_WIN) || defined(OS_MAC)');
+TEST_F('PrintPreviewPolicyTest', 'PrintPdfAsImageAvailability', function() {
+  this.runMochaTest(policy_tests.TestNames.PrintPdfAsImageAvailability);
+});
+GEN('#endif');
+
 // eslint-disable-next-line no-var
 var PrintPreviewSettingsSelectTest = class extends PrintPreviewTest {
   /** @override */
diff --git a/chrome/test/data/webui/read_later/side_panel/bookmark_folder_test.js b/chrome/test/data/webui/read_later/side_panel/bookmark_folder_test.js
index 403b4b6..cd794d5 100644
--- a/chrome/test/data/webui/read_later/side_panel/bookmark_folder_test.js
+++ b/chrome/test/data/webui/read_later/side_panel/bookmark_folder_test.js
@@ -70,10 +70,7 @@
   test('UpdatesDepthVariables', () => {
     bookmarkFolder.depth = 3;
     assertEquals('3', bookmarkFolder.style.getPropertyValue('--node-depth'));
-    assertEquals(
-        '4',
-        bookmarkFolder.shadowRoot.querySelector('#children')
-            .style.getPropertyValue('--node-depth'));
+    assertEquals('4', bookmarkFolder.style.getPropertyValue('--child-depth'));
   });
 
   test('RendersChildren', () => {
@@ -85,10 +82,10 @@
 
     assertEquals(
         folder.children[1].title,
-        childElements[1].querySelector('.title').innerText);
+        childElements[1].querySelector('.title').textContent);
     assertEquals(
         folder.children[2].title,
-        childElements[2].querySelector('.title').innerText);
+        childElements[2].querySelector('.title').textContent);
   });
 
   test('ShowsFaviconForBookmarks', () => {
diff --git a/chrome/test/data/webui/read_later/side_panel/bookmarks_list_test.js b/chrome/test/data/webui/read_later/side_panel/bookmarks_list_test.js
index 3149ef6..c69be5f3 100644
--- a/chrome/test/data/webui/read_later/side_panel/bookmarks_list_test.js
+++ b/chrome/test/data/webui/read_later/side_panel/bookmarks_list_test.js
@@ -111,7 +111,7 @@
 
     const folderElement = getFolderElements(bookmarksList)[rootFolderIndex];
     const bookmarkElement = getBookmarkElements(folderElement)[bookmarkIndex];
-    assertEquals('New title', bookmarkElement.innerText);
+    assertEquals('New title', bookmarkElement.textContent);
     assertEquals('http://new/url', bookmarkElement.href);
   });
 
@@ -162,7 +162,7 @@
 
     const bookmarksBarFolder = getFolderElements(bookmarksList)[0];
     const movedBookmarkElement = getBookmarkElements(bookmarksBarFolder)[0];
-    assertEquals('Nested bookmark', movedBookmarkElement.innerText);
+    assertEquals('Nested bookmark', movedBookmarkElement.textContent);
 
     const childFolder = getFolderElements(bookmarksBarFolder)[0];
     const childFolderBookmarks = getBookmarkElements(childFolder);
diff --git a/chrome/test/data/webui/settings/a11y/passwords_a11y_test.js b/chrome/test/data/webui/settings/a11y/passwords_a11y_test.js
index d6c9b3e..0fc61f0b 100644
--- a/chrome/test/data/webui/settings/a11y/passwords_a11y_test.js
+++ b/chrome/test/data/webui/settings/a11y/passwords_a11y_test.js
@@ -21,7 +21,7 @@
   // APIs for fake data.
   window.history.pushState('object or string', 'Test', routes.PASSWORDS.path);
 
-  PasswordManagerImpl.instance_ = new TestPasswordManagerProxy();
+  PasswordManagerImpl.setInstance(new TestPasswordManagerProxy());
   passwordManager = PasswordManagerImpl.getInstance();
 
   settingsUi = document.createElement('settings-ui');
diff --git a/chrome/test/data/webui/settings/advanced_page_test.js b/chrome/test/data/webui/settings/advanced_page_test.js
index 9957507..f2aca8c 100644
--- a/chrome/test/data/webui/settings/advanced_page_test.js
+++ b/chrome/test/data/webui/settings/advanced_page_test.js
@@ -62,9 +62,9 @@
     const main = stampedChildren.filter(function(element) {
       return element.getAttribute('route-path') === 'default';
     });
+    const sectionName = /** @type {{section: string}} */ (section).section;
     assertEquals(
-        main.length, 1,
-        'default card not found for section ' + section.section);
+        main.length, 1, 'default card not found for section ' + sectionName);
     assertGT(main[0].offsetHeight, 0);
 
     // Any other stamped subpages should not be visible.
@@ -74,7 +74,7 @@
     for (const subpage of subpages) {
       assertEquals(
           subpage.offsetHeight, 0,
-          'Expected subpage #' + subpage.id + ' in ' + section.section +
+          'Expected subpage #' + subpage.id + ' in ' + sectionName +
               ' not to be visible.');
     }
   }
diff --git a/chrome/test/data/webui/settings/autofill_page_test.js b/chrome/test/data/webui/settings/autofill_page_test.js
index 73e483e48..959b7ae 100644
--- a/chrome/test/data/webui/settings/autofill_page_test.js
+++ b/chrome/test/data/webui/settings/autofill_page_test.js
@@ -143,7 +143,7 @@
 
     // Override the PasswordManagerImpl for testing.
     passwordManager = new TestPasswordManagerProxy();
-    PasswordManagerImpl.instance_ = passwordManager;
+    PasswordManagerImpl.setInstance(passwordManager);
 
     // Override the AutofillManagerImpl for testing.
     autofillManager = new TestAutofillManager();
@@ -310,10 +310,10 @@
 
   setup(function() {
     openWindowProxy = new TestOpenWindowProxy();
-    OpenWindowProxyImpl.instance_ = openWindowProxy;
+    OpenWindowProxyImpl.setInstance(openWindowProxy);
     // Override the PasswordManagerImpl for testing.
     passwordManager = new TestPasswordManagerProxy();
-    PasswordManagerImpl.instance_ = passwordManager;
+    PasswordManagerImpl.setInstance(passwordManager);
     pluralString = new TestPluralStringProxy();
     SettingsPluralStringProxyImpl.setInstance(pluralString);
 
diff --git a/chrome/test/data/webui/settings/chrome_cleanup_page_test.js b/chrome/test/data/webui/settings/chrome_cleanup_page_test.js
index 563ea8d..b26a400 100644
--- a/chrome/test/data/webui/settings/chrome_cleanup_page_test.js
+++ b/chrome/test/data/webui/settings/chrome_cleanup_page_test.js
@@ -271,7 +271,7 @@
 suite('ChromeCleanupHandler', function() {
   setup(function() {
     chromeCleanupProxy = new TestChromeCleanupProxy();
-    ChromeCleanupProxyImpl.instance_ = chromeCleanupProxy;
+    ChromeCleanupProxyImpl.setInstance(chromeCleanupProxy);
 
     PolymerTest.clearBody();
 
diff --git a/chrome/test/data/webui/settings/cookies_page_test.js b/chrome/test/data/webui/settings/cookies_page_test.js
index 80eddae8..1b2d55c8 100644
--- a/chrome/test/data/webui/settings/cookies_page_test.js
+++ b/chrome/test/data/webui/settings/cookies_page_test.js
@@ -36,7 +36,7 @@
 
   setup(function() {
     testMetricsBrowserProxy = new TestMetricsBrowserProxy();
-    MetricsBrowserProxyImpl.instance_ = testMetricsBrowserProxy;
+    MetricsBrowserProxyImpl.setInstance(testMetricsBrowserProxy);
     siteSettingsBrowserProxy = new TestSiteSettingsPrefsBrowserProxy();
     SiteSettingsPrefsBrowserProxyImpl.instance_ = siteSettingsBrowserProxy;
     document.body.innerHTML = '';
@@ -265,7 +265,7 @@
 
   setup(function() {
     testMetricsBrowserProxy = new TestMetricsBrowserProxy();
-    MetricsBrowserProxyImpl.instance_ = testMetricsBrowserProxy;
+    MetricsBrowserProxyImpl.setInstance(testMetricsBrowserProxy);
     document.body.innerHTML = '';
     page = /** @type {!SettingsCookiesPageElement} */ (
         document.createElement('settings-cookies-page'));
diff --git a/chrome/test/data/webui/settings/default_browser_browsertest.js b/chrome/test/data/webui/settings/default_browser_browsertest.js
index 8843a319..8776f15f 100644
--- a/chrome/test/data/webui/settings/default_browser_browsertest.js
+++ b/chrome/test/data/webui/settings/default_browser_browsertest.js
@@ -59,7 +59,7 @@
 
   setup(function() {
     browserProxy = new TestDefaultBrowserBrowserProxy();
-    DefaultBrowserBrowserProxyImpl.instance_ = browserProxy;
+    DefaultBrowserBrowserProxyImpl.setInstance(browserProxy);
     return initPage();
   });
 
diff --git a/chrome/test/data/webui/settings/do_not_track_toggle_test.js b/chrome/test/data/webui/settings/do_not_track_toggle_test.js
index 53d90d0..8a7f82f0 100644
--- a/chrome/test/data/webui/settings/do_not_track_toggle_test.js
+++ b/chrome/test/data/webui/settings/do_not_track_toggle_test.js
@@ -22,7 +22,7 @@
 
   setup(function() {
     testMetricsBrowserProxy = new TestMetricsBrowserProxy();
-    MetricsBrowserProxyImpl.instance_ = testMetricsBrowserProxy;
+    MetricsBrowserProxyImpl.setInstance(testMetricsBrowserProxy);
     document.body.innerHTML = '';
     testElement = /** @type {SettingsDoNotTrackToggleElement} */ (
         document.createElement('settings-do-not-track-toggle'));
diff --git a/chrome/test/data/webui/settings/password_check_test.js b/chrome/test/data/webui/settings/password_check_test.js
index d0fe33c..eb51e67 100644
--- a/chrome/test/data/webui/settings/password_check_test.js
+++ b/chrome/test/data/webui/settings/password_check_test.js
@@ -212,7 +212,7 @@
     PolymerTest.clearBody();
     // Override the PasswordManagerImpl for testing.
     passwordManager = new TestPasswordManagerProxy();
-    PasswordManagerImpl.instance_ = passwordManager;
+    PasswordManagerImpl.setInstance(passwordManager);
 
     // Override the SyncBrowserProxyImpl for testing.
     syncBrowserProxy = new TestSyncBrowserProxy();
@@ -462,7 +462,7 @@
   // records a corresponding user action.
   test('changePasswordOpensUrlAndRecordsAction', async function() {
     const testOpenWindowProxy = new TestOpenWindowProxy();
-    OpenWindowProxyImpl.instance_ = testOpenWindowProxy;
+    OpenWindowProxyImpl.setInstance(testOpenWindowProxy);
 
     const password = makeCompromisedCredential('one.com', 'test4', 'LEAKED');
     const passwordCheckListItem = createLeakedPasswordItem(password);
diff --git a/chrome/test/data/webui/settings/passwords_device_section_test.js b/chrome/test/data/webui/settings/passwords_device_section_test.js
index 6834232..4d4f1a4 100644
--- a/chrome/test/data/webui/settings/passwords_device_section_test.js
+++ b/chrome/test/data/webui/settings/passwords_device_section_test.js
@@ -74,7 +74,7 @@
   setup(function() {
     PolymerTest.clearBody();
     passwordManager = new TestPasswordManagerProxy();
-    PasswordManagerImpl.instance_ = passwordManager;
+    PasswordManagerImpl.setInstance(passwordManager);
     syncBrowserProxy = new TestSyncBrowserProxy();
     SyncBrowserProxyImpl.setInstance(syncBrowserProxy);
     elementFactory = new PasswordDeviceSectionElementFactory(document);
diff --git a/chrome/test/data/webui/settings/passwords_section_test.js b/chrome/test/data/webui/settings/passwords_section_test.js
index 4082e0cd..cbb413bb 100644
--- a/chrome/test/data/webui/settings/passwords_section_test.js
+++ b/chrome/test/data/webui/settings/passwords_section_test.js
@@ -336,9 +336,9 @@
     pluralString = new TestPluralStringProxy();
     SettingsPluralStringProxyImpl.setInstance(pluralString);
     testHatsBrowserProxy = new TestHatsBrowserProxy();
-    HatsBrowserProxyImpl.instance_ = testHatsBrowserProxy;
+    HatsBrowserProxyImpl.setInstance(testHatsBrowserProxy);
 
-    PasswordManagerImpl.instance_ = passwordManager;
+    PasswordManagerImpl.setInstance(passwordManager);
     elementFactory = new PasswordSectionElementFactory(document);
   });
 
diff --git a/chrome/test/data/webui/settings/passwords_section_test_cros.js b/chrome/test/data/webui/settings/passwords_section_test_cros.js
index 14d14689c..f8555f4 100644
--- a/chrome/test/data/webui/settings/passwords_section_test_cros.js
+++ b/chrome/test/data/webui/settings/passwords_section_test_cros.js
@@ -114,7 +114,7 @@
     PolymerTest.clearBody();
     // Override the PasswordManagerImpl for testing.
     passwordManager = new TestPasswordManagerProxy();
-    PasswordManagerImpl.instance_ = passwordManager;
+    PasswordManagerImpl.setInstance(passwordManager);
 
     // Define a fake BlockingRequestManager to track when a token request
     // comes in by resolving requestPromise.
diff --git a/chrome/test/data/webui/settings/payments_section_test.js b/chrome/test/data/webui/settings/payments_section_test.js
index 4cb2fb5e..f21a4c0 100644
--- a/chrome/test/data/webui/settings/payments_section_test.js
+++ b/chrome/test/data/webui/settings/payments_section_test.js
@@ -599,7 +599,7 @@
 
   test('CanMakePaymentToggle_RecordsMetrics', async function() {
     const testMetricsBrowserProxy = new TestMetricsBrowserProxy();
-    MetricsBrowserProxyImpl.instance_ = testMetricsBrowserProxy;
+    MetricsBrowserProxyImpl.setInstance(testMetricsBrowserProxy);
 
     const section = createPaymentsSection(
         /*creditCards=*/[], /*upiIds=*/[], /*prefValues=*/ {});
diff --git a/chrome/test/data/webui/settings/privacy_page_test.js b/chrome/test/data/webui/settings/privacy_page_test.js
index 0c6efcd..f954946d 100644
--- a/chrome/test/data/webui/settings/privacy_page_test.js
+++ b/chrome/test/data/webui/settings/privacy_page_test.js
@@ -200,7 +200,7 @@
 
   setup(function() {
     metricsBrowserProxy = new TestMetricsBrowserProxy();
-    MetricsBrowserProxyImpl.instance_ = metricsBrowserProxy;
+    MetricsBrowserProxyImpl.setInstance(metricsBrowserProxy);
 
     document.body.innerHTML = '';
     page = /** @type {!SettingsPrivacyPageElement} */
@@ -449,7 +449,7 @@
 
   setup(function() {
     testHatsBrowserProxy = new TestHatsBrowserProxy();
-    HatsBrowserProxyImpl.instance_ = testHatsBrowserProxy;
+    HatsBrowserProxyImpl.setInstance(testHatsBrowserProxy);
     document.body.innerHTML = '';
     page = /** @type {!SettingsPrivacyPageElement} */
         (document.createElement('settings-privacy-page'));
diff --git a/chrome/test/data/webui/settings/privacy_sandbox_test.js b/chrome/test/data/webui/settings/privacy_sandbox_test.js
index e51f3028..c6110d0 100644
--- a/chrome/test/data/webui/settings/privacy_sandbox_test.js
+++ b/chrome/test/data/webui/settings/privacy_sandbox_test.js
@@ -37,15 +37,15 @@
 
   setup(function() {
     testHatsBrowserProxy = new TestHatsBrowserProxy();
-    HatsBrowserProxyImpl.instance_ = testHatsBrowserProxy;
+    HatsBrowserProxyImpl.setInstance(testHatsBrowserProxy);
 
     metricsBrowserProxy = new TestMetricsBrowserProxy();
-    MetricsBrowserProxyImpl.instance_ = metricsBrowserProxy;
+    MetricsBrowserProxyImpl.setInstance(metricsBrowserProxy);
 
     CrSettingsPrefs.deferInitialization = true;
 
     openWindowProxy = new TestOpenWindowProxy();
-    OpenWindowProxyImpl.instance_ = openWindowProxy;
+    OpenWindowProxyImpl.setInstance(openWindowProxy);
 
     document.body.innerHTML = '';
     page = /** @type {!PrivacySandboxAppElement} */
@@ -153,7 +153,7 @@
     document.body.innerHTML = '';
 
     testMetricsBrowserProxy = new TestMetricsBrowserProxy();
-    MetricsBrowserProxyImpl.instance_ = testMetricsBrowserProxy;
+    MetricsBrowserProxyImpl.setInstance(testMetricsBrowserProxy);
 
     testPrivacySandboxBrowserProxy =
         TestBrowserProxy.fromClass(PrivacySandboxBrowserProxy);
diff --git a/chrome/test/data/webui/settings/safety_check_chrome_cleaner_test.js b/chrome/test/data/webui/settings/safety_check_chrome_cleaner_test.js
index ab291823..690b8a6b 100644
--- a/chrome/test/data/webui/settings/safety_check_chrome_cleaner_test.js
+++ b/chrome/test/data/webui/settings/safety_check_chrome_cleaner_test.js
@@ -105,10 +105,10 @@
     chromeCleanupBrowserProxy = TestBrowserProxy.fromClass(ChromeCleanupProxy);
     chromeCleanupBrowserProxy.setResultFor(
         'restartComputer', Promise.resolve(0));
-    ChromeCleanupProxyImpl.instance_ = chromeCleanupBrowserProxy;
+    ChromeCleanupProxyImpl.setInstance(chromeCleanupBrowserProxy);
 
     metricsBrowserProxy = new TestMetricsBrowserProxy();
-    MetricsBrowserProxyImpl.instance_ = metricsBrowserProxy;
+    MetricsBrowserProxyImpl.setInstance(metricsBrowserProxy);
 
     document.body.innerHTML = '';
     page = /** @type {!SettingsSafetyCheckChromeCleanerChildElement} */ (
diff --git a/chrome/test/data/webui/settings/safety_check_page_test.js b/chrome/test/data/webui/settings/safety_check_page_test.js
index b458e63..9742d14 100644
--- a/chrome/test/data/webui/settings/safety_check_page_test.js
+++ b/chrome/test/data/webui/settings/safety_check_page_test.js
@@ -173,7 +173,7 @@
 
   setup(function() {
     metricsBrowserProxy = new TestMetricsBrowserProxy();
-    MetricsBrowserProxyImpl.instance_ = metricsBrowserProxy;
+    MetricsBrowserProxyImpl.setInstance(metricsBrowserProxy);
 
     safetyCheckBrowserProxy = new TestSafetyCheckBrowserProxy();
     safetyCheckBrowserProxy.setParentRanDisplayString('Dummy string');
@@ -253,7 +253,7 @@
 
   test('HappinessTrackingSurveysTest', async function() {
     const testHatsBrowserProxy = new TestHatsBrowserProxy();
-    HatsBrowserProxyImpl.instance_ = testHatsBrowserProxy;
+    HatsBrowserProxyImpl.setInstance(testHatsBrowserProxy);
     page.shadowRoot.querySelector('#safetyCheckParentButton').click();
     const interaction =
         await testHatsBrowserProxy.whenCalled('trustSafetyInteractionOccurred');
@@ -419,7 +419,7 @@
     lifetimeBrowserProxy = new TestLifetimeBrowserProxy();
     LifetimeBrowserProxyImpl.setInstance(lifetimeBrowserProxy);
     metricsBrowserProxy = new TestMetricsBrowserProxy();
-    MetricsBrowserProxyImpl.instance_ = metricsBrowserProxy;
+    MetricsBrowserProxyImpl.setInstance(metricsBrowserProxy);
 
     document.body.innerHTML = '';
     page = /** @type {!SettingsSafetyCheckUpdatesChildElement} */ (
@@ -541,7 +541,7 @@
 
   setup(function() {
     metricsBrowserProxy = new TestMetricsBrowserProxy();
-    MetricsBrowserProxyImpl.instance_ = metricsBrowserProxy;
+    MetricsBrowserProxyImpl.setInstance(metricsBrowserProxy);
 
     document.body.innerHTML = '';
     page = /** @type {!SettingsSafetyCheckPasswordsChildElement} */ (
@@ -603,7 +603,7 @@
     });
 
     const passwordManager = new TestPasswordManagerProxy();
-    PasswordManagerImpl.instance_ = passwordManager;
+    PasswordManagerImpl.setInstance(passwordManager);
 
     // User clicks the manage passwords button.
     page.shadowRoot.querySelector('#safetyCheckChild')
@@ -698,7 +698,7 @@
 
   setup(function() {
     metricsBrowserProxy = new TestMetricsBrowserProxy();
-    MetricsBrowserProxyImpl.instance_ = metricsBrowserProxy;
+    MetricsBrowserProxyImpl.setInstance(metricsBrowserProxy);
 
     document.body.innerHTML = '';
     page = /** @type {!SettingsSafetyCheckSafeBrowsingChildElement} */ (
@@ -838,9 +838,9 @@
 
   setup(function() {
     metricsBrowserProxy = new TestMetricsBrowserProxy();
-    MetricsBrowserProxyImpl.instance_ = metricsBrowserProxy;
+    MetricsBrowserProxyImpl.setInstance(metricsBrowserProxy);
     openWindowProxy = new TestOpenWindowProxy();
-    OpenWindowProxyImpl.instance_ = openWindowProxy;
+    OpenWindowProxyImpl.setInstance(openWindowProxy);
 
     document.body.innerHTML = '';
     page = /** @type {!SettingsSafetyCheckExtensionsChildElement} */ (
diff --git a/chrome/test/data/webui/settings/security_page_test.js b/chrome/test/data/webui/settings/security_page_test.js
index 2f3889ce..d04cf287 100644
--- a/chrome/test/data/webui/settings/security_page_test.js
+++ b/chrome/test/data/webui/settings/security_page_test.js
@@ -35,7 +35,7 @@
 
   setup(function() {
     testMetricsBrowserProxy = new TestMetricsBrowserProxy();
-    MetricsBrowserProxyImpl.instance_ = testMetricsBrowserProxy;
+    MetricsBrowserProxyImpl.setInstance(testMetricsBrowserProxy);
     testPrivacyBrowserProxy = new TestPrivacyPageBrowserProxy();
     PrivacyPageBrowserProxyImpl.instance_ = testPrivacyBrowserProxy;
     document.body.innerHTML = '';
diff --git a/chrome/test/data/webui/settings/settings_page_test_util.js b/chrome/test/data/webui/settings/settings_page_test_util.js
index 10d42e8..d0f6340 100644
--- a/chrome/test/data/webui/settings/settings_page_test_util.js
+++ b/chrome/test/data/webui/settings/settings_page_test_util.js
@@ -43,7 +43,7 @@
   assertTrue(!!sections);
   for (let i = 0; i < sections.length; ++i) {
     const s = sections[i];
-    if (s.section === section) {
+    if (/** @type {{section: string}} */ (s).section === section) {
       return s;
     }
   }
diff --git a/chrome/test/data/webui/settings/site_data_details_subpage_tests.js b/chrome/test/data/webui/settings/site_data_details_subpage_tests.js
index 2dec303..4e5f2a7 100644
--- a/chrome/test/data/webui/settings/site_data_details_subpage_tests.js
+++ b/chrome/test/data/webui/settings/site_data_details_subpage_tests.js
@@ -49,7 +49,7 @@
     browserProxy.setCookieDetails([cookieDetails]);
     LocalDataBrowserProxyImpl.instance_ = browserProxy;
     testMetricsBrowserProxy = new TestMetricsBrowserProxy();
-    MetricsBrowserProxyImpl.instance_ = testMetricsBrowserProxy;
+    MetricsBrowserProxyImpl.setInstance(testMetricsBrowserProxy);
     PolymerTest.clearBody();
     page = document.createElement('site-data-details-subpage');
     Router.getInstance().navigateTo(
diff --git a/chrome/test/data/webui/settings/site_data_test.js b/chrome/test/data/webui/settings/site_data_test.js
index fb5cff40..d5edf7d 100644
--- a/chrome/test/data/webui/settings/site_data_test.js
+++ b/chrome/test/data/webui/settings/site_data_test.js
@@ -26,7 +26,7 @@
   setup(function() {
     Router.getInstance().navigateTo(routes.SITE_SETTINGS);
     testMetricsBrowserProxy = new TestMetricsBrowserProxy();
-    MetricsBrowserProxyImpl.instance_ = testMetricsBrowserProxy;
+    MetricsBrowserProxyImpl.setInstance(testMetricsBrowserProxy);
     testBrowserProxy = new TestLocalDataBrowserProxy();
     LocalDataBrowserProxyImpl.instance_ = testBrowserProxy;
     siteData = document.createElement('site-data');
diff --git a/chrome/test/data/webui/settings/site_details_tests.js b/chrome/test/data/webui/settings/site_details_tests.js
index c2113d2..3213a401 100644
--- a/chrome/test/data/webui/settings/site_details_tests.js
+++ b/chrome/test/data/webui/settings/site_details_tests.js
@@ -180,7 +180,7 @@
     browserProxy = new TestSiteSettingsPrefsBrowserProxy();
     SiteSettingsPrefsBrowserProxyImpl.instance_ = browserProxy;
     testMetricsBrowserProxy = new TestMetricsBrowserProxy();
-    MetricsBrowserProxyImpl.instance_ = testMetricsBrowserProxy;
+    MetricsBrowserProxyImpl.setInstance(testMetricsBrowserProxy);
     websiteUsageProxy = new TestWebsiteUsageBrowserProxy();
     WebsiteUsageBrowserProxyImpl.instance_ = websiteUsageProxy;
 
diff --git a/chromecast/metrics/cast_metrics_service_client.cc b/chromecast/metrics/cast_metrics_service_client.cc
index 225ec9f..fc8973f3 100644
--- a/chromecast/metrics/cast_metrics_service_client.cc
+++ b/chromecast/metrics/cast_metrics_service_client.cc
@@ -9,6 +9,7 @@
 #include "base/bind.h"
 #include "base/callback.h"
 #include "base/command_line.h"
+#include "base/files/file_path.h"
 #include "base/guid.h"
 #include "base/i18n/rtl.h"
 #include "base/logging.h"
@@ -301,6 +302,9 @@
   DCHECK(!metrics_state_manager_);
   metrics_state_manager_ = ::metrics::MetricsStateManager::Create(
       pref_service_, this, std::wstring(),
+      // Pass an empty file path since Chromecast does not use the Variations
+      // framework.
+      /*user_data_dir=*/base::FilePath(),
       base::BindRepeating(&CastMetricsServiceClient::StoreClientInfo,
                           base::Unretained(this)),
       base::BindRepeating(&CastMetricsServiceClient::LoadClientInfo,
diff --git a/chromeos/components/proximity_auth/proximity_auth_system.cc b/chromeos/components/proximity_auth/proximity_auth_system.cc
index 1ddae7b..eb3b356 100644
--- a/chromeos/components/proximity_auth/proximity_auth_system.cc
+++ b/chromeos/components/proximity_auth/proximity_auth_system.cc
@@ -187,4 +187,11 @@
   }
 }
 
+std::string ProximityAuthSystem::GetLastRemoteStatusUnlockForLogging() {
+  if (unlock_manager_) {
+    return unlock_manager_->GetLastRemoteStatusUnlockForLogging();
+  }
+  return std::string();
+}
+
 }  // namespace proximity_auth
diff --git a/chromeos/components/proximity_auth/proximity_auth_system.h b/chromeos/components/proximity_auth/proximity_auth_system.h
index bdc3097..448cd2a 100644
--- a/chromeos/components/proximity_auth/proximity_auth_system.h
+++ b/chromeos/components/proximity_auth/proximity_auth_system.h
@@ -74,6 +74,12 @@
   // Called in order to disable attempts to get RemoteStatus from host devices.
   void CancelConnectionAttempt();
 
+  // The last value emitted to the SmartLock.GetRemoteStatus.Unlock(.Failure)
+  // metrics. Helps to understand whether/why not Smart Lock was an available
+  // choice for unlock. Returns the empty string if |unlock_manager_| is
+  // nullptr.
+  std::string GetLastRemoteStatusUnlockForLogging();
+
  protected:
   // Constructor which allows passing in a custom |unlock_manager_|.
   // Exposed for testing.
diff --git a/chromeos/components/proximity_auth/proximity_auth_system_unittest.cc b/chromeos/components/proximity_auth/proximity_auth_system_unittest.cc
index d8c6119..7573ee38 100644
--- a/chromeos/components/proximity_auth/proximity_auth_system_unittest.cc
+++ b/chromeos/components/proximity_auth/proximity_auth_system_unittest.cc
@@ -70,6 +70,7 @@
   MOCK_METHOD1(SetRemoteDeviceLifeCycle, void(RemoteDeviceLifeCycle*));
   MOCK_METHOD1(OnAuthAttempted, void(mojom::AuthType));
   MOCK_METHOD0(CancelConnectionAttempt, void());
+  MOCK_METHOD0(GetLastRemoteStatusUnlockForLogging, std::string());
 
  private:
   DISALLOW_COPY_AND_ASSIGN(MockUnlockManager);
diff --git a/chromeos/components/proximity_auth/unlock_manager.h b/chromeos/components/proximity_auth/unlock_manager.h
index 5a29996..f13e4591 100644
--- a/chromeos/components/proximity_auth/unlock_manager.h
+++ b/chromeos/components/proximity_auth/unlock_manager.h
@@ -32,6 +32,11 @@
 
   // Disable attempts to get RemoteStatus from host devices.
   virtual void CancelConnectionAttempt() = 0;
+
+  // The last value emitted to the SmartLock.GetRemoteStatus.Unlock(.Failure)
+  // metrics. Helps to understand whether/why not Smart Lock was an available
+  // choice for unlock.
+  virtual std::string GetLastRemoteStatusUnlockForLogging() = 0;
 };
 
 }  // namespace proximity_auth
diff --git a/chromeos/components/proximity_auth/unlock_manager_impl.cc b/chromeos/components/proximity_auth/unlock_manager_impl.cc
index b2ed989..e17a288 100644
--- a/chromeos/components/proximity_auth/unlock_manager_impl.cc
+++ b/chromeos/components/proximity_auth/unlock_manager_impl.cc
@@ -38,20 +38,6 @@
   kMaxValue = kTimedOut
 };
 
-// This enum is tied directly to a UMA enum defined in
-// //tools/metrics/histograms/enums.xml, and should always reflect it (do not
-// change one without changing the other). Entries should be never modified
-// or deleted. Only additions possible.
-enum class GetRemoteStatusResultFailureReason {
-  kCanceledBluetoothDisabled = 0,
-  kDeprecatedTimedOutCouldNotEstablishAuthenticatedChannel = 1,
-  kTimedOutDidNotReceiveRemoteStatusUpdate = 2,
-  kDeprecatedUserEnteredPasswordWhileBluetoothDisabled = 3,
-  kCanceledUserEnteredPassword = 4,
-  kAuthenticatedChannelDropped = 5,
-  kMaxValue = kAuthenticatedChannelDropped
-};
-
 // The maximum amount of time that the unlock manager can stay in the 'waking
 // up' state after resuming from sleep.
 constexpr base::TimeDelta kWakingUpDuration = base::TimeDelta::FromSeconds(15);
@@ -76,6 +62,9 @@
     base::TimeDelta::FromSeconds(15);
 const int kNumDurationMetricBuckets = 100;
 
+const char kGetRemoteStatusNone[] = "none";
+const char kGetRemoteStatusSuccess[] = "success";
+
 // Returns the remote device's security settings state, for metrics,
 // corresponding to a remote status update.
 metrics::RemoteSecuritySettingsState GetRemoteSecuritySettingsState(
@@ -134,24 +123,6 @@
       result);
 }
 
-void RecordGetRemoteStatusResultSuccess(
-    ProximityAuthSystem::ScreenlockType screenlock_type,
-    bool success = true) {
-  base::UmaHistogramBoolean("SmartLock.GetRemoteStatus." +
-                                GetHistogramScreenLockTypeName(screenlock_type),
-                            success);
-}
-
-void RecordGetRemoteStatusResultFailure(
-    ProximityAuthSystem::ScreenlockType screenlock_type,
-    GetRemoteStatusResultFailureReason failure_reason) {
-  RecordGetRemoteStatusResultSuccess(screenlock_type, false /* success */);
-  base::UmaHistogramEnumeration(
-      "SmartLock.GetRemoteStatus." +
-          GetHistogramScreenLockTypeName(screenlock_type) + ".Failure",
-      failure_reason);
-}
-
 void RecordAuthResultFailure(
     ProximityAuthSystem::ScreenlockType screenlock_type,
     SmartLockMetricsRecorder::SmartLockAuthResultFailureReason failure_reason) {
@@ -910,4 +881,68 @@
   bluetooth_suspension_recovery_timer_ = std::move(timer);
 }
 
+void UnlockManagerImpl::RecordGetRemoteStatusResultSuccess(
+    ProximityAuthSystem::ScreenlockType screenlock_type,
+    bool success) {
+  base::UmaHistogramBoolean("SmartLock.GetRemoteStatus." +
+                                GetHistogramScreenLockTypeName(screenlock_type),
+                            success);
+
+  if (screenlock_type == ProximityAuthSystem::SESSION_LOCK) {
+    get_remote_status_unlock_success_ = success;
+  }
+}
+
+void UnlockManagerImpl::RecordGetRemoteStatusResultFailure(
+    ProximityAuthSystem::ScreenlockType screenlock_type,
+    GetRemoteStatusResultFailureReason failure_reason) {
+  RecordGetRemoteStatusResultSuccess(screenlock_type, false /* success */);
+  base::UmaHistogramEnumeration(
+      "SmartLock.GetRemoteStatus." +
+          GetHistogramScreenLockTypeName(screenlock_type) + ".Failure",
+      failure_reason);
+
+  if (screenlock_type == ProximityAuthSystem::SESSION_LOCK) {
+    get_remote_status_unlock_failure_reason_ = failure_reason;
+  }
+}
+
+std::string UnlockManagerImpl::GetRemoteStatusResultFailureReasonToString(
+    GetRemoteStatusResultFailureReason reason) {
+  switch (reason) {
+    case GetRemoteStatusResultFailureReason::kCanceledBluetoothDisabled:
+      return "CanceledBluetoothDisabled";
+    case GetRemoteStatusResultFailureReason::
+        kDeprecatedTimedOutCouldNotEstablishAuthenticatedChannel:
+      return "DeprecatedTimedOutCouldNotEstablishAuthenticatedChannel";
+    case GetRemoteStatusResultFailureReason::
+        kTimedOutDidNotReceiveRemoteStatusUpdate:
+      return "TimedOutDidNotReceiveRemoteStatusUpdate";
+    case GetRemoteStatusResultFailureReason::
+        kDeprecatedUserEnteredPasswordWhileBluetoothDisabled:
+      return "DeprecatedUserEnteredPasswordWhileBluetoothDisabled";
+    case GetRemoteStatusResultFailureReason::kCanceledUserEnteredPassword:
+      return "CanceledUserEnteredPassword";
+    case GetRemoteStatusResultFailureReason::kAuthenticatedChannelDropped:
+      return "AuthenticatedChannelDropped";
+  }
+}
+
+std::string UnlockManagerImpl::GetLastRemoteStatusUnlockForLogging() {
+  if (!get_remote_status_unlock_success_.has_value()) {
+    return kGetRemoteStatusNone;
+  }
+
+  if (*get_remote_status_unlock_success_) {
+    return kGetRemoteStatusSuccess;
+  }
+
+  if (!get_remote_status_unlock_failure_reason_.has_value()) {
+    return kGetRemoteStatusNone;
+  }
+
+  return GetRemoteStatusResultFailureReasonToString(
+      *get_remote_status_unlock_failure_reason_);
+}
+
 }  // namespace proximity_auth
diff --git a/chromeos/components/proximity_auth/unlock_manager_impl.h b/chromeos/components/proximity_auth/unlock_manager_impl.h
index 8741fd5b..2bc2c6e 100644
--- a/chromeos/components/proximity_auth/unlock_manager_impl.h
+++ b/chromeos/components/proximity_auth/unlock_manager_impl.h
@@ -53,6 +53,7 @@
   void SetRemoteDeviceLifeCycle(RemoteDeviceLifeCycle* life_cycle) override;
   void OnAuthAttempted(mojom::AuthType auth_type) override;
   void CancelConnectionAttempt() override;
+  std::string GetLastRemoteStatusUnlockForLogging() override;
 
  protected:
   // Creates a ProximityMonitor instance for the given |connection|.
@@ -72,6 +73,20 @@
     PRIMARY_USER_ABSENT,
   };
 
+  // This enum is tied directly to a UMA enum defined in
+  // //tools/metrics/histograms/enums.xml, and should always reflect it (do not
+  // change one without changing the other). Entries should be never modified
+  // or deleted. Only additions possible.
+  enum class GetRemoteStatusResultFailureReason {
+    kCanceledBluetoothDisabled = 0,
+    kDeprecatedTimedOutCouldNotEstablishAuthenticatedChannel = 1,
+    kTimedOutDidNotReceiveRemoteStatusUpdate = 2,
+    kDeprecatedUserEnteredPasswordWhileBluetoothDisabled = 3,
+    kCanceledUserEnteredPassword = 4,
+    kAuthenticatedChannelDropped = 5,
+    kMaxValue = kAuthenticatedChannelDropped
+  };
+
   // MessengerObserver:
   void OnUnlockEventSent(bool success) override;
   void OnRemoteStatusUpdate(const RemoteStatusUpdate& status_update) override;
@@ -189,6 +204,16 @@
   void SetBluetoothSuspensionRecoveryTimerForTesting(
       std::unique_ptr<base::OneShotTimer> timer);
 
+  // For recording metrics.
+  void RecordGetRemoteStatusResultSuccess(
+      ProximityAuthSystem::ScreenlockType screenlock_type,
+      bool success = true);
+  void RecordGetRemoteStatusResultFailure(
+      ProximityAuthSystem::ScreenlockType screenlock_type,
+      GetRemoteStatusResultFailureReason failure_reason);
+  std::string GetRemoteStatusResultFailureReasonToString(
+      GetRemoteStatusResultFailureReason reason);
+
   // Whether |this| manager is being used for sign-in or session unlock.
   const ProximityAuthSystem::ScreenlockType screenlock_type_;
 
@@ -270,6 +295,13 @@
   // RemoteDeviceLifeCycle, and begins to try to fetch its "remote status".
   base::Time attempt_get_remote_status_start_time_;
 
+  // Stores the last value emitted to the
+  // SmartLock.GetRemoteStatus.Unlock(.Failure) metrics. Should be |nullopt|
+  // until the first time GetRemoteStatus succeeds or fails.
+  absl::optional<bool> get_remote_status_unlock_success_;
+  absl::optional<GetRemoteStatusResultFailureReason>
+      get_remote_status_unlock_failure_reason_;
+
   // Used to track if the "initial scan" has timed out. See
   // |is_performing_initial_scan_| for more.
   base::WeakPtrFactory<UnlockManagerImpl>
diff --git a/chromeos/components/telemetry_extension_ui/test/telemetry_extension_ui_browsertest.cc b/chromeos/components/telemetry_extension_ui/test/telemetry_extension_ui_browsertest.cc
index e8c22db..b949ba4 100644
--- a/chromeos/components/telemetry_extension_ui/test/telemetry_extension_ui_browsertest.cc
+++ b/chromeos/components/telemetry_extension_ui/test/telemetry_extension_ui_browsertest.cc
@@ -21,14 +21,6 @@
 
 namespace {
 
-// File with utility functions for testing, defines `test_util`.
-constexpr base::FilePath::CharType kWebUiTestUtil[] =
-    FILE_PATH_LITERAL("chrome/test/data/webui/test_util.js");
-
-// File that `kWebUiTestUtil` is dependent on, defines `cr`.
-constexpr base::FilePath::CharType kCr[] =
-    FILE_PATH_LITERAL("ui/webui/resources/js/cr.js");
-
 // Folder containing the resources for JS browser tests.
 constexpr base::FilePath::CharType kUntrustedAppResources[] = FILE_PATH_LITERAL(
     "chromeos/components/telemetry_extension_ui/test/untrusted_app_resources");
@@ -53,8 +45,7 @@
     : SandboxedWebUiAppTestBase(
           chromeos::kChromeUITelemetryExtensionURL,
           chromeos::kChromeUIUntrustedTelemetryExtensionURL,
-          {base::FilePath(kCr), base::FilePath(kWebUiTestUtil),
-           base::FilePath(kUntrustedTestHandlers),
+          {base::FilePath(kUntrustedTestHandlers),
            base::FilePath(kUntrustedTestUtils),
            base::FilePath(kUntrustedTestCases)}) {}
 
diff --git a/chromeos/dbus/shill/shill_client_helper.cc b/chromeos/dbus/shill/shill_client_helper.cc
index 8c6d81c..c0c9773 100644
--- a/chromeos/dbus/shill/shill_client_helper.cc
+++ b/chromeos/dbus/shill/shill_client_helper.cc
@@ -128,14 +128,25 @@
 // Handles responses for methods with base::Value results.
 void OnValueMethod(ShillClientHelper::RefHolder* ref_holder,
                    DBusMethodCallback<base::Value> callback,
-                   dbus::Response* response) {
+                   dbus::Response* response,
+                   dbus::ErrorResponse* error_response) {
   if (!response) {
+    if (error_response) {
+      dbus::MessageReader reader(error_response);
+      std::string error_message;
+      reader.PopString(&error_message);
+      NET_LOG(ERROR) << "DBus call failed. Error: "
+                     << error_response->GetErrorName()
+                     << " Message: " << error_message;
+    } else {
+      NET_LOG(ERROR) << "DBus call failed with no error.";
+    }
     std::move(callback).Run(absl::nullopt);
     return;
   }
   dbus::MessageReader reader(response);
   std::unique_ptr<base::Value> value(dbus::PopDataAsValue(&reader));
-  if (!value.get() || !value->is_dict()) {
+  if (!value.get()) {
     std::move(callback).Run(absl::nullopt);
     return;
   }
@@ -286,7 +297,7 @@
     dbus::MethodCall* method_call,
     DBusMethodCallback<base::Value> callback) {
   DCHECK(!callback.is_null());
-  proxy_->CallMethod(
+  proxy_->CallMethodWithErrorResponse(
       method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
       base::BindOnce(&OnValueMethod,
                      base::Owned(new RefHolder(weak_ptr_factory_.GetWeakPtr())),
@@ -475,6 +486,20 @@
   writer->CloseContainer(&array_writer);
 }
 
+// static
+void ShillClientHelper::OnGetProperties(
+    const dbus::ObjectPath& device_path,
+    DBusMethodCallback<base::Value> callback,
+    absl::optional<base::Value> result) {
+  if (result && !result->is_dict()) {
+    NET_LOG(ERROR) << "GetProperties for: " << device_path.value()
+                   << " returned non dictionary Value: " << *result;
+    std::move(callback).Run(absl::nullopt);
+    return;
+  }
+  std::move(callback).Run(std::move(result));
+}
+
 void ShillClientHelper::AddRef() {
   ++active_refs_;
 }
diff --git a/chromeos/dbus/shill/shill_client_helper.h b/chromeos/dbus/shill/shill_client_helper.h
index d30233d..d5077c64 100644
--- a/chromeos/dbus/shill/shill_client_helper.h
+++ b/chromeos/dbus/shill/shill_client_helper.h
@@ -83,7 +83,7 @@
                                              ObjectPathCallback callback,
                                              ErrorCallback error_callback);
 
-  // Calls a method with a dictionary value result.
+  // Calls a method with a value result.
   void CallValueMethod(dbus::MethodCall* method_call,
                        DBusMethodCallback<base::Value> callback);
 
@@ -126,6 +126,11 @@
   static void AppendServiceProperties(dbus::MessageWriter* writer,
                                       const base::Value& dictionary);
 
+  // Helper method to check for a dictionary result in GetProperties calls.
+  static void OnGetProperties(const dbus::ObjectPath& device_path,
+                              DBusMethodCallback<base::Value> callback,
+                              absl::optional<base::Value> result);
+
  protected:
   // Reference / Ownership management. If the number of active refs (observers
   // + in-progress method calls) becomes 0, |released_callback_| (if set) will
diff --git a/chromeos/dbus/shill/shill_device_client.cc b/chromeos/dbus/shill/shill_device_client.cc
index 4cf1009..21813de0 100644
--- a/chromeos/dbus/shill/shill_device_client.cc
+++ b/chromeos/dbus/shill/shill_device_client.cc
@@ -63,7 +63,10 @@
                      DBusMethodCallback<base::Value> callback) override {
     dbus::MethodCall method_call(shill::kFlimflamDeviceInterface,
                                  shill::kGetPropertiesFunction);
-    GetHelper(device_path)->CallValueMethod(&method_call, std::move(callback));
+    GetHelper(device_path)
+        ->CallValueMethod(&method_call,
+                          base::BindOnce(&ShillClientHelper::OnGetProperties,
+                                         device_path, std::move(callback)));
   }
 
   void SetProperty(const dbus::ObjectPath& device_path,
diff --git a/chromeos/dbus/shill/shill_ipconfig_client.cc b/chromeos/dbus/shill/shill_ipconfig_client.cc
index e72731e..4b20809f 100644
--- a/chromeos/dbus/shill/shill_ipconfig_client.cc
+++ b/chromeos/dbus/shill/shill_ipconfig_client.cc
@@ -90,7 +90,10 @@
     DBusMethodCallback<base::Value> callback) {
   dbus::MethodCall method_call(shill::kFlimflamIPConfigInterface,
                                shill::kGetPropertiesFunction);
-  GetHelper(ipconfig_path)->CallValueMethod(&method_call, std::move(callback));
+  GetHelper(ipconfig_path)
+      ->CallValueMethod(&method_call,
+                        base::BindOnce(&ShillClientHelper::OnGetProperties,
+                                       ipconfig_path, std::move(callback)));
 }
 
 void ShillIPConfigClientImpl::SetProperty(const dbus::ObjectPath& ipconfig_path,
diff --git a/chromeos/dbus/shill/shill_manager_client.cc b/chromeos/dbus/shill/shill_manager_client.cc
index 5b9ecc4..96b84e0 100644
--- a/chromeos/dbus/shill/shill_manager_client.cc
+++ b/chromeos/dbus/shill/shill_manager_client.cc
@@ -48,7 +48,11 @@
   void GetProperties(DBusMethodCallback<base::Value> callback) override {
     dbus::MethodCall method_call(shill::kFlimflamManagerInterface,
                                  shill::kGetPropertiesFunction);
-    helper_->CallValueMethod(&method_call, std::move(callback));
+    helper_->CallValueMethod(
+        &method_call,
+        base::BindOnce(&ShillClientHelper::OnGetProperties,
+                       dbus::ObjectPath(shill::kFlimflamServicePath),
+                       std::move(callback)));
   }
 
   void GetNetworksForGeolocation(
diff --git a/chromeos/language/language_packs/language_pack_manager.cc b/chromeos/language/language_packs/language_pack_manager.cc
index 55fbe63..e038a75 100644
--- a/chromeos/language/language_packs/language_pack_manager.cc
+++ b/chromeos/language/language_packs/language_pack_manager.cc
@@ -17,6 +17,28 @@
 namespace language_packs {
 namespace {
 
+PackResult ConvertDlcStateToPackResult(const dlcservice::DlcState& dlc_state) {
+  PackResult result;
+
+  switch (dlc_state.state()) {
+    case dlcservice::DlcState_State_INSTALLED:
+      result.pack_state = PackResult::INSTALLED;
+      result.path = dlc_state.root_path();
+      break;
+    case dlcservice::DlcState_State_INSTALLING:
+      result.pack_state = PackResult::IN_PROGRESS;
+      break;
+    case dlcservice::DlcState_State_NOT_INSTALLED:
+      result.pack_state = PackResult::NOT_INSTALLED;
+      break;
+    default:
+      result.pack_state = PackResult::UNKNOWN;
+      break;
+  }
+
+  return result;
+}
+
 const base::flat_map<PackSpecPair, std::string>& GetAllDlcIds() {
   // Create the map of all DLCs and corresponding IDs.
   // Whenever a new DLC is created, it needs to be added here.
@@ -27,9 +49,8 @@
   return *all_dlc_ids;
 }
 
-void OnInstallDlcComplete(
-    OnInstallCompleteCallback callback,
-    const chromeos::DlcserviceClient::InstallResult& dlc_result) {
+void OnInstallDlcComplete(OnInstallCompleteCallback callback,
+                          const DlcserviceClient::InstallResult& dlc_result) {
   PackResult result;
   result.operation_error = dlc_result.error;
 
@@ -43,36 +64,6 @@
   std::move(callback).Run(result);
 }
 
-void OnGetDlcState(GetPackStateCallback callback,
-                   const std::string& err,
-                   const dlcservice::DlcState& dlc_state) {
-  PackResult result;
-  result.operation_error = err;
-
-  if (err == dlcservice::kErrorNone) {
-    switch (dlc_state.state()) {
-      case dlcservice::DlcState_State_INSTALLED:
-        result.pack_state = PackResult::INSTALLED;
-        result.path = dlc_state.root_path();
-        break;
-      case dlcservice::DlcState_State_INSTALLING:
-        result.pack_state = PackResult::IN_PROGRESS;
-        break;
-      case dlcservice::DlcState_State_NOT_INSTALLED:
-        result.pack_state = PackResult::NOT_INSTALLED;
-        break;
-      default:
-        result.pack_state = PackResult::UNKNOWN;
-        break;
-    }
-
-  } else {
-    result.pack_state = PackResult::UNKNOWN;
-  }
-
-  std::move(callback).Run(result);
-}
-
 void OnUninstallDlcComplete(OnUninstallCompleteCallback callback,
                             const std::string& err) {
   PackResult result;
@@ -87,6 +78,21 @@
   std::move(callback).Run(result);
 }
 
+void OnGetDlcState(GetPackStateCallback callback,
+                   const std::string& err,
+                   const dlcservice::DlcState& dlc_state) {
+  PackResult result;
+  if (err == dlcservice::kErrorNone) {
+    result = ConvertDlcStateToPackResult(dlc_state);
+  } else {
+    result.pack_state = PackResult::UNKNOWN;
+  }
+
+  result.operation_error = err;
+
+  std::move(callback).Run(result);
+}
+
 }  // namespace
 
 bool LanguagePackManager::IsPackAvailable(const std::string& pack_id,
@@ -127,7 +133,7 @@
     return;
   }
 
-  chromeos::DlcserviceClient::Get()->Install(
+  DlcserviceClient::Get()->Install(
       dlc_id, base::BindOnce(&OnInstallDlcComplete, std::move(callback)),
       base::DoNothing());
 }
@@ -148,7 +154,7 @@
     return;
   }
 
-  chromeos::DlcserviceClient::Get()->GetDlcState(
+  DlcserviceClient::Get()->GetDlcState(
       dlc_id, base::BindOnce(&OnGetDlcState, std::move(callback)));
 }
 
@@ -168,13 +174,46 @@
     return;
   }
 
-  chromeos::DlcserviceClient::Get()->Uninstall(
+  DlcserviceClient::Get()->Uninstall(
       dlc_id, base::BindOnce(&OnUninstallDlcComplete, std::move(callback)));
 }
 
+void LanguagePackManager::AddObserver(Observer* const observer) {
+  observers_.AddObserver(observer);
+}
+
+void LanguagePackManager::RemoveObserver(Observer* const observer) {
+  observers_.RemoveObserver(observer);
+}
+
+void LanguagePackManager::NotifyPackStateChanged(
+    const dlcservice::DlcState& dlc_state) {
+  PackResult result = ConvertDlcStateToPackResult(dlc_state);
+  for (Observer& observer : observers_)
+    observer.OnPackStateChanged(result);
+}
+
+void LanguagePackManager::OnDlcStateChanged(
+    const dlcservice::DlcState& dlc_state) {
+  // As of now, we only have Handwriting as a client.
+  // We will check the full list once we have more than one DLC.
+  if (dlc_state.id() != kHandwritingFeatureId)
+    return;
+
+  NotifyPackStateChanged(dlc_state);
+}
+
 LanguagePackManager::LanguagePackManager() = default;
 LanguagePackManager::~LanguagePackManager() = default;
 
+void LanguagePackManager::Initialize() {
+  DlcserviceClient::Get()->AddObserver(this);
+}
+
+void LanguagePackManager::ResetForTesting() {
+  observers_.Clear();
+}
+
 // static
 LanguagePackManager* LanguagePackManager::GetInstance() {
   static base::NoDestructor<LanguagePackManager> instance;
diff --git a/chromeos/language/language_packs/language_pack_manager.h b/chromeos/language/language_packs/language_pack_manager.h
index 1fd0506..45f43944 100644
--- a/chromeos/language/language_packs/language_pack_manager.h
+++ b/chromeos/language/language_packs/language_pack_manager.h
@@ -9,7 +9,9 @@
 
 #include "base/callback.h"
 #include "base/no_destructor.h"
+#include "base/observer_list.h"
 #include "base/strings/strcat.h"
+#include "chromeos/dbus/dlcservice/dlcservice_client.h"
 
 namespace chromeos {
 namespace language_packs {
@@ -80,8 +82,19 @@
 
 // This class manages all Language Packs on the device.
 // This is a Singleton and needs to be accessed via Get().
-class LanguagePackManager {
+class LanguagePackManager : public DlcserviceClient::Observer {
  public:
+  // Observer of Language Packs.
+  // TODO(crbug.com/1194688): Make the Observers dependent on feature and
+  // locale, so that clients don't get notified for things they are not
+  // interested in.
+  class Observer : public base::CheckedObserver {
+   public:
+    // Called whenever the state of a Language Pack changes, which includes
+    // installation, download, removal or errors.
+    virtual void OnPackStateChanged(const PackResult& pack_result) = 0;
+  };
+
   // Returns true if the given Language Pack exists and can be installed on
   // this device.
   // TODO(claudiomagni): Check per board.
@@ -114,6 +127,19 @@
                   const std::string& locale,
                   OnUninstallCompleteCallback callback);
 
+  // Adds an observer to the observer list.
+  void AddObserver(Observer* observer);
+
+  // Removes an observer from the observer list.
+  void RemoveObserver(Observer* observer);
+
+  // Must be called before using the class.
+  void Initialize();
+
+  // Testing only: called to free up resources since this object should never
+  // be destroyed.
+  void ResetForTesting();
+
   // Returns the global instance..
   static LanguagePackManager* GetInstance();
 
@@ -122,7 +148,7 @@
 
   // This class should be accessed only via GetInstance();
   LanguagePackManager();
-  ~LanguagePackManager();
+  ~LanguagePackManager() override;
 
   // Disallow copy and assign.
   LanguagePackManager(const LanguagePackManager&) = delete;
@@ -133,6 +159,14 @@
   bool GetDlcId(const std::string& pack_id,
                 const std::string& locale,
                 std::string* dlc_id);
+
+  // DlcserviceClient::Observer overrides.
+  void OnDlcStateChanged(const dlcservice::DlcState& dlc_state) override;
+
+  // Notification method called upon change of DLCs state.
+  void NotifyPackStateChanged(const dlcservice::DlcState& dlc_state);
+
+  base::ObserverList<Observer> observers_;
 };
 
 }  // namespace language_packs
diff --git a/chromeos/language/language_packs/language_pack_manager_unittest.cc b/chromeos/language/language_packs/language_pack_manager_unittest.cc
index 4dc7fd2..6fe40a86 100644
--- a/chromeos/language/language_packs/language_pack_manager_unittest.cc
+++ b/chromeos/language/language_packs/language_pack_manager_unittest.cc
@@ -5,6 +5,7 @@
 #include "chromeos/language/language_packs/language_pack_manager.h"
 
 #include "base/bind.h"
+#include "base/logging.h"
 #include "base/run_loop.h"
 #include "base/test/task_environment.h"
 #include "chromeos/dbus/dlcservice/dlcservice_client.h"
@@ -13,6 +14,7 @@
 #include "testing/gtest/include/gtest/gtest.h"
 
 using ::chromeos::language_packs::LanguagePackManager;
+using ::dlcservice::DlcState;
 using ::testing::_;
 using ::testing::Invoke;
 using ::testing::Return;
@@ -46,23 +48,43 @@
   MOCK_METHOD(void, Callback, (const PackResult&), ());
 };
 
+class MockObserver : public LanguagePackManager::Observer {
+ public:
+  MOCK_METHOD(void, OnPackStateChanged, (const PackResult& pack_result));
+};
+
+// Utility function that creates a DlcState with no error, populated with id
+// and path.
+DlcState CreateInstalledState() {
+  DlcState output;
+  output.set_state(dlcservice::DlcState_State_INSTALLED);
+  output.set_id(kHandwritingFeatureId);
+  output.set_root_path("/path");
+  return output;
+}
+
 }  // namespace
 
 class LanguagePackManagerTest : public testing::Test {
  public:
   void SetUp() override {
-    manager_ = LanguagePackManager::GetInstance();
-
+    // The Fake DLC Service needs to be initialized before we instantiate
+    // LanguagePackManager.
     DlcserviceClient::InitializeFake();
     dlcservice_client_ =
         static_cast<FakeDlcserviceClient*>(DlcserviceClient::Get());
 
+    manager_ = LanguagePackManager::GetInstance();
+    manager_->Initialize();
     ResetPackResult();
 
     base::RunLoop().RunUntilIdle();
   }
 
-  void TearDown() override { DlcserviceClient::Shutdown(); }
+  void TearDown() override {
+    manager_->ResetForTesting();
+    DlcserviceClient::Shutdown();
+  }
 
   void InstallTestCallback(const PackResult& pack_result) {
     pack_result_ = pack_result;
@@ -263,5 +285,41 @@
   base::RunLoop().RunUntilIdle();
 }
 
+TEST_F(LanguagePackManagerTest, InstallObserverTest) {
+  dlcservice_client_->set_install_error(dlcservice::kErrorNone);
+  dlcservice_client_->set_install_root_path("/path");
+  const DlcState dlc_state = CreateInstalledState();
+  MockObserver observer;
+
+  EXPECT_CALL(observer, OnPackStateChanged(_)).Times(0);
+  dlcservice_client_->NotifyObserversForTest(dlc_state);
+
+  // Add an Observer and expect it to be notified.
+  manager_->AddObserver(&observer);
+  EXPECT_CALL(observer, OnPackStateChanged(_)).Times(1);
+  dlcservice_client_->NotifyObserversForTest(dlc_state);
+
+  base::RunLoop().RunUntilIdle();
+}
+
+TEST_F(LanguagePackManagerTest, RemoveObserverTest) {
+  dlcservice_client_->set_install_error(dlcservice::kErrorNone);
+  dlcservice_client_->set_install_root_path("/path");
+  const DlcState dlc_state = CreateInstalledState();
+  MockObserver observer;
+
+  // Add an Observer and expect it to be notified.
+  manager_->AddObserver(&observer);
+  EXPECT_CALL(observer, OnPackStateChanged(_)).Times(1);
+  dlcservice_client_->NotifyObserversForTest(dlc_state);
+
+  // Remove the Observer and there should be no more notifications.
+  manager_->RemoveObserver(&observer);
+  EXPECT_CALL(observer, OnPackStateChanged(_)).Times(0);
+  dlcservice_client_->NotifyObserversForTest(dlc_state);
+
+  base::RunLoop().RunUntilIdle();
+}
+
 }  // namespace language_packs
 }  // namespace chromeos
diff --git a/chromeos/network/geolocation_handler.cc b/chromeos/network/geolocation_handler.cc
index 0e6c376..01307fc8 100644
--- a/chromeos/network/geolocation_handler.cc
+++ b/chromeos/network/geolocation_handler.cc
@@ -144,7 +144,7 @@
 
 void GeolocationHandler::GeolocationCallback(
     absl::optional<base::Value> properties) {
-  if (!properties) {
+  if (!properties || !properties->is_dict()) {
     LOG(ERROR) << "Failed to get Geolocation data";
     return;
   }
diff --git a/chromeos/services/libassistant/grpc/BUILD.gn b/chromeos/services/libassistant/grpc/BUILD.gn
index cd423b8..ef106fa 100644
--- a/chromeos/services/libassistant/grpc/BUILD.gn
+++ b/chromeos/services/libassistant/grpc/BUILD.gn
@@ -50,7 +50,9 @@
 
 source_set("grpc_service") {
   sources = [
+    "assistant_connection_status.h",
     "async_service_driver.h",
+    "rpc_method_driver.h",
     "services_initializer_base.cc",
     "services_initializer_base.h",
   ]
@@ -60,6 +62,8 @@
     "//base",
     "//third_party/grpc:grpc++",
   ]
+
+  all_dependent_configs = [ "//third_party/grpc:grpc_config" ]
 }
 
 source_set("grpc_util") {
diff --git a/chromeos/services/libassistant/grpc/assistant_connection_status.h b/chromeos/services/libassistant/grpc/assistant_connection_status.h
new file mode 100644
index 0000000..e55d9b8
--- /dev/null
+++ b/chromeos/services/libassistant/grpc/assistant_connection_status.h
@@ -0,0 +1,37 @@
+// 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 CHROMEOS_SERVICES_LIBASSISTANT_GRPC_ASSISTANT_CONNECTION_STATUS_H_
+#define CHROMEOS_SERVICES_LIBASSISTANT_GRPC_ASSISTANT_CONNECTION_STATUS_H_
+
+namespace chromeos {
+namespace libassistant {
+
+// Status of the grpc connection.
+enum class AssistantConnectionStatus {
+  // Connection to the assistant's gRPC server is not available. The assistant
+  // is either booting up or restarting after a crash.
+  OFFLINE,
+
+  // All statuses indicating that the assistant's gRPC server is online:
+  //
+  // Once online, the statuses here are monotonic. The status may transition
+  // back to OFFLINE though at any time (ex: libassistant crashes).
+  //
+  // Communication with the assistant process has initiated. Only a few
+  // core gRPC services are available in the assistant process. All rpcs to
+  // these core bootup services are made internally within AssistantClient.
+  // AssistantClient's caller need not worry about interpreting this status for
+  // the purposes of making RPCs; it's only an indication that the assistant
+  // process is alive.
+  ONLINE_BOOTING_UP,
+  // The assistant process is fully initialized, and all of its services are
+  // available for calling.
+  ONLINE_ALL_SERVICES_AVAILABLE
+};
+
+}  // namespace libassistant
+}  // namespace chromeos
+
+#endif  // CHROMEOS_SERVICES_LIBASSISTANT_GRPC_ASSISTANT_CONNECTION_STATUS_H_
diff --git a/chromeos/services/libassistant/grpc/external_services/BUILD.gn b/chromeos/services/libassistant/grpc/external_services/BUILD.gn
index 8c09298..69ae238 100644
--- a/chromeos/services/libassistant/grpc/external_services/BUILD.gn
+++ b/chromeos/services/libassistant/grpc/external_services/BUILD.gn
@@ -10,6 +10,7 @@
 
   deps = [
     ":customer_registration_client",
+    ":heartbeat_event_handler_driver",
     "//base",
     "//chromeos/services/libassistant/grpc:grpc_client",
     "//chromeos/services/libassistant/grpc:grpc_service",
@@ -33,3 +34,21 @@
     "//chromeos/services/libassistant/grpc:libassistant_client",
   ]
 }
+
+source_set("heartbeat_event_handler_driver") {
+  sources = [
+    "heartbeat_event_handler_driver.cc",
+    "heartbeat_event_handler_driver.h",
+  ]
+
+  public_deps = [
+    "//chromeos/assistant/internal/proto:assistant",
+    "//chromeos/assistant/internal/proto:assistant_grpc",
+  ]
+
+  deps = [
+    "//base",
+    "//chromeos/services/libassistant/grpc:grpc_service",
+    "//third_party/grpc:grpc++",
+  ]
+}
diff --git a/chromeos/services/libassistant/grpc/external_services/grpc_services_initializer.cc b/chromeos/services/libassistant/grpc/external_services/grpc_services_initializer.cc
index 6dc62a44..a25fa2a 100644
--- a/chromeos/services/libassistant/grpc/external_services/grpc_services_initializer.cc
+++ b/chromeos/services/libassistant/grpc/external_services/grpc_services_initializer.cc
@@ -13,6 +13,7 @@
 #include "base/threading/sequenced_task_runner_handle.h"
 #include "base/time/time.h"
 #include "chromeos/services/libassistant/grpc/external_services/customer_registration_client.h"
+#include "chromeos/services/libassistant/grpc/external_services/heartbeat_event_handler_driver.h"
 #include "chromeos/services/libassistant/grpc/grpc_libassistant_client.h"
 #include "chromeos/services/libassistant/grpc/grpc_util.h"
 #include "third_party/grpc/src/include/grpc/grpc_security_constants.h"
@@ -35,7 +36,9 @@
 GrpcServicesInitializer::GrpcServicesInitializer(
     const std::string& libassistant_service_address,
     const std::string& assistant_service_address)
-    : ServicesInitializerBase(assistant_service_address + ".GrpcCQ"),
+    : ServicesInitializerBase(
+          /*cq_thread_name=*/assistant_service_address + ".GrpcCQ",
+          /*main_task_runner=*/base::SequencedTaskRunnerHandle::Get()),
       assistant_service_address_(assistant_service_address),
       libassistant_service_address_(libassistant_service_address) {
   DCHECK(!libassistant_service_address.empty());
@@ -82,9 +85,9 @@
 }
 
 void GrpcServicesInitializer::InitDrivers(grpc::ServerBuilder* server_builder) {
-  // All services, i.e. HeartbeatEventHandler, must be registered here before we
-  // start the gRPC server.
-  NOTIMPLEMENTED();
+  // Inits heartbeat driver.
+  service_drivers_.emplace_back(
+      std::make_unique<HeartbeatEventHandlerDriver>(&server_builder_));
 }
 
 void GrpcServicesInitializer::InitLibassistGrpcClient() {
diff --git a/chromeos/services/libassistant/grpc/external_services/heartbeat_event_handler_driver.cc b/chromeos/services/libassistant/grpc/external_services/heartbeat_event_handler_driver.cc
new file mode 100644
index 0000000..4142b4df
--- /dev/null
+++ b/chromeos/services/libassistant/grpc/external_services/heartbeat_event_handler_driver.cc
@@ -0,0 +1,103 @@
+// 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.
+
+#include "chromeos/services/libassistant/grpc/external_services/heartbeat_event_handler_driver.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/sequence_checker.h"
+#include "chromeos/assistant/internal/proto/shared/proto/v2/delegate/event_handler_interface.pb.h"
+#include "chromeos/assistant/internal/proto/shared/proto/v2/delegate/libas_server_status.pb.h"
+#include "chromeos/services/libassistant/grpc/assistant_connection_status.h"
+
+namespace chromeos {
+namespace libassistant {
+
+namespace {
+
+using ::assistant::api::HeartbeatEventHandlerInterface;
+using ::assistant::api::OnHeartbeatEventRequest;
+using ::assistant::api::OnHeartbeatEventResponse;
+
+bool ConvertServerStatus(::assistant::api::LibasServerStatus input,
+                         AssistantConnectionStatus* output) {
+  switch (input) {
+    // We consider both states as booting up as a customer, although they are
+    // two different states in Libassistant bootup protocol.
+    case ::assistant::api::CUSTOMER_REGISTRATION_SERVICE_AVAILABLE:
+    case ::assistant::api::ESSENTIAL_SERVICES_AVAILABLE:
+      *output = AssistantConnectionStatus::ONLINE_BOOTING_UP;
+      return true;
+
+    case ::assistant::api::ALL_SERVICES_AVAILABLE:
+      *output = AssistantConnectionStatus::ONLINE_ALL_SERVICES_AVAILABLE;
+      return true;
+
+    case ::assistant::api::UNKNOWN_LIBAS_SERVER_STATUS:
+      return false;
+  }
+}
+
+std::string GetServerStatusLogString(AssistantConnectionStatus status) {
+  switch (status) {
+    case AssistantConnectionStatus::OFFLINE:
+      return "Libassistant service is OFFLINE.";
+    case AssistantConnectionStatus::ONLINE_BOOTING_UP:
+      return "Libassistant service is BOOTING UP.";
+    case AssistantConnectionStatus::ONLINE_ALL_SERVICES_AVAILABLE:
+      return "Libassistant service is ALL READY.";
+  }
+}
+
+}  // namespace
+
+HeartbeatEventHandlerDriver::HeartbeatEventHandlerDriver(
+    ::grpc::ServerBuilder* server_builder)
+    : AsyncServiceDriver(server_builder) {
+  server_builder_->RegisterService(&service_);
+}
+
+HeartbeatEventHandlerDriver::~HeartbeatEventHandlerDriver() = default;
+
+void HeartbeatEventHandlerDriver::StartCQ(::grpc::ServerCompletionQueue* cq) {
+  on_event_rpc_method_driver_ = std::make_unique<
+      RpcMethodDriver<OnHeartbeatEventRequest, OnHeartbeatEventResponse>>(
+      cq,
+      base::BindRepeating(&HeartbeatEventHandlerInterface::AsyncService::
+                              RequestOnEventFromLibas,
+                          async_service_weak_factory_.GetWeakPtr()),
+      base::BindRepeating(&HeartbeatEventHandlerDriver::HandleEvent,
+                          weak_factory_.GetWeakPtr()));
+}
+
+void HeartbeatEventHandlerDriver::HandleEvent(
+    grpc::ServerContext* context,
+    const OnHeartbeatEventRequest* request,
+    base::OnceCallback<void(const grpc::Status&,
+                            const OnHeartbeatEventResponse&)> done) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  int heartbeat_count = request->heartbeat_num();
+  DVLOG(3) << "Heartbeat from libassistant : " << heartbeat_count;
+  OnHeartbeatEventResponse response;
+  std::move(done).Run(grpc::Status::OK, response);
+
+  // Parse heartbeat request.
+  if (!request->has_current_server_status()) {
+    LOG(ERROR) << "Received Libassistant heartbeat without server status";
+    return;
+  }
+
+  AssistantConnectionStatus new_status;
+  if (!ConvertServerStatus(request->current_server_status(), &new_status)) {
+    LOG(ERROR) << "Received unknown Libassistant server status";
+    return;
+  }
+
+  DVLOG(3) << GetServerStatusLogString(new_status);
+}
+
+}  // namespace libassistant
+}  // namespace chromeos
diff --git a/chromeos/services/libassistant/grpc/external_services/heartbeat_event_handler_driver.h b/chromeos/services/libassistant/grpc/external_services/heartbeat_event_handler_driver.h
new file mode 100644
index 0000000..7259fdc
--- /dev/null
+++ b/chromeos/services/libassistant/grpc/external_services/heartbeat_event_handler_driver.h
@@ -0,0 +1,71 @@
+// 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 CHROMEOS_SERVICES_LIBASSISTANT_GRPC_EXTERNAL_SERVICES_HEARTBEAT_EVENT_HANDLER_DRIVER_H_
+#define CHROMEOS_SERVICES_LIBASSISTANT_GRPC_EXTERNAL_SERVICES_HEARTBEAT_EVENT_HANDLER_DRIVER_H_
+
+#include <memory>
+
+#include "base/memory/scoped_refptr.h"
+#include "base/memory/weak_ptr.h"
+#include "chromeos/assistant/internal/proto/shared/proto/v2/delegate/event_handler_service.grpc.pb.h"
+#include "chromeos/services/libassistant/grpc/async_service_driver.h"
+#include "chromeos/services/libassistant/grpc/rpc_method_driver.h"
+
+namespace assistant {
+namespace api {
+class OnHeartbeatEventRequest;
+class OnHeartbeatEventResponse;
+}  // namespace api
+}  // namespace assistant
+
+namespace chromeos {
+namespace libassistant {
+
+class HeartbeatEventHandlerDriver : public AsyncServiceDriver {
+ public:
+  explicit HeartbeatEventHandlerDriver(::grpc::ServerBuilder* server_builder);
+  HeartbeatEventHandlerDriver(const HeartbeatEventHandlerDriver&) = delete;
+  HeartbeatEventHandlerDriver& operator=(const HeartbeatEventHandlerDriver&) =
+      delete;
+  ~HeartbeatEventHandlerDriver() override;
+
+ private:
+  // Generally we should use fully qualified namespace inside classes to avoid
+  // potential confliction, we use |using| here to simply the naming thus
+  // increase the readability.
+  using OnHeartbeatEventRequest = ::assistant::api::OnHeartbeatEventRequest;
+  using OnHeartbeatEventResponse = ::assistant::api::OnHeartbeatEventResponse;
+  using HeartbeatEventHandlerInterface =
+      ::assistant::api::HeartbeatEventHandlerInterface;
+
+  // AsyncServiceDriver overrides:
+  void StartCQ(::grpc::ServerCompletionQueue* cq) override;
+
+  // Handles incoming heartbeat RPC event delivery.
+  void HandleEvent(
+      grpc::ServerContext* context,
+      const OnHeartbeatEventRequest* request,
+      base::OnceCallback<void(const grpc::Status& status,
+                              const OnHeartbeatEventResponse& response)> done);
+
+  HeartbeatEventHandlerInterface::AsyncService service_;
+
+  std::unique_ptr<
+      RpcMethodDriver<OnHeartbeatEventRequest, OnHeartbeatEventResponse>>
+      on_event_rpc_method_driver_;
+
+  // This sequence checker ensures that all callbacks are called on the main
+  // sequence.
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  base::WeakPtrFactory<HeartbeatEventHandlerInterface::AsyncService>
+      async_service_weak_factory_{&service_};
+  base::WeakPtrFactory<HeartbeatEventHandlerDriver> weak_factory_{this};
+};
+
+}  // namespace libassistant
+}  // namespace chromeos
+
+#endif  // CHROMEOS_SERVICES_LIBASSISTANT_GRPC_EXTERNAL_SERVICES_HEARTBEAT_EVENT_HANDLER_DRIVER_H_
diff --git a/chromeos/services/libassistant/grpc/rpc_method_driver.h b/chromeos/services/libassistant/grpc/rpc_method_driver.h
new file mode 100644
index 0000000..c342ecb1
--- /dev/null
+++ b/chromeos/services/libassistant/grpc/rpc_method_driver.h
@@ -0,0 +1,187 @@
+// 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 CHROMEOS_SERVICES_LIBASSISTANT_GRPC_RPC_METHOD_DRIVER_H_
+#define CHROMEOS_SERVICES_LIBASSISTANT_GRPC_RPC_METHOD_DRIVER_H_
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/check.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/sequence_checker.h"
+#include "base/threading/thread.h"
+#include "third_party/grpc/src/include/grpcpp/grpcpp.h"
+#include "third_party/grpc/src/include/grpcpp/server_context.h"
+
+namespace chromeos {
+namespace libassistant {
+
+// Implements async RPC driver for an RPC method.
+// Request and Response are the RPC method's request and response protos.
+//
+// Sample usage for handling SetCapabilities() RPC method from service
+// ConversationInterface:
+//
+//  set_capabilities_rpc_driver_.reset(
+//      std::make_unique<RpcMethodDriver<SetCapabilitiesRequest,
+//      SetCapabilitiesResponse>>(
+//          cq,
+//          base::BindRepeating(
+//              &ConversationInterface::AsyncService::RequestSetCapabilities,
+//              service_.WeakPtr()),
+//          base::BindRepeating(
+//              &ConversationInterfaceImpl::SetCapabilities,
+//              conversation_interface_impl_.WeakPtr())));
+//
+template <class Request, class Response>
+class RpcMethodDriver {
+ public:
+  // Callback object encapsulating service.Request##RpcMethod() which looks
+  // for next incoming RPC.
+  using ServiceRpcCallFn =
+      base::RepeatingCallback<void(grpc::ServerContext*,
+                                   Request*,
+                                   grpc::ServerAsyncResponseWriter<Response>*,
+                                   grpc::CompletionQueue*,
+                                   grpc::ServerCompletionQueue*,
+                                   void*)>;
+
+  // Callback object for calling RPC async business logic implementation.
+  using RpcImplAsyncFn = base::RepeatingCallback<void(
+      grpc::ServerContext*,
+      const Request*,
+      base::OnceCallback<void(const grpc::Status&, const Response&)>)>;
+
+  // Constructs the class and initializes the completion queue.
+  // cq: CompletionQueue
+  // service_rpc_call_fn: Callback object encapsulating
+  //         service.Request##RpcMethod() which looks for next incoming RPC.
+  // rpc_impl_async_fn: Callback object for calling implementation of
+  //              business logic of the RPC.
+  RpcMethodDriver(grpc::ServerCompletionQueue* cq,
+                  ServiceRpcCallFn service_rpc_call_fn,
+                  RpcImplAsyncFn rpc_impl_async_fn)
+      : cq_(cq),
+        service_rpc_call_fn_(service_rpc_call_fn),
+        rpc_impl_async_fn_(rpc_impl_async_fn) {
+    DCHECK(cq);
+    RequestNextRpc();
+  }
+
+  ~RpcMethodDriver() = default;
+  RpcMethodDriver(const RpcMethodDriver&) = delete;
+  RpcMethodDriver& operator=(const RpcMethodDriver&) = delete;
+
+ private:
+  // Look for the next incoming RPC.
+  void RequestNextRpc() {
+    // Owned by CleanupAfterRpc() run at the end of the lifecycle of current
+    // RPC.
+    auto ctx = std::make_unique<grpc::ServerContext>();
+    auto request = std::make_unique<Request>();
+    auto responder =
+        std::make_unique<grpc::ServerAsyncResponseWriter<Response>>(ctx.get());
+
+    // Prestore valid pointers before std::move() nulls the smart pointers.
+    auto* ctx_ptr = ctx.get();
+    auto* request_ptr = request.get();
+    auto* responder_ptr = responder.get();
+
+    // A raw pointer has to be used here since |service_rpc_call_fn_| is
+    // expecting void* as the parameter. It will be deleted by server cq
+    // after being executed.
+    auto* process_rpc_cb = new base::OnceCallback<void(bool)>(
+        base::BindOnce(&RpcMethodDriver<Request, Response>::ProcessRpc,
+                       weak_factory_.GetWeakPtr(), std::move(ctx),
+                       std::move(request), std::move(responder)));
+
+    DCHECK(service_rpc_call_fn_);
+    service_rpc_call_fn_.Run(ctx_ptr, request_ptr, responder_ptr, cq_, cq_,
+                             process_rpc_cb);
+  }
+
+  // Process the RPC received.
+  void ProcessRpc(
+      std::unique_ptr<grpc::ServerContext> ctx,
+      std::unique_ptr<Request> request,
+      std::unique_ptr<grpc::ServerAsyncResponseWriter<Response>> responder,
+      bool ok) {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+    if (!ok) {
+      // If not okay, logs error and returns. Used data, i.e. ctx, will be
+      // cleaned up automatically when unique_ptrs go out of scope.
+      LOG(ERROR) << "OnEventFromLibas request not ok.";
+      return;
+    }
+
+    // Start waiting for the next RPC.
+    RequestNextRpc();
+
+    ExecuteRpc(std::move(ctx), std::move(request), std::move(responder));
+  }
+
+  // Execute the RPC received.
+  void ExecuteRpc(
+      std::unique_ptr<grpc::ServerContext> ctx,
+      std::unique_ptr<Request> request,
+      std::unique_ptr<grpc::ServerAsyncResponseWriter<Response>> responder) {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+    // Prestore valid pointers before std::move() nulls the smart pointers.
+    auto* ctx_ptr = ctx.get();
+    auto* request_ptr = request.get();
+    auto* responder_ptr = responder.get();
+
+    auto* finish_rpc_cb = new base::OnceCallback<void(bool)>(
+        base::BindOnce(&RpcMethodDriver<Request, Response>::CleanupAfterRpc,
+                       weak_factory_.GetWeakPtr(), std::move(ctx),
+                       std::move(request), std::move(responder)));
+
+    auto async_cb = base::BindOnce(
+        [](grpc::ServerAsyncResponseWriter<Response>* responder,
+           base::OnceCallback<void(bool)>* finish_rpc_cb,
+           const grpc::Status& status, const Response& response) {
+          responder->Finish(response, status, finish_rpc_cb);
+        },
+        responder_ptr, finish_rpc_cb);
+
+    DCHECK(rpc_impl_async_fn_);
+    // Call the async implementation of the RPC business logic.
+    rpc_impl_async_fn_.Run(ctx_ptr, request_ptr, std::move(async_cb));
+  }
+
+  void CleanupAfterRpc(
+      std::unique_ptr<grpc::ServerContext> ctx,
+      std::unique_ptr<Request> request,
+      std::unique_ptr<grpc::ServerAsyncResponseWriter<Response>> responder,
+      bool ignored_ok) {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+    DVLOG(3) << "OnEventFromLibas is finished.";
+
+    // Unique_ptrs will delete the objects after they go out of scope
+    // so no manual data clean-up needed here.
+  }
+
+  // Owned by |ServicesInitializerBase|.
+  grpc::ServerCompletionQueue* cq_ = nullptr;
+
+  ServiceRpcCallFn service_rpc_call_fn_;
+  RpcImplAsyncFn rpc_impl_async_fn_;
+
+  // This sequence checker ensures that all callbacks are called on the
+  // main sequence.
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  base::WeakPtrFactory<RpcMethodDriver> weak_factory_{this};
+};
+
+}  // namespace libassistant
+}  // namespace chromeos
+
+#endif  // CHROMEOS_SERVICES_LIBASSISTANT_GRPC_RPC_METHOD_DRIVER_H_
diff --git a/chromeos/services/libassistant/grpc/services_initializer_base.cc b/chromeos/services/libassistant/grpc/services_initializer_base.cc
index 73161ae..e9f6cc45 100644
--- a/chromeos/services/libassistant/grpc/services_initializer_base.cc
+++ b/chromeos/services/libassistant/grpc/services_initializer_base.cc
@@ -13,8 +13,9 @@
 namespace libassistant {
 
 ServicesInitializerBase::ServicesInitializerBase(
-    const std::string& cq_thread_name)
-    : cq_thread_(cq_thread_name) {
+    const std::string& cq_thread_name,
+    scoped_refptr<base::SequencedTaskRunner> main_task_runner)
+    : cq_thread_(cq_thread_name), main_task_runner_(main_task_runner) {
   base::Thread::Options options(base::MessagePumpType::IO,
                                 0 /* default maximum stack size */);
   options.priority = base::ThreadPriority::NORMAL;
@@ -35,9 +36,9 @@
 
 void ServicesInitializerBase::StartCQ() {
   // Initialize completion queues for each service.
-  for (auto& driver : service_drivers_)
-    driver.StartCQ(cq_.get());
-
+  for (auto& driver : service_drivers_) {
+    driver->StartCQ(cq_.get());
+  }
   cq_thread_.task_runner()->PostTask(
       FROM_HERE, base::BindOnce(&ServicesInitializerBase::ScanCQInternal,
                                 base::Unretained(this)));
@@ -60,6 +61,7 @@
   // Stop() will be called in its destructor.
 }
 
+// Runs on the cq polling thread.
 void ServicesInitializerBase::ScanCQInternal() {
   // Poll the completion queue.
   while (true) {
@@ -75,7 +77,10 @@
       continue;
     }
     auto* cb_ptr = static_cast<base::OnceCallback<void(bool)>*>(tag);
-    std::move(*cb_ptr).Run(ok);
+    // Use PostTask() to ensure callbacks are run on the same sequence on which
+    // they are bound.
+    main_task_runner_->PostTask(FROM_HERE,
+                                base::BindOnce(std::move(*cb_ptr), ok));
     delete cb_ptr;
   }
 }
diff --git a/chromeos/services/libassistant/grpc/services_initializer_base.h b/chromeos/services/libassistant/grpc/services_initializer_base.h
index da9c045a6..2da3964 100644
--- a/chromeos/services/libassistant/grpc/services_initializer_base.h
+++ b/chromeos/services/libassistant/grpc/services_initializer_base.h
@@ -21,7 +21,9 @@
 // Initializes all services exposed by libassistant.
 class ServicesInitializerBase {
  public:
-  explicit ServicesInitializerBase(const std::string& cq_thread_name);
+  ServicesInitializerBase(
+      const std::string& cq_thread_name,
+      scoped_refptr<base::SequencedTaskRunner> main_task_runner);
   ServicesInitializerBase(const ServicesInitializerBase&) = delete;
   ServicesInitializerBase& operator=(const ServicesInitializerBase&) = delete;
   virtual ~ServicesInitializerBase();
@@ -44,11 +46,15 @@
   void ScanCQInternal();
 
   std::unique_ptr<grpc::ServerCompletionQueue> cq_;
-  std::vector<AsyncServiceDriver> service_drivers_;
+  std::vector<std::unique_ptr<AsyncServiceDriver>> service_drivers_;
+
   // Use a dedicated thread to poll completion queue. Will also responsible
   // for cleaning up the tags returned by calling cq_->Next() after they are
   // executed.
   base::Thread cq_thread_;
+
+  // The task runner for the main thread.
+  scoped_refptr<base::SequencedTaskRunner> main_task_runner_;
 };
 
 }  // namespace libassistant
diff --git a/chromeos/system/core_scheduling.cc b/chromeos/system/core_scheduling.cc
index e41b7d9..ea3b951 100644
--- a/chromeos/system/core_scheduling.cc
+++ b/chromeos/system/core_scheduling.cc
@@ -7,9 +7,17 @@
 #include <errno.h>
 #include <sys/prctl.h>
 
+#include <set>
+#include <string>
+
 #include "base/feature_list.h"
+#include "base/files/file_util.h"
 #include "base/logging.h"
 #include "base/metrics/field_trial_params.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/system/sys_info.h"
+#include "base/threading/thread_restrictions.h"
 
 // Downstream core scheduling interface for 4.19, 5.4 kernels.
 // TODO(b/152605392): Remove once those kernel versions are obsolete.
@@ -26,6 +34,9 @@
 #define PR_SCHED_CORE_SHARE_FROM 3
 #define PR_SCHED_CORE_MAX 4
 #endif
+#ifndef PID_T_MAX
+#define PID_T_MAX (1 << 30)
+#endif
 
 enum pid_type { PIDTYPE_PID = 0, PIDTYPE_TGID, PIDTYPE_PGID };
 
@@ -50,5 +61,84 @@
   }
 }
 
+bool IsCoreSchedulingAvailable() {
+  static const bool kernel_support = []() {
+    // Test for kernel 4.19, 5.4 downstream support.
+    // Pass bad param `prctl(0x200, 2)`. If it is supported, we will get ERANGE
+    // rather than EINVAL.
+    if (prctl(PR_SET_CORE_SCHED, 2) == -1 && errno == ERANGE) {
+      VLOG(1) << "Core scheduling legacy interface supported";
+      return true;
+    }
+
+    // Test for kernel 5.10 upstream support.
+    // Pass bad param pid=PID_T_MAX. If it is supported, we will get ENODEV
+    // (supported by not enabled) or ESRCH (bad param).
+    int res =
+        prctl(PR_SCHED_CORE, PR_SCHED_CORE_CREATE, PID_T_MAX, PIDTYPE_PID, 0);
+    int saved_errno = errno;
+    if (res == -1 && saved_errno == ENODEV) {
+      VLOG(1) << "Core scheduling supported, but not enabled "
+                 "(likely because SMT is not enabled)";
+      return false;
+    }
+    if (res == -1 && saved_errno == ESRCH) {
+      VLOG(1) << "Core scheduling supported and enabled";
+      return true;
+    }
+    VLOG(1) << "Core scheduling not supported in kernel";
+    return false;
+  }();
+  if (!kernel_support) {
+    return false;
+  }
+
+  static const bool has_vulns = []() {
+    // Reading from sysfs doesn't block.
+    base::ScopedAllowBlocking scoped_allow_blocking;
+
+    base::FilePath sysfs_vulns("/sys/devices/system/cpu/vulnerabilities");
+    std::string buf;
+    for (const std::string& s : {"l1tf", "mds"}) {
+      base::FilePath vuln = sysfs_vulns.Append(s);
+      if (!base::ReadFileToString(vuln, &buf)) {
+        LOG(ERROR) << "Could not read " << vuln;
+        continue;
+      }
+      base::TrimWhitespaceASCII(buf, base::TRIM_ALL, &buf);
+      if (buf != "Not affected") {
+        VLOG(1) << "Core scheduling available: " << vuln << "=" << buf;
+        return true;
+      }
+    }
+    VLOG(1) << "Core scheduling not required";
+    return false;
+  }();
+  return has_vulns;
+}
+
+int NumberOfProcessorsForCoreScheduling() {
+  // cat /sys/devices/system/cpu/cpu[0-9]*/topology/thread_siblings_list |\
+  //   sort -u | wc -l
+  static const int num_physical_cores = []() {
+    // Reading from sysfs doesn't block.
+    base::ScopedAllowBlocking scoped_allow_blocking;
+
+    std::set<std::string> lists;
+    std::string buf;
+    for (int i = 0; i < base::SysInfo::NumberOfProcessors(); ++i) {
+      base::FilePath list(base::StringPrintf(
+          "/sys/devices/system/cpu/cpu%d/topology/thread_siblings_list", i));
+      if (!base::ReadFileToString(list, &buf)) {
+        LOG(ERROR) << "Could not read " << list;
+      } else {
+        lists.insert(buf);
+      }
+    }
+    return lists.size() > 0 ? lists.size() : 1;
+  }();
+  return num_physical_cores;
+}
+
 }  // namespace system
 }  // namespace chromeos
diff --git a/chromeos/system/core_scheduling.h b/chromeos/system/core_scheduling.h
index dfcc9f6..7303d830 100644
--- a/chromeos/system/core_scheduling.h
+++ b/chromeos/system/core_scheduling.h
@@ -14,6 +14,14 @@
 // if it's available,
 void CHROMEOS_EXPORT EnableCoreSchedulingIfAvailable();
 
+// Returns true if core scheduling is supported in the kernel, and CPU has MDS
+// or L1TF vulnerabilities. Core scheduling does not run on CPUs that are not
+// vulnerable.
+bool CHROMEOS_EXPORT IsCoreSchedulingAvailable();
+
+// Returns number of physical cores.
+int CHROMEOS_EXPORT NumberOfProcessorsForCoreScheduling();
+
 }  // namespace system
 }  // namespace chromeos
 
diff --git a/chromeos/tast_control.gni b/chromeos/tast_control.gni
index 47f446a..d926edfe 100644
--- a/chromeos/tast_control.gni
+++ b/chromeos/tast_control.gni
@@ -25,15 +25,9 @@
   # crbug.com/1154794
   "policy.DefaultGeolocationSetting",
 
-  # crbug.com/1115622
-  "inputs.VirtualKeyboardOOBE",
-
   # crbug.com/1156006
   "filemanager.DragDrop",
 
-  # crbug.com/1183238
-  "filemanager.DrivefsUI",
-
   # crbug.com/1171146
   "session.LogoutCleanup",
 
diff --git a/components/arc/arc_util.cc b/components/arc/arc_util.cc
index 74adda1..70481207 100644
--- a/components/arc/arc_util.cc
+++ b/components/arc/arc_util.cc
@@ -4,25 +4,19 @@
 
 #include "components/arc/arc_util.h"
 
-#include <sys/prctl.h>
-
 #include <algorithm>
 #include <cstdio>
-#include <set>
 
 #include "ash/constants/app_types.h"
 #include "ash/constants/ash_switches.h"
 #include "base/bind.h"
 #include "base/command_line.h"
 #include "base/feature_list.h"
-#include "base/files/file_util.h"
 #include "base/logging.h"
 #include "base/process/launch.h"
 #include "base/process/process_metrics.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
-#include "base/strings/stringprintf.h"
-#include "base/system/sys_info.h"
 #include "chromeos/dbus/concierge/concierge_client.h"
 #include "chromeos/dbus/debug_daemon/debug_daemon_client.h"
 #include "chromeos/dbus/session_manager/session_manager_client.h"
@@ -35,12 +29,6 @@
 #include "ui/aura/window.h"
 #include "ui/display/types/display_constants.h"
 
-// Downstream core scheduling interface for 4.19, 5.4 kernels.
-// TODO(b/152605392): Remove once those kernel versions are obsolete.
-#ifndef PR_SET_CORE_SCHED
-#define PR_SET_CORE_SCHED 0x200
-#endif
-
 namespace arc {
 
 namespace {
@@ -494,56 +482,4 @@
   }
 }
 
-bool IsCoreSchedulingAvailable() {
-  static const bool has_vulns = []() {
-    // Check for support in the kernel by passing bad param `prctl(0x200, 2)`.
-    // If it is supported, we will get ERANGE rather than EINVAL.
-    // TODO(joelhockey): The public prctl(PR_SCHED_CORE, ...) API added in
-    // kernel 5.14 returns EINVAL rather than ERANGE for invalid params which
-    // doesn't give us any way to tell if core scheduling is supported. We
-    // should not remove PR_SET_CORE_SCHED 0x200 until all devices are at 5.14+.
-    if (prctl(PR_SET_CORE_SCHED, 2) == -1 && errno != ERANGE) {
-      VLOG(1) << "Core scheduling not supported in kernel";
-      return false;
-    }
-
-    base::FilePath sysfs_vulns("/sys/devices/system/cpu/vulnerabilities");
-    std::string buf;
-    for (const std::string& s : {"l1tf", "mds"}) {
-      base::FilePath vuln = sysfs_vulns.Append(s);
-      if (!base::ReadFileToString(vuln, &buf)) {
-        LOG(ERROR) << "Could not read " << vuln;
-        continue;
-      }
-      base::TrimWhitespaceASCII(buf, base::TRIM_ALL, &buf);
-      if (buf != "Not affected") {
-        VLOG(1) << "Core scheduling available: " << vuln << "=" << buf;
-        return true;
-      }
-    }
-    VLOG(1) << "Core scheduling not required";
-    return false;
-  }();
-  return has_vulns;
-}
-
-int NumberOfProcessorsForCoreScheduling() {
-  // cat /sys/devices/system/cpu/cpu[0-9]*/topology/thread_siblings_list |\
-  //   sort -u | wc -l
-  static const int num_physical_cores = []() {
-    std::set<std::string> lists;
-    std::string buf;
-    for (int i = 0; i < base::SysInfo::NumberOfProcessors(); ++i) {
-      base::FilePath list(base::StringPrintf(
-          "/sys/devices/system/cpu/cpu%d/topology/thread_siblings_list", i));
-      if (!base::ReadFileToString(list, &buf))
-        LOG(ERROR) << "Could not read " << list;
-      else
-        lists.insert(buf);
-    }
-    return lists.size() > 0 ? lists.size() : 1;
-  }();
-  return num_physical_cores;
-}
-
 }  // namespace arc
diff --git a/components/arc/arc_util.h b/components/arc/arc_util.h
index 0130ed0c..e8a0eeb 100644
--- a/components/arc/arc_util.h
+++ b/components/arc/arc_util.h
@@ -206,18 +206,6 @@
 void ConfigureUpstartJobs(std::deque<JobDesc> jobs,
                           chromeos::VoidDBusMethodCallback callback);
 
-// Returns true if core scheduling is supported in the kernel, and CPU has MDS
-// or L1TF vulnerabilities. Core scheduling does not run on CPUs that are not
-// vulnerable.
-// TODO(yusukes): Once https://crrev.com/c/3063740 is submitted, switch to the
-// function in the CL and delete this.
-bool IsCoreSchedulingAvailable();
-
-// Returns number of physical cores.
-// TODO(yusukes): Once https://crrev.com/c/3063740 is submitted, switch to the
-// function in the CL and delete this.
-int NumberOfProcessorsForCoreScheduling();
-
 }  // namespace arc
 
 #endif  // COMPONENTS_ARC_ARC_UTIL_H_
diff --git a/components/arc/session/arc_vm_client_adapter.cc b/components/arc/session/arc_vm_client_adapter.cc
index eb6110c..ec6e9af 100644
--- a/components/arc/session/arc_vm_client_adapter.cc
+++ b/components/arc/session/arc_vm_client_adapter.cc
@@ -53,6 +53,7 @@
 #include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/debug_daemon/debug_daemon_client.h"
 #include "chromeos/dbus/session_manager/session_manager_client.h"
+#include "chromeos/system/core_scheduling.h"
 #include "chromeos/system/statistics_provider.h"
 #include "components/arc/arc_features.h"
 #include "components/arc/arc_service_manager.h"
@@ -870,8 +871,8 @@
     // TODO(yusukes): Stop doing this on the other thread once we migrate to
     // https://crrev.com/c/3063740.
     const int32_t cpus =
-        IsCoreSchedulingAvailable()
-            ? NumberOfProcessorsForCoreScheduling()
+        chromeos::system::IsCoreSchedulingAvailable()
+            ? chromeos::system::NumberOfProcessorsForCoreScheduling()
             : base::SysInfo::NumberOfProcessors() - num_cores_disabled;
     DCHECK_LT(0, cpus);
     return cpus;
diff --git a/components/autofill/content/browser/content_autofill_driver.cc b/components/autofill/content/browser/content_autofill_driver.cc
index d20107c..636d526 100644
--- a/components/autofill/content/browser/content_autofill_driver.cc
+++ b/components/autofill/content/browser/content_autofill_driver.cc
@@ -135,6 +135,11 @@
   return render_frame_host_->GetParent() == nullptr;
 }
 
+bool ContentAutofillDriver::IsPrerendering() const {
+  return render_frame_host_->GetLifecycleState() ==
+         content::RenderFrameHost::LifecycleState::kPrerendering;
+}
+
 bool ContentAutofillDriver::CanShowAutofillUi() const {
   // Don't show AutofillUi for inactive RenderFrameHost. Here it is safe to
   // ignore the calls from inactive RFH as the renderer is not expecting a reply
@@ -396,6 +401,7 @@
 }
 
 void ContentAutofillDriver::HidePopupImpl() {
+  DCHECK(!IsPrerendering()) << "We should never affect UI while prerendering";
   autofill_manager_->OnHidePopup();
 }
 
diff --git a/components/autofill/content/browser/content_autofill_driver.h b/components/autofill/content/browser/content_autofill_driver.h
index 30e26958..2954584f 100644
--- a/components/autofill/content/browser/content_autofill_driver.h
+++ b/components/autofill/content/browser/content_autofill_driver.h
@@ -138,6 +138,7 @@
   // AutofillDriver:
   bool IsIncognito() const override;
   bool IsInMainFrame() const override;
+  bool IsPrerendering() const override;
   bool CanShowAutofillUi() const override;
   ui::AXTreeID GetAxTreeId() const override;
   scoped_refptr<network::SharedURLLoaderFactory> GetURLLoaderFactory() override;
diff --git a/components/autofill/content/browser/content_autofill_driver_factory.cc b/components/autofill/content/browser/content_autofill_driver_factory.cc
index 9593cc21..c193ca2 100644
--- a/components/autofill/content/browser/content_autofill_driver_factory.cc
+++ b/components/autofill/content/browser/content_autofill_driver_factory.cc
@@ -211,7 +211,8 @@
         router_.UnregisterDriver(driver);
       }
     }
-    NavigationFinished();
+    NavigationFinished(AutofillDriverFactory::HideUi(
+        !navigation_handle->IsInPrerenderedMainFrame()));
     driver->DidNavigateFrame(navigation_handle);
   }
 }
diff --git a/components/autofill/content/renderer/autofill_agent.cc b/components/autofill/content/renderer/autofill_agent.cc
index 13004e9..a6073e2 100644
--- a/components/autofill/content/renderer/autofill_agent.cc
+++ b/components/autofill/content/renderer/autofill_agent.cc
@@ -718,8 +718,7 @@
   // has made, and doesn't depend on form_cache's internal state.
   std::vector<WebElement> fieldsets;
   std::vector<WebFormControlElement> control_elements =
-      form_util::GetUnownedAutofillableFormFieldElements(document.All(),
-                                                         &fieldsets);
+      form_util::GetUnownedAutofillableFormFieldElements(document, &fieldsets);
 
   std::vector<WebElement> iframe_elements =
       form_util::GetUnownedIframeElements(document);
@@ -1400,7 +1399,7 @@
     std::vector<WebElement> fieldsets;
     WebVector<WebFormControlElement> elements =
         form_util::GetUnownedAutofillableFormFieldElements(
-            element_.GetDocument().All(), &fieldsets);
+            element_.GetDocument(), &fieldsets);
     // If a unique match was found.
     if (FindTheUniqueNewVersionOfOldElement(
             elements, potential_match_encountered, matching_element,
diff --git a/components/autofill/content/renderer/form_autofill_util.cc b/components/autofill/content/renderer/form_autofill_util.cc
index 1095a48a..e6eb436 100644
--- a/components/autofill/content/renderer/form_autofill_util.cc
+++ b/components/autofill/content/renderer/form_autofill_util.cc
@@ -1211,8 +1211,8 @@
     return {};
 
   std::vector<WebFormControlElement> control_elements =
-      GetUnownedAutofillableFormFieldElements(
-          initiating_element.GetDocument().All(), nullptr);
+      GetUnownedAutofillableFormFieldElements(initiating_element.GetDocument(),
+                                              nullptr);
   if (!IsElementInControlElementSet(initiating_element, control_elements))
     return {};
 
@@ -2137,9 +2137,10 @@
 }
 
 std::vector<WebFormControlElement> GetUnownedFormFieldElements(
-    const WebElementCollection& elements,
+    const WebDocument& document,
     std::vector<WebElement>* fieldsets) {
   std::vector<WebFormControlElement> unowned_fieldset_children;
+  const WebElementCollection& elements = document.All();
   for (WebElement element = elements.FirstItem(); !element.IsNull();
        element = elements.NextItem()) {
     if (element.IsFormControlElement()) {
@@ -2158,10 +2159,10 @@
 }
 
 std::vector<WebFormControlElement> GetUnownedAutofillableFormFieldElements(
-    const WebElementCollection& elements,
+    const WebDocument& document,
     std::vector<WebElement>* fieldsets) {
   return ExtractAutofillableElementsFromSet(
-      GetUnownedFormFieldElements(elements, fieldsets));
+      GetUnownedFormFieldElements(document, fieldsets));
 }
 
 std::vector<WebElement> GetUnownedIframeElements(const WebDocument& document) {
@@ -2240,7 +2241,7 @@
     WebDocument document = element.GetDocument();
     std::vector<WebElement> fieldsets;
     std::vector<WebFormControlElement> control_elements =
-        GetUnownedAutofillableFormFieldElements(document.All(), &fieldsets);
+        GetUnownedAutofillableFormFieldElements(document, &fieldsets);
     std::vector<WebElement> iframe_elements =
         GetUnownedIframeElements(document);
     return UnownedFormElementsAndFieldSetsToFormData(
diff --git a/components/autofill/content/renderer/form_autofill_util.h b/components/autofill/content/renderer/form_autofill_util.h
index d871539..fbf94b51 100644
--- a/components/autofill/content/renderer/form_autofill_util.h
+++ b/components/autofill/content/renderer/form_autofill_util.h
@@ -226,14 +226,14 @@
 // If |fieldsets| is not NULL, also append the fieldsets encountered that are
 // not part of a form.
 std::vector<blink::WebFormControlElement> GetUnownedFormFieldElements(
-    const blink::WebElementCollection& elements,
+    const blink::WebDocument& document,
     std::vector<blink::WebElement>* fieldsets);
 
 // A shorthand for filtering the results of GetUnownedFormFieldElements with
 // ExtractAutofillableElementsFromSet.
 std::vector<blink::WebFormControlElement>
 GetUnownedAutofillableFormFieldElements(
-    const blink::WebElementCollection& elements,
+    const blink::WebDocument& document,
     std::vector<blink::WebElement>* fieldsets);
 
 // Returns the <iframe> elements that are not in the scope of any <form>.
diff --git a/components/autofill/content/renderer/form_autofill_util_browsertest.cc b/components/autofill/content/renderer/form_autofill_util_browsertest.cc
index 1ba4693..36db010a 100644
--- a/components/autofill/content/renderer/form_autofill_util_browsertest.cc
+++ b/components/autofill/content/renderer/form_autofill_util_browsertest.cc
@@ -1142,7 +1142,7 @@
   if (!test_case.form_id) {  // Synthetic form.
     std::vector<blink::WebElement> fieldsets;
     std::vector<WebFormControlElement> control_elements =
-        GetUnownedAutofillableFormFieldElements(doc.All(), &fieldsets);
+        GetUnownedAutofillableFormFieldElements(doc, &fieldsets);
     std::vector<WebElement> iframe_elements =
         form_util::GetUnownedIframeElements(doc);
     ASSERT_TRUE(UnownedFormElementsAndFieldSetsToFormData(
diff --git a/components/autofill/content/renderer/form_cache.cc b/components/autofill/content/renderer/form_cache.cc
index ead89798..0da65d73 100644
--- a/components/autofill/content/renderer/form_cache.cc
+++ b/components/autofill/content/renderer/form_cache.cc
@@ -220,8 +220,8 @@
   // Look for more parseable fields outside of forms. Create a synthetic form
   // from them.
   std::vector<WebElement> fieldsets;
-  control_elements = form_util::GetUnownedAutofillableFormFieldElements(
-      document.All(), &fieldsets);
+  control_elements =
+      form_util::GetUnownedAutofillableFormFieldElements(document, &fieldsets);
   std::vector<WebElement> iframe_elements =
       form_util::GetUnownedIframeElements(document);
 
@@ -318,8 +318,7 @@
   // Look for more parseable fields outside of forms.
   std::vector<WebElement> fieldsets;
   std::vector<WebFormControlElement> control_elements =
-      form_util::GetUnownedAutofillableFormFieldElements(document.All(),
-                                                         &fieldsets);
+      form_util::GetUnownedAutofillableFormFieldElements(document, &fieldsets);
   std::vector<WebElement> iframe_elements =
       form_util::GetUnownedIframeElements(document);
 
@@ -432,7 +431,7 @@
   std::vector<WebFormControlElement> control_elements =
       form_element.IsNull()
           ? form_util::GetUnownedAutofillableFormFieldElements(
-                element.GetDocument().All(), nullptr)
+                element.GetDocument(), nullptr)
           : form_util::ExtractAutofillableElementsInForm(form_element);
 
   if (control_elements.empty())
@@ -479,8 +478,8 @@
 
   if (form.data.unique_renderer_id.is_null()) {  // Form is synthetic.
     WebDocument document = frame_->GetDocument();
-    control_elements = form_util::GetUnownedAutofillableFormFieldElements(
-        document.All(), nullptr);
+    control_elements =
+        form_util::GetUnownedAutofillableFormFieldElements(document, nullptr);
   } else {
     for (const WebFormElement& form_element : frame_->GetDocument().Forms()) {
       FormRendererId form_id(form_element.UniqueRendererFormId());
diff --git a/components/autofill/content/renderer/password_autofill_agent.cc b/components/autofill/content/renderer/password_autofill_agent.cc
index 1a6c8d8..b173b6d 100644
--- a/components/autofill/content/renderer/password_autofill_agent.cc
+++ b/components/autofill/content/renderer/password_autofill_agent.cc
@@ -323,7 +323,7 @@
   std::vector<WebFormControlElement> elements;
   if (password_element.Form().IsNull()) {
     elements = form_util::GetUnownedAutofillableFormFieldElements(
-        frame->GetDocument().All(), nullptr);
+        frame->GetDocument(), nullptr);
   } else {
     elements = password_element.Form().GetFormControlElements().ReleaseVector();
   }
@@ -1106,7 +1106,7 @@
 
   std::vector<WebFormControlElement> unowned_elements =
       form_util::GetUnownedAutofillableFormFieldElements(
-          render_frame()->GetWebFrame()->GetDocument().All(), nullptr);
+          render_frame()->GetWebFrame()->GetDocument(), nullptr);
   std::unique_ptr<FormData> form_data = GetFormDataFromUnownedInputElements();
   std::string form_signature;
   if (form_data)
@@ -1193,8 +1193,8 @@
   bool add_unowned_inputs = true;
   if (only_visible) {
     std::vector<WebFormControlElement> control_elements =
-        form_util::GetUnownedAutofillableFormFieldElements(
-            frame->GetDocument().All(), nullptr);
+        form_util::GetUnownedAutofillableFormFieldElements(frame->GetDocument(),
+                                                           nullptr);
     add_unowned_inputs =
         form_util::IsSomeControlElementVisible(control_elements);
     LogBoolean(logger.get(), Logger::STRING_UNOWNED_INPUTS_VISIBLE,
diff --git a/components/autofill/content/renderer/password_form_conversion_utils.cc b/components/autofill/content/renderer/password_form_conversion_utils.cc
index f7eb1f8..7448f588 100644
--- a/components/autofill/content/renderer/password_form_conversion_utils.cc
+++ b/components/autofill/content/renderer/password_form_conversion_utils.cc
@@ -171,8 +171,7 @@
   std::vector<WebElement> fieldsets;
 
   std::vector<WebFormControlElement> control_elements =
-      form_util::GetUnownedFormFieldElements(frame.GetDocument().All(),
-                                             &fieldsets);
+      form_util::GetUnownedFormFieldElements(frame.GetDocument(), &fieldsets);
   if (control_elements.empty())
     return nullptr;
 
diff --git a/components/autofill/content/renderer/password_generation_agent.cc b/components/autofill/content/renderer/password_generation_agent.cc
index 74d3022e..1d8662b8 100644
--- a/components/autofill/content/renderer/password_generation_agent.cc
+++ b/components/autofill/content/renderer/password_generation_agent.cc
@@ -414,8 +414,7 @@
       blink::WebDocument doc = frame.GetDocument();
       if (doc.IsNull())
         return false;
-      control_elements =
-          form_util::GetUnownedFormFieldElements(doc.All(), nullptr);
+      control_elements = form_util::GetUnownedFormFieldElements(doc, nullptr);
     }
 
     MaybeCreateCurrentGenerationItem(
diff --git a/components/autofill/core/browser/autofill_driver.h b/components/autofill/core/browser/autofill_driver.h
index ef392d7..76b3dec 100644
--- a/components/autofill/core/browser/autofill_driver.h
+++ b/components/autofill/core/browser/autofill_driver.h
@@ -39,9 +39,13 @@
   // Returns whether the user is currently operating in an incognito context.
   virtual bool IsIncognito() const = 0;
 
-  // Returns whether AutofillDriver instance is associated to the main frame.
+  // Returns whether AutofillDriver instance is associated with a main frame.
   virtual bool IsInMainFrame() const = 0;
 
+  // Returns whether the AutofillDriver instance is associated with a
+  // prerendered frame.
+  virtual bool IsPrerendering() const = 0;
+
   // Returns true iff a popup can be shown on the behalf of the associated
   // frame.
   virtual bool CanShowAutofillUi() const = 0;
diff --git a/components/autofill/core/browser/autofill_driver_factory.cc b/components/autofill/core/browser/autofill_driver_factory.cc
index dc1a34db..ef162afb 100644
--- a/components/autofill/core/browser/autofill_driver_factory.cc
+++ b/components/autofill/core/browser/autofill_driver_factory.cc
@@ -20,8 +20,9 @@
   return mapping == driver_map_.end() ? nullptr : mapping->second.get();
 }
 
-void AutofillDriverFactory::NavigationFinished() {
-  client_->HideAutofillPopup(PopupHidingReason::kNavigation);
+void AutofillDriverFactory::NavigationFinished(HideUi hide_ui) {
+  if (hide_ui)
+    client_->HideAutofillPopup(PopupHidingReason::kNavigation);
 }
 
 void AutofillDriverFactory::TabHidden() {
diff --git a/components/autofill/core/browser/autofill_driver_factory.h b/components/autofill/core/browser/autofill_driver_factory.h
index 68f8be8..e34d1c1 100644
--- a/components/autofill/core/browser/autofill_driver_factory.h
+++ b/components/autofill/core/browser/autofill_driver_factory.h
@@ -10,6 +10,7 @@
 
 #include "base/callback_forward.h"
 #include "base/macros.h"
+#include "base/types/strong_alias.h"
 
 namespace autofill {
 
@@ -20,6 +21,8 @@
 // creating, notifying, retrieving and deleting on demand.
 class AutofillDriverFactory {
  public:
+  using HideUi = base::StrongAlias<class HideUiTag, bool>;
+
   explicit AutofillDriverFactory(AutofillClient* client);
 
   ~AutofillDriverFactory();
@@ -28,8 +31,8 @@
   // null if there is none.
   AutofillDriver* DriverForKey(void* key);
 
-  // Handles finished navigation in the main frame.
-  void NavigationFinished();
+  // Handles finished navigation in a main frame.
+  void NavigationFinished(HideUi hide_ui);
 
   // Handles hiding of the corresponding tab.
   void TabHidden();
diff --git a/components/autofill/core/browser/autofill_driver_factory_unittest.cc b/components/autofill/core/browser/autofill_driver_factory_unittest.cc
index b3a5aa8..1867780 100644
--- a/components/autofill/core/browser/autofill_driver_factory_unittest.cc
+++ b/components/autofill/core/browser/autofill_driver_factory_unittest.cc
@@ -170,7 +170,7 @@
 
 TEST_F(AutofillDriverFactoryTest, NavigationFinished) {
   EXPECT_CALL(client_, HideAutofillPopup(PopupHidingReason::kNavigation));
-  factory_.NavigationFinished();
+  factory_.NavigationFinished(AutofillDriverFactory::HideUi(true));
 }
 
 TEST_F(AutofillDriverFactoryTest, TabHidden) {
diff --git a/components/autofill/core/browser/autofill_external_delegate.cc b/components/autofill/core/browser/autofill_external_delegate.cc
index 8048922..61ecfc5 100644
--- a/components/autofill/core/browser/autofill_external_delegate.cc
+++ b/components/autofill/core/browser/autofill_external_delegate.cc
@@ -361,7 +361,9 @@
 }
 
 void AutofillExternalDelegate::Reset() {
-  manager_->client()->HideAutofillPopup(PopupHidingReason::kNavigation);
+  // We should not affect UI on the active page due to a prerendered page.
+  if (!manager_->driver()->IsPrerendering())
+    manager_->client()->HideAutofillPopup(PopupHidingReason::kNavigation);
 }
 
 base::WeakPtr<AutofillExternalDelegate> AutofillExternalDelegate::GetWeakPtr() {
diff --git a/components/autofill/core/browser/autofill_manager.cc b/components/autofill/core/browser/autofill_manager.cc
index f014be9e..63bf5624 100644
--- a/components/autofill/core/browser/autofill_manager.cc
+++ b/components/autofill/core/browser/autofill_manager.cc
@@ -167,9 +167,9 @@
   translate_observation_.Reset();
 }
 
-LanguageCode AutofillManager::GetCurrentPageLanguage() const {
-  DCHECK(client_);
-  const translate::LanguageState* language_state = client_->GetLanguageState();
+LanguageCode AutofillManager::GetCurrentPageLanguage() {
+  DCHECK(client());
+  const translate::LanguageState* language_state = client()->GetLanguageState();
   if (!language_state)
     return LanguageCode();
   return LanguageCode(language_state->current_language());
@@ -375,11 +375,11 @@
 
 std::unique_ptr<AutofillMetrics::FormInteractionsUkmLogger>
 AutofillManager::CreateFormInteractionsUkmLogger() {
-  if (!client())
+  if (!unsafe_client())
     return nullptr;
 
   return std::make_unique<AutofillMetrics::FormInteractionsUkmLogger>(
-      client()->GetUkmRecorder(), client()->GetUkmSourceId());
+      unsafe_client()->GetUkmRecorder(), unsafe_client()->GetUkmSourceId());
 }
 
 size_t AutofillManager::FindCachedFormsBySignature(
diff --git a/components/autofill/core/browser/autofill_manager.h b/components/autofill/core/browser/autofill_manager.h
index a73d88351..0f09f65 100644
--- a/components/autofill/core/browser/autofill_manager.h
+++ b/components/autofill/core/browser/autofill_manager.h
@@ -79,8 +79,16 @@
 
   ~AutofillManager() override;
 
-  AutofillClient* client() { return client_; }
-  const AutofillClient* client() const { return client_; }
+  // The following will fail a DCHECK if called for a prerendered main frame.
+  AutofillClient* client() {
+    DCHECK(!driver()->IsPrerendering());
+    return client_;
+  }
+
+  const AutofillClient* client() const {
+    DCHECK(!driver()->IsPrerendering());
+    return client_;
+  }
 
   // Invoked when the value of textfield is changed.
   // |bounding_box| are viewport coordinates.
@@ -193,6 +201,7 @@
   }
 
   AutofillDriver* driver() { return driver_; }
+  const AutofillDriver* driver() const { return driver_; }
 
   AutofillDownloadManager* download_manager() {
     return download_manager_.get();
@@ -244,7 +253,16 @@
   LogManager* log_manager() { return log_manager_; }
 
   // Retrieves the page language from |client_|
-  LanguageCode GetCurrentPageLanguage() const;
+  LanguageCode GetCurrentPageLanguage();
+
+  // The following do not check for prerendering. These should only used while
+  // constructing or resetting the manager.
+  // TODO(crbug.com/1239281): if we never intend to support multiple navigations
+  // while prerendering, these will be unnecessary (they're used during Reset
+  // which can be called during prerendering, but we could skip Reset for
+  // prerendering if we never have state to clear).
+  AutofillClient* unsafe_client() { return client_; }
+  const AutofillClient* unsafe_client() const { return client_; }
 
   virtual void OnFormSubmittedImpl(const FormData& form,
                                    bool known_success,
@@ -339,6 +357,9 @@
   // outlive this object.
   AutofillDriver* const driver_;
 
+  // Do not access this directly. Instead, please use client() or
+  // unsafe_client(). These functions check (or explicitly don't check) that the
+  // client isn't accessed incorrectly.
   AutofillClient* const client_;
 
   LogManager* const log_manager_;
diff --git a/components/autofill/core/browser/browser_autofill_manager.cc b/components/autofill/core/browser/browser_autofill_manager.cc
index 7c34877..bd5a916b 100644
--- a/components/autofill/core/browser/browser_autofill_manager.cc
+++ b/components/autofill/core/browser/browser_autofill_manager.cc
@@ -1588,12 +1588,14 @@
   DCHECK(!pending_form_data_);
   AutofillManager::Reset();
   address_form_event_logger_ = std::make_unique<AddressFormEventLogger>(
-      driver()->IsInMainFrame(), form_interactions_ukm_logger(), client());
+      driver()->IsInMainFrame(), form_interactions_ukm_logger(),
+      unsafe_client());
   credit_card_form_event_logger_ = std::make_unique<CreditCardFormEventLogger>(
       driver()->IsInMainFrame(), form_interactions_ukm_logger(), personal_data_,
-      client());
+      unsafe_client());
   credit_card_access_manager_ = std::make_unique<CreditCardAccessManager>(
-      driver(), client(), personal_data_, credit_card_form_event_logger_.get());
+      driver(), unsafe_client(), personal_data_,
+      credit_card_form_event_logger_.get());
 
   has_logged_autofill_enabled_ = false;
   has_logged_address_suggestions_count_ = false;
diff --git a/components/autofill/core/browser/payments/test_internal_authenticator.h b/components/autofill/core/browser/payments/test_internal_authenticator.h
index f33094c2..982879a 100644
--- a/components/autofill/core/browser/payments/test_internal_authenticator.h
+++ b/components/autofill/core/browser/payments/test_internal_authenticator.h
@@ -19,6 +19,7 @@
 
   // InternalAuthenticator:
   void SetEffectiveOrigin(const url::Origin& origin) override {}
+  void SetPaymentOptions(blink::mojom::PaymentOptionsPtr payment) override {}
   void MakeCredential(
       blink::mojom::PublicKeyCredentialCreationOptionsPtr options,
       blink::mojom::Authenticator::MakeCredentialCallback callback) override {}
diff --git a/components/autofill/core/browser/test_autofill_driver.cc b/components/autofill/core/browser/test_autofill_driver.cc
index daa6497..2d6a6576 100644
--- a/components/autofill/core/browser/test_autofill_driver.cc
+++ b/components/autofill/core/browser/test_autofill_driver.cc
@@ -29,6 +29,10 @@
   return is_in_main_frame_;
 }
 
+bool TestAutofillDriver::IsPrerendering() const {
+  return false;
+}
+
 bool TestAutofillDriver::CanShowAutofillUi() const {
   return true;
 }
diff --git a/components/autofill/core/browser/test_autofill_driver.h b/components/autofill/core/browser/test_autofill_driver.h
index d12b80c..283a276 100644
--- a/components/autofill/core/browser/test_autofill_driver.h
+++ b/components/autofill/core/browser/test_autofill_driver.h
@@ -37,6 +37,7 @@
   // AutofillDriver implementation overrides.
   bool IsIncognito() const override;
   bool IsInMainFrame() const override;
+  bool IsPrerendering() const override;
   bool CanShowAutofillUi() const override;
   ui::AXTreeID GetAxTreeId() const override;
   scoped_refptr<network::SharedURLLoaderFactory> GetURLLoaderFactory() override;
diff --git a/components/autofill/ios/browser/autofill_driver_ios.h b/components/autofill/ios/browser/autofill_driver_ios.h
index d4204b8e..f25fa74 100644
--- a/components/autofill/ios/browser/autofill_driver_ios.h
+++ b/components/autofill/ios/browser/autofill_driver_ios.h
@@ -43,6 +43,7 @@
   // AutofillDriver:
   bool IsIncognito() const override;
   bool IsInMainFrame() const override;
+  bool IsPrerendering() const override;
   bool CanShowAutofillUi() const override;
   ui::AXTreeID GetAxTreeId() const override;
   scoped_refptr<network::SharedURLLoaderFactory> GetURLLoaderFactory() override;
diff --git a/components/autofill/ios/browser/autofill_driver_ios.mm b/components/autofill/ios/browser/autofill_driver_ios.mm
index fdb7ecd..4fcf8c4 100644
--- a/components/autofill/ios/browser/autofill_driver_ios.mm
+++ b/components/autofill/ios/browser/autofill_driver_ios.mm
@@ -74,6 +74,10 @@
   return web_frame ? web_frame->IsMainFrame() : true;
 }
 
+bool AutofillDriverIOS::IsPrerendering() const {
+  return false;
+}
+
 bool AutofillDriverIOS::CanShowAutofillUi() const {
   return true;
 }
diff --git a/components/content_settings/core/browser/website_settings_registry.cc b/components/content_settings/core/browser/website_settings_registry.cc
index be0b2df44..e2382ce 100644
--- a/components/content_settings/core/browser/website_settings_registry.cc
+++ b/components/content_settings/core/browser/website_settings_registry.cc
@@ -239,6 +239,11 @@
            WebsiteSettingsInfo::NOT_LOSSY,
            WebsiteSettingsInfo::SINGLE_ORIGIN_ONLY_SCOPE, ALL_PLATFORMS,
            WebsiteSettingsInfo::DONT_INHERIT_IN_INCOGNITO);
+  Register(ContentSettingsType::HTTP_ALLOWED, "http-allowed", nullptr,
+           WebsiteSettingsInfo::UNSYNCABLE, WebsiteSettingsInfo::NOT_LOSSY,
+           WebsiteSettingsInfo::SINGLE_ORIGIN_WITH_EMBEDDED_EXCEPTIONS_SCOPE,
+           DESKTOP | PLATFORM_ANDROID,
+           WebsiteSettingsInfo::INHERIT_IN_INCOGNITO);
 }
 
 }  // namespace content_settings
diff --git a/components/content_settings/core/common/content_settings.cc b/components/content_settings/core/common/content_settings.cc
index c9f2d85..1e897d01 100644
--- a/components/content_settings/core/common/content_settings.cc
+++ b/components/content_settings/core/common/content_settings.cc
@@ -98,6 +98,7 @@
     {ContentSettingsType::FEDERATED_IDENTITY_SHARING, 77},
     {ContentSettingsType::FEDERATED_IDENTITY_REQUEST, 78},
     {ContentSettingsType::JAVASCRIPT_JIT, 79},
+    {ContentSettingsType::HTTP_ALLOWED, 80},
 };
 
 }  // namespace
diff --git a/components/content_settings/core/common/content_settings_types.h b/components/content_settings/core/common/content_settings_types.h
index 44f58f3..1b4cafc 100644
--- a/components/content_settings/core/common/content_settings_types.h
+++ b/components/content_settings/core/common/content_settings_types.h
@@ -266,6 +266,13 @@
   // Whether to use the v8 optimized JIT for running JavaScript on the page.
   JAVASCRIPT_JIT,
 
+  // Content setting which stores user decisions to allow loading a site over
+  // HTTP. Entries are added by hostname when a user bypasses the HTTPS-First
+  // Mode interstitial warning when a site does not support HTTPS. Allowed hosts
+  // are exact hostname matches -- subdomains of a host on the allowlist must be
+  // separately allowlisted.
+  HTTP_ALLOWED,
+
   NUM_TYPES,
 };
 
diff --git a/components/crash/content/browser/child_exit_observer_android.cc b/components/crash/content/browser/child_exit_observer_android.cc
index bf9ab3a..6987882 100644
--- a/components/crash/content/browser/child_exit_observer_android.cc
+++ b/components/crash/content/browser/child_exit_observer_android.cc
@@ -32,12 +32,6 @@
   info->threw_exception_during_init = content_info.threw_exception_during_init;
   info->was_killed_intentionally_by_browser =
       content_info.was_killed_intentionally_by_browser;
-  info->remaining_process_with_strong_binding =
-      content_info.remaining_process_with_strong_binding;
-  info->remaining_process_with_moderate_binding =
-      content_info.remaining_process_with_moderate_binding;
-  info->remaining_process_with_waived_binding =
-      content_info.remaining_process_with_waived_binding;
   info->best_effort_reverse_rank = content_info.best_effort_reverse_rank;
   info->was_oom_protected_status =
       content_info.status == base::TERMINATION_STATUS_OOM_PROTECTED;
diff --git a/components/crash/content/browser/child_exit_observer_android.h b/components/crash/content/browser/child_exit_observer_android.h
index 3b17693..3d35d0c 100644
--- a/components/crash/content/browser/child_exit_observer_android.h
+++ b/components/crash/content/browser/child_exit_observer_android.h
@@ -74,9 +74,6 @@
         base::android::ChildBindingState::UNBOUND;
     bool threw_exception_during_init = false;
     bool was_killed_intentionally_by_browser = false;
-    int remaining_process_with_strong_binding = 0;
-    int remaining_process_with_moderate_binding = 0;
-    int remaining_process_with_waived_binding = 0;
     int best_effort_reverse_rank = -1;
 
     // Note this is slightly different |has_oom_protection_bindings|.
diff --git a/components/crash/content/browser/crash_metrics_reporter_android.cc b/components/crash/content/browser/crash_metrics_reporter_android.cc
index a7f4ad0..17e71b4 100644
--- a/components/crash/content/browser/crash_metrics_reporter_android.cc
+++ b/components/crash/content/browser/crash_metrics_reporter_android.cc
@@ -294,32 +294,6 @@
                      &reported_counts);
   }
 
-  if (app_foreground && android_oom_kill &&
-      info.binding_state == base::android::ChildBindingState::STRONG) {
-    const bool has_waived = info.remaining_process_with_waived_binding > 0;
-    const bool has_moderate = info.remaining_process_with_moderate_binding > 0;
-    const bool has_strong = info.remaining_process_with_strong_binding > 0;
-    BindingStateCombo combo;
-    if (has_waived && has_moderate) {
-      combo = has_strong ? BindingStateCombo::kHasWaivedHasModerateHasStrong
-                         : BindingStateCombo::kHasWaivedHasModerateNoStrong;
-    } else if (has_waived) {
-      combo = has_strong ? BindingStateCombo::kHasWaivedNoModerateHasStrong
-                         : BindingStateCombo::kHasWaivedNoModerateNoStrong;
-    } else if (has_moderate) {
-      combo = has_strong ? BindingStateCombo::kNoWaivedHasModerateHasStrong
-                         : BindingStateCombo::kNoWaivedHasModerateNoStrong;
-    } else {
-      combo = has_strong ? BindingStateCombo::kNoWaivedNoModerateHasStrong
-                         : BindingStateCombo::kNoWaivedNoModerateNoStrong;
-    }
-    UMA_HISTOGRAM_ENUMERATION(
-        "Stability.Android.StrongBindingOomRemainingBindingState", combo);
-    UMA_HISTOGRAM_EXACT_LINEAR(
-        "Stability.Android.StrongBindingOomRemainingStrongBindingCount",
-        info.remaining_process_with_strong_binding, 20);
-  }
-
   if (android_oom_kill) {
     if (info.best_effort_reverse_rank >= 0) {
       UMA_HISTOGRAM_EXACT_LINEAR("Stability.Android.OomKillReverseRank",
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 3e6fd76..90b28c5 100644
--- a/components/embedder_support/android/metrics/android_metrics_service_client.cc
+++ b/components/embedder_support/android/metrics/android_metrics_service_client.cc
@@ -240,16 +240,18 @@
   ukm::UkmService::RegisterPrefs(registry);
 }
 
-void AndroidMetricsServiceClient::Initialize(PrefService* pref_service) {
+void AndroidMetricsServiceClient::Initialize(
+    const base::FilePath& user_data_dir,
+    PrefService* pref_service) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(!init_finished_);
 
   pref_service_ = pref_service;
 
-  metrics_state_manager_ =
-      MetricsStateManager::Create(pref_service_, this, std::wstring(),
-                                  base::BindRepeating(&StoreClientInfo),
-                                  base::BindRepeating(&LoadClientInfo));
+  metrics_state_manager_ = MetricsStateManager::Create(
+      pref_service_, this, std::wstring(), user_data_dir,
+      base::BindRepeating(&StoreClientInfo),
+      base::BindRepeating(&LoadClientInfo));
 
   init_finished_ = true;
 
diff --git a/components/embedder_support/android/metrics/android_metrics_service_client.h b/components/embedder_support/android/metrics/android_metrics_service_client.h
index 6fd7b4c..534a5aff 100644
--- a/components/embedder_support/android/metrics/android_metrics_service_client.h
+++ b/components/embedder_support/android/metrics/android_metrics_service_client.h
@@ -9,6 +9,7 @@
 #include <string>
 
 #include "base/callback_forward.h"
+#include "base/files/file_path.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/metrics/field_trial.h"
 #include "base/sequence_checker.h"
@@ -103,7 +104,13 @@
 
   static void RegisterPrefs(PrefRegistrySimple* registry);
 
-  void Initialize(PrefService* pref_service);
+  // Initializes, but does not necessarily start, the MetricsService. See the
+  // documentation at the top of the file for more details.
+  //
+  // |user_data_dir| is the path to the client's user data directory. If empty,
+  // a separate file will not be used for Variations Safe Mode prefs.
+  void Initialize(const base::FilePath& user_data_dir,
+                  PrefService* pref_service);
   void SetHaveMetricsConsent(bool user_consent, bool app_consent);
   void SetFastStartupForTesting(bool fast_startup_for_testing);
   void SetUploadIntervalForTesting(const base::TimeDelta& upload_interval);
diff --git a/components/embedder_support/android/metrics/android_metrics_service_client_unittest.cc b/components/embedder_support/android/metrics/android_metrics_service_client_unittest.cc
index b9091d5..fb928ed 100644
--- a/components/embedder_support/android/metrics/android_metrics_service_client_unittest.cc
+++ b/components/embedder_support/android/metrics/android_metrics_service_client_unittest.cc
@@ -7,6 +7,7 @@
 #include <memory>
 
 #include "base/command_line.h"
+#include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/macros.h"
 #include "base/memory/scoped_refptr.h"
@@ -41,6 +42,10 @@
 
   ~TestClient() override = default;
 
+  void Initialize(PrefService* pref_service) {
+    AndroidMetricsServiceClient::Initialize(base::FilePath(), pref_service);
+  }
+
   bool IsRecordingActive() {
     auto* service = GetMetricsService();
     if (service)
diff --git a/components/history/core/browser/history_service.cc b/components/history/core/browser/history_service.cc
index 7d47ba709..5a9234b3 100644
--- a/components/history/core/browser/history_service.cc
+++ b/components/history/core/browser/history_service.cc
@@ -472,8 +472,8 @@
 }
 
 void HistoryService::AddContentModelAnnotationsForVisit(
-    VisitID visit_id,
-    const VisitContentModelAnnotations& model_annotations) {
+    const VisitContentModelAnnotations& model_annotations,
+    VisitID visit_id) {
   TRACE_EVENT0("browser", "HistoryService::AddContentModelAnnotationsForVisit");
   DCHECK(backend_task_runner_) << "History service being called after cleanup";
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -483,6 +483,17 @@
                      history_backend_, visit_id, model_annotations));
 }
 
+void HistoryService::AddRelatedSearchesForVisit(
+    const std::vector<std::string>& related_searches,
+    VisitID visit_id) {
+  TRACE_EVENT0("browser", "HistoryService::AddRelatedSearchesForVisit");
+  DCHECK(backend_task_runner_) << "History service being called after cleanup";
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  ScheduleTask(PRIORITY_NORMAL,
+               base::BindOnce(&HistoryBackend::AddRelatedSearchesForVisit,
+                              history_backend_, visit_id, related_searches));
+}
+
 void HistoryService::AddPageWithDetails(const GURL& url,
                                         const std::u16string& title,
                                         int visit_count,
diff --git a/components/history/core/browser/history_service.h b/components/history/core/browser/history_service.h
index 959265a..c15e82d 100644
--- a/components/history/core/browser/history_service.h
+++ b/components/history/core/browser/history_service.h
@@ -225,8 +225,14 @@
   // Updates the history database with the content model annotations for the
   // visit.
   void AddContentModelAnnotationsForVisit(
-      VisitID visit_id,
-      const VisitContentModelAnnotations& model_annotations);
+      const VisitContentModelAnnotations& model_annotations,
+      VisitID visit_id);
+
+  // Updates the history database with the related searches for the Google SRP
+  // visit.
+  void AddRelatedSearchesForVisit(
+      const std::vector<std::string>& related_searches,
+      VisitID visit_id);
 
   // Querying ------------------------------------------------------------------
 
diff --git a/components/media_message_center/media_notification_view_modern_impl.cc b/components/media_message_center/media_notification_view_modern_impl.cc
index acc1769..8beda3e 100644
--- a/components/media_message_center/media_notification_view_modern_impl.cc
+++ b/components/media_message_center/media_notification_view_modern_impl.cc
@@ -649,6 +649,9 @@
 }
 
 void MediaNotificationViewModernImpl::UpdateForegroundColor() {
+  if (!GetWidget())
+    return;
+
   const SkColor background =
       GetMediaNotificationBackground()->GetBackgroundColor(*this);
   const SkColor foreground =
diff --git a/components/metrics/BUILD.gn b/components/metrics/BUILD.gn
index 4cb2faed..2804579 100644
--- a/components/metrics/BUILD.gn
+++ b/components/metrics/BUILD.gn
@@ -124,6 +124,7 @@
     "//build:chromeos_buildflags",
     "//components/prefs",
     "//components/variations",
+    "//components/variations/service:constants",
     "//components/version_info:version_info",
     "//crypto",
     "//extensions/buildflags",
@@ -473,6 +474,7 @@
     "//components/sync/base",
     "//components/sync/driver:test_support",
     "//components/variations",
+    "//components/variations/service:constants",
     "//extensions/buildflags",
     "//mojo/public/cpp/bindings",
     "//net:test_support",
diff --git a/components/metrics/clean_exit_beacon.cc b/components/metrics/clean_exit_beacon.cc
index 5cd6a60..2fbd86f 100644
--- a/components/metrics/clean_exit_beacon.cc
+++ b/components/metrics/clean_exit_beacon.cc
@@ -4,15 +4,24 @@
 
 #include "components/metrics/clean_exit_beacon.h"
 
+#include <memory>
+#include <utility>
+
 #include "base/check_op.h"
 #include "base/cxx17_backports.h"
+#include "base/files/file_util.h"
+#include "base/json/json_file_value_serializer.h"
+#include "base/json/json_string_value_serializer.h"
+#include "base/metrics/field_trial.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
-#include "build/build_config.h"
+#include "base/path_service.h"
+#include "base/values.h"
 #include "components/metrics/metrics_pref_names.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
 #include "components/variations/pref_names.h"
+#include "components/variations/service/variations_safe_mode_constants.h"
 
 #if defined(OS_WIN)
 #include <windows.h>
@@ -24,15 +33,51 @@
 namespace metrics {
 namespace {
 
+using ::variations::kExtendedSafeModeTrial;
+using ::variations::kSignalAndWriteSynchronouslyViaPrefServiceGroup;
+using ::variations::kSignalAndWriteViaFileUtilGroup;
+using ::variations::kWriteSynchronouslyViaPrefServiceGroup;
+using ::variations::prefs::kVariationsCrashStreak;
+
 // Denotes whether Chrome should perform clean shutdown steps: signaling that
 // Chrome is exiting cleanly and then CHECKing that is has shutdown cleanly.
 // This may be modified by SkipCleanShutdownStepsForTesting().
 bool g_skip_clean_shutdown_steps = false;
 
+// Writes |exited_cleanly| and the crash streak to the file located at
+// |beacon_file_path|.
+void WriteVariationsSafeModeFile(const base::FilePath& beacon_file_path,
+                                 bool exited_cleanly,
+                                 PrefService* local_state) {
+  DCHECK_EQ(base::FieldTrialList::FindFullName(kExtendedSafeModeTrial),
+            kSignalAndWriteViaFileUtilGroup);
+  base::Value dict(base::Value::Type::DICTIONARY);
+  dict.SetBoolKey(prefs::kStabilityExitedCleanly, exited_cleanly);
+  dict.SetIntKey(kVariationsCrashStreak,
+                 local_state->GetInteger(kVariationsCrashStreak));
+  std::string json_string;
+  JSONStringValueSerializer serializer(&json_string);
+  bool success = serializer.Serialize(dict);
+  DCHECK(success);
+  int data_size = static_cast<int>(json_string.size());
+  DCHECK_NE(data_size, 0);
+  base::WriteFile(beacon_file_path, json_string.data(), data_size);
+}
+
 // Increments kVariationsCrashStreak if |did_previous_session_exit_cleanly| is
 // false. Also, emits the crash streak to a histogram.
+//
+// Either |local_state| or |beacon_file_contents| is used to retrieve the crash
+// streak depending on the client's Extended Variations Safe Mode experiment
+// group in the last session.
 void MaybeIncrementCrashStreak(bool did_previous_session_exit_cleanly,
+                               base::Value* beacon_file_contents,
                                PrefService* local_state) {
+  int num_crashes =
+      beacon_file_contents
+          ? beacon_file_contents->FindKey(kVariationsCrashStreak)->GetInt()
+          : local_state->GetInteger(kVariationsCrashStreak);
+
   // Increment the crash streak if the previous session crashed. Note that the
   // streak is not cleared if the previous run didn’t crash. Instead, it’s
   // incremented on each crash until Chrome is able to successfully fetch a new
@@ -44,34 +89,91 @@
   // kStabilityExitedCleanly. Second, if kVariationsCrashStreak were updated in
   // another function, any crash between CleanExitBeacon() and that function
   // would cause the crash streak to not be to incremented. A consequence of
-  // failing to increment the crash streak is that variations safe mode might
+  // failing to increment the crash streak is that Variations Safe Mode might
   // undercount or be completely unaware of repeated crashes early on in
   // startup.
-  int num_crashes =
-      local_state->GetInteger(variations::prefs::kVariationsCrashStreak);
   if (!did_previous_session_exit_cleanly) {
     ++num_crashes;
-    local_state->SetInteger(variations::prefs::kVariationsCrashStreak,
-                            num_crashes);
+    // Schedule only a Local State write. If the client happens to be in an
+    // Extended Variations Safe Mode experiment group that introduces new
+    // behavior, the crash streak will be written synchronously to disk later on
+    // in startup. See MaybeExtendVariationsSafeMode().
+    local_state->SetInteger(kVariationsCrashStreak, num_crashes);
     local_state->CommitPendingWrite();
   }
   base::UmaHistogramSparse("Variations.SafeMode.Streak.Crashes",
                            base::clamp(num_crashes, 0, 100));
 }
 
+// Returns true if the previous session exited cleanly. Either |local_state| or
+// |beacon_file_contents| is used to get this information. Which is used depends
+// on the client's Extended Variations Safe Mode experiment group in the
+// previous session.
+bool DidPreviousSessionExitCleanly(base::Value* beacon_file_contents,
+                                   PrefService* local_state) {
+  if (beacon_file_contents)
+    return beacon_file_contents->FindKey(prefs::kStabilityExitedCleanly)
+        ->GetBool();
+  return local_state->GetBoolean(prefs::kStabilityExitedCleanly);
+}
+
+// Returns the contents of the file at |beacon_file_path| if the following
+// conditions are all true. Otherwise, returns nullptr.
+//
+// 1. The file path is non-empty.
+// 2. The file exists.
+// 3. The file is successfully read.
+// 4. The file contents are in the expected format with the expected info.
+//
+// The file is not expected to exist for clients that do not belong to the
+// kSignalAndWriteViaFileUtilGroup, but even among clients in that group, there
+// are some edge cases. MaybeGetFileContents() is called before clients are
+// assigned to an Extended Variations Safe Mode experiment group, so a client
+// that is later assigned to the kSignalAndWriteViaFileUtilGroup will not have
+// the file in the first session after updating. It is also possible for a user
+// to delete the file or to reset their variations state with
+// kResetVariationState.
+std::unique_ptr<base::Value> MaybeGetFileContents(
+    const base::FilePath& beacon_file_path) {
+  JSONFileValueDeserializer deserializer(beacon_file_path);
+  std::unique_ptr<base::Value> beacon_file_contents = deserializer.Deserialize(
+      /*error_code=*/nullptr, /*error_message=*/nullptr);
+
+  bool got_beacon_file_contents =
+      beacon_file_contents && beacon_file_contents->is_dict() &&
+      beacon_file_contents->FindKeyOfType(kVariationsCrashStreak,
+                                          base::Value::Type::INTEGER) &&
+      beacon_file_contents->FindKeyOfType(prefs::kStabilityExitedCleanly,
+                                          base::Value::Type::BOOLEAN);
+  base::UmaHistogramBoolean(
+      "Variations.ExtendedSafeMode.GotVariationsFileContents",
+      got_beacon_file_contents);
+
+  if (got_beacon_file_contents)
+    return beacon_file_contents;
+  return nullptr;
+}
+
 }  // namespace
 
 CleanExitBeacon::CleanExitBeacon(const std::wstring& backup_registry_key,
+                                 const base::FilePath& user_data_dir,
                                  PrefService* local_state)
     : local_state_(local_state),
-      did_previous_session_exit_cleanly_(
-          local_state->GetBoolean(prefs::kStabilityExitedCleanly)),
       initial_browser_last_live_timestamp_(
           local_state->GetTime(prefs::kStabilityBrowserLastLiveTimeStamp)),
       backup_registry_key_(backup_registry_key) {
   DCHECK_NE(PrefService::INITIALIZATION_STATUS_WAITING,
             local_state_->GetInitializationStatus());
 
+  if (!user_data_dir.empty())
+    beacon_file_path_ = user_data_dir.Append(variations::kVariationsFilename);
+
+  std::unique_ptr<base::Value> beacon_file_contents =
+      MaybeGetFileContents(beacon_file_path_);
+  did_previous_session_exit_cleanly_ =
+      DidPreviousSessionExitCleanly(beacon_file_contents.get(), local_state_);
+
 #if defined(OS_WIN) || defined(OS_IOS)
   // An enumeration of all possible permutations of the the beacon state in the
   // registry (Windows) or NSUserDefaults (iOS) and in Local State.
@@ -142,12 +244,16 @@
   base::UmaHistogramEnumeration("UMA.CleanExitBeaconConsistency2", consistency);
 
 #if defined(OS_IOS)
+  // For the time being, this is a no-op to avoid interference with the Extended
+  // Variations Safe Mode experiment; i.e., ShouldUseUserDefaultsBeacon() always
+  // returns false.
   if (ShouldUseUserDefaultsBeacon())
     did_previous_session_exit_cleanly_ = backup_beacon_was_last_shutdown_clean;
 #endif
 #endif  // defined(OS_WIN) || defined(OS_IOS)
 
-  MaybeIncrementCrashStreak(did_previous_session_exit_cleanly_, local_state_);
+  MaybeIncrementCrashStreak(did_previous_session_exit_cleanly_,
+                            beacon_file_contents.get(), local_state_);
 }
 
 CleanExitBeacon::~CleanExitBeacon() = default;
@@ -162,16 +268,30 @@
   if (update_beacon)
     local_state_->SetBoolean(prefs::kStabilityExitedCleanly, exited_cleanly);
 
+  const std::string group_name =
+      base::FieldTrialList::FindFullName(kExtendedSafeModeTrial);
+
   if (write_synchronously) {
-    {
-      // Time the write for two experiment groups: the group which only writes
-      // prefs and the group which updates and writes prefs.
+    if (group_name == kWriteSynchronouslyViaPrefServiceGroup ||
+        group_name == kSignalAndWriteSynchronouslyViaPrefServiceGroup) {
       SCOPED_UMA_HISTOGRAM_TIMER_MICROS(
           "Variations.ExtendedSafeMode.WritePrefsTime");
       local_state_->CommitPendingWriteSynchronously();
+    } else if (group_name == kSignalAndWriteViaFileUtilGroup) {
+      SCOPED_UMA_HISTOGRAM_TIMER_MICROS(
+          "Variations.ExtendedSafeMode.WritePrefsTime");
+      WriteVariationsSafeModeFile(beacon_file_path_, exited_cleanly,
+                                  local_state_);
     }
   } else {
     local_state_->CommitPendingWrite();
+    if (group_name == kSignalAndWriteViaFileUtilGroup) {
+      // Clients in this group also write to the Variations Safe Mode file. This
+      // is because the file will be used in the next session, and thus, should
+      // be updated whenever kStabilityExitedCleanly is.
+      WriteVariationsSafeModeFile(beacon_file_path_, exited_cleanly,
+                                  local_state_);
+    }
   }
 
 #if defined(OS_WIN)
@@ -202,7 +322,7 @@
   // SafeSeedManager::RegisterPrefs() because the CleanExitBeacon is
   // responsible for incrementing this value. (See the comments in
   // MaybeIncrementCrashStreak() for more details.)
-  registry->RegisterIntegerPref(variations::prefs::kVariationsCrashStreak, 0);
+  registry->RegisterIntegerPref(kVariationsCrashStreak, 0);
 }
 
 // static
diff --git a/components/metrics/clean_exit_beacon.h b/components/metrics/clean_exit_beacon.h
index 7973e9f..7c5ceb4 100644
--- a/components/metrics/clean_exit_beacon.h
+++ b/components/metrics/clean_exit_beacon.h
@@ -7,6 +7,7 @@
 
 #include <string>
 
+#include "base/files/file_path.h"
 #include "base/macros.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
@@ -27,7 +28,11 @@
   // that the pref's value corresponds to the registry's. |backup_registry_key|
   // is ignored on other platforms, but iOS has a similar verification
   // mechanism embedded inside CleanExitBeacon.
+  //
+  // |user_data_dir| is the path to the client's user data directory. If empty,
+  // a separate file will not be used for Variations Safe Mode prefs.
   CleanExitBeacon(const std::wstring& backup_registry_key,
+                  const base::FilePath& user_data_dir,
                   PrefService* local_state);
 
   ~CleanExitBeacon();
@@ -113,6 +118,10 @@
   const base::Time initial_browser_last_live_timestamp_;
   const std::wstring backup_registry_key_;
 
+  // Where the clean exit beacon and the variations crash streak may be stored
+  // for some clients in the Extended Variations Safe Mode experiment.
+  base::FilePath beacon_file_path_;
+
   DISALLOW_COPY_AND_ASSIGN(CleanExitBeacon);
 };
 
diff --git a/components/metrics/clean_exit_beacon_unittest.cc b/components/metrics/clean_exit_beacon_unittest.cc
index 6b99b0e5..78a9e508 100644
--- a/components/metrics/clean_exit_beacon_unittest.cc
+++ b/components/metrics/clean_exit_beacon_unittest.cc
@@ -4,14 +4,29 @@
 
 #include "components/metrics/clean_exit_beacon.h"
 
+#include <memory>
 #include <string>
 
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/metrics/field_trial.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
 #include "base/test/metrics/histogram_tester.h"
+#include "base/test/mock_entropy_provider.h"
+#include "base/test/scoped_field_trial_list_resetter.h"
+#include "base/test/task_environment.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
 #include "components/metrics/metrics_pref_names.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service_factory.h"
 #include "components/prefs/testing_pref_service.h"
+#include "components/prefs/testing_pref_store.h"
 #include "components/variations/pref_names.h"
+#include "components/variations/service/variations_safe_mode_constants.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace metrics {
@@ -21,76 +36,277 @@
 
 }  // namespace
 
-TEST(CleanExitBeaconTest, CrashStreakMetricWithDefaultPrefs) {
-  TestingPrefServiceSimple pref_service;
-  CleanExitBeacon::RegisterPrefs(pref_service.registry());
-  base::HistogramTester histogram_tester;
-#if defined(OS_IOS)
-  // On iOS using TestingPrefServiceSimple won't clear NSUserDefaults, so
-  // forcefully clear it here.
-  CleanExitBeacon::ResetStabilityExitedCleanlyForTesting(&pref_service);
-#endif
+class FakeTestingPrefStore : public TestingPrefStore {
+ public:
+  void CommitPendingWriteSynchronously() override {
+    was_commit_pending_write_synchronously_called_ = true;
+  }
 
-  CleanExitBeacon clean_exit_beacon(kDummyWindowsRegistryKey, &pref_service);
-  histogram_tester.ExpectUniqueSample("Variations.SafeMode.Streak.Crashes", 0,
-                                      1);
+  bool was_commit_pending_write_synchronously_called() {
+    return was_commit_pending_write_synchronously_called_;
+  }
+
+ protected:
+  ~FakeTestingPrefStore() override = default;
+
+ private:
+  bool was_commit_pending_write_synchronously_called_ = false;
+};
+
+class CleanExitBeaconTest : public ::testing::Test {
+ public:
+  void SetUp() override {
+    metrics::CleanExitBeacon::RegisterPrefs(prefs_.registry());
+    ASSERT_TRUE(user_data_dir_.CreateUniqueTempDir());
+  }
+
+ protected:
+  base::HistogramTester histogram_tester_;
+  TestingPrefServiceSimple prefs_;
+  base::ScopedTempDir user_data_dir_;
+
+ private:
+  base::test::TaskEnvironment task_environment_;
+};
+
+struct BeaconTestParams {
+  const std::string test_name;
+  bool user_data_dir_exists;
+  bool variations_file_exists;
+  const std::string beacon_file_contents;
+};
+
+class CleanExitBeaconParameterizedTest
+    : public CleanExitBeaconTest,
+      public testing::WithParamInterface<BeaconTestParams> {};
+
+// Verify that the crash streak metric is 0 when default pref values are used.
+TEST_F(CleanExitBeaconTest, CrashStreakMetricWithDefaultPrefs) {
+  CleanExitBeacon::ResetStabilityExitedCleanlyForTesting(&prefs_);
+  CleanExitBeacon clean_exit_beacon(kDummyWindowsRegistryKey, base::FilePath(),
+                                    &prefs_);
+  histogram_tester_.ExpectUniqueSample("Variations.SafeMode.Streak.Crashes", 0,
+                                       1);
 }
 
-TEST(CleanExitBeaconTest, CrashStreakMetricWithNoCrashes) {
-  TestingPrefServiceSimple pref_service;
-  CleanExitBeacon::RegisterPrefs(pref_service.registry());
+// Verify that the crash streak metric is 0 when prefs are explicitly set to
+// their defaults.
+TEST_F(CleanExitBeaconTest, CrashStreakMetricWithNoCrashes) {
   // The default value for kStabilityExitedCleanly is true, but defaults can
   // change, so we explicitly set it to true here. Similarly, we explicitly set
   // kVariationsCrashStreak to 0.
-  CleanExitBeacon::SetStabilityExitedCleanlyForTesting(&pref_service, true);
-  pref_service.SetInteger(variations::prefs::kVariationsCrashStreak, 0);
-  base::HistogramTester histogram_tester;
-
-  CleanExitBeacon clean_exit_beacon(kDummyWindowsRegistryKey, &pref_service);
-  histogram_tester.ExpectUniqueSample("Variations.SafeMode.Streak.Crashes", 0,
-                                      1);
+  CleanExitBeacon::SetStabilityExitedCleanlyForTesting(&prefs_, true);
+  prefs_.SetInteger(variations::prefs::kVariationsCrashStreak, 0);
+  CleanExitBeacon clean_exit_beacon(kDummyWindowsRegistryKey, base::FilePath(),
+                                    &prefs_);
+  histogram_tester_.ExpectUniqueSample("Variations.SafeMode.Streak.Crashes", 0,
+                                       1);
 }
 
-TEST(CleanExitBeaconTest, CrashStreakMetricWithSomeCrashes) {
-  TestingPrefServiceSimple pref_service;
-  CleanExitBeacon::RegisterPrefs(pref_service.registry());
+// Verify that the crash streak metric is correctly recorded when there is a
+// non-zero crash streak.
+TEST_F(CleanExitBeaconTest, CrashStreakMetricWithSomeCrashes) {
   // The default value for kStabilityExitedCleanly is true, but defaults can
   // change, so we explicitly set it to true here.
-  CleanExitBeacon::SetStabilityExitedCleanlyForTesting(&pref_service, true);
-  pref_service.SetInteger(variations::prefs::kVariationsCrashStreak, 1);
-  base::HistogramTester histogram_tester;
-
-  CleanExitBeacon clean_exit_beacon(kDummyWindowsRegistryKey, &pref_service);
-  histogram_tester.ExpectUniqueSample("Variations.SafeMode.Streak.Crashes", 1,
-                                      1);
+  CleanExitBeacon::SetStabilityExitedCleanlyForTesting(&prefs_, true);
+  prefs_.SetInteger(variations::prefs::kVariationsCrashStreak, 1);
+  CleanExitBeacon clean_exit_beacon(kDummyWindowsRegistryKey, base::FilePath(),
+                                    &prefs_);
+  histogram_tester_.ExpectUniqueSample("Variations.SafeMode.Streak.Crashes", 1,
+                                       1);
 }
 
-TEST(CleanExitBeaconTest, CrashIncrementsCrashStreak) {
-  TestingPrefServiceSimple pref_service;
-  CleanExitBeacon::RegisterPrefs(pref_service.registry());
-  CleanExitBeacon::SetStabilityExitedCleanlyForTesting(&pref_service, false);
-  pref_service.SetInteger(variations::prefs::kVariationsCrashStreak, 1);
-  base::HistogramTester histogram_tester;
-
-  CleanExitBeacon clean_exit_beacon(kDummyWindowsRegistryKey, &pref_service);
-  EXPECT_EQ(pref_service.GetInteger(variations::prefs::kVariationsCrashStreak),
-            2);
-  histogram_tester.ExpectUniqueSample("Variations.SafeMode.Streak.Crashes", 2,
-                                      1);
+// Verify that the crash streak is correctly incremented and recorded when the
+// last Chrome session did not exit cleanly.
+TEST_F(CleanExitBeaconTest, CrashIncrementsCrashStreak) {
+  CleanExitBeacon::SetStabilityExitedCleanlyForTesting(&prefs_, false);
+  prefs_.SetInteger(variations::prefs::kVariationsCrashStreak, 1);
+  CleanExitBeacon clean_exit_beacon(kDummyWindowsRegistryKey, base::FilePath(),
+                                    &prefs_);
+  EXPECT_EQ(prefs_.GetInteger(variations::prefs::kVariationsCrashStreak), 2);
+  histogram_tester_.ExpectUniqueSample("Variations.SafeMode.Streak.Crashes", 2,
+                                       1);
 }
 
-TEST(CleanExitBeaconTest,
-     CrashIncrementsCrashStreakWithDefaultCrashStreakPref) {
-  TestingPrefServiceSimple pref_service;
-  CleanExitBeacon::RegisterPrefs(pref_service.registry());
-  CleanExitBeacon::SetStabilityExitedCleanlyForTesting(&pref_service, false);
-  base::HistogramTester histogram_tester;
+// Verify that the crash streak is correctly incremented and recorded when the
+// last Chrome session did not exit cleanly and the default crash streak value
+// is used.
+TEST_F(CleanExitBeaconTest,
+       CrashIncrementsCrashStreakWithDefaultCrashStreakPref) {
+  CleanExitBeacon::SetStabilityExitedCleanlyForTesting(&prefs_, false);
+  CleanExitBeacon clean_exit_beacon(kDummyWindowsRegistryKey, base::FilePath(),
+                                    &prefs_);
+  EXPECT_EQ(prefs_.GetInteger(variations::prefs::kVariationsCrashStreak), 1);
+  histogram_tester_.ExpectUniqueSample("Variations.SafeMode.Streak.Crashes", 1,
+                                       1);
+}
 
-  CleanExitBeacon clean_exit_beacon(kDummyWindowsRegistryKey, &pref_service);
-  EXPECT_EQ(pref_service.GetInteger(variations::prefs::kVariationsCrashStreak),
-            1);
-  histogram_tester.ExpectUniqueSample("Variations.SafeMode.Streak.Crashes", 1,
-                                      1);
+INSTANTIATE_TEST_SUITE_P(
+    All,
+    CleanExitBeaconParameterizedTest,
+    ::testing::Values(
+        BeaconTestParams{.test_name = "NoUserDataDir",
+                         .user_data_dir_exists = false,
+                         .variations_file_exists = false,
+                         .beacon_file_contents = ""},
+        BeaconTestParams{.test_name = "NoVariationsFile",
+                         .user_data_dir_exists = true,
+                         .variations_file_exists = false,
+                         .beacon_file_contents = ""},
+        BeaconTestParams{.test_name = "EmptyVariationsFile",
+                         .user_data_dir_exists = true,
+                         .variations_file_exists = true,
+                         .beacon_file_contents = ""},
+        BeaconTestParams{.test_name = "NotDictionary",
+                         .user_data_dir_exists = true,
+                         .variations_file_exists = true,
+                         .beacon_file_contents = "{abc123"},
+        BeaconTestParams{.test_name = "EmptyDictionary",
+                         .user_data_dir_exists = true,
+                         .variations_file_exists = true,
+                         .beacon_file_contents = "{}"},
+        BeaconTestParams{
+            .test_name = "MissingCrashStreak",
+            .user_data_dir_exists = true,
+            .variations_file_exists = true,
+            .beacon_file_contents =
+                "{\"user_experience_metrics.stability.exited_cleanly\": true}"},
+        BeaconTestParams{
+            .test_name = "MissingBeacon",
+            .user_data_dir_exists = true,
+            .variations_file_exists = true,
+            .beacon_file_contents = "{\"variations_crash_streak\": 1}"}),
+    [](const ::testing::TestParamInfo<BeaconTestParams>& params) {
+      return params.param.test_name;
+    });
+
+// Verify that the inability to get the Variations Safe Mode file's contents for
+// a plethora of reasons (a) doesn't crash and (b) correctly records the
+// GotVariationsFileContents metric.
+TEST_P(CleanExitBeaconParameterizedTest, CtorWithUnusableVariationsFile) {
+  BeaconTestParams params = GetParam();
+
+  const base::FilePath user_data_dir_path = user_data_dir_.GetPath();
+  if (params.variations_file_exists) {
+    const base::FilePath temp_beacon_file_path =
+        user_data_dir_path.Append(variations::kVariationsFilename);
+    ASSERT_LT(0, base::WriteFile(temp_beacon_file_path,
+                                 params.beacon_file_contents.data()));
+  }
+
+  CleanExitBeacon clean_exit_beacon(
+      kDummyWindowsRegistryKey,
+      params.user_data_dir_exists ? user_data_dir_path : base::FilePath(),
+      &prefs_);
+  histogram_tester_.ExpectUniqueSample(
+      "Variations.ExtendedSafeMode.GotVariationsFileContents", false, 1);
+}
+
+// Verify that successfully reading the Variations Safe Mode file's contents
+// results in correctly (a) setting the |did_previous_session_exit_cleanly_|
+// field and (b) recording metrics when the last session exited cleanly.
+TEST_F(CleanExitBeaconTest, CtorWithVariationsFile) {
+  const base::FilePath user_data_dir_path = user_data_dir_.GetPath();
+  const base::FilePath temp_beacon_file_path =
+      user_data_dir_path.Append(variations::kVariationsFilename);
+  const int num_crashes = 2;
+  const std::string contents = base::StringPrintf(
+      "{\n"
+      "  \"user_experience_metrics.stability.exited_cleanly\": true,\n"
+      "  \"variations_crash_streak\": %s\n"
+      "}",
+      base::NumberToString(num_crashes).data());
+  ASSERT_LT(0, base::WriteFile(temp_beacon_file_path, contents.data()));
+
+  CleanExitBeacon clean_exit_beacon(kDummyWindowsRegistryKey,
+                                    user_data_dir_path, &prefs_);
+  histogram_tester_.ExpectUniqueSample(
+      "Variations.ExtendedSafeMode.GotVariationsFileContents", true, 1);
+  EXPECT_TRUE(clean_exit_beacon.exited_cleanly());
+  histogram_tester_.ExpectUniqueSample("Variations.SafeMode.Streak.Crashes",
+                                       num_crashes, 1);
+}
+
+// Verify that successfully reading the Variations Safe Mode file's contents
+// results in correctly (a) setting the |did_previous_session_exit_cleanly_|
+// field and (b) recording metrics when the last session did not exit cleanly.
+TEST_F(CleanExitBeaconTest, CtorWithCrashAndVariationsFile) {
+  const base::FilePath user_data_dir_path = user_data_dir_.GetPath();
+  const base::FilePath temp_beacon_file_path =
+      user_data_dir_path.Append(variations::kVariationsFilename);
+  const int last_session_num_crashes = 2;
+  const std::string contents = base::StringPrintf(
+      "{\n"
+      "  \"user_experience_metrics.stability.exited_cleanly\": false,\n"
+      "  \"variations_crash_streak\": %s\n"
+      "}",
+      base::NumberToString(last_session_num_crashes).data());
+  ASSERT_LT(0, base::WriteFile(temp_beacon_file_path, contents.data()));
+
+  const int updated_num_crashes = last_session_num_crashes + 1;
+  CleanExitBeacon clean_exit_beacon(kDummyWindowsRegistryKey,
+                                    user_data_dir_path, &prefs_);
+  histogram_tester_.ExpectUniqueSample(
+      "Variations.ExtendedSafeMode.GotVariationsFileContents", true, 1);
+  EXPECT_FALSE(clean_exit_beacon.exited_cleanly());
+  histogram_tester_.ExpectUniqueSample("Variations.SafeMode.Streak.Crashes",
+                                       updated_num_crashes, 1);
+}
+
+// Verify that attempting to write synchronously is a no-op for clients that do
+// not belong to specific Extended Variations Safe Mode experiment groups.
+TEST_F(CleanExitBeaconTest, WriteBeaconValue_NoopSynchronousWrite) {
+  base::test::ScopedFieldTrialListResetter resetter;
+  base::FieldTrialList field_trial_list(
+      std::make_unique<base::MockEntropyProvider>(0.1));
+  scoped_refptr<base::FieldTrial> trial(
+      base::FieldTrialList::FactoryGetFieldTrial(
+          variations::kExtendedSafeModeTrial, 100, variations::kDefaultGroup,
+          base::FieldTrial::ONE_TIME_RANDOMIZED, nullptr));
+  trial->AppendGroup(variations::kControlGroup, 100);
+  trial->SetForced();
+  ASSERT_EQ(variations::kControlGroup, base::FieldTrialList::FindFullName(
+                                           variations::kExtendedSafeModeTrial));
+
+  PrefServiceFactory factory;
+  scoped_refptr<FakeTestingPrefStore> pref_store(new FakeTestingPrefStore);
+  factory.set_user_prefs(pref_store);
+  scoped_refptr<PrefRegistrySimple> registry(new PrefRegistrySimple);
+  std::unique_ptr<PrefService> prefs(factory.Create(registry.get()));
+  CleanExitBeacon::RegisterPrefs(registry.get());
+  CleanExitBeacon clean_exit_beacon(kDummyWindowsRegistryKey,
+                                    user_data_dir_.GetPath(), prefs.get());
+
+  clean_exit_beacon.WriteBeaconValue(/*exited_cleanly=*/false,
+                                     /*write_synchronously=*/true);
+
+  // Verify that CommitPendingWriteSynchronously() was not called and that
+  // the WritePrefsTime metric was not emitted.
+  EXPECT_FALSE(pref_store->was_commit_pending_write_synchronously_called());
+  histogram_tester_.ExpectTotalCount(
+      "Variations.ExtendedSafeMode.WritePrefsTime", 0);
+}
+
+// Verify that calling WriteBeaconValue() with update_beacon=false does not
+// update the beacon. Also, verify that using update_beacon=true updates the
+// beacon.
+TEST_F(CleanExitBeaconTest, WriteBeaconValue_UpdateBeacon) {
+  CleanExitBeacon::SetStabilityExitedCleanlyForTesting(&prefs_, true);
+  CleanExitBeacon clean_exit_beacon(kDummyWindowsRegistryKey, base::FilePath(),
+                                    &prefs_);
+  bool exited_cleanly = false;
+  bool write_synchronously = false;
+
+  ASSERT_TRUE(prefs_.GetBoolean(prefs::kStabilityExitedCleanly));
+  clean_exit_beacon.WriteBeaconValue(exited_cleanly, write_synchronously,
+                                     /*update_beacon=*/false);
+  // Verify that the beacon is not changed when update_beacon is false.
+  EXPECT_TRUE(prefs_.GetBoolean(prefs::kStabilityExitedCleanly));
+
+  clean_exit_beacon.WriteBeaconValue(exited_cleanly, write_synchronously,
+                                     /*update_beacon=*/true);
+  // Verify that the beacon is changed when update_beacon is true.
+  EXPECT_FALSE(prefs_.GetBoolean(prefs::kStabilityExitedCleanly));
 }
 
 }  // namespace metrics
diff --git a/components/metrics/metrics_service_unittest.cc b/components/metrics/metrics_service_unittest.cc
index 656638f6..2335239 100644
--- a/components/metrics/metrics_service_unittest.cc
+++ b/components/metrics/metrics_service_unittest.cc
@@ -11,6 +11,7 @@
 #include <string>
 
 #include "base/bind.h"
+#include "base/files/file_path.h"
 #include "base/macros.h"
 #include "base/metrics/field_trial.h"
 #include "base/metrics/histogram_functions.h"
@@ -145,7 +146,7 @@
     if (!metrics_state_manager_) {
       metrics_state_manager_ = MetricsStateManager::Create(
           GetLocalState(), enabled_state_provider_.get(), std::wstring(),
-          base::BindRepeating(&StoreNoClientInfoBackup),
+          base::FilePath(), base::BindRepeating(&StoreNoClientInfoBackup),
           base::BindRepeating(&ReturnNoBackup));
     }
     return metrics_state_manager_.get();
diff --git a/components/metrics/metrics_state_manager.cc b/components/metrics/metrics_state_manager.cc
index e7cc3d6..78e1227 100644
--- a/components/metrics/metrics_state_manager.cc
+++ b/components/metrics/metrics_state_manager.cc
@@ -162,13 +162,14 @@
     PrefService* local_state,
     EnabledStateProvider* enabled_state_provider,
     const std::wstring& backup_registry_key,
+    const base::FilePath& user_data_dir,
     StoreClientInfoCallback store_client_info,
     LoadClientInfoCallback retrieve_client_info)
     : local_state_(local_state),
       enabled_state_provider_(enabled_state_provider),
       store_client_info_(std::move(store_client_info)),
       load_client_info_(std::move(retrieve_client_info)),
-      clean_exit_beacon_(backup_registry_key, local_state),
+      clean_exit_beacon_(backup_registry_key, user_data_dir, local_state),
       entropy_state_(local_state),
       entropy_source_returned_(ENTROPY_SOURCE_NONE),
       metrics_ids_were_reset_(false) {
@@ -371,13 +372,14 @@
     PrefService* local_state,
     EnabledStateProvider* enabled_state_provider,
     const std::wstring& backup_registry_key,
+    const base::FilePath& user_data_dir,
     StoreClientInfoCallback store_client_info,
     LoadClientInfoCallback retrieve_client_info) {
   std::unique_ptr<MetricsStateManager> result;
   // Note: |instance_exists_| is updated in the constructor and destructor.
   if (!instance_exists_) {
     result.reset(new MetricsStateManager(
-        local_state, enabled_state_provider, backup_registry_key,
+        local_state, enabled_state_provider, backup_registry_key, user_data_dir,
         std::move(store_client_info), std::move(retrieve_client_info)));
   }
   return result;
diff --git a/components/metrics/metrics_state_manager.h b/components/metrics/metrics_state_manager.h
index 7675bab..8cf7026 100644
--- a/components/metrics/metrics_state_manager.h
+++ b/components/metrics/metrics_state_manager.h
@@ -9,6 +9,7 @@
 #include <string>
 
 #include "base/callback.h"
+#include "base/files/file_path.h"
 #include "base/gtest_prod_util.h"
 #include "base/macros.h"
 #include "base/metrics/field_trial.h"
@@ -116,12 +117,18 @@
 
   // Creates the MetricsStateManager, enforcing that only a single instance
   // of the class exists at a time. Returns nullptr if an instance exists
-  // already. On Windows, |backup_registry_key| is used to store a backup of the
-  // clean exit beacon. It is ignored on other platforms.
+  // already.
+  //
+  // On Windows, |backup_registry_key| is used to store a backup of the clean
+  // exit beacon. It is ignored on other platforms.
+  //
+  // |user_data_dir| is the path to the client's user data directory. If empty,
+  // a separate file will not be used for Variations Safe Mode prefs.
   static std::unique_ptr<MetricsStateManager> Create(
       PrefService* local_state,
       EnabledStateProvider* enabled_state_provider,
       const std::wstring& backup_registry_key,
+      const base::FilePath& user_data_dir,
       StoreClientInfoCallback store_client_info,
       LoadClientInfoCallback load_client_info);
 
@@ -182,6 +189,7 @@
   MetricsStateManager(PrefService* local_state,
                       EnabledStateProvider* enabled_state_provider,
                       const std::wstring& backup_registry_key,
+                      const base::FilePath& user_data_dir,
                       StoreClientInfoCallback store_client_info,
                       LoadClientInfoCallback load_client_info);
 
diff --git a/components/metrics/metrics_state_manager_unittest.cc b/components/metrics/metrics_state_manager_unittest.cc
index f22b310b..f2dece1 100644
--- a/components/metrics/metrics_state_manager_unittest.cc
+++ b/components/metrics/metrics_state_manager_unittest.cc
@@ -14,6 +14,7 @@
 
 #include "base/bind.h"
 #include "base/command_line.h"
+#include "base/files/file_path.h"
 #include "base/macros.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
@@ -74,6 +75,7 @@
   std::unique_ptr<MetricsStateManager> CreateStateManager() {
     return MetricsStateManager::Create(
         &prefs_, enabled_state_provider_.get(), std::wstring(),
+        base::FilePath(),
         base::BindRepeating(&MetricsStateManagerTest::MockStoreClientInfoBackup,
                             base::Unretained(this)),
         base::BindRepeating(&MetricsStateManagerTest::LoadFakeClientInfoBackup,
@@ -183,17 +185,17 @@
 
   std::unique_ptr<MetricsStateManager> state_manager(CreateStateManager());
   state_manager->CreateDefaultEntropyProvider();
-  EXPECT_EQ(MetricsStateManager::ENTROPY_SOURCE_LOW,
-            state_manager->entropy_source_returned());
-  EXPECT_EQ("", state_manager->initial_client_id_for_testing());
+  EXPECT_EQ(state_manager->entropy_source_returned(),
+            MetricsStateManager::ENTROPY_SOURCE_LOW);
+  EXPECT_EQ(state_manager->initial_client_id_for_testing(), "");
 }
 
 TEST_F(MetricsStateManagerTest, EntropySourceUsed_High) {
   EnableMetricsReporting();
   std::unique_ptr<MetricsStateManager> state_manager(CreateStateManager());
   state_manager->CreateDefaultEntropyProvider();
-  EXPECT_EQ(MetricsStateManager::ENTROPY_SOURCE_HIGH,
-            state_manager->entropy_source_returned());
+  EXPECT_EQ(state_manager->entropy_source_returned(),
+            MetricsStateManager::ENTROPY_SOURCE_HIGH);
   EXPECT_EQ(state_manager->initial_client_id_for_testing(),
             state_manager->client_id());
 }
@@ -216,7 +218,7 @@
   {
     std::unique_ptr<MetricsStateManager> state_manager(CreateStateManager());
     state_manager->ForceClientIdCreation();
-    EXPECT_EQ(kInitialClientId, state_manager->client_id());
+    EXPECT_EQ(state_manager->client_id(), kInitialClientId);
     EXPECT_FALSE(state_manager->metrics_ids_were_reset_);
     EXPECT_THAT(prefs_, HaveNoClonedInstallInfo());
   }
@@ -228,10 +230,10 @@
   {
     std::unique_ptr<MetricsStateManager> state_manager(CreateStateManager());
     state_manager->ForceClientIdCreation();
-    EXPECT_NE(kInitialClientId, state_manager->client_id());
+    EXPECT_NE(state_manager->client_id(), kInitialClientId);
     EXPECT_TRUE(state_manager->metrics_ids_were_reset_);
-    EXPECT_EQ(kInitialClientId, state_manager->previous_client_id_);
-    EXPECT_EQ(0, client_info_load_count_);
+    EXPECT_EQ(state_manager->previous_client_id_, kInitialClientId);
+    EXPECT_EQ(client_info_load_count_, 0);
 
     state_manager->GetLowEntropySource();
 
@@ -243,7 +245,7 @@
               prefs_.GetInt64(prefs::kLastClonedResetTimestamp));
   }
 
-  EXPECT_NE(kInitialClientId, prefs_.GetString(prefs::kMetricsClientID));
+  EXPECT_NE(prefs_.GetString(prefs::kMetricsClientID), kInitialClientId);
 }
 
 TEST_F(MetricsStateManagerTest, LogHasSessionShutdownCleanly) {
@@ -271,24 +273,24 @@
 
     // client_id shouldn't be auto-generated if metrics reporting is not
     // enabled.
-    EXPECT_EQ(std::string(), state_manager->client_id());
-    EXPECT_EQ(0, prefs_.GetInt64(prefs::kMetricsReportingEnabledTimestamp));
+    EXPECT_EQ(state_manager->client_id(), std::string());
+    EXPECT_EQ(prefs_.GetInt64(prefs::kMetricsReportingEnabledTimestamp), 0);
 
     // Confirm that the initial ForceClientIdCreation call creates the client id
     // and backs it up via MockStoreClientInfoBackup.
     EXPECT_FALSE(stored_client_info_backup_);
     EnableMetricsReporting();
     state_manager->ForceClientIdCreation();
-    EXPECT_NE(std::string(), state_manager->client_id());
+    EXPECT_NE(state_manager->client_id(), std::string());
     EXPECT_GE(prefs_.GetInt64(prefs::kMetricsReportingEnabledTimestamp),
               test_begin_time_);
 
     ASSERT_TRUE(stored_client_info_backup_);
-    EXPECT_EQ(1, client_info_load_count_);
+    EXPECT_EQ(client_info_load_count_, 1);
     EXPECT_EQ(state_manager->client_id(),
               stored_client_info_backup_->client_id);
-    EXPECT_EQ(kFakeInstallationDate,
-              stored_client_info_backup_->installation_date);
+    EXPECT_EQ(stored_client_info_backup_->installation_date,
+              kFakeInstallationDate);
     EXPECT_EQ(prefs_.GetInt64(prefs::kMetricsReportingEnabledTimestamp),
               stored_client_info_backup_->reporting_enabled_date);
   }
@@ -303,8 +305,8 @@
   std::unique_ptr<MetricsStateManager> state_manager(CreateStateManager());
 
   ASSERT_TRUE(stored_client_info_backup_);
-  EXPECT_NE(0, stored_client_info_backup_->installation_date);
-  EXPECT_EQ(1, client_info_load_count_);
+  EXPECT_NE(stored_client_info_backup_->installation_date, 0);
+  EXPECT_EQ(client_info_load_count_, 1);
 }
 
 #if !defined(OS_WIN)
@@ -319,8 +321,8 @@
   int low_entropy_source = state_manager->GetLowEntropySource();
   // The default entropy provider should be the high entropy one.
   state_manager->CreateDefaultEntropyProvider();
-  EXPECT_EQ(MetricsStateManager::ENTROPY_SOURCE_HIGH,
-            state_manager->entropy_source_returned());
+  EXPECT_EQ(state_manager->entropy_source_returned(),
+            MetricsStateManager::ENTROPY_SOURCE_HIGH);
 
   // Forcing client id creation should promote the provisional client id to
   // become the real client id and keep the low entropy source.
@@ -328,10 +330,10 @@
   state_manager->ForceClientIdCreation();
   std::string client_id = state_manager->client_id();
   EXPECT_EQ(provisional_client_id, client_id);
-  EXPECT_EQ(client_id, prefs_.GetString(prefs::kMetricsClientID));
+  EXPECT_EQ(prefs_.GetString(prefs::kMetricsClientID), client_id);
   EXPECT_TRUE(state_manager->provisional_client_id_.empty());
-  EXPECT_EQ(low_entropy_source, state_manager->GetLowEntropySource());
-  EXPECT_EQ(1, client_info_load_count_);
+  EXPECT_EQ(state_manager->GetLowEntropySource(), low_entropy_source);
+  EXPECT_EQ(client_info_load_count_, 1);
 }
 
 TEST_F(MetricsStateManagerTest, ProvisionalClientId_NotPersisted) {
@@ -350,8 +352,8 @@
     low_entropy_source = state_manager->GetLowEntropySource();
     // The default entropy provider should be the high entropy one.
     state_manager->CreateDefaultEntropyProvider();
-    EXPECT_EQ(MetricsStateManager::ENTROPY_SOURCE_HIGH,
-              state_manager->entropy_source_returned());
+    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
@@ -361,13 +363,13 @@
     std::unique_ptr<MetricsStateManager> state_manager(CreateStateManager());
     EXPECT_TRUE(state_manager->provisional_client_id_.empty());
     EXPECT_TRUE(state_manager->client_id().empty());
-    EXPECT_EQ(low_entropy_source, state_manager->GetLowEntropySource());
+    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.
     state_manager->CreateDefaultEntropyProvider();
-    EXPECT_EQ(MetricsStateManager::ENTROPY_SOURCE_LOW,
-              state_manager->entropy_source_returned());
+    EXPECT_EQ(state_manager->entropy_source_returned(),
+              MetricsStateManager::ENTROPY_SOURCE_LOW);
   }
 }
 #endif  // !defined(OS_WIN)
@@ -388,7 +390,7 @@
 
     // client_id should be auto-obtained from the constructor when metrics
     // reporting is enabled.
-    EXPECT_EQ(client_info.client_id, state_manager->client_id());
+    EXPECT_EQ(state_manager->client_id(), client_info.client_id);
 
     // The backup should not be modified.
     ASSERT_FALSE(stored_client_info_backup_);
@@ -397,8 +399,8 @@
     // shouldn't affect the existing client id.
     state_manager->ForceClientIdCreation();
     EXPECT_FALSE(stored_client_info_backup_);
-    EXPECT_EQ(client_info.client_id, state_manager->client_id());
-    EXPECT_EQ(0, client_info_load_count_);
+    EXPECT_EQ(state_manager->client_id(), client_info.client_id);
+    EXPECT_EQ(client_info_load_count_, 0);
   }
 }
 
@@ -422,11 +424,11 @@
     EXPECT_FALSE(stored_client_info_backup_);
 
     std::unique_ptr<MetricsStateManager> state_manager(CreateStateManager());
-    EXPECT_EQ(client_info.client_id, state_manager->client_id());
+    EXPECT_EQ(state_manager->client_id(), client_info.client_id);
 
     // The backup should not be modified.
     ASSERT_FALSE(stored_client_info_backup_);
-    EXPECT_EQ(0, client_info_load_count_);
+    EXPECT_EQ(client_info_load_count_, 0);
   }
 }
 
@@ -454,14 +456,14 @@
     EXPECT_FALSE(stored_client_info_backup_);
 
     std::unique_ptr<MetricsStateManager> state_manager(CreateStateManager());
-    EXPECT_EQ(client_info2.client_id, state_manager->client_id());
-    EXPECT_EQ(client_info2.installation_date,
-              prefs_.GetInt64(prefs::kInstallDate));
-    EXPECT_EQ(client_info2.reporting_enabled_date,
-              prefs_.GetInt64(prefs::kMetricsReportingEnabledTimestamp));
+    EXPECT_EQ(state_manager->client_id(), client_info2.client_id);
+    EXPECT_EQ(prefs_.GetInt64(prefs::kInstallDate),
+              client_info2.installation_date);
+    EXPECT_EQ(prefs_.GetInt64(prefs::kMetricsReportingEnabledTimestamp),
+              client_info2.reporting_enabled_date);
 
     EXPECT_TRUE(stored_client_info_backup_);
-    EXPECT_EQ(1, client_info_load_count_);
+    EXPECT_EQ(client_info_load_count_, 1);
   }
 }
 
@@ -484,16 +486,16 @@
     std::unique_ptr<MetricsStateManager> state_manager(CreateStateManager());
 
     // A brand new client id should have been generated.
-    EXPECT_NE(std::string(), state_manager->client_id());
-    EXPECT_NE(client_info.client_id, state_manager->client_id());
+    EXPECT_NE(state_manager->client_id(), std::string());
+    EXPECT_NE(state_manager->client_id(), client_info.client_id);
     EXPECT_TRUE(state_manager->metrics_ids_were_reset_);
-    EXPECT_EQ(client_info.client_id, state_manager->previous_client_id_);
+    EXPECT_EQ(state_manager->previous_client_id_, client_info.client_id);
     EXPECT_TRUE(stored_client_info_backup_);
-    EXPECT_EQ(0, client_info_load_count_);
+    EXPECT_EQ(client_info_load_count_, 0);
 
     // The installation date should not have been affected.
-    EXPECT_EQ(client_info.installation_date,
-              prefs_.GetInt64(prefs::kInstallDate));
+    EXPECT_EQ(prefs_.GetInt64(prefs::kInstallDate),
+              client_info.installation_date);
 
     // The metrics-reporting-enabled date will be reset to Now().
     EXPECT_GE(prefs_.GetInt64(prefs::kMetricsReportingEnabledTimestamp),
@@ -588,10 +590,10 @@
   std::unique_ptr<MetricsStateManager> state_manager(CreateStateManager());
   // Verify that MetricsStateManager has the a new client_id after reset and has
   // the right previous_client_id (equals to the client_id before being reset).
-  EXPECT_NE(client_info.client_id, state_manager->client_id());
+  EXPECT_NE(state_manager->client_id(), client_info.client_id);
   EXPECT_TRUE(state_manager->metrics_ids_were_reset_);
-  EXPECT_EQ(client_info.client_id, state_manager->previous_client_id_);
-  EXPECT_EQ(0, client_info_load_count_);
+  EXPECT_EQ(state_manager->previous_client_id_, client_info.client_id);
+  EXPECT_EQ(client_info_load_count_, 0);
 
   uint64_t hashed_previous_client_id =
       MetricsLog::Hash(state_manager->previous_client_id_);
@@ -659,12 +661,12 @@
 
   {
     std::unique_ptr<MetricsStateManager> state_manager(CreateStateManager());
-    EXPECT_NE(client_info.client_id, state_manager->client_id());
+    EXPECT_NE(state_manager->client_id(), client_info.client_id);
     EXPECT_TRUE(state_manager->metrics_ids_were_reset_);
     // Verify that MetricsStateManager has the right previous_client_id (the ID
     // that was there before being reset).
-    EXPECT_EQ(client_info.client_id, state_manager->previous_client_id_);
-    EXPECT_EQ(0, client_info_load_count_);
+    EXPECT_EQ(state_manager->previous_client_id_, client_info.client_id);
+    EXPECT_EQ(client_info_load_count_, 0);
 
     std::unique_ptr<MetricsProvider> provider = state_manager->GetProvider();
     SystemProfileProto system_profile;
diff --git a/components/omnibox/browser/keyword_provider.cc b/components/omnibox/browser/keyword_provider.cc
index ef0e719a1..be18c22 100644
--- a/components/omnibox/browser/keyword_provider.cc
+++ b/components/omnibox/browser/keyword_provider.cc
@@ -191,20 +191,32 @@
   }
 
   if (keyword.empty())
-    return keyword;
+    return u"";
 
   // Don't provide a keyword if it doesn't support replacement.
   const TemplateURL* const template_url =
       url_service->GetTemplateURLForKeyword(keyword);
   if (!template_url ||
-      !template_url->SupportsReplacement(url_service->search_terms_data()))
+      !template_url->SupportsReplacement(url_service->search_terms_data())) {
     return std::u16string();
+  }
 
   // Don't provide a keyword for inactive/disabled extension keywords.
   if ((template_url->type() == TemplateURL::OMNIBOX_API_EXTENSION) &&
       extensions_delegate_ &&
-      !extensions_delegate_->IsEnabledExtension(template_url->GetExtensionId()))
+      !extensions_delegate_->IsEnabledExtension(
+          template_url->GetExtensionId())) {
     return std::u16string();
+  }
+
+  // Don't provide a keyword for inactive search engines (if the active search
+  // engine flag is enabled). Prepopulated engines should always work regardless
+  // of is_active.
+  if (OmniboxFieldTrial::IsActiveSearchEnginesEnabled() &&
+      template_url->prepopulate_id() == 0 &&
+      template_url->is_active() != TemplateURLData::ActiveStatus::kTrue) {
+    return std::u16string();
+  }
 
   return keyword;
 }
diff --git a/components/optimization_guide/content/browser/BUILD.gn b/components/optimization_guide/content/browser/BUILD.gn
index a7e261a..bc271ed 100644
--- a/components/optimization_guide/content/browser/BUILD.gn
+++ b/components/optimization_guide/content/browser/BUILD.gn
@@ -12,8 +12,8 @@
     "optimization_guide_decider.h",
     "page_content_annotations_service.cc",
     "page_content_annotations_service.h",
-    "page_content_annotations_web_contents_helper.cc",
-    "page_content_annotations_web_contents_helper.h",
+    "page_content_annotations_web_contents_observer.cc",
+    "page_content_annotations_web_contents_observer.h",
     "page_text_dump_result.cc",
     "page_text_dump_result.h",
     "page_text_observer.cc",
@@ -28,6 +28,9 @@
 
   public_deps = [
     "//base",
+    "//components/continuous_search/browser",
+    "//components/continuous_search/common/public/mojom",
+    "//components/google/core/common",
     "//components/history/core/browser",
     "//components/keyed_service/core",
     "//components/optimization_guide:machine_learning_tflite_buildflags",
@@ -68,7 +71,7 @@
 source_set("unit_tests") {
   testonly = true
   sources = [
-    "page_content_annotations_web_contents_helper_unittest.cc",
+    "page_content_annotations_web_contents_observer_unittest.cc",
     "page_text_dump_result_unittest.cc",
     "page_text_observer_unittest.cc",
   ]
diff --git a/components/optimization_guide/content/browser/DEPS b/components/optimization_guide/content/browser/DEPS
index d173161..91070aa 100644
--- a/components/optimization_guide/content/browser/DEPS
+++ b/components/optimization_guide/content/browser/DEPS
@@ -1,4 +1,7 @@
 include_rules = [
+  "+components/continuous_search/browser",
+  "+components/continuous_search/common/public/mojom",
+  "+components/google/core",
   "+components/history/core/browser",
   "+components/keyed_service/core",
   "+components/optimization_guide/core",
diff --git a/components/optimization_guide/content/browser/page_content_annotations_service.cc b/components/optimization_guide/content/browser/page_content_annotations_service.cc
index 3a3eeb4..6948898 100644
--- a/components/optimization_guide/content/browser/page_content_annotations_service.cc
+++ b/components/optimization_guide/content/browser/page_content_annotations_service.cc
@@ -5,6 +5,7 @@
 #include "components/optimization_guide/content/browser/page_content_annotations_service.h"
 
 #include "base/metrics/histogram_functions.h"
+#include "base/strings/string_util.h"
 #include "components/history/core/browser/history_service.h"
 #include "components/optimization_guide/core/optimization_guide_enums.h"
 #include "components/optimization_guide/core/optimization_guide_features.h"
@@ -18,7 +19,6 @@
 
 namespace optimization_guide {
 
-#if BUILDFLAG(BUILD_WITH_TFLITE_LIB)
 namespace {
 
 void LogPageContentAnnotationsStorageStatus(
@@ -31,7 +31,6 @@
 }
 
 }  // namespace
-#endif
 
 PageContentAnnotationsService::PageContentAnnotationsService(
     OptimizationGuideModelProvider* optimization_guide_model_provider,
@@ -40,8 +39,8 @@
           features::MaxContentAnnotationRequestsCached()) {
   DCHECK(optimization_guide_model_provider);
   DCHECK(history_service);
-#if BUILDFLAG(BUILD_WITH_TFLITE_LIB)
   history_service_ = history_service;
+#if BUILDFLAG(BUILD_WITH_TFLITE_LIB)
   model_manager_ = std::make_unique<PageContentAnnotationsModelManager>(
       optimization_guide_model_provider);
 #endif
@@ -67,6 +66,15 @@
 #endif
 }
 
+void PageContentAnnotationsService::ExtractRelatedSearches(
+    const HistoryVisit& visit,
+    content::WebContents* web_contents) {
+  search_result_extractor_client_.RequestData(
+      web_contents, {continuous_search::mojom::ResultType::kRelatedSearches},
+      base::BindOnce(&PageContentAnnotationsService::OnRelatedSearchesExtracted,
+                     weak_ptr_factory_.GetWeakPtr(), visit));
+}
+
 #if BUILDFLAG(BUILD_WITH_TFLITE_LIB)
 void PageContentAnnotationsService::OnPageContentAnnotated(
     const HistoryVisit& visit,
@@ -81,19 +89,69 @@
   if (!features::ShouldWriteContentAnnotationsToHistoryService())
     return;
 
+  QueryURL(visit,
+           base::BindOnce(
+               &history::HistoryService::AddContentModelAnnotationsForVisit,
+               history_service_->AsWeakPtr(), *content_annotations));
+}
+#endif
+
+void PageContentAnnotationsService::OnRelatedSearchesExtracted(
+    const HistoryVisit& visit,
+    continuous_search::SearchResultExtractorClientStatus status,
+    continuous_search::mojom::CategoryResultsPtr results) {
+  const bool success =
+      status == continuous_search::SearchResultExtractorClientStatus::kSuccess;
+  base::UmaHistogramBoolean(
+      "OptimizationGuide.PageContentAnnotationsService."
+      "RelatedSearchesExtracted",
+      success);
+
+  if (!success) {
+    return;
+  }
+
+  std::vector<std::string> related_searches;
+  for (const auto& group : results->groups) {
+    if (group->type != continuous_search::mojom::ResultType::kRelatedSearches) {
+      continue;
+    }
+    std::transform(std::begin(group->results), std::end(group->results),
+                   std::back_inserter(related_searches),
+                   [](const continuous_search::mojom::SearchResultPtr& result) {
+                     return base::UTF16ToUTF8(
+                         base::CollapseWhitespace(result->title, true));
+                   });
+    break;
+  }
+
+  if (related_searches.empty()) {
+    return;
+  }
+
+  if (!features::ShouldWriteContentAnnotationsToHistoryService()) {
+    return;
+  }
+
+  QueryURL(visit,
+           base::BindOnce(&history::HistoryService::AddRelatedSearchesForVisit,
+                          history_service_->AsWeakPtr(), related_searches));
+}
+
+void PageContentAnnotationsService::QueryURL(
+    const HistoryVisit& visit,
+    PersistAnnotationsCallback callback) {
   history_service_->QueryURL(
       visit.url, /*want_visits=*/true,
       base::BindOnce(&PageContentAnnotationsService::OnURLQueried,
                      weak_ptr_factory_.GetWeakPtr(), visit,
-                     *content_annotations),
+                     std::move(callback)),
       &history_service_task_tracker_);
 }
-#endif
 
-#if BUILDFLAG(BUILD_WITH_TFLITE_LIB)
 void PageContentAnnotationsService::OnURLQueried(
     const HistoryVisit& visit,
-    const history::VisitContentModelAnnotations& content_annotations,
+    PersistAnnotationsCallback callback,
     history::QueryURLResult url_result) {
   if (!url_result.success) {
     LogPageContentAnnotationsStorageStatus(
@@ -106,8 +164,7 @@
     if (visit.nav_entry_timestamp != visit_for_url.visit_time)
       continue;
 
-    history_service_->AddContentModelAnnotationsForVisit(visit_for_url.visit_id,
-                                                         content_annotations);
+    std::move(callback).Run(visit_for_url.visit_id);
 
     did_store_content_annotations = true;
     break;
@@ -115,7 +172,6 @@
   LogPageContentAnnotationsStorageStatus(
       did_store_content_annotations ? kSuccess : kSpecificVisitForUrlNotFound);
 }
-#endif
 
 absl::optional<int64_t>
 PageContentAnnotationsService::GetPageTopicsModelVersion() const {
diff --git a/components/optimization_guide/content/browser/page_content_annotations_service.h b/components/optimization_guide/content/browser/page_content_annotations_service.h
index 2157100..4caaabd 100644
--- a/components/optimization_guide/content/browser/page_content_annotations_service.h
+++ b/components/optimization_guide/content/browser/page_content_annotations_service.h
@@ -7,12 +7,16 @@
 
 #include <string>
 
+#include "base/callback_forward.h"
 #include "base/containers/mru_cache.h"
 #include "base/hash/hash.h"
 #include "base/memory/weak_ptr.h"
 #include "base/strings/strcat.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/task/cancelable_task_tracker.h"
+#include "components/continuous_search/browser/search_result_extractor_client.h"
+#include "components/continuous_search/browser/search_result_extractor_client_status.h"
+#include "components/continuous_search/common/public/mojom/continuous_search.mojom.h"
 #include "components/history/core/browser/history_types.h"
 #include "components/history/core/browser/url_row.h"
 #include "components/keyed_service/core/keyed_service.h"
@@ -69,6 +73,15 @@
   // Virtualized for testing.
   virtual void Annotate(const HistoryVisit& visit, const std::string& text);
 
+  // Requests |search_result_extractor_client_| to extract related searches from
+  // the Google SRP DOM associated with |web_contents|.
+  //
+  // Once finished, it will store the related searches in History Service.
+  //
+  // Virtualized for testing.
+  virtual void ExtractRelatedSearches(const HistoryVisit& visit,
+                                      content::WebContents* web_contents);
+
   // Returns the version of the page topics model that is currently being used
   // to annotate page content. Will return |absl::nullopt| if no model is being
   // used to annotate page topics for received page content.
@@ -82,22 +95,36 @@
       const absl::optional<history::VisitContentModelAnnotations>&
           content_annotations);
 
-  // Callback invoked when |history_service| has returned results for the visits
-  // to a URL.
-  void OnURLQueried(
+  std::unique_ptr<PageContentAnnotationsModelManager> model_manager_;
+#endif
+
+  // Callback invoked when related searches have been extracted for |visit|.
+  void OnRelatedSearchesExtracted(
       const HistoryVisit& visit,
-      const history::VisitContentModelAnnotations& content_annotations,
-      history::QueryURLResult url_result);
+      continuous_search::SearchResultExtractorClientStatus status,
+      continuous_search::mojom::CategoryResultsPtr results);
+
+  using PersistAnnotationsCallback = base::OnceCallback<void(history::VisitID)>;
+  // Queries |history_service| for all the visits to the visited URL of |visit|.
+  // |callback| will be invoked to write the bound content annotations to
+  // |history_service| once the visits to the given URL have returned.
+  void QueryURL(const HistoryVisit& visit, PersistAnnotationsCallback callback);
+  // Callback invoked when |history_service| has returned results for the visits
+  // to a URL. In turn invokes |callback| to write the bound content annotations
+  // to |history_service|.
+  void OnURLQueried(const HistoryVisit& visit,
+                    PersistAnnotationsCallback callback,
+                    history::QueryURLResult url_result);
 
   // The history service to write content annotations to. Not owned. Guaranteed
   // to outlive |this|.
   history::HistoryService* history_service_;
   // The task tracker to keep track of tasks to query |history_service|.
   base::CancelableTaskTracker history_service_task_tracker_;
-
-  std::unique_ptr<PageContentAnnotationsModelManager> model_manager_;
-#endif
-
+  // The client of continuous_search::mojom::SearchResultExtractor interface
+  // used for extracting data from the main frame of Google SRP |web_contents|.
+  continuous_search::SearchResultExtractorClient
+      search_result_extractor_client_;
   // A MRU Cache keeping track of the visits that have been requested for
   // annotation. If the requested visit is in this cache, the models will not be
   // requested for another annotation on the same visit.
diff --git a/components/optimization_guide/content/browser/page_content_annotations_web_contents_helper.cc b/components/optimization_guide/content/browser/page_content_annotations_web_contents_observer.cc
similarity index 66%
rename from components/optimization_guide/content/browser/page_content_annotations_web_contents_helper.cc
rename to components/optimization_guide/content/browser/page_content_annotations_web_contents_observer.cc
index 262b795..e7b851f 100644
--- a/components/optimization_guide/content/browser/page_content_annotations_web_contents_helper.cc
+++ b/components/optimization_guide/content/browser/page_content_annotations_web_contents_observer.cc
@@ -2,20 +2,21 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/optimization_guide/content/browser/page_content_annotations_web_contents_helper.h"
+#include "components/optimization_guide/content/browser/page_content_annotations_web_contents_observer.h"
 
 #include "base/bind.h"
+#include "components/google/core/common/google_util.h"
 #include "components/optimization_guide/content/browser/page_content_annotations_service.h"
 #include "components/optimization_guide/core/optimization_guide_features.h"
 #include "content/public/browser/navigation_handle.h"
 
 namespace optimization_guide {
 
-PageContentAnnotationsWebContentsHelper::
-    PageContentAnnotationsWebContentsHelper(
+PageContentAnnotationsWebContentsObserver::
+    PageContentAnnotationsWebContentsObserver(
         content::WebContents* web_contents,
         PageContentAnnotationsService* page_content_annotations_service)
-    : web_contents_(web_contents),
+    : content::WebContentsObserver(web_contents),
       page_content_annotations_service_(page_content_annotations_service),
       max_size_for_text_dump_(features::MaxSizeForPageContentTextDump()) {
   DCHECK(page_content_annotations_service_);
@@ -26,17 +27,42 @@
   observer->AddConsumer(this);
 }
 
-PageContentAnnotationsWebContentsHelper::
-    ~PageContentAnnotationsWebContentsHelper() {
-  // Only detach ourselves if PageTextObserver is still alive for
-  // |web_contents_|.
-  PageTextObserver* observer = PageTextObserver::FromWebContents(web_contents_);
+PageContentAnnotationsWebContentsObserver::
+    ~PageContentAnnotationsWebContentsObserver() {
+  // Only detach ourselves if |web_contents()| as well as PageTextObserver for
+  // |web_contents()| are still alive.
+  if (!web_contents())
+    return;
+
+  PageTextObserver* observer =
+      PageTextObserver::FromWebContents(web_contents());
   if (observer)
     observer->RemoveConsumer(this);
 }
 
+void PageContentAnnotationsWebContentsObserver::DidFinishNavigation(
+    content::NavigationHandle* navigation_handle) {
+  if (!optimization_guide::features::ShouldExtractRelatedSearches()) {
+    return;
+  }
+
+  if (!google_util::IsGoogleSearchUrl(navigation_handle->GetURL())) {
+    return;
+  }
+
+  if (!navigation_handle->IsInPrimaryMainFrame() ||
+      !navigation_handle->HasCommitted()) {
+    return;
+  }
+
+  page_content_annotations_service_->ExtractRelatedSearches(
+      optimization_guide::PageContentAnnotationsService::
+          CreateHistoryVisitFromWebContents(web_contents()),
+      web_contents());
+}
+
 std::unique_ptr<PageTextObserver::ConsumerTextDumpRequest>
-PageContentAnnotationsWebContentsHelper::MaybeRequestFrameTextDump(
+PageContentAnnotationsWebContentsObserver::MaybeRequestFrameTextDump(
     content::NavigationHandle* navigation_handle) {
   DCHECK(navigation_handle->HasCommitted());
   // TODO(https://crbug.com/1218946): With MPArch there may be multiple main
@@ -55,14 +81,14 @@
   request->events = {mojom::TextDumpEvent::kFirstLayout};
   request->dump_amp_subframes = true;
   request->callback = base::BindOnce(
-      &PageContentAnnotationsWebContentsHelper::OnTextDumpReceived,
+      &PageContentAnnotationsWebContentsObserver::OnTextDumpReceived,
       weak_ptr_factory_.GetWeakPtr(),
       PageContentAnnotationsService::CreateHistoryVisitFromWebContents(
           navigation_handle->GetWebContents()));
   return request;
 }
 
-void PageContentAnnotationsWebContentsHelper::OnTextDumpReceived(
+void PageContentAnnotationsWebContentsObserver::OnTextDumpReceived(
     const HistoryVisit& visit,
     const PageTextDumpResult& result) {
   if (result.empty()) {
@@ -80,6 +106,6 @@
       visit, *result.GetMainFrameTextContent());
 }
 
-WEB_CONTENTS_USER_DATA_KEY_IMPL(PageContentAnnotationsWebContentsHelper)
+WEB_CONTENTS_USER_DATA_KEY_IMPL(PageContentAnnotationsWebContentsObserver)
 
 }  // namespace optimization_guide
diff --git a/components/optimization_guide/content/browser/page_content_annotations_web_contents_helper.h b/components/optimization_guide/content/browser/page_content_annotations_web_contents_observer.h
similarity index 65%
rename from components/optimization_guide/content/browser/page_content_annotations_web_contents_helper.h
rename to components/optimization_guide/content/browser/page_content_annotations_web_contents_observer.h
index 7911424d..662810e 100644
--- a/components/optimization_guide/content/browser/page_content_annotations_web_contents_helper.h
+++ b/components/optimization_guide/content/browser/page_content_annotations_web_contents_observer.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef COMPONENTS_OPTIMIZATION_GUIDE_CONTENT_BROWSER_PAGE_CONTENT_ANNOTATIONS_WEB_CONTENTS_HELPER_H_
-#define COMPONENTS_OPTIMIZATION_GUIDE_CONTENT_BROWSER_PAGE_CONTENT_ANNOTATIONS_WEB_CONTENTS_HELPER_H_
+#ifndef COMPONENTS_OPTIMIZATION_GUIDE_CONTENT_BROWSER_PAGE_CONTENT_ANNOTATIONS_WEB_CONTENTS_OBSERVER_H_
+#define COMPONENTS_OPTIMIZATION_GUIDE_CONTENT_BROWSER_PAGE_CONTENT_ANNOTATIONS_WEB_CONTENTS_OBSERVER_H_
 
 #include "base/memory/weak_ptr.h"
 #include "components/optimization_guide/content/browser/page_text_dump_result.h"
@@ -21,27 +21,31 @@
 
 // This class is used to dispatch page content to the
 // PageContentAnnotationsService to be annotated.
-class PageContentAnnotationsWebContentsHelper
-    : public content::WebContentsUserData<
-          PageContentAnnotationsWebContentsHelper>,
+class PageContentAnnotationsWebContentsObserver
+    : public content::WebContentsObserver,
+      public content::WebContentsUserData<
+          PageContentAnnotationsWebContentsObserver>,
       public PageTextObserver::Consumer {
  public:
-  ~PageContentAnnotationsWebContentsHelper() override;
+  ~PageContentAnnotationsWebContentsObserver() override;
 
-  PageContentAnnotationsWebContentsHelper(
-      const PageContentAnnotationsWebContentsHelper&) = delete;
-  PageContentAnnotationsWebContentsHelper& operator=(
-      const PageContentAnnotationsWebContentsHelper&) = delete;
+  PageContentAnnotationsWebContentsObserver(
+      const PageContentAnnotationsWebContentsObserver&) = delete;
+  PageContentAnnotationsWebContentsObserver& operator=(
+      const PageContentAnnotationsWebContentsObserver&) = delete;
 
  protected:
-  PageContentAnnotationsWebContentsHelper(
+  PageContentAnnotationsWebContentsObserver(
       content::WebContents* web_contents,
       PageContentAnnotationsService* page_content_annotations_service);
 
  private:
   friend class content::WebContentsUserData<
-      PageContentAnnotationsWebContentsHelper>;
-  friend class PageContentAnnotationsWebContentsHelperTest;
+      PageContentAnnotationsWebContentsObserver>;
+  friend class PageContentAnnotationsWebContentsObserverTest;
+
+  // content::WebContentsObserver:
+  void DidFinishNavigation(content::NavigationHandle* handle) override;
 
   // PageTextObserver::Consumer:
   std::unique_ptr<PageTextObserver::ConsumerTextDumpRequest>
@@ -53,13 +57,12 @@
                           const PageTextDumpResult& result);
 
   // Not owned. Guaranteed to outlive |this|.
-  content::WebContents* web_contents_;
   PageContentAnnotationsService* page_content_annotations_service_;
 
   // The max size to request for text dump.
   const uint64_t max_size_for_text_dump_;
 
-  base::WeakPtrFactory<PageContentAnnotationsWebContentsHelper>
+  base::WeakPtrFactory<PageContentAnnotationsWebContentsObserver>
       weak_ptr_factory_{this};
 
   WEB_CONTENTS_USER_DATA_KEY_DECL();
@@ -67,4 +70,4 @@
 
 }  // namespace optimization_guide
 
-#endif  // COMPONENTS_OPTIMIZATION_GUIDE_CONTENT_BROWSER_PAGE_CONTENT_ANNOTATIONS_WEB_CONTENTS_HELPER_H_
+#endif  // COMPONENTS_OPTIMIZATION_GUIDE_CONTENT_BROWSER_PAGE_CONTENT_ANNOTATIONS_WEB_CONTENTS_OBSERVER_H_
diff --git a/components/optimization_guide/content/browser/page_content_annotations_web_contents_helper_unittest.cc b/components/optimization_guide/content/browser/page_content_annotations_web_contents_observer_unittest.cc
similarity index 60%
rename from components/optimization_guide/content/browser/page_content_annotations_web_contents_helper_unittest.cc
rename to components/optimization_guide/content/browser/page_content_annotations_web_contents_observer_unittest.cc
index 19ec776..6a909a9 100644
--- a/components/optimization_guide/content/browser/page_content_annotations_web_contents_helper_unittest.cc
+++ b/components/optimization_guide/content/browser/page_content_annotations_web_contents_observer_unittest.cc
@@ -2,9 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/optimization_guide/content/browser/page_content_annotations_web_contents_helper.h"
+#include "components/optimization_guide/content/browser/page_content_annotations_web_contents_observer.h"
 
+#include "base/command_line.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/test/scoped_feature_list.h"
+#include "components/google/core/common/google_switches.h"
 #include "components/history/core/browser/history_service.h"
 #include "components/optimization_guide/content/browser/page_content_annotations_service.h"
 #include "components/optimization_guide/content/browser/page_text_dump_result.h"
@@ -47,18 +50,37 @@
     last_annotation_request_.emplace(std::make_pair(visit, text));
   }
 
+  void ExtractRelatedSearches(const HistoryVisit& visit,
+                              content::WebContents* web_contents) override {
+    last_related_searches_extraction_request_.emplace(
+        std::make_pair(visit, web_contents));
+  }
+
   absl::optional<std::pair<HistoryVisit, std::string>> last_annotation_request()
       const {
     return last_annotation_request_;
   }
 
+  absl::optional<std::pair<HistoryVisit, content::WebContents*>>
+  last_related_searches_extraction_request() const {
+    return last_related_searches_extraction_request_;
+  }
+
  private:
   absl::optional<std::pair<HistoryVisit, std::string>> last_annotation_request_;
+  absl::optional<std::pair<HistoryVisit, content::WebContents*>>
+      last_related_searches_extraction_request_;
 };
 
-class PageContentAnnotationsWebContentsHelperTest
+class PageContentAnnotationsWebContentsObserverTest
     : public content::RenderViewHostTestHarness {
  public:
+  PageContentAnnotationsWebContentsObserverTest() {
+    scoped_feature_list_.InitAndEnableFeatureWithParameters(
+        features::kPageContentAnnotations,
+        {{"extract_related_searches", "false"}});
+  }
+
   void SetUp() override {
     content::RenderViewHostTestHarness::SetUp();
 
@@ -73,7 +95,7 @@
     web_contents()->SetUserData(TestPageTextObserver::UserDataKey(),
                                 base::WrapUnique(page_text_observer_));
 
-    PageContentAnnotationsWebContentsHelper::CreateForWebContents(
+    PageContentAnnotationsWebContentsObserver::CreateForWebContents(
         web_contents(), page_content_annotations_service_.get());
   }
 
@@ -89,8 +111,8 @@
     return page_content_annotations_service_.get();
   }
 
-  PageContentAnnotationsWebContentsHelper* helper() {
-    return PageContentAnnotationsWebContentsHelper::FromWebContents(
+  PageContentAnnotationsWebContentsObserver* helper() {
+    return PageContentAnnotationsWebContentsObserver::FromWebContents(
         web_contents());
   }
 
@@ -107,6 +129,7 @@
   }
 
  private:
+  base::test::ScopedFeatureList scoped_feature_list_;
   std::unique_ptr<TestOptimizationGuideModelProvider>
       optimization_guide_model_provider_;
   std::unique_ptr<history::HistoryService> history_service_;
@@ -115,16 +138,17 @@
   TestPageTextObserver* page_text_observer_;
 };
 
-TEST_F(PageContentAnnotationsWebContentsHelperTest, HooksIntoPageTextObserver) {
+TEST_F(PageContentAnnotationsWebContentsObserverTest,
+       HooksIntoPageTextObserver) {
   EXPECT_TRUE(page_text_observer()->add_consumer_called());
 }
 
-TEST_F(PageContentAnnotationsWebContentsHelperTest,
+TEST_F(PageContentAnnotationsWebContentsObserverTest,
        DoesNotRequestForNonHttpHttps) {
   EXPECT_EQ(RequestTextDumpForUrl(GURL("chrome://new-tab")), nullptr);
 }
 
-TEST_F(PageContentAnnotationsWebContentsHelperTest,
+TEST_F(PageContentAnnotationsWebContentsObserverTest,
        RequestsForMainFrameHttpUrlCallbackDispatchesToService) {
   // Navigate and commit so there is an entry. In actual situations, we are
   // guaranteed that MaybeRequestFrameTextDump will only be called for
@@ -159,4 +183,63 @@
   EXPECT_EQ(last_annotation_request->second, "some text");
 }
 
+TEST_F(PageContentAnnotationsWebContentsObserverTest,
+       RequestsRelatedSearchesForMainFrameSRPUrl) {
+  // Navigate to non-Google SRP and commit.
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL("http://www.foo.com/search?q=a"));
+
+  absl::optional<std::pair<HistoryVisit, content::WebContents*>> last_request =
+      service()->last_related_searches_extraction_request();
+  EXPECT_FALSE(last_request.has_value());
+
+  // Overwrite Google base URL.
+  base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
+      switches::kGoogleBaseURL, "http://www.foo.com/");
+
+  // Navigate to Google SRP and commit.
+  // No request should be sent since extracting related searches is disabled.
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL("http://www.foo.com/search?q=a"));
+  last_request = service()->last_related_searches_extraction_request();
+  EXPECT_FALSE(last_request.has_value());
+}
+
+class PageContentAnnotationsWebContentsObserverRelatedSearchesTest
+    : public PageContentAnnotationsWebContentsObserverTest {
+ public:
+  PageContentAnnotationsWebContentsObserverRelatedSearchesTest() {
+    scoped_feature_list_.InitAndEnableFeatureWithParameters(
+        features::kPageContentAnnotations,
+        {{"extract_related_searches", "true"}});
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+TEST_F(PageContentAnnotationsWebContentsObserverRelatedSearchesTest,
+       RequestsRelatedSearchesForMainFrameSRPUrl) {
+  // Navigate to non-Google SRP and commit.
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL("http://www.foo.com/search?q=a"));
+
+  absl::optional<std::pair<HistoryVisit, content::WebContents*>> last_request =
+      service()->last_related_searches_extraction_request();
+  EXPECT_FALSE(last_request.has_value());
+
+  // Overwrite Google base URL.
+  base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
+      switches::kGoogleBaseURL, "http://www.foo.com/");
+
+  // Navigate to Google SRP and commit.
+  // Expect a request to be sent since extracting related searches is enabled.
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL("http://www.foo.com/search?q=a"));
+  last_request = service()->last_related_searches_extraction_request();
+  EXPECT_TRUE(last_request.has_value());
+  EXPECT_EQ(last_request->first.url, GURL("http://www.foo.com/search?q=a"));
+  EXPECT_EQ(last_request->second, web_contents());
+}
+
 }  // namespace optimization_guide
diff --git a/components/optimization_guide/core/optimization_guide_features.cc b/components/optimization_guide/core/optimization_guide_features.cc
index f7428d7..1e4edfc 100644
--- a/components/optimization_guide/core/optimization_guide_features.cc
+++ b/components/optimization_guide/core/optimization_guide_features.cc
@@ -363,6 +363,13 @@
       kPageContentAnnotations, "max_content_annotation_requests_cached", 50);
 }
 
+const base::FeatureParam<bool> kContentAnnotationsExtractRelatedSearchesParam{
+    &kPageContentAnnotations, "extract_related_searches", false};
+
+bool ShouldExtractRelatedSearches() {
+  return kContentAnnotationsExtractRelatedSearchesParam.Get();
+}
+
 std::vector<optimization_guide::proto::OptimizationTarget>
 GetPageContentModelsToExecute() {
   if (!IsPageContentAnnotationEnabled())
diff --git a/components/optimization_guide/core/optimization_guide_features.h b/components/optimization_guide/core/optimization_guide_features.h
index 621d469..babfda4 100644
--- a/components/optimization_guide/core/optimization_guide_features.h
+++ b/components/optimization_guide/core/optimization_guide_features.h
@@ -10,6 +10,7 @@
 
 #include "base/containers/flat_set.h"
 #include "base/feature_list.h"
+#include "base/metrics/field_trial_params.h"
 #include "base/time/time.h"
 #include "components/optimization_guide/proto/hints.pb.h"
 #include "components/optimization_guide/proto/models.pb.h"
@@ -193,6 +194,10 @@
 // for annotation.
 size_t MaxContentAnnotationRequestsCached();
 
+// Returns whether or not related searches should be extracted from Google SRP
+// as part of page content annotations.
+bool ShouldExtractRelatedSearches();
+
 // Returns an ordered vector of models to execute on the page content for each
 // page load. It is guaranteed that an optimization target will only be present
 // at most once in the returned vector. However, it is not guaranteed that it
diff --git a/components/page_info/page_info.cc b/components/page_info/page_info.cc
index aeccafb..06d0484 100644
--- a/components/page_info/page_info.cc
+++ b/components/page_info/page_info.cc
@@ -57,7 +57,6 @@
 #include "components/url_formatter/elide_url.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_thread.h"
-#include "content/public/browser/web_contents.h"
 #include "content/public/common/content_features.h"
 #include "content/public/common/url_constants.h"
 #include "net/cert/cert_status_flags.h"
@@ -292,7 +291,7 @@
 PageInfo::PageInfo(std::unique_ptr<PageInfoDelegate> delegate,
                    content::WebContents* web_contents,
                    const GURL& url)
-    : content::WebContentsObserver(web_contents),
+    : web_contents_(web_contents->GetWeakPtr()),
       delegate_(std::move(delegate)),
       show_info_bar_(false),
       site_url_(url),
@@ -427,9 +426,9 @@
 
   UMA_HISTOGRAM_ENUMERATION("WebsiteSettings.Action", action, PAGE_INFO_COUNT);
 
-  if (web_contents()) {
+  if (web_contents_) {
     ukm::builders::PageInfoBubble(
-        ukm::GetSourceIdForWebContentsDocument(web_contents()))
+        ukm::GetSourceIdForWebContentsDocument(web_contents_.get()))
         .SetActionTaken(action)
         .Record(ukm::UkmRecorder::Get());
   }
@@ -487,8 +486,9 @@
     }
   }
 
+  DCHECK(web_contents_);
   permissions::PermissionUmaUtil::ScopedRevocationReporter
-      scoped_revocation_reporter(web_contents()->GetBrowserContext(), site_url_,
+      scoped_revocation_reporter(web_contents_->GetBrowserContext(), site_url_,
                                  site_url_, type,
                                  permissions::PermissionSourceUI::OIB);
 
@@ -532,7 +532,7 @@
 #if defined(OS_ANDROID)
   NOTREACHED();
 #else
-  if (show_info_bar_ && web_contents() && !web_contents()->IsBeingDestroyed()) {
+  if (show_info_bar_ && web_contents_ && !web_contents_->IsBeingDestroyed()) {
     if (delegate_->CreateInfoBarDelegate() && reload_prompt)
       *reload_prompt = true;
   }
@@ -563,7 +563,7 @@
 #if defined(OS_ANDROID)
   NOTREACHED();
 #else
-  if (!web_contents() || web_contents()->IsBeingDestroyed())
+  if (!web_contents_ || web_contents_->IsBeingDestroyed())
     return;
 
   RecordPageInfoAction(PAGE_INFO_COOKIES_DIALOG_OPENED);
@@ -575,10 +575,10 @@
 #if defined(OS_ANDROID)
   NOTREACHED();
 #else
-  if (!web_contents() || web_contents()->IsBeingDestroyed())
+  if (!web_contents_ || web_contents_->IsBeingDestroyed())
     return;
 
-  gfx::NativeWindow top_window = web_contents()->GetTopLevelNativeWindow();
+  gfx::NativeWindow top_window = web_contents_->GetTopLevelNativeWindow();
   if (certificate && top_window) {
     RecordPageInfoAction(PAGE_INFO_CERTIFICATE_DIALOG_OPENED);
     delegate_->OpenCertificateDialog(certificate);
@@ -904,8 +904,9 @@
   // SSL host errors for this host in the past, and we're not presently on a
   // Safe Browsing error (since otherwise it's confusing which warning you're
   // re-enabling).
+  DCHECK(web_contents_);
   show_ssl_decision_revoke_button_ =
-      delegate->HasAllowException(url.host(), web_contents()) &&
+      delegate->HasAllowException(url.host(), web_contents_.get()) &&
       visible_security_state.malicious_content_status ==
           security_state::MALICIOUS_CONTENT_STATUS_NONE;
 }
@@ -916,6 +917,7 @@
 
   PermissionInfo permission_info;
   HostContentSettingsMap* content_settings = GetContentSettings();
+  DCHECK(web_contents_);
   for (const ContentSettingsType type : kPermissionType) {
     permission_info.type = type;
 
@@ -967,7 +969,7 @@
     }
 
     if (ShouldShowPermission(
-            permission_info, site_url_, content_settings, web_contents(),
+            permission_info, site_url_, content_settings, web_contents_.get(),
             HasContentSettingChangedViaPageInfo(permission_info.type),
             delegate_->IsSubresourceFilterActivated(site_url_))) {
       permission_info_list.push_back(permission_info);
@@ -997,8 +999,6 @@
   CookieInfoList cookie_info_list;
 
   // Add first party cookie and site data counts.
-  // TODO(crbug.com/1058597): Remove the calls to the |delegate_| once
-  // PageSpecificContentSettings has been componentized.
   PageInfoUI::CookieInfo cookie_info;
   cookie_info.allowed = GetFirstPartyAllowedCookiesCount(site_url_);
   cookie_info.blocked = GetFirstPartyBlockedCookiesCount(site_url_);
@@ -1132,10 +1132,12 @@
 
 content_settings::PageSpecificContentSettings*
 PageInfo::GetPageSpecificContentSettings() const {
-  // TODO(https://crbug.com/1103176): PageInfo should be per page. Why is it
-  // a WebContentsObserver if it is not observing anything?
+  // TODO(https://crbug.com/1103176, https://crbug.com/1233122): PageInfo should
+  // be per page. Why is it a WebContentsObserver if it is not observing
+  // anything?
+  DCHECK(web_contents_);
   return content_settings::PageSpecificContentSettings::GetForFrame(
-      web_contents()->GetMainFrame());
+      web_contents_->GetMainFrame());
 }
 
 bool PageInfo::HasContentSettingChangedViaPageInfo(ContentSettingsType type) {
diff --git a/components/page_info/page_info.h b/components/page_info/page_info.h
index 730ca46..c6766f4 100644
--- a/components/page_info/page_info.h
+++ b/components/page_info/page_info.h
@@ -14,11 +14,7 @@
 #include "components/content_settings/core/common/content_settings_types.h"
 #include "components/safe_browsing/buildflags.h"
 #include "components/security_state/core/security_state.h"
-#include "content/public/browser/web_contents_observer.h"
-
-namespace content {
-class WebContents;
-}
+#include "content/public/browser/web_contents.h"
 
 namespace content_settings {
 class PageSpecificContentSettings;
@@ -46,7 +42,7 @@
 // information and allows users to change the permissions. |PageInfo|
 // objects must be created on the heap. They destroy themselves after the UI is
 // closed.
-class PageInfo : public content::WebContentsObserver {
+class PageInfo {
  public:
   // TODO(palmer): Figure out if it is possible to unify SiteConnectionStatus
   // and SiteIdentityStatus.
@@ -185,7 +181,7 @@
   PageInfo(std::unique_ptr<PageInfoDelegate> delegate,
            content::WebContents* web_contents,
            const GURL& url);
-  ~PageInfo() override;
+  ~PageInfo();
 
   // Checks whether this permission is currently the factory default, as set by
   // Chrome. Specifically, that the following three conditions are true:
@@ -351,6 +347,9 @@
   // information (identity, connection status, etc.).
   PageInfoUI* ui_;
 
+  // A web contents getter used to retrieve the associated WebContents object.
+  base::WeakPtr<content::WebContents> web_contents_;
+
   // The delegate allows the embedder to customize |PageInfo|'s behavior.
   std::unique_ptr<PageInfoDelegate> delegate_;
 
diff --git a/components/page_info/page_info_ui.h b/components/page_info/page_info_ui.h
index 90d0e0b..061e1c43 100644
--- a/components/page_info/page_info_ui.h
+++ b/components/page_info/page_info_ui.h
@@ -132,11 +132,11 @@
     // Textual description of the site's connection status that is displayed to
     // the user.
     std::string connection_status_description;
-    // Set when the user has explicitly bypassed an SSL error for this host and
-    // has a flag set to remember ssl decisions (explicit flag or in the
-    // experimental group).  When |show_ssl_decision_revoke_button| is true, the
-    // connection area of the page info will include an option for the user to
-    // revoke their decision to bypass the SSL error for this host.
+    // Set when the user has explicitly bypassed an SSL error for this host
+    // and/or the user has explicitly bypassed an HTTP warning (from HTTPS-First
+    // Mode) for this host. When `show_ssl_decision_revoke_button` is true, the
+    // connection area of the page info UI will include an option for the user
+    // to revoke their decision to bypass warnings for this host.
     bool show_ssl_decision_revoke_button;
     // Set when the user ignored the password reuse modal warning dialog. When
     // |show_change_password_buttons| is true, the page identity area of the
diff --git a/components/page_info_strings.grdp b/components/page_info_strings.grdp
index 00b1af62..d97cdb2 100644
--- a/components/page_info_strings.grdp
+++ b/components/page_info_strings.grdp
@@ -177,16 +177,9 @@
     <message name="IDS_PAGE_INFO_INVALID_CERTIFICATE_DESCRIPTION" desc="A short paragraph to the user that security warnings are disabled. This is the case when the user has encountered a certificate error for the current site and chosen to override it.">
       You have chosen to disable security warnings for this site.
     </message>
-    <if expr="is_android">
-      <message name="IDS_PAGE_INFO_RESET_INVALID_CERTIFICATE_DECISIONS_BUTTON" desc="Text of button in the page info that resets allow/deny decisions of invalid certificates, which will start showing security warnings for the page again.">
-        Stop using an invalid certificate
-      </message>
-    </if>
-    <if expr="not is_android">
-      <message name="IDS_PAGE_INFO_RESET_INVALID_CERTIFICATE_DECISIONS_BUTTON" desc="Text of button in the page info that resets allow/deny decisions of invalid certificates, which will start showing security warnings for the page again.">
-        Re-enable warnings
-      </message>
-    </if>
+    <message name="IDS_PAGE_INFO_RESET_INVALID_CERTIFICATE_DECISIONS_BUTTON" desc="Text of button in the page info that resets allow/deny decisions of invalid certificates, which will start showing security warnings for the page again.">
+      Re-enable warnings
+    </message>
 
     <!-- Old connection info UI. Only used on Android, but still compiled on desktop. -->
     <message name="IDS_PAGE_INFO_HELP_CENTER_LINK" desc="This is the text of the link pointing to the Help Center. This appears at the bottom of the SSL dialog and 'this' refers to the sections within the bubble.">
diff --git a/components/payments/content/secure_payment_confirmation_app.cc b/components/payments/content/secure_payment_confirmation_app.cc
index e0ef755e..da9ff5ee 100644
--- a/components/payments/content/secure_payment_confirmation_app.cc
+++ b/components/payments/content/secure_payment_confirmation_app.cc
@@ -110,9 +110,9 @@
   options->allow_credentials = std::move(credentials);
 
   options->challenge = request_->challenge;
-  options->payment = blink::mojom::PaymentOptions::New(
+  authenticator_->SetPaymentOptions(blink::mojom::PaymentOptions::New(
       spec_->GetTotal(/*selected_app=*/this)->amount.Clone(),
-      request_->instrument.Clone());
+      request_->instrument.Clone()));
 
   // We are nullifying the security check by design, and the origin that created
   // the credential isn't saved anywhere.
diff --git a/components/payments/content/secure_payment_confirmation_app_unittest.cc b/components/payments/content/secure_payment_confirmation_app_unittest.cc
index 9dda34487..5cbafa0 100644
--- a/components/payments/content/secure_payment_confirmation_app_unittest.cc
+++ b/components/payments/content/secure_payment_confirmation_app_unittest.cc
@@ -43,6 +43,7 @@
   ~MockAuthenticator() override = default;
 
   MOCK_METHOD1(SetEffectiveOrigin, void(const url::Origin&));
+  MOCK_METHOD1(SetPaymentOptions, void(blink::mojom::PaymentOptionsPtr));
   MOCK_METHOD2(
       MakeCredential,
       void(blink::mojom::PublicKeyCredentialCreationOptionsPtr options,
diff --git a/components/pdf/renderer/pdf_view_web_plugin_client.cc b/components/pdf/renderer/pdf_view_web_plugin_client.cc
index 5614983..7a283e0 100644
--- a/components/pdf/renderer/pdf_view_web_plugin_client.cc
+++ b/components/pdf/renderer/pdf_view_web_plugin_client.cc
@@ -6,6 +6,7 @@
 
 #include "base/check.h"
 #include "components/pdf/renderer/pdf_accessibility_tree.h"
+#include "content/public/common/use_zoom_for_dsf_policy.h"
 #include "content/public/renderer/render_thread.h"
 #include "printing/buildflags/buildflags.h"
 #include "third_party/blink/public/web/web_element.h"
@@ -41,4 +42,8 @@
   return std::make_unique<PdfAccessibilityTree>(render_frame_, action_handler);
 }
 
+bool PdfViewWebPluginClient::IsUseZoomForDSFEnabled() const {
+  return content::IsUseZoomForDSFEnabled();
+}
+
 }  // namespace pdf
diff --git a/components/pdf/renderer/pdf_view_web_plugin_client.h b/components/pdf/renderer/pdf_view_web_plugin_client.h
index 082713c..efa7bd28 100644
--- a/components/pdf/renderer/pdf_view_web_plugin_client.h
+++ b/components/pdf/renderer/pdf_view_web_plugin_client.h
@@ -26,6 +26,7 @@
   std::unique_ptr<chrome_pdf::PdfAccessibilityDataHandler>
   CreateAccessibilityDataHandler(
       chrome_pdf::PdfAccessibilityActionHandler* action_handler) override;
+  bool IsUseZoomForDSFEnabled() const override;
 
  private:
   content::RenderFrame* const render_frame_;
diff --git a/components/performance_manager/graph/frame_node_impl.cc b/components/performance_manager/graph/frame_node_impl.cc
index 8dc5c77..65ee88e 100644
--- a/components/performance_manager/graph/frame_node_impl.cc
+++ b/components/performance_manager/graph/frame_node_impl.cc
@@ -26,7 +26,6 @@
 FrameNodeImpl::FrameNodeImpl(ProcessNodeImpl* process_node,
                              PageNodeImpl* page_node,
                              FrameNodeImpl* parent_frame_node,
-                             int frame_tree_node_id,
                              int render_frame_id,
                              const blink::LocalFrameToken& frame_token,
                              content::BrowsingInstanceId browsing_instance_id,
@@ -34,7 +33,6 @@
     : parent_frame_node_(parent_frame_node),
       page_node_(page_node),
       process_node_(process_node),
-      frame_tree_node_id_(frame_tree_node_id),
       render_frame_id_(render_frame_id),
       frame_token_(frame_token),
       browsing_instance_id_(browsing_instance_id),
@@ -133,11 +131,6 @@
   return process_node_;
 }
 
-int FrameNodeImpl::frame_tree_node_id() const {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  return frame_tree_node_id_;
-}
-
 int FrameNodeImpl::render_frame_id() const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   return render_frame_id_;
@@ -256,27 +249,17 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   is_current_.SetAndMaybeNotify(this, is_current);
 
-#if DCHECK_IS_ON()
-  // We maintain an invariant that of all sibling nodes with the same
-  // |frame_tree_node_id|, at most one may be current.
-  if (is_current) {
-    const base::flat_set<FrameNodeImpl*>* siblings = nullptr;
-    if (parent_frame_node_) {
-      siblings = &parent_frame_node_->child_frame_nodes();
-    } else {
-      siblings = &page_node_->main_frame_nodes();
-    }
-
-    size_t current_siblings = 0;
-    for (auto* frame : *siblings) {
-      if (frame->frame_tree_node_id() == frame_tree_node_id_ &&
-          frame->is_current()) {
-        ++current_siblings;
-      }
-    }
-    DCHECK_EQ(1u, current_siblings);
-  }
-#endif
+  // TODO(crbug.com/1211368): We maintain an invariant that of all sibling
+  // frame nodes in the same FrameTreeNode, at most one may be current. We used
+  // to save the RenderFrameHost's `frame_tree_node_id` at FrameNode creation
+  // time to check this invariant, but prerendering RenderFrameHost's can be
+  // moved to a new FrameTreeNode when they're activated so the
+  // `frame_tree_node_id` can go out of date. Because of this,
+  // RenderFrameHost::GetFrameTreeNodeId() is being deprecated. (See the
+  // discussion at crbug.com/1179502 and in the comment thread at
+  // https://chromium-review.googlesource.com/c/chromium/src/+/2966195/comments/58550eac_5795f790
+  // for more details.) We need to find another way to check this invariant
+  // here.
 }
 
 void FrameNodeImpl::SetIsHoldingWebLock(bool is_holding_weblock) {
@@ -433,11 +416,6 @@
   return process_node();
 }
 
-int FrameNodeImpl::GetFrameTreeNodeId() const {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  return frame_tree_node_id();
-}
-
 const blink::LocalFrameToken& FrameNodeImpl::GetFrameToken() const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   return frame_token();
diff --git a/components/performance_manager/graph/frame_node_impl.h b/components/performance_manager/graph/frame_node_impl.h
index 1c71942..ccfd20d 100644
--- a/components/performance_manager/graph/frame_node_impl.h
+++ b/components/performance_manager/graph/frame_node_impl.h
@@ -32,28 +32,8 @@
 class ExecutionContextAccess;
 }  // namespace execution_context
 
-// Frame nodes form a tree structure, each FrameNode at most has one parent that
-// is a FrameNode. Conceptually, a frame corresponds to a
-// content::RenderFrameHost in the browser, and a content::RenderFrameImpl /
-// blink::LocalFrame in a renderer.
-//
-// Note that a frame in a frame tree can be replaced with another, with the
-// continuity of that position represented via the |frame_tree_node_id|. It is
-// possible to have multiple "sibling" nodes that share the same
-// |frame_tree_node_id|. Only one of these may contribute to the content being
-// rendered, and this node is designated the "current" node in content
-// terminology. A swap is effectively atomic but will take place in two steps
-// in the graph: the outgoing frame will first be marked as not current, and the
-// incoming frame will be marked as current. As such, the graph invariant is
-// that there will be 0 or 1 |is_current| frames with a given
-// |frame_tree_node_id|.
-//
-// This occurs when a frame is navigated and the existing frame can't be reused.
-// In that case a "provisional" frame is created to start the navigation. Once
-// the navigation completes (which may actually involve a redirect to another
-// origin meaning the frame has to be destroyed and another one created in
-// another process!) and commits, the frame will be swapped with the previously
-// active frame.
+// Frame nodes for a tree structure that is described in
+// components/performance_manager/public/graph/frame_node.h.
 class FrameNodeImpl
     : public PublicNodeImpl<FrameNodeImpl, FrameNode>,
       public TypedNodeBase<FrameNodeImpl, FrameNode, FrameNodeObserver>,
@@ -69,7 +49,6 @@
   FrameNodeImpl(ProcessNodeImpl* process_node,
                 PageNodeImpl* page_node,
                 FrameNodeImpl* parent_frame_node,
-                int frame_tree_node_id,
                 int render_frame_id,
                 const blink::LocalFrameToken& frame_token,
                 content::BrowsingInstanceId browsing_instance_id,
@@ -100,7 +79,6 @@
   FrameNodeImpl* parent_frame_node() const;
   PageNodeImpl* page_node() const;
   ProcessNodeImpl* process_node() const;
-  int frame_tree_node_id() const;
   int render_frame_id() const;
   const blink::LocalFrameToken& frame_token() const;
   content::BrowsingInstanceId browsing_instance_id() const;
@@ -182,7 +160,6 @@
   const FrameNode* GetParentFrameNode() const override;
   const PageNode* GetPageNode() const override;
   const ProcessNode* GetProcessNode() const override;
-  int GetFrameTreeNodeId() const override;
   const blink::LocalFrameToken& GetFrameToken() const override;
   content::BrowsingInstanceId GetBrowsingInstanceId() const override;
   content::SiteInstanceId GetSiteInstanceId() const override;
@@ -272,9 +249,6 @@
   FrameNodeImpl* const parent_frame_node_;
   PageNodeImpl* const page_node_;
   ProcessNodeImpl* const process_node_;
-  // Can be used to tie together "sibling" frames, where a navigation is ongoing
-  // in a new frame that will soon replace the existing one.
-  const int frame_tree_node_id_;
   // The routing id of the frame.
   const int render_frame_id_;
 
diff --git a/components/performance_manager/graph/frame_node_impl_describer.cc b/components/performance_manager/graph/frame_node_impl_describer.cc
index cf74af2d..400b2211 100644
--- a/components/performance_manager/graph/frame_node_impl_describer.cc
+++ b/components/performance_manager/graph/frame_node_impl_describer.cc
@@ -71,7 +71,6 @@
   ret.SetKey("document", std::move(doc));
 
   // Frame node properties.
-  ret.SetIntKey("frame_tree_node_id", impl->frame_tree_node_id_);
   ret.SetIntKey("render_frame_id", impl->render_frame_id_);
   ret.SetStringKey("frame_token", impl->frame_token_.value().ToString());
   ret.SetIntKey("browsing_instance_id", impl->browsing_instance_id_.value());
diff --git a/components/performance_manager/graph/frame_node_impl_unittest.cc b/components/performance_manager/graph/frame_node_impl_unittest.cc
index 6a5f11fd..3e28fd36 100644
--- a/components/performance_manager/graph/frame_node_impl_unittest.cc
+++ b/components/performance_manager/graph/frame_node_impl_unittest.cc
@@ -57,9 +57,9 @@
   auto page = CreateNode<PageNodeImpl>();
   auto parent_node = CreateFrameNodeAutoId(process.get(), page.get());
   auto child2_node =
-      CreateFrameNodeAutoId(process.get(), page.get(), parent_node.get(), 1);
+      CreateFrameNodeAutoId(process.get(), page.get(), parent_node.get());
   auto child3_node =
-      CreateFrameNodeAutoId(process.get(), page.get(), parent_node.get(), 2);
+      CreateFrameNodeAutoId(process.get(), page.get(), parent_node.get());
 
   EXPECT_EQ(nullptr, parent_node->parent_frame_node());
   EXPECT_EQ(2u, parent_node->child_frame_nodes().size());
@@ -114,8 +114,8 @@
   auto process = CreateNode<ProcessNodeImpl>();
   auto page = CreateNode<PageNodeImpl>();
   auto parent_frame_node = CreateFrameNodeAutoId(process.get(), page.get());
-  auto child_frame_node = CreateFrameNodeAutoId(process.get(), page.get(),
-                                                parent_frame_node.get(), 1);
+  auto child_frame_node =
+      CreateFrameNodeAutoId(process.get(), page.get(), parent_frame_node.get());
 
   // Ensure correct Parent-child relationships have been established.
   EXPECT_EQ(1u, parent_frame_node->child_frame_nodes().size());
@@ -494,8 +494,6 @@
             public_frame_node->GetPageNode());
   EXPECT_EQ(static_cast<const ProcessNode*>(frame_node->process_node()),
             public_frame_node->GetProcessNode());
-  EXPECT_EQ(frame_node->frame_tree_node_id(),
-            public_frame_node->GetFrameTreeNodeId());
   EXPECT_EQ(frame_node->frame_token(), public_frame_node->GetFrameToken());
   EXPECT_EQ(frame_node->browsing_instance_id(),
             public_frame_node->GetBrowsingInstanceId());
diff --git a/components/performance_manager/graph/graph_impl_operations_unittest.cc b/components/performance_manager/graph/graph_impl_operations_unittest.cc
index 03098a7..32e8c9c 100644
--- a/components/performance_manager/graph/graph_impl_operations_unittest.cc
+++ b/components/performance_manager/graph/graph_impl_operations_unittest.cc
@@ -26,18 +26,16 @@
     process2_ = CreateNode<ProcessNodeImpl>();
     page1_ = CreateNode<PageNodeImpl>();
     page2_ = CreateNode<PageNodeImpl>();
-    mainframe1_ =
-        CreateFrameNodeAutoId(process1_.get(), page1_.get(), nullptr, 0);
-    mainframe2_ =
-        CreateFrameNodeAutoId(process2_.get(), page2_.get(), nullptr, 1);
-    childframe1a_ = CreateFrameNodeAutoId(process2_.get(), page1_.get(),
-                                          mainframe1_.get(), 2);
-    childframe1b_ = CreateFrameNodeAutoId(process2_.get(), page1_.get(),
-                                          mainframe1_.get(), 3);
-    childframe2a_ = CreateFrameNodeAutoId(process1_.get(), page2_.get(),
-                                          mainframe2_.get(), 4);
-    childframe2b_ = CreateFrameNodeAutoId(process1_.get(), page2_.get(),
-                                          mainframe2_.get(), 5);
+    mainframe1_ = CreateFrameNodeAutoId(process1_.get(), page1_.get(), nullptr);
+    mainframe2_ = CreateFrameNodeAutoId(process2_.get(), page2_.get(), nullptr);
+    childframe1a_ =
+        CreateFrameNodeAutoId(process2_.get(), page1_.get(), mainframe1_.get());
+    childframe1b_ =
+        CreateFrameNodeAutoId(process2_.get(), page1_.get(), mainframe1_.get());
+    childframe2a_ =
+        CreateFrameNodeAutoId(process1_.get(), page2_.get(), mainframe2_.get());
+    childframe2b_ =
+        CreateFrameNodeAutoId(process1_.get(), page2_.get(), mainframe2_.get());
   }
 
   TestNodeWrapper<ProcessNodeImpl> process1_;
@@ -78,8 +76,8 @@
 
 TEST_F(GraphImplOperationsTest, GetFrameNodes) {
   // Add a grandchild frame.
-  auto grandchild = CreateFrameNodeAutoId(process1_.get(), page1_.get(),
-                                          childframe1a_.get(), 6);
+  auto grandchild =
+      CreateFrameNodeAutoId(process1_.get(), page1_.get(), childframe1a_.get());
 
   auto frame_nodes = GraphImplOperations::GetFrameNodes(page1_.get());
   EXPECT_THAT(frame_nodes, testing::UnorderedElementsAre(
diff --git a/components/performance_manager/graph/graph_operations_unittest.cc b/components/performance_manager/graph/graph_operations_unittest.cc
index 6e66b47..5f03e76b 100644
--- a/components/performance_manager/graph/graph_operations_unittest.cc
+++ b/components/performance_manager/graph/graph_operations_unittest.cc
@@ -26,18 +26,16 @@
     process2_ = CreateNode<ProcessNodeImpl>();
     page1_ = CreateNode<PageNodeImpl>();
     page2_ = CreateNode<PageNodeImpl>();
-    mainframe1_ =
-        CreateFrameNodeAutoId(process1_.get(), page1_.get(), nullptr, 0);
-    mainframe2_ =
-        CreateFrameNodeAutoId(process2_.get(), page2_.get(), nullptr, 1);
-    childframe1a_ = CreateFrameNodeAutoId(process2_.get(), page1_.get(),
-                                          mainframe1_.get(), 2);
-    childframe1b_ = CreateFrameNodeAutoId(process2_.get(), page1_.get(),
-                                          mainframe1_.get(), 3);
-    childframe2a_ = CreateFrameNodeAutoId(process1_.get(), page2_.get(),
-                                          mainframe2_.get(), 4);
-    childframe2b_ = CreateFrameNodeAutoId(process1_.get(), page2_.get(),
-                                          mainframe2_.get(), 5);
+    mainframe1_ = CreateFrameNodeAutoId(process1_.get(), page1_.get(), nullptr);
+    mainframe2_ = CreateFrameNodeAutoId(process2_.get(), page2_.get(), nullptr);
+    childframe1a_ =
+        CreateFrameNodeAutoId(process2_.get(), page1_.get(), mainframe1_.get());
+    childframe1b_ =
+        CreateFrameNodeAutoId(process2_.get(), page1_.get(), mainframe1_.get());
+    childframe2a_ =
+        CreateFrameNodeAutoId(process1_.get(), page2_.get(), mainframe2_.get());
+    childframe2b_ =
+        CreateFrameNodeAutoId(process1_.get(), page2_.get(), mainframe2_.get());
   }
 
   TestNodeWrapper<ProcessNodeImpl> process1_;
@@ -84,8 +82,8 @@
 
 TEST_F(GraphOperationsTest, GetFrameNodes) {
   // Add a grandchild frame.
-  auto grandchild = CreateFrameNodeAutoId(process1_.get(), page1_.get(),
-                                          childframe1a_.get(), 6);
+  auto grandchild =
+      CreateFrameNodeAutoId(process1_.get(), page1_.get(), childframe1a_.get());
 
   auto frame_nodes = GraphOperations::GetFrameNodes(page1_.get());
   EXPECT_THAT(frame_nodes,
diff --git a/components/performance_manager/graph/page_node_impl_unittest.cc b/components/performance_manager/graph/page_node_impl_unittest.cc
index a5f8bf5..d4895c5 100644
--- a/components/performance_manager/graph/page_node_impl_unittest.cc
+++ b/components/performance_manager/graph/page_node_impl_unittest.cc
@@ -61,9 +61,9 @@
   auto parent_frame =
       CreateFrameNodeAutoId(process_node.get(), page_node.get());
   auto child1_frame = CreateFrameNodeAutoId(process_node.get(), page_node.get(),
-                                            parent_frame.get(), 1);
+                                            parent_frame.get());
   auto child2_frame = CreateFrameNodeAutoId(process_node.get(), page_node.get(),
-                                            parent_frame.get(), 2);
+                                            parent_frame.get());
 
   // Validate that all frames are tallied to the page.
   EXPECT_EQ(3u, GraphImplOperations::GetFrameNodes(page_node.get()).size());
@@ -73,7 +73,7 @@
   auto process_node = CreateNode<ProcessNodeImpl>();
   auto page_node = CreateNode<PageNodeImpl>();
   auto frame_node =
-      CreateFrameNodeAutoId(process_node.get(), page_node.get(), nullptr, 0);
+      CreateFrameNodeAutoId(process_node.get(), page_node.get(), nullptr);
 
   // Ensure correct page-frame relationship has been established.
   auto frame_nodes = GraphImplOperations::GetFrameNodes(page_node.get());
diff --git a/components/performance_manager/performance_manager_impl.cc b/components/performance_manager/performance_manager_impl.cc
index 71035cc..4910a4a 100644
--- a/components/performance_manager/performance_manager_impl.cc
+++ b/components/performance_manager/performance_manager_impl.cc
@@ -130,7 +130,6 @@
     ProcessNodeImpl* process_node,
     PageNodeImpl* page_node,
     FrameNodeImpl* parent_frame_node,
-    int frame_tree_node_id,
     int render_frame_id,
     const blink::LocalFrameToken& frame_token,
     content::BrowsingInstanceId browsing_instance_id,
@@ -138,8 +137,7 @@
     FrameNodeCreationCallback creation_callback) {
   return CreateNodeImpl<FrameNodeImpl>(
       std::move(creation_callback), process_node, page_node, parent_frame_node,
-      frame_tree_node_id, render_frame_id, frame_token, browsing_instance_id,
-      site_instance_id);
+      render_frame_id, frame_token, browsing_instance_id, site_instance_id);
 }
 
 // static
diff --git a/components/performance_manager/performance_manager_impl.h b/components/performance_manager/performance_manager_impl.h
index d9616e4..204cc2c 100644
--- a/components/performance_manager/performance_manager_impl.h
+++ b/components/performance_manager/performance_manager_impl.h
@@ -94,7 +94,6 @@
       ProcessNodeImpl* process_node,
       PageNodeImpl* page_node,
       FrameNodeImpl* parent_frame_node,
-      int frame_tree_node_id,
       int render_frame_id,
       const blink::LocalFrameToken& frame_token,
       content::BrowsingInstanceId browsing_instance_id,
diff --git a/components/performance_manager/performance_manager_impl_unittest.cc b/components/performance_manager/performance_manager_impl_unittest.cc
index f509462..8e6d18c 100644
--- a/components/performance_manager/performance_manager_impl_unittest.cc
+++ b/components/performance_manager/performance_manager_impl_unittest.cc
@@ -65,9 +65,9 @@
   // Create a node of each type.
   std::unique_ptr<FrameNodeImpl> frame_node =
       PerformanceManagerImpl::CreateFrameNode(
-          process_node.get(), page_node.get(), nullptr, 0,
-          ++next_render_frame_id, blink::LocalFrameToken(),
-          content::BrowsingInstanceId(0), content::SiteInstanceId(0));
+          process_node.get(), page_node.get(), nullptr, ++next_render_frame_id,
+          blink::LocalFrameToken(), content::BrowsingInstanceId(0),
+          content::SiteInstanceId(0));
   EXPECT_NE(nullptr, frame_node.get());
 
   PerformanceManagerImpl::DeleteNode(std::move(frame_node));
@@ -88,34 +88,34 @@
 
   std::unique_ptr<FrameNodeImpl> parent1_frame =
       PerformanceManagerImpl::CreateFrameNode(
-          process_node.get(), page_node.get(), nullptr, 0,
-          ++next_render_frame_id, blink::LocalFrameToken(),
-          content::BrowsingInstanceId(0), content::SiteInstanceId(0));
+          process_node.get(), page_node.get(), nullptr, ++next_render_frame_id,
+          blink::LocalFrameToken(), content::BrowsingInstanceId(0),
+          content::SiteInstanceId(0));
   std::unique_ptr<FrameNodeImpl> parent2_frame =
       PerformanceManagerImpl::CreateFrameNode(
-          process_node.get(), page_node.get(), nullptr, 1,
-          ++next_render_frame_id, blink::LocalFrameToken(),
-          content::BrowsingInstanceId(0), content::SiteInstanceId(0));
+          process_node.get(), page_node.get(), nullptr, ++next_render_frame_id,
+          blink::LocalFrameToken(), content::BrowsingInstanceId(0),
+          content::SiteInstanceId(0));
 
   std::unique_ptr<FrameNodeImpl> child1_frame =
       PerformanceManagerImpl::CreateFrameNode(
-          process_node.get(), page_node.get(), parent1_frame.get(), 2,
+          process_node.get(), page_node.get(), parent1_frame.get(),
           ++next_render_frame_id, blink::LocalFrameToken(),
           content::BrowsingInstanceId(0), content::SiteInstanceId(0));
   std::unique_ptr<FrameNodeImpl> child2_frame =
       PerformanceManagerImpl::CreateFrameNode(
-          process_node.get(), page_node.get(), parent2_frame.get(), 3,
+          process_node.get(), page_node.get(), parent2_frame.get(),
           ++next_render_frame_id, blink::LocalFrameToken(),
           content::BrowsingInstanceId(0), content::SiteInstanceId(0));
 
   std::vector<std::unique_ptr<NodeBase>> nodes;
   for (size_t i = 0; i < 10; ++i) {
     nodes.push_back(PerformanceManagerImpl::CreateFrameNode(
-        process_node.get(), page_node.get(), child1_frame.get(), 0,
+        process_node.get(), page_node.get(), child1_frame.get(),
         ++next_render_frame_id, blink::LocalFrameToken(),
         content::BrowsingInstanceId(0), content::SiteInstanceId(0)));
     nodes.push_back(PerformanceManagerImpl::CreateFrameNode(
-        process_node.get(), page_node.get(), child1_frame.get(), 1,
+        process_node.get(), page_node.get(), child1_frame.get(),
         ++next_render_frame_id, blink::LocalFrameToken(),
         content::BrowsingInstanceId(0), content::SiteInstanceId(0)));
   }
diff --git a/components/performance_manager/performance_manager_tab_helper.cc b/components/performance_manager/performance_manager_tab_helper.cc
index 8b3bbc0..c9983d79 100644
--- a/components/performance_manager/performance_manager_tab_helper.cc
+++ b/components/performance_manager/performance_manager_tab_helper.cc
@@ -197,7 +197,6 @@
   std::unique_ptr<FrameNodeImpl> frame =
       PerformanceManagerImpl::CreateFrameNode(
           process_node, primary_page_node(), parent_frame_node,
-          render_frame_host->GetFrameTreeNodeId(),
           render_frame_host->GetRoutingID(),
           blink::LocalFrameToken(render_frame_host->GetFrameToken()),
           site_instance->GetBrowsingInstanceId(), site_instance->GetId(),
diff --git a/components/performance_manager/performance_manager_tab_helper.h b/components/performance_manager/performance_manager_tab_helper.h
index 6cc2e19..04c16f1 100644
--- a/components/performance_manager/performance_manager_tab_helper.h
+++ b/components/performance_manager/performance_manager_tab_helper.h
@@ -137,6 +137,13 @@
     // primary sort key for the page, as it remains constant over the lifetime
     // of the page. It allows an abitrary RFH to be mapped to the appropriate
     // page via RFH::GetMainFrame()->GetFrameTreeNodeId().
+    // TODO(crbug.com/1211368): This is not true, for two reasons. Until MPArch
+    // support is finished, the "main frame" of a page can change (for instance
+    // when a prerendered frame is activated). And even with MPArch, the frame
+    // tree node ID of a perendered frame changes when it's activated.
+    // Fortunately `main_frame_tree_node_id` is currently only used as a DCHECK
+    // that pages are not added twice to the `pages_` set. Make `pages_` a
+    // simple list, or a set keyed on something else.
     int main_frame_tree_node_id = 0;
 
     // The UKM source ID for this page.
diff --git a/components/performance_manager/public/graph/frame_node.h b/components/performance_manager/public/graph/frame_node.h
index 233c639c..07ec615 100644
--- a/components/performance_manager/public/graph/frame_node.h
+++ b/components/performance_manager/public/graph/frame_node.h
@@ -31,28 +31,32 @@
 
 using execution_context_priority::PriorityAndReason;
 
-// Frame nodes form a tree structure, each FrameNode at most has one parent that
-// is a FrameNode. Conceptually, a frame corresponds to a
-// content::RenderFrameHost in the browser, and a content::RenderFrameImpl /
-// blink::LocalFrame/blink::Document in a renderer.
+// Frame nodes form a tree structure, each FrameNode at most has one parent
+// that is a FrameNode. Conceptually, a frame corresponds to a
+// content::RenderFrameHost (RFH) in the browser, and a
+// content::RenderFrameImpl / blink::LocalFrame in a renderer.
 //
-// Note that a frame in a frame tree can be replaced with another, with the
-// continuity of that position represented via the |frame_tree_node_id|. It is
-// possible to have multiple "sibling" nodes that share the same
-// |frame_tree_node_id|. Only one of these may contribute to the content being
+// TODO(crbug.com/1211368): The naming is misleading. In the browser,
+// FrameTreeNode tracks state about a frame and RenderFrameHost tracks state
+// about a document loaded into that frame, which can change over time. The PM
+// node types should be cleaned up to more accurately reflect this.
+//
+// Each RFH is part of a frame tree made up of content::FrameTreeNodes (FTNs).
+// Note that a document in an FTN can be replaced with another, so it is
+// possible to have multiple "sibling" FrameNodes corresponding to RFHs in the
+// same FTN. Only one of these may contribute to the content being
 // rendered, and this node is designated the "current" node in content
 // terminology. A swap is effectively atomic but will take place in two steps
 // in the graph: the outgoing frame will first be marked as not current, and the
 // incoming frame will be marked as current. As such, the graph invariant is
-// that there will be 0 or 1 |is_current| frames with a given
-// |frame_tree_node_id|.
+// that there will be 0 or 1 |is_current| FrameNode's for a given FTN.
 //
-// This occurs when a frame is navigated and the existing frame can't be reused.
-// In that case a "provisional" frame is created to start the navigation. Once
-// the navigation completes (which may actually involve a redirect to another
-// origin meaning the frame has to be destroyed and another one created in
-// another process!) and commits, the frame will be swapped with the previously
-// active frame.
+// This can occur, for example, when a frame is navigated and the existing
+// document can't be reused. In that case a "speculative" RFH is created to
+// start the navigation. Once the navigation completes (which may actually
+// involve a redirect to another origin meaning the document has to be
+// destroyed and another one created in another process!) and commits, the new
+// RFH will be swapped with the previously active RFH.
 //
 // It is only valid to access this object on the sequence of the graph that owns
 // it.
@@ -90,11 +94,6 @@
   // over the lifetime of the frame.
   virtual const ProcessNode* GetProcessNode() const = 0;
 
-  // Gets the FrameTree node ID associated with this node. There may be multiple
-  // sibling nodes with the same frame tree node ID, but at most 1 of them may
-  // be current at a time. This is a constant over the lifetime of the frame.
-  virtual int GetFrameTreeNodeId() const = 0;
-
   // Gets the unique token associated with this frame. This is a constant over
   // the lifetime of the frame and unique across all frames for all time.
   virtual const blink::LocalFrameToken& GetFrameToken() const = 0;
diff --git a/components/performance_manager/test_support/graph_test_harness.cc b/components/performance_manager/test_support/graph_test_harness.cc
index 1a618bf0..8398e3aa 100644
--- a/components/performance_manager/test_support/graph_test_harness.cc
+++ b/components/performance_manager/test_support/graph_test_harness.cc
@@ -15,11 +15,10 @@
 TestNodeWrapper<FrameNodeImpl> TestGraphImpl::CreateFrameNodeAutoId(
     ProcessNodeImpl* process_node,
     PageNodeImpl* page_node,
-    FrameNodeImpl* parent_frame_node,
-    int frame_tree_node_id) {
-  return TestNodeWrapper<FrameNodeImpl>::Create(
-      this, process_node, page_node, parent_frame_node, frame_tree_node_id,
-      ++next_frame_routing_id_);
+    FrameNodeImpl* parent_frame_node) {
+  return TestNodeWrapper<FrameNodeImpl>::Create(this, process_node, page_node,
+                                                parent_frame_node,
+                                                ++next_frame_routing_id_);
 }
 
 GraphTestHarness::GraphTestHarness()
diff --git a/components/performance_manager/test_support/graph_test_harness.h b/components/performance_manager/test_support/graph_test_harness.h
index fda41dc..c0d65fd 100644
--- a/components/performance_manager/test_support/graph_test_harness.h
+++ b/components/performance_manager/test_support/graph_test_harness.h
@@ -79,15 +79,14 @@
       ProcessNodeImpl* process_node,
       PageNodeImpl* page_node,
       FrameNodeImpl* parent_frame_node,
-      int frame_tree_node_id,
       int render_frame_id,
       const blink::LocalFrameToken& frame_token = blink::LocalFrameToken(),
       content::BrowsingInstanceId browsing_instance_id =
           content::BrowsingInstanceId(0),
       content::SiteInstanceId site_instance_id = content::SiteInstanceId(0)) {
     return std::make_unique<FrameNodeImpl>(
-        process_node, page_node, parent_frame_node, frame_tree_node_id,
-        render_frame_id, frame_token, browsing_instance_id, site_instance_id);
+        process_node, page_node, parent_frame_node, render_frame_id,
+        frame_token, browsing_instance_id, site_instance_id);
   }
 };
 
@@ -185,8 +184,7 @@
   TestNodeWrapper<FrameNodeImpl> CreateFrameNodeAutoId(
       ProcessNodeImpl* process_node,
       PageNodeImpl* page_node,
-      FrameNodeImpl* parent_frame_node = nullptr,
-      int frame_tree_node_id = 0);
+      FrameNodeImpl* parent_frame_node = nullptr);
 
  private:
   int next_frame_routing_id_ = 0;
@@ -227,10 +225,9 @@
   TestNodeWrapper<FrameNodeImpl> CreateFrameNodeAutoId(
       ProcessNodeImpl* process_node,
       PageNodeImpl* page_node,
-      FrameNodeImpl* parent_frame_node = nullptr,
-      int frame_tree_node_id = 0) {
-    return graph()->CreateFrameNodeAutoId(
-        process_node, page_node, parent_frame_node, frame_tree_node_id);
+      FrameNodeImpl* parent_frame_node = nullptr) {
+    return graph()->CreateFrameNodeAutoId(process_node, page_node,
+                                          parent_frame_node);
   }
 
   TestNodeWrapper<SystemNodeImpl> GetSystemNode() {
diff --git a/components/performance_manager/test_support/mock_graphs.cc b/components/performance_manager/test_support/mock_graphs.cc
index 8affb17f..2d71ead4 100644
--- a/components/performance_manager/test_support/mock_graphs.cc
+++ b/components/performance_manager/test_support/mock_graphs.cc
@@ -48,8 +48,7 @@
       other_page(TestNodeWrapper<PageNodeImpl>::Create(graph)),
       other_frame(graph->CreateFrameNodeAutoId(process.get(),
                                                other_page.get(),
-                                               nullptr,
-                                               1)) {}
+                                               nullptr)) {}
 
 MockMultiplePagesInSingleProcessGraph::
     ~MockMultiplePagesInSingleProcessGraph() {
@@ -63,8 +62,7 @@
       other_process(TestNodeWrapper<TestProcessNodeImpl>::Create(graph)),
       child_frame(graph->CreateFrameNodeAutoId(other_process.get(),
                                                page.get(),
-                                               frame.get(),
-                                               2)) {
+                                               frame.get())) {
   other_process->SetProcessWithPid(2, base::Process::Current(),
                                    base::Time::Now());
 }
@@ -78,8 +76,7 @@
       other_process(TestNodeWrapper<TestProcessNodeImpl>::Create(graph)),
       child_frame(graph->CreateFrameNodeAutoId(other_process.get(),
                                                other_page.get(),
-                                               other_frame.get(),
-                                               3)) {
+                                               other_frame.get())) {
   other_process->SetProcessWithPid(2, base::Process::Current(),
                                    base::Time::Now());
 }
diff --git a/components/performance_manager/v8_memory/v8_context_tracker_helpers_unittest.cc b/components/performance_manager/v8_memory/v8_context_tracker_helpers_unittest.cc
index 237b6d42..f19ebe7a 100644
--- a/components/performance_manager/v8_memory/v8_context_tracker_helpers_unittest.cc
+++ b/components/performance_manager/v8_memory/v8_context_tracker_helpers_unittest.cc
@@ -65,7 +65,7 @@
   // Fails for a same-process child frame.
   TestNodeWrapper<FrameNodeImpl> child_frame(graph()->CreateFrameNodeAutoId(
       mock_graph->process.get(), mock_graph->page.get(),
-      mock_graph->frame.get(), 4));
+      mock_graph->frame.get()));
   EXPECT_FALSE(HasCrossProcessParent(child_frame.get()));
 }
 
@@ -107,7 +107,7 @@
 TEST_F(V8ContextTrackerHelpersTest, ValidateV8ContextDescriptionMainWorld) {
   TestNodeWrapper<FrameNodeImpl> child_frame(graph()->CreateFrameNodeAutoId(
       mock_graph->process.get(), mock_graph->page.get(),
-      mock_graph->frame.get(), 4));
+      mock_graph->frame.get()));
 
   // A valid description of a main frame.
   auto desc = mojom::V8ContextDescription(
diff --git a/components/performance_manager/v8_memory/v8_context_tracker_unittest.cc b/components/performance_manager/v8_memory/v8_context_tracker_unittest.cc
index 989a88c..f348aded 100644
--- a/components/performance_manager/v8_memory/v8_context_tracker_unittest.cc
+++ b/components/performance_manager/v8_memory/v8_context_tracker_unittest.cc
@@ -166,7 +166,7 @@
   // Create a child of mock_graph_->frame that is in the same process.
   TestNodeWrapper<FrameNodeImpl> child2_frame(graph()->CreateFrameNodeAutoId(
       mock_graph_->process.get(), mock_graph_->page.get(),
-      mock_graph_->frame.get(), 3));
+      mock_graph_->frame.get()));
   // This should explode because synchronous iframe data is expected, but not
   // provided.
   EXPECT_DCHECK_DEATH(tracker_->OnV8ContextCreated(
diff --git a/components/performance_manager/v8_memory/v8_detailed_memory_unittest.cc b/components/performance_manager/v8_memory/v8_detailed_memory_unittest.cc
index 996b52c9..cee694c 100644
--- a/components/performance_manager/v8_memory/v8_detailed_memory_unittest.cc
+++ b/components/performance_manager/v8_memory/v8_detailed_memory_unittest.cc
@@ -591,12 +591,12 @@
   auto page = CreateNode<PageNodeImpl>();
 
   blink::LocalFrameToken frame1_id = blink::LocalFrameToken();
-  auto frame1 = CreateNode<FrameNodeImpl>(process.get(), page.get(), nullptr, 1,
-                                          2, frame1_id);
+  auto frame1 = CreateNode<FrameNodeImpl>(process.get(), page.get(), nullptr,
+                                          /*render_frame_id=*/1, frame1_id);
 
   blink::LocalFrameToken frame2_id = blink::LocalFrameToken();
-  auto frame2 = CreateNode<FrameNodeImpl>(process.get(), page.get(), nullptr, 3,
-                                          4, frame2_id);
+  auto frame2 = CreateNode<FrameNodeImpl>(process.get(), page.get(), nullptr,
+                                          /*render_frame_id=*/2, frame2_id);
   {
     auto data = NewPerProcessV8MemoryUsage(2);
     AddIsolateMemoryUsage(frame1_id, 1001u, data->isolates[0].get());
@@ -646,12 +646,12 @@
   auto page = CreateNode<PageNodeImpl>();
 
   blink::LocalFrameToken frame1_id = blink::LocalFrameToken();
-  auto frame1 = CreateNode<FrameNodeImpl>(process.get(), page.get(), nullptr, 1,
-                                          2, frame1_id);
+  auto frame1 = CreateNode<FrameNodeImpl>(process.get(), page.get(), nullptr,
+                                          /*render_frame_id=*/1, frame1_id);
 
   blink::LocalFrameToken frame2_id = blink::LocalFrameToken();
-  auto frame2 = CreateNode<FrameNodeImpl>(process.get(), page.get(), nullptr, 3,
-                                          4, frame2_id);
+  auto frame2 = CreateNode<FrameNodeImpl>(process.get(), page.get(), nullptr,
+                                          /*render_frame_id=*/2, frame2_id);
   {
     auto data = NewPerProcessV8MemoryUsage(1);
     AddIsolateMemoryUsage(frame1_id, 1001u, data->isolates[0].get());
@@ -1978,8 +1978,8 @@
   auto page = CreateNode<PageNodeImpl>();
 
   blink::LocalFrameToken frame_id = blink::LocalFrameToken();
-  auto frame = CreateNode<FrameNodeImpl>(process.get(), page.get(), nullptr, 1,
-                                         2, frame_id);
+  auto frame = CreateNode<FrameNodeImpl>(process.get(), page.get(), nullptr,
+                                         /*render_frame_id=*/1, frame_id);
 
   blink::DedicatedWorkerToken worker_id = blink::DedicatedWorkerToken();
   auto worker = CreateNode<WorkerNodeImpl>(
@@ -2022,8 +2022,8 @@
   auto page = CreateNode<PageNodeImpl>();
 
   blink::LocalFrameToken frame_id = blink::LocalFrameToken();
-  auto frame = CreateNode<FrameNodeImpl>(process.get(), page.get(), nullptr, 1,
-                                         2, frame_id);
+  auto frame = CreateNode<FrameNodeImpl>(process.get(), page.get(), nullptr,
+                                         /*render_frame_id=*/1, frame_id);
 
   {
     auto data = NewPerProcessV8MemoryUsage(1);
diff --git a/components/performance_manager/v8_memory/v8_memory_test_helpers.cc b/components/performance_manager/v8_memory/v8_memory_test_helpers.cc
index 65b21d2..19827df2 100644
--- a/components/performance_manager/v8_memory/v8_memory_test_helpers.cc
+++ b/components/performance_manager/v8_memory/v8_memory_test_helpers.cc
@@ -258,11 +258,10 @@
     page->SetOpenerFrameNode(opener);
   }
 
-  int frame_tree_node_id = GetNextUniqueId();
   int frame_routing_id = GetNextUniqueId();
   auto frame_token = blink::LocalFrameToken();
   auto frame = CreateNode<FrameNodeImpl>(
-      process, page, parent, frame_tree_node_id, frame_routing_id, frame_token,
+      process, page, parent, frame_routing_id, frame_token,
       content::BrowsingInstanceId(browsing_instance_id));
   if (url) {
     frame->OnNavigationCommitted(GURL(*url), /*same document*/ true);
diff --git a/components/performance_manager/worker_watcher_unittest.cc b/components/performance_manager/worker_watcher_unittest.cc
index 65af46a..a83d4f9 100644
--- a/components/performance_manager/worker_watcher_unittest.cc
+++ b/components/performance_manager/worker_watcher_unittest.cc
@@ -639,7 +639,7 @@
   content::GlobalRenderFrameHostId render_frame_host_id(render_process_id,
                                                         frame_id);
   auto frame_node = PerformanceManagerImpl::CreateFrameNode(
-      process_node, page_node_.get(), nullptr, 0, frame_id,
+      process_node, page_node_.get(), nullptr, frame_id,
       blink::LocalFrameToken(), content::BrowsingInstanceId(0),
       content::SiteInstanceId(0));
 
diff --git a/components/policy/resources/policy_templates.json b/components/policy/resources/policy_templates.json
index ea5bee33..467b5ce5 100644
--- a/components/policy/resources/policy_templates.json
+++ b/components/policy/resources/policy_templates.json
@@ -1122,6 +1122,8 @@
         'DeviceExternalPrintServersAllowlist',
         'PrinterTypeDenyList',
         'PrintRasterizationMode',
+        'PrintPdfAsImageAvailability',
+        'PrintRasterizePdfDpi',
         'DeletePrintJobHistoryAllowed',
         'CloudPrintWarningsSuppressed',
       ]
@@ -1850,6 +1852,16 @@
         'dynamic_refresh': True,
         'per_profile': True,
       },
+      'items': [
+        {
+          'value': True,
+          'caption': 'Enable JavaScript',
+        },
+        {
+          'value': False,
+          'caption': 'Disable JavaScript',
+        },
+      ],
       'deprecated': True,
       'example_value': True,
       'id': 9,
@@ -1873,6 +1885,16 @@
         'dynamic_refresh': True,
         'per_profile': True,
       },
+      'items': [
+        {
+          'value': True,
+          'caption': 'Enable Incognito mode',
+        },
+        {
+          'value': False,
+          'caption': 'Disable Incognito mode',
+        },
+      ],
       'deprecated': True,
       'example_value': False,
       'id': 10,
@@ -1955,6 +1977,16 @@
         'dynamic_refresh': True,
         'per_profile': True,
       },
+      'items': [
+        {
+          'value': True,
+          'caption': 'Disable saving browser history',
+        },
+        {
+          'value': False,
+          'caption': 'Enable saving browser history',
+        },
+      ],
       'example_value': True,
       'id': 11,
       'caption': '''Disable saving browser history''',
@@ -1973,6 +2005,16 @@
         'dynamic_refresh': True,
         'per_profile': True,
       },
+      'items': [
+        {
+          'value': True,
+          'caption': 'Enable deleting browser and download history',
+        },
+        {
+          'value': False,
+          'caption': 'Disable deleting browser and download history',
+        },
+      ],
       'example_value': True,
       'id': 187,
       'caption': '''Enable deleting browser and download history''',
@@ -1991,6 +2033,16 @@
         'dynamic_refresh': False,
         'per_profile': True,
       },
+      'items': [
+        {
+          'value': True,
+          'caption': 'Enable dinosaur easter egg game',
+        },
+        {
+          'value': False,
+          'caption': 'Disable dinosaur easter egg game',
+        },
+      ],
       'example_value': False,
       'id': 309,
       'default_for_enterprise_users': False,
@@ -2010,6 +2062,16 @@
         'dynamic_refresh': True,
         'per_profile': False,
       },
+      'items': [
+        {
+          'value': True,
+          'caption': 'Use a default referrer policy of no-referrer-when-downgrade',
+        },
+        {
+          'value': False,
+          'caption': 'Do not use a default referrer policy of no-referrer-when-downgrade',
+        },
+      ],
       'deprecated': True,
       'example_value': False,
       'id': 648,
@@ -2101,6 +2163,16 @@
         'per_profile': False,
         'platform_only': True,  # No cloud-policy support outside of Chrome OS.
       },
+      'items': [
+        {
+          'value': True,
+          'caption': 'Enable firewall traversal from remote access host',
+        },
+        {
+          'value': False,
+          'caption': 'Disable firewall traversal from remote access host',
+        },
+      ],
       'example_value': False,
       'id': 95,
       'caption': '''Enable firewall traversal from remote access host''',
@@ -24040,6 +24112,65 @@
       When this policy is not set, <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph> will be in Full mode.'''
     },
     {
+      'name': 'PrintPdfAsImageAvailability',
+      'owners': ['awscreen@chromium.org', 'file://printing/OWNERS'],
+      'type': 'main',
+      'schema': { 'type': 'boolean' },
+      'items': [
+        {
+          'value': True,
+          'caption': 'Print as image option available to user to allow PDF rasterization prior to sending print job to destination.',
+        },
+        {
+          'value': False,
+          'caption': 'Print as image option not available for user selection.',
+        },
+      ],
+      'supported_on': ['chrome.win:94-', 'chrome.mac:94-'],
+      'features': {
+        'dynamic_refresh': True,
+        'per_profile': True,
+      },
+      'example_value': True,
+      'default': False,
+      'id': 889,
+      'caption': '''Print PDF as Image Available''',
+      'tags': [],
+      'desc': '''Controls how <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph> makes the Print as image option available on <ph name="MS_WIN_NAME">Microsoft® Windows®</ph> and <ph name="MAC_OS_NAME">macOS</ph> when printing PDFs.
+
+      When printing a PDF on <ph name="MS_WIN_NAME">Microsoft® Windows®</ph> or <ph name="MAC_OS_NAME">macOS</ph>, sometimes print jobs need to be rasterized to an image for certain printers to get correct looking output.
+
+      When this policy is set to Enabled, <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph> will make the Print as image option available in the Print Preview when printing a PDF.
+
+      When this policy is set to Disabled or not set <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph> the Print as image option will not be available to users in Print Preview and PDFs will be printed as usual without being rasterized to an image before being sent to the destination.'''
+    },
+    {
+      'name': 'PrintRasterizePdfDpi',
+      'owners': ['awscreen@chromium.org', 'file://printing/OWNERS'],
+      'type': 'int',
+      'schema': {
+        'type': 'integer',
+        'minimum': 0,
+      },
+      'supported_on': ['chrome.*:94-', 'chrome_os:94-'],
+      'features': {
+        'dynamic_refresh': True,
+        'per_profile': True,
+      },
+      'example_value': 300,
+      'default': 0,
+      'id': 890,
+      'caption': '''Print Rasterize PDF DPI''',
+      'tags': [],
+      'desc': '''Controls print image resolution when <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph> prints PDFs with rasterization.
+
+      When printing a PDF using the Print to image option, it can be beneficial to specify a print resolution other than a device's printer setting or the PDF default.  A high resolution will significantly increase the processing and printing time while a low resolution can lead to poor imaging quality.
+
+      This policy allows a particular resolution to be specified for use when rasterizing PDFs for printing.
+
+      If this policy is set to zero or not set at all then the system default resolution will be used during rasterization of page images.'''
+    },
+    {
       'name': 'DeletePrintJobHistoryAllowed',
       'owners': ['jimmyxgong@chromium.org', 'file://printing/OWNERS'],
       'type': 'main',
@@ -24374,7 +24505,8 @@
         'type': 'array',
         'items': { 'type': 'string' },
       },
-      'future_on': ['chrome.*', 'chrome_os'],
+      'future_on': ['chrome.*'],
+      'supported_on': ['chrome_os:94-'],
       'features': {
         'dynamic_refresh': True,
         'per_profile': True,
@@ -24401,7 +24533,8 @@
         'type': 'array',
         'items': { 'type': 'string' },
       },
-      'future_on': ['chrome.*', 'chrome_os'],
+      'future_on': ['chrome.*'],
+      'supported_on': ['chrome_os:94-'],
       'features': {
         'dynamic_refresh': True,
         'per_profile': True,
@@ -24428,7 +24561,8 @@
         'type': 'array',
         'items': { 'type': 'string' },
       },
-      'future_on': ['chrome.*', 'chrome_os'],
+      'future_on': ['chrome.*'],
+      'supported_on': ['chrome_os:94-'],
       'features': {
         'dynamic_refresh': True,
         'per_profile': True,
@@ -24457,7 +24591,8 @@
         'type': 'array',
         'items': { 'type': 'string' },
       },
-      'future_on': ['chrome.*', 'chrome_os'],
+      'future_on': ['chrome.*'],
+      'supported_on': ['chrome_os:94-'],
       'features': {
         'dynamic_refresh': True,
         'per_profile': True,
@@ -27987,6 +28122,6 @@
   'placeholders': [],
   'deleted_policy_ids': [114, 115, 204, 205, 206, 412, 476, 544, 546, 562, 569, 578, 583, 585, 586, 587, 588, 589, 590, 591, 600, 668, 669],
   'deleted_atomic_policy_group_ids': [19],
-  'highest_id_currently_used': 888,
+  'highest_id_currently_used': 890,
   'highest_atomic_group_id_currently_used': 41
 }
diff --git a/components/printing/browser/print_manager_utils.cc b/components/printing/browser/print_manager_utils.cc
index 2e8bd20..35af634 100644
--- a/components/printing/browser/print_manager_utils.cc
+++ b/components/printing/browser/print_manager_utils.cc
@@ -61,6 +61,7 @@
   params->dpi = settings.dpi_size();
   params->scale_factor = settings.scale_factor();
   params->rasterize_pdf = settings.rasterize_pdf();
+  params->rasterize_pdf_dpi = settings.rasterize_pdf_dpi();
   // Always use an invalid cookie.
   params->document_cookie = 0;
   params->selection_only = settings.selection_only();
diff --git a/components/printing/common/print.mojom b/components/printing/common/print.mojom
index 623659a..51ebcb4 100644
--- a/components/printing/common/print.mojom
+++ b/components/printing/common/print.mojom
@@ -148,6 +148,10 @@
   mojo_base.mojom.String16 footer_template;
   // Whether to rasterize a PDF for printing
   bool rasterize_pdf = false;
+  // Override of the DPI to use if `rasterize_pdf` is true.  A non-positive
+  // value would be an invalid choice of a DPI, and indicates no override
+  // should be applied.
+  int32 rasterize_pdf_dpi = 0;
   // True if print backgrounds is requested by the user.
   bool should_print_backgrounds = false;
   // The document type of printed page(s) from render.
diff --git a/components/printing/renderer/print_render_frame_helper.cc b/components/printing/renderer/print_render_frame_helper.cc
index 868f170..ed31786 100644
--- a/components/printing/renderer/print_render_frame_helper.cc
+++ b/components/printing/renderer/print_render_frame_helper.cc
@@ -331,6 +331,9 @@
     // See https://crbug.com/943462
     webkit_print_params->printer_dpi = kDefaultPdfDpi;
 #endif
+
+    if (print_params.rasterize_pdf && print_params.rasterize_pdf_dpi > 0)
+      webkit_print_params->printer_dpi = print_params.rasterize_pdf_dpi;
   }
   webkit_print_params->rasterize_pdf = print_params.rasterize_pdf;
   webkit_print_params->print_scaling_option = print_params.print_scaling_option;
diff --git a/components/safe_browsing/content/browser/browser_url_loader_throttle.cc b/components/safe_browsing/content/browser/browser_url_loader_throttle.cc
index 8c080a5a..91a1a7ec 100644
--- a/components/safe_browsing/content/browser/browser_url_loader_throttle.cc
+++ b/components/safe_browsing/content/browser/browser_url_loader_throttle.cc
@@ -65,8 +65,10 @@
         !url_checker_delegate ||
         !url_checker_delegate->GetDatabaseManager()->IsSupported() ||
         url_checker_delegate->ShouldSkipRequestCheck(
-            url, frame_tree_node_id_, -1 /* render_process_id */,
-            -1 /* render_frame_id */, originated_from_service_worker);
+            url, frame_tree_node_id_,
+            content::ChildProcessHost::kInvalidUniqueID /* render_process_id */,
+            MSG_ROUTING_NONE /* render_frame_id */,
+            originated_from_service_worker);
     if (skip_checks_) {
       content::GetUIThreadTaskRunner({})->PostTask(
           FROM_HERE,
diff --git a/components/safe_browsing/content/browser/mojo_safe_browsing_impl.cc b/components/safe_browsing/content/browser/mojo_safe_browsing_impl.cc
index 09857bf..dc1bed7 100644
--- a/components/safe_browsing/content/browser/mojo_safe_browsing_impl.cc
+++ b/components/safe_browsing/content/browser/mojo_safe_browsing_impl.cc
@@ -134,9 +134,9 @@
     CreateCheckerAndCheckCallback callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
 
-  if (delegate_->ShouldSkipRequestCheck(url, -1 /* frame_tree_node_id */,
-                                        render_process_id_, render_frame_id,
-                                        originated_from_service_worker)) {
+  if (delegate_->ShouldSkipRequestCheck(
+          url, content::RenderFrameHost::kNoFrameTreeNodeId, render_process_id_,
+          render_frame_id, originated_from_service_worker)) {
     // Ensure that we don't destroy an uncalled CreateCheckerAndCheckCallback
     if (callback) {
       std::move(callback).Run(mojo::NullReceiver(), true /* proceed */,
diff --git a/components/safe_browsing/content/browser/web_api_handshake_checker.cc b/components/safe_browsing/content/browser/web_api_handshake_checker.cc
index 69bee2a..4aeae2a 100644
--- a/components/safe_browsing/content/browser/web_api_handshake_checker.cc
+++ b/components/safe_browsing/content/browser/web_api_handshake_checker.cc
@@ -41,8 +41,10 @@
         !url_checker_delegate ||
         !url_checker_delegate->GetDatabaseManager()->IsSupported() ||
         url_checker_delegate->ShouldSkipRequestCheck(
-            url, frame_tree_node_id_, /*render_process_id=*/-1,
-            /*render_frame_id=*/-1, /*originated_from_service_worker=*/false);
+            url, frame_tree_node_id_,
+            /*render_process_id=*/content::ChildProcessHost::kInvalidUniqueID,
+            /*render_frame_id=*/MSG_ROUTING_NONE,
+            /*originated_from_service_worker=*/false);
     if (skip_checks) {
       OnCompleteCheck(/*slow_check=*/false, /*proceed=*/true,
                       /*showed_interstitial=*/false);
diff --git a/components/safe_browsing/core/browser/url_checker_delegate.h b/components/safe_browsing/core/browser/url_checker_delegate.h
index 839031e..14a98db 100644
--- a/components/safe_browsing/core/browser/url_checker_delegate.h
+++ b/components/safe_browsing/core/browser/url_checker_delegate.h
@@ -69,10 +69,13 @@
 
   // If the method returns true, the entire request won't be checked, including
   // the original URL and redirects.
-  // If neither of |render_process_id| and |render_frame_id| is -1, they will be
-  // used to identify the frame making the request; otherwise
-  // |frame_tree_node_id| will be used. Please note that |frame_tree_node_id|
-  // could also be -1, if a request is not associated with a frame.
+  // If neither of |render_process_id| and |render_frame_id| is a sentinel
+  // value, they will be used to identify the frame making the request;
+  // otherwise |frame_tree_node_id| will be used. Please note that
+  // |frame_tree_node_id| could also be a sentinel value, if a request is not
+  // associated with a frame. Also note that these ids are content/ specific.
+  // See comments in content::RenderFrameHost for the meaning of these ids and
+  // their sentinel values.
   virtual bool ShouldSkipRequestCheck(
       const GURL& original_url,
       int frame_tree_node_id,
diff --git a/components/safe_browsing/core/common/features.cc b/components/safe_browsing/core/common/features.cc
index b216b54b..a034ab2 100644
--- a/components/safe_browsing/core/common/features.cc
+++ b/components/safe_browsing/core/common/features.cc
@@ -102,6 +102,10 @@
     "SafeBrowsingRealTimeUrlLookupReferrerChain",
     base::FEATURE_ENABLED_BY_DEFAULT};
 
+const base::Feature kRealTimeUrlLookupReferrerChainForEnterprise{
+    "SafeBrowsingRealTimeUrlLookupReferrerChainForEnterprise",
+    base::FEATURE_DISABLED_BY_DEFAULT};
+
 const base::Feature
     kSafeBrowsingPasswordCheckIntegrationForSavedPasswordsAndroid{
         "SafeBrowsingPasswordCheckIntegrationForSavedPasswordsAndroid",
@@ -171,6 +175,7 @@
     {&kPasswordProtectionForSignedInUsers, true},
     {&kPasswordProtectionWithToken, true},
     {&kRealTimeUrlLookupReferrerChain, true},
+    {&kRealTimeUrlLookupReferrerChainForEnterprise, true},
     {&kSafeBrowsingSeparateNetworkContexts, true},
     {&kSuspiciousSiteTriggerQuotaFeature, true},
     {&kThreatDomDetailsTagAndAttributeFeature, false},
diff --git a/components/safe_browsing/core/common/features.h b/components/safe_browsing/core/common/features.h
index 15f4f23..32d9aab 100644
--- a/components/safe_browsing/core/common/features.h
+++ b/components/safe_browsing/core/common/features.h
@@ -115,6 +115,10 @@
 // Controls whether the referrer chain is attached to real time requests.
 extern const base::Feature kRealTimeUrlLookupReferrerChain;
 
+// Controls whether the referrer chain is attached to real time requests for
+// enterprise.
+extern const base::Feature kRealTimeUrlLookupReferrerChainForEnterprise;
+
 // Status of the SimplifiedUrlDisplay experiments. This does not control the
 // individual experiments, those are controlled by their own feature flags.
 // The feature is only set by Finch so that we can differentiate between
diff --git a/components/search_engines/template_url_data.cc b/components/search_engines/template_url_data.cc
index 3198d61..877ab23 100644
--- a/components/search_engines/template_url_data.cc
+++ b/components/search_engines/template_url_data.cc
@@ -45,6 +45,7 @@
       usage_count(0),
       prepopulate_id(0),
       sync_guid(base::GenerateGUID()),
+      is_active(ActiveStatus::kTrue),
       keyword_(u"dummy"),
       url_("x") {}
 
diff --git a/components/search_engines/template_url_fetcher.cc b/components/search_engines/template_url_fetcher.cc
index 775366d5..c234b44 100644
--- a/components/search_engines/template_url_fetcher.cc
+++ b/components/search_engines/template_url_fetcher.cc
@@ -216,6 +216,12 @@
   // Mark the keyword as replaceable so it can be removed if necessary.
   // Add() will automatically remove conflicting keyword replaceable engines.
   data.safe_for_autoreplace = true;
+
+  // Autogenerated keywords are kUnspecified active status by default. When the
+  // active search engine feature flag is enabled, kUnspecified keywords are
+  // inactive and cannot be triggered in the omnibox until they are activated.
+  data.is_active = TemplateURLData::ActiveStatus::kUnspecified;
+
   model->Add(std::make_unique<TemplateURL>(data));
 
   fetcher_->RequestCompleted(this);
diff --git a/components/search_engines/template_url_service.cc b/components/search_engines/template_url_service.cc
index 36a5216d..9f592cc 100644
--- a/components/search_engines/template_url_service.cc
+++ b/components/search_engines/template_url_service.cc
@@ -646,7 +646,8 @@
   }
   data.safe_for_autoreplace = false;
   data.last_modified = clock_->Now();
-  data.is_active = TemplateURLData::ActiveStatus::kUnspecified;
+  data.is_active = TemplateURLData::ActiveStatus::kTrue;
+
   Update(url, TemplateURL(data));
 }
 
@@ -675,6 +676,7 @@
   // Play API engines are created by explicit user gesture, and should not be
   // auto-replaceable by an auto-generated engine as the user browses.
   data.safe_for_autoreplace = false;
+  data.is_active = TemplateURLData::ActiveStatus::kTrue;
 
   // The Play API search engine is not guaranteed to be the best engine for
   // |keyword|, if there are user-defined, extension, or policy engines.
@@ -909,6 +911,7 @@
 
   {
     PatchMissingSyncGUIDs(template_urls.get());
+    MaybeSetIsActiveSearchEngines(template_urls.get());
     SetTemplateURLs(std::move(template_urls));
 
     // This initializes provider_map_ which should be done before
@@ -1300,6 +1303,39 @@
 }
 
 // static
+TemplateURLData::ActiveStatus TemplateURLService::ActiveStatusFromSync(
+    sync_pb::SearchEngineSpecifics_ActiveStatus is_active) {
+  switch (is_active) {
+    case sync_pb::SearchEngineSpecifics_ActiveStatus::
+        SearchEngineSpecifics_ActiveStatus_ACTIVE_STATUS_UNSPECIFIED:
+      return TemplateURLData::ActiveStatus::kUnspecified;
+    case sync_pb::SearchEngineSpecifics_ActiveStatus::
+        SearchEngineSpecifics_ActiveStatus_ACTIVE_STATUS_TRUE:
+      return TemplateURLData::ActiveStatus::kTrue;
+    case sync_pb::SearchEngineSpecifics_ActiveStatus::
+        SearchEngineSpecifics_ActiveStatus_ACTIVE_STATUS_FALSE:
+      return TemplateURLData::ActiveStatus::kFalse;
+  }
+}
+
+// static
+sync_pb::SearchEngineSpecifics_ActiveStatus
+TemplateURLService::ActiveStatusToSync(
+    TemplateURLData::ActiveStatus is_active) {
+  switch (is_active) {
+    case TemplateURLData::ActiveStatus::kUnspecified:
+      return sync_pb::SearchEngineSpecifics_ActiveStatus::
+          SearchEngineSpecifics_ActiveStatus_ACTIVE_STATUS_UNSPECIFIED;
+    case TemplateURLData::ActiveStatus::kTrue:
+      return sync_pb::SearchEngineSpecifics_ActiveStatus::
+          SearchEngineSpecifics_ActiveStatus_ACTIVE_STATUS_TRUE;
+    case TemplateURLData::ActiveStatus::kFalse:
+      return sync_pb::SearchEngineSpecifics_ActiveStatus::
+          SearchEngineSpecifics_ActiveStatus_ACTIVE_STATUS_FALSE;
+  }
+}
+
+// static
 syncer::SyncData TemplateURLService::CreateSyncDataFromTemplateURL(
     const TemplateURL& turl) {
   sync_pb::EntitySpecifics specifics;
@@ -1331,6 +1367,7 @@
   se_specifics->set_sync_guid(turl.sync_guid());
   for (size_t i = 0; i < turl.alternate_urls().size(); ++i)
     se_specifics->add_alternate_urls(turl.alternate_urls()[i]);
+  se_specifics->set_is_active(ActiveStatusToSync(turl.is_active()));
 
   return syncer::SyncData::CreateLocalData(se_specifics->sync_guid(),
                                            se_specifics->keyword(),
@@ -1402,6 +1439,7 @@
   data.alternate_urls.clear();
   for (int i = 0; i < specifics.alternate_urls_size(); ++i)
     data.alternate_urls.push_back(specifics.alternate_urls(i));
+  data.is_active = ActiveStatusFromSync(specifics.is_active());
 
   std::unique_ptr<TemplateURL> turl(new TemplateURL(data));
   // If this TemplateURL matches a built-in prepopulated template URL, it's
@@ -2115,6 +2153,22 @@
     default_search_manager_.SetUserSelectedDefaultSearchEngine(turl->data());
 }
 
+void TemplateURLService::MaybeSetIsActiveSearchEngines(
+    OwnedTemplateURLVector* template_urls) {
+  DCHECK(template_urls);
+  for (auto& turl : *template_urls) {
+    DCHECK(turl);
+    // An turl is "active" if it has ever been used or manually added/modified.
+    // |safe_for_autoreplace| is false if the entry has been modified.
+    if (turl->is_active() == TemplateURLData::ActiveStatus::kUnspecified &&
+        (!turl->safe_for_autoreplace() || turl->usage_count() > 0)) {
+      turl->data_.is_active = TemplateURLData::ActiveStatus::kTrue;
+      if (web_data_service_)
+        web_data_service_->UpdateKeyword(turl->data());
+    }
+  }
+}
+
 template <typename Container>
 void TemplateURLService::AddMatchingKeywordsHelper(
     const Container& keyword_to_turl_and_length,
diff --git a/components/search_engines/template_url_service.h b/components/search_engines/template_url_service.h
index 7c4a49d..2bd3101 100644
--- a/components/search_engines/template_url_service.h
+++ b/components/search_engines/template_url_service.h
@@ -31,6 +31,7 @@
 #include "components/search_engines/template_url.h"
 #include "components/sync/model/sync_change.h"
 #include "components/sync/model/syncable_service.h"
+#include "components/sync/protocol/search_engine_specifics.pb.h"
 #include "components/webdata/common/web_data_service_consumer.h"
 #if defined(OS_ANDROID)
 #include "base/android/scoped_java_ref.h"
@@ -410,6 +411,16 @@
   // data.
   void ClearSessionToken();
 
+  // Explicitly converts from ActiveStatus enum in sync protos to enum in
+  // TemplateURLData.
+  static TemplateURLData::ActiveStatus ActiveStatusFromSync(
+      sync_pb::SearchEngineSpecifics_ActiveStatus is_active);
+
+  // Explicitly converts from ActiveStatus enum in TemplateURLData to enum in
+  // sync protos.
+  static sync_pb::SearchEngineSpecifics_ActiveStatus ActiveStatusToSync(
+      TemplateURLData::ActiveStatus is_active);
+
   // Returns a SyncData with a sync representation of the search engine data
   // from |turl|.
   static syncer::SyncData CreateSyncDataFromTemplateURL(
@@ -636,6 +647,11 @@
 
   void OnSyncedDefaultSearchProviderGUIDChanged();
 
+  // Goes through a vector of TemplateURLs and sets is_active to true if it was
+  // not previously set (currently kUnspecified) and has been interacted with
+  // by the user.
+  void MaybeSetIsActiveSearchEngines(OwnedTemplateURLVector* template_urls);
+
   // Adds to |matches| all TemplateURLs stored in |keyword_to_turl_and_length|
   // whose keywords begin with |prefix|, sorted shortest-keyword-first.  If
   // |supports_replacement_only| is true, only TemplateURLs that support
diff --git a/components/security_interstitials/content/stateful_ssl_host_state_delegate.cc b/components/security_interstitials/content/stateful_ssl_host_state_delegate.cc
index 94670c9..f66a7c2b 100644
--- a/components/security_interstitials/content/stateful_ssl_host_state_delegate.cc
+++ b/components/security_interstitials/content/stateful_ssl_host_state_delegate.cc
@@ -19,6 +19,7 @@
 #include "base/command_line.h"
 #include "base/containers/contains.h"
 #include "base/feature_list.h"
+#include "base/json/values_util.h"
 #include "base/logging.h"
 #include "base/metrics/field_trial.h"
 #include "base/metrics/field_trial_params.h"
@@ -71,8 +72,8 @@
 constexpr int kRecurrentInterstitialDefaultResetTime =
     259200;  // 3 days in seconds
 
-// The default expiration for certificate error bypasses is one week, unless
-// overidden by a field trial group.  See https://crbug.com/487270.
+// The default expiration for certificate error and HTTPS-First Mode bypasses is
+// one week.
 const uint64_t kDeltaDefaultExpirationInSeconds = UINT64_C(604800);
 
 // Keys for the per-site error + certificate finger to judgment content
@@ -83,6 +84,10 @@
 
 const int kDefaultSSLCertDecisionVersion = 1;
 
+// Key for the expiration time of a decision in the per-site HTTP allowlist
+// content settings dictionary.
+const char kHTTPAllowlistExpirationTimeKey[] = "decision_expiration_time";
+
 // Records a new occurrence of |error|. The occurrence is stored in the
 // recurrent interstitial pref, which keeps track of the most recent timestamps
 // at which each error type occurred (up to the |threshold| most recent
@@ -275,6 +280,9 @@
   host_content_settings_map_->ClearSettingsForOneTypeWithPredicate(
       ContentSettingsType::SSL_CERT_DECISIONS, base::Time(), base::Time::Max(),
       pattern_filter);
+  host_content_settings_map_->ClearSettingsForOneTypeWithPredicate(
+      ContentSettingsType::HTTP_ALLOWED, base::Time(), base::Time::Max(),
+      pattern_filter);
 }
 
 content::SSLHostStateDelegate::CertJudgment
@@ -361,15 +369,68 @@
   return false;
 }
 
-// TODO(crbug.com/1218526): Hook up to content settings and expiration logic.
-void StatefulSSLHostStateDelegate::AllowHttpForHost(const std::string& host) {
-  allow_http_hosts_.insert(host);
+void StatefulSSLHostStateDelegate::AllowHttpForHost(
+    const std::string& host,
+    content::WebContents* web_contents) {
+  DCHECK(web_contents);
+
+  content::StoragePartition* storage_partition =
+      browser_context_->GetStoragePartition(
+          web_contents->GetMainFrame()->GetSiteInstance(),
+          /*can_create=*/false);
+  if (!storage_partition ||
+      storage_partition != browser_context_->GetDefaultStoragePartition()) {
+    // Decisions for non-default storage partitions are stored in memory only.
+    allowed_http_hosts_for_non_default_storage_partitions_.insert(host);
+    return;
+  }
+
+  // Store when the HTTP allowlist entry for this host should expire. This value
+  // must be stored inside a dictionary as content settings don't support
+  // directly storing a string value.
+  GURL url = GetSecureGURLForHost(host);
+  base::Time expiration_time =
+      clock_->Now() +
+      base::TimeDelta::FromSeconds(kDeltaDefaultExpirationInSeconds);
+  auto dict = std::make_unique<base::Value>(base::Value::Type::DICTIONARY);
+  dict->SetKey(kHTTPAllowlistExpirationTimeKey,
+               base::TimeToValue(expiration_time));
+  host_content_settings_map_->SetWebsiteSettingDefaultScope(
+      url, GURL(), ContentSettingsType::HTTP_ALLOWED, std::move(dict));
 }
 
-// TODO(crbug.com/1218526): Hook up to content settings and expiration logic.
 bool StatefulSSLHostStateDelegate::IsHttpAllowedForHost(
-    const std::string& host) {
-  return base::Contains(allow_http_hosts_, host);
+    const std::string& host,
+    content::WebContents* web_contents) {
+  content::StoragePartition* storage_partition =
+      browser_context_->GetStoragePartition(
+          web_contents->GetMainFrame()->GetSiteInstance(),
+          false /* can_create */);
+  if (!storage_partition ||
+      storage_partition != browser_context_->GetDefaultStoragePartition()) {
+    return base::Contains(
+        allowed_http_hosts_for_non_default_storage_partitions_, host);
+  }
+
+  GURL url = GetSecureGURLForHost(host);
+  const ContentSettingsPattern pattern =
+      ContentSettingsPattern::FromURLNoWildcard(url);
+
+  auto value = host_content_settings_map_->GetWebsiteSetting(
+      url, url, ContentSettingsType::HTTP_ALLOWED, nullptr);
+  if (!value || !value->is_dict()) {
+    return false;
+  }
+
+  auto* decision_expiration_value =
+      value->FindKey(kHTTPAllowlistExpirationTimeKey);
+  auto decision_expiration = base::ValueToTime(decision_expiration_value);
+  if (decision_expiration <= clock_->Now()) {
+    // Allowlist entry has expired.
+    return false;
+  }
+
+  return true;
 }
 
 void StatefulSSLHostStateDelegate::RevokeUserAllowExceptions(
@@ -378,47 +439,21 @@
 
   host_content_settings_map_->SetWebsiteSettingDefaultScope(
       url, GURL(), ContentSettingsType::SSL_CERT_DECISIONS, nullptr);
+  host_content_settings_map_->SetWebsiteSettingDefaultScope(
+      url, GURL(), ContentSettingsType::HTTP_ALLOWED, nullptr);
 
   // Decisions for non-default storage partitions are stored separately in
   // memory; delete those as well.
   allowed_certs_for_non_default_storage_partitions_.erase(host);
+  allowed_http_hosts_for_non_default_storage_partitions_.erase(host);
 }
 
 bool StatefulSSLHostStateDelegate::HasAllowException(
     const std::string& host,
     content::WebContents* web_contents) {
   DCHECK(web_contents);
-
-  content::StoragePartition* storage_partition =
-      browser_context_->GetStoragePartition(
-          web_contents->GetMainFrame()->GetSiteInstance(),
-          false /* can_create */);
-  if (!storage_partition ||
-      storage_partition != browser_context_->GetDefaultStoragePartition()) {
-    return allowed_certs_for_non_default_storage_partitions_.find(host) !=
-           allowed_certs_for_non_default_storage_partitions_.end();
-  }
-
-  GURL url = GetSecureGURLForHost(host);
-  const ContentSettingsPattern pattern =
-      ContentSettingsPattern::FromURLNoWildcard(url);
-
-  std::unique_ptr<base::Value> value(
-      host_content_settings_map_->GetWebsiteSetting(
-          url, url, ContentSettingsType::SSL_CERT_DECISIONS, nullptr));
-
-  if (!value.get() || !value->is_dict())
-    return false;
-
-  for (auto pair : value->DictItems()) {
-    if (!pair.second.is_int())
-      continue;
-
-    if (static_cast<CertJudgment>(pair.second.GetInt()) == ALLOWED)
-      return true;
-  }
-
-  return false;
+  return HasCertAllowException(host, web_contents) ||
+         IsHttpAllowedForHost(host, web_contents);
 }
 
 // TODO(jww): This will revoke all of the decisions in the browser context.
@@ -541,6 +576,41 @@
   }
 }
 
+bool StatefulSSLHostStateDelegate::HasCertAllowException(
+    const std::string& host,
+    content::WebContents* web_contents) {
+  content::StoragePartition* storage_partition =
+      browser_context_->GetStoragePartition(
+          web_contents->GetMainFrame()->GetSiteInstance(),
+          false /* can_create */);
+  if (!storage_partition ||
+      storage_partition != browser_context_->GetDefaultStoragePartition()) {
+    return base::Contains(allowed_certs_for_non_default_storage_partitions_,
+                          host);
+  }
+
+  GURL url = GetSecureGURLForHost(host);
+  const ContentSettingsPattern pattern =
+      ContentSettingsPattern::FromURLNoWildcard(url);
+
+  std::unique_ptr<base::Value> value(
+      host_content_settings_map_->GetWebsiteSetting(
+          url, url, ContentSettingsType::SSL_CERT_DECISIONS, nullptr));
+
+  if (!value.get() || !value->is_dict())
+    return false;
+
+  for (auto pair : value->DictItems()) {
+    if (!pair.second.is_int())
+      continue;
+
+    if (static_cast<CertJudgment>(pair.second.GetInt()) == ALLOWED)
+      return true;
+  }
+
+  return false;
+}
+
 // This helper function gets the dictionary of certificate fingerprints to
 // errors of certificates that have been accepted by the user from the content
 // dictionary that has been passed in. The returned pointer is owned by the the
@@ -582,28 +652,16 @@
   // checks.
   bool expired = false;
   base::Time now = clock_->Now();
-  base::Time decision_expiration;
-  const std::string* decision_expiration_string =
-      dict->FindStringKey(kSSLCertDecisionExpirationTimeKey);
-  if (decision_expiration_string) {
-    int64_t decision_expiration_int64;
-    if (!base::StringToInt64(*decision_expiration_string,
-                             &decision_expiration_int64)) {
-      LOG(ERROR) << "Failed to parse a certificate error exception that has a "
-                 << "bad value for an expiration time: "
-                 << decision_expiration_string;
-      return nullptr;
-    }
-    decision_expiration =
-        base::Time::FromInternalValue(decision_expiration_int64);
-  }
+  auto* decision_expiration_value =
+      dict->FindKey(kSSLCertDecisionExpirationTimeKey);
+  auto decision_expiration = base::ValueToTime(decision_expiration_value);
 
   // Check to see if the user's certificate decision has expired.
   // - Expired and |create_entries| is DO_NOT_CREATE_DICTIONARY_ENTRIES, return
   // nullptr.
   // - Expired and |create_entries| is CREATE_DICTIONARY_ENTRIES, update the
   // expiration time.
-  if (decision_expiration.ToInternalValue() <= now.ToInternalValue()) {
+  if (decision_expiration <= now) {
     if (create_entries == DO_NOT_CREATE_DICTIONARY_ENTRIES)
       return nullptr;
 
@@ -613,8 +671,8 @@
     // Unfortunately, JSON (and thus content settings) doesn't support int64_t
     // values, only doubles. Since this mildly depends on precision, it is
     // better to store the value as a string.
-    dict->SetStringKey(kSSLCertDecisionExpirationTimeKey,
-                       base::NumberToString(expiration_time.ToInternalValue()));
+    dict->SetKey(kSSLCertDecisionExpirationTimeKey,
+                 base::TimeToValue(expiration_time));
   }
 
   // Extract the map of certificate fingerprints to errors from the setting.
diff --git a/components/security_interstitials/content/stateful_ssl_host_state_delegate.h b/components/security_interstitials/content/stateful_ssl_host_state_delegate.h
index c7f9f2c..bf61e0c5 100644
--- a/components/security_interstitials/content/stateful_ssl_host_state_delegate.h
+++ b/components/security_interstitials/content/stateful_ssl_host_state_delegate.h
@@ -65,8 +65,10 @@
   bool DidHostRunInsecureContent(const std::string& host,
                                  int child_id,
                                  InsecureContentType content_type) override;
-  void AllowHttpForHost(const std::string& host) override;
-  bool IsHttpAllowedForHost(const std::string& host) override;
+  void AllowHttpForHost(const std::string& host,
+                        content::WebContents* web_contents) override;
+  bool IsHttpAllowedForHost(const std::string& host,
+                            content::WebContents* web_contents) override;
   void RevokeUserAllowExceptions(const std::string& host) override;
   bool HasAllowException(const std::string& host,
                          content::WebContents* web_contents) override;
@@ -111,6 +113,11 @@
     DO_NOT_CREATE_DICTIONARY_ENTRIES
   };
 
+  // Returns whether the user has allowed a certificate error exception for
+  // |host|.
+  bool HasCertAllowException(const std::string& host,
+                             content::WebContents* web_contents);
+
   // Returns a dictionary of certificate fingerprints and errors that have been
   // allowed as exceptions by the user.
   //
@@ -161,10 +168,18 @@
   // to a certain threshold value.
   std::map<int /* error code */, int /* count */> recurrent_errors_;
 
-  // Tracks sites that are allowed to load over HTTP when HTTPS-Only Mode is
-  // enabled. Allowed hosts are exact hostname matches -- subdomains of a host
-  // on the allowlist must be separately allowlisted.
-  std::set<std::string /* host */> allow_http_hosts_;
+  // Tracks sites that are allowed to load over HTTP when HTTPS-First Mode is
+  // enabled, for non-default storage partitions. Allowed hosts are exact
+  // hostname matches -- subdomains of a host on the allowlist must be
+  // separately allowlisted.
+  //
+  // In most cases, HTTP interstitial decisions are stored in ContentSettings
+  // and persisted to disk, like cert decisions. Similar to cert decisions, for
+  // non-default StoragePartitions the decisions should be isolated from normal
+  // browsing and don't need to be persisted to disk. For these cases, track
+  // allowlist decisions purely in memory.
+  std::set<std::string /* host */>
+      allowed_http_hosts_for_non_default_storage_partitions_;
 
   DISALLOW_COPY_AND_ASSIGN(StatefulSSLHostStateDelegate);
 
diff --git a/components/services/storage/public/mojom/BUILD.gn b/components/services/storage/public/mojom/BUILD.gn
index 497c854..5697880 100644
--- a/components/services/storage/public/mojom/BUILD.gn
+++ b/components/services/storage/public/mojom/BUILD.gn
@@ -27,6 +27,7 @@
     "//components/services/storage/public/mojom/buckets",
     "//components/services/storage/public/mojom/filesystem",
     "//mojo/public/mojom/base",
+    "//sandbox/policy/mojom",
     "//services/network/public/mojom",
     "//third_party/blink/public/mojom:mojom_core",
     "//third_party/blink/public/mojom:mojom_modules",
diff --git a/components/services/storage/public/mojom/storage_service.mojom b/components/services/storage/public/mojom/storage_service.mojom
index 5ccfe67..869d409 100644
--- a/components/services/storage/public/mojom/storage_service.mojom
+++ b/components/services/storage/public/mojom/storage_service.mojom
@@ -6,12 +6,14 @@
 
 import "components/services/storage/public/mojom/partition.mojom";
 import "mojo/public/mojom/base/file_path.mojom";
+import "sandbox/policy/mojom/sandbox.mojom";
 
 [EnableIf=is_not_android]
 import "components/services/storage/public/mojom/filesystem/directory.mojom";
 
 // The main interface into the Storage Service. The browser maintains a single
 // global connection to this interface.
+[ServiceSandbox=sandbox.mojom.Sandbox.kUtility]
 interface StorageService {
   // Enables more aggressive flushing of DOM Storage data to disk in order to
   // minimize chance of data loss.
diff --git a/components/sync/protocol/proto_enum_conversions.cc b/components/sync/protocol/proto_enum_conversions.cc
index 02b0fa9..96ef711 100644
--- a/components/sync/protocol/proto_enum_conversions.cc
+++ b/components/sync/protocol/proto_enum_conversions.cc
@@ -156,6 +156,19 @@
   return "";
 }
 
+const char* ProtoEnumToString(
+    sync_pb::SearchEngineSpecifics::ActiveStatus is_active) {
+  ASSERT_ENUM_BOUNDS(sync_pb::SearchEngineSpecifics, ActiveStatus,
+                     ACTIVE_STATUS_UNSPECIFIED, ACTIVE_STATUS_FALSE);
+  switch (is_active) {
+    ENUM_CASE(sync_pb::SearchEngineSpecifics, ACTIVE_STATUS_UNSPECIFIED);
+    ENUM_CASE(sync_pb::SearchEngineSpecifics, ACTIVE_STATUS_TRUE);
+    ENUM_CASE(sync_pb::SearchEngineSpecifics, ACTIVE_STATUS_FALSE);
+  }
+  NOTREACHED();
+  return "";
+}
+
 const char* ProtoEnumToString(sync_pb::SessionTab::FaviconType favicon_type) {
   ASSERT_ENUM_BOUNDS(sync_pb::SessionTab, FaviconType, TYPE_WEB_FAVICON,
                      TYPE_WEB_FAVICON);
diff --git a/components/sync/protocol/proto_enum_conversions.h b/components/sync/protocol/proto_enum_conversions.h
index b4bee94e..9ae07b1 100644
--- a/components/sync/protocol/proto_enum_conversions.h
+++ b/components/sync/protocol/proto_enum_conversions.h
@@ -52,6 +52,9 @@
 const char* ProtoEnumToString(
     sync_pb::ReadingListSpecifics::ReadingListEntryStatus status);
 
+const char* ProtoEnumToString(
+    sync_pb::SearchEngineSpecifics::ActiveStatus is_active);
+
 const char* ProtoEnumToString(sync_pb::SessionTab::FaviconType favicon_type);
 
 const char* ProtoEnumToString(sync_pb::SessionWindow::BrowserType browser_type);
diff --git a/components/sync/protocol/proto_visitors.h b/components/sync/protocol/proto_visitors.h
index e687c1a..38e65bea 100644
--- a/components/sync/protocol/proto_visitors.h
+++ b/components/sync/protocol/proto_visitors.h
@@ -812,6 +812,7 @@
   VISIT(suggestions_url_post_params);
   VISIT(image_url_post_params);
   VISIT(new_tab_url);
+  VISIT_ENUM(is_active);
 }
 
 VISIT_PROTO_FIELDS(const sync_pb::SendTabToSelfSpecifics& proto) {
diff --git a/components/sync/protocol/search_engine_specifics.proto b/components/sync/protocol/search_engine_specifics.proto
index e81b2f6..29f5448 100644
--- a/components/sync/protocol/search_engine_specifics.proto
+++ b/components/sync/protocol/search_engine_specifics.proto
@@ -80,4 +80,16 @@
 
   // The parameterized URL for a search provider specified new tab page.
   optional string new_tab_url = 26;
+
+  enum ActiveStatus {
+    // The default state when a SE is auto-added. Unspecified SE are inactive.
+    ACTIVE_STATUS_UNSPECIFIED = 0;
+    // The SE is active and can be triggered via the omnibox.
+    ACTIVE_STATUS_TRUE = 1;
+    // The SE has been manually set to inactive by the user.
+    ACTIVE_STATUS_FALSE = 2;
+  }
+  // Whether a search engine is 'active' and can be triggered via the omnibox by
+  // typing in the relevant keyword.
+  optional ActiveStatus is_active = 27;
 }
diff --git a/components/test/data/payments/iframe_receiver.js b/components/test/data/payments/iframe_receiver.js
index cb371bdb..2fd789f 100644
--- a/components/test/data/payments/iframe_receiver.js
+++ b/components/test/data/payments/iframe_receiver.js
@@ -17,7 +17,8 @@
  * identifier.
  * @param {string} credentialId - The base64 encoded identifier of the
  * credential to use for payment.
- * @return {Promise<object>} - Either the string 'success' or an error message.
+ * @return {Promise<string>} - Either the clientDataJSON string or an error
+ * message.
  */
 async function requestPayment(credentialId) {
   try {
@@ -38,7 +39,8 @@
         {total: {label: 'TEST', amount: {currency: 'USD', value: '0.01'}}});
     const response = await request.show();
     await response.complete();
-    return 'success';
+    return String.fromCharCode(...new Uint8Array(
+        response.details.response.clientDataJSON));
   } catch (e) {
     return e.message;
   }
diff --git a/components/variations/service/BUILD.gn b/components/variations/service/BUILD.gn
index 3dc5267..c59ad03 100644
--- a/components/variations/service/BUILD.gn
+++ b/components/variations/service/BUILD.gn
@@ -36,6 +36,7 @@
 
   deps = [
     ":buildflags",
+    ":constants",
     "//base",
     "//build:branding_buildflags",
     "//build:chromeos_buildflags",
@@ -66,6 +67,7 @@
   ]
 
   deps = [
+    ":constants",
     ":service",
     "//base",
     "//base/test:test_support",
@@ -83,3 +85,12 @@
     "//testing/gtest",
   ]
 }
+
+static_library("constants") {
+  sources = [
+    "variations_safe_mode_constants.cc",
+    "variations_safe_mode_constants.h",
+  ]
+
+  deps = [ "//base" ]
+}
diff --git a/components/variations/service/variations_field_trial_creator.cc b/components/variations/service/variations_field_trial_creator.cc
index ecf2e64..34182a0b 100644
--- a/components/variations/service/variations_field_trial_creator.cc
+++ b/components/variations/service/variations_field_trial_creator.cc
@@ -34,6 +34,7 @@
 #include "components/variations/proto/variations_seed.pb.h"
 #include "components/variations/service/buildflags.h"
 #include "components/variations/service/safe_seed_manager.h"
+#include "components/variations/service/variations_safe_mode_constants.h"
 #include "components/variations/service/variations_service.h"
 #include "components/variations/service/variations_service_client.h"
 #include "components/variations/variations_ids_provider.h"
@@ -594,37 +595,37 @@
     metrics::MetricsStateManager* metrics_state_manager) const {
   version_info::Channel channel = client_->GetChannelForVariations();
   if (channel != version_info::Channel::CANARY &&
-      channel != version_info::Channel::DEV &&
-      channel != version_info::Channel::BETA) {
+      channel != version_info::Channel::DEV) {
     return;
   }
 
   int default_group;
   scoped_refptr<base::FieldTrial> trial(
       base::FieldTrialList::FactoryGetFieldTrial(
-          "ExtendedVariationsSafeMode", 100, "Default",
+          kExtendedSafeModeTrial, 100, kDefaultGroup,
           base::FieldTrial::ONE_TIME_RANDOMIZED, &default_group));
 
-  const int control_group = trial->AppendGroup("Control", 33);
-  const int write_prefs_group = trial->AppendGroup("WritePrefs", 33);
-  const int signal_early_and_write_prefs_group =
-      trial->AppendGroup("SignalEarlyAndWritePrefs", 33);
+  const int control_group = trial->AppendGroup(kControlGroup, 25);
+  const int write_only_group =
+      trial->AppendGroup(kWriteSynchronouslyViaPrefServiceGroup, 25);
+  trial->AppendGroup(kSignalAndWriteSynchronouslyViaPrefServiceGroup, 25);
+  trial->AppendGroup(kSignalAndWriteViaFileUtilGroup, 25);
   const int assigned_group = trial->group();
 
-  if (assigned_group == default_group || assigned_group == control_group)
+  DCHECK_NE(assigned_group, default_group);
+  if (assigned_group == control_group)
     return;
 
-  if (assigned_group == write_prefs_group) {
-    metrics_state_manager->LogHasSessionShutdownCleanly(
-        /*has_session_shutdown_cleanly=*/false,
-        /*write_synchronously=*/true, /*update_beacon=*/false);
-    return;
-  }
+  // For clients in the SignalAndWrite* groups, the beacon is updated and a
+  // synchronous write is performed. Conversely, for clients in
+  // |write_only_group|, i.e. the WriteSynchronouslyViaPrefService group, prefs
+  // are written synchronously without updating the beacon, i.e. without
+  // signaling that Chrome should start watching for crashes.
+  bool update_beacon = assigned_group != write_only_group;
 
-  DCHECK_EQ(assigned_group, signal_early_and_write_prefs_group);
   metrics_state_manager->LogHasSessionShutdownCleanly(
-      /*has_session_shutdown_cleanly=*/false,
-      /*write_synchronously=*/true, /*update_beacon=*/true);
+      /*has_session_shutdown_cleanly=*/false, /*write_synchronously=*/true,
+      update_beacon);
 }
 #endif  // !defined(OS_ANDROID)
 
diff --git a/components/variations/service/variations_field_trial_creator_unittest.cc b/components/variations/service/variations_field_trial_creator_unittest.cc
index 2baf0448..961534c 100644
--- a/components/variations/service/variations_field_trial_creator_unittest.cc
+++ b/components/variations/service/variations_field_trial_creator_unittest.cc
@@ -12,9 +12,11 @@
 #include "base/callback_helpers.h"
 #include "base/containers/contains.h"
 #include "base/feature_list.h"
+#include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/files/scoped_temp_dir.h"
 #include "base/macros.h"
+#include "base/test/gtest_util.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/mock_entropy_provider.h"
 #include "base/test/scoped_field_trial_list_resetter.h"
@@ -35,6 +37,7 @@
 #include "components/variations/proto/variations_seed.pb.h"
 #include "components/variations/scoped_variations_ids_provider.h"
 #include "components/variations/service/safe_seed_manager.h"
+#include "components/variations/service/variations_safe_mode_constants.h"
 #include "components/variations/service/variations_service.h"
 #include "components/variations/service/variations_service_client.h"
 #include "components/variations/variations_seed_store.h"
@@ -67,18 +70,8 @@
 const char kTestSeedSignature[] = "a totally valid signature, I swear!";
 
 #if !defined(OS_ANDROID)
-// Trial and group names for the extended variations safe mode experiment.
-const char kExtendedSafeMode[] = "ExtendedVariationsSafeMode";
-const char kDefaultGroup[] = "Default";
-const char kControlGroup[] = "Control";
-const char kWritePrefsGroup[] = "WritePrefs";
-const char kSignalEarlyAndWritePrefsGroup[] = "SignalEarlyAndWritePrefs";
-
 // The content of an empty prefs file.
 const char kEmptyPrefsFile[] = "{}";
-
-// The suffix of the |kStabilityExitedCleanly| pref.
-const char kExitedCleanly[] = "exited_cleanly";
 #endif  // !defined(OS_ANDROID)
 
 // Used for similar tests.
@@ -283,9 +276,11 @@
 
 class TestVariationsFieldTrialCreator : public VariationsFieldTrialCreator {
  public:
-  TestVariationsFieldTrialCreator(PrefService* local_state,
-                                  TestVariationsServiceClient* client,
-                                  SafeSeedManager* safe_seed_manager)
+  TestVariationsFieldTrialCreator(
+      PrefService* local_state,
+      TestVariationsServiceClient* client,
+      SafeSeedManager* safe_seed_manager,
+      const base::FilePath user_data_dir = base::FilePath())
       : VariationsFieldTrialCreator(
             client,
             std::make_unique<VariationsSeedStore>(local_state),
@@ -295,7 +290,7 @@
         safe_seed_manager_(safe_seed_manager),
         build_time_(base::Time::Now()) {
     metrics_state_manager_ = metrics::MetricsStateManager::Create(
-        local_state, &enabled_state_provider_, std::wstring(),
+        local_state, &enabled_state_provider_, std::wstring(), user_data_dir,
         base::BindRepeating(&NoOpStoreClientInfoBackup),
         base::BindRepeating(&NoOpLoadClientInfoBackup));
   }
@@ -730,7 +725,7 @@
   metrics::TestEnabledStateProvider enabled_state_provider(/*consent=*/true,
                                                            /*enabled=*/true);
   auto metrics_state_manager = metrics::MetricsStateManager::Create(
-      &prefs_, &enabled_state_provider, std::wstring(),
+      &prefs_, &enabled_state_provider, std::wstring(), base::FilePath(),
       base::BindRepeating(&NoOpStoreClientInfoBackup),
       base::BindRepeating(&NoOpLoadClientInfoBackup));
 
@@ -805,7 +800,7 @@
     int default_group;
     scoped_refptr<base::FieldTrial> trial(
         base::FieldTrialList::FactoryGetFieldTrial(
-            kExtendedSafeMode, 100, kDefaultGroup,
+            kExtendedSafeModeTrial, 100, kDefaultGroup,
             base::FieldTrial::ONE_TIME_RANDOMIZED, &default_group));
 
     int active_group = group_name == kDefaultGroup
@@ -815,7 +810,10 @@
     return active_group;
   }
 
-  const base::FilePath file_path() const { return prefs_file_; }
+  const base::FilePath prefs_file() const { return prefs_file_; }
+  const base::FilePath user_data_dir_path() const {
+    return temp_dir_.GetPath();
+  }
 
  private:
   base::test::TaskEnvironment task_environment_;
@@ -846,7 +844,7 @@
 
   // Verify that the experiment is not active and that the WritePrefsTime metric
   // was not recorded.
-  EXPECT_FALSE(base::FieldTrialList::IsTrialActive(kExtendedSafeMode));
+  EXPECT_FALSE(base::FieldTrialList::IsTrialActive(kExtendedSafeModeTrial));
   histogram_tester.ExpectTotalCount(
       "Variations.ExtendedSafeMode.WritePrefsTime", 0);
 
@@ -865,7 +863,8 @@
       .WillByDefault(Return(false));
 
   std::vector<version_info::Channel> channels = {
-      version_info::Channel::STABLE, version_info::Channel::UNKNOWN};
+      version_info::Channel::BETA, version_info::Channel::STABLE,
+      version_info::Channel::UNKNOWN};
   for (const version_info::Channel channel : channels) {
     NiceMock<MockVariationsServiceClient> variations_service_client;
     ON_CALL(variations_service_client, GetChannel())
@@ -878,12 +877,12 @@
     ASSERT_TRUE(field_trial_creator.SetupFieldTrials());
 
     // Verify that the experiment is not active.
-    EXPECT_FALSE(base::FieldTrialList::IsTrialActive(kExtendedSafeMode));
+    EXPECT_FALSE(base::FieldTrialList::IsTrialActive(kExtendedSafeModeTrial));
 
     // Check that no prefs were written and that the WritePrefsTime metric was
     // not recorded.
     std::string pref_file_contents;
-    ASSERT_TRUE(base::ReadFileToString(file_path(), &pref_file_contents));
+    ASSERT_TRUE(base::ReadFileToString(prefs_file(), &pref_file_contents));
     EXPECT_EQ(kEmptyPrefsFile, pref_file_contents);
     histogram_tester.ExpectTotalCount(
         "Variations.ExtendedSafeMode.WritePrefsTime", 0);
@@ -908,23 +907,10 @@
   TestVariationsFieldTrialCreator field_trial_creator(
       pref_service.get(), &variations_service_client, &safe_seed_manager);
 
-  int active_group = SetUpExtendedSafeModeExperiment(kDefaultGroup);
-
-  base::HistogramTester histogram_tester;
-  ASSERT_TRUE(field_trial_creator.SetupFieldTrials());
-
-  // Verify that the field trial is active and that the client is in the
-  // default group.
-  EXPECT_TRUE(base::FieldTrialList::IsTrialActive(kExtendedSafeMode));
-  EXPECT_EQ(active_group, base::FieldTrialList::FindValue(kExtendedSafeMode));
-
-  // Check that no prefs were written and that the WritePrefsTime metric was
-  // not recorded.
-  std::string pref_file_contents;
-  ASSERT_TRUE(base::ReadFileToString(file_path(), &pref_file_contents));
-  EXPECT_EQ(kEmptyPrefsFile, pref_file_contents);
-  histogram_tester.ExpectTotalCount(
-      "Variations.ExtendedSafeMode.WritePrefsTime", 0);
+  SetUpExtendedSafeModeExperiment(kDefaultGroup);
+  // The experiment has four experiment groups of equal weight. Verify that
+  // there's a DCHECK if a client is assigned to the default group.
+  EXPECT_DCHECK_DEATH(field_trial_creator.SetupFieldTrials());
 }
 
 TEST_F(FieldTrialCreatorSafeModeExperimentTest,
@@ -950,56 +936,25 @@
 
   // Verify that the field trial is active and that the client is in the
   // control group.
-  EXPECT_TRUE(base::FieldTrialList::IsTrialActive(kExtendedSafeMode));
-  EXPECT_EQ(active_group, base::FieldTrialList::FindValue(kExtendedSafeMode));
+  EXPECT_TRUE(base::FieldTrialList::IsTrialActive(kExtendedSafeModeTrial));
+  EXPECT_EQ(active_group,
+            base::FieldTrialList::FindValue(kExtendedSafeModeTrial));
 
-  // Check that no prefs were written and that the WritePrefsTime metric was
-  // not recorded.
+  // Check that no prefs were written and that the WritePrefsTime metric was not
+  // recorded.
   std::string pref_file_contents;
-  ASSERT_TRUE(base::ReadFileToString(file_path(), &pref_file_contents));
+  ASSERT_TRUE(base::ReadFileToString(prefs_file(), &pref_file_contents));
   EXPECT_EQ(kEmptyPrefsFile, pref_file_contents);
   histogram_tester.ExpectTotalCount(
       "Variations.ExtendedSafeMode.WritePrefsTime", 0);
+
+  // Verify that the Variations Safe Mode file does not exist.
+  EXPECT_FALSE(base::PathExists(
+      user_data_dir_path().Append(variations::kVariationsFilename)));
 }
 
 TEST_F(FieldTrialCreatorSafeModeExperimentTest,
-       EnableExperimentOnBeta_WritePrefsGroup) {
-  std::unique_ptr<PrefService> pref_service(CreatePrefService());
-
-  NiceMock<MockVariationsServiceClient> variations_service_client;
-  ON_CALL(variations_service_client, GetChannel())
-      .WillByDefault(Return(version_info::Channel::BETA));
-
-  // Ensure that variations safe mode is not triggered.
-  NiceMock<MockSafeSeedManager> safe_seed_manager(pref_service.get());
-  ON_CALL(safe_seed_manager, ShouldRunInSafeMode())
-      .WillByDefault(Return(false));
-
-  TestVariationsFieldTrialCreator field_trial_creator(
-      pref_service.get(), &variations_service_client, &safe_seed_manager);
-
-  int active_group = SetUpExtendedSafeModeExperiment(kWritePrefsGroup);
-
-  base::HistogramTester histogram_tester;
-  ASSERT_TRUE(field_trial_creator.SetupFieldTrials());
-
-  // Verify that the field trial is active and that the client is in the
-  // WritePrefs group.
-  EXPECT_TRUE(base::FieldTrialList::IsTrialActive(kExtendedSafeMode));
-  EXPECT_EQ(active_group, base::FieldTrialList::FindValue(kExtendedSafeMode));
-
-  // Check that prefs were written and do not contain |kStabilityExitedCleanly|.
-  // Also, check that the WritePrefsTime metric was recorded.
-  std::string pref_file_contents;
-  ASSERT_TRUE(base::ReadFileToString(file_path(), &pref_file_contents));
-  EXPECT_NE(kEmptyPrefsFile, pref_file_contents);
-  EXPECT_FALSE(base::Contains(pref_file_contents, kExitedCleanly));
-  histogram_tester.ExpectTotalCount(
-      "Variations.ExtendedSafeMode.WritePrefsTime", 1);
-}
-
-TEST_F(FieldTrialCreatorSafeModeExperimentTest,
-       EnableExperimentOnDev_SignalEarlyAndWritePrefsGroup) {
+       EnableExperimentOnDev_WriteSynchronouslyViaPrefServiceGroup) {
   std::unique_ptr<PrefService> pref_service(CreatePrefService());
 
   NiceMock<MockVariationsServiceClient> variations_service_client;
@@ -1012,26 +967,126 @@
       .WillByDefault(Return(false));
 
   TestVariationsFieldTrialCreator field_trial_creator(
-      pref_service.get(), &variations_service_client, &safe_seed_manager);
+      pref_service.get(), &variations_service_client, &safe_seed_manager,
+      user_data_dir_path());
 
   int active_group =
-      SetUpExtendedSafeModeExperiment(kSignalEarlyAndWritePrefsGroup);
+      SetUpExtendedSafeModeExperiment(kWriteSynchronouslyViaPrefServiceGroup);
 
   base::HistogramTester histogram_tester;
   ASSERT_TRUE(field_trial_creator.SetupFieldTrials());
 
   // Verify that the field trial is active and that the client is in the
-  // SignalEarlyAndWritePrefs group.
-  EXPECT_TRUE(base::FieldTrialList::IsTrialActive(kExtendedSafeMode));
-  EXPECT_EQ(active_group, base::FieldTrialList::FindValue(kExtendedSafeMode));
+  // WriteSynchronouslyViaPrefService group.
+  EXPECT_TRUE(base::FieldTrialList::IsTrialActive(kExtendedSafeModeTrial));
+  EXPECT_EQ(active_group,
+            base::FieldTrialList::FindValue(kExtendedSafeModeTrial));
 
-  // Check that prefs were written and contain |kStabilityExitedCleanly|. Also,
-  // check that the WritePrefsTime metric was recorded.
+  // Check that prefs were written and do not contain kStabilityExitedCleanly.
+  // Also, check that the WritePrefsTime metric was recorded.
   std::string pref_file_contents;
-  ASSERT_TRUE(base::ReadFileToString(file_path(), &pref_file_contents));
-  EXPECT_TRUE(base::Contains(pref_file_contents, kExitedCleanly));
+  ASSERT_TRUE(base::ReadFileToString(prefs_file(), &pref_file_contents));
+  EXPECT_NE(kEmptyPrefsFile, pref_file_contents);
+  EXPECT_FALSE(base::Contains(pref_file_contents, "exited_cleanly"));
   histogram_tester.ExpectTotalCount(
       "Variations.ExtendedSafeMode.WritePrefsTime", 1);
+
+  // Verify that the Variations Safe Mode file does not exist.
+  EXPECT_FALSE(base::PathExists(
+      user_data_dir_path().Append(variations::kVariationsFilename)));
+}
+
+TEST_F(FieldTrialCreatorSafeModeExperimentTest,
+       EnableExperimentOnDev_SignalAndWriteSynchronouslyViaPrefServiceGroup) {
+  std::unique_ptr<PrefService> pref_service(CreatePrefService());
+
+  NiceMock<MockVariationsServiceClient> variations_service_client;
+  ON_CALL(variations_service_client, GetChannel())
+      .WillByDefault(Return(version_info::Channel::DEV));
+
+  // Ensure that variations safe mode is not triggered.
+  NiceMock<MockSafeSeedManager> safe_seed_manager(pref_service.get());
+  ON_CALL(safe_seed_manager, ShouldRunInSafeMode())
+      .WillByDefault(Return(false));
+
+  TestVariationsFieldTrialCreator field_trial_creator(
+      pref_service.get(), &variations_service_client, &safe_seed_manager,
+      user_data_dir_path());
+
+  int active_group = SetUpExtendedSafeModeExperiment(
+      kSignalAndWriteSynchronouslyViaPrefServiceGroup);
+
+  base::HistogramTester histogram_tester;
+  ASSERT_TRUE(field_trial_creator.SetupFieldTrials());
+
+  // Verify that the field trial is active and that the client is in the
+  // SignalAndWriteSynchronouslyViaPrefService group.
+  EXPECT_TRUE(base::FieldTrialList::IsTrialActive(kExtendedSafeModeTrial));
+  EXPECT_EQ(active_group,
+            base::FieldTrialList::FindValue(kExtendedSafeModeTrial));
+
+  // Check that prefs were written and contain kStabilityExitedCleanly. Also,
+  // check that the WritePrefsTime metric was recorded.
+  std::string pref_file_contents;
+  ASSERT_TRUE(base::ReadFileToString(prefs_file(), &pref_file_contents));
+  EXPECT_TRUE(base::Contains(pref_file_contents, "\"exited_cleanly\":false"));
+  histogram_tester.ExpectTotalCount(
+      "Variations.ExtendedSafeMode.WritePrefsTime", 1);
+
+  // Verify that the Variations Safe Mode file does not exist.
+  EXPECT_FALSE(base::PathExists(
+      user_data_dir_path().Append(variations::kVariationsFilename)));
+}
+
+TEST_F(FieldTrialCreatorSafeModeExperimentTest,
+       EnableExperimentOnDev_SignalAndWriteViaFileUtilGroup) {
+  std::unique_ptr<PrefService> pref_service(CreatePrefService());
+
+  NiceMock<MockVariationsServiceClient> variations_service_client;
+  ON_CALL(variations_service_client, GetChannel())
+      .WillByDefault(Return(version_info::Channel::DEV));
+
+  // Ensure that variations safe mode is not triggered.
+  NiceMock<MockSafeSeedManager> safe_seed_manager(pref_service.get());
+  ON_CALL(safe_seed_manager, ShouldRunInSafeMode())
+      .WillByDefault(Return(false));
+
+  TestVariationsFieldTrialCreator field_trial_creator(
+      pref_service.get(), &variations_service_client, &safe_seed_manager,
+      user_data_dir_path());
+
+  int active_group =
+      SetUpExtendedSafeModeExperiment(kSignalAndWriteViaFileUtilGroup);
+
+  base::HistogramTester histogram_tester;
+  ASSERT_TRUE(field_trial_creator.SetupFieldTrials());
+
+  // Verify that the field trial is active and that the client is in the
+  // SignalAndWriteViaFileUtil group.
+  EXPECT_TRUE(base::FieldTrialList::IsTrialActive(kExtendedSafeModeTrial));
+  EXPECT_EQ(active_group,
+            base::FieldTrialList::FindValue(kExtendedSafeModeTrial));
+
+  // Verify that the Variations Safe Mode file was written and that the contents
+  // are correct.
+  const base::FilePath variations_file_path =
+      user_data_dir_path().Append(variations::kVariationsFilename);
+  EXPECT_TRUE(base::PathExists(variations_file_path));
+  std::string beacon_file_contents;
+  ASSERT_TRUE(
+      base::ReadFileToString(variations_file_path, &beacon_file_contents));
+  EXPECT_EQ(beacon_file_contents,
+            "{\"user_experience_metrics.stability.exited_cleanly\":false,"
+            "\"variations_crash_streak\":0}");
+
+  // Verify that the WritePrefsTime metric was recorded.
+  histogram_tester.ExpectTotalCount(
+      "Variations.ExtendedSafeMode.WritePrefsTime", 1);
+
+  // Check that no prefs were written to the Local State file.
+  std::string pref_file_contents;
+  ASSERT_TRUE(base::ReadFileToString(prefs_file(), &pref_file_contents));
+  EXPECT_EQ(kEmptyPrefsFile, pref_file_contents);
 }
 #endif  // !defined(OS_ANDROID)
 
diff --git a/components/variations/service/variations_safe_mode_constants.cc b/components/variations/service/variations_safe_mode_constants.cc
new file mode 100644
index 0000000..e96572d
--- /dev/null
+++ b/components/variations/service/variations_safe_mode_constants.cc
@@ -0,0 +1,21 @@
+// 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.
+
+#include "components/variations/service/variations_safe_mode_constants.h"
+
+namespace variations {
+
+const base::FilePath::CharType kVariationsFilename[] =
+    FILE_PATH_LITERAL("Variations");
+
+const char kExtendedSafeModeTrial[] = "ExtendedVariationsSafeMode";
+const char kControlGroup[] = "Control2";
+const char kDefaultGroup[] = "Default2";
+const char kSignalAndWriteSynchronouslyViaPrefServiceGroup[] =
+    "SignalAndWriteSynchronouslyViaPrefService";
+const char kSignalAndWriteViaFileUtilGroup[] = "SignalAndWriteViaFileUtil";
+const char kWriteSynchronouslyViaPrefServiceGroup[] =
+    "WriteSynchronouslyViaPrefService";
+
+}  // namespace variations
diff --git a/components/variations/service/variations_safe_mode_constants.h b/components/variations/service/variations_safe_mode_constants.h
new file mode 100644
index 0000000..2295c972
--- /dev/null
+++ b/components/variations/service/variations_safe_mode_constants.h
@@ -0,0 +1,29 @@
+// 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.
+
+// A file containing extended-variations-safe-mode-related constants, etc.
+
+#ifndef COMPONENTS_VARIATIONS_SERVICE_VARIATIONS_SAFE_MODE_CONSTANTS_H_
+#define COMPONENTS_VARIATIONS_SERVICE_VARIATIONS_SAFE_MODE_CONSTANTS_H_
+
+#include "base/files/file_path.h"
+
+namespace variations {
+
+// The name of the file, which is relative to the user data directory, used by
+// the Extended Variations Safe Mode experiment's SignalAndWriteViaFileUtilGroup
+// to store the stability beacon and the variations crash streak.
+extern const base::FilePath::CharType kVariationsFilename[];
+
+// Trial and group names for the extended variations safe mode experiment.
+extern const char kExtendedSafeModeTrial[];
+extern const char kControlGroup[];
+extern const char kDefaultGroup[];
+extern const char kSignalAndWriteSynchronouslyViaPrefServiceGroup[];
+extern const char kSignalAndWriteViaFileUtilGroup[];
+extern const char kWriteSynchronouslyViaPrefServiceGroup[];
+
+}  // namespace variations
+
+#endif  // COMPONENTS_VARIATIONS_SERVICE_VARIATIONS_SAFE_MODE_CONSTANTS_H_
diff --git a/components/variations/service/variations_service_unittest.cc b/components/variations/service/variations_service_unittest.cc
index 4df303b3..020053f 100644
--- a/components/variations/service/variations_service_unittest.cc
+++ b/components/variations/service/variations_service_unittest.cc
@@ -15,6 +15,7 @@
 #include "base/bind.h"
 #include "base/cxx17_backports.h"
 #include "base/feature_list.h"
+#include "base/files/file_path.h"
 #include "base/json/json_string_value_serializer.h"
 #include "base/macros.h"
 #include "base/run_loop.h"
@@ -349,7 +350,7 @@
     if (!metrics_state_manager_) {
       metrics_state_manager_ = metrics::MetricsStateManager::Create(
           &prefs_, enabled_state_provider_.get(), std::wstring(),
-          base::BindRepeating(&StubStoreClientInfo),
+          base::FilePath(), base::BindRepeating(&StubStoreClientInfo),
           base::BindRepeating(&StubLoadClientInfo));
     }
     return metrics_state_manager_.get();
diff --git a/components/viz/service/display/skia_renderer.cc b/components/viz/service/display/skia_renderer.cc
index 70df059..67d8f6b2 100644
--- a/components/viz/service/display/skia_renderer.cc
+++ b/components/viz/service/display/skia_renderer.cc
@@ -4,6 +4,7 @@
 
 #include "components/viz/service/display/skia_renderer.h"
 
+#include <limits>
 #include <string>
 #include <utility>
 
@@ -88,6 +89,15 @@
 // was chosen to match that used by gl_renderer.
 static const float kAAEpsilon = 1.0f / 1024.0f;
 
+#if defined(OS_APPLE) || defined(USE_OZONE)
+SkScalar remove_epsilon(SkScalar v) {
+  return v < std::numeric_limits<SkScalar>::epsilon() &&
+                 v > -std::numeric_limits<SkScalar>::epsilon()
+             ? 0
+             : v;
+}
+#endif  // defined(OS_APPLE) || defined(USE_OZONE)
+
 // The gfx::QuadF draw_region passed to DoDrawQuad, converted to Skia types
 struct SkDrawRegion {
   SkDrawRegion() = default;
@@ -2871,6 +2881,17 @@
   if (!shared_quad_state->mask_filter_info.IsEmpty()) {
     auto result = shared_quad_state->mask_filter_info.Transform(
         *quad_to_target_transform_inverse);
+    if (!result) {
+      // Skia cannot transform a SkRRect with a matrix which contains epsilons,
+      // workaround the problem by removing epsilons in the matrix.
+      auto matrix = quad_to_target_transform_inverse->matrix();
+      matrix.set(0, 0, remove_epsilon(matrix.get(0, 0)));
+      matrix.set(0, 1, remove_epsilon(matrix.get(0, 1)));
+      matrix.set(1, 0, remove_epsilon(matrix.get(1, 0)));
+      matrix.set(1, 1, remove_epsilon(matrix.get(1, 1)));
+      result =
+          shared_quad_state->mask_filter_info.Transform(gfx::Transform(matrix));
+    }
     DCHECK(result) << "shared_quad_state->mask_filter_info.Transform() failed.";
   }
 
diff --git a/components/webauthn/android/internal_authenticator_android.cc b/components/webauthn/android/internal_authenticator_android.cc
index 762dec4..e4260225 100644
--- a/components/webauthn/android/internal_authenticator_android.cc
+++ b/components/webauthn/android/internal_authenticator_android.cc
@@ -52,6 +52,21 @@
                                                 origin.CreateJavaObject());
 }
 
+void InternalAuthenticatorAndroid::SetPaymentOptions(
+    blink::mojom::PaymentOptionsPtr payment) {
+  JNIEnv* env = AttachCurrentThread();
+  JavaRef<jobject>& obj = GetJavaObject();
+  DCHECK(!obj.is_null());
+
+  std::vector<uint8_t> byte_vector =
+      blink::mojom::PaymentOptions::Serialize(&payment);
+  ScopedJavaLocalRef<jobject> byte_buffer = ScopedJavaLocalRef<jobject>(
+      env, env->NewDirectByteBuffer(byte_vector.data(), byte_vector.size()));
+  base::android::CheckException(env);
+
+  Java_InternalAuthenticator_setPaymentOptions(env, obj, byte_buffer);
+}
+
 void InternalAuthenticatorAndroid::MakeCredential(
     blink::mojom::PublicKeyCredentialCreationOptionsPtr options,
     blink::mojom::Authenticator::MakeCredentialCallback callback) {
diff --git a/components/webauthn/android/internal_authenticator_android.h b/components/webauthn/android/internal_authenticator_android.h
index 8628c563..bdefafa 100644
--- a/components/webauthn/android/internal_authenticator_android.h
+++ b/components/webauthn/android/internal_authenticator_android.h
@@ -36,6 +36,7 @@
 
   // InternalAuthenticator:
   void SetEffectiveOrigin(const url::Origin& origin) override;
+  void SetPaymentOptions(blink::mojom::PaymentOptionsPtr payment) override;
   void MakeCredential(
       blink::mojom::PublicKeyCredentialCreationOptionsPtr options,
       blink::mojom::Authenticator::MakeCredentialCallback callback) override;
diff --git a/components/webauthn/android/java/src/org/chromium/components/webauthn/AuthenticatorImpl.java b/components/webauthn/android/java/src/org/chromium/components/webauthn/AuthenticatorImpl.java
index 2858aca..156f558 100644
--- a/components/webauthn/android/java/src/org/chromium/components/webauthn/AuthenticatorImpl.java
+++ b/components/webauthn/android/java/src/org/chromium/components/webauthn/AuthenticatorImpl.java
@@ -14,6 +14,7 @@
 import org.chromium.blink.mojom.AuthenticatorStatus;
 import org.chromium.blink.mojom.GetAssertionAuthenticatorResponse;
 import org.chromium.blink.mojom.MakeCredentialAuthenticatorResponse;
+import org.chromium.blink.mojom.PaymentOptions;
 import org.chromium.blink.mojom.PublicKeyCredentialCreationOptions;
 import org.chromium.blink.mojom.PublicKeyCredentialRequestOptions;
 import org.chromium.content_public.browser.ContentFeatureList;
@@ -41,6 +42,9 @@
      */
     private Origin mOrigin;
 
+    /** The payment information to be added to the "clientDataJson". */
+    private PaymentOptions mPayment;
+
     private org.chromium.mojo.bindings.Callbacks
             .Callback2<Integer, MakeCredentialAuthenticatorResponse> mMakeCredentialCallback;
     private org.chromium.mojo.bindings.Callbacks
@@ -72,6 +76,14 @@
         mOrigin = origin;
     }
 
+    /**
+     * @param payment The payment information to be added to the "clientDataJson". Should be used
+     * only if the user has confirmed the payment information that was displayed to the user.
+     */
+    public void setPaymentOptions(PaymentOptions payment) {
+        mPayment = payment;
+    }
+
     @Override
     public void makeCredential(
             PublicKeyCredentialCreationOptions options, MakeCredentialResponse callback) {
@@ -113,7 +125,7 @@
             return;
         }
 
-        Fido2ApiHandler.getInstance().getAssertion(options, mRenderFrameHost, mOrigin,
+        Fido2ApiHandler.getInstance().getAssertion(options, mRenderFrameHost, mOrigin, mPayment,
                 (status, response) -> onSignResponse(status, response), status -> onError(status));
     }
 
diff --git a/components/webauthn/android/java/src/org/chromium/components/webauthn/Fido2ApiHandler.java b/components/webauthn/android/java/src/org/chromium/components/webauthn/Fido2ApiHandler.java
index 4219eb36..80972fb 100644
--- a/components/webauthn/android/java/src/org/chromium/components/webauthn/Fido2ApiHandler.java
+++ b/components/webauthn/android/java/src/org/chromium/components/webauthn/Fido2ApiHandler.java
@@ -9,6 +9,7 @@
 import org.chromium.base.ContextUtils;
 import org.chromium.base.PackageUtils;
 import org.chromium.base.ThreadUtils;
+import org.chromium.blink.mojom.PaymentOptions;
 import org.chromium.blink.mojom.PublicKeyCredentialCreationOptions;
 import org.chromium.blink.mojom.PublicKeyCredentialRequestOptions;
 import org.chromium.content_public.browser.RenderFrameHost;
@@ -54,10 +55,10 @@
     }
 
     protected void getAssertion(PublicKeyCredentialRequestOptions options,
-            RenderFrameHost frameHost, Origin origin, GetAssertionResponseCallback callback,
-            FidoErrorResponseCallback errorCallback) {
+            RenderFrameHost frameHost, Origin origin, PaymentOptions payment,
+            GetAssertionResponseCallback callback, FidoErrorResponseCallback errorCallback) {
         new Fido2CredentialRequest().handleGetAssertionRequest(
-                options, frameHost, origin, callback, errorCallback);
+                options, frameHost, origin, payment, callback, errorCallback);
     }
 
     protected void isUserVerifyingPlatformAuthenticatorAvailable(
diff --git a/components/webauthn/android/java/src/org/chromium/components/webauthn/Fido2CredentialRequest.java b/components/webauthn/android/java/src/org/chromium/components/webauthn/Fido2CredentialRequest.java
index f49d19d..1cf2c7b 100644
--- a/components/webauthn/android/java/src/org/chromium/components/webauthn/Fido2CredentialRequest.java
+++ b/components/webauthn/android/java/src/org/chromium/components/webauthn/Fido2CredentialRequest.java
@@ -29,6 +29,7 @@
 import org.chromium.base.ContextUtils;
 import org.chromium.base.Log;
 import org.chromium.blink.mojom.AuthenticatorStatus;
+import org.chromium.blink.mojom.PaymentOptions;
 import org.chromium.blink.mojom.PublicKeyCredentialRequestOptions;
 import org.chromium.components.externalauth.ExternalAuthUtils;
 import org.chromium.components.externalauth.UserRecoverableErrorHandler;
@@ -139,8 +140,8 @@
     }
 
     public void handleGetAssertionRequest(PublicKeyCredentialRequestOptions options,
-            RenderFrameHost frameHost, Origin callerOrigin, GetAssertionResponseCallback callback,
-            FidoErrorResponseCallback errorCallback) {
+            RenderFrameHost frameHost, Origin callerOrigin, PaymentOptions payment,
+            GetAssertionResponseCallback callback, FidoErrorResponseCallback errorCallback) {
         assert mGetAssertionCallback == null && mErrorCallback == null;
         mGetAssertionCallback = callback;
         mErrorCallback = errorCallback;
@@ -178,13 +179,12 @@
                         .setPublicKeyCredentialRequestOptions(getAssertionOptions)
                         .setOrigin(Uri.parse(callerOriginString));
 
-        if (options.payment != null
+        if (payment != null
                 && PaymentFeatureList.isEnabled(PaymentFeatureList.SECURE_PAYMENT_CONFIRMATION)) {
             assert options.challenge != null;
             mClientDataJson = ClientDataJson.buildClientDataJson(ClientDataRequestType.PAYMENT_GET,
                     callerOriginString, options.challenge,
-                    webAuthSecurityChecksResults.isCrossOrigin, options.payment,
-                    options.relyingPartyId,
+                    webAuthSecurityChecksResults.isCrossOrigin, payment, options.relyingPartyId,
                     mWebContents.getLastCommittedUrl().getOrigin().getSpec());
             if (mClientDataJson == null) {
                 returnErrorAndResetCallback(AuthenticatorStatus.NOT_ALLOWED_ERROR);
diff --git a/components/webauthn/android/java/src/org/chromium/components/webauthn/InternalAuthenticator.java b/components/webauthn/android/java/src/org/chromium/components/webauthn/InternalAuthenticator.java
index a26c5c0..7e8ab769 100644
--- a/components/webauthn/android/java/src/org/chromium/components/webauthn/InternalAuthenticator.java
+++ b/components/webauthn/android/java/src/org/chromium/components/webauthn/InternalAuthenticator.java
@@ -8,6 +8,7 @@
 
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.NativeMethods;
+import org.chromium.blink.mojom.PaymentOptions;
 import org.chromium.blink.mojom.PublicKeyCredentialCreationOptions;
 import org.chromium.blink.mojom.PublicKeyCredentialRequestOptions;
 import org.chromium.content_public.browser.RenderFrameHost;
@@ -53,6 +54,11 @@
         mAuthenticator.setEffectiveOrigin(origin);
     }
 
+    @CalledByNative
+    public void setPaymentOptions(ByteBuffer payment) {
+        mAuthenticator.setPaymentOptions(PaymentOptions.deserialize(payment));
+    }
+
     /**
      * Called by InternalAuthenticatorAndroid, which facilitates WebAuthn for processes that
      * originate from the browser process.
diff --git a/components/webauthn/android/java/src/org/chromium/components/webauthn/MockFido2ApiHandler.java b/components/webauthn/android/java/src/org/chromium/components/webauthn/MockFido2ApiHandler.java
index 4aa5dd6..8d4bc97 100644
--- a/components/webauthn/android/java/src/org/chromium/components/webauthn/MockFido2ApiHandler.java
+++ b/components/webauthn/android/java/src/org/chromium/components/webauthn/MockFido2ApiHandler.java
@@ -5,6 +5,7 @@
 package org.chromium.components.webauthn;
 
 import org.chromium.blink.mojom.AuthenticatorStatus;
+import org.chromium.blink.mojom.PaymentOptions;
 import org.chromium.blink.mojom.PublicKeyCredentialCreationOptions;
 import org.chromium.blink.mojom.PublicKeyCredentialRequestOptions;
 import org.chromium.content_public.browser.RenderFrameHost;
@@ -21,8 +22,8 @@
 
     @Override
     protected void getAssertion(PublicKeyCredentialRequestOptions options,
-            RenderFrameHost frameHost, Origin origin, GetAssertionResponseCallback callback,
-            FidoErrorResponseCallback errorCallback) {
+            RenderFrameHost frameHost, Origin origin, PaymentOptions payment,
+            GetAssertionResponseCallback callback, FidoErrorResponseCallback errorCallback) {
         errorCallback.onError(AuthenticatorStatus.NOT_IMPLEMENTED);
     }
 
diff --git a/components/webauthn/content/browser/internal_authenticator_impl.cc b/components/webauthn/content/browser/internal_authenticator_impl.cc
index 41a57d5..93fbf9a 100644
--- a/components/webauthn/content/browser/internal_authenticator_impl.cc
+++ b/components/webauthn/content/browser/internal_authenticator_impl.cc
@@ -36,6 +36,11 @@
   DCHECK(!effective_origin_.opaque());
 }
 
+void InternalAuthenticatorImpl::SetPaymentOptions(
+    blink::mojom::PaymentOptionsPtr payment) {
+  payment_ = std::move(payment);
+}
+
 void InternalAuthenticatorImpl::MakeCredential(
     blink::mojom::PublicKeyCredentialCreationOptionsPtr options,
     blink::mojom::Authenticator::MakeCredentialCallback callback) {
@@ -47,7 +52,7 @@
     blink::mojom::PublicKeyCredentialRequestOptionsPtr options,
     blink::mojom::Authenticator::GetAssertionCallback callback) {
   authenticator_common_->GetAssertion(effective_origin_, std::move(options),
-                                      std::move(callback));
+                                      std::move(payment_), std::move(callback));
 }
 
 void InternalAuthenticatorImpl::IsUserVerifyingPlatformAuthenticatorAvailable(
diff --git a/components/webauthn/content/browser/internal_authenticator_impl.h b/components/webauthn/content/browser/internal_authenticator_impl.h
index 7f3f71e..6ad5ec8 100644
--- a/components/webauthn/content/browser/internal_authenticator_impl.h
+++ b/components/webauthn/content/browser/internal_authenticator_impl.h
@@ -40,6 +40,7 @@
 
   // InternalAuthenticator:
   void SetEffectiveOrigin(const url::Origin& origin) override;
+  void SetPaymentOptions(blink::mojom::PaymentOptionsPtr payment) override;
   void MakeCredential(
       blink::mojom::PublicKeyCredentialCreationOptionsPtr options,
       blink::mojom::Authenticator::MakeCredentialCallback callback) override;
@@ -64,6 +65,7 @@
   }
 
   url::Origin effective_origin_;
+  blink::mojom::PaymentOptionsPtr payment_;
   std::unique_ptr<AuthenticatorCommon> authenticator_common_;
 
   base::WeakPtrFactory<InternalAuthenticatorImpl> weak_factory_{this};
diff --git a/components/webauthn/core/browser/internal_authenticator.h b/components/webauthn/core/browser/internal_authenticator.h
index 6014ae3c..825100e 100644
--- a/components/webauthn/core/browser/internal_authenticator.h
+++ b/components/webauthn/core/browser/internal_authenticator.h
@@ -26,6 +26,11 @@
   // process, the Relying Party ID may be different from the renderer's origin.
   virtual void SetEffectiveOrigin(const url::Origin& origin) = 0;
 
+  // Sets the payment information to be added to the "clientDataJson". Should be
+  // used only if the user has confirmed the payment information that was
+  // displayed to the user.
+  virtual void SetPaymentOptions(blink::mojom::PaymentOptionsPtr payment) = 0;
+
   // Gets the credential info for a new public key credential created by an
   // authenticator for the given |options|. Invokes |callback| with credentials
   // if authentication was successful.
diff --git a/content/browser/child_process_launcher_helper_android.cc b/content/browser/child_process_launcher_helper_android.cc
index 6ec7ad1..87ee2c7 100644
--- a/content/browser/child_process_launcher_helper_android.cc
+++ b/content/browser/child_process_launcher_helper_android.cc
@@ -195,9 +195,6 @@
     jboolean killed_by_us,
     jboolean clean_exit,
     jboolean exception_during_init,
-    jint remaining_process_with_strong_binding,
-    jint remaining_process_with_moderate_binding,
-    jint remaining_process_with_waived_binding,
     jint reverse_rank) {
   ChildProcessTerminationInfo* info =
       reinterpret_cast<ChildProcessTerminationInfo*>(termination_info_ptr);
@@ -206,12 +203,6 @@
   info->was_killed_intentionally_by_browser = killed_by_us;
   info->threw_exception_during_init = exception_during_init;
   info->clean_exit = clean_exit;
-  info->remaining_process_with_strong_binding =
-      remaining_process_with_strong_binding;
-  info->remaining_process_with_moderate_binding =
-      remaining_process_with_moderate_binding;
-  info->remaining_process_with_waived_binding =
-      remaining_process_with_waived_binding;
   info->best_effort_reverse_rank = reverse_rank;
 }
 
diff --git a/content/browser/devtools/browser_devtools_agent_host.cc b/content/browser/devtools/browser_devtools_agent_host.cc
index f7e31d12..4ae5d7fa 100644
--- a/content/browser/devtools/browser_devtools_agent_host.cc
+++ b/content/browser/devtools/browser_devtools_agent_host.cc
@@ -191,6 +191,10 @@
 void BrowserDevToolsAgentHost::DetachSession(DevToolsSession* session) {
 }
 
+protocol::TargetAutoAttacher* BrowserDevToolsAgentHost::auto_attacher() {
+  return auto_attacher_.get();
+}
+
 std::string BrowserDevToolsAgentHost::GetType() {
   return kTypeBrowser;
 }
diff --git a/content/browser/devtools/browser_devtools_agent_host.h b/content/browser/devtools/browser_devtools_agent_host.h
index 3ca3aa8..10b0b8a 100644
--- a/content/browser/devtools/browser_devtools_agent_host.h
+++ b/content/browser/devtools/browser_devtools_agent_host.h
@@ -27,6 +27,7 @@
   // DevToolsAgentHostImpl overrides.
   bool AttachSession(DevToolsSession* session, bool acquire_wake_lock) override;
   void DetachSession(DevToolsSession* session) override;
+  protocol::TargetAutoAttacher* auto_attacher() override;
 
   // DevToolsAgentHost implementation.
   std::string GetType() override;
diff --git a/content/browser/devtools/devtools_agent_host_impl.cc b/content/browser/devtools/devtools_agent_host_impl.cc
index 06532544..646396d 100644
--- a/content/browser/devtools/devtools_agent_host_impl.cc
+++ b/content/browser/devtools/devtools_agent_host_impl.cc
@@ -430,4 +430,8 @@
   return absl::nullopt;
 }
 
+protocol::TargetAutoAttacher* DevToolsAgentHostImpl::auto_attacher() {
+  return nullptr;
+}
+
 }  // namespace content
diff --git a/content/browser/devtools/devtools_agent_host_impl.h b/content/browser/devtools/devtools_agent_host_impl.h
index 5889120..8e1fa41 100644
--- a/content/browser/devtools/devtools_agent_host_impl.h
+++ b/content/browser/devtools/devtools_agent_host_impl.h
@@ -27,6 +27,10 @@
 
 class BrowserContext;
 
+namespace protocol {
+class TargetAutoAttacher;
+}  // namespace protocol
+
 // Describes interface for managing devtools agents from the browser process.
 class CONTENT_EXPORT DevToolsAgentHostImpl : public DevToolsAgentHost {
  public:
@@ -95,6 +99,8 @@
   virtual absl::optional<network::CrossOriginOpenerPolicy>
   cross_origin_opener_policy(const std::string& id);
 
+  virtual protocol::TargetAutoAttacher* auto_attacher();
+
  protected:
   explicit DevToolsAgentHostImpl(const std::string& id);
   ~DevToolsAgentHostImpl() override;
diff --git a/content/browser/devtools/devtools_instrumentation.cc b/content/browser/devtools/devtools_instrumentation.cc
index f0a570d..4d703fc 100644
--- a/content/browser/devtools/devtools_instrumentation.cc
+++ b/content/browser/devtools/devtools_instrumentation.cc
@@ -385,19 +385,6 @@
                    protocol::Network::ResourceTypeEnum::Other, status);
 }
 
-void CreateThrottlesForAgentHost(
-    DevToolsAgentHostImpl* agent_host,
-    NavigationHandle* navigation_handle,
-    std::vector<std::unique_ptr<NavigationThrottle>>* result) {
-  for (auto* target_handler :
-       protocol::TargetHandler::ForAgentHost(agent_host)) {
-    std::unique_ptr<NavigationThrottle> throttle =
-        target_handler->CreateThrottleForNavigation(navigation_handle);
-    if (throttle)
-      result->push_back(std::move(throttle));
-  }
-}
-
 void ThrottleForServiceWorkerAgentHost(
     ServiceWorkerDevToolsAgentHost* agent_host,
     DevToolsAgentHostImpl* requesting_agent_host,
@@ -426,14 +413,15 @@
 
   std::vector<std::unique_ptr<NavigationThrottle>> result;
   if (parent) {
-    DevToolsAgentHostImpl* agent_host =
-        RenderFrameDevToolsAgentHost::GetFor(parent);
-    if (agent_host)
-      CreateThrottlesForAgentHost(agent_host, navigation_handle, &result);
+    if (auto* agent_host = RenderFrameDevToolsAgentHost::GetFor(parent)) {
+      agent_host->auto_attacher()->AppendNavigationThrottles(navigation_handle,
+                                                             &result);
+    }
   } else {
-    for (auto* browser_agent_host : BrowserDevToolsAgentHost::Instances())
-      CreateThrottlesForAgentHost(browser_agent_host, navigation_handle,
-                                  &result);
+    for (DevToolsAgentHostImpl* host : BrowserDevToolsAgentHost::Instances()) {
+      host->auto_attacher()->AppendNavigationThrottles(navigation_handle,
+                                                       &result);
+    }
   }
 
   return result;
diff --git a/content/browser/devtools/protocol/target_auto_attacher.cc b/content/browser/devtools/protocol/target_auto_attacher.cc
index 1510f3e5..72ab522 100644
--- a/content/browser/devtools/protocol/target_auto_attacher.cc
+++ b/content/browser/devtools/protocol/target_auto_attacher.cc
@@ -15,7 +15,11 @@
 namespace protocol {
 
 TargetAutoAttacher::TargetAutoAttacher() = default;
-TargetAutoAttacher::~TargetAutoAttacher() = default;
+
+TargetAutoAttacher::~TargetAutoAttacher() {
+  for (auto& client : clients_)
+    client.AutoAttacherDestroyed(this);
+}
 
 bool TargetAutoAttacher::auto_attach() const {
   return !clients_.empty();
@@ -56,8 +60,7 @@
           RenderFrameDevToolsAgentHost::CreateForLocalRootOrPortalNavigation(
               navigation_request);
     }
-    return DispatchAutoAttach(agent_host.get(), wait_for_debugger_on_start) &&
-                   wait_for_debugger_on_start
+    return DispatchAutoAttach(agent_host.get(), wait_for_debugger_on_start)
                ? agent_host.get()
                : nullptr;
   }
@@ -90,6 +93,22 @@
     std::move(callback).Run();
 }
 
+void TargetAutoAttacher::UpdateWaitForDebuggerOnStart(
+    Client* client,
+    bool wait_for_debugger_on_start,
+    base::OnceClosure callback) {
+  DCHECK(clients_.HasObserver(client));
+  bool was_empty = clients_requesting_wait_for_debugger_.empty();
+  if (wait_for_debugger_on_start)
+    clients_requesting_wait_for_debugger_.insert(client);
+  else
+    clients_requesting_wait_for_debugger_.erase(client);
+  if (clients_requesting_wait_for_debugger_.empty() != was_empty)
+    UpdateAutoAttach(std::move(callback));
+  else
+    std::move(callback).Run();
+}
+
 void TargetAutoAttacher::RemoveClient(Client* client) {
   clients_.RemoveObserver(client);
   clients_requesting_wait_for_debugger_.erase(client);
@@ -98,13 +117,24 @@
     UpdateAutoAttach(base::DoNothing());
 }
 
+void TargetAutoAttacher::AppendNavigationThrottles(
+    NavigationHandle* navigation_handle,
+    std::vector<std::unique_ptr<NavigationThrottle>>* throttles) {
+  for (auto& client : clients_) {
+    std::unique_ptr<NavigationThrottle> throttle =
+        client.CreateThrottleForNavigation(this, navigation_handle);
+    if (throttle)
+      throttles->push_back(std::move(throttle));
+  }
+}
+
 bool TargetAutoAttacher::DispatchAutoAttach(DevToolsAgentHost* host,
                                             bool waiting_for_debugger) {
   bool attached = false;
   for (auto& client : clients_) {
     attached =
         client.AutoAttach(
-            host,
+            this, host,
             waiting_for_debugger &&
                 clients_requesting_wait_for_debugger_.contains(&client)) ||
         attached;
@@ -114,14 +144,14 @@
 
 void TargetAutoAttacher::DispatchAutoDetach(DevToolsAgentHost* host) {
   for (auto& client : clients_)
-    client.AutoDetach(host);
+    client.AutoDetach(this, host);
 }
 
 void TargetAutoAttacher::DispatchSetAttachedTargetsOfType(
     const base::flat_set<scoped_refptr<DevToolsAgentHost>>& hosts,
     const std::string& type) {
   for (auto& client : clients_)
-    client.SetAttachedTargetsOfType(hosts, type);
+    client.SetAttachedTargetsOfType(this, hosts, type);
 }
 
 RendererAutoAttacherBase::RendererAutoAttacherBase(
diff --git a/content/browser/devtools/protocol/target_auto_attacher.h b/content/browser/devtools/protocol/target_auto_attacher.h
index 810f05ee8..80cf735 100644
--- a/content/browser/devtools/protocol/target_auto_attacher.h
+++ b/content/browser/devtools/protocol/target_auto_attacher.h
@@ -15,7 +15,9 @@
 class DevToolsAgentHost;
 class DevToolsAgentHostImpl;
 class DevToolsRendererChannel;
+class NavigationHandle;
 class NavigationRequest;
+class NavigationThrottle;
 
 namespace protocol {
 
@@ -23,12 +25,19 @@
  public:
   class Client : public base::CheckedObserver {
    public:
-    virtual bool AutoAttach(DevToolsAgentHost* host,
+    virtual bool AutoAttach(TargetAutoAttacher* source,
+                            DevToolsAgentHost* host,
                             bool waiting_for_debugger) = 0;
-    virtual void AutoDetach(DevToolsAgentHost* host) = 0;
+    virtual void AutoDetach(TargetAutoAttacher* source,
+                            DevToolsAgentHost* host) = 0;
     virtual void SetAttachedTargetsOfType(
+        TargetAutoAttacher* source,
         const base::flat_set<scoped_refptr<DevToolsAgentHost>>& hosts,
         const std::string& type) = 0;
+    virtual void AutoAttacherDestroyed(TargetAutoAttacher* auto_attacher) = 0;
+    virtual std::unique_ptr<NavigationThrottle> CreateThrottleForNavigation(
+        TargetAutoAttacher* auto_attacher,
+        NavigationHandle* navigation_handle) = 0;
 
    protected:
     Client() = default;
@@ -41,6 +50,13 @@
                  bool wait_for_debugger_on_start,
                  base::OnceClosure callback);
   void RemoveClient(Client* client);
+  void UpdateWaitForDebuggerOnStart(Client* client,
+                                    bool wait_for_debugger_on_start,
+                                    base::OnceClosure callback);
+
+  void AppendNavigationThrottles(
+      NavigationHandle* navigation_handle,
+      std::vector<std::unique_ptr<NavigationThrottle>>* throttles);
 
   DevToolsAgentHost* AutoAttachToFrame(NavigationRequest* navigation_request,
                                        bool wait_for_debugger_on_start);
@@ -62,7 +78,7 @@
       const std::string& type);
 
  private:
-  base::ObserverList<Client, true, true> clients_;
+  base::ObserverList<Client, false, true> clients_;
   base::flat_set<Client*> clients_requesting_wait_for_debugger_;
 
   DISALLOW_COPY_AND_ASSIGN(TargetAutoAttacher);
diff --git a/content/browser/devtools/protocol/target_handler.cc b/content/browser/devtools/protocol/target_handler.cc
index 7f5cf22..177e725a 100644
--- a/content/browser/devtools/protocol/target_handler.cc
+++ b/content/browser/devtools/protocol/target_handler.cc
@@ -82,6 +82,8 @@
   })();
 )";
 
+static const char kTargetNotFound[] = "No target with given id found";
+
 std::unique_ptr<Target::TargetInfo> CreateInfo(DevToolsAgentHost* host) {
   std::unique_ptr<Target::TargetInfo> target_info =
       Target::TargetInfo::Create()
@@ -289,14 +291,21 @@
 // Throttle is owned externally by the navigation subsystem.
 class TargetHandler::Throttle : public content::NavigationThrottle {
  public:
-  ~Throttle() override;
+  ~Throttle() override { CleanupPointers(); }
+  TargetAutoAttacher* auto_attacher() const { return auto_attacher_; }
   void Clear();
   // content::NavigationThrottle implementation:
   const char* GetNameForLogging() override;
 
  protected:
   Throttle(base::WeakPtr<protocol::TargetHandler> target_handler,
-           content::NavigationHandle* navigation_handle);
+           TargetAutoAttacher* auto_attacher,
+           content::NavigationHandle* navigation_handle)
+      : content::NavigationThrottle(navigation_handle),
+        target_handler_(target_handler),
+        auto_attacher_(auto_attacher) {
+    target_handler->throttles_.insert(this);
+  }
   void SetThrottledAgentHost(DevToolsAgentHost* agent_host);
 
   bool is_deferring_ = false;
@@ -305,6 +314,7 @@
 
  private:
   void CleanupPointers();
+  TargetAutoAttacher* auto_attacher_;
 
   DISALLOW_COPY_AND_ASSIGN(Throttle);
 };
@@ -312,8 +322,9 @@
 class TargetHandler::ResponseThrottle : public TargetHandler::Throttle {
  public:
   ResponseThrottle(base::WeakPtr<protocol::TargetHandler> target_handler,
+                   TargetAutoAttacher* auto_attacher,
                    content::NavigationHandle* navigation_handle)
-      : Throttle(target_handler, navigation_handle) {}
+      : Throttle(target_handler, auto_attacher, navigation_handle) {}
   ~ResponseThrottle() override = default;
 
  private:
@@ -323,10 +334,13 @@
   ThrottleCheckResult WillFailRequest() override { return MaybeThrottle(); }
 
   ThrottleCheckResult MaybeThrottle() {
-    if (target_handler_) {
+    if (target_handler_ && auto_attacher()) {
       NavigationRequest* request = NavigationRequest::From(navigation_handle());
-      SetThrottledAgentHost(target_handler_->auto_attacher_->AutoAttachToFrame(
-          request, target_handler_->wait_for_debugger_on_start_));
+      bool wait_for_debugger_on_start =
+          target_handler_->ShouldWaitForDebuggerOnStart(request);
+      DevToolsAgentHost* new_host = auto_attacher()->AutoAttachToFrame(
+          request, wait_for_debugger_on_start);
+      SetThrottledAgentHost(wait_for_debugger_on_start ? new_host : nullptr);
     }
     is_deferring_ = !!agent_host_;
     return is_deferring_ ? DEFER : PROCEED;
@@ -338,7 +352,9 @@
   RequestThrottle(base::WeakPtr<protocol::TargetHandler> target_handler,
                   content::NavigationHandle* navigation_handle,
                   DevToolsAgentHost* throttled_agent_host)
-      : Throttle(target_handler, navigation_handle) {
+      : Throttle(target_handler,
+                 target_handler->auto_attacher_,
+                 navigation_handle) {
     SetThrottledAgentHost(throttled_agent_host);
   }
   ~RequestThrottle() override = default;
@@ -523,22 +539,11 @@
   DevToolsSession* devtools_session_ = nullptr;
   Throttle* throttle_ = nullptr;
   scoped_refptr<DevToolsThrottleHandle> service_worker_throttle_;
+  TargetAutoAttacher* auto_attacher_ = nullptr;
 
   DISALLOW_COPY_AND_ASSIGN(Session);
 };
 
-TargetHandler::Throttle::Throttle(
-    base::WeakPtr<protocol::TargetHandler> target_handler,
-    content::NavigationHandle* navigation_handle)
-    : content::NavigationThrottle(navigation_handle),
-      target_handler_(target_handler) {
-  target_handler->throttles_.insert(this);
-}
-
-TargetHandler::Throttle::~Throttle() {
-  CleanupPointers();
-}
-
 void TargetHandler::Throttle::CleanupPointers() {
   if (target_handler_ && agent_host_) {
     auto it = target_handler_->auto_attached_sessions_.find(agent_host_.get());
@@ -567,6 +572,7 @@
 void TargetHandler::Throttle::Clear() {
   CleanupPointers();
   agent_host_ = nullptr;
+  auto_attacher_ = nullptr;
   if (is_deferring_)
     Resume();
   is_deferring_ = false;
@@ -620,15 +626,15 @@
 }
 
 std::unique_ptr<NavigationThrottle> TargetHandler::CreateThrottleForNavigation(
+    TargetAutoAttacher* auto_attacher,
     NavigationHandle* navigation_handle) {
-  if (!auto_attach_)
-    return nullptr;
-  if (access_mode_ == AccessMode::kBrowser) {
-    FrameTreeNode* frame_tree_node =
-        NavigationRequest::From(navigation_handle)->frame_tree_node();
-    // Top-level target handler is expected to create throttles only for new
-    // pages.
-    DCHECK(!frame_tree_node->parent());
+  DCHECK(auto_attach_ || !auto_attach_related_targets_.empty());
+  FrameTreeNode* frame_tree_node =
+      NavigationRequest::From(navigation_handle)->frame_tree_node();
+  DCHECK(access_mode_ != AccessMode::kBrowser ||
+         !auto_attach_related_targets_.empty() || !frame_tree_node->parent());
+  if (access_mode_ == AccessMode::kBrowser && !frame_tree_node->parent()) {
+    DCHECK(auto_attacher == auto_attacher_);
     DevToolsAgentHost* host =
         RenderFrameDevToolsAgentHost::GetFor(frame_tree_node);
     // For new pages create Throttle only if the session is still paused.
@@ -657,7 +663,7 @@
                                              navigation_handle, host);
   }
   return std::make_unique<ResponseThrottle>(weak_factory_.GetWeakPtr(),
-                                            navigation_handle);
+                                            auto_attacher, navigation_handle);
 }
 
 void TargetHandler::ClearThrottles() {
@@ -671,6 +677,9 @@
                                           bool wait_for_debugger_on_start,
                                           bool flatten,
                                           base::OnceClosure callback) {
+  for (auto& entry : auto_attach_related_targets_)
+    entry.first->RemoveClient(this);
+  auto_attach_related_targets_.clear();
   flatten_auto_attach_ = flatten;
   if (auto_attach_)
     auto_attacher_->RemoveClient(this);
@@ -680,8 +689,10 @@
     auto_attacher_->AddClient(this, wait_for_debugger_on_start,
                               std::move(callback));
   } else {
-    while (!auto_attached_sessions_.empty())
-      AutoDetach(auto_attached_sessions_.begin()->first);
+    while (!auto_attached_sessions_.empty()) {
+      auto it = auto_attached_sessions_.begin();
+      AutoDetach(it->second->auto_attacher_, it->first);
+    }
     ClearThrottles();
     std::move(callback).Run();
   }
@@ -697,24 +708,31 @@
     DevToolsAgentHost::RemoveObserver(this);
 }
 
-bool TargetHandler::AutoAttach(DevToolsAgentHost* host,
+bool TargetHandler::AutoAttach(TargetAutoAttacher* source,
+                               DevToolsAgentHost* host,
                                bool waiting_for_debugger) {
   if (auto_attached_sessions_.find(host) != auto_attached_sessions_.end())
     return false;
   std::string session_id =
       Session::Attach(this, host, waiting_for_debugger, flatten_auto_attach_);
-  auto_attached_sessions_[host] = attached_sessions_[session_id].get();
+  Session* session = attached_sessions_[session_id].get();
+  session->auto_attacher_ = source;
+  auto_attached_sessions_[host] = session;
   return true;
 }
 
-void TargetHandler::AutoDetach(DevToolsAgentHost* host) {
+void TargetHandler::AutoDetach(TargetAutoAttacher* source,
+                               DevToolsAgentHost* host) {
   auto it = auto_attached_sessions_.find(host);
-  if (it == auto_attached_sessions_.end())
+  if (it == auto_attached_sessions_.end() ||
+      it->second->auto_attacher_ != source) {
     return;
+  }
   it->second->Detach(false);
 }
 
 void TargetHandler::SetAttachedTargetsOfType(
+    TargetAutoAttacher* source,
     const base::flat_set<scoped_refptr<DevToolsAgentHost>>& new_hosts,
     const std::string& type) {
   DCHECK(!type.empty());
@@ -722,15 +740,40 @@
   for (auto& entry : old_sessions) {
     scoped_refptr<DevToolsAgentHost> host(entry.first);
     bool matches_type = type.empty() || host->GetType() == type;
-    if (matches_type && new_hosts.find(host) == new_hosts.end())
-      AutoDetach(host.get());
+    if (matches_type && entry.second->auto_attacher_ == source &&
+        new_hosts.find(host) == new_hosts.end()) {
+      AutoDetach(source, host.get());
+    }
   }
   for (auto& host : new_hosts) {
     if (old_sessions.find(host.get()) == old_sessions.end())
-      AutoAttach(host.get(), false);
+      AutoAttach(source, host.get(), false);
   }
 }
 
+void TargetHandler::AutoAttacherDestroyed(TargetAutoAttacher* auto_attacher) {
+  auto throttles = throttles_;
+  for (auto* throttle : throttles_) {
+    if (throttle->auto_attacher() == auto_attacher)
+      throttle->Clear();
+  }
+
+  auto_attach_related_targets_.erase(auto_attacher);
+}
+
+bool TargetHandler::ShouldWaitForDebuggerOnStart(
+    NavigationRequest* navigation_request) const {
+  if (auto_attach_)
+    return wait_for_debugger_on_start_;
+  DCHECK(!auto_attach_related_targets_.empty());
+  auto* host = RenderFrameDevToolsAgentHost::GetFor(
+      navigation_request->frame_tree_node());
+  if (!host)
+    return false;
+  auto it = auto_attach_related_targets_.find(host->auto_attacher());
+  return it != auto_attach_related_targets_.end() && it->second;
+}
+
 bool TargetHandler::ShouldThrottlePopups() const {
   return auto_attach_;
 }
@@ -791,6 +834,48 @@
       base::BindOnce(&SetAutoAttachCallback::sendSuccess, std::move(callback)));
 }
 
+void TargetHandler::AutoAttachRelated(
+    const std::string& targetId,
+    bool wait_for_debugger_on_start,
+    std::unique_ptr<AutoAttachRelatedCallback> callback) {
+  if (access_mode_ != AccessMode::kBrowser) {
+    callback->sendFailure(Response::ServerError(
+        "Target.autoAttachRelated is only supported on the Browser target"));
+    return;
+  }
+  scoped_refptr<DevToolsAgentHostImpl> host =
+      DevToolsAgentHostImpl::GetForId(targetId);
+  if (!host) {
+    callback->sendFailure(Response::InvalidParams(kTargetNotFound));
+    return;
+  }
+  TargetAutoAttacher* auto_attacher = host->auto_attacher();
+  if (!auto_attacher) {
+    callback->sendFailure(
+        Response::InvalidParams("Target does not support auto-attaching"));
+    return;
+  }
+  if (auto_attach_) {
+    DCHECK(auto_attach_related_targets_.empty());
+    SetAutoAttachInternal(false, false, true, base::DoNothing());
+  }
+  flatten_auto_attach_ = true;
+  auto inserted = auto_attach_related_targets_.insert(
+      std::make_pair(auto_attacher, wait_for_debugger_on_start));
+  if (!inserted.second) {
+    auto_attacher->UpdateWaitForDebuggerOnStart(
+        this, wait_for_debugger_on_start,
+        base::BindOnce(&AutoAttachRelatedCallback::sendSuccess,
+                       std::move(callback)));
+    inserted.first->second = wait_for_debugger_on_start;
+    return;
+  }
+  auto_attacher->AddClient(
+      this, wait_for_debugger_on_start,
+      base::BindOnce(&AutoAttachRelatedCallback::sendSuccess,
+                     std::move(callback)));
+}
+
 Response TargetHandler::SetRemoteLocations(
     std::unique_ptr<protocol::Array<Target::RemoteLocation>>) {
   return Response::ServerError("Not supported");
@@ -805,7 +890,7 @@
   scoped_refptr<DevToolsAgentHost> agent_host =
       DevToolsAgentHost::GetForId(target_id);
   if (!agent_host)
-    return Response::InvalidParams("No target with given id found");
+    return Response::InvalidParams(kTargetNotFound);
   *out_session_id =
       Session::Attach(this, agent_host.get(), false, flatten.fromMaybe(false));
   return Response::Success();
@@ -862,7 +947,7 @@
   scoped_refptr<DevToolsAgentHost> agent_host(
       DevToolsAgentHost::GetForId(target_id));
   if (!agent_host)
-    return Response::InvalidParams("No target with given id found");
+    return Response::InvalidParams(kTargetNotFound);
   *target_info = CreateInfo(agent_host.get());
   return Response::Success();
 }
@@ -874,7 +959,7 @@
   scoped_refptr<DevToolsAgentHost> agent_host(
       DevToolsAgentHost::GetForId(target_id));
   if (!agent_host)
-    return Response::InvalidParams("No target with given id found");
+    return Response::InvalidParams(kTargetNotFound);
   agent_host->Activate();
   return Response::Success();
 }
@@ -886,7 +971,7 @@
   scoped_refptr<DevToolsAgentHost> agent_host =
       DevToolsAgentHost::GetForId(target_id);
   if (!agent_host)
-    return Response::InvalidParams("No target with given id found");
+    return Response::InvalidParams(kTargetNotFound);
   if (!agent_host->Close())
     return Response::InvalidParams("Specified target doesn't support closing");
   *out_success = true;
@@ -901,7 +986,7 @@
   scoped_refptr<DevToolsAgentHost> agent_host =
       DevToolsAgentHost::GetForId(target_id);
   if (!agent_host)
-    return Response::InvalidParams("No target with given id found");
+    return Response::InvalidParams(kTargetNotFound);
 
   if (g_browser_to_page_connectors.Get()[agent_host.get()]) {
     return Response::ServerError(base::StringPrintf(
diff --git a/content/browser/devtools/protocol/target_handler.h b/content/browser/devtools/protocol/target_handler.h
index a539e47..4883826 100644
--- a/content/browser/devtools/protocol/target_handler.h
+++ b/content/browser/devtools/protocol/target_handler.h
@@ -54,9 +54,6 @@
   void Wire(UberDispatcher* dispatcher) override;
   Response Disable() override;
 
-  std::unique_ptr<NavigationThrottle> CreateThrottleForNavigation(
-      NavigationHandle* navigation_handle);
-
   void UpdatePortals();
   bool ShouldThrottlePopups() const;
 
@@ -66,6 +63,10 @@
                      bool wait_for_debugger_on_start,
                      Maybe<bool> flatten,
                      std::unique_ptr<SetAutoAttachCallback> callback) override;
+  void AutoAttachRelated(
+      const std::string& targetId,
+      bool wait_for_debugger_on_start,
+      std::unique_ptr<AutoAttachRelatedCallback> callback) override;
   Response SetRemoteLocations(
       std::unique_ptr<protocol::Array<Target::RemoteLocation>>) override;
   Response AttachToTarget(const std::string& target_id,
@@ -124,11 +125,21 @@
   class ResponseThrottle;
 
   // TargetAutoAttacher::Delegate implementation.
-  bool AutoAttach(DevToolsAgentHost* host, bool waiting_for_debugger) override;
-  void AutoDetach(DevToolsAgentHost* host) override;
+  bool AutoAttach(TargetAutoAttacher* source,
+                  DevToolsAgentHost* host,
+                  bool waiting_for_debugger) override;
+  void AutoDetach(TargetAutoAttacher* source, DevToolsAgentHost* host) override;
   void SetAttachedTargetsOfType(
+      TargetAutoAttacher* source,
       const base::flat_set<scoped_refptr<DevToolsAgentHost>>& new_hosts,
       const std::string& type) override;
+  std::unique_ptr<NavigationThrottle> CreateThrottleForNavigation(
+      TargetAutoAttacher* auto_attacher,
+      NavigationHandle* navigation_handle) override;
+  void AutoAttacherDestroyed(TargetAutoAttacher* auto_attacher) override;
+
+  bool ShouldWaitForDebuggerOnStart(
+      NavigationRequest* navigation_request) const;
 
   Response FindSession(Maybe<std::string> session_id,
                        Maybe<std::string> target_id,
@@ -152,13 +163,17 @@
 
   TargetAutoAttacher* const auto_attacher_;
   std::unique_ptr<Target::Frontend> frontend_;
+
   bool flatten_auto_attach_ = false;
   bool auto_attach_ = false;
   bool wait_for_debugger_on_start_ = false;
+  std::map<DevToolsAgentHost*, Session*> auto_attached_sessions_;
+  base::flat_map<TargetAutoAttacher*, bool /* wait_for_debugger_on_start */>
+      auto_attach_related_targets_;
+
   bool discover_;
   bool observing_agent_hosts_ = false;
   std::map<std::string, std::unique_ptr<Session>> attached_sessions_;
-  std::map<DevToolsAgentHost*, Session*> auto_attached_sessions_;
   std::set<DevToolsAgentHost*> reported_hosts_;
   base::flat_set<std::string> dispose_on_detach_context_ids_;
   base::flat_map<std::string, net::ProxyConfig> contexts_with_overridden_proxy_;
diff --git a/content/browser/devtools/protocol_config.json b/content/browser/devtools/protocol_config.json
index 937f3dfa..f40fc94 100644
--- a/content/browser/devtools/protocol_config.json
+++ b/content/browser/devtools/protocol_config.json
@@ -94,7 +94,7 @@
             },
             {
                 "domain": "Target",
-                "async": ["createBrowserContext", "disposeBrowserContext", "setAutoAttach"]
+                "async": ["createBrowserContext", "disposeBrowserContext", "setAutoAttach", "autoAttachRelated" ]
             },
             {
                 "domain": "Tethering",
diff --git a/content/browser/devtools/render_frame_devtools_agent_host.cc b/content/browser/devtools/render_frame_devtools_agent_host.cc
index 02bd11d..e15734b 100644
--- a/content/browser/devtools/render_frame_devtools_agent_host.cc
+++ b/content/browser/devtools/render_frame_devtools_agent_host.cc
@@ -848,6 +848,10 @@
   auto_attacher_->SetRenderFrameHost(frame_host_);
 }
 
+protocol::TargetAutoAttacher* RenderFrameDevToolsAgentHost::auto_attacher() {
+  return auto_attacher_.get();
+}
+
 bool RenderFrameDevToolsAgentHost::IsChildFrame() {
   return frame_tree_node_ && frame_tree_node_->parent();
 }
diff --git a/content/browser/devtools/render_frame_devtools_agent_host.h b/content/browser/devtools/render_frame_devtools_agent_host.h
index e9634fd..64694e9 100644
--- a/content/browser/devtools/render_frame_devtools_agent_host.h
+++ b/content/browser/devtools/render_frame_devtools_agent_host.h
@@ -125,6 +125,7 @@
   void DetachSession(DevToolsSession* session) override;
   void InspectElement(RenderFrameHost* frame_host, int x, int y) override;
   void UpdateRendererChannel(bool force) override;
+  protocol::TargetAutoAttacher* auto_attacher() override;
 
   // WebContentsObserver overrides.
   void DidStartNavigation(NavigationHandle* navigation_handle) override;
diff --git a/content/browser/devtools/service_worker_devtools_agent_host.cc b/content/browser/devtools/service_worker_devtools_agent_host.cc
index 645d18f..a3afbfb 100644
--- a/content/browser/devtools/service_worker_devtools_agent_host.cc
+++ b/content/browser/devtools/service_worker_devtools_agent_host.cc
@@ -154,6 +154,10 @@
     UpdateIsAttached(false);
 }
 
+protocol::TargetAutoAttacher* ServiceWorkerDevToolsAgentHost::auto_attacher() {
+  return auto_attacher_.get();
+}
+
 void ServiceWorkerDevToolsAgentHost::WorkerReadyForInspection(
     mojo::PendingRemote<blink::mojom::DevToolsAgent> agent_remote,
     mojo::PendingReceiver<blink::mojom::DevToolsAgentHost> host_receiver) {
diff --git a/content/browser/devtools/service_worker_devtools_agent_host.h b/content/browser/devtools/service_worker_devtools_agent_host.h
index a71c9ba..b170b68 100644
--- a/content/browser/devtools/service_worker_devtools_agent_host.h
+++ b/content/browser/devtools/service_worker_devtools_agent_host.h
@@ -25,10 +25,6 @@
 
 class BrowserContext;
 
-namespace protocol {
-class TargetAutoAttacher;
-}  // namespace protocol
-
 class ServiceWorkerDevToolsAgentHost : public DevToolsAgentHostImpl,
                                        RenderProcessHostObserver {
  public:
@@ -111,6 +107,7 @@
   // DevToolsAgentHostImpl overrides.
   bool AttachSession(DevToolsSession* session, bool acquire_wake_lock) override;
   void DetachSession(DevToolsSession* session) override;
+  protocol::TargetAutoAttacher* auto_attacher() override;
 
   // RenderProcessHostObserver implementation.
   void RenderProcessHostDestroyed(RenderProcessHost* host) override;
diff --git a/content/browser/devtools/shared_worker_devtools_agent_host.cc b/content/browser/devtools/shared_worker_devtools_agent_host.cc
index 6a55c90c..0c5966e 100644
--- a/content/browser/devtools/shared_worker_devtools_agent_host.cc
+++ b/content/browser/devtools/shared_worker_devtools_agent_host.cc
@@ -159,4 +159,8 @@
   return worker_host_->GetProcessHost();
 }
 
+protocol::TargetAutoAttacher* SharedWorkerDevToolsAgentHost::auto_attacher() {
+  return auto_attacher_.get();
+}
+
 }  // namespace content
diff --git a/content/browser/devtools/shared_worker_devtools_agent_host.h b/content/browser/devtools/shared_worker_devtools_agent_host.h
index ba0d1b4..5d663d3 100644
--- a/content/browser/devtools/shared_worker_devtools_agent_host.h
+++ b/content/browser/devtools/shared_worker_devtools_agent_host.h
@@ -19,10 +19,6 @@
 
 namespace content {
 
-namespace protocol {
-class TargetAutoAttacher;
-}  // namespace protocol
-
 class SharedWorkerHost;
 
 class SharedWorkerDevToolsAgentHost : public DevToolsAgentHostImpl {
@@ -47,6 +43,7 @@
   NetworkLoaderFactoryParamsAndInfo CreateNetworkFactoryParamsForDevTools()
       override;
   RenderProcessHost* GetProcessHost() override;
+  protocol::TargetAutoAttacher* auto_attacher() override;
 
   blink::StorageKey GetStorageKey() const;
 
diff --git a/content/browser/devtools/worker_devtools_agent_host.cc b/content/browser/devtools/worker_devtools_agent_host.cc
index e796b6ca..058c27b 100644
--- a/content/browser/devtools/worker_devtools_agent_host.cc
+++ b/content/browser/devtools/worker_devtools_agent_host.cc
@@ -107,6 +107,10 @@
   // Destroying session automatically detaches in renderer.
 }
 
+protocol::TargetAutoAttacher* WorkerDevToolsAgentHost::auto_attacher() {
+  return auto_attacher_.get();
+}
+
 DedicatedWorkerHost* WorkerDevToolsAgentHost::GetDedicatedWorkerHost() {
   RenderProcessHost* process = RenderProcessHost::FromID(process_id_);
   auto* storage_partition_impl =
diff --git a/content/browser/devtools/worker_devtools_agent_host.h b/content/browser/devtools/worker_devtools_agent_host.h
index c5f5a90..c7a459f 100644
--- a/content/browser/devtools/worker_devtools_agent_host.h
+++ b/content/browser/devtools/worker_devtools_agent_host.h
@@ -16,10 +16,6 @@
 
 class DedicatedWorkerHost;
 
-namespace protocol {
-class TargetAutoAttacher;
-}  // namespace protocol
-
 // The WorkerDevToolsAgentHost is the devtools host class for dedicated workers,
 // (but not shared or service workers), and worklets. It does not have a pointer
 // to a DedicatedWorkerHost object, but in case the host is for a dedicated
@@ -57,6 +53,7 @@
   // DevToolsAgentHostImpl overrides.
   bool AttachSession(DevToolsSession* session, bool acquire_wake_lock) override;
   void DetachSession(DevToolsSession* session) override;
+  protocol::TargetAutoAttacher* auto_attacher() override;
 
   const int process_id_;
   const GURL url_;
diff --git a/content/browser/prerender/prerender_browsertest.cc b/content/browser/prerender/prerender_browsertest.cc
index a183d27..c3ffd62 100644
--- a/content/browser/prerender/prerender_browsertest.cc
+++ b/content/browser/prerender/prerender_browsertest.cc
@@ -2427,10 +2427,9 @@
     WebContentsConsoleObserver console_observer(web_contents_impl());
     console_observer.SetPattern(kConsolePattern);
     test::PrerenderHostRegistryObserver observer(*web_contents_impl());
+    test::PrerenderHostObserver host_observer(*web_contents_impl(),
+                                              disallowed_url);
     AddPrerenderAsync(disallowed_url);
-    observer.WaitForTrigger(disallowed_url);
-    int host_id = GetHostForUrl(disallowed_url);
-    test::PrerenderHostObserver host_observer(*web_contents_impl(), host_id);
     console_observer.Wait();
     EXPECT_EQ(1u, console_observer.messages().size());
     EXPECT_EQ(GetRequestCount(disallowed_url), 0);
diff --git a/content/browser/prerender/prerender_host.cc b/content/browser/prerender/prerender_host.cc
index 3b936748..6b59cd7c 100644
--- a/content/browser/prerender/prerender_host.cc
+++ b/content/browser/prerender/prerender_host.cc
@@ -67,30 +67,6 @@
   std::unique_ptr<StoredPage> page;
 };
 
-bool AreHttpRequestHeadersCompatible(
-    const std::string& potential_activation_headers_str,
-    const std::string& prerender_headers_str) {
-  net::HttpRequestHeaders prerender_headers;
-  prerender_headers.AddHeadersFromString(prerender_headers_str);
-
-  net::HttpRequestHeaders potential_activation_headers;
-  potential_activation_headers.AddHeadersFromString(
-      potential_activation_headers_str);
-
-  // `potential_activation_headers` are observed before the User-Agent override
-  // while `prerender_headers` are observed after. As a workaround, remove
-  // User-Agent matching from consideration so that activation works with
-  // DevTools mobile emulation.
-  // TODO(https://crbug.com/1238578): Adjust when the headers are observed so we
-  // don't need this workaround.
-  prerender_headers.RemoveHeader(net::HttpRequestHeaders::kUserAgent);
-  potential_activation_headers.RemoveHeader(
-      net::HttpRequestHeaders::kUserAgent);
-
-  return prerender_headers.ToString() ==
-         potential_activation_headers.ToString();
-}
-
 }  // namespace
 
 class PrerenderHost::PageHolder : public FrameTree::Delegate,
@@ -389,8 +365,6 @@
   // synchronously.
   DCHECK_GE(navigation_request->state(),
             NavigationRequest::WAITING_FOR_RENDERER_RESPONSE);
-  begin_params_ = navigation_request->begin_params().Clone();
-  common_params_ = navigation_request->common_params().Clone();
   return true;
 }
 
@@ -546,8 +520,7 @@
     return false;
   }
 
-  if (!AreHttpRequestHeadersCompatible(potential_activation.headers,
-                                       begin_params_->headers)) {
+  if (potential_activation.headers != begin_params_->headers) {
     return false;
   }
 
@@ -802,9 +775,11 @@
   return initial_navigation_id_;
 }
 
-void PrerenderHost::SetInitialNavigationId(int64_t navigation_id) {
+void PrerenderHost::SetInitialNavigation(NavigationRequest* navigation) {
   DCHECK(!initial_navigation_id_.has_value());
-  initial_navigation_id_ = navigation_id;
+  initial_navigation_id_ = navigation->GetNavigationId();
+  begin_params_ = navigation->begin_params().Clone();
+  common_params_ = navigation->common_params().Clone();
 }
 
 void PrerenderHost::Cancel(FinalStatus status) {
diff --git a/content/browser/prerender/prerender_host.h b/content/browser/prerender/prerender_host.h
index 0ca56b7..ed319296 100644
--- a/content/browser/prerender/prerender_host.h
+++ b/content/browser/prerender/prerender_host.h
@@ -139,11 +139,11 @@
   void AddObserver(Observer* observer);
   void RemoveObserver(Observer* observer);
 
-  // The initial navigation id is set by the PrerenderNavigationThrottle
+  // The initial navigation is set by the PrerenderNavigationThrottle
   // when the PrerenderHost is first navigated, which happens immediately
   // after creation.
+  void SetInitialNavigation(NavigationRequest* navigation);
   absl::optional<int64_t> GetInitialNavigationId() const;
-  void SetInitialNavigationId(int64_t navigation_id);
 
   url::Origin initiator_origin() const { return initiator_origin_; }
 
diff --git a/content/browser/prerender/prerender_host_registry_unittest.cc b/content/browser/prerender/prerender_host_registry_unittest.cc
index 7c2d7e3..99ead45 100644
--- a/content/browser/prerender/prerender_host_registry_unittest.cc
+++ b/content/browser/prerender/prerender_host_registry_unittest.cc
@@ -711,18 +711,7 @@
        CompareInitialAndActivationBeginParams_Headers) {
   EXPECT_FALSE(CheckIsActivatedForParams(
       base::BindLambdaForTesting([](NavigationSimulatorImpl* navigation) {
-        navigation->set_request_headers("X-Foo: Test");
-      })));
-}
-
-// Tests that the User-Agent header is ignored when comparing request headers.
-//
-// TODO(https://crbug.com/1213299): Check the User-Agent header in a way that
-// works with the DevTools override.
-TEST_F(PrerenderHostRegistryTest, UserAgentIsIgnoredForParamMatching) {
-  EXPECT_TRUE(CheckIsActivatedForParams(
-      base::BindLambdaForTesting([](NavigationSimulatorImpl* navigation) {
-        navigation->set_request_headers("User-Agent: MyBrowser");
+        navigation->set_request_headers("User-Agent: Test");
       })));
 }
 
diff --git a/content/browser/prerender/prerender_navigation_throttle.cc b/content/browser/prerender/prerender_navigation_throttle.cc
index d4ded8d..785aaf5 100644
--- a/content/browser/prerender/prerender_navigation_throttle.cc
+++ b/content/browser/prerender/prerender_navigation_throttle.cc
@@ -76,8 +76,8 @@
     // will later cancel the navigation in Will*Request(). Just do nothing
     // until then.
   } else {
-    prerender_host->SetInitialNavigationId(
-        navigation_handle->GetNavigationId());
+    prerender_host->SetInitialNavigation(
+        static_cast<NavigationRequest*>(navigation_handle));
   }
 }
 
diff --git a/content/browser/service_sandbox_type.h b/content/browser/service_sandbox_type.h
index 4c60769..a226d27 100644
--- a/content/browser/service_sandbox_type.h
+++ b/content/browser/service_sandbox_type.h
@@ -44,30 +44,6 @@
                                    : sandbox::policy::SandboxType::kNoSandbox;
 }
 
-// storage::mojom::StorageService
-namespace storage {
-namespace mojom {
-class StorageService;
-}
-}  // namespace storage
-template <>
-inline sandbox::policy::SandboxType
-content::GetServiceSandboxType<storage::mojom::StorageService>() {
-  return sandbox::policy::SandboxType::kUtility;
-}
-
-// tracing::mojom::TracingService
-namespace tracing {
-namespace mojom {
-class TracingService;
-}
-}  // namespace tracing
-template <>
-inline sandbox::policy::SandboxType
-content::GetServiceSandboxType<tracing::mojom::TracingService>() {
-  return sandbox::policy::SandboxType::kUtility;
-}
-
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING) && BUILDFLAG(IS_CHROMEOS_ASH)
 // shape_detection::mojom::ShapeDetectionService
 namespace shape_detection {
diff --git a/content/browser/storage_partition_impl.cc b/content/browser/storage_partition_impl.cc
index 5bc81a08..5edf24182 100644
--- a/content/browser/storage_partition_impl.cc
+++ b/content/browser/storage_partition_impl.cc
@@ -72,7 +72,6 @@
 #include "content/browser/push_messaging/push_messaging_context.h"
 #include "content/browser/quota/quota_context.h"
 #include "content/browser/renderer_host/frame_tree_node.h"
-#include "content/browser/service_sandbox_type.h"
 #include "content/browser/service_worker/service_worker_container_host.h"
 #include "content/browser/service_worker/service_worker_context_wrapper.h"
 #include "content/browser/ssl/ssl_client_auth_handler.h"
diff --git a/content/browser/tracing/tracing_service_controller.cc b/content/browser/tracing/tracing_service_controller.cc
index c25c871..588341ca 100644
--- a/content/browser/tracing/tracing_service_controller.cc
+++ b/content/browser/tracing/tracing_service_controller.cc
@@ -8,7 +8,6 @@
 
 #include "base/task/thread_pool.h"
 #include "base/time/time.h"
-#include "content/browser/service_sandbox_type.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/service_process_host.h"
@@ -18,6 +17,7 @@
 #include "mojo/public/cpp/bindings/self_owned_receiver.h"
 #include "services/tracing/public/cpp/traced_process.h"
 #include "services/tracing/public/cpp/tracing_features.h"
+#include "services/tracing/public/mojom/tracing_service.mojom.h"
 #include "services/tracing/tracing_service.h"
 
 namespace content {
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index 3170d101..aac8edf2 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -1162,6 +1162,10 @@
   return GetController().GetBrowserContext();
 }
 
+base::WeakPtr<WebContents> WebContentsImpl::GetWeakPtr() {
+  return weak_factory_.GetWeakPtr();
+}
+
 const GURL& WebContentsImpl::GetURL() {
   return GetVisibleURL();
 }
diff --git a/content/browser/web_contents/web_contents_impl.h b/content/browser/web_contents/web_contents_impl.h
index 01ac163d..d6582d6 100644
--- a/content/browser/web_contents/web_contents_impl.h
+++ b/content/browser/web_contents/web_contents_impl.h
@@ -325,6 +325,7 @@
   void SetDelegate(WebContentsDelegate* delegate) override;
   NavigationControllerImpl& GetController() override;
   BrowserContext* GetBrowserContext() override;
+  base::WeakPtr<WebContents> GetWeakPtr() override;
   const GURL& GetURL() override;
   const GURL& GetVisibleURL() override;
   const GURL& GetLastCommittedURL() override;
diff --git a/content/browser/webauth/authenticator_common.cc b/content/browser/webauth/authenticator_common.cc
index c796efc..5d798d4 100644
--- a/content/browser/webauth/authenticator_common.cc
+++ b/content/browser/webauth/authenticator_common.cc
@@ -1098,6 +1098,7 @@
 void AuthenticatorCommon::GetAssertion(
     url::Origin caller_origin,
     blink::mojom::PublicKeyCredentialRequestOptionsPtr options,
+    blink::mojom::PaymentOptionsPtr payment,
     blink::mojom::Authenticator::GetAssertionCallback callback) {
   if (request_) {
     if (WebAuthRequestSecurityChecker::OriginIsCryptoTokenExtension(
@@ -1168,7 +1169,7 @@
     client_data_json_ = BuildClientDataJson(
         ClientDataRequestType::kU2fSign, options->relying_party_id,
         options->challenge, /*is_cross_origin=*/false);
-  } else if (options->payment) {
+  } else if (payment) {
     auto* web_contents = WebContents::FromRenderFrameHost(GetRenderFrameHost());
     if (!web_contents) {
       CompleteGetAssertionRequest(
@@ -1179,7 +1180,7 @@
         url::Origin::Create(web_contents->GetLastCommittedURL());
     client_data_json_ = BuildClientDataJson(
         ClientDataRequestType::kPaymentGet, caller_origin_.Serialize(),
-        options->challenge, is_cross_origin, std::move(options->payment),
+        options->challenge, is_cross_origin, std::move(payment),
         relying_party_id_, top_origin.Serialize());
   } else {
     client_data_json_ = BuildClientDataJson(
diff --git a/content/browser/webauth/authenticator_common.h b/content/browser/webauth/authenticator_common.h
index 4c1d216..305444a 100644
--- a/content/browser/webauth/authenticator_common.h
+++ b/content/browser/webauth/authenticator_common.h
@@ -70,13 +70,16 @@
 
   // This is not-quite an implementation of blink::mojom::Authenticator. The
   // first two functions take the caller's origin explicitly. This allows the
-  // caller origin to be overridden if needed.
+  // caller origin to be overridden if needed. `GetAssertion()` also takes the
+  // optional `payment` to add to "clientDataJson" after the browser displays
+  // the payment confirmation dialog to the user.
   void MakeCredential(
       url::Origin caller_origin,
       blink::mojom::PublicKeyCredentialCreationOptionsPtr options,
       blink::mojom::Authenticator::MakeCredentialCallback callback);
   void GetAssertion(url::Origin caller_origin,
                     blink::mojom::PublicKeyCredentialRequestOptionsPtr options,
+                    blink::mojom::PaymentOptionsPtr payment,
                     blink::mojom::Authenticator::GetAssertionCallback callback);
   void IsUserVerifyingPlatformAuthenticatorAvailable(
       blink::mojom::Authenticator::
diff --git a/content/browser/webauth/authenticator_impl.cc b/content/browser/webauth/authenticator_impl.cc
index 78b654e..512bf2b 100644
--- a/content/browser/webauth/authenticator_impl.cc
+++ b/content/browser/webauth/authenticator_impl.cc
@@ -59,7 +59,7 @@
     blink::mojom::PublicKeyCredentialRequestOptionsPtr options,
     GetAssertionCallback callback) {
   authenticator_common_->GetAssertion(origin(), std::move(options),
-                                      std::move(callback));
+                                      /*payment=*/nullptr, std::move(callback));
 }
 
 void AuthenticatorImpl::IsUserVerifyingPlatformAuthenticatorAvailable(
diff --git a/content/browser/webauth/webauth_browsertest.cc b/content/browser/webauth/webauth_browsertest.cc
index e1160616..7b5d2bb 100644
--- a/content/browser/webauth/webauth_browsertest.cc
+++ b/content/browser/webauth/webauth_browsertest.cc
@@ -577,7 +577,7 @@
         std::vector<device::CableDiscoveryData>(), /*prf=*/false,
         /*prf_inputs=*/std::vector<blink::mojom::PRFValuesPtr>(),
         /*large_blob_read=*/false, /*large_blob_write=*/absl::nullopt,
-        /*get_cred_blob=*/false, /*payment=*/nullptr);
+        /*get_cred_blob=*/false);
     return mojo_options;
   }
 
diff --git a/content/public/android/java/src/org/chromium/content/browser/ChildProcessLauncherHelperImpl.java b/content/public/android/java/src/org/chromium/content/browser/ChildProcessLauncherHelperImpl.java
index 8b5c9ef9..3b82e74f 100644
--- a/content/public/android/java/src/org/chromium/content/browser/ChildProcessLauncherHelperImpl.java
+++ b/content/public/android/java/src/org/chromium/content/browser/ChildProcessLauncherHelperImpl.java
@@ -518,7 +518,6 @@
         // Note there is no guarantee that connection lost has happened. However ChildProcessRanking
         // is not thread safe, so this is the best we can do.
         int reverseRank = getReverseRankWhenConnectionLost();
-        int bindingCounts[] = connection.remainingBindingStateCountsCurrentOrWhenDied();
         String exceptionString = connection.getExceptionDuringInit();
         if (exceptionString != null && !mReportedException) {
             mReportedException = true;
@@ -527,9 +526,7 @@
         }
         ChildProcessLauncherHelperImplJni.get().setTerminationInfo(terminationInfoPtr,
                 connection.bindingStateCurrentOrWhenDied(), connection.isKilledByUs(),
-                connection.hasCleanExit(), exceptionString != null,
-                bindingCounts[ChildBindingState.STRONG], bindingCounts[ChildBindingState.MODERATE],
-                bindingCounts[ChildBindingState.WAIVED], reverseRank);
+                connection.hasCleanExit(), exceptionString != null, reverseRank);
         LauncherThread.post(() -> mLauncher.stop());
     }
 
@@ -750,7 +747,7 @@
 
         void setTerminationInfo(long termiantionInfoPtr, @ChildBindingState int bindingState,
                 boolean killedByUs, boolean cleanExit, boolean exceptionDuringInit,
-                int remainingStrong, int remainingModerate, int remainingWaived, int reverseRank);
+                int reverseRank);
 
         boolean serviceGroupImportanceEnabled();
     }
diff --git a/content/public/browser/child_process_termination_info.h b/content/public/browser/child_process_termination_info.h
index 8195864..94965dd7 100644
--- a/content/public/browser/child_process_termination_info.h
+++ b/content/public/browser/child_process_termination_info.h
@@ -52,11 +52,6 @@
   // True if the child shut itself down cleanly by quitting the main runloop.
   bool clean_exit = false;
 
-  // Counts of remaining child processes with corresponding binding.
-  int remaining_process_with_strong_binding = 0;
-  int remaining_process_with_moderate_binding = 0;
-  int remaining_process_with_waived_binding = 0;
-
   // Eg lowest ranked process at time of death should have value 0.
   // Valid values are non-negative.
   // -1 means could not be obtained due to threading restrictions.
diff --git a/content/public/browser/ssl_host_state_delegate.h b/content/public/browser/ssl_host_state_delegate.h
index e3f07d0..941a2ab 100644
--- a/content/public/browser/ssl_host_state_delegate.h
+++ b/content/public/browser/ssl_host_state_delegate.h
@@ -71,22 +71,24 @@
                                          int child_id,
                                          InsecureContentType content_type) = 0;
 
-  // Allowlists site so it can be loaded over HTTPS when HTTPS-Only Mode is
+  // Allowlists site so it can be loaded over HTTP when HTTPS-First Mode is
   // enabled.
-  virtual void AllowHttpForHost(const std::string& host) = 0;
+  virtual void AllowHttpForHost(const std::string& host,
+                                WebContents* web_contents) = 0;
 
-  // Returns whether site is allowed to load over HTTPS when HTTPS-Only Mode is
+  // Returns whether site is allowed to load over HTTP when HTTPS-First Mode is
   // enabled.
-  virtual bool IsHttpAllowedForHost(const std::string& host) = 0;
+  virtual bool IsHttpAllowedForHost(const std::string& host,
+                                    WebContents* web_contents) = 0;
 
   // Revokes all SSL certificate error allow exceptions made by the user for
   // |host|.
   virtual void RevokeUserAllowExceptions(const std::string& host) = 0;
 
-  // Returns whether the user has allowed a certificate error exception for
-  // |host|. This does not mean that *all* certificate errors are allowed, just
-  // that there exists an exception. To see if a particular certificate and
-  // error combination exception is allowed, use QueryPolicy().
+  // Returns whether the user has allowed a certificate error exception or
+  // HTTP exception for |host|. This does not mean that *all* certificate errors
+  // are allowed, just that there exists an exception. To see if a particular
+  // certificate and error combination exception is allowed, use QueryPolicy().
   virtual bool HasAllowException(const std::string& host,
                                  WebContents* web_contents) = 0;
 
diff --git a/content/public/browser/web_contents.h b/content/public/browser/web_contents.h
index 0ccd96c..91c8bb5 100644
--- a/content/public/browser/web_contents.h
+++ b/content/public/browser/web_contents.h
@@ -16,6 +16,7 @@
 #include "base/callback_helpers.h"
 #include "base/compiler_specific.h"
 #include "base/memory/scoped_refptr.h"
+#include "base/memory/weak_ptr.h"
 #include "base/process/kill.h"
 #include "base/supports_user_data.h"
 #include "base/time/time.h"
@@ -300,6 +301,9 @@
   // NavigationController).
   virtual content::BrowserContext* GetBrowserContext() = 0;
 
+  // Returns a weak pointer.
+  virtual base::WeakPtr<WebContents> GetWeakPtr() = 0;
+
   // Gets the URL that is currently being displayed, if there is one.
   // This method is deprecated. DO NOT USE! Pick either |GetVisibleURL| or
   // |GetLastCommittedURL| as appropriate.
diff --git a/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt
index 54a0421..3d2a74c 100644
--- a/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt
@@ -741,6 +741,8 @@
 
 crbug.com/1191030 [ android android-pixel-4 ] conformance/textures/misc/video-rotation.html [ Failure ]
 
+crbug.com/1239079 [ android no-passthrough ] conformance2/transform_feedback/too-small-buffers.html [ Failure ]
+
 # Android ANGLE GLES
 # TODO(crbug.com/979444): once this is passing on the passthrough
 # command decoder, simplify the RetryOnFailure expectation at the top
diff --git a/content/test/mock_ssl_host_state_delegate.cc b/content/test/mock_ssl_host_state_delegate.cc
index f02d074c..17af93d 100644
--- a/content/test/mock_ssl_host_state_delegate.cc
+++ b/content/test/mock_ssl_host_state_delegate.cc
@@ -62,11 +62,13 @@
          hosts_ran_insecure_content_.end();
 }
 
-void MockSSLHostStateDelegate::AllowHttpForHost(const std::string& host) {
+void MockSSLHostStateDelegate::AllowHttpForHost(const std::string& host,
+                                                WebContents* web_contents) {
   allow_http_hosts_.insert(host);
 }
 
-bool MockSSLHostStateDelegate::IsHttpAllowedForHost(const std::string& host) {
+bool MockSSLHostStateDelegate::IsHttpAllowedForHost(const std::string& host,
+                                                    WebContents* web_contents) {
   return base::Contains(allow_http_hosts_, host);
 }
 
diff --git a/content/test/mock_ssl_host_state_delegate.h b/content/test/mock_ssl_host_state_delegate.h
index 87dac48..72925b6 100644
--- a/content/test/mock_ssl_host_state_delegate.h
+++ b/content/test/mock_ssl_host_state_delegate.h
@@ -35,9 +35,11 @@
                                  int child_id,
                                  InsecureContentType content_type) override;
 
-  void AllowHttpForHost(const std::string& host) override;
+  void AllowHttpForHost(const std::string& host,
+                        WebContents* web_contents) override;
 
-  bool IsHttpAllowedForHost(const std::string& host) override;
+  bool IsHttpAllowedForHost(const std::string& host,
+                            WebContents* web_contents) override;
 
   void RevokeUserAllowExceptions(const std::string& host) override;
 
diff --git a/device/fido/cable/websocket_adapter.cc b/device/fido/cable/websocket_adapter.cc
index 817863e..0e3ffdf 100644
--- a/device/fido/cable/websocket_adapter.cc
+++ b/device/fido/cable/websocket_adapter.cc
@@ -6,6 +6,7 @@
 
 #include "base/callback_helpers.h"
 #include "base/logging.h"
+#include "base/metrics/histogram_functions.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
 #include "components/device_event_log/device_event_log.h"
@@ -66,6 +67,9 @@
   LOG(ERROR) << "Tunnel server connection failed: " << message << " "
              << net_error << " " << response_code;
 
+  base::UmaHistogramSparse("WebAuthentication.CableV2.TunnelServerError",
+                           response_code > 0 ? response_code : net_error);
+
   if (response_code != net::HTTP_GONE) {
     // The callback will be cleaned up when the pipe disconnects.
     return;
diff --git a/extensions/browser/api/test/BUILD.gn b/extensions/browser/api/test/BUILD.gn
index 41db5ed5..f79322b7 100644
--- a/extensions/browser/api/test/BUILD.gn
+++ b/extensions/browser/api/test/BUILD.gn
@@ -11,10 +11,14 @@
   sources = [
     "test_api.cc",
     "test_api.h",
+    "test_api_observer.cc",
+    "test_api_observer.h",
+    "test_api_observer_registry.cc",
+    "test_api_observer_registry.h",
   ]
 
   deps = [
-    "//content/public/browser",
+    "//base",
     "//content/public/common",
     "//extensions/common",
     "//extensions/common/api",
diff --git a/extensions/browser/api/test/test_api.cc b/extensions/browser/api/test/test_api.cc
index 07113d5..8841de8 100644
--- a/extensions/browser/api/test/test_api.cc
+++ b/extensions/browser/api/test/test_api.cc
@@ -8,11 +8,10 @@
 
 #include "base/command_line.h"
 #include "base/memory/singleton.h"
-#include "content/public/browser/notification_service.h"
 #include "content/public/common/content_switches.h"
+#include "extensions/browser/api/test/test_api_observer_registry.h"
 #include "extensions/browser/extension_function_dispatcher.h"
 #include "extensions/browser/extension_system.h"
-#include "extensions/browser/notification_types.h"
 #include "extensions/common/api/test.h"
 
 namespace {
@@ -50,10 +49,7 @@
 TestNotifyPassFunction::~TestNotifyPassFunction() {}
 
 ExtensionFunction::ResponseAction TestNotifyPassFunction::Run() {
-  content::NotificationService::current()->Notify(
-      extensions::NOTIFICATION_EXTENSION_TEST_PASSED,
-      content::Source<content::BrowserContext>(dispatcher()->browser_context()),
-      content::NotificationService::NoDetails());
+  TestApiObserverRegistry::GetInstance()->NotifyTestPassed(browser_context());
   return RespondNow(NoArguments());
 }
 
@@ -63,10 +59,8 @@
   std::unique_ptr<NotifyFail::Params> params(
       NotifyFail::Params::Create(*args_));
   EXTENSION_FUNCTION_VALIDATE(params.get());
-  content::NotificationService::current()->Notify(
-      extensions::NOTIFICATION_EXTENSION_TEST_FAILED,
-      content::Source<content::BrowserContext>(dispatcher()->browser_context()),
-      content::Details<std::string>(&params->message));
+  TestApiObserverRegistry::GetInstance()->NotifyTestFailed(
+      browser_context(), params->message);
   return RespondNow(NoArguments());
 }
 
@@ -85,13 +79,9 @@
   std::unique_ptr<PassMessage::Params> params(
       PassMessage::Params::Create(*args_));
   EXTENSION_FUNCTION_VALIDATE(params.get());
-  bool listener_will_respond = false;
-  std::pair<std::string, bool*> details(params->message,
-                                        &listener_will_respond);
-  content::NotificationService::current()->Notify(
-      extensions::NOTIFICATION_EXTENSION_TEST_MESSAGE,
-      content::Source<TestSendMessageFunction>(this),
-      content::Details<std::pair<std::string, bool*>>(&details));
+  bool listener_will_respond =
+      TestApiObserverRegistry::GetInstance()->NotifyTestMessage(
+          this, params->message);
   // If none of the listeners intend to respond, or one has already responded,
   // finish the function. We always reply to the message, even if it's just an
   // empty string.
diff --git a/extensions/browser/api/test/test_api_observer.cc b/extensions/browser/api/test/test_api_observer.cc
new file mode 100644
index 0000000..e439a3e
--- /dev/null
+++ b/extensions/browser/api/test/test_api_observer.cc
@@ -0,0 +1,14 @@
+// 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.
+
+#include "extensions/browser/api/test/test_api_observer.h"
+
+namespace extensions {
+
+bool TestApiObserver::OnTestMessage(TestSendMessageFunction* function,
+                                    const std::string& message) {
+  return false;
+}
+
+}  // namespace extensions
diff --git a/extensions/browser/api/test/test_api_observer.h b/extensions/browser/api/test/test_api_observer.h
new file mode 100644
index 0000000..1d59899
--- /dev/null
+++ b/extensions/browser/api/test/test_api_observer.h
@@ -0,0 +1,39 @@
+// 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 EXTENSIONS_BROWSER_API_TEST_TEST_API_OBSERVER_H_
+#define EXTENSIONS_BROWSER_API_TEST_TEST_API_OBSERVER_H_
+
+#include <string>
+
+#include "base/observer_list_types.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+
+class TestSendMessageFunction;
+
+// Allows browser-side test code to be notified of calls to chrome.test.*
+// functions from a test extension.
+class TestApiObserver : public base::CheckedObserver {
+ public:
+  // Called on chrome.test.notifyPass.
+  virtual void OnTestPassed(content::BrowserContext* browser_context) {}
+
+  // Called on chrome.test.notifyFail.
+  virtual void OnTestFailed(content::BrowserContext* browser_context,
+                            const std::string& message) {}
+
+  // Called on chrome.test.sendMessage.
+  // If the observer will reply to |function|, returns true.
+  virtual bool OnTestMessage(TestSendMessageFunction* function,
+                             const std::string& message);
+};
+
+}  // namespace extensions
+
+#endif  // EXTENSIONS_BROWSER_API_TEST_TEST_API_OBSERVER_H_
diff --git a/extensions/browser/api/test/test_api_observer_registry.cc b/extensions/browser/api/test/test_api_observer_registry.cc
new file mode 100644
index 0000000..63f4c4f
--- /dev/null
+++ b/extensions/browser/api/test/test_api_observer_registry.cc
@@ -0,0 +1,59 @@
+// 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.
+
+#include "extensions/browser/api/test/test_api_observer_registry.h"
+
+#include <ostream>
+
+#include "base/check.h"
+
+namespace extensions {
+
+TestApiObserverRegistry::TestApiObserverRegistry() = default;
+TestApiObserverRegistry::~TestApiObserverRegistry() = default;
+
+// static
+TestApiObserverRegistry* TestApiObserverRegistry::GetInstance() {
+  static base::NoDestructor<TestApiObserverRegistry> registry;
+  return registry.get();
+}
+
+void TestApiObserverRegistry::NotifyTestPassed(
+    content::BrowserContext* browser_context) {
+  for (auto& observer : observers_) {
+    observer.OnTestPassed(browser_context);
+  }
+}
+
+void TestApiObserverRegistry::NotifyTestFailed(
+    content::BrowserContext* browser_context,
+    const std::string& message) {
+  for (auto& observer : observers_) {
+    observer.OnTestFailed(browser_context, message);
+  }
+}
+
+bool TestApiObserverRegistry::NotifyTestMessage(
+    TestSendMessageFunction* function,
+    const std::string& message) {
+  bool any_listener_will_respond = false;
+  for (auto& observer : observers_) {
+    bool listener_will_respond = observer.OnTestMessage(function, message);
+    DCHECK(!any_listener_will_respond || !listener_will_respond)
+        << "Only one listener may reply.";
+    any_listener_will_respond =
+        any_listener_will_respond || listener_will_respond;
+  }
+  return any_listener_will_respond;
+}
+
+void TestApiObserverRegistry::AddObserver(TestApiObserver* observer) {
+  observers_.AddObserver(observer);
+}
+
+void TestApiObserverRegistry::RemoveObserver(TestApiObserver* observer) {
+  observers_.RemoveObserver(observer);
+}
+
+}  // namespace extensions
diff --git a/extensions/browser/api/test/test_api_observer_registry.h b/extensions/browser/api/test/test_api_observer_registry.h
new file mode 100644
index 0000000..a96b8cb
--- /dev/null
+++ b/extensions/browser/api/test/test_api_observer_registry.h
@@ -0,0 +1,56 @@
+// 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 EXTENSIONS_BROWSER_API_TEST_TEST_API_OBSERVER_REGISTRY_H_
+#define EXTENSIONS_BROWSER_API_TEST_TEST_API_OBSERVER_REGISTRY_H_
+
+#include <string>
+
+#include "base/no_destructor.h"
+#include "base/observer_list.h"
+#include "extensions/browser/api/test/test_api_observer.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+
+class TestSendMessageFunction;
+
+class TestApiObserverRegistry {
+ public:
+  TestApiObserverRegistry(const TestApiObserverRegistry&) = delete;
+  TestApiObserverRegistry& operator=(const TestApiObserverRegistry&) = delete;
+
+  // Unfortunately, most existing observers do not specify a BrowserContext for
+  // which they intend to listen for events, so we resort to having a global
+  // registry. Observers in tests where multiple BrowserContexts exist should
+  // check which BrowserContext is associated with an event.
+  static TestApiObserverRegistry* GetInstance();
+
+  void NotifyTestPassed(content::BrowserContext* browser_context);
+  void NotifyTestFailed(content::BrowserContext* browser_context,
+                        const std::string& message);
+
+  // Returns true if one of the TestApiObservers has indicated that it will
+  // respond to the message.
+  bool NotifyTestMessage(TestSendMessageFunction* function,
+                         const std::string& message);
+
+  void AddObserver(TestApiObserver* observer);
+  void RemoveObserver(TestApiObserver* observer);
+
+ private:
+  friend class base::NoDestructor<TestApiObserverRegistry>;
+
+  TestApiObserverRegistry();
+  ~TestApiObserverRegistry();
+
+  base::ObserverList<TestApiObserver> observers_;
+};
+
+}  // namespace extensions
+
+#endif  // EXTENSIONS_BROWSER_API_TEST_TEST_API_OBSERVER_REGISTRY_H_
diff --git a/extensions/browser/notification_types.h b/extensions/browser/notification_types.h
index d5c724d..8cfbdc3b 100644
--- a/extensions/browser/notification_types.h
+++ b/extensions/browser/notification_types.h
@@ -92,19 +92,6 @@
   // TODO(https://crbug.com/1174744): Remove.
   NOTIFICATION_EXTENSION_BACKGROUND_PAGE_READY,
 
-  // Sent by an extension to notify the browser about the results of a unit
-  // test.
-  // TODO(https://crbug.com/1174745): Remove.
-  NOTIFICATION_EXTENSION_TEST_PASSED,
-  NOTIFICATION_EXTENSION_TEST_FAILED,
-
-  // Sent by extension test javascript code, typically in a browser test. The
-  // sender is a std::string representing the extension id, and the details
-  // are a std::string with some message. This is particularly useful when you
-  // want to have C++ code wait for javascript code to do something.
-  // TODO(https://crbug.com/1174746): Remove.
-  NOTIFICATION_EXTENSION_TEST_MESSAGE,
-
   // Sent when an bookmarks extensions API function was successfully invoked.
   // The source is the id of the extension that invoked the function, and the
   // details are a pointer to the const BookmarksFunction in question.
diff --git a/extensions/test/extension_test_message_listener.cc b/extensions/test/extension_test_message_listener.cc
index a2a0eb88..c560ab6 100644
--- a/extensions/test/extension_test_message_listener.cc
+++ b/extensions/test/extension_test_message_listener.cc
@@ -7,26 +7,21 @@
 #include "base/run_loop.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
-#include "content/public/browser/notification_service.h"
-#include "content/public/browser/notification_source.h"
 #include "content/public/test/test_utils.h"
 #include "extensions/browser/api/test/test_api.h"
-#include "extensions/browser/notification_types.h"
 
 ExtensionTestMessageListener::ExtensionTestMessageListener(
     const std::string& expected_message,
     bool will_reply)
     : expected_message_(expected_message), will_reply_(will_reply) {
-  registrar_.Add(this,
-                 extensions::NOTIFICATION_EXTENSION_TEST_MESSAGE,
-                 content::NotificationService::AllSources());
+  test_api_observation_.Observe(
+      extensions::TestApiObserverRegistry::GetInstance());
 }
 
 ExtensionTestMessageListener::ExtensionTestMessageListener(bool will_reply)
     : will_reply_(will_reply) {
-  registrar_.Add(this,
-                 extensions::NOTIFICATION_EXTENSION_TEST_MESSAGE,
-                 content::NotificationService::AllSources());
+  test_api_observation_.Observe(
+      extensions::TestApiObserverRegistry::GetInstance());
 }
 
 ExtensionTestMessageListener::~ExtensionTestMessageListener() {
@@ -71,17 +66,11 @@
   extension_id_for_message_.clear();
 }
 
-void ExtensionTestMessageListener::Observe(
-    int type,
-    const content::NotificationSource& source,
-    const content::NotificationDetails& details) {
-  DCHECK_EQ(extensions::NOTIFICATION_EXTENSION_TEST_MESSAGE, type);
-
+bool ExtensionTestMessageListener::OnTestMessage(
+    extensions::TestSendMessageFunction* function,
+    const std::string& message) {
   // Return immediately if we're already satisfied or it's not the right
   // extension.
-  extensions::TestSendMessageFunction* function =
-      content::Source<extensions::TestSendMessageFunction>(source).ptr();
-
   std::string sender_extension_id;
   if (function->extension())
     sender_extension_id = function->extension_id();
@@ -89,17 +78,14 @@
   if (satisfied_ ||
       (!extension_id_.empty() && sender_extension_id != extension_id_) ||
       (browser_context_ && function->browser_context() != browser_context_)) {
-    return;
+    return false;
   }
 
   // We should have an empty message if we're not already satisfied.
   CHECK(message_.empty());
   CHECK(extension_id_for_message_.empty());
 
-  std::pair<std::string, bool*>* message_details =
-      content::Details<std::pair<std::string, bool*>>(details).ptr();
-  const std::string& message = message_details->first;
-  bool* listener_will_respond = message_details->second;
+  bool listener_will_respond = false;
 
   const bool wait_for_any_message = !expected_message_;
   const bool is_expected_message =
@@ -115,9 +101,7 @@
     had_user_gesture_ = function->user_gesture();
 
     if (will_reply_) {
-      DCHECK(!*listener_will_respond) << "Only one listener may reply.";
-
-      *listener_will_respond = true;
+      listener_will_respond = true;
       function_ = function;
     }
 
@@ -129,4 +113,6 @@
     if (on_repeatedly_satisfied_)
       on_repeatedly_satisfied_.Run(message);
   }
+
+  return listener_will_respond;
 }
diff --git a/extensions/test/extension_test_message_listener.h b/extensions/test/extension_test_message_listener.h
index 6b876277..01335c8 100644
--- a/extensions/test/extension_test_message_listener.h
+++ b/extensions/test/extension_test_message_listener.h
@@ -10,8 +10,9 @@
 #include "base/callback.h"
 #include "base/compiler_specific.h"
 #include "base/memory/ref_counted.h"
-#include "content/public/browser/notification_observer.h"
-#include "content/public/browser/notification_registrar.h"
+#include "base/scoped_observation.h"
+#include "extensions/browser/api/test/test_api_observer.h"
+#include "extensions/browser/api/test/test_api_observer_registry.h"
 #include "extensions/common/extension_id.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
@@ -99,7 +100,7 @@
 // either make it a local variable inside your test body, or if it's a member
 // variable of a ExtensionBrowserTest subclass, override the
 // BrowserTestBase::TearDownOnMainThread() method and clean it up there.
-class ExtensionTestMessageListener : public content::NotificationObserver {
+class ExtensionTestMessageListener : public extensions::TestApiObserver {
  public:
   // We immediately start listening for |expected_message|.
   ExtensionTestMessageListener(const std::string& expected_message,
@@ -163,12 +164,9 @@
   bool had_user_gesture() const { return had_user_gesture_; }
 
  private:
-  // Implements the content::NotificationObserver interface.
-  void Observe(int type,
-               const content::NotificationSource& source,
-               const content::NotificationDetails& details) override;
-
-  content::NotificationRegistrar registrar_;
+  // extensions::TestApiObserver:
+  bool OnTestMessage(extensions::TestSendMessageFunction* function,
+                     const std::string& message) override;
 
   // The message we're expecting. If empty, we will wait for any message,
   // regardless of contents.
@@ -212,6 +210,10 @@
 
   // The function we need to reply to.
   scoped_refptr<extensions::TestSendMessageFunction> function_;
+
+  base::ScopedObservation<extensions::TestApiObserverRegistry,
+                          extensions::TestApiObserver>
+      test_api_observation_{this};
 };
 
 #endif  // EXTENSIONS_TEST_EXTENSION_TEST_MESSAGE_LISTENER_H_
diff --git a/extensions/test/result_catcher.cc b/extensions/test/result_catcher.cc
index 6c690c7d..e98214e 100644
--- a/extensions/test/result_catcher.cc
+++ b/extensions/test/result_catcher.cc
@@ -5,23 +5,15 @@
 #include "extensions/test/result_catcher.h"
 
 #include "base/run_loop.h"
-#include "content/public/browser/notification_service.h"
 #include "content/public/test/test_utils.h"
-#include "extensions/browser/notification_types.h"
 
 namespace extensions {
 
 ResultCatcher::ResultCatcher() : browser_context_restriction_(nullptr) {
-  registrar_.Add(this,
-                 extensions::NOTIFICATION_EXTENSION_TEST_PASSED,
-                 content::NotificationService::AllSources());
-  registrar_.Add(this,
-                 extensions::NOTIFICATION_EXTENSION_TEST_FAILED,
-                 content::NotificationService::AllSources());
+  test_api_observation_.Observe(TestApiObserverRegistry::GetInstance());
 }
 
-ResultCatcher::~ResultCatcher() {
-}
+ResultCatcher::~ResultCatcher() = default;
 
 bool ResultCatcher::GetNextResult() {
   // Depending on the tests, multiple results can come in from a single call
@@ -46,35 +38,31 @@
   return false;
 }
 
-void ResultCatcher::Observe(int type,
-                            const content::NotificationSource& source,
-                            const content::NotificationDetails& details) {
+void ResultCatcher::OnTestPassed(content::BrowserContext* browser_context) {
   if (browser_context_restriction_ &&
-      content::Source<content::BrowserContext>(source).ptr() !=
-          browser_context_restriction_) {
+      browser_context != browser_context_restriction_) {
     return;
   }
 
-  switch (type) {
-    case extensions::NOTIFICATION_EXTENSION_TEST_PASSED:
-      VLOG(1) << "Got EXTENSION_TEST_PASSED notification.";
-      results_.push_back(true);
-      messages_.push_back(std::string());
-      if (!quit_closure_.is_null())
-        std::move(quit_closure_).Run();
-      break;
+  VLOG(1) << "Got chrome.test.notifyPass notification.";
+  results_.push_back(true);
+  messages_.push_back(std::string());
+  if (!quit_closure_.is_null())
+    std::move(quit_closure_).Run();
+}
 
-    case extensions::NOTIFICATION_EXTENSION_TEST_FAILED:
-      VLOG(1) << "Got EXTENSION_TEST_FAILED notification.";
-      results_.push_back(false);
-      messages_.push_back(*(content::Details<std::string>(details).ptr()));
-      if (!quit_closure_.is_null())
-        std::move(quit_closure_).Run();
-      break;
-
-    default:
-      NOTREACHED();
+void ResultCatcher::OnTestFailed(content::BrowserContext* browser_context,
+                                 const std::string& message) {
+  if (browser_context_restriction_ &&
+      browser_context != browser_context_restriction_) {
+    return;
   }
+
+  VLOG(1) << "Got chrome.test.notifyFail notification.";
+  results_.push_back(false);
+  messages_.push_back(message);
+  if (!quit_closure_.is_null())
+    std::move(quit_closure_).Run();
 }
 
 }  // namespace extensions
diff --git a/extensions/test/result_catcher.h b/extensions/test/result_catcher.h
index e9d140d..4169256 100644
--- a/extensions/test/result_catcher.h
+++ b/extensions/test/result_catcher.h
@@ -10,8 +10,9 @@
 #include "base/callback.h"
 #include "base/compiler_specific.h"
 #include "base/containers/circular_deque.h"
-#include "content/public/browser/notification_observer.h"
-#include "content/public/browser/notification_registrar.h"
+#include "base/scoped_observation.h"
+#include "extensions/browser/api/test/test_api_observer.h"
+#include "extensions/browser/api/test/test_api_observer_registry.h"
 
 namespace content {
 class BrowserContext;
@@ -24,7 +25,7 @@
 // GetNextResult() and message() if GetNextResult() return false. If there
 // are no results, this method will pump the UI message loop until one is
 // received.
-class ResultCatcher : public content::NotificationObserver {
+class ResultCatcher : public TestApiObserver {
  public:
   ResultCatcher();
   ~ResultCatcher() override;
@@ -40,12 +41,10 @@
   const std::string& message() { return message_; }
 
  private:
-  // content::NotificationObserver:
-  void Observe(int type,
-               const content::NotificationSource& source,
-               const content::NotificationDetails& details) override;
-
-  content::NotificationRegistrar registrar_;
+  // TestApiObserver:
+  void OnTestPassed(content::BrowserContext* browser_context) override;
+  void OnTestFailed(content::BrowserContext* browser_context,
+                    const std::string& message) override;
 
   // A sequential list of pass/fail notifications from the test extension(s).
   base::circular_deque<bool> results_;
@@ -60,6 +59,9 @@
   // Only set if we're in a nested run loop waiting for results from
   // the extension.
   base::OnceClosure quit_closure_;
+
+  base::ScopedObservation<TestApiObserverRegistry, TestApiObserver>
+      test_api_observation_{this};
 };
 
 }  // namespace extensions
diff --git a/gpu/ipc/service/shared_image_stub.cc b/gpu/ipc/service/shared_image_stub.cc
index 999a5e34..fc82505 100644
--- a/gpu/ipc/service/shared_image_stub.cc
+++ b/gpu/ipc/service/shared_image_stub.cc
@@ -226,7 +226,9 @@
     return;
   }
 
-  if (!MakeContextCurrent()) {
+  // Some shared image backing factories will use GL.
+  // TODO(crbug.com/1239365): Only request GL when needed.
+  if (!MakeContextCurrent(/*needs_gl=*/true)) {
     OnError();
     return;
   }
diff --git a/ios/build/bots/scripts/test_runner.py b/ios/build/bots/scripts/test_runner.py
index 02fde4f..00c8e70b 100644
--- a/ios/build/bots/scripts/test_runner.py
+++ b/ios/build/bots/scripts/test_runner.py
@@ -262,7 +262,10 @@
     LOGGER.info(line)
     sys.stdout.flush()
   LOGGER.debug('Finished print_process_output.')
-  return out.decode('utf-8') if sys.version_info.major == 3 else out
+  if sys.version_info.major == 3:
+    for index in range(len(out)):
+      out[index] = out[index].decode('utf-8')
+  return out
 
 
 def get_current_xcode_info():
diff --git a/ios/build/bots/scripts/xcode_log_parser.py b/ios/build/bots/scripts/xcode_log_parser.py
index 1a062df3..1181083a 100644
--- a/ios/build/bots/scripts/xcode_log_parser.py
+++ b/ios/build/bots/scripts/xcode_log_parser.py
@@ -80,7 +80,9 @@
   _find_list_of_tests(failed_tests, failed_test_regex)
   failed_tests_dict = {}
   for failed_test in failed_tests:
-    failed_tests_dict[failed_test] = 'Test failed in interrupted(timedout) run.'
+    failed_tests_dict[failed_test] = ([
+        'Test failed in interrupted(timedout) run.'
+    ])
 
   LOGGER.info('%d passed tests for interrupted build.' % len(passed_tests))
   LOGGER.info('%d failed tests for interrupted build.' % len(failed_tests_dict))
@@ -238,8 +240,10 @@
             rootFailure = json.loads(
                 Xcode11LogParser._xcresulttool_get(
                     xcresult, test['summaryRef']['id']['_value']))
-            failure_message = []
-            for failure in rootFailure['failureSummaries']['_values']:
+            failure_message = ['Logs from "failureSummaries" in .xcresult:']
+            # On rare occasions rootFailure doesn't have 'failureSummaries'.
+            for failure in rootFailure.get('failureSummaries',
+                                           {}).get('_values', []):
               file_name = _sanitize_str(
                   failure.get('fileName', {}).get('_value', ''))
               line_number = _sanitize_str(
diff --git a/ios/build/bots/scripts/xcode_log_parser_test.py b/ios/build/bots/scripts/xcode_log_parser_test.py
index 6b61a30..f7477d7 100644
--- a/ios/build/bots/scripts/xcode_log_parser_test.py
+++ b/ios/build/bots/scripts/xcode_log_parser_test.py
@@ -401,8 +401,9 @@
         'SyncFakeServerTestCase/testSyncDownloadBookmark'
     ]
     expected_failed = {
-        'LinkToTextTestCase/testGenerateLinkForSimpleText':
+        'LinkToTextTestCase/testGenerateLinkForSimpleText': [
             'Test failed in interrupted(timedout) run.'
+        ]
     }
     results = xcode_log_parser.parse_passed_failed_tests_for_interrupted_run(
         test_output_list)
diff --git a/ios/chrome/browser/metrics/BUILD.gn b/ios/chrome/browser/metrics/BUILD.gn
index fe46f87..a0afaa8 100644
--- a/ios/chrome/browser/metrics/BUILD.gn
+++ b/ios/chrome/browser/metrics/BUILD.gn
@@ -75,6 +75,7 @@
     "//components/version_info",
     "//ios/chrome/app/application_delegate:app_state_header",
     "//ios/chrome/browser",
+    "//ios/chrome/browser:chrome_paths",
     "//ios/chrome/browser/browser_state",
     "//ios/chrome/browser/crash_report",
     "//ios/chrome/browser/history",
diff --git a/ios/chrome/browser/metrics/ios_chrome_metrics_service_client_unittest.mm b/ios/chrome/browser/metrics/ios_chrome_metrics_service_client_unittest.mm
index 0ab0b68..18b73490 100644
--- a/ios/chrome/browser/metrics/ios_chrome_metrics_service_client_unittest.mm
+++ b/ios/chrome/browser/metrics/ios_chrome_metrics_service_client_unittest.mm
@@ -7,6 +7,7 @@
 #include <string>
 
 #include "base/compiler_specific.h"
+#include "base/files/file_path.h"
 #include "base/metrics/persistent_histogram_allocator.h"
 #include "base/test/scoped_feature_list.h"
 #include "build/branding_buildflags.h"
@@ -44,7 +45,7 @@
     PlatformTest::SetUp();
     metrics::MetricsService::RegisterPrefs(prefs_.registry());
     metrics_state_manager_ = metrics::MetricsStateManager::Create(
-        &prefs_, &enabled_state_provider_, std::wstring(),
+        &prefs_, &enabled_state_provider_, std::wstring(), base::FilePath(),
         base::BindRepeating(
             &IOSChromeMetricsServiceClientTest::FakeStoreClientInfoBackup,
             base::Unretained(this)),
diff --git a/ios/chrome/browser/metrics/ios_chrome_metrics_services_manager_client.mm b/ios/chrome/browser/metrics/ios_chrome_metrics_services_manager_client.mm
index 40b8b524..ff9d6f6 100644
--- a/ios/chrome/browser/metrics/ios_chrome_metrics_services_manager_client.mm
+++ b/ios/chrome/browser/metrics/ios_chrome_metrics_services_manager_client.mm
@@ -9,12 +9,15 @@
 #include "base/bind.h"
 #include "base/check.h"
 #include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/path_service.h"
 #include "components/metrics/enabled_state_provider.h"
 #include "components/metrics/metrics_state_manager.h"
 #include "components/prefs/pref_service.h"
 #include "components/variations/service/variations_service.h"
 #include "ios/chrome/browser/application_context.h"
 #include "ios/chrome/browser/browser_state/chrome_browser_state_manager.h"
+#include "ios/chrome/browser/chrome_paths.h"
 #include "ios/chrome/browser/chrome_switches.h"
 #import "ios/chrome/browser/main/browser.h"
 #import "ios/chrome/browser/main/browser_list.h"
@@ -89,9 +92,11 @@
 IOSChromeMetricsServicesManagerClient::GetMetricsStateManager() {
   DCHECK(thread_checker_.CalledOnValidThread());
   if (!metrics_state_manager_) {
+    base::FilePath user_data_dir;
+    base::PathService::Get(ios::DIR_USER_DATA, &user_data_dir);
     metrics_state_manager_ = metrics::MetricsStateManager::Create(
         local_state_, enabled_state_provider_.get(), std::wstring(),
-        base::BindRepeating(&PostStoreMetricsClientInfo),
+        user_data_dir, base::BindRepeating(&PostStoreMetricsClientInfo),
         base::BindRepeating(&LoadMetricsClientInfo));
   }
   return metrics_state_manager_.get();
diff --git a/ios/chrome/browser/metrics/mobile_session_shutdown_metrics_provider_unittest.mm b/ios/chrome/browser/metrics/mobile_session_shutdown_metrics_provider_unittest.mm
index 6d8dc7a5..f75aa767 100644
--- a/ios/chrome/browser/metrics/mobile_session_shutdown_metrics_provider_unittest.mm
+++ b/ios/chrome/browser/metrics/mobile_session_shutdown_metrics_provider_unittest.mm
@@ -10,6 +10,7 @@
 #import <Foundation/Foundation.h>
 
 #include "base/bind.h"
+#include "base/files/file_path.h"
 #include "base/macros.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/test_simple_task_runner.h"
@@ -151,7 +152,8 @@
       &local_state_, was_last_shutdown_clean);
   metrics_state_ = metrics::MetricsStateManager::Create(
       &local_state_, new metrics::TestEnabledStateProvider(false, false),
-      std::wstring(), metrics::MetricsStateManager::StoreClientInfoCallback(),
+      std::wstring(), base::FilePath(),
+      metrics::MetricsStateManager::StoreClientInfoCallback(),
       metrics::MetricsStateManager::LoadClientInfoCallback());
   metrics_service_.reset(new metrics::MetricsService(
       metrics_state_.get(), &metrics_client_, &local_state_));
@@ -189,7 +191,8 @@
                                                                 true);
   metrics_state_ = metrics::MetricsStateManager::Create(
       &local_state_, new metrics::TestEnabledStateProvider(false, false),
-      std::wstring(), metrics::MetricsStateManager::StoreClientInfoCallback(),
+      std::wstring(), base::FilePath(),
+      metrics::MetricsStateManager::StoreClientInfoCallback(),
       metrics::MetricsStateManager::LoadClientInfoCallback());
   metrics_service_.reset(new metrics::MetricsService(
       metrics_state_.get(), &metrics_client_, &local_state_));
@@ -226,7 +229,8 @@
                                                                 false);
   metrics_state_ = metrics::MetricsStateManager::Create(
       &local_state_, new metrics::TestEnabledStateProvider(false, false),
-      std::wstring(), metrics::MetricsStateManager::StoreClientInfoCallback(),
+      std::wstring(), base::FilePath(),
+      metrics::MetricsStateManager::StoreClientInfoCallback(),
       metrics::MetricsStateManager::LoadClientInfoCallback());
   metrics_service_.reset(new metrics::MetricsService(
       metrics_state_.get(), &metrics_client_, &local_state_));
@@ -263,7 +267,8 @@
                                                                 false);
   metrics_state_ = metrics::MetricsStateManager::Create(
       &local_state_, new metrics::TestEnabledStateProvider(false, false),
-      std::wstring(), metrics::MetricsStateManager::StoreClientInfoCallback(),
+      std::wstring(), base::FilePath(),
+      metrics::MetricsStateManager::StoreClientInfoCallback(),
       metrics::MetricsStateManager::LoadClientInfoCallback());
   metrics_service_.reset(new metrics::MetricsService(
       metrics_state_.get(), &metrics_client_, &local_state_));
@@ -302,7 +307,8 @@
                                                                 false);
   metrics_state_ = metrics::MetricsStateManager::Create(
       &local_state_, new metrics::TestEnabledStateProvider(false, false),
-      std::wstring(), metrics::MetricsStateManager::StoreClientInfoCallback(),
+      std::wstring(), base::FilePath(),
+      metrics::MetricsStateManager::StoreClientInfoCallback(),
       metrics::MetricsStateManager::LoadClientInfoCallback());
   metrics_service_.reset(new metrics::MetricsService(
       metrics_state_.get(), &metrics_client_, &local_state_));
@@ -343,7 +349,8 @@
                                                                 false);
   metrics_state_ = metrics::MetricsStateManager::Create(
       &local_state_, new metrics::TestEnabledStateProvider(false, false),
-      std::wstring(), metrics::MetricsStateManager::StoreClientInfoCallback(),
+      std::wstring(), base::FilePath(),
+      metrics::MetricsStateManager::StoreClientInfoCallback(),
       metrics::MetricsStateManager::LoadClientInfoCallback());
   metrics_service_.reset(new metrics::MetricsService(
       metrics_state_.get(), &metrics_client_, &local_state_));
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.arm64.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.arm64.zip.sha1
index 2b06d113..da17cd8d 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.arm64.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.arm64.zip.sha1
@@ -1 +1 @@
-f68ccf3a5adbd4c201a6c36e4529c062a1a3d050
\ No newline at end of file
+401e428b8323b5d7afbadbec770e3a11640e6de0
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.x64.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.x64.zip.sha1
index 836af6d9..600198b6 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.x64.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.x64.zip.sha1
@@ -1 +1 @@
-adfdbb9cfcb05ece04bbfb266425c61fb6e479eb
\ No newline at end of file
+6349b175b6419775727373c4cf0e51d49025a029
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.arm64.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.arm64.zip.sha1
index 7ad6bf4..8b1aaac 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.arm64.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.arm64.zip.sha1
@@ -1 +1 @@
-079cd43e9cdc3e5f8932fab72a2707cca363f27e
\ No newline at end of file
+468c2e2d38eaeab2f57c1b2827fd3b086802fef8
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.x64.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.x64.zip.sha1
index 24f840c4..62dfdcf 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.x64.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.x64.zip.sha1
@@ -1 +1 @@
-77647704e4016b73e128999c1202dc04af818014
\ No newline at end of file
+1e04de0b7a64df175db782020fe743e48224cb63
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.arm64.zip.sha1 b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.arm64.zip.sha1
index c5976f3..f0578847 100644
--- a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.arm64.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.arm64.zip.sha1
@@ -1 +1 @@
-55856a60259b389bcf71a99676ca10ac8fab1a94
\ No newline at end of file
+ee047d2f3f3556bc73034121ab8ec8acd7b9d87d
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.x64.zip.sha1 b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.x64.zip.sha1
index 9d477ce..9373d77 100644
--- a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.x64.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.x64.zip.sha1
@@ -1 +1 @@
-42d950f3f807c4ceded15ed988510f37e29ca47b
\ No newline at end of file
+93ea02f17458cef8f9402615a660b1033b3bc912
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.arm64.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.arm64.zip.sha1
index f9b64a7..7b2b07e4 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.arm64.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.arm64.zip.sha1
@@ -1 +1 @@
-f8d751c22eb3f80f38516d3988bd7dbfe1ba78af
\ No newline at end of file
+f26c4666f649903f1fd96340f627ec0e14f7e114
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.x64.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.x64.zip.sha1
index 3f9d82d8..c1f058f6 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.x64.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.x64.zip.sha1
@@ -1 +1 @@
-8b2f59270782d39c13ff337ecd5679d6f66ca975
\ No newline at end of file
+3a8d31f6efc3f4059fdb34be783c7c34ea229619
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.arm64.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.arm64.zip.sha1
index d6b68fa..0281f51 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.arm64.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.arm64.zip.sha1
@@ -1 +1 @@
-48043561d34006f9e6d502ce83db2dfa17aaed5c
\ No newline at end of file
+b897f50665b69432380bd5a571abc055b6a9e1f1
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.x64.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.x64.zip.sha1
index 22ec68fe..709ae0d 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.x64.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.x64.zip.sha1
@@ -1 +1 @@
-cfa83a4edaf2ca5f25442121a81748551a4c05cb
\ No newline at end of file
+162f0171638a9647d5a2b288ff4a1efca86d263e
\ No newline at end of file
diff --git a/ios/web_view/test/web_view_inttest_base.mm b/ios/web_view/test/web_view_inttest_base.mm
index cf39109..8869693 100644
--- a/ios/web_view/test/web_view_inttest_base.mm
+++ b/ios/web_view/test/web_view_inttest_base.mm
@@ -116,9 +116,9 @@
   // The WKWebView must be present in the view hierarchy in order to prevent
   // WebKit optimizations which may pause internal parts of the web view
   // without notice. Work around this by adding the view directly.
-  // TODO:(crbug.com/944077) Remove this workaround once fixed in ios/web.
   UIViewController* view_controller = [GetAnyKeyWindow() rootViewController];
   [view_controller.view addSubview:web_view_];
+
   test_server_->AddDefaultHandlers(FILE_PATH_LITERAL(base::FilePath()));
   test_server_->RegisterRequestHandler(
       base::BindRepeating(&TestRequestHandler));
diff --git a/ios/web_view/test/web_view_restorable_state_inttest.mm b/ios/web_view/test/web_view_restorable_state_inttest.mm
index 8ad7a4a8..f30249ec 100644
--- a/ios/web_view/test/web_view_restorable_state_inttest.mm
+++ b/ios/web_view/test/web_view_restorable_state_inttest.mm
@@ -43,7 +43,6 @@
   // The WKWebView must be present in the view hierarchy in order to prevent
   // WebKit optimizations which may pause internal parts of the web view
   // without notice. Work around this by adding the view directly.
-  // TODO(crbug.com/944077): Remove this workaround once fixed in ios/web_view.
   UIViewController* view_controller = [GetAnyKeyWindow() rootViewController];
   [view_controller.view addSubview:restored_web_view];
 
diff --git a/media/gpu/chromeos/libyuv_image_processor_backend.cc b/media/gpu/chromeos/libyuv_image_processor_backend.cc
index 1cec987..76a5cf5e 100644
--- a/media/gpu/chromeos/libyuv_image_processor_backend.cc
+++ b/media/gpu/chromeos/libyuv_image_processor_backend.cc
@@ -6,6 +6,7 @@
 
 #include "base/containers/contains.h"
 #include "base/memory/ptr_util.h"
+#include "base/numerics/checked_math.h"
 #include "media/gpu/chromeos/fourcc.h"
 #include "media/gpu/macros.h"
 #include "media/gpu/video_frame_mapper.h"
@@ -53,8 +54,13 @@
   }
 
   // Rotating.
-  const int tmp_uv_width = (dst_width + 1) / 2;
-  const int tmp_uv_height = (dst_height + 1) / 2;
+  int tmp_uv_width = 0;
+  int tmp_uv_height = 0;
+  if (!(base::CheckAdd<int>(dst_width, 1) / 2).AssignIfValid(&tmp_uv_width) ||
+      !(base::CheckAdd<int>(dst_height, 1) / 2).AssignIfValid(&tmp_uv_height)) {
+    VLOGF(1) << "Overflow occurred for " << dst_width << "x" << dst_height;
+    return -1;
+  }
   uint8_t* const tmp_u = tmp_buffer;
   uint8_t* const tmp_v = tmp_u + tmp_uv_width * tmp_uv_height;
 
diff --git a/pdf/pdf_view_web_plugin.cc b/pdf/pdf_view_web_plugin.cc
index 95326eb..470e5dd 100644
--- a/pdf/pdf_view_web_plugin.cc
+++ b/pdf/pdf_view_web_plugin.cc
@@ -232,6 +232,10 @@
   return nullptr;
 }
 
+bool PdfViewWebPlugin::Client::IsUseZoomForDSFEnabled() const {
+  return false;
+}
+
 PdfViewWebPlugin::PdfViewWebPlugin(
     std::unique_ptr<Client> client,
     mojo::AssociatedRemote<pdf::mojom::PdfService> pdf_service_remote,
@@ -340,12 +344,19 @@
     blink::DocumentUpdateReason reason) {}
 
 void PdfViewWebPlugin::Paint(cc::PaintCanvas* canvas, const gfx::Rect& rect) {
+  // The scale level used to convert DIPs to CSS pixels.
+  float inverse_scale = 1.0f / (device_scale() * viewport_to_dip_scale_);
+
+  // `rect` is in CSS pixels, and the plugin rect is in DIPs. The plugin rect
+  // needs to be converted into CSS pixels before calculating the rect area to
+  // be invalidated.
+  gfx::Rect plugin_rect_in_css_pixels =
+      gfx::ScaleToEnclosingRectSafe(plugin_rect(), inverse_scale);
+
   // Clip the intersection of the paint rect and the plugin rect, so that
   // painting outside the plugin or the paint rect area can be avoided.
-  // Note: Same as the plugin rect, `invalidate_rect` and `rect` are not in CSS
-  // pixels, and the device scale has already been applied to them.
   SkRect invalidate_rect =
-      gfx::RectToSkRect(gfx::IntersectRects(plugin_rect(), rect));
+      gfx::RectToSkRect(gfx::IntersectRects(plugin_rect_in_css_pixels, rect));
   cc::PaintCanvasAutoRestore auto_restore(canvas, /*save=*/true);
   canvas->clipRect(invalidate_rect);
 
@@ -358,6 +369,9 @@
     return;
   }
 
+  if (inverse_scale != 1.0f)
+    canvas->scale(inverse_scale, inverse_scale);
+
   canvas->drawImage(snapshot_, plugin_rect().x(), plugin_rect().y());
 }
 
@@ -366,12 +380,14 @@
                                       const gfx::Rect& unobscured_rect,
                                       bool is_visible) {
   float device_scale = container_wrapper_->DeviceScaleFactor();
+  viewport_to_dip_scale_ =
+      client_->IsUseZoomForDSFEnabled() ? 1.0f / device_scale : 1.0f;
 
-  // Note that the device scale has been applied to this `window_rect`.
-  // `window_rect` needs to be converted to CSS pixels before getting passed
-  // into PdfViewPluginBase::UpdateGeometryOnViewChanged().
+  // Note that `window_rect` is in viewport coordinates. It needs to be
+  // converted to DIPs before getting passed into
+  // PdfViewPluginBase::UpdateGeometryOnViewChanged().
   OnViewportChanged(
-      gfx::ScaleToEnclosingRectSafe(window_rect, 1.0f / device_scale),
+      gfx::ScaleToEnclosingRectSafe(window_rect, viewport_to_dip_scale_),
       device_scale);
 }
 
diff --git a/pdf/pdf_view_web_plugin.h b/pdf/pdf_view_web_plugin.h
index 41fb726..2ad39f801 100644
--- a/pdf/pdf_view_web_plugin.h
+++ b/pdf/pdf_view_web_plugin.h
@@ -137,6 +137,9 @@
     virtual std::unique_ptr<PdfAccessibilityDataHandler>
     CreateAccessibilityDataHandler(
         PdfAccessibilityActionHandler* action_handler);
+
+    // Indicates whether to use zoom for DSF (device scale factor).
+    virtual bool IsUseZoomForDSFEnabled() const;
   };
 
   PdfViewWebPlugin(
@@ -324,6 +327,9 @@
 
   cc::PaintImage snapshot_;
 
+  // The viewport coordinates to DIP (device-independent pixel) ratio.
+  float viewport_to_dip_scale_ = 1.0f;
+
   // May be null in unit tests.
   std::unique_ptr<PdfAccessibilityDataHandler> const
       pdf_accessibility_data_handler_;
diff --git a/pdf/pdf_view_web_plugin_unittest.cc b/pdf/pdf_view_web_plugin_unittest.cc
index 9df2931..b5046a32 100644
--- a/pdf/pdf_view_web_plugin_unittest.cc
+++ b/pdf/pdf_view_web_plugin_unittest.cc
@@ -34,7 +34,9 @@
 using ::testing::InSequence;
 using ::testing::Invoke;
 using ::testing::MockFunction;
+using ::testing::NiceMock;
 using ::testing::Pointwise;
+using ::testing::Return;
 
 // `kCanvasSize` needs to be big enough to hold plugin's snapshots during
 // testing.
@@ -67,11 +69,15 @@
 // Generates the expected `SkBitmap` with `paint_color` filled in the expected
 // clipped area and `kDefaultColor` as the background color.
 SkBitmap GenerateExpectedBitmapForPaint(float device_scale,
+                                        bool use_zoom_for_dsf,
                                         const gfx::Rect& plugin_rect,
                                         const gfx::Rect& paint_rect,
                                         SkColor paint_color) {
-  gfx::Rect expected_clipped_area =
-      gfx::IntersectRects(plugin_rect, paint_rect);
+  float inverse_scale = use_zoom_for_dsf ? 1.0f : 1.0f / device_scale;
+  // TODO(crbug.com/1238395): Improve the test by pre-define the
+  // `expected_clipped_area` instead of calculating it inside the test.
+  gfx::Rect expected_clipped_area = gfx::IntersectRects(
+      gfx::ScaleToEnclosingRectSafe(plugin_rect, inverse_scale), paint_rect);
   SkBitmap expected_bitmap =
       CreateN32PremulSkBitmap(gfx::SizeToSkISize(kCanvasSize));
   expected_bitmap.eraseColor(kDefaultColor);
@@ -162,6 +168,18 @@
   PdfViewWebPlugin* web_plugin_;
 };
 
+class FakePdfViewWebPluginClient : public PdfViewWebPlugin::Client {
+ public:
+  FakePdfViewWebPluginClient() = default;
+  FakePdfViewWebPluginClient(const FakePdfViewWebPluginClient&) = delete;
+  FakePdfViewWebPluginClient& operator=(const FakePdfViewWebPluginClient&) =
+      delete;
+  ~FakePdfViewWebPluginClient() override = default;
+
+  // PdfViewWebPlugin::Client:
+  MOCK_METHOD(bool, IsUseZoomForDSFEnabled, (), (const, override));
+};
+
 }  // namespace
 
 class PdfViewWebPluginTest : public testing::Test {
@@ -183,7 +201,9 @@
     params.attribute_names.push_back(blink::WebString("src"));
     params.attribute_values.push_back(blink::WebString("dummy.pdf"));
 
-    auto client = std::make_unique<PdfViewWebPlugin::Client>();
+    auto client = std::make_unique<NiceMock<FakePdfViewWebPluginClient>>();
+    client_ptr_ = client.get();
+
     mojo::AssociatedRemote<pdf::mojom::PdfService> unbound_remote;
     plugin_ =
         std::unique_ptr<PdfViewWebPlugin, PluginDeleter>(new PdfViewWebPlugin(
@@ -218,8 +238,11 @@
   }
 
   void TestPaintEmptySnapshots(float device_scale,
+                               bool use_zoom_for_dsf,
                                const gfx::Rect& window_rect,
                                const gfx::Rect& paint_rect) {
+    EXPECT_CALL(*client_ptr_, IsUseZoomForDSFEnabled)
+        .WillOnce(Return(use_zoom_for_dsf));
     UpdatePluginGeometry(device_scale, window_rect);
     canvas_.DrawColor(kDefaultColor);
 
@@ -228,8 +251,8 @@
     // Expect the clipped area on canvas to be filled with plugin's background
     // color.
     SkBitmap expected_bitmap = GenerateExpectedBitmapForPaint(
-        device_scale, plugin_->GetPluginRectForTesting(), paint_rect,
-        plugin_->GetBackgroundColor());
+        device_scale, use_zoom_for_dsf, plugin_->GetPluginRectForTesting(),
+        paint_rect, plugin_->GetBackgroundColor());
     EXPECT_TRUE(
         cc::MatchesBitmap(canvas_.GetBitmap(), expected_bitmap,
                           cc::ExactPixelComparator(/*discard_alpha=*/false)))
@@ -238,8 +261,11 @@
   }
 
   void TestPaintSnapshots(float device_scale,
+                          bool use_zoom_for_dsf,
                           const gfx::Rect& window_rect,
                           const gfx::Rect& paint_rect) {
+    EXPECT_CALL(*client_ptr_, IsUseZoomForDSFEnabled)
+        .WillOnce(Return(use_zoom_for_dsf));
     UpdatePluginGeometry(device_scale, window_rect);
     canvas_.DrawColor(kDefaultColor);
 
@@ -257,7 +283,7 @@
 
     // Expect the clipped area on canvas to be filled with `kPaintColor`.
     SkBitmap expected_bitmap = GenerateExpectedBitmapForPaint(
-        device_scale, plugin_rect, paint_rect, kPaintColor);
+        device_scale, use_zoom_for_dsf, plugin_rect, paint_rect, kPaintColor);
     EXPECT_TRUE(
         cc::MatchesBitmap(canvas_.GetBitmap(), expected_bitmap,
                           cc::ExactPixelComparator(/*discard_alpha=*/false)))
@@ -266,13 +292,33 @@
   }
 
   FakeContainerWrapper* wrapper_ptr_;
+  FakePdfViewWebPluginClient* client_ptr_;
   std::unique_ptr<PdfViewWebPlugin, PluginDeleter> plugin_;
 
   // Provides the cc::PaintCanvas for painting.
   gfx::Canvas canvas_{kCanvasSize, /*image_scale=*/1.0f, /*is_opaque=*/true};
 };
 
-TEST_F(PdfViewWebPluginTest, UpdateGeometrySetsPluginRect) {
+// TODO(crbug.com/1238395): Split this test into two: One with zoom-for-DSF
+// enabled, one with zoom-for-DSF disabled.
+TEST_F(PdfViewWebPluginTest,
+       UpdateGeometrySetsPluginRectOnDifferentUseZoomForDSFSettings) {
+  // Use the same device scale for this test.
+  const float device_scale = 2.0f;
+
+  // Test when using zoom for DSF is enabled.
+  EXPECT_CALL(*client_ptr_, IsUseZoomForDSFEnabled).WillOnce(Return(true));
+  TestUpdateGeometrySetsPluginRect(device_scale, gfx::Rect(4, 4, 12, 12),
+                                   gfx::Rect(4, 4, 12, 12));
+
+  // Test when using zoom for DSF is disabled.
+  EXPECT_CALL(*client_ptr_, IsUseZoomForDSFEnabled).WillOnce(Return(false));
+  TestUpdateGeometrySetsPluginRect(device_scale, gfx::Rect(4, 4, 12, 12),
+                                   gfx::Rect(8, 8, 24, 24));
+}
+
+TEST_F(PdfViewWebPluginTest,
+       UpdateGeometrySetsPluginRectOnVariousDeviceScales) {
   struct UpdateGeometryParams {
     // The plugin container's device scale.
     float device_scale;
@@ -284,8 +330,10 @@
     gfx::Rect expected_plugin_rect;
   };
 
-  // The expected plugin rect should be similar to or the same as the input
-  // `window_rect`, since they both have the device scale applied.
+  // Keep the using zoom for DSF setting consistent within the test.
+  EXPECT_CALL(*client_ptr_, IsUseZoomForDSFEnabled)
+      .WillRepeatedly(Return(true));
+
   static constexpr UpdateGeometryParams kUpdateGeometryParams[] = {
       {1.0f, gfx::Rect(3, 4, 5, 6), gfx::Rect(3, 4, 5, 6)},
       {2.0f, gfx::Rect(4, 4, 12, 12), gfx::Rect(4, 4, 12, 12)},
@@ -309,8 +357,10 @@
   };
 
   for (const auto& params : kPaintEmptySnapshotsParams) {
-    TestPaintEmptySnapshots(params.device_scale, params.window_rect,
-                            params.paint_rect);
+    TestPaintEmptySnapshots(params.device_scale, /*use_zoom_for_dsf=*/true,
+                            params.window_rect, params.paint_rect);
+    TestPaintEmptySnapshots(params.device_scale, /*use_zoom_for_dsf=*/false,
+                            params.window_rect, params.paint_rect);
   }
 }
 
@@ -325,8 +375,10 @@
   };
 
   for (const auto& params : kPaintWithScalesTestParams) {
-    TestPaintSnapshots(params.device_scale, params.window_rect,
-                       params.paint_rect);
+    TestPaintSnapshots(params.device_scale, /*use_zoom_for_dsf=*/true,
+                       params.window_rect, params.paint_rect);
+    TestPaintSnapshots(params.device_scale, /*use_zoom_for_dsf=*/false,
+                       params.window_rect, params.paint_rect);
   }
 }
 
diff --git a/printing/print_job_constants.cc b/printing/print_job_constants.cc
index 971675c..fd6436a 100644
--- a/printing/print_job_constants.cc
+++ b/printing/print_job_constants.cc
@@ -180,6 +180,9 @@
 // Whether to rasterize the PDF for printing.
 const char kSettingRasterizePdf[] = "rasterizePDF";
 
+// The DPI override to use when rasterize the PDF for printing.
+const char kSettingRasterizePdfDpi[] = "rasterizePdfDpi";
+
 // Ticket option. Contains the ticket in CJT format.
 const char kSettingTicket[] = "ticket";
 
diff --git a/printing/print_job_constants.h b/printing/print_job_constants.h
index 2ea9b408..1df500ee 100644
--- a/printing/print_job_constants.h
+++ b/printing/print_job_constants.h
@@ -117,6 +117,8 @@
 COMPONENT_EXPORT(PRINTING_BASE)
 extern const char kSettingRasterizePdf[];
 COMPONENT_EXPORT(PRINTING_BASE)
+extern const char kSettingRasterizePdfDpi[];
+COMPONENT_EXPORT(PRINTING_BASE)
 extern const char kSettingScaleFactor[];
 COMPONENT_EXPORT(PRINTING_BASE)
 extern const char kSettingScalingType[];
diff --git a/printing/print_settings.cc b/printing/print_settings.cc
index b12f1f5..0115615 100644
--- a/printing/print_settings.cc
+++ b/printing/print_settings.cc
@@ -274,6 +274,7 @@
   dpi_ = gfx::Size();
   scale_factor_ = 1.0f;
   rasterize_pdf_ = false;
+  rasterize_pdf_dpi_ = 0;
   landscape_ = false;
   supports_alpha_blend_ = true;
 #if defined(OS_WIN)
diff --git a/printing/print_settings.h b/printing/print_settings.h
index 9592024..efba1ec 100644
--- a/printing/print_settings.h
+++ b/printing/print_settings.h
@@ -137,6 +137,9 @@
   void set_rasterize_pdf(bool rasterize_pdf) { rasterize_pdf_ = rasterize_pdf; }
   bool rasterize_pdf() const { return rasterize_pdf_; }
 
+  void set_rasterize_pdf_dpi(int32_t dpi) { rasterize_pdf_dpi_ = dpi; }
+  int32_t rasterize_pdf_dpi() const { return rasterize_pdf_dpi_; }
+
   void set_supports_alpha_blend(bool supports_alpha_blend) {
     supports_alpha_blend_ = supports_alpha_blend;
   }
@@ -297,6 +300,11 @@
   // True if PDF should be printed as a raster PDF
   bool rasterize_pdf_;
 
+  // The DPI which overrides the calculated value normally used when
+  // rasterizing a PDF.  A non-positive value would be an invalid choice of a
+  // DPI and indicates no override.
+  int32_t rasterize_pdf_dpi_;
+
   // Is the orientation landscape or portrait.
   bool landscape_;
 
diff --git a/printing/print_settings_conversion.cc b/printing/print_settings_conversion.cc
index 31b4576e..ee2aaea 100644
--- a/printing/print_settings_conversion.cc
+++ b/printing/print_settings_conversion.cc
@@ -192,6 +192,11 @@
     return nullptr;
   settings->set_dpi_xy(dpi_horizontal.value(), dpi_vertical.value());
 
+  absl::optional<int> rasterize_pdf_dpi =
+      job_settings.FindIntKey(kSettingRasterizePdfDpi);
+  if (rasterize_pdf_dpi.has_value())
+    settings->set_rasterize_pdf_dpi(rasterize_pdf_dpi.value());
+
   settings->set_collate(collate.value());
   settings->set_copies(copies.value());
   settings->SetOrientation(landscape.value());
diff --git a/printing/print_settings_conversion_unittest.cc b/printing/print_settings_conversion_unittest.cc
index c9f21e6e..0690253 100644
--- a/printing/print_settings_conversion_unittest.cc
+++ b/printing/print_settings_conversion_unittest.cc
@@ -38,6 +38,7 @@
   "deviceName": "printer",
   "scaleFactor": 100,
   "rasterizePDF": false,
+  "rasterizePdfDpi": 150,
   "pagesPerSheet": 1,
   "dpiHorizontal": 300,
   "dpiVertical": 300,
@@ -71,6 +72,7 @@
   value->SetIntKey("dpiVertical", 600);
   settings = PrintSettingsFromJobSettings(value.value());
   ASSERT_TRUE(settings);
+  EXPECT_EQ(settings->rasterize_pdf_dpi(), 150);
   EXPECT_EQ(settings->dpi_horizontal(), 300);
   EXPECT_EQ(settings->dpi_vertical(), 600);
   EXPECT_TRUE(value->RemoveKey("dpiVertical"));
diff --git a/remoting/protocol/webrtc_transport.cc b/remoting/protocol/webrtc_transport.cc
index 63fb52d..db73896 100644
--- a/remoting/protocol/webrtc_transport.cc
+++ b/remoting/protocol/webrtc_transport.cc
@@ -41,7 +41,6 @@
 #include "third_party/webrtc/api/call/call_factory_interface.h"
 #include "third_party/webrtc/api/peer_connection_interface.h"
 #include "third_party/webrtc/api/rtc_event_log/rtc_event_log_factory.h"
-#include "third_party/webrtc/api/stats/rtcstats_objects.h"
 #include "third_party/webrtc/api/video_codecs/builtin_video_decoder_factory.h"
 #include "third_party/webrtc/media/engine/webrtc_media_engine.h"
 #include "third_party/webrtc/modules/audio_processing/include/audio_processing.h"
@@ -71,13 +70,6 @@
 // Global maximum bitrate set for the PeerConnection.
 const int kMaxBitrateBps = 1e8;  // 100 Mbps.
 
-// Frequency of polling for RTCStats. Polling is needed because WebRTC native
-// API does not provide a route-change notification for the connection type
-// (direct/STUN/relay).
-// TODO(lambroslambrou): Remove polling when a native API is provided.
-constexpr base::TimeDelta kRtcStatsPollingInterval =
-    base::TimeDelta::FromSeconds(2);
-
 // Frequency of polling the event and control data channels for their current
 // state while waiting for them to close.
 constexpr base::TimeDelta kDefaultDataChannelStatePollingInterval =
@@ -120,82 +112,21 @@
   }
 }
 
-const webrtc::RTCIceCandidatePairStats* GetSelectedCandidatePair(
-    const rtc::scoped_refptr<const webrtc::RTCStatsReport>& report) {
-  auto transport_stats_list =
-      report->GetStatsOfType<webrtc::RTCTransportStats>();
-  if (transport_stats_list.size() != 1) {
-    LOG(ERROR) << "Unexpected number of transport stats: "
-               << transport_stats_list.size();
-    return nullptr;
-  }
-  std::string selected_candidate_pair_id =
-      *(transport_stats_list[0]->selected_candidate_pair_id);
-  const auto* selected_candidate_pair =
-      report->GetAs<webrtc::RTCIceCandidatePairStats>(
-          selected_candidate_pair_id);
-  if (!selected_candidate_pair) {
-    LOG(ERROR) << "Expected to find RTC stats for id: "
-               << selected_candidate_pair;
-  }
-  return selected_candidate_pair;
+std::string GetTransportProtocol(const cricket::CandidatePair& candidate_pair) {
+  const cricket::Candidate& local_candidate = candidate_pair.local_candidate();
+  return (local_candidate.type() == "relay") ? local_candidate.relay_protocol()
+                                             : local_candidate.protocol();
 }
 
-template <typename T>
-const T* GetIceCandidate(
-    const rtc::scoped_refptr<const webrtc::RTCStatsReport>& report,
-    const std::string& candidate_id) {
-  const T* candidate = report->GetAs<T>(candidate_id);
-  if (!candidate) {
-    LOG(ERROR) << "Expected to find RTC stats for id: " << candidate_id;
-  }
-  return candidate;
-}
-
-std::string GetTransportProtocol(
-    const rtc::scoped_refptr<const webrtc::RTCStatsReport>& report) {
-  const webrtc::RTCIceCandidatePairStats* selected_candidate_pair =
-      GetSelectedCandidatePair(report);
-  if (!selected_candidate_pair) {
-    return "api-error";
-  }
-  const auto* local_candidate =
-      GetIceCandidate<webrtc::RTCLocalIceCandidateStats>(
-          report, *selected_candidate_pair->local_candidate_id);
-  if (!local_candidate) {
-    return "api-error";
-  }
-  return *local_candidate->candidate_type == "relay"
-             ? *local_candidate->relay_protocol
-             : *local_candidate->protocol;
-}
-
-// Returns true if the RTC stats report indicates a relay connection. If the
-// connection type cannot be determined (which should never happen with a valid
-// RTCStatsReport), nullopt is returned.
+// Returns true if the selected candidate-pair indicates a relay connection.
 absl::optional<bool> IsConnectionRelayed(
-    const rtc::scoped_refptr<const webrtc::RTCStatsReport>& report) {
-  const webrtc::RTCIceCandidatePairStats* selected_candidate_pair =
-      GetSelectedCandidatePair(report);
-  if (!selected_candidate_pair) {
-    return absl::nullopt;
-  }
-  const auto* local_candidate =
-      GetIceCandidate<webrtc::RTCLocalIceCandidateStats>(
-          report, *selected_candidate_pair->local_candidate_id);
-  if (!local_candidate) {
-    return absl::nullopt;
-  }
-  std::string local_candidate_type = *local_candidate->candidate_type;
-  const auto* remote_candidate =
-      GetIceCandidate<webrtc::RTCRemoteIceCandidateStats>(
-          report, *selected_candidate_pair->remote_candidate_id);
-  if (!remote_candidate) {
-    return absl::nullopt;
-  }
-  std::string remote_candidate_type = *remote_candidate->candidate_type;
-
-  return local_candidate_type == "relay" || remote_candidate_type == "relay";
+    const cricket::CandidatePair& selected_candidate_pair) {
+  const cricket::Candidate& local_candidate =
+      selected_candidate_pair.local_candidate();
+  const cricket::Candidate& remote_candidate =
+      selected_candidate_pair.remote_candidate();
+  return local_candidate.type() == "relay" ||
+         remote_candidate.type() == "relay";
 }
 
 // Utility function to map a cricket::Candidate string type to a
@@ -281,33 +212,6 @@
   DISALLOW_COPY_AND_ASSIGN(SetSessionDescriptionObserver);
 };
 
-class RTCStatsCollectorCallback : public webrtc::RTCStatsCollectorCallback {
- public:
-  typedef base::OnceCallback<void(
-      const rtc::scoped_refptr<const webrtc::RTCStatsReport>& report)>
-      ResultCallback;
-
-  static RTCStatsCollectorCallback* Create(ResultCallback result_callback) {
-    return new rtc::RefCountedObject<RTCStatsCollectorCallback>(
-        std::move(result_callback));
-  }
-
-  void OnStatsDelivered(
-      const rtc::scoped_refptr<const webrtc::RTCStatsReport>& report) override {
-    std::move(result_callback_).Run(report);
-  }
-
- protected:
-  explicit RTCStatsCollectorCallback(ResultCallback result_callback)
-      : result_callback_(std::move(result_callback)) {}
-  ~RTCStatsCollectorCallback() override = default;
-
- private:
-  ResultCallback result_callback_;
-
-  DISALLOW_COPY_AND_ASSIGN(RTCStatsCollectorCallback);
-};
-
 class RtcEventLogOutput : public webrtc::RtcEventLogOutput {
  public:
   // |event_log_data| will be populated with the RTC event data during logging.
@@ -665,7 +569,8 @@
 
     // SetRemoteDescription() might overwrite any bitrate caps previously set,
     // so (re)apply them here. This might happen if ICE state were already
-    // connected and OnStatsDelivered() had already set the caps.
+    // connected and OnIceSelectedCandidatePairChanged() had already set the
+    // caps.
     int min_bitrate_bps, max_bitrate_bps;
     std::tie(min_bitrate_bps, max_bitrate_bps) = BitratesForConnection();
     SetPeerConnectionBitrates(min_bitrate_bps, max_bitrate_bps);
@@ -1019,10 +924,6 @@
     connected_ = true;
     connection_relayed_.reset();
     event_handler_->OnWebrtcTransportConnected();
-
-    // Request RTC statistics, to determine if the connection is direct or
-    // relayed.
-    RequestRtcStats();
   } else if (connected_ &&
              new_state ==
                  webrtc::PeerConnectionInterface::kIceConnectionDisconnected &&
@@ -1064,6 +965,37 @@
     const cricket::CandidatePairChangeEvent& event) {
   DCHECK(thread_checker_.CalledOnValidThread());
 
+  std::string transport_protocol =
+      GetTransportProtocol(event.selected_candidate_pair);
+  if (transport_protocol != transport_protocol_) {
+    transport_protocol_ = transport_protocol;
+    event_handler_->OnWebrtcTransportProtocolChanged();
+  }
+
+  // Unknown -> direct/relayed is treated as a
+  // change, so the correct initial bitrate caps are set.
+  absl::optional<bool> connection_relayed =
+      IsConnectionRelayed(event.selected_candidate_pair);
+  if (connection_relayed != connection_relayed_) {
+    connection_relayed_ = connection_relayed;
+    if (connection_relayed_.has_value()) {
+      VLOG(0) << "Relay connection: "
+              << (connection_relayed_.value() ? "true" : "false");
+    } else {
+      LOG(ERROR) << "Connection type unknown, treating as direct.";
+    }
+
+    // The max-bitrate needs to be applied even for direct (non-TURN)
+    // connections. Otherwise the video-sender b/w estimate is capped to a low
+    // default value (~600kbps).
+    // Set the global bitrate caps in addition to the VideoSender bitrates. The
+    // global caps affect the probing configuration used by b/w estimator.
+    int min_bitrate_bps, max_bitrate_bps;
+    std::tie(min_bitrate_bps, max_bitrate_bps) = BitratesForConnection();
+    SetPeerConnectionBitrates(min_bitrate_bps, max_bitrate_bps);
+    SetSenderBitrates(min_bitrate_bps, max_bitrate_bps);
+  }
+
   const cricket::Candidate& local_candidate =
       event.selected_candidate_pair.local_candidate();
   const cricket::Candidate& remote_candidate =
@@ -1102,49 +1034,6 @@
   event_handler_->OnWebrtcTransportRouteChanged(route);
 }
 
-void WebrtcTransport::OnStatsDelivered(
-    const rtc::scoped_refptr<const webrtc::RTCStatsReport>& report) {
-  if (!connected_)
-    return;
-
-  base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
-      FROM_HERE,
-      base::BindOnce(&WebrtcTransport::RequestRtcStats,
-                     weak_factory_.GetWeakPtr()),
-      kRtcStatsPollingInterval);
-
-  std::string transport_protocol = GetTransportProtocol(report);
-  if (transport_protocol != transport_protocol_) {
-    transport_protocol_ = transport_protocol;
-    event_handler_->OnWebrtcTransportProtocolChanged();
-  }
-
-  absl::optional<bool> connection_relayed = IsConnectionRelayed(report);
-  if (connection_relayed == connection_relayed_) {
-    // No change in connection type. Unknown -> direct/relayed is treated as a
-    // change, so the correct initial bitrate caps are set.
-    return;
-  }
-
-  connection_relayed_ = connection_relayed;
-  if (connection_relayed_.has_value()) {
-    VLOG(0) << "Relay connection: "
-            << (connection_relayed_.value() ? "true" : "false");
-  } else {
-    LOG(ERROR) << "Connection type unknown, treating as direct.";
-  }
-
-  // The max-bitrate needs to be applied even for direct (non-TURN) connections.
-  // Otherwise the video-sender b/w estimate is capped to a low default value
-  // (~600kbps).
-  // Set the global bitrate caps in addition to the VideoSender bitrates. The
-  // global caps affect the probing configuration used by b/w estimator.
-  int min_bitrate_bps, max_bitrate_bps;
-  std::tie(min_bitrate_bps, max_bitrate_bps) = BitratesForConnection();
-  SetPeerConnectionBitrates(min_bitrate_bps, max_bitrate_bps);
-  SetSenderBitrates(min_bitrate_bps, max_bitrate_bps);
-}
-
 std::tuple<int, int> WebrtcTransport::BitratesForConnection() {
   int max_bitrate_bps = kMaxBitrateBps;
   if (connection_relayed_.value_or(false)) {
@@ -1235,14 +1124,6 @@
   DCHECK(result.ok()) << "SetParameters() failed: " << result.message();
 }
 
-void WebrtcTransport::RequestRtcStats() {
-  if (!connected_)
-    return;
-
-  peer_connection()->GetStats(RTCStatsCollectorCallback::Create(base::BindOnce(
-      &WebrtcTransport::OnStatsDelivered, weak_factory_.GetWeakPtr())));
-}
-
 void WebrtcTransport::RequestNegotiation() {
   DCHECK(transport_context_->role() == TransportRole::SERVER);
 
diff --git a/services/tracing/public/mojom/BUILD.gn b/services/tracing/public/mojom/BUILD.gn
index 02020fe3..9d3720a5 100644
--- a/services/tracing/public/mojom/BUILD.gn
+++ b/services/tracing/public/mojom/BUILD.gn
@@ -15,7 +15,10 @@
     "tracing_service.mojom",
   ]
 
-  public_deps = [ "//mojo/public/mojom/base" ]
+  public_deps = [
+    "//mojo/public/mojom/base",
+    "//sandbox/policy/mojom",
+  ]
 
   if (!is_nacl && !is_ios) {
     enabled_features = [ "is_perfetto_supported_os" ]
diff --git a/services/tracing/public/mojom/tracing_service.mojom b/services/tracing/public/mojom/tracing_service.mojom
index a57035c8..390e341 100644
--- a/services/tracing/public/mojom/tracing_service.mojom
+++ b/services/tracing/public/mojom/tracing_service.mojom
@@ -7,6 +7,7 @@
 [EnableIf=is_perfetto_supported_os]
 import "services/tracing/public/mojom/perfetto_service.mojom";
 
+import "sandbox/policy/mojom/sandbox.mojom";
 import "services/tracing/public/mojom/traced_process.mojom";
 
 // Represents a single client process to be traced.
@@ -20,6 +21,7 @@
 
 // The main interface to the Tracing service. This is only consumed by
 // privileged clients (e.g. browser process).
+[ServiceSandbox=sandbox.mojom.Sandbox.kUtility]
 interface TracingService {
   // Initializes the service with the current known set of running processes.
   Initialize(array<ClientInfo> clients);
diff --git a/testing/buildbot/chrome.json b/testing/buildbot/chrome.json
index 20ceb09..6ee8ad09 100644
--- a/testing/buildbot/chrome.json
+++ b/testing/buildbot/chrome.json
@@ -1805,6 +1805,29 @@
         "test_id_prefix": "ninja://ui/aura:aura_unittests/"
       },
       {
+        "args": [
+          "--enable-features=UseSkiaRenderer"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "skia_renderer_aura_unittests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04",
+              "pool": "chrome.tests",
+              "ssd": "0"
+            }
+          ],
+          "service_account": "chrome-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "aura_unittests",
+        "test_id_prefix": "ninja://ui/aura:aura_unittests/"
+      },
+      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
diff --git a/testing/buildbot/chromium.chromiumos.json b/testing/buildbot/chromium.chromiumos.json
index f7c14f69..8c2e698 100644
--- a/testing/buildbot/chromium.chromiumos.json
+++ b/testing/buildbot/chromium.chromiumos.json
@@ -1080,6 +1080,104 @@
       {
         "args": [
           "--browser=cros-chrome",
+          "--extra-browser-args=--enable-features=UseSkiaRenderer",
+          "--remote=127.0.0.1",
+          "--remote-ssh-port=9222",
+          "--xvfb",
+          "--typ-max-failures=3"
+        ],
+        "ci_only": true,
+        "isolate_name": "telemetry_perf_unittests",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "skia_renderer_telemetry_perf_unittests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86",
+              "kvm": "1",
+              "os": "Ubuntu-18.04",
+              "pool": "chromium.tests"
+            }
+          ],
+          "idempotent": false,
+          "named_caches": [
+            {
+              "name": "cros_vm",
+              "path": "magic_cros_vm_cache"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "cros_vm"
+              }
+            ]
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 6
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_perf_unittests/"
+      },
+      {
+        "args": [
+          "--jobs=1",
+          "--browser=cros-chrome",
+          "--extra-browser-args=--enable-features=UseSkiaRenderer",
+          "--remote=127.0.0.1",
+          "--remote-ssh-port=9222",
+          "--typ-max-failures=3"
+        ],
+        "ci_only": true,
+        "isolate_name": "telemetry_unittests",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "skia_renderer_telemetry_unittests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86",
+              "kvm": "1",
+              "os": "Ubuntu-18.04",
+              "pool": "chromium.tests"
+            }
+          ],
+          "idempotent": false,
+          "named_caches": [
+            {
+              "name": "cros_vm",
+              "path": "magic_cros_vm_cache"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "cros_vm"
+              }
+            ]
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 24
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_unittests/"
+      },
+      {
+        "args": [
+          "--browser=cros-chrome",
           "--remote=127.0.0.1",
           "--remote-ssh-port=9222",
           "--xvfb",
@@ -1698,6 +1796,27 @@
         "test_id_prefix": "ninja://ui/aura:aura_unittests/"
       },
       {
+        "args": [
+          "--enable-features=UseSkiaRenderer"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "skia_renderer_aura_unittests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "aura_unittests",
+        "test_id_prefix": "ninja://ui/aura:aura_unittests/"
+      },
+      {
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -3353,6 +3472,28 @@
         "test_id_prefix": "ninja://ui/aura:aura_unittests/"
       },
       {
+        "args": [
+          "--enable-features=UseSkiaRenderer"
+        ],
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "skia_renderer_aura_unittests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "aura_unittests",
+        "test_id_prefix": "ninja://ui/aura:aura_unittests/"
+      },
+      {
         "isolate_profile_data": true,
         "merge": {
           "args": [],
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index caddf4fb..a8d15fcc 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -19294,6 +19294,104 @@
       {
         "args": [
           "--browser=cros-chrome",
+          "--extra-browser-args=--enable-features=UseSkiaRenderer",
+          "--remote=127.0.0.1",
+          "--remote-ssh-port=9222",
+          "--xvfb",
+          "--typ-max-failures=3"
+        ],
+        "ci_only": true,
+        "isolate_name": "telemetry_perf_unittests",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "skia_renderer_telemetry_perf_unittests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86",
+              "kvm": "1",
+              "os": "Ubuntu-18.04",
+              "pool": "chromium.tests"
+            }
+          ],
+          "idempotent": false,
+          "named_caches": [
+            {
+              "name": "cros_vm",
+              "path": "magic_cros_vm_cache"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "cros_vm"
+              }
+            ]
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 6
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_perf_unittests/"
+      },
+      {
+        "args": [
+          "--jobs=1",
+          "--browser=cros-chrome",
+          "--extra-browser-args=--enable-features=UseSkiaRenderer",
+          "--remote=127.0.0.1",
+          "--remote-ssh-port=9222",
+          "--typ-max-failures=3"
+        ],
+        "ci_only": true,
+        "isolate_name": "telemetry_unittests",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "skia_renderer_telemetry_unittests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86",
+              "kvm": "1",
+              "os": "Ubuntu-18.04",
+              "pool": "chromium.tests"
+            }
+          ],
+          "idempotent": false,
+          "named_caches": [
+            {
+              "name": "cros_vm",
+              "path": "magic_cros_vm_cache"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "cros_vm"
+              }
+            ]
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 24
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_unittests/"
+      },
+      {
+        "args": [
+          "--browser=cros-chrome",
           "--remote=127.0.0.1",
           "--remote-ssh-port=9222",
           "--xvfb",
@@ -20470,6 +20568,104 @@
       {
         "args": [
           "--browser=cros-chrome",
+          "--extra-browser-args=--enable-features=UseSkiaRenderer",
+          "--remote=127.0.0.1",
+          "--remote-ssh-port=9222",
+          "--xvfb",
+          "--typ-max-failures=3"
+        ],
+        "ci_only": true,
+        "isolate_name": "telemetry_perf_unittests",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "skia_renderer_telemetry_perf_unittests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86",
+              "kvm": "1",
+              "os": "Ubuntu-18.04",
+              "pool": "chromium.tests"
+            }
+          ],
+          "idempotent": false,
+          "named_caches": [
+            {
+              "name": "cros_vm",
+              "path": "magic_cros_vm_cache"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "cros_vm"
+              }
+            ]
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 6
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_perf_unittests/"
+      },
+      {
+        "args": [
+          "--jobs=1",
+          "--browser=cros-chrome",
+          "--extra-browser-args=--enable-features=UseSkiaRenderer",
+          "--remote=127.0.0.1",
+          "--remote-ssh-port=9222",
+          "--typ-max-failures=3"
+        ],
+        "ci_only": true,
+        "isolate_name": "telemetry_unittests",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "skia_renderer_telemetry_unittests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86",
+              "kvm": "1",
+              "os": "Ubuntu-18.04",
+              "pool": "chromium.tests"
+            }
+          ],
+          "idempotent": false,
+          "named_caches": [
+            {
+              "name": "cros_vm",
+              "path": "magic_cros_vm_cache"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "cros_vm"
+              }
+            ]
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 24
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_unittests/"
+      },
+      {
+        "args": [
+          "--browser=cros-chrome",
           "--remote=127.0.0.1",
           "--remote-ssh-port=9222",
           "--xvfb",
@@ -21641,6 +21837,104 @@
       {
         "args": [
           "--browser=cros-chrome",
+          "--extra-browser-args=--enable-features=UseSkiaRenderer",
+          "--remote=127.0.0.1",
+          "--remote-ssh-port=9222",
+          "--xvfb",
+          "--typ-max-failures=3"
+        ],
+        "ci_only": true,
+        "isolate_name": "telemetry_perf_unittests",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "skia_renderer_telemetry_perf_unittests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86",
+              "kvm": "1",
+              "os": "Ubuntu-18.04",
+              "pool": "chromium.tests"
+            }
+          ],
+          "idempotent": false,
+          "named_caches": [
+            {
+              "name": "cros_vm",
+              "path": "magic_cros_vm_cache"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "cros_vm"
+              }
+            ]
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 6
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_perf_unittests/"
+      },
+      {
+        "args": [
+          "--jobs=1",
+          "--browser=cros-chrome",
+          "--extra-browser-args=--enable-features=UseSkiaRenderer",
+          "--remote=127.0.0.1",
+          "--remote-ssh-port=9222",
+          "--typ-max-failures=3"
+        ],
+        "ci_only": true,
+        "isolate_name": "telemetry_unittests",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "skia_renderer_telemetry_unittests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86",
+              "kvm": "1",
+              "os": "Ubuntu-18.04",
+              "pool": "chromium.tests"
+            }
+          ],
+          "idempotent": false,
+          "named_caches": [
+            {
+              "name": "cros_vm",
+              "path": "magic_cros_vm_cache"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "cros_vm"
+              }
+            ]
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 24
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_unittests/"
+      },
+      {
+        "args": [
+          "--browser=cros-chrome",
           "--remote=127.0.0.1",
           "--remote-ssh-port=9222",
           "--xvfb",
@@ -74851,6 +75145,28 @@
         "test_id_prefix": "ninja://ui/aura:aura_unittests/"
       },
       {
+        "args": [
+          "--enable-features=UseSkiaRenderer"
+        ],
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "skia_renderer_aura_unittests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "aura_unittests",
+        "test_id_prefix": "ninja://ui/aura:aura_unittests/"
+      },
+      {
         "isolate_profile_data": true,
         "merge": {
           "args": [],
diff --git a/testing/buildbot/chromium.gpu.fyi.json b/testing/buildbot/chromium.gpu.fyi.json
index 69bfa8d..a2d56a2 100644
--- a/testing/buildbot/chromium.gpu.fyi.json
+++ b/testing/buildbot/chromium.gpu.fyi.json
@@ -5737,6 +5737,382 @@
       },
       {
         "args": [
+          "context_lost",
+          "--show-stdout",
+          "--browser=cros-chrome",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--log-level=0 --js-flags=--expose-gc --disable-features=UseSkiaRenderer",
+          "--remote=127.0.0.1",
+          "--remote-ssh-port=9222"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "gl_renderer_context_lost_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "cpu": "x86",
+              "kvm": "1",
+              "os": "Ubuntu-18.04",
+              "pool": "chromium.tests"
+            }
+          ],
+          "idempotent": false,
+          "named_caches": [
+            {
+              "name": "cros_vm",
+              "path": "magic_cros_vm_cache"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "cros_vm"
+              }
+            ]
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "depth_capture",
+          "--show-stdout",
+          "--browser=cros-chrome",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--log-level=0 --js-flags=--expose-gc --disable-features=UseSkiaRenderer",
+          "--remote=127.0.0.1",
+          "--remote-ssh-port=9222"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "gl_renderer_depth_capture_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "cpu": "x86",
+              "kvm": "1",
+              "os": "Ubuntu-18.04",
+              "pool": "chromium.tests"
+            }
+          ],
+          "idempotent": false,
+          "named_caches": [
+            {
+              "name": "cros_vm",
+              "path": "magic_cros_vm_cache"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "cros_vm"
+              }
+            ]
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "gpu_process",
+          "--show-stdout",
+          "--browser=cros-chrome",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--log-level=0 --js-flags=--expose-gc --disable-features=UseSkiaRenderer",
+          "--remote=127.0.0.1",
+          "--remote-ssh-port=9222"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "gl_renderer_gpu_process_launch_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "cpu": "x86",
+              "kvm": "1",
+              "os": "Ubuntu-18.04",
+              "pool": "chromium.tests"
+            }
+          ],
+          "idempotent": false,
+          "named_caches": [
+            {
+              "name": "cros_vm",
+              "path": "magic_cros_vm_cache"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "cros_vm"
+              }
+            ]
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "hardware_accelerated_feature",
+          "--show-stdout",
+          "--browser=cros-chrome",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--log-level=0 --js-flags=--expose-gc --disable-features=UseSkiaRenderer",
+          "--remote=127.0.0.1",
+          "--remote-ssh-port=9222"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "gl_renderer_hardware_accelerated_feature_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "cpu": "x86",
+              "kvm": "1",
+              "os": "Ubuntu-18.04",
+              "pool": "chromium.tests"
+            }
+          ],
+          "idempotent": false,
+          "named_caches": [
+            {
+              "name": "cros_vm",
+              "path": "magic_cros_vm_cache"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "cros_vm"
+              }
+            ]
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "maps",
+          "--show-stdout",
+          "--browser=cros-chrome",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--log-level=0 --js-flags=--expose-gc --disable-features=UseSkiaRenderer",
+          "--dont-restore-color-profile-after-test",
+          "--test-machine-name",
+          "${buildername}",
+          "--remote=127.0.0.1",
+          "--remote-ssh-port=9222",
+          "--git-revision=${got_revision}"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "gl_renderer_maps_pixel_tests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "cpu": "x86",
+              "kvm": "1",
+              "os": "Ubuntu-18.04",
+              "pool": "chromium.tests"
+            }
+          ],
+          "idempotent": false,
+          "named_caches": [
+            {
+              "name": "cros_vm",
+              "path": "magic_cros_vm_cache"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "cros_vm"
+              }
+            ]
+          },
+          "service_account": "chrome-gpu-gold@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "pixel",
+          "--show-stdout",
+          "--browser=cros-chrome",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--log-level=0 --js-flags=--expose-gc --disable-features=UseSkiaRenderer",
+          "--dont-restore-color-profile-after-test",
+          "--test-machine-name",
+          "${buildername}",
+          "--remote=127.0.0.1",
+          "--remote-ssh-port=9222",
+          "--git-revision=${got_revision}"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "gl_renderer_pixel_skia_gold_tests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "cpu": "x86",
+              "kvm": "1",
+              "os": "Ubuntu-18.04",
+              "pool": "chromium.tests"
+            }
+          ],
+          "idempotent": false,
+          "named_caches": [
+            {
+              "name": "cros_vm",
+              "path": "magic_cros_vm_cache"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "cros_vm"
+              }
+            ]
+          },
+          "service_account": "chrome-gpu-gold@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "screenshot_sync",
+          "--show-stdout",
+          "--browser=cros-chrome",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--log-level=0 --js-flags=--expose-gc --disable-features=UseSkiaRenderer",
+          "--dont-restore-color-profile-after-test",
+          "--remote=127.0.0.1",
+          "--remote-ssh-port=9222"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "gl_renderer_screenshot_sync_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "cpu": "x86",
+              "kvm": "1",
+              "os": "Ubuntu-18.04",
+              "pool": "chromium.tests"
+            }
+          ],
+          "idempotent": false,
+          "named_caches": [
+            {
+              "name": "cros_vm",
+              "path": "magic_cros_vm_cache"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "cros_vm"
+              }
+            ]
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
           "gpu_process",
           "--show-stdout",
           "--browser=cros-chrome",
diff --git a/testing/buildbot/chromium.memory.json b/testing/buildbot/chromium.memory.json
index a26580f2..8ef8373 100644
--- a/testing/buildbot/chromium.memory.json
+++ b/testing/buildbot/chromium.memory.json
@@ -3725,6 +3725,28 @@
       },
       {
         "args": [
+          "--enable-features=UseSkiaRenderer",
+          "--test-launcher-print-test-stdio=always"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "skia_renderer_aura_unittests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-18.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "aura_unittests",
+        "test_id_prefix": "ninja://ui/aura:aura_unittests/"
+      },
+      {
+        "args": [
           "--test-launcher-print-test-stdio=always"
         ],
         "merge": {
@@ -5628,6 +5650,29 @@
       },
       {
         "args": [
+          "--enable-features=UseSkiaRenderer",
+          "--test-launcher-print-test-stdio=always"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "skia_renderer_aura_unittests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "aura_unittests",
+        "test_id_prefix": "ninja://ui/aura:aura_unittests/"
+      },
+      {
+        "args": [
           "--test-launcher-print-test-stdio=always"
         ],
         "merge": {
diff --git a/testing/buildbot/internal.chromeos.fyi.json b/testing/buildbot/internal.chromeos.fyi.json
index 3b004b8..3928459 100644
--- a/testing/buildbot/internal.chromeos.fyi.json
+++ b/testing/buildbot/internal.chromeos.fyi.json
@@ -1282,7 +1282,7 @@
       {
         "args": [],
         "cros_board": "atlas",
-        "cros_img": "atlas-release/R94-14105.0.0",
+        "cros_img": "atlas-release/R94-14140.0.0",
         "name": "lacros_fyi_tast_tests_ATLAS_TOT",
         "swarming": {},
         "tast_expr": "(\"group:mainline\" && \"dep:lacros\" && !informational)",
@@ -1293,7 +1293,7 @@
       {
         "args": [],
         "cros_board": "atlas",
-        "cros_img": "atlas-release/R93-14092.5.0",
+        "cros_img": "atlas-release/R93-14092.19.0",
         "name": "lacros_fyi_tast_tests_ATLAS_TOT-1",
         "swarming": {},
         "tast_expr": "(\"group:mainline\" && \"dep:lacros\" && !informational)",
@@ -1304,7 +1304,7 @@
       {
         "args": [],
         "cros_board": "atlas",
-        "cros_img": "atlas-release/R92-13982.60.0",
+        "cros_img": "atlas-release/R92-13982.69.0",
         "name": "lacros_fyi_tast_tests_ATLAS_TOT-2",
         "swarming": {},
         "tast_expr": "(\"group:mainline\" && \"dep:lacros\" && !informational)",
@@ -1315,7 +1315,7 @@
       {
         "args": [],
         "cros_board": "eve",
-        "cros_img": "eve-release/R94-14105.0.0",
+        "cros_img": "eve-release/R94-14140.0.0",
         "name": "lacros_fyi_tast_tests_EVE_TOT",
         "swarming": {},
         "tast_expr": "(\"group:mainline\" && \"dep:lacros\" && !informational)",
@@ -1326,7 +1326,7 @@
       {
         "args": [],
         "cros_board": "eve",
-        "cros_img": "eve-release/R93-14092.5.0",
+        "cros_img": "eve-release/R93-14092.13.0",
         "name": "lacros_fyi_tast_tests_EVE_TOT-1",
         "swarming": {},
         "tast_expr": "(\"group:mainline\" && \"dep:lacros\" && !informational)",
@@ -1337,7 +1337,7 @@
       {
         "args": [],
         "cros_board": "eve",
-        "cros_img": "eve-release/R92-13982.60.0",
+        "cros_img": "eve-release/R92-13982.69.0",
         "name": "lacros_fyi_tast_tests_EVE_TOT-2",
         "swarming": {},
         "tast_expr": "(\"group:mainline\" && \"dep:lacros\" && !informational)",
@@ -1348,7 +1348,7 @@
       {
         "args": [],
         "cros_board": "atlas",
-        "cros_img": "atlas-release/R94-14105.0.0",
+        "cros_img": "atlas-release/R94-14140.0.0",
         "name": "ozone_unittests_ATLAS_TOT",
         "swarming": {},
         "test": "ozone_unittests",
@@ -1358,7 +1358,7 @@
       {
         "args": [],
         "cros_board": "atlas",
-        "cros_img": "atlas-release/R93-14092.5.0",
+        "cros_img": "atlas-release/R93-14092.19.0",
         "name": "ozone_unittests_ATLAS_TOT-1",
         "swarming": {},
         "test": "ozone_unittests",
@@ -1368,7 +1368,7 @@
       {
         "args": [],
         "cros_board": "atlas",
-        "cros_img": "atlas-release/R92-13982.60.0",
+        "cros_img": "atlas-release/R92-13982.69.0",
         "name": "ozone_unittests_ATLAS_TOT-2",
         "swarming": {},
         "test": "ozone_unittests",
@@ -1378,7 +1378,7 @@
       {
         "args": [],
         "cros_board": "eve",
-        "cros_img": "eve-release/R94-14105.0.0",
+        "cros_img": "eve-release/R94-14140.0.0",
         "name": "ozone_unittests_EVE_TOT",
         "swarming": {},
         "test": "ozone_unittests",
@@ -1388,7 +1388,7 @@
       {
         "args": [],
         "cros_board": "eve",
-        "cros_img": "eve-release/R93-14092.5.0",
+        "cros_img": "eve-release/R93-14092.13.0",
         "name": "ozone_unittests_EVE_TOT-1",
         "swarming": {},
         "test": "ozone_unittests",
@@ -1398,7 +1398,7 @@
       {
         "args": [],
         "cros_board": "eve",
-        "cros_img": "eve-release/R92-13982.60.0",
+        "cros_img": "eve-release/R92-13982.69.0",
         "name": "ozone_unittests_EVE_TOT-2",
         "swarming": {},
         "test": "ozone_unittests",
@@ -1415,7 +1415,7 @@
       {
         "args": [],
         "cros_board": "kevin",
-        "cros_img": "kevin-release/R94-14105.0.0",
+        "cros_img": "kevin-release/R94-14140.0.0",
         "name": "lacros_fyi_tast_tests_KEVIN_TOT",
         "swarming": {},
         "tast_expr": "(\"group:mainline\" && \"dep:lacros\" && !informational)",
@@ -1426,7 +1426,7 @@
       {
         "args": [],
         "cros_board": "kevin",
-        "cros_img": "kevin-release/R94-14105.0.0",
+        "cros_img": "kevin-release/R94-14140.0.0",
         "name": "ozone_unittests_KEVIN_TOT",
         "swarming": {},
         "test": "ozone_unittests",
diff --git a/testing/buildbot/test_suite_exceptions.pyl b/testing/buildbot/test_suite_exceptions.pyl
index e40e78ae..1b2ebba 100644
--- a/testing/buildbot/test_suite_exceptions.pyl
+++ b/testing/buildbot/test_suite_exceptions.pyl
@@ -1564,6 +1564,51 @@
       'Linux Chromium OS ASan LSan Tests (1)',  # https://crbug.com/831667
     ],
   },
+  # TODO(crbug.com/1202958): Remove these GLRenderer variants when
+  # SkiaRenderer is fully shipped on Chrome OS.
+  # Don't run these on real devices due to capacity issues.
+  'gl_renderer_context_lost_tests': {
+    'remove_from': [
+      'ChromeOS FYI Release (kevin)',
+      'Lacros FYI x64 Release (Intel)',
+    ],
+  },
+  'gl_renderer_depth_capture_tests': {
+    'remove_from': [
+      'ChromeOS FYI Release (kevin)',
+      'Lacros FYI x64 Release (Intel)',
+    ],
+  },
+  'gl_renderer_gpu_process_launch_tests': {
+    'remove_from': [
+      'ChromeOS FYI Release (kevin)',
+      'Lacros FYI x64 Release (Intel)',
+    ],
+  },
+  'gl_renderer_hardware_accelerated_feature_tests': {
+    'remove_from': [
+      'ChromeOS FYI Release (kevin)',
+      'Lacros FYI x64 Release (Intel)',
+    ],
+  },
+  'gl_renderer_maps_pixel_tests': {
+    'remove_from': [
+      'ChromeOS FYI Release (kevin)',
+      'Lacros FYI x64 Release (Intel)',
+    ],
+  },
+  'gl_renderer_pixel_skia_gold_tests': {
+    'remove_from': [
+      'ChromeOS FYI Release (kevin)',
+      'Lacros FYI x64 Release (Intel)',
+    ],
+  },
+  'gl_renderer_screenshot_sync_tests': {
+    'remove_from': [
+      'ChromeOS FYI Release (kevin)',
+      'Lacros FYI x64 Release (Intel)',
+    ],
+  },
   'gl_tests_passthrough': {
     'remove_from': [
       'CFI Linux ToT',
@@ -2582,6 +2627,50 @@
       },
     }
   },
+  # TODO(crbug.com/1202958): Remove these when SkiaRenderer is fully shipped
+  # on Chrome OS.
+  'skia_renderer_telemetry_perf_unittests': {
+    'remove_from': [
+      # Not enough capacity.
+      'chromeos-betty-pi-arc-chrome',
+      'chromeos-betty-pi-arc-chrome-dchecks',
+    ],
+    'modifications': {
+      'chromeos-amd64-generic-rel': {
+        'ci_only': True,
+      },
+      'chromeos-amd64-generic-rel (goma cache silo)': {
+        'ci_only': True,
+      },
+      'chromeos-amd64-generic-rel (reclient)': {
+        'ci_only': True,
+      },
+      'chromeos-amd64-generic-rel-dchecks': {
+        'ci_only': True,
+      },
+    },
+  },
+  'skia_renderer_telemetry_unittests': {
+    'remove_from': [
+      # Not enough capacity.
+      'chromeos-betty-pi-arc-chrome',
+      'chromeos-betty-pi-arc-chrome-dchecks',
+    ],
+    'modifications': {
+      'chromeos-amd64-generic-rel': {
+        'ci_only': True,
+      },
+      'chromeos-amd64-generic-rel (goma cache silo)': {
+        'ci_only': True,
+      },
+      'chromeos-amd64-generic-rel (reclient)': {
+        'ci_only': True,
+      },
+      'chromeos-amd64-generic-rel-dchecks': {
+        'ci_only': True,
+      },
+    },
+  },
   'swiftshader_unittests': {
     'remove_from': [
       # Save capacity on the hardware where we have only a few machines.
diff --git a/testing/buildbot/test_suites.pyl b/testing/buildbot/test_suites.pyl
index 74ba631..2a89d2c 100644
--- a/testing/buildbot/test_suites.pyl
+++ b/testing/buildbot/test_suites.pyl
@@ -557,6 +557,42 @@
     },
 
     'chromeos_isolated_scripts': {
+      # TODO(crbug.com/1202958): Remove these SkiaRenderer variants when
+      # SkiaRenderer is shipped by default on Chrome OS.
+      'skia_renderer_telemetry_perf_unittests': {
+        'isolate_name': 'telemetry_perf_unittests',
+        'args': [
+          '--browser=cros-chrome',
+          '--extra-browser-args=--enable-features=UseSkiaRenderer',
+          '$$MAGIC_SUBSTITUTION_ChromeOSTelemetryRemote',
+          '--xvfb',
+          # 3 is arbitrary, but if we're having more than 3 of these tests
+          # fail in a single shard, then something is probably wrong, so fail
+          # fast.
+          '--typ-max-failures=3',
+        ],
+        'swarming': {
+          'idempotent': False,  # https://crbug.com/549140
+          'shards': 6,
+        },
+      },
+      'skia_renderer_telemetry_unittests': {
+        'isolate_name': 'telemetry_unittests',
+        'args': [
+          '--jobs=1',
+          '--browser=cros-chrome',
+          '--extra-browser-args=--enable-features=UseSkiaRenderer',
+          '$$MAGIC_SUBSTITUTION_ChromeOSTelemetryRemote',
+          # 3 is arbitrary, but if we're having more than 3 of these tests
+          # fail in a single shard, then something is probably wrong, so fail
+          # fast.
+          '--typ-max-failures=3',
+        ],
+        'swarming': {
+          'idempotent': False,  # https://crbug.com/549140
+          'shards': 24,
+        },
+      },
       'telemetry_perf_unittests': {
         'args': [
           '--browser=cros-chrome',
@@ -2834,13 +2870,17 @@
       },
     },
 
-    # TODO(crbug.com/894929): remove it when SkiaRenderer is shipped on macOS.
+    # TODO(crbug.com/1202958): Remove all gpu_gl_renderer_* targets when
+    # SkiaRenderer is shipped on all platforms.
     'gpu_gl_renderer_full_telemetry_tests': {
       'gl_renderer_context_lost_tests': {
         'telemetry_test_name': 'context_lost',
         'args': [
           '--extra-browser-args=--disable-features=UseSkiaRenderer',
         ],
+        'chromeos_args': [
+          '$$MAGIC_SUBSTITUTION_ChromeOSTelemetryRemote',
+        ],
         'mixins': [
           'has_native_resultdb_integration',
         ],
@@ -2850,6 +2890,9 @@
         'args': [
           '--extra-browser-args=--disable-features=UseSkiaRenderer',
         ],
+        'chromeos_args': [
+          '$$MAGIC_SUBSTITUTION_ChromeOSTelemetryRemote',
+        ],
         'mixins': [
           'has_native_resultdb_integration',
         ],
@@ -2859,6 +2902,9 @@
         'args': [
           '--extra-browser-args=--disable-features=UseSkiaRenderer',
         ],
+        'chromeos_args': [
+          '$$MAGIC_SUBSTITUTION_ChromeOSTelemetryRemote',
+        ],
         'mixins': [
           'has_native_resultdb_integration',
         ],
@@ -2868,6 +2914,9 @@
         'args': [
           '--extra-browser-args=--disable-features=UseSkiaRenderer',
         ],
+        'chromeos_args': [
+          '$$MAGIC_SUBSTITUTION_ChromeOSTelemetryRemote',
+        ],
         'mixins': [
           'has_native_resultdb_integration',
         ],
@@ -2880,6 +2929,9 @@
           '${buildername}',
           '--extra-browser-args=--disable-features=UseSkiaRenderer',
         ],
+        'chromeos_args': [
+          '$$MAGIC_SUBSTITUTION_ChromeOSTelemetryRemote',
+        ],
         'mixins': [
           'chrome-gpu-gold-service-account',
           'has_native_resultdb_integration',
@@ -2894,6 +2946,9 @@
           '${buildername}',
           '--extra-browser-args=--disable-features=UseSkiaRenderer',
         ],
+        'chromeos_args': [
+          '$$MAGIC_SUBSTITUTION_ChromeOSTelemetryRemote',
+        ],
         'mixins': [
           'chrome-gpu-gold-service-account',
           'has_native_resultdb_integration',
@@ -2906,6 +2961,9 @@
           '--dont-restore-color-profile-after-test',
           '--extra-browser-args=--disable-features=UseSkiaRenderer',
         ],
+        'chromeos_args': [
+          '$$MAGIC_SUBSTITUTION_ChromeOSTelemetryRemote',
+        ],
         'mixins': [
           'has_native_resultdb_integration',
         ],
@@ -4265,7 +4323,6 @@
       'unit_tests': {},
     },
 
-
     'linux_chromeos_lacros_gtests': {
       # Chrome OS (Ash) and Lacros only.
       'chromeos_unittests': {},
@@ -4287,6 +4344,24 @@
       },
     },
 
+    # TODO(crbug.com/1202958): Remove this when SkiaRenderer is fully shipped
+    # on Chrome OS.
+    'linux_chromeos_skia_renderer_specific_gtests': {
+      # TODO(b/195938162): Re-enable this when animation tests are deflaked.
+      # 'skia_renderer_ash_unittests': {
+      #   'test': 'ash_unittests',
+      #   'args': [
+      #     '--enable-features=UseSkiaRenderer',
+      #   ],
+      # },
+      'skia_renderer_aura_unittests': {
+        'test': 'aura_unittests',
+        'args': [
+          '--enable-features=UseSkiaRenderer',
+        ],
+      },
+    },
+
     'linux_chromeos_specific_gtests': {
       # Chrome OS only.
       'ash_unittests': {},
@@ -6368,6 +6443,7 @@
       'gpu_validating_telemetry_tests',
       'gpu_webcodecs_telemetry_test',
       'gpu_webgl_conformance_validating_telemetry_tests',
+      'gpu_gl_renderer_full_telemetry_tests',
       # Large amounts of WebGL/WebGL2 tests are failing due to issues that are
       # possibly related to other CrOS issues that are already reported.
       # TODO(crbug.com/1080424): Try enabling these again once some of the
@@ -6742,6 +6818,7 @@
       'chromium_gtests_for_linux_and_chromeos_only',
       'chromium_gtests_for_win_and_linux_only',
       'linux_chromeos_lacros_gtests',
+      'linux_chromeos_skia_renderer_specific_gtests',
       'linux_chromeos_specific_gtests',
       'linux_flavor_specific_chromium_gtests',
       'non_android_chromium_gtests',
@@ -6755,6 +6832,7 @@
       'chromium_gtests_for_win_and_linux_only',
       'linux_chromeos_lacros_gtests',
       'linux_chromeos_oobe_specific_tests',
+      'linux_chromeos_skia_renderer_specific_gtests',
       'linux_chromeos_specific_gtests',
       'linux_flavor_specific_chromium_gtests',
       'non_android_chromium_gtests',
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl
index b5c4566..1c50a0e 100644
--- a/testing/buildbot/variants.pyl
+++ b/testing/buildbot/variants.pyl
@@ -549,49 +549,49 @@
   'CROS_ATLAS_TOT': {
     'skylab': {
       'cros_board': 'atlas',
-      'cros_img': 'atlas-release/R94-14105.0.0',
+      'cros_img': 'atlas-release/R94-14140.0.0',
     },
     'identifier': 'ATLAS_TOT',
   },
   'CROS_ATLAS_TOT-1': {
     'skylab': {
       'cros_board': 'atlas',
-      'cros_img': 'atlas-release/R93-14092.5.0',
+      'cros_img': 'atlas-release/R93-14092.19.0',
     },
     'identifier': 'ATLAS_TOT-1',
   },
   'CROS_ATLAS_TOT-2': {
     'skylab': {
       'cros_board': 'atlas',
-      'cros_img': 'atlas-release/R92-13982.60.0',
+      'cros_img': 'atlas-release/R92-13982.69.0',
     },
     'identifier': 'ATLAS_TOT-2',
   },
   'CROS_EVE_TOT': {
     'skylab': {
       'cros_board': 'eve',
-      'cros_img': 'eve-release/R94-14105.0.0',
+      'cros_img': 'eve-release/R94-14140.0.0',
     },
     'identifier': 'EVE_TOT',
   },
   'CROS_EVE_TOT-1': {
     'skylab': {
       'cros_board': 'eve',
-      'cros_img': 'eve-release/R93-14092.5.0',
+      'cros_img': 'eve-release/R93-14092.13.0',
     },
     'identifier': 'EVE_TOT-1',
   },
   'CROS_EVE_TOT-2': {
     'skylab': {
       'cros_board': 'eve',
-      'cros_img': 'eve-release/R92-13982.60.0',
+      'cros_img': 'eve-release/R92-13982.69.0',
     },
     'identifier': 'EVE_TOT-2',
   },
   'CROS_KEVIN_TOT': {
     'skylab': {
       'cros_board': 'kevin',
-      'cros_img': 'kevin-release/R94-14105.0.0',
+      'cros_img': 'kevin-release/R94-14140.0.0',
     },
     'identifier': 'KEVIN_TOT',
   },
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index ea50bbf..84e628a4 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -153,6 +153,24 @@
             ]
         }
     ],
+    "AndroidElasticOverscroll": [
+        {
+            "platforms": [
+                "android"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "params": {
+                        "type": "transform"
+                    },
+                    "enable_features": [
+                        "ElasticOverscroll"
+                    ]
+                }
+            ]
+        }
+    ],
     "AndroidHatsNext": [
         {
             "platforms": [
@@ -4939,6 +4957,32 @@
             ]
         }
     ],
+    "MeetDevicesMojoServices": [
+        {
+            "platforms": [
+                "chromeos"
+            ],
+            "experiments": [
+                {
+                    "name": "Hotlog-Experiment",
+                    "params": {
+                        "CommandLoggerEnabled": "true",
+                        "CommandLoggerFrequencySeconds": "30",
+                        "CrosHealthdProducerFrequencySeconds": "60",
+                        "DiagnosticsLoggerEnabled": "true",
+                        "ProcessLoggerEnabled": "true",
+                        "SystemLoggerEnabled": "true",
+                        "SystemLoggerFrequencySeconds": "30"
+                    },
+                    "enable_features": [
+                        "EncryptedReportingPipeline",
+                        "MeetDevicesCloudLogger",
+                        "MeetDevicesMojoServices"
+                    ]
+                }
+            ]
+        }
+    ],
     "MemoriesHistoryClusters": [
         {
             "platforms": [
@@ -8736,6 +8780,7 @@
             "platforms": [
                 "android",
                 "android_weblayer",
+                "chromeos",
                 "linux",
                 "mac",
                 "windows"
diff --git a/third_party/blink/public/devtools_protocol/browser_protocol.pdl b/third_party/blink/public/devtools_protocol/browser_protocol.pdl
index c5c5726..cf556789 100644
--- a/third_party/blink/public/devtools_protocol/browser_protocol.pdl
+++ b/third_party/blink/public/devtools_protocol/browser_protocol.pdl
@@ -8815,6 +8815,8 @@
   # Controls whether to automatically attach to new targets which are considered to be related to
   # this one. When turned on, attaches to all existing related targets as well. When turned off,
   # automatically detaches from all currently attached targets.
+  # This also clears all targets added by `autoAttachRelated` from the list of targets to watch
+  # for creation of related targets.
   experimental command setAutoAttach
     parameters
       # Whether to auto-attach to related targets.
@@ -8827,6 +8829,17 @@
       # and eventually retire it. See crbug.com/991325.
       optional boolean flatten
 
+  # Adds the specified target to the list of targets that will be monitored for any related target
+  # creation (such as child frames, child workers and new versions of service worker) and reported
+  # through `attachedToTarget`. This cancel the effect of any previous `setAutoAttach` and is also
+  # cancelled by subsequent `setAutoAttach`. Only available at the Browser target.
+  experimental command autoAttachRelated
+    parameters
+      TargetID targetId
+      # Whether to pause new targets when attaching to them. Use `Runtime.runIfWaitingForDebugger`
+      # to run paused targets.
+      boolean waitForDebuggerOnStart
+
   # Controls whether to discover available targets and notify via
   # `targetCreated/targetInfoChanged/targetDestroyed` events.
   command setDiscoverTargets
diff --git a/third_party/blink/public/mojom/loader/referrer.mojom b/third_party/blink/public/mojom/loader/referrer.mojom
index 8f3aba5..ffcca24 100644
--- a/third_party/blink/public/mojom/loader/referrer.mojom
+++ b/third_party/blink/public/mojom/loader/referrer.mojom
@@ -12,5 +12,5 @@
 // up being used for URL requests, always use this struct.
 struct Referrer {
   url.mojom.Url url;
-  network.mojom.ReferrerPolicy policy;
+  network.mojom.ReferrerPolicy policy = network.mojom.ReferrerPolicy.kDefault;
 };
diff --git a/third_party/blink/public/mojom/webauthn/authenticator.mojom b/third_party/blink/public/mojom/webauthn/authenticator.mojom
index c56feb9e8..19d0d761 100644
--- a/third_party/blink/public/mojom/webauthn/authenticator.mojom
+++ b/third_party/blink/public/mojom/webauthn/authenticator.mojom
@@ -293,6 +293,8 @@
 
 // Payment parameters passed into calls to GetAssertion for Secure Payment
 // Confirmation.
+// TODO(https://crbug.com/1239249): Convert to a non-mojo struct, because this
+// is not used in any mojo interfaces.
 struct PaymentOptions {
   payments.mojom.PaymentCurrencyAmount total;
   payments.mojom.PaymentCredentialInstrument instrument;
@@ -362,9 +364,6 @@
   // If true, attempt to read a credblob.
   // https://fidoalliance.org/specs/fido-v2.1-rd-20201208/fido-client-to-authenticator-protocol-v2.1-rd-20201208.html#sctn-credBlob-extension
   bool get_cred_blob;
-
-  // If present, put the payment information into clientDataJSON.
-  PaymentOptions? payment;
 };
 
 // See https://w3c.github.io/webauthn/#enumdef-attestationconveyancepreference
diff --git a/third_party/blink/public/web/web_document.h b/third_party/blink/public/web/web_document.h
index b463f30c..9b465ac 100644
--- a/third_party/blink/public/web/web_document.h
+++ b/third_party/blink/public/web/web_document.h
@@ -108,7 +108,7 @@
   BLINK_EXPORT WebElement Head();
   BLINK_EXPORT WebString Title() const;
   BLINK_EXPORT WebString ContentAsTextForTesting() const;
-  BLINK_EXPORT WebElementCollection All();
+  BLINK_EXPORT WebElementCollection All() const;
   BLINK_EXPORT WebVector<WebFormElement> Forms() const;
   BLINK_EXPORT WebURL CompleteURL(const WebString&) const;
   BLINK_EXPORT WebElement GetElementById(const WebString&) const;
diff --git a/third_party/blink/renderer/build/scripts/core/style/make_computed_style_base.py b/third_party/blink/renderer/build/scripts/core/style/make_computed_style_base.py
index 130386e..fc519151 100755
--- a/third_party/blink/renderer/build/scripts/core/style/make_computed_style_base.py
+++ b/third_party/blink/renderer/build/scripts/core/style/make_computed_style_base.py
@@ -166,7 +166,7 @@
             assert name in [
                 field.property_name for field in root_group.all_fields], \
                 "The field '{}' isn't a defined field on ComputedStyle. " \
-                "Please check that there's an entry for '{}' in" \
+                "Please check that there's an entry for '{}' in " \
                 "css_properties.json5 or " \
                 "computed_style_extra_fields.json5".format(name, name)
         diff_functions_map[entry['name'].original] = _create_diff_groups(
diff --git a/third_party/blink/renderer/core/css/counter_style_map.cc b/third_party/blink/renderer/core/css/counter_style_map.cc
index 31d7c9f9..17f0c9a 100644
--- a/third_party/blink/renderer/core/css/counter_style_map.cc
+++ b/third_party/blink/renderer/core/css/counter_style_map.cc
@@ -59,8 +59,9 @@
     if (!counter_style)
       continue;
     AtomicString name = rule->GetName();
-    if (CounterStyle* replaced = counter_styles_.DeprecatedAtOrEmptyValue(name))
-      replaced->SetIsDirty();
+    auto replaced_iter = counter_styles_.find(name);
+    if (replaced_iter != counter_styles_.end())
+      replaced_iter->value->SetIsDirty();
     counter_styles_.Set(rule->GetName(), counter_style);
   }
 
@@ -99,9 +100,9 @@
       return iter->value;
     return &const_cast<CounterStyleMap*>(this)->CreateUACounterStyle(name);
   }
-
-  if (CounterStyle* style = counter_styles_.DeprecatedAtOrEmptyValue(name))
-    return style;
+  auto it = counter_styles_.find(name);
+  if (it != counter_styles_.end())
+    return it->value;
   return GetAncestorMap()->FindCounterStyleAcrossScopes(name);
 }
 
diff --git a/third_party/blink/renderer/core/css/css_paint_value.cc b/third_party/blink/renderer/core/css/css_paint_value.cc
index 42d810d7..1d85e9f 100644
--- a/third_party/blink/renderer/core/css/css_paint_value.cc
+++ b/third_party/blink/renderer/core/css/css_paint_value.cc
@@ -63,30 +63,27 @@
 
 const Vector<CSSPropertyID>* CSSPaintValue::NativeInvalidationProperties(
     const Document& document) const {
-  const CSSPaintImageGenerator* generator =
-      generators_.DeprecatedAtOrEmptyValue(&document);
-  if (!generator)
+  auto it = generators_.find(&document);
+  if (it == generators_.end())
     return nullptr;
-  return &generator->NativeInvalidationProperties();
+  return &it->value->NativeInvalidationProperties();
 }
 
 const Vector<AtomicString>* CSSPaintValue::CustomInvalidationProperties(
     const Document& document) const {
-  const CSSPaintImageGenerator* generator =
-      generators_.DeprecatedAtOrEmptyValue(&document);
-  if (!generator)
+  auto it = generators_.find(&document);
+  if (it == generators_.end())
     return nullptr;
-  return &generator->CustomInvalidationProperties();
+  return &it->value->CustomInvalidationProperties();
 }
 
 bool CSSPaintValue::IsUsingCustomProperty(
     const AtomicString& custom_property_name,
     const Document& document) const {
-  const CSSPaintImageGenerator* generator =
-      generators_.DeprecatedAtOrEmptyValue(&document);
-  if (!generator || !generator->IsImageGeneratorReady())
+  auto it = generators_.find(&document);
+  if (it == generators_.end() || !it->value->IsImageGeneratorReady())
     return false;
-  return generator->CustomInvalidationProperties().Contains(
+  return it->value->CustomInvalidationProperties().Contains(
       custom_property_name);
 }
 
@@ -201,10 +198,14 @@
       !RuntimeEnabledFeatures::CSSPaintAPIArgumentsEnabled())
     return true;
 
-  DCHECK(
-      generators_.DeprecatedAtOrEmptyValue(&document)->IsImageGeneratorReady());
+  auto it = generators_.find(&document);
+  if (it == generators_.end()) {
+    input_arguments_invalid_ = true;
+    return false;
+  }
+  DCHECK(it->value->IsImageGeneratorReady());
   const Vector<CSSSyntaxDefinition>& input_argument_types =
-      generators_.DeprecatedAtOrEmptyValue(&document)->InputArgumentTypes();
+      it->value->InputArgumentTypes();
   if (argument_variable_data_.size() != input_argument_types.size()) {
     input_arguments_invalid_ = true;
     return false;
@@ -245,9 +246,8 @@
 
 bool CSSPaintValue::KnownToBeOpaque(const Document& document,
                                     const ComputedStyle&) const {
-  const CSSPaintImageGenerator* generator =
-      generators_.DeprecatedAtOrEmptyValue(&document);
-  return generator && !generator->HasAlpha();
+  auto it = generators_.find(&document);
+  return it != generators_.end() && !it->value->HasAlpha();
 }
 
 bool CSSPaintValue::Equals(const CSSPaintValue& other) const {
diff --git a/third_party/blink/renderer/core/css/css_value_keywords.json5 b/third_party/blink/renderer/core/css/css_value_keywords.json5
index c96876f..efd4db0c 100644
--- a/third_party/blink/renderer/core/css/css_value_keywords.json5
+++ b/third_party/blink/renderer/core/css/css_value_keywords.json5
@@ -1442,5 +1442,8 @@
     // ([video]-dynamic-range) media feature
     "standard",
     "high",
+
+    // Layered @import rule
+    "layer",
   ],
 }
diff --git a/third_party/blink/renderer/core/css/cssom/prepopulated_computed_style_property_map.cc b/third_party/blink/renderer/core/css/cssom/prepopulated_computed_style_property_map.cc
index 6dff022d..7946da2 100644
--- a/third_party/blink/renderer/core/css/cssom/prepopulated_computed_style_property_map.cc
+++ b/third_party/blink/renderer/core/css/cssom/prepopulated_computed_style_property_map.cc
@@ -87,7 +87,8 @@
 
 const CSSValue* PrepopulatedComputedStylePropertyMap::GetCustomProperty(
     AtomicString property_name) const {
-  return custom_values_.DeprecatedAtOrEmptyValue(property_name);
+  auto it = custom_values_.find(property_name);
+  return it != custom_values_.end() ? it->value : nullptr;
 }
 
 void PrepopulatedComputedStylePropertyMap::ForEachProperty(
diff --git a/third_party/blink/renderer/core/css/media_feature_overrides.h b/third_party/blink/renderer/core/css/media_feature_overrides.h
index 5ad58a1..3dd0479c 100644
--- a/third_party/blink/renderer/core/css/media_feature_overrides.h
+++ b/third_party/blink/renderer/core/css/media_feature_overrides.h
@@ -15,7 +15,8 @@
  public:
   void SetOverride(const AtomicString& feature, const String& value_string);
   MediaQueryExpValue GetOverride(const AtomicString& feature) const {
-    return overrides_.DeprecatedAtOrEmptyValue(feature);
+    auto it = overrides_.find(feature);
+    return it != overrides_.end() ? it->value : MediaQueryExpValue();
   }
 
  private:
diff --git a/third_party/blink/renderer/core/css/parser/css_parser_impl.cc b/third_party/blink/renderer/core/css/parser/css_parser_impl.cc
index e4559ea..d143617 100644
--- a/third_party/blink/renderer/core/css/parser/css_parser_impl.cc
+++ b/third_party/blink/renderer/core/css/parser/css_parser_impl.cc
@@ -704,6 +704,27 @@
   if (uri.IsNull())
     return nullptr;  // Parse error, expected string or URI
 
+  absl::optional<StyleRuleBase::LayerName> layer;
+  if (RuntimeEnabledFeatures::CSSCascadeLayersEnabled()) {
+    if (prelude.Peek().GetType() == kIdentToken &&
+        prelude.Peek().Id() == CSSValueID::kLayer) {
+      prelude.ConsumeIncludingWhitespace();
+      layer = StyleRuleBase::LayerName();
+    } else if (prelude.Peek().GetType() == kFunctionToken &&
+               prelude.Peek().FunctionId() == CSSValueID::kLayer) {
+      CSSParserTokenRange original_prelude = prelude;
+      CSSParserTokenRange name_range =
+          css_parsing_utils::ConsumeFunction(prelude);
+      StyleRuleBase::LayerName name = ConsumeCascadeLayerName(name_range);
+      if (!name.size() || !name_range.AtEnd()) {
+        // Invalid layer() function can still be parsed as <general-enclosed>
+        prelude = original_prelude;
+      } else {
+        layer = std::move(name);
+      }
+    }
+  }
+
   if (observer_) {
     observer_->StartRuleHeader(StyleRule::kImport, prelude_offset_start);
     observer_->EndRuleHeader(prelude_offset_end);
@@ -712,7 +733,7 @@
   }
 
   return MakeGarbageCollected<StyleRuleImport>(
-      uri,
+      uri, std::move(layer),
       MediaQueryParser::ParseMediaQuerySet(prelude,
                                            context_->GetExecutionContext()),
       context_->IsOriginClean() ? OriginClean::kTrue : OriginClean::kFalse);
diff --git a/third_party/blink/renderer/core/css/parser/css_parser_impl_test.cc b/third_party/blink/renderer/core/css/parser/css_parser_impl_test.cc
index d6c99df5..fca18b66 100644
--- a/third_party/blink/renderer/core/css/parser/css_parser_impl_test.cc
+++ b/third_party/blink/renderer/core/css/parser/css_parser_impl_test.cc
@@ -10,6 +10,7 @@
 #include "third_party/blink/renderer/core/css/parser/css_parser_observer.h"
 #include "third_party/blink/renderer/core/css/parser/css_parser_token_stream.h"
 #include "third_party/blink/renderer/core/css/parser/css_tokenizer.h"
+#include "third_party/blink/renderer/core/css/style_rule_import.h"
 #include "third_party/blink/renderer/core/css/style_sheet_contents.h"
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
@@ -454,4 +455,146 @@
   }
 }
 
+TEST(CSSParserImplTest, LayeredImportDisabled) {
+  ScopedCSSCascadeLayersForTest disabled_scope(false);
+
+  using css_test_helpers::ParseRule;
+  Document* document = Document::CreateForTest();
+
+  // When the feature is disabled, layered @import rules should still parse and
+  // the layer keyword/function should be parsed as a <general-enclosed>, and
+  // hence has no effect.
+
+  {
+    String rule = "@import url(foo.css) layer;";
+    auto* parsed = DynamicTo<StyleRuleImport>(ParseRule(*document, rule));
+    ASSERT_TRUE(parsed);
+    EXPECT_FALSE(parsed->IsLayered());
+    EXPECT_EQ("layer", parsed->MediaQueries()->MediaText());
+  }
+
+  {
+    String rule = "@import url(foo.css) layer(bar);";
+    auto* parsed = DynamicTo<StyleRuleImport>(ParseRule(*document, rule));
+    ASSERT_TRUE(parsed);
+    EXPECT_FALSE(parsed->IsLayered());
+    EXPECT_EQ("not all", parsed->MediaQueries()->MediaText());
+  }
+
+  {
+    String rule = "@import url(foo.css) layer(bar.baz);";
+    auto* parsed = DynamicTo<StyleRuleImport>(ParseRule(*document, rule));
+    ASSERT_TRUE(parsed);
+    EXPECT_FALSE(parsed->IsLayered());
+    EXPECT_EQ("not all", parsed->MediaQueries()->MediaText());
+  }
+}
+
+TEST(CSSParserImplTest, LayeredImportRules) {
+  ScopedCSSCascadeLayersForTest enabled_scope(true);
+
+  using css_test_helpers::ParseRule;
+  Document* document = Document::CreateForTest();
+
+  {
+    String rule = "@import url(foo.css) layer;";
+    auto* parsed = DynamicTo<StyleRuleImport>(ParseRule(*document, rule));
+    ASSERT_TRUE(parsed);
+    ASSERT_TRUE(parsed->IsLayered());
+    EXPECT_EQ(0u, parsed->GetLayerName().size());
+  }
+
+  {
+    String rule = "@import url(foo.css) layer(bar);";
+    auto* parsed = DynamicTo<StyleRuleImport>(ParseRule(*document, rule));
+    ASSERT_TRUE(parsed);
+    ASSERT_TRUE(parsed->IsLayered());
+    ASSERT_EQ(1u, parsed->GetLayerName().size());
+    EXPECT_EQ("bar", parsed->GetLayerName()[0]);
+  }
+
+  {
+    String rule = "@import url(foo.css) layer(bar.baz);";
+    auto* parsed = DynamicTo<StyleRuleImport>(ParseRule(*document, rule));
+    ASSERT_TRUE(parsed);
+    ASSERT_TRUE(parsed->IsLayered());
+    ASSERT_EQ(2u, parsed->GetLayerName().size());
+    EXPECT_EQ("bar", parsed->GetLayerName()[0]);
+    EXPECT_EQ("baz", parsed->GetLayerName()[1]);
+  }
+}
+
+TEST(CSSParserImplTest, LayeredImportRulesInvalid) {
+  ScopedCSSCascadeLayersForTest enabled_scope(true);
+
+  using css_test_helpers::ParseRule;
+  Document* document = Document::CreateForTest();
+
+  // Invalid layer declarations in @import rules should not make the entire rule
+  // invalid. They should be parsed as <general-enclosed> and have no effect.
+
+  {
+    String rule = "@import url(foo.css) layer();";
+    auto* parsed = DynamicTo<StyleRuleImport>(ParseRule(*document, rule));
+    ASSERT_TRUE(parsed);
+    EXPECT_FALSE(parsed->IsLayered());
+    EXPECT_EQ("not all", parsed->MediaQueries()->MediaText());
+  }
+
+  {
+    String rule = "@import url(foo.css) layer(bar, baz);";
+    auto* parsed = DynamicTo<StyleRuleImport>(ParseRule(*document, rule));
+    ASSERT_TRUE(parsed);
+    EXPECT_FALSE(parsed->IsLayered());
+    EXPECT_EQ("not all", parsed->MediaQueries()->MediaText());
+  }
+
+  {
+    String rule = "@import url(foo.css) layer(bar.baz.);";
+    auto* parsed = DynamicTo<StyleRuleImport>(ParseRule(*document, rule));
+    ASSERT_TRUE(parsed);
+    EXPECT_FALSE(parsed->IsLayered());
+    EXPECT_EQ("not all", parsed->MediaQueries()->MediaText());
+  }
+}
+
+TEST(CSSParserImplTest, LayeredImportRulesMultipleLayers) {
+  ScopedCSSCascadeLayersForTest enabled_scope(true);
+
+  using css_test_helpers::ParseRule;
+  Document* document = Document::CreateForTest();
+
+  // If an @import rule has more than one layer keyword/function, only the first
+  // one is parsed as layer, and the remaining ones are parsed as
+  // <general-enclosed> and hence have no effect.
+
+  {
+    String rule = "@import url(foo.css) layer layer;";
+    auto* parsed = DynamicTo<StyleRuleImport>(ParseRule(*document, rule));
+    ASSERT_TRUE(parsed);
+    ASSERT_TRUE(parsed->IsLayered());
+    EXPECT_EQ(0u, parsed->GetLayerName().size());
+    EXPECT_EQ("layer", parsed->MediaQueries()->MediaText());
+  }
+
+  {
+    String rule = "@import url(foo.css) layer layer(bar);";
+    auto* parsed = DynamicTo<StyleRuleImport>(ParseRule(*document, rule));
+    ASSERT_TRUE(parsed);
+    ASSERT_TRUE(parsed->IsLayered());
+    EXPECT_EQ(0u, parsed->GetLayerName().size());
+    EXPECT_EQ("not all", parsed->MediaQueries()->MediaText());
+  }
+
+  {
+    String rule = "@import url(foo.css) layer(bar) layer;";
+    auto* parsed = DynamicTo<StyleRuleImport>(ParseRule(*document, rule));
+    ASSERT_TRUE(parsed);
+    ASSERT_TRUE(parsed->IsLayered());
+    ASSERT_EQ(1u, parsed->GetLayerName().size());
+    EXPECT_EQ("bar", parsed->GetLayerName()[0]);
+    EXPECT_EQ("layer", parsed->MediaQueries()->MediaText());
+  }
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/property_registry.cc b/third_party/blink/renderer/core/css/property_registry.cc
index 6150a83..6ad3d1f 100644
--- a/third_party/blink/renderer/core/css/property_registry.cc
+++ b/third_party/blink/renderer/core/css/property_registry.cc
@@ -32,10 +32,11 @@
   // the registration from CSS.registerProperty must win.
   //
   // https://drafts.css-houdini.org/css-properties-values-api-1/#determining-registration
-  if (const auto* registration =
-          registered_properties_.DeprecatedAtOrEmptyValue(name))
-    return registration;
-  return declared_properties_.DeprecatedAtOrEmptyValue(name);
+  auto it = registered_properties_.find(name);
+  if (it != registered_properties_.end())
+    return it->value;
+  it = declared_properties_.find(name);
+  return it != declared_properties_.end() ? it->value : nullptr;
 }
 
 bool PropertyRegistry::IsEmpty() const {
diff --git a/third_party/blink/renderer/core/css/resolver/cascade_map.cc b/third_party/blink/renderer/core/css/resolver/cascade_map.cc
index 91ac523..1417d90c 100644
--- a/third_party/blink/renderer/core/css/resolver/cascade_map.cc
+++ b/third_party/blink/renderer/core/css/resolver/cascade_map.cc
@@ -48,7 +48,8 @@
 
 inline CascadePriority AtCustom(const CSSPropertyName& name,
                                 const CascadeMap::CustomMap& map) {
-  return map.DeprecatedAtOrEmptyValue(name);
+  auto it = map.find(name);
+  return it != map.end() ? it->value : CascadePriority();
 }
 
 inline CascadePriority AtNative(const CSSPropertyName& name,
diff --git a/third_party/blink/renderer/core/css/rule_set.h b/third_party/blink/renderer/core/css/rule_set.h
index 9ee704a2..923e66e 100644
--- a/third_party/blink/renderer/core/css/rule_set.h
+++ b/third_party/blink/renderer/core/css/rule_set.h
@@ -256,22 +256,26 @@
   const HeapVector<Member<const RuleData>>* IdRules(
       const AtomicString& key) const {
     DCHECK(!pending_rules_);
-    return id_rules_.DeprecatedAtOrEmptyValue(key);
+    auto it = id_rules_.find(key);
+    return it != id_rules_.end() ? it->value : nullptr;
   }
   const HeapVector<Member<const RuleData>>* ClassRules(
       const AtomicString& key) const {
     DCHECK(!pending_rules_);
-    return class_rules_.DeprecatedAtOrEmptyValue(key);
+    auto it = class_rules_.find(key);
+    return it != class_rules_.end() ? it->value : nullptr;
   }
   const HeapVector<Member<const RuleData>>* TagRules(
       const AtomicString& key) const {
     DCHECK(!pending_rules_);
-    return tag_rules_.DeprecatedAtOrEmptyValue(key);
+    auto it = tag_rules_.find(key);
+    return it != tag_rules_.end() ? it->value : nullptr;
   }
   const HeapVector<Member<const RuleData>>* UAShadowPseudoElementRules(
       const AtomicString& key) const {
     DCHECK(!pending_rules_);
-    return ua_shadow_pseudo_element_rules_.DeprecatedAtOrEmptyValue(key);
+    auto it = ua_shadow_pseudo_element_rules_.find(key);
+    return it != ua_shadow_pseudo_element_rules_.end() ? it->value : nullptr;
   }
   const HeapVector<Member<const RuleData>>* LinkPseudoClassRules() const {
     DCHECK(!pending_rules_);
diff --git a/third_party/blink/renderer/core/css/style_engine.cc b/third_party/blink/renderer/core/css/style_engine.cc
index a04f5aa..b9d75db 100644
--- a/third_party/blink/renderer/core/css/style_engine.cc
+++ b/third_party/blink/renderer/core/css/style_engine.cc
@@ -1980,7 +1980,8 @@
 
 CSSScrollTimeline* StyleEngine::FindScrollTimeline(const AtomicString& name) {
   DCHECK(!timelines_need_update_);
-  return scroll_timeline_map_.DeprecatedAtOrEmptyValue(name);
+  auto it = scroll_timeline_map_.find(name);
+  return it != scroll_timeline_map_.end() ? it->value : nullptr;
 }
 
 void StyleEngine::ScrollTimelineInvalidated(CSSScrollTimeline& timeline) {
diff --git a/third_party/blink/renderer/core/css/style_rule_import.cc b/third_party/blink/renderer/core/css/style_rule_import.cc
index ed9abb6b..f7d67765 100644
--- a/third_party/blink/renderer/core/css/style_rule_import.cc
+++ b/third_party/blink/renderer/core/css/style_rule_import.cc
@@ -35,12 +35,14 @@
 namespace blink {
 
 StyleRuleImport::StyleRuleImport(const String& href,
+                                 absl::optional<LayerName>&& layer,
                                  scoped_refptr<MediaQuerySet> media,
                                  OriginClean origin_clean)
     : StyleRuleBase(kImport),
       parent_style_sheet_(nullptr),
       style_sheet_client_(MakeGarbageCollected<ImportedStyleSheetClient>(this)),
       str_href_(href),
+      layer_(std::move(layer)),
       media_queries_(media),
       loading_(false),
       origin_clean_(origin_clean) {
diff --git a/third_party/blink/renderer/core/css/style_rule_import.h b/third_party/blink/renderer/core/css/style_rule_import.h
index 30f02f3..94a07cf0 100644
--- a/third_party/blink/renderer/core/css/style_rule_import.h
+++ b/third_party/blink/renderer/core/css/style_rule_import.h
@@ -38,6 +38,7 @@
 
  public:
   StyleRuleImport(const String& href,
+                  absl::optional<LayerName>&& layer,
                   scoped_refptr<MediaQuerySet>,
                   OriginClean origin_clean);
   ~StyleRuleImport();
@@ -57,6 +58,9 @@
 
   void RequestStyleSheet();
 
+  bool IsLayered() const { return layer_.has_value(); }
+  const LayerName& GetLayerName() const { return layer_.value(); }
+
   void TraceAfterDispatch(blink::Visitor*) const;
 
  private:
@@ -95,6 +99,7 @@
 
   Member<ImportedStyleSheetClient> style_sheet_client_;
   String str_href_;
+  absl::optional<LayerName> layer_;
   scoped_refptr<MediaQuerySet> media_queries_;
   Member<StyleSheetContents> style_sheet_;
   bool loading_;
diff --git a/third_party/blink/renderer/core/css/style_sheet_contents.cc b/third_party/blink/renderer/core/css/style_sheet_contents.cc
index ef3cb134..b82942c 100644
--- a/third_party/blink/renderer/core/css/style_sheet_contents.cc
+++ b/third_party/blink/renderer/core/css/style_sheet_contents.cc
@@ -316,7 +316,8 @@
 
 const AtomicString& StyleSheetContents::NamespaceURIFromPrefix(
     const AtomicString& prefix) const {
-  return namespaces_.DeprecatedAtOrEmptyValue(prefix);
+  auto it = namespaces_.find(prefix);
+  return it != namespaces_.end() ? it->value : WTF::g_null_atom;
 }
 
 void StyleSheetContents::ParseAuthorStyleSheet(
diff --git a/third_party/blink/renderer/core/css/style_sheet_contents.h b/third_party/blink/renderer/core/css/style_sheet_contents.h
index f09594b..50d499d 100644
--- a/third_party/blink/renderer/core/css/style_sheet_contents.h
+++ b/third_party/blink/renderer/core/css/style_sheet_contents.h
@@ -29,6 +29,7 @@
 #include "third_party/blink/renderer/platform/loader/fetch/render_blocking_behavior.h"
 #include "third_party/blink/renderer/platform/weborigin/kurl.h"
 #include "third_party/blink/renderer/platform/wtf/hash_map.h"
+#include "third_party/blink/renderer/platform/wtf/text/atomic_string.h"
 #include "third_party/blink/renderer/platform/wtf/text/atomic_string_hash.h"
 #include "third_party/blink/renderer/platform/wtf/text/string_hash.h"
 #include "third_party/blink/renderer/platform/wtf/text/text_position.h"
diff --git a/third_party/blink/renderer/core/exported/web_document.cc b/third_party/blink/renderer/core/exported/web_document.cc
index a33e3cb..2194e0a4 100644
--- a/third_party/blink/renderer/core/exported/web_document.cc
+++ b/third_party/blink/renderer/core/exported/web_document.cc
@@ -174,8 +174,9 @@
   return document_element->innerText();
 }
 
-WebElementCollection WebDocument::All() {
-  return WebElementCollection(Unwrap<Document>()->all());
+WebElementCollection WebDocument::All() const {
+  return WebElementCollection(
+      const_cast<Document*>(ConstUnwrap<Document>())->all());
 }
 
 WebVector<WebFormElement> WebDocument::Forms() const {
diff --git a/third_party/blink/renderer/core/frame/local_frame_view.cc b/third_party/blink/renderer/core/frame/local_frame_view.cc
index 45882fb..e1614f4 100644
--- a/third_party/blink/renderer/core/frame/local_frame_view.cc
+++ b/third_party/blink/renderer/core/frame/local_frame_view.cc
@@ -2665,6 +2665,18 @@
   ForAllNonThrottledLocalFrameViews(
       [](LocalFrameView& frame_view) {
         frame_view.Lifecycle().AdvanceTo(DocumentLifecycle::kInPrePaint);
+
+        // Validate all HighlightMarkers of all non-throttled LocalFrameViews
+        // before paint phase so the nodes affected by markers removed/added are
+        // invalidated and then painted during this lifecycle.
+        if (LocalDOMWindow* window = frame_view.GetFrame().DomWindow()) {
+          if (HighlightRegistry* highlight_registry =
+                  window->Supplementable<LocalDOMWindow>::RequireSupplement<
+                      HighlightRegistry>()) {
+            highlight_registry->ValidateHighlightMarkers();
+          }
+        }
+
         // We skipped pre-paint for this frame while it was throttled, or we
         // have never run pre-paint for this frame. Either way, we're
         // unthrottled now, so we must propagate our dirty bits into our
@@ -2733,18 +2745,6 @@
   if (AnyFrameIsPrintingOrPaintingPreview())
     return;
 
-  // Validate all HighlightMarkers of all non-throttled LocalFrameViews before
-  // the call to PaintTree() so they're updated during this lifecycle.
-  ForAllNonThrottledLocalFrameViews([](LocalFrameView& frame_view) {
-    if (LocalDOMWindow* window = frame_view.GetFrame().DomWindow()) {
-      if (HighlightRegistry* highlight_registry =
-              window->Supplementable<LocalDOMWindow>::RequireSupplement<
-                  HighlightRegistry>()) {
-        highlight_registry->ValidateHighlightMarkers();
-      }
-    }
-  });
-
   bool needed_update;
   {
     PaintController::CycleScope cycle_scope;
diff --git a/third_party/blink/renderer/core/inspector/inspector_highlight.cc b/third_party/blink/renderer/core/inspector/inspector_highlight.cc
index 16a0c574..53baeb1 100644
--- a/third_party/blink/renderer/core/inspector/inspector_highlight.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_highlight.cc
@@ -1242,8 +1242,11 @@
   // all the way to the left. In NG, this is not the case, and will stop sooner
   // if the tracks don't take up the full size of the grid.
   LayoutUnit rtl_offset;
-  if (layout_object->IsLayoutNGGrid())
-    rtl_offset = To<LayoutBox>(layout_object)->LogicalWidth() - columns.back();
+  if (layout_object->IsLayoutNGGrid()) {
+    const LayoutBox* layout_box = To<LayoutBox>(layout_object);
+    rtl_offset = layout_box->LogicalWidth() - columns.back() -
+                 layout_box->BorderAndPaddingLogicalRight();
+  }
 
   if (grid_highlight_config.show_track_sizes) {
     Element* element = DynamicTo<Element>(node);
diff --git a/third_party/blink/renderer/core/layout/layout_box_model_object.h b/third_party/blink/renderer/core/layout/layout_box_model_object.h
index 8d78ce3..a70df2e 100644
--- a/third_party/blink/renderer/core/layout/layout_box_model_object.h
+++ b/third_party/blink/renderer/core/layout/layout_box_model_object.h
@@ -397,7 +397,12 @@
     return StyleRef().IsHorizontalWritingMode() ? BorderLeft() + PaddingLeft()
                                                 : BorderTop() + PaddingTop();
   }
-
+  DISABLE_CFI_PERF LayoutUnit BorderAndPaddingLogicalRight() const {
+    NOT_DESTROYED();
+    return StyleRef().IsHorizontalWritingMode()
+               ? BorderRight() + PaddingRight()
+               : BorderBottom() + PaddingBottom();
+  }
   LayoutUnit BorderLogicalLeft() const {
     NOT_DESTROYED();
     return LayoutUnit(StyleRef().IsHorizontalWritingMode() ? BorderLeft()
diff --git a/third_party/blink/renderer/core/layout/ng/grid/layout_ng_grid.cc b/third_party/blink/renderer/core/layout/ng/grid/layout_ng_grid.cc
index b8b33b7..b8542395 100644
--- a/third_party/blink/renderer/core/layout/ng/grid/layout_ng_grid.cc
+++ b/third_party/blink/renderer/core/layout/ng/grid/layout_ng_grid.cc
@@ -40,11 +40,17 @@
 wtf_size_t LayoutNGGrid::ExplicitGridEndForDirection(
     GridTrackSizingDirection direction) const {
   NOT_DESTROYED();
-  const auto* grid_data = GetGridData();
-  if (!grid_data)
-    return 0;
-  return (direction == kForRows) ? grid_data->row_geometry.total_track_count
-                                 : grid_data->column_geometry.total_track_count;
+  wtf_size_t leading = ExplicitGridStartForDirection(direction);
+
+  if (direction == kForRows) {
+    return base::checked_cast<wtf_size_t>(
+        leading + GridPositionsResolver::ExplicitGridRowCount(
+                      StyleRef(), AutoRepeatCountForDirection(direction)));
+  }
+
+  return base::checked_cast<wtf_size_t>(
+      leading + GridPositionsResolver::ExplicitGridColumnCount(
+                    StyleRef(), AutoRepeatCountForDirection(direction)));
 }
 
 wtf_size_t LayoutNGGrid::AutoRepeatCountForDirection(
diff --git a/third_party/blink/renderer/core/style/computed_style.cc b/third_party/blink/renderer/core/style/computed_style.cc
index 29ca0c6..f240ccd 100644
--- a/third_party/blink/renderer/core/style/computed_style.cc
+++ b/third_party/blink/renderer/core/style/computed_style.cc
@@ -1441,8 +1441,11 @@
 
 const CounterDirectives ComputedStyle::GetCounterDirectives(
     const AtomicString& identifier) const {
-  if (const CounterDirectiveMap* directives = GetCounterDirectives())
-    return directives->DeprecatedAtOrEmptyValue(identifier);
+  if (GetCounterDirectives()) {
+    auto it = GetCounterDirectives()->find(identifier);
+    if (it != GetCounterDirectives()->end())
+      return it->value;
+  }
   return CounterDirectives();
 }
 
diff --git a/third_party/blink/renderer/modules/webaudio/audio_context.cc b/third_party/blink/renderer/modules/webaudio/audio_context.cc
index 49ed5e61..6b2ae86a 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_context.cc
+++ b/third_party/blink/renderer/modules/webaudio/audio_context.cc
@@ -5,6 +5,7 @@
 #include "third_party/blink/renderer/modules/webaudio/audio_context.h"
 
 #include "base/metrics/histogram_functions.h"
+#include "base/metrics/histogram_macros.h"
 #include "build/build_config.h"
 #include "services/metrics/public/cpp/ukm_builders.h"
 #include "services/metrics/public/cpp/ukm_recorder.h"
@@ -146,6 +147,7 @@
     return nullptr;
   }
 
+  SCOPED_UMA_HISTOGRAM_TIMER("WebAudio.AudioContext.CreateTime");
   AudioContext* audio_context =
       MakeGarbageCollected<AudioContext>(document, latency_hint, sample_rate);
   ++g_hardware_context_count;
diff --git a/third_party/blink/renderer/modules/webaudio/offline_audio_context.cc b/third_party/blink/renderer/modules/webaudio/offline_audio_context.cc
index 40a93ec..dd785d44 100644
--- a/third_party/blink/renderer/modules/webaudio/offline_audio_context.cc
+++ b/third_party/blink/renderer/modules/webaudio/offline_audio_context.cc
@@ -26,6 +26,7 @@
 #include "third_party/blink/renderer/modules/webaudio/offline_audio_context.h"
 
 #include "base/metrics/histogram_functions.h"
+#include "base/metrics/histogram_macros.h"
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_offline_audio_context_options.h"
 #include "third_party/blink/renderer/core/dom/document.h"
@@ -98,6 +99,7 @@
     return nullptr;
   }
 
+  SCOPED_UMA_HISTOGRAM_TIMER("WebAudio.OfflineAudioContext.CreateTime");
   OfflineAudioContext* audio_context =
       MakeGarbageCollected<OfflineAudioContext>(
           window->document(), number_of_channels, number_of_frames, sample_rate,
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index ed4a851a..c716e33 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -2675,8 +2675,6 @@
 crbug.com/626703 [ Mac10.13 ] virtual/off-main-thread-css-paint/external/wpt/css/css-paint-api/no-op-animation.https.html [ Failure ]
 crbug.com/626703 [ Mac10.14 ] virtual/off-main-thread-css-paint/external/wpt/css/css-paint-api/no-op-animation.https.html [ Failure ]
 crbug.com/626703 [ Mac10.15 ] virtual/off-main-thread-css-paint/external/wpt/css/css-paint-api/no-op-animation.https.html [ Failure ]
-crbug.com/626703 [ Win7 ] external/wpt/css/css-highlight-api/painting/custom-highlight-painting-staticrange-005.html [ Failure ]
-crbug.com/626703 [ Win7 ] external/wpt/css/css-highlight-api/painting/custom-highlight-painting-staticrange-004.html [ Failure ]
 crbug.com/626703 [ Mac10.12 ] external/wpt/html/cross-origin-opener-policy/coop-navigate-same-origin-csp-sandbox.html [ Timeout ]
 crbug.com/626703 [ Mac10.15 ] external/wpt/html/cross-origin-opener-policy/coop-navigate-same-origin-csp-sandbox.html [ Timeout ]
 crbug.com/626703 [ Linux ] external/wpt/selection/textcontrols/selectionchange-bubble.html [ Timeout ]
@@ -7851,7 +7849,6 @@
 crbug.com/1178292 [ Mac ] fast/mediacapturefromelement/CanvasCaptureMediaStream-set-size-too-large.html [ Pass Timeout ]
 
 # Sheriff 2021-08-10
-crbug.com/1238266 [ Mac ] virtual/file-system-access-access-handle-incognito/external/wpt/file-system-access/sandboxed_FileSystemSyncAccessHandle-truncate.https.tentative.worker.html [ Pass Failure ]
 crbug.com/1233840 external/wpt/html/cross-origin-opener-policy/historical/coep-navigate-popup-unsafe-inherit.https.html [ Pass Timeout ]
 crbug.com/1230534 external/wpt/webrtc/simulcast/basic.https.html [ Pass Timeout ]
 crbug.com/1230534 external/wpt/webrtc/simulcast/setParameters-active.https.html [ Failure Pass Timeout ]
@@ -7861,18 +7858,6 @@
 crbug.com/1239175 http/tests/navigation/same-and-different-back.html [ Failure Pass ]
 crbug.com/1239164 http/tests/inspector-protocol/network/navigate-iframe-in2in.js [ Failure Pass ]
 crbug.com/1192932 http/tests/inspector-protocol/network/xhr-interception.js [ Failure Pass ]
-crbug.com/1238845 [ Win ] external/wpt/css/css-highlight-api/painting/custom-highlight-painting-invalidation-001.html [ Failure Pass ]
-crbug.com/1238845 [ Mac ] external/wpt/css/css-highlight-api/painting/custom-highlight-painting-invalidation-001.html [ Failure Pass ]
-crbug.com/1238845 [ Win ] external/wpt/css/css-highlight-api/painting/custom-highlight-painting-invalidation-002.html [ Failure Pass ]
-crbug.com/1238845 [ Mac ] external/wpt/css/css-highlight-api/painting/custom-highlight-painting-invalidation-002.html [ Failure Pass ]
-crbug.com/1238845 [ Win ] external/wpt/css/css-highlight-api/painting/custom-highlight-painting-invalidation-003.html [ Failure Pass ]
-crbug.com/1238845 [ Mac ] external/wpt/css/css-highlight-api/painting/custom-highlight-painting-invalidation-003.html [ Failure Pass ]
-crbug.com/1238845 [ Win ] external/wpt/css/css-highlight-api/painting/custom-highlight-painting-invalidation-004.html [ Failure Pass ]
-crbug.com/1238845 [ Mac ] external/wpt/css/css-highlight-api/painting/custom-highlight-painting-invalidation-004.html [ Failure Pass ]
-crbug.com/1238845 [ Win ] external/wpt/css/css-highlight-api/painting/custom-highlight-painting-invalidation-005.html [ Failure Pass ]
-crbug.com/1238845 [ Mac ] external/wpt/css/css-highlight-api/painting/custom-highlight-painting-invalidation-005.html [ Failure Pass ]
-crbug.com/1238845 [ Win ] external/wpt/css/css-highlight-api/painting/custom-highlight-painting-invalidation-006.html [ Failure Pass ]
-crbug.com/1238845 [ Mac ] external/wpt/css/css-highlight-api/painting/custom-highlight-painting-invalidation-006.html [ Failure Pass ]
 crbug.com/1192215 external/wpt/html/browsers/history/the-location-interface/location-protocol-setter-non-broken-weird.html [ Failure Pass ]
 crbug.com/1237909 external/wpt/webrtc-svc/RTCRtpParameters-scalability.html [ Crash Failure Pass Timeout ]
 crbug.com/1239161 external/wpt/html/semantics/links/links-created-by-a-and-area-elements/htmlanchorelement_noopener.html [ Failure Pass ]
diff --git a/third_party/blink/web_tests/WebGPUExpectations b/third_party/blink/web_tests/WebGPUExpectations
index d338154..6c139b28 100644
--- a/third_party/blink/web_tests/WebGPUExpectations
+++ b/third_party/blink/web_tests/WebGPUExpectations
@@ -236,6 +236,9 @@
 crbug.com/dawn/812 wpt_internal/webgpu/cts.html?q=webgpu:api,validation,resource_usages,texture,in_pass_encoder:subresources_and_binding_types_combination_for_aspect:format="depth24plus-stencil8";aspect1="depth-only";type1="render-target";* [ Failure ]
 crbug.com/dawn/812 wpt_internal/webgpu/cts.html?q=webgpu:api,validation,resource_usages,texture,in_pass_encoder:subresources_and_binding_types_combination_for_aspect:format="depth24plus-stencil8";aspect1="stencil-only";type1="render-target";* [ Failure ]
 
+# Failing new updated tests, about to remove shortly after
+crbug.com/dawn/811 wpt_internal/webgpu/cts.html?q=webgpu:api,validation,createRenderPipeline:pipeline_output_targets,blend:* [ Failure ]
+
 ###
 ### Mac (Metal) specific
 ###
diff --git a/third_party/blink/web_tests/external/wpt/css/css-highlight-api/painting/custom-highlight-painting-invalidation-001.html b/third_party/blink/web_tests/external/wpt/css/css-highlight-api/painting/custom-highlight-painting-invalidation-001.html
index 909779d..aef391ec 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-highlight-api/painting/custom-highlight-painting-invalidation-001.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-highlight-api/painting/custom-highlight-painting-invalidation-001.html
@@ -1,9 +1,11 @@
 <!DOCTYPE html>
+<html class="reftest-wait">
 <meta charset="UTF-8">
 <title>CSS Highlight API Test: </title>
 <link rel="help" href="https://drafts.csswg.org/css-highlight-api-1/">
 <link rel="match" href="custom-highlight-painting-001-ref.html">
 <meta name="assert" value="::highlight overlay is correctly invalidated and repainted">
+<script src="resources/run-after-layout-and-paint.js"></script>
 <style>
   ::highlight(example-highlight) {
     background-color: yellow;
@@ -17,9 +19,9 @@
   r.setEnd(document.body, 2);
 
   // Force frame paint before registering the Highlight.
-  requestAnimationFrame(()=>{
-    requestAnimationFrame(()=>{
-      CSS.highlights.set("example-highlight", new Highlight(r));
-    });
+  runAfterLayoutAndPaint(()=>{
+    CSS.highlights.set("example-highlight", new Highlight(r));
+    document.documentElement.removeAttribute("class");
   });
-</script>
\ No newline at end of file
+</script>
+</html>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/css/css-highlight-api/painting/custom-highlight-painting-invalidation-002.html b/third_party/blink/web_tests/external/wpt/css/css-highlight-api/painting/custom-highlight-painting-invalidation-002.html
index 197b9a4..99271ff 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-highlight-api/painting/custom-highlight-painting-invalidation-002.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-highlight-api/painting/custom-highlight-painting-invalidation-002.html
@@ -1,9 +1,11 @@
 <!DOCTYPE html>
+<html class="reftest-wait">
 <meta charset="UTF-8">
 <title>CSS Highlight API Test: </title>
 <link rel="help" href="https://drafts.csswg.org/css-highlight-api-1/">
 <link rel="match" href="custom-highlight-painting-staticrange-001-ref.html">
 <meta name="assert" value="::highlight overlay is correctly invalidated and repainted after deletion">
+<script src="resources/run-after-layout-and-paint.js"></script>
 <style>
   ::highlight(example-highlight) {
     background-color: yellow;
@@ -18,9 +20,9 @@
   CSS.highlights.set("example-highlight", new Highlight(r));
 
   // Force frame paint before deleting the Highlight.
-  requestAnimationFrame(()=>{
-    requestAnimationFrame(()=>{
-      CSS.highlights.delete("example-highlight");
-    });
+  runAfterLayoutAndPaint(()=>{
+    CSS.highlights.delete("example-highlight");
+    document.documentElement.removeAttribute("class");
   });
-</script>
\ No newline at end of file
+</script>
+</html>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/css/css-highlight-api/painting/custom-highlight-painting-invalidation-003.html b/third_party/blink/web_tests/external/wpt/css/css-highlight-api/painting/custom-highlight-painting-invalidation-003.html
index a02d050..d1f87223 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-highlight-api/painting/custom-highlight-painting-invalidation-003.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-highlight-api/painting/custom-highlight-painting-invalidation-003.html
@@ -1,9 +1,11 @@
 <!DOCTYPE html>
+<html class="reftest-wait">
 <meta charset="UTF-8">
 <title>CSS Highlight API Test: </title>
 <link rel="help" href="https://drafts.csswg.org/css-highlight-api-1/">
 <link rel="match" href="custom-highlight-painting-001-ref.html">
 <meta name="assert" value="::highlight overlay is correctly invalidated and repainted after modifying its range">
+<script src="resources/run-after-layout-and-paint.js"></script>
 <style>
   ::highlight(example-highlight) {
     background-color: yellow;
@@ -16,10 +18,10 @@
   CSS.highlights.set("example-highlight", new Highlight(r));
 
   // Force frame paint before modifying the Highlight's range.
-  requestAnimationFrame(()=>{
-    requestAnimationFrame(()=>{
-      r.setStart(document.body, 0);
-      r.setEnd(document.body, 2);
-    });
+  runAfterLayoutAndPaint(()=>{
+    r.setStart(document.body, 0);
+    r.setEnd(document.body, 2);
+    document.documentElement.removeAttribute("class");
   });
-</script>
\ No newline at end of file
+</script>
+</html>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/css/css-highlight-api/painting/custom-highlight-painting-invalidation-004.html b/third_party/blink/web_tests/external/wpt/css/css-highlight-api/painting/custom-highlight-painting-invalidation-004.html
index 2b22743..62f05a84 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-highlight-api/painting/custom-highlight-painting-invalidation-004.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-highlight-api/painting/custom-highlight-painting-invalidation-004.html
@@ -1,9 +1,11 @@
 <!DOCTYPE html>
+<html class="reftest-wait">
 <meta charset="UTF-8">
 <title>CSS Highlight API Test: </title>
 <link rel="help" href="https://drafts.csswg.org/css-highlight-api-1/">
 <link rel="match" href="custom-highlight-painting-001-ref.html">
 <meta name="assert" value="::highlight overlay is correctly invalidated and repainted after adding a range to it">
+<script src="resources/run-after-layout-and-paint.js"></script>
 <style>
   ::highlight(example-highlight) {
     background-color: yellow;
@@ -19,9 +21,9 @@
   CSS.highlights.set("example-highlight", h);
 
   // Force frame paint before modifying the Highlight.
-  requestAnimationFrame(()=>{
-    requestAnimationFrame(()=>{
-      h.add(r);
-    });
+  runAfterLayoutAndPaint(()=>{
+    h.add(r);
+    document.documentElement.removeAttribute("class");
   });
-</script>
\ No newline at end of file
+</script>
+</html>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/css/css-highlight-api/painting/custom-highlight-painting-invalidation-005.html b/third_party/blink/web_tests/external/wpt/css/css-highlight-api/painting/custom-highlight-painting-invalidation-005.html
index b22b7d7..cabc4c3 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-highlight-api/painting/custom-highlight-painting-invalidation-005.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-highlight-api/painting/custom-highlight-painting-invalidation-005.html
@@ -1,9 +1,11 @@
 <!DOCTYPE html>
+<html class="reftest-wait">
 <meta charset="UTF-8">
 <title>CSS Highlight API Test: </title>
 <link rel="help" href="https://drafts.csswg.org/css-highlight-api-1/">
 <link rel="match" href="custom-highlight-painting-001-ref.html">
 <meta name="assert" value="::highlight overlay is correctly invalidated and repainted after modifying its priority">
+<script src="resources/run-after-layout-and-paint.js"></script>
 <style>
   ::highlight(example-highlight) {
     background-color: yellow;
@@ -27,9 +29,9 @@
   CSS.highlights.set("another-highlight", h2);
 
   // Force frame paint before modifying the Highlight.
-  requestAnimationFrame(()=>{
-    requestAnimationFrame(()=>{
-      h1.priority = 3;
-    });
+  runAfterLayoutAndPaint(()=>{
+    h1.priority = 3;
+    document.documentElement.removeAttribute("class");
   });
-</script>
\ No newline at end of file
+</script>
+</html>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/css/css-highlight-api/painting/custom-highlight-painting-invalidation-006.html b/third_party/blink/web_tests/external/wpt/css/css-highlight-api/painting/custom-highlight-painting-invalidation-006.html
index d773a01..81c2298e 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-highlight-api/painting/custom-highlight-painting-invalidation-006.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-highlight-api/painting/custom-highlight-painting-invalidation-006.html
@@ -1,9 +1,11 @@
 <!DOCTYPE html>
+<html class="reftest-wait">
 <meta charset="UTF-8">
 <title>CSS Highlight API Test: </title>
 <link rel="help" href="https://drafts.csswg.org/css-highlight-api-1/">
 <link rel="match" href="custom-highlight-painting-001-ref.html">
 <meta name="assert" value="::highlight overlay is correctly invalidated and repainted after inserting a new node inside one of its ranges">
+<script src="resources/run-after-layout-and-paint.js"></script>
 <style>
   ::highlight(example-highlight) {
     background-color: yellow;
@@ -11,18 +13,18 @@
   }
 </style>
 <body><span>two </span><span>three…</span>
-  <script>
-    let r = new Range();
-    r.setStart(document.body, 0);
-    r.setEnd(document.body, 1);
-    CSS.highlights.set("example-highlight", new Highlight(r));
-    let newNode = document.createElement("span");
-    newNode.innerText = "One ";
+<script>
+  let r = new Range();
+  r.setStart(document.body, 0);
+  r.setEnd(document.body, 1);
+  CSS.highlights.set("example-highlight", new Highlight(r));
+  let newNode = document.createElement("span");
+  newNode.innerText = "One ";
 
-    // Force frame paint before inserting a new node.
-    requestAnimationFrame(()=>{
-      requestAnimationFrame(()=>{
-        document.body.insertBefore(newNode, document.body.firstChild);
-      });
-    });
-  </script>
\ No newline at end of file
+  // Force frame paint before inserting a new node.
+  runAfterLayoutAndPaint(()=>{
+    document.body.insertBefore(newNode, document.body.firstChild);
+    document.documentElement.removeAttribute("class");
+  });
+</script>
+</html>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/css/css-highlight-api/painting/custom-highlight-painting-staticrange-004.html b/third_party/blink/web_tests/external/wpt/css/css-highlight-api/painting/custom-highlight-painting-staticrange-004.html
index fa3d7ce..14e9766 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-highlight-api/painting/custom-highlight-painting-staticrange-004.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-highlight-api/painting/custom-highlight-painting-staticrange-004.html
@@ -1,9 +1,11 @@
 <!DOCTYPE html>
+<html class="reftest-wait">
 <meta charset="UTF-8">
 <title>CSS Highlight API Test: </title>
 <link rel="help" href="https://drafts.csswg.org/css-highlight-api-1/">
 <link rel="match" href="custom-highlight-painting-staticrange-001-ref.html">
 <meta name="assert" value="Highlight is repainted correctly after a node crossed by a StaticRange becomes a containment (so the range is not painted anymore)">
+<script src="resources/run-after-layout-and-paint.js"></script>
 <style>
   ::highlight(example-highlight) {
     background-color: yellow;
@@ -20,9 +22,9 @@
 
   const targetSpan = document.querySelector("#target");
   // Force frame paint before changing targetSpan's id.
-  requestAnimationFrame( () => {
-    requestAnimationFrame( () => {
-      targetSpan.id = "contained";
-    });
+  runAfterLayoutAndPaint(()=>{
+    targetSpan.id = "contained";
+    document.documentElement.removeAttribute("class");
   });
-</script>
\ No newline at end of file
+</script>
+</html>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/css/css-highlight-api/painting/custom-highlight-painting-staticrange-005.html b/third_party/blink/web_tests/external/wpt/css/css-highlight-api/painting/custom-highlight-painting-staticrange-005.html
index ab486ae..b955b0fc 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-highlight-api/painting/custom-highlight-painting-staticrange-005.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-highlight-api/painting/custom-highlight-painting-staticrange-005.html
@@ -1,9 +1,11 @@
 <!DOCTYPE html>
+<html class="reftest-wait">
 <meta charset="UTF-8">
 <title>CSS Highlight API Test: </title>
 <link rel="help" href="https://drafts.csswg.org/css-highlight-api-1/">
 <link rel="match" href="custom-highlight-painting-001-ref.html">
 <meta name="assert" value="Highlight is repainted correctly after a node crossed by a StaticRange is not a containment anymore (so the range should be painted now)">
+<script src="resources/run-after-layout-and-paint.js"></script>
 <style>
   ::highlight(example-highlight) {
     background-color: yellow;
@@ -20,9 +22,9 @@
 
   const targetSpan = document.querySelector("#contained");
   // Force frame paint before changing targetSpan's id.
-  requestAnimationFrame( () => {
-    requestAnimationFrame( () => {
-      targetSpan.id = "not-contained";
-    });
+  runAfterLayoutAndPaint(()=>{
+    targetSpan.id = "not-contained";
+    document.documentElement.removeAttribute("class");
   });
-</script>
\ No newline at end of file
+</script>
+</html>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/css/css-highlight-api/painting/custom-highlight-painting-staticrange-006.html b/third_party/blink/web_tests/external/wpt/css/css-highlight-api/painting/custom-highlight-painting-staticrange-006.html
index 52ab8c7..84361022 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-highlight-api/painting/custom-highlight-painting-staticrange-006.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-highlight-api/painting/custom-highlight-painting-staticrange-006.html
@@ -1,9 +1,11 @@
 <!DOCTYPE html>
+<html class="reftest-wait">
 <meta charset="UTF-8">
 <title>CSS Highlight API Test: </title>
 <link rel="help" href="https://drafts.csswg.org/css-highlight-api-1/">
 <link rel="match" href="custom-highlight-painting-staticrange-001-ref.html">
 <meta name="assert" value="Highlight is repainted correctly after a node crossed by a StaticRange becomes a containment because of a CSSStyleSheet change (so the range should be painted now)">
+<script src="resources/run-after-layout-and-paint.js"></script>
 <style>
   ::highlight(example-highlight) {
     background-color: yellow;
@@ -19,9 +21,9 @@
   document.adoptedStyleSheets = [styles];
 
   // Force frame paint before changing the style sheet.
-  requestAnimationFrame( () => {
-    requestAnimationFrame( () => {
-      styles.replaceSync(`#target { contain: style; }`);
-    });
+  runAfterLayoutAndPaint(()=>{
+    styles.replaceSync(`#target { contain: style; }`);
+    document.documentElement.removeAttribute("class");
   });
 </script>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-highlight-api/painting/custom-highlight-painting-staticrange-007.html b/third_party/blink/web_tests/external/wpt/css/css-highlight-api/painting/custom-highlight-painting-staticrange-007.html
index d56082a..03a7ee6 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-highlight-api/painting/custom-highlight-painting-staticrange-007.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-highlight-api/painting/custom-highlight-painting-staticrange-007.html
@@ -1,9 +1,11 @@
 <!DOCTYPE html>
+<html class="reftest-wait">
 <meta charset="UTF-8">
 <title>CSS Highlight API Test: </title>
 <link rel="help" href="https://drafts.csswg.org/css-highlight-api-1/">
 <link rel="match" href="custom-highlight-painting-001-ref.html">
 <meta name="assert" value="Highlight is repainted correctly after a node crossed by a StaticRange is not a containment anymore because of a CSSStyleSheet change (so the range should be painted now)">
+<script src="resources/run-after-layout-and-paint.js"></script>
 <style>
   ::highlight(example-highlight) {
     background-color: yellow;
@@ -19,9 +21,9 @@
   CSS.highlights.set("example-highlight", new Highlight(r));
 
   // Force frame paint before changing the style sheet.
-  requestAnimationFrame( () => {
-    requestAnimationFrame( () => {
-      styles.replaceSync(`#target {}`);
-    });
+  runAfterLayoutAndPaint(()=>{
+    styles.replaceSync(`#target {}`);
+    document.documentElement.removeAttribute("class");
   });
 </script>
+</html>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/css/css-highlight-api/painting/resources/run-after-layout-and-paint.js b/third_party/blink/web_tests/external/wpt/css/css-highlight-api/painting/resources/run-after-layout-and-paint.js
new file mode 100644
index 0000000..75d3e279
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-highlight-api/painting/resources/run-after-layout-and-paint.js
@@ -0,0 +1,11 @@
+// This is inspired in runAfterLayoutAndPaint() from
+// third_party/blink/web_tests/resources/run-after-layout-and-paint.js.
+function runAfterLayoutAndPaint(callback) {
+  // See http://crrev.com/c/1395193/10/third_party/blink/web_tests/http/tests/resources/run-after-layout-and-paint.js
+  // for more discussions.
+  requestAnimationFrame(function() {
+    requestAnimationFrame(function() {
+      callback();
+    });
+  });
+}
\ No newline at end of file
diff --git a/third_party/blink/web_tests/http/tests/devtools/elements/highlight/highlight-css-grid-expected.txt b/third_party/blink/web_tests/http/tests/devtools/elements/highlight/highlight-css-grid-expected.txt
index 37c5938..3c3e351 100644
--- a/third_party/blink/web_tests/http/tests/devtools/elements/highlight/highlight-css-grid-expected.txt
+++ b/third_party/blink/web_tests/http/tests/devtools/elements/highlight/highlight-css-grid-expected.txt
@@ -240,14 +240,6 @@
         {
           "x": 173,
           "y": 133
-        },
-        {
-          "x": 205.5,
-          "y": 133
-        },
-        {
-          "x": 238,
-          "y": 133
         }
       ],
       "negativeColumnLineNumberPositions": [
@@ -533,14 +525,6 @@
         {
           "x": 228,
           "y": 858
-        },
-        {
-          "x": 253,
-          "y": 858
-        },
-        {
-          "x": 278,
-          "y": 858
         }
       ],
       "negativeColumnLineNumberPositions": [
diff --git a/third_party/blink/web_tests/http/tests/devtools/elements/highlight/highlight-multiple-css-grid-expected.txt b/third_party/blink/web_tests/http/tests/devtools/elements/highlight/highlight-multiple-css-grid-expected.txt
index 6032ee6..833f9b0 100644
--- a/third_party/blink/web_tests/http/tests/devtools/elements/highlight/highlight-multiple-css-grid-expected.txt
+++ b/third_party/blink/web_tests/http/tests/devtools/elements/highlight/highlight-multiple-css-grid-expected.txt
@@ -145,14 +145,6 @@
         {
           "x": 173,
           "y": 133
-        },
-        {
-          "x": 205.5,
-          "y": 133
-        },
-        {
-          "x": 238,
-          "y": 133
         }
       ],
       "negativeColumnLineNumberPositions": [
@@ -351,14 +343,6 @@
         {
           "x": 108,
           "y": -92
-        },
-        {
-          "x": 218,
-          "y": -92
-        },
-        {
-          "x": 328,
-          "y": -92
         }
       ],
       "negativeColumnLineNumberPositions": [
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/resources/inspector-protocol-test.js b/third_party/blink/web_tests/http/tests/inspector-protocol/resources/inspector-protocol-test.js
index 07b6afe..f8798ba 100644
--- a/third_party/blink/web_tests/http/tests/inspector-protocol/resources/inspector-protocol-test.js
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/resources/inspector-protocol-test.js
@@ -296,6 +296,10 @@
     this._targetId = targetId;
   }
 
+  targetId() {
+    return this._targetId;
+  }
+
   async createSession() {
     let dp = this._testRunner._browserSession.protocol;
     const sessionId = (await dp.Target.attachToTarget({targetId: this._targetId, flatten: true})).result.sessionId;
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/target/auto-attach-related-expected.txt b/third_party/blink/web_tests/http/tests/inspector-protocol/target/auto-attach-related-expected.txt
new file mode 100644
index 0000000..2511472
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/target/auto-attach-related-expected.txt
@@ -0,0 +1,11 @@
+Tests basic functionality of Tatget.autoAttachRelated.
+Attached to iframe target:  (waiting: false)
+Attached to iframe target:  (waiting: true)
+Attached to iframe target:  (waiting: true)
+Detached from 3 targets
+Disalbing auto-attach
+Re-enabling auto-attach for page1
+Attached to iframe target: http://devtools.oopif.test:8080/inspector-protocol/resources/iframe.html (waiting: false)
+Detached from 1 targets
+DONE
+
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/target/auto-attach-related.js b/third_party/blink/web_tests/http/tests/inspector-protocol/target/auto-attach-related.js
new file mode 100644
index 0000000..1718fea
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/target/auto-attach-related.js
@@ -0,0 +1,73 @@
+(async function(testRunner) {
+  testRunner.log('Tests basic functionality of Tatget.autoAttachRelated.');
+  const bp = testRunner.browserP();
+
+  const createChildFrame = function(url) {
+    return new Promise(resolve => {
+      const frame = document.createElement(`iframe`);
+      frame.src = url;
+      frame.addEventListener('load', resolve);
+      document.body.appendChild(frame);
+    });
+  }
+
+  async function expectTargetsDetached(count) {
+    for (let i = 0; i < count; ++i)
+      await bp.Target.onceDetachedFromTarget();
+    testRunner.log(`Detached from ${count} targets`);
+  }
+
+  const page1 = await testRunner.createPage();
+  const page2 = await testRunner.createPage();
+  const page1_session = await page1.createSession();
+  const page2_session = await page2.createSession();
+
+  bp.Target.onAttachedToTarget(event => {
+    const params = event.params;
+    testRunner.log(`Attached to ${params.targetInfo.type} target: ${params.targetInfo.url} (waiting: ${params.waitingForDebugger})`);
+  });
+
+  // page1 will auto-attach, but page2 will not.
+  await bp.Target.autoAttachRelated({targetId: page1.targetId(), waitForDebuggerOnStart: false});
+
+  page1_session.evaluate(`(${createChildFrame})('http://devtools.oopif.test:8080/inspector-protocol/resources/iframe.html')`);
+  const frame1_attached = (await bp.Target.onceAttachedToTarget()).params;
+  const frame1SessionId = frame1_attached.sessionId;
+  const frame1TargetId = frame1_attached.targetInfo.targetId;
+  const frame1_session = new TestRunner.Session(testRunner, frame1SessionId);
+
+  await page2_session.evaluateAsync(`(${createChildFrame})('https://devtools.oopif.test:8443/inspector-protocol/resources/iframe.html')`);
+
+  // This one should auto-attach and wait.
+  await bp.Target.autoAttachRelated({targetId: frame1TargetId, waitForDebuggerOnStart: true});
+  frame1_session.evaluate(`(${createChildFrame})('http://inner-frame.test:8080/inspector-protocol/resources/iframe.html')`);
+
+  const frame11SessionId = (await bp.Target.onceAttachedToTarget()).params.sessionId;
+  const frame11_dp = (new TestRunner.Session(testRunner, frame11SessionId)).protocol;
+  await frame11_dp.Runtime.runIfWaitingForDebugger();
+
+  // Change waitForDebuggerOnStart for the tartget we already observe...
+  await bp.Target.autoAttachRelated({targetId: page1.targetId(), waitForDebuggerOnStart: true});
+  // and assure it has the effect.
+  page1_session.evaluate(`(${createChildFrame})('http://devtools.oopif.test:8080/inspector-protocol/resources/iframe.html?frame3')`);
+  const frame3SessionId = (await bp.Target.onceAttachedToTarget()).params.sessionId;
+  const frame3_dp = (new TestRunner.Session(testRunner, frame3SessionId)).protocol;
+  frame3_dp.Runtime.runIfWaitingForDebugger();
+
+  page1_session.evaluate(`document.body.textContent='';`);
+  await expectTargetsDetached(3);
+
+  testRunner.log(`Disalbing auto-attach`);
+  // Disable auto-attach altogether.
+  bp.Target.setAutoAttach({autoAttach: false, waitForDebuggerOnStart: false, flatten: true});
+  await page1_session.evaluate(`(${createChildFrame})('http://devtools.oopif.test:8080/inspector-protocol/resources/iframe.html')`);
+  testRunner.log(`Re-enabling auto-attach for page1`);
+  bp.Target.autoAttachRelated({targetId: page1.targetId(), waitForDebuggerOnStart: false});
+  await bp.Target.onceAttachedToTarget();
+  // Now disable auto-attach again and assure the target is detached.
+  bp.Target.setAutoAttach({autoAttach: false, waitForDebuggerOnStart: false, flatten: true});
+  await expectTargetsDetached(1);
+
+  testRunner.log('DONE');
+  testRunner.completeTest();
+});
diff --git a/third_party/libaom/README.chromium b/third_party/libaom/README.chromium
index f3b22ece..c0868a1 100644
--- a/third_party/libaom/README.chromium
+++ b/third_party/libaom/README.chromium
@@ -1,7 +1,8 @@
 Name: Alliance for Open Media Video Codec
 Short Name: libaom
 URL: https://aomedia.googlesource.com/aom/
-Version: 0
+Version: 3.1.2
+CPEPrefix: cpe:/a:aomedia:aomedia:3.1.2
 Date: Friday August 06 2021
 Branch: master
 Commit: da0b537ee186143863ba7b41f004b2ecbb7b66b2
diff --git a/third_party/libvpx/README.chromium b/third_party/libvpx/README.chromium
index 76693d0..20da3382 100644
--- a/third_party/libvpx/README.chromium
+++ b/third_party/libvpx/README.chromium
@@ -1,7 +1,7 @@
 Name: libvpx
 URL: http://www.webmproject.org
-Version: v1.8.2
-CPEPrefix: cpe:/a:john_koleszar:libvpx:1.8.2
+Version: v1.10.0
+CPEPrefix: cpe:/a:webmproject:libvpx:1.10.0
 License: BSD
 License File: source/libvpx/LICENSE
 Security Critical: yes
diff --git a/third_party/libwebm/README.chromium b/third_party/libwebm/README.chromium
index 6a3fcd52..cdbc21d6 100644
--- a/third_party/libwebm/README.chromium
+++ b/third_party/libwebm/README.chromium
@@ -1,8 +1,8 @@
 Name: WebM container parser and writer.
 Short Name: libwebm
 URL: http://www.webmproject.org/code/
-Version: unknown
-CPEPrefix: cpe:/a:webmproject:libwebm:1.0.0.27
+Version: 1.0.0.28
+CPEPrefix: cpe:/a:webmproject:libwebm:1.0.0.28
 License: BSD
 License File: source/LICENSE.TXT
 Security Critical: yes
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 6861bbc0..3261891 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -11065,7 +11065,7 @@
   <int value="25" label="kExploreSites"/>
   <int value="26" label="kLegacyStrikes"/>
   <int value="27" label="kWebrtcEventLogs"/>
-  <int value="28" label="kDrmLicenses"/>
+  <int value="28" label="kCdmLicenses"/>
   <int value="29" label="kHostCache"/>
   <int value="30" label="kTpmAttestationKeys"/>
   <int value="31" label="kStrikes"/>
@@ -26016,6 +26016,8 @@
   <int value="886" label="LensRegionSearchEnabled"/>
   <int value="887" label="ArcAppToWebAppSharingEnabled"/>
   <int value="888" label="EnhancedNetworkVoicesInSelectToSpeakAllowed"/>
+  <int value="889" label="PrintPdfAsImageAvailability"/>
+  <int value="890" label="PrintRasterizePdfDpi"/>
 </enum>
 
 <enum name="EnterprisePolicyDeviceIdValidity">
@@ -40696,6 +40698,9 @@
   <int value="4" label="kShowInFolder"/>
   <int value="5" label="kUnpin"/>
   <int value="6" label="kRemove"/>
+  <int value="7" label="kCancel"/>
+  <int value="8" label="kPause"/>
+  <int value="9" label="kResume"/>
 </enum>
 
 <enum name="HoldingSpaceItemType">
@@ -49455,6 +49460,7 @@
   <int value="65245449" label="FilesBannerFramework:enabled"/>
   <int value="66897259" label="ModalPermissionDialogView:enabled"/>
   <int value="67639499" label="stop-loading-in-background:disabled"/>
+  <int value="72558615" label="TrafficCountersSettingsUi:enabled"/>
   <int value="73929836" label="VrBrowsingInCustomTab:enabled"/>
   <int value="75207621" label="enable-stylus-virtual-keyboard:enabled"/>
   <int value="75237697" label="ash-enable-new-overview-ui"/>
@@ -49990,6 +49996,7 @@
   <int value="505561325" label="OpenXR:disabled"/>
   <int value="506680761" label="WebNFC:disabled"/>
   <int value="508272289" label="SharedHighlightingAmp:disabled"/>
+  <int value="509602783" label="TrafficCountersSettingsUi:disabled"/>
   <int value="510066229"
       label="AutofillEnforceMinRequiredFieldsForQuery:enabled"/>
   <int value="510814146" label="OfflineBookmarks:enabled"/>
diff --git a/tools/metrics/histograms/metadata/holding_space/histograms.xml b/tools/metrics/histograms/metadata/holding_space/histograms.xml
index 9bf82cbd..218dd335 100644
--- a/tools/metrics/histograms/metadata/holding_space/histograms.xml
+++ b/tools/metrics/histograms/metadata/holding_space/histograms.xml
@@ -22,11 +22,14 @@
 <histograms>
 
 <variants name="HoldingSpaceItemAction">
+  <variant name="Cancel" summary="The user cancelled the item."/>
   <variant name="Copy" summary="The user copied the item."/>
   <variant name="Drag" summary="The user dragged the item."/>
   <variant name="Launch" summary="The user launched the item."/>
+  <variant name="Pause" summary="The user paused the item."/>
   <variant name="Pin" summary="The user pinned the item."/>
   <variant name="Remove" summary="The user removed the item."/>
+  <variant name="Resume" summary="The user resumed the item."/>
   <variant name="ShowInFolder"
       summary="The user showed the item in its folder."/>
   <variant name="Unpin" summary="The user unpinned the item."/>
diff --git a/tools/metrics/histograms/metadata/optimization/histograms.xml b/tools/metrics/histograms/metadata/optimization/histograms.xml
index dc4e9a1..7e980b9 100644
--- a/tools/metrics/histograms/metadata/optimization/histograms.xml
+++ b/tools/metrics/histograms/metadata/optimization/histograms.xml
@@ -479,8 +479,9 @@
   <owner>mcrouse@chromium.org</owner>
   <summary>
     Records the status of whether the content annotations were stored in History
-    Service when the page content has been annotated successfully. This will
-    approximately be once a page load after the page load has finished loading.
+    Service when the page content has been annotated successfully. This will be
+    recorded multiple times per page load, for both the related searches as well
+    as the model annotations once the page has finished loading.
   </summary>
 </histogram>
 
@@ -496,6 +497,18 @@
   </summary>
 </histogram>
 
+<histogram
+    name="OptimizationGuide.PageContentAnnotationsService.RelatedSearchesExtracted"
+    enum="BooleanAnnotated" expires_after="M106">
+  <owner>mahmadi@chromium.org</owner>
+  <owner>sophiechang@chromium.org</owner>
+  <summary>
+    Records whether related searches were successfully extracted from Google
+    SRP, if requested. This will be recorded approximately once per page load
+    after the page has finished loading.
+  </summary>
+</histogram>
+
 <histogram name="OptimizationGuide.PageTextDump.AbandonedRequests"
     units="count" expires_after="M106">
   <owner>robertogden@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/others/histograms.xml b/tools/metrics/histograms/metadata/others/histograms.xml
index 063d47a..d97e0af 100644
--- a/tools/metrics/histograms/metadata/others/histograms.xml
+++ b/tools/metrics/histograms/metadata/others/histograms.xml
@@ -10612,7 +10612,7 @@
 </histogram>
 
 <histogram name="NativeSmbFileShare.AuthenticationMethod"
-    enum="NativeSmbFileShare_AuthMethod" expires_after="M94">
+    enum="NativeSmbFileShare_AuthMethod" expires_after="M102">
   <owner>simmonsjosh@google.com</owner>
   <owner>src/chrome/browser/ash/smb_client/OWNERS</owner>
   <summary>
@@ -10622,7 +10622,7 @@
 </histogram>
 
 <histogram name="NativeSmbFileShare.GetSharesResult"
-    enum="NativeSmbFileShare_MountResult" expires_after="M94">
+    enum="NativeSmbFileShare_MountResult" expires_after="M102">
   <owner>simmonsjosh@google.com</owner>
   <owner>src/chrome/browser/ash/smb_client/OWNERS</owner>
   <summary>
@@ -10631,8 +10631,8 @@
   </summary>
 </histogram>
 
-<histogram name="NativeSmbFileShare.MountCount" units="units"
-    expires_after="M94">
+<histogram name="NativeSmbFileShare.MountCount" units="Active Mounts"
+    expires_after="M102">
   <owner>simmonsjosh@google.com</owner>
   <owner>src/chrome/browser/ash/smb_client/OWNERS</owner>
   <summary>
@@ -10642,7 +10642,7 @@
 </histogram>
 
 <histogram name="NativeSmbFileShare.MountResult"
-    enum="NativeSmbFileShare_MountResult" expires_after="M94">
+    enum="NativeSmbFileShare_MountResult" expires_after="M102">
   <owner>simmonsjosh@google.com</owner>
   <owner>src/chrome/browser/ash/smb_client/OWNERS</owner>
   <summary>
@@ -10677,6 +10677,9 @@
 
 <histogram name="NativeSmbFileShare.RemountResult"
     enum="NativeSmbFileShare_MountResult" expires_after="M94">
+  <obsolete>
+    Removed in M94.
+  </obsolete>
   <owner>simmonsjosh@google.com</owner>
   <owner>src/chrome/browser/ash/smb_client/OWNERS</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/stability/histograms.xml b/tools/metrics/histograms/metadata/stability/histograms.xml
index 94b5e1e..61e7bf54 100644
--- a/tools/metrics/histograms/metadata/stability/histograms.xml
+++ b/tools/metrics/histograms/metadata/stability/histograms.xml
@@ -101,6 +101,9 @@
 <histogram name="Stability.Android.StrongBindingOomRemainingBindingState"
     enum="Android.ChildProcessBindingStateCombination"
     expires_after="2021-08-09">
+  <obsolete>
+    Removed on 08/2021. No longer needed.
+  </obsolete>
   <owner>boliu@chromium.org</owner>
   <owner>ssid@chromium.org</owner>
   <summary>
@@ -113,6 +116,9 @@
 
 <histogram name="Stability.Android.StrongBindingOomRemainingStrongBindingCount"
     units="units" expires_after="2020-08-30">
+  <obsolete>
+    Removed on 08/2021. No longer needed.
+  </obsolete>
   <owner>boliu@chromium.org</owner>
   <owner>ssid@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/variations/histograms.xml b/tools/metrics/histograms/metadata/variations/histograms.xml
index b1c06e57..8b87fac 100644
--- a/tools/metrics/histograms/metadata/variations/histograms.xml
+++ b/tools/metrics/histograms/metadata/variations/histograms.xml
@@ -51,8 +51,22 @@
   </summary>
 </histogram>
 
+<histogram name="Variations.ExtendedSafeMode.GotVariationsFileContents"
+    enum="BooleanSuccess" expires_after="2022-01-22">
+  <owner>caitlinfischer@google.com</owner>
+  <owner>src/base/metrics/OWNERS</owner>
+  <component>Internals&gt;Metrics&gt;Variations</component>
+  <summary>
+    Whether getting file contents from the Variations beacon file succeeded.
+    This is the case when the file exists, its contents are successfully read,
+    and its contents are non-empty and in the expected format. Note that this
+    file is used by only one branch of the Extended Variations Safe Mode
+    experiment. Recorded when the CleanExitBeacon is constructed.
+  </summary>
+</histogram>
+
 <histogram name="Variations.ExtendedSafeMode.WritePrefsTime"
-    units="microseconds" expires_after="2021-11-22">
+    units="microseconds" expires_after="2022-01-22">
   <owner>caitlinfischer@google.com</owner>
   <owner>src/base/metrics/OWNERS</owner>
   <component>Internals&gt;Metrics&gt;Variations</component>
diff --git a/tools/metrics/histograms/metadata/web_audio/histograms.xml b/tools/metrics/histograms/metadata/web_audio/histograms.xml
index 689bf19d..76d5cb4 100644
--- a/tools/metrics/histograms/metadata/web_audio/histograms.xml
+++ b/tools/metrics/histograms/metadata/web_audio/histograms.xml
@@ -58,6 +58,16 @@
   </summary>
 </histogram>
 
+<histogram name="WebAudio.AudioContext.CreateTime" units="ms"
+    expires_after="2022-08-12">
+  <owner>hongchan@chromium.org</owner>
+  <owner>cduvall@chromium.org</owner>
+  <summary>
+    Measures the time it takes to create the AudioContext object. This is
+    recorded every time an AudioContext is successfully created.
+  </summary>
+</histogram>
+
 <histogram name="WebAudio.AudioContext.HardwareSampleRate" units="Hz"
     expires_after="2021-12-31">
   <owner>hongchan@chromium.org</owner>
@@ -211,6 +221,16 @@
   </summary>
 </histogram>
 
+<histogram name="WebAudio.OfflineAudioContext.CreateTime" units="ms"
+    expires_after="2022-08-12">
+  <owner>hongchan@chromium.org</owner>
+  <owner>cduvall@chromium.org</owner>
+  <summary>
+    Measures the time it takes to create the OfflineAudioContext object. This is
+    recorded every time an OfflineAudioContext is successfully created.
+  </summary>
+</histogram>
+
 <histogram name="WebAudio.OfflineAudioContext.Length" units="frames"
     expires_after="2021-05-23">
   <obsolete>
diff --git a/tools/metrics/histograms/metadata/webauthn/histograms.xml b/tools/metrics/histograms/metadata/webauthn/histograms.xml
index 35717446..3c1078df 100644
--- a/tools/metrics/histograms/metadata/webauthn/histograms.xml
+++ b/tools/metrics/histograms/metadata/webauthn/histograms.xml
@@ -103,6 +103,16 @@
   </summary>
 </histogram>
 
+<histogram name="WebAuthentication.CableV2.TunnelServerError"
+    enum="CombinedHttpResponseAndNetErrorCode" expires_after="2022-02-01">
+  <owner>agl@chromium.org</owner>
+  <owner>martinkr@google.com</owner>
+  <summary>
+    Records network and HTTP errors when a tunnel server connection fails. (The
+    HTTP error takes precedence if it's available.)
+  </summary>
+</histogram>
+
 <histogram name="WebAuthentication.ChromeOS.GetAssertionStatus"
     enum="WebAuthenticationChromeOSGetAssertionResult"
     expires_after="2022-01-23">
diff --git a/tools/metrics/ukm/ukm.xml b/tools/metrics/ukm/ukm.xml
index b53d7a50..f099217b 100644
--- a/tools/metrics/ukm/ukm.xml
+++ b/tools/metrics/ukm/ukm.xml
@@ -6743,6 +6743,9 @@
     </summary>
     <aggregation>
       <history>
+        <index fields="profile.country"/>
+        <index fields="profile.form_factor"/>
+        <index fields="profile.system_ram"/>
         <statistics>
           <quantiles type="std-percentiles"/>
         </statistics>
@@ -6757,6 +6760,9 @@
     </summary>
     <aggregation>
       <history>
+        <index fields="profile.country"/>
+        <index fields="profile.form_factor"/>
+        <index fields="profile.system_ram"/>
         <statistics>
           <quantiles type="std-percentiles"/>
         </statistics>
@@ -6769,6 +6775,9 @@
     </summary>
     <aggregation>
       <history>
+        <index fields="profile.country"/>
+        <index fields="profile.form_factor"/>
+        <index fields="profile.system_ram"/>
         <statistics>
           <quantiles type="std-percentiles"/>
         </statistics>
@@ -6783,6 +6792,9 @@
     </summary>
     <aggregation>
       <history>
+        <index fields="profile.country"/>
+        <index fields="profile.form_factor"/>
+        <index fields="profile.system_ram"/>
         <statistics>
           <quantiles type="std-percentiles"/>
         </statistics>
@@ -6796,6 +6808,9 @@
     </summary>
     <aggregation>
       <history>
+        <index fields="profile.country"/>
+        <index fields="profile.form_factor"/>
+        <index fields="profile.system_ram"/>
         <statistics>
           <quantiles type="std-percentiles"/>
         </statistics>
@@ -6809,6 +6824,9 @@
     </summary>
     <aggregation>
       <history>
+        <index fields="profile.country"/>
+        <index fields="profile.form_factor"/>
+        <index fields="profile.system_ram"/>
         <statistics>
           <quantiles type="std-percentiles"/>
         </statistics>
@@ -6822,6 +6840,9 @@
     </summary>
     <aggregation>
       <history>
+        <index fields="profile.country"/>
+        <index fields="profile.form_factor"/>
+        <index fields="profile.system_ram"/>
         <statistics>
           <quantiles type="std-percentiles"/>
         </statistics>
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index 88531933..320a962 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -5,20 +5,20 @@
             "remote_path": "perfetto_binaries/trace_processor_shell/linux_arm/49b4b5dcbc312d8d2c3751cf29238b8efeb4e494/trace_processor_shell"
         },
         "win": {
-            "hash": "1202c0883c68706d24d33229cb772730dbee4b3a",
-            "remote_path": "perfetto_binaries/trace_processor_shell/win/f73e19a324498c7896ddb2e082fad8cb4607545d/trace_processor_shell.exe"
+            "hash": "1f6bb8e12362e8de7f884199381c6f64bd5a95e6",
+            "remote_path": "perfetto_binaries/trace_processor_shell/win/91a6d3a9b815351984230325e644e263c00825f0/trace_processor_shell.exe"
         },
         "mac": {
-            "hash": "0cfe0976cb4f931833b822104d2c212a1e737f65",
-            "remote_path": "perfetto_binaries/trace_processor_shell/mac/b34dc62800c061e62c78039a60b9efd9786ede89/trace_processor_shell"
+            "hash": "b34a79092c97578fe45f05879e98dab701e278a6",
+            "remote_path": "perfetto_binaries/trace_processor_shell/mac/91a6d3a9b815351984230325e644e263c00825f0/trace_processor_shell"
         },
         "linux_arm64": {
             "hash": "5074025a2898ec41a872e70a5719e417acb0a380",
             "remote_path": "perfetto_binaries/trace_processor_shell/linux_arm64/49b4b5dcbc312d8d2c3751cf29238b8efeb4e494/trace_processor_shell"
         },
         "linux": {
-            "hash": "f5cfa6d6c5c869b45c48d523322f4a3f061aceb2",
-            "remote_path": "perfetto_binaries/trace_processor_shell/linux/91a6d3a9b815351984230325e644e263c00825f0/trace_processor_shell"
+            "hash": "0bdcb784edd807a695bcbd1cc42ba569161f917a",
+            "remote_path": "perfetto_binaries/trace_processor_shell/linux/e0c4d9b9566c52974287aa1a6678e16f1949c6c4/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/tools/tracing/breakpad_file_extractor.py b/tools/tracing/breakpad_file_extractor.py
index 00719ea..4f2aecb 100644
--- a/tools/tracing/breakpad_file_extractor.py
+++ b/tools/tracing/breakpad_file_extractor.py
@@ -6,14 +6,23 @@
 """
 
 import os
+import sys
 import logging
 import subprocess
+import rename_breakpad
+
+sys.path.insert(
+    0,
+    os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, 'third_party',
+                 'catapult', 'common', 'py_utils'))
+from py_utils import tempfile_ext
 
 
 def ExtractBreakpadFiles(dump_syms_path,
                          build_dir,
                          breakpad_output_dir,
-                         search_unstripped=True):
+                         search_unstripped=True,
+                         module_ids=None):
   """Uses dump_syms to extract breakpad files.
 
   Args:
@@ -26,6 +35,9 @@
       subdirectory or not. If specified and '|build_dir|/lib.unstripped'
       exists, dump_syms is run on this directory instead. If not specified,
       dump_syms is run on |build_dir|.
+    module_ids: A set of module IDs needed to symbolize the trace. Only extracts
+      breakpad on symbol binaries with a module ID in this set. Extracts all
+      symbols if |module_ids| is None.
 
   Returns:
     True if at least one breakpad file could be extracted from |build_dir|;
@@ -58,35 +70,159 @@
   for file_iter in os.listdir(symbol_dir):
     input_file_path = os.path.join(symbol_dir, file_iter)
     if os.path.isfile(input_file_path) and _IsValidBinaryPath(input_file_path):
+      if not _IsModuleNeededForSymbolization(dump_syms_path, module_ids,
+                                             input_file_path):
+        continue
       # Construct absolute file paths for input and output files.
       output_file_path = os.path.join(breakpad_output_dir,
                                       file_iter + '.breakpad')
+
+      logging.debug('Extracting breakpad file from: ' + input_file_path)
       if _RunDumpSyms(dump_syms_binary, input_file_path, output_file_path):
+        logging.debug('Extracted breakpad to: ' + output_file_path)
         breakpad_file_count += 1
 
   # Extracting breakpad symbols should be successful with at least one file.
   return breakpad_file_count > 0
 
 
-def _RunDumpSyms(dump_syms_binary, input_file_path, output_file_path):
+def ExtractBreakpadOnSubtree(symbols_root, metadata, dump_syms_path):
+  """Converts symbol files in the given subtree into breakpad files.
+
+  Args:
+    symbols_root: root of subtree containing symbol files to convert to
+      breakpad format.
+    metadata: trace metadata to extract module ids from.
+    dump_syms_path: local path to dump_syms binary.
+
+  Raises:
+    Exception: if path to dump_syms binary not passed or no breakpad files
+      could be extracted from subtree.
+  """
+  logging.debug('Converting symbols to breakpad format.')
+  if dump_syms_path is None:
+    raise Exception('Path to dump_syms binary is required for symbolizing '
+                    'official Android traces. You can build dump_syms from '
+                    'your local build directory with the right architecture '
+                    'with: autoninja -C out_<arch>/Release dump_syms.')
+
+  # Set of module IDs we need to symbolize.
+  module_ids = GetModuleIdsToSymbolize(metadata)
+
+  did_extract = False
+  for root_dir, _, _ in os.walk(symbols_root, topdown=True):
+    root_path = os.path.abspath(root_dir)
+    did_extract |= ExtractBreakpadFiles(dump_syms_path,
+                                        root_path,
+                                        root_path,
+                                        search_unstripped=False,
+                                        module_ids=module_ids)
+
+  if not did_extract:
+    raise Exception(
+        'No breakpad symbols could be extracted from files in the subtree: ' +
+        symbols_root)
+
+
+def GetModuleIdsToSymbolize(metadata):
+  """Returns module IDs needed for symbolization and logs breakpad message.
+
+  We log the message before calling |ExtractBreakpadFiles| because otherwise
+  we will repeatedly log when |ExtractBreakpadOnSubtree| recursively runs
+  |ExtractBreakpadFiles|.
+  """
+  module_ids = metadata.GetModuleIds()
+
+  if module_ids is None:
+    logging.info('No specified modules to extract. Converting all symbol '
+                 'binaries to breakpad.')
+  else:
+    logging.debug('Module IDs to symbolize: %s' % (module_ids))
+
+  return module_ids
+
+
+def _IsModuleNeededForSymbolization(dump_syms_path, module_ids, symbol_binary):
+  """Determines if we should extract breakpad from symbol binary.
+
+  If module_ids is None, then we extract breakpad on all symbol binaries.
+  Otherwise, we only extract breakpad on binaries with a module ID needed to
+  symbolize the trace.
+
+  Args:
+    dump_syms_path: The path to the dump_syms binary that should be run.
+    module_ids: A set of module IDs needed to symbolize the trace. Only extracts
+      breakpad on symbol binaries with a module ID in this set. Extracts all
+      symbols if |module_ids| is None.
+    symbol_binary: Symbol binary file to symbolize trace.
+
+  Returns:
+    True if symbols should be extracted to breakpad; false, otherwise.
+  """
+  if module_ids is None:
+    return True
+
+  # Only convert breakpad if binary has module ID we need to symbolize.
+  module_id = _GetModuleIDFromBinary(dump_syms_path, symbol_binary)
+  if module_id is None or module_id not in module_ids:
+    logging.debug('Skipping breakpad extraction for module (%s, %s) '
+                  'since trace has no frames with this ID.' %
+                  (module_id, symbol_binary))
+    return False
+  return True
+
+
+def _GetModuleIDFromBinary(dump_syms_path, symbol_binary):
+  """Gets module ID of symbol binary.
+
+  Args:
+    dump_syms_path: The path to the dump_syms binary that should be run.
+    symbol_binary: path to symbol binary.
+
+  Returns:
+    Module ID from symbol binary, or None if fails to extract.
+  """
+  # Creates temp file because |_RunDumpSyms| pipes result into a file.
+  # After extracting the module ID, we do not need this output file.
+  with tempfile_ext.NamedTemporaryFile(mode='w+') as output_file:
+    output_file.close()  # RunDumpsyms opens the file again.
+    if not _RunDumpSyms(dump_syms_path,
+                        symbol_binary,
+                        output_file.name,
+                        only_module_header=True):
+      return None
+    return rename_breakpad.ExtractModuleIdIfValidBreakpad(output_file.name)
+
+
+def _RunDumpSyms(dump_syms_binary,
+                 input_file_path,
+                 output_file_path,
+                 only_module_header=False):
   """Runs the dump_syms binary on a file and outputs the resulting breakpad
      symbols to the specified file.
   Args:
-    cmd: The command to run dump_syms.
-    output_file_path: The file path for the output breakpad symbol file.
+    dump_syms_path: The path to the dump_syms binary that should be run.
+    input_file_path: Input file path to run dump_syms on.
+    output_file_path: Output file path to store result.
+    only_module_header: Only extracts the module header, if specified.
 
   Returns:
     True if the command succeeded and false otherwise.
   """
-  cmd = [dump_syms_binary, input_file_path]
+  cmd = [dump_syms_binary]
+  if only_module_header:
+    cmd.append('-i')
+  cmd.append(input_file_path)
+
   with open(output_file_path, 'w') as f:
     proc = subprocess.Popen(cmd, stdout=f, stderr=subprocess.PIPE)
   stderr = proc.communicate()[1]
   if proc.returncode != 0:
     logging.warning('%s', str(stderr))
-    logging.debug('Could not create breakpad symbols %s', input_file_path)
+    logging.debug(
+        'Dump_syms failed to extract information from symbol binary: ' +
+        input_file_path)
     return False
-  logging.debug('Created breakpad symbols from %s', input_file_path)
   return True
 
 
diff --git a/tools/tracing/breakpad_file_extractor_unittest.py b/tools/tracing/breakpad_file_extractor_unittest.py
index 6921f59b..25a290b 100755
--- a/tools/tracing/breakpad_file_extractor_unittest.py
+++ b/tools/tracing/breakpad_file_extractor_unittest.py
@@ -40,11 +40,99 @@
     with open(self.test_dump_syms_binary, 'w'):
       pass
 
+    # Stash function.
+    self.RunDumpSyms_stash = breakpad_file_extractor._RunDumpSyms
+
   def tearDown(self):
     shutil.rmtree(self.test_build_dir)
     shutil.rmtree(self.test_breakpad_dir)
     shutil.rmtree(self.test_dump_syms_dir)
 
+    # Unstash function.
+    breakpad_file_extractor._RunDumpSyms = self.RunDumpSyms_stash
+
+  def _setupSubtreeFiles(self):
+    # Create subtree directory structure. All files deleted when
+    # |test_breakpad_dir| is recursively deleted.
+    out = tempfile.mkdtemp(dir=self.test_breakpad_dir)
+    release = tempfile.mkdtemp(dir=out)
+    subdir = tempfile.mkdtemp(dir=release)
+    unstripped_dir = os.path.join(release, 'lib.unstripped')
+    os.mkdir(unstripped_dir)
+
+    # Create symbol files.
+    symbol_files = []
+    symbol_files.append(os.path.join(subdir, 'subdir.so'))
+    symbol_files.append(os.path.join(unstripped_dir, 'unstripped.so'))
+    symbol_files.append(os.path.join(unstripped_dir, 'unstripped2.so'))
+
+    for new_file in symbol_files:
+      with open(new_file, 'w') as _:
+        pass
+
+    # Build side effect mapping.
+    side_effect_map = {
+        symbol_files[0]: 'MODULE Android x86_64 34984AB4EF948C subdir.so',
+        symbol_files[1]: 'MODULE Android x86_64 34984AB4EF948D unstripped.so',
+        symbol_files[2]: 'MODULE Android x86_64 34984AB4EF949A unstripped2.so'
+    }
+
+    return symbol_files, side_effect_map
+
+  def _mockDumpSyms(self, side_effect_map):
+    def run_dumpsyms_side_effect(dump_syms_binary,
+                                 input_file_path,
+                                 output_file_path,
+                                 only_module_header=False):
+      self.assertEqual(self.test_dump_syms_binary, dump_syms_binary)
+      if only_module_header:
+        # Extract Module ID.
+        with open(output_file_path, 'w') as f:
+          # Write the correct module header into the output f
+          f.write(side_effect_map[input_file_path])
+      else:
+        # Extract breakpads.
+        with open(output_file_path, 'w'):
+          pass
+      return True
+
+    return run_dumpsyms_side_effect
+
+  def _getExpectedModuleExtractionCalls(self, symbol_files):
+    expected_module_calls = [
+        mock.call(self.test_dump_syms_binary,
+                  symbol_fle,
+                  mock.ANY,
+                  only_module_header=True) for symbol_fle in symbol_files
+    ]
+    return expected_module_calls
+
+  def _getExpectedBreakpadExtractionCalls(self, extracted_files,
+                                          breakpad_files):
+    expected_extract_calls = [
+        mock.call(self.test_dump_syms_binary, extracted_file,
+                  breakpad_files[file_iter])
+        for file_iter, extracted_file in enumerate(extracted_files)
+    ]
+    return expected_extract_calls
+
+  def _getAndEnsureExtractedBreakpadFiles(self, extracted_files):
+    breakpad_files = []
+    for extracted_file in extracted_files:
+      breakpad_filename = os.path.basename(extracted_file) + '.breakpad'
+      breakpad_file = os.path.join(self.test_breakpad_dir, breakpad_filename)
+      assert (os.path.isfile(breakpad_file))
+      breakpad_files.append(breakpad_file)
+    return breakpad_files
+
+  def _getAndEnsureExpectedSubtreeBreakpadFiles(self, extracted_files):
+    breakpad_files = []
+    for extracted_file in extracted_files:
+      breakpad_file = extracted_file + '.breakpad'
+      assert (os.path.isfile(breakpad_file))
+      breakpad_files.append(breakpad_file)
+    return breakpad_files
+
   def _checkExtractWithOneBinary(self, dump_syms_path, build_dir, breakpad_dir):
     # Create test file in |test_build_dir| and test file in |test_breakpad_dir|.
     test_input_file = tempfile.NamedTemporaryFile(suffix='.so', dir=build_dir)
@@ -99,56 +187,34 @@
   def testMultipleBinaryFiles(self):
     # Create files in |test_build_dir|. All files are removed when
     # |test_build_dir| is recursively deleted.
-    input_filenames = []
+    symbol_files = []
     so_file = os.path.join(self.test_build_dir, 'test_file.so')
     with open(so_file, 'w') as _:
       pass
-    input_filenames.append(so_file)
+    symbol_files.append(so_file)
     exe_file = os.path.join(self.test_build_dir, 'test_file.exe')
     with open(exe_file, 'w') as _:
       pass
-    input_filenames.append(exe_file)
+    symbol_files.append(exe_file)
     chrome_file = os.path.join(self.test_build_dir, 'chrome')
     with open(chrome_file, 'w') as _:
       pass
-    input_filenames.append(chrome_file)
+    symbol_files.append(chrome_file)
 
     # Form output file paths.
-    output_file_paths = []
-    for file_iter in input_filenames:
-      input_file_name = os.path.split(file_iter)[1]
-      test_output_file_path = '{output_path}.breakpad'.format(
-          output_path=os.path.join(self.test_breakpad_dir, input_file_name))
-      with open(test_output_file_path, 'w'):
-        pass
-      output_file_paths.append(test_output_file_path)
-    breakpad_file_extractor._RunDumpSyms = mock.MagicMock()
+    breakpad_file_extractor._RunDumpSyms = mock.MagicMock(
+        side_effect=self._mockDumpSyms({}))
     breakpad_file_extractor.ExtractBreakpadFiles(self.test_dump_syms_binary,
                                                  self.test_build_dir,
                                                  self.test_breakpad_dir)
 
-    # Check that each call expected call to _RunDumpSyms() has been made.
-    expected_calls = [
-        mock.call(self.test_dump_syms_binary, input_filename,
-                  output_file_paths[file_iter])
-        for file_iter, input_filename in enumerate(input_filenames)
-    ]
+    # Check that each expected call to _RunDumpSyms() has been made.
+    breakpad_files = self._getAndEnsureExtractedBreakpadFiles(symbol_files)
+    expected_calls = self._getExpectedBreakpadExtractionCalls(
+        symbol_files, breakpad_files)
     breakpad_file_extractor._RunDumpSyms.assert_has_calls(expected_calls,
                                                           any_order=True)
 
-    # Check that the expected files exist in the output directory.
-    expected_files = [
-        os.path.basename(f) + '.breakpad' for f in input_filenames
-    ]
-    file_count = 0
-    for filename in os.listdir(self.test_breakpad_dir):
-      file_path = os.path.join(self.test_breakpad_dir, filename)
-      if os.path.isfile(file_path):
-        file_count += 1
-    self.assertEqual(file_count, 3)
-    self.assertEqual(set(expected_files),
-                     set(os.listdir(self.test_breakpad_dir)))
-
   def testDumpSymsNotFound(self):
     breakpad_file_extractor._RunDumpSyms = mock.MagicMock()
     exception_msg = 'dump_syms is missing.'
@@ -233,6 +299,64 @@
     os.remove(combined_file1)
     os.remove(combined_file2)
 
+  def testExtractOnSubtree(self):
+    # Setup subtree symbol files.
+    symbol_files, side_effect_map = self._setupSubtreeFiles()
+    subdir_symbols = symbol_files[0]
+    unstripped_symbols = symbol_files[1]
+
+    # Setup metadata.
+    metadata = metadata_extractor.MetadataExtractor('trace_processor_shell',
+                                                    'trace_file.proto')
+    metadata.InitializeForTesting(modules={
+        '/subdir.so': '34984AB4EF948D',
+        '/unstripped.so': '34984AB4EF948C'
+    })
+    extracted_files = [subdir_symbols, unstripped_symbols]
+
+    # Setup |_RunDumpSyms| mock for module ID optimization.
+    breakpad_file_extractor._RunDumpSyms = mock.MagicMock(
+        side_effect=self._mockDumpSyms(side_effect_map))
+    breakpad_file_extractor.ExtractBreakpadOnSubtree(self.test_breakpad_dir,
+                                                     metadata,
+                                                     self.test_dump_syms_binary)
+
+    # Ensure correct |_RunDumpSyms| calls.
+    expected_module_calls = self._getExpectedModuleExtractionCalls(symbol_files)
+
+    breakpad_files = self._getAndEnsureExpectedSubtreeBreakpadFiles(
+        extracted_files)
+    expected_extract_calls = self._getExpectedBreakpadExtractionCalls(
+        extracted_files, breakpad_files)
+
+    breakpad_file_extractor._RunDumpSyms.assert_has_calls(
+        expected_module_calls + expected_extract_calls, any_order=True)
+
+  def testSubtreeNoFilesExtracted(self):
+    # Setup subtree symbol files. No files to be extracted.
+    symbol_files, side_effect_map = self._setupSubtreeFiles()
+
+    # Empty set of module IDs to extract. Nothing should be extracted.
+    metadata = metadata_extractor.MetadataExtractor('trace_processor_shell',
+                                                    'trace_file.proto')
+    metadata.InitializeForTesting(modules={})
+
+    # Setup |_RunDumpSyms| mock for module ID optimization.
+    breakpad_file_extractor._RunDumpSyms = mock.MagicMock(
+        side_effect=self._mockDumpSyms(side_effect_map))
+    exception_msg = (
+        'No breakpad symbols could be extracted from files in the subtree: ' +
+        self.test_breakpad_dir)
+    with self.assertRaises(Exception) as e:
+      breakpad_file_extractor.ExtractBreakpadOnSubtree(
+          self.test_breakpad_dir, metadata, self.test_dump_syms_binary)
+    self.assertIn(exception_msg, str(e.exception))
+
+    # Should be calls to extract module ID, but none to extract breakpad.
+    expected_module_calls = self._getExpectedModuleExtractionCalls(symbol_files)
+    breakpad_file_extractor._RunDumpSyms.assert_has_calls(expected_module_calls,
+                                                          any_order=True)
+
 
 if __name__ == "__main__":
   unittest.main()
diff --git a/tools/tracing/metadata_extractor.py b/tools/tracing/metadata_extractor.py
index 046577f9..eecc8f55 100644
--- a/tools/tracing/metadata_extractor.py
+++ b/tools/tracing/metadata_extractor.py
@@ -86,6 +86,14 @@
   def trace_file(self):
     return self._trace_file
 
+  def GetModuleIds(self):
+    """Returns set of all module IDs in |modules| field.
+    """
+    self.Initialize()
+    if self.modules is None:
+      return None
+    return set(self.modules.values())
+
   def Initialize(self):
     """Extracts metadata from perfetto system trace.
     """
diff --git a/tools/tracing/metadata_extractor_unittest.py b/tools/tracing/metadata_extractor_unittest.py
index 2f6256a..357fa45 100755
--- a/tools/tracing/metadata_extractor_unittest.py
+++ b/tools/tracing/metadata_extractor_unittest.py
@@ -280,6 +280,29 @@
       extractor.Initialize()
     self.assertEqual(exception_msg, str(context.exception))
 
+  def testGetModuleIds(self):
+    extractor = metadata_extractor.MetadataExtractor(self.trace_processor_path,
+                                                     self.trace_file)
+    extractor.InitializeForTesting(modules={
+        'name': '13423EDFAB2',
+        'name2': '321468945',
+        'name3': '4093492737482'
+    })
+    self.assertEqual(extractor.GetModuleIds(),
+                     {'13423EDFAB2', '321468945', '4093492737482'})
+
+  def testGetModuleIdsEmpty(self):
+    extractor = metadata_extractor.MetadataExtractor(self.trace_processor_path,
+                                                     self.trace_file)
+    extractor.InitializeForTesting(modules={})
+    self.assertEqual(extractor.GetModuleIds(), set())
+
+  def testGetModuleIdsNone(self):
+    extractor = metadata_extractor.MetadataExtractor(self.trace_processor_path,
+                                                     self.trace_file)
+    extractor.InitializeForTesting(modules=None)
+    self.assertEqual(extractor.GetModuleIds(), None)
+
 
 if __name__ == '__main__':
   unittest.main()
diff --git a/tools/tracing/rename_breakpad.py b/tools/tracing/rename_breakpad.py
index 2613212..223c81ec 100644
--- a/tools/tracing/rename_breakpad.py
+++ b/tools/tracing/rename_breakpad.py
@@ -41,7 +41,7 @@
         logging.debug("File is not a breakpad file: " + file_path)
         continue
 
-      module_id = _ExtractModuleIdIfValidBreakpad(file_path)
+      module_id = ExtractModuleIdIfValidBreakpad(file_path)
       if module_id is None:
         logging.debug("Failed to extract file module id: " + file_path)
         continue
@@ -65,7 +65,7 @@
   # Doesn't matter if |breakpad_output_dir| is a temporary directory.
 
 
-def _ExtractModuleIdIfValidBreakpad(file_path):
+def ExtractModuleIdIfValidBreakpad(file_path):
   """Extracts breakpad file's module id if the file is valid.
 
   A breakpad file is valid for extracting its module id if it
diff --git a/tools/tracing/symbol_fetcher.py b/tools/tracing/symbol_fetcher.py
index 5dc69e1..9503b58 100644
--- a/tools/tracing/symbol_fetcher.py
+++ b/tools/tracing/symbol_fetcher.py
@@ -51,7 +51,8 @@
   # Obtain breakpad symbols by platform.
   if metadata.os_name == OSName.ANDROID:
     _GetAndroidSymbols(cloud_storage_bucket, metadata, breakpad_output_dir)
-    _ConvertSymbolsToBreakpad(breakpad_output_dir, dump_syms_path)
+    breakpad_file_extractor.ExtractBreakpadOnSubtree(breakpad_output_dir,
+                                                     metadata, dump_syms_path)
     rename_breakpad.RenameBreakpadFiles(breakpad_output_dir,
                                         breakpad_output_dir)
   elif metadata.os_name == OSName.WINDOWS:
@@ -218,38 +219,6 @@
       raise Exception('Failed to find symbols on GCS: %s[.zip].' % (gcs_file))
 
 
-def _ConvertSymbolsToBreakpad(symbols_root, dump_syms_path):
-  """Converts symbol files in the given subtree into breakpad files.
-
-  Args:
-    symbols_root: root of subtree containing symbol files to convert to
-      breakpad format.
-    dump_syms_path: local path to dump_syms binary.
-
-  Raises:
-    Exception: if path to dump_syms binary not passed or no breakpad files
-      could be extracted from subtree.
-  """
-  logging.debug('Converting symbols to breakpad format.')
-  if dump_syms_path is None:
-    raise Exception('Path to dump_syms binary is required for symbolizing '
-                    'official Android traces. You can build dump_syms from '
-                    'your local build directory with the right architecture '
-                    'with: autoninja -C out_<arch>/Release dump_syms.')
-  did_extract = False
-  for root_dir, _, _ in os.walk(symbols_root, topdown=True):
-    root_path = os.path.abspath(root_dir)
-    # TODO(rhuckleberry): Only run dump_syms on files listed in trace's
-    # modules metadata to speed up breakpad extraction time.
-    did_extract |= breakpad_file_extractor.ExtractBreakpadFiles(
-        dump_syms_path, root_path, root_path, search_unstripped=False)
-
-  if not did_extract:
-    raise Exception(
-        'No breakpad symbols could be extracted from files in the subtree: ' +
-        symbols_root)
-
-
 def _FetchAndUnzipGCSFile(cloud_storage_bucket, gcs_file, gcs_output,
                           output_dir):
   """Fetch file from GCS to local |gcs_output|, then unzip it into |output_dir|.
diff --git a/tools/tracing/symbol_fetcher_unittest.py b/tools/tracing/symbol_fetcher_unittest.py
index 1fdfb64..30198f1 100755
--- a/tools/tracing/symbol_fetcher_unittest.py
+++ b/tools/tracing/symbol_fetcher_unittest.py
@@ -273,8 +273,7 @@
   def _setUpBasicRunDumpSyms(self):
     """Sets up symbol files to run the |RunDumpSyms| function.
 
-    Basic file setup used across all (non-error) Android tests that do not
-    specifically test the |_ConvertSymbolsToBreakpad| function.
+    Basic file setup used across all (non-error) Android tests.
     """
     extracted_files = []
     extracted_files.append(os.path.join(self.unstripped_dir, 'unstripped.so'))
@@ -380,7 +379,8 @@
                                              os_name=OSName.ANDROID,
                                              architecture='x86_64',
                                              bitness='64',
-                                             version_code='358923')
+                                             version_code='358923',
+                                             modules=None)
     match_arch_folder = 'x86_64'
     symbol_fetcher._FetchGCSFile = mock.Mock(
         side_effect=self._mockVersionCodeFetcher(match_arch_folder, metadata))
@@ -396,12 +396,13 @@
                                                           any_order=True)
     self._ensureRunDumpSymsAndRenameCalls(extract_files)
 
-  def testDifferentArchAndMatchingFolder(self):
+  def testCrossArchitecture(self):
     metadata = self._createMetadataExtractor(version_number='123',
                                              os_name=OSName.ANDROID,
                                              architecture='x86_64',
                                              bitness='64',
-                                             version_code='358923')
+                                             version_code='358923',
+                                             modules=None)
     match_arch_folder = 'next-x86'
     symbol_fetcher._FetchGCSFile = mock.Mock(
         side_effect=self._mockVersionCodeFetcher(match_arch_folder, metadata))
@@ -422,7 +423,8 @@
                                              os_name=OSName.ANDROID,
                                              architecture=None,
                                              bitness='64',
-                                             version_code='358923')
+                                             version_code='358923',
+                                             modules=None)
     match_arch_folder = 'x86_64'
     symbol_fetcher._FetchGCSFile = mock.Mock(
         side_effect=self._mockVersionCodeFetcher(match_arch_folder, metadata))
@@ -441,7 +443,8 @@
                                              os_name=OSName.ANDROID,
                                              architecture='x86_64',
                                              bitness='64',
-                                             version_code=None)
+                                             version_code=None,
+                                             modules=None)
     match_arch_folder = 'x86_64'
     symbol_fetcher._FetchGCSFile = mock.Mock(
         side_effect=self._mockVersionCodeFetcher(match_arch_folder, metadata))
@@ -460,7 +463,8 @@
                                              os_name=OSName.ANDROID,
                                              architecture='x86_64',
                                              bitness='64',
-                                             version_code='328954')
+                                             version_code='328954',
+                                             modules=None)
     match_arch_folder = 'x86_64'
     symbol_fetcher._FetchGCSFile = mock.Mock(
         side_effect=self._mockVersionCodeFetcher(match_arch_folder, metadata))
@@ -481,7 +485,8 @@
                                              os_name=OSName.ANDROID,
                                              architecture='x86_64',
                                              bitness='64',
-                                             version_code='358923')
+                                             version_code='358923',
+                                             modules=None)
 
     # Fails to fetch all 'version_codes.txt' files from GCS.
     symbol_fetcher._FetchGCSFile = mock.MagicMock(return_value=False)
@@ -501,7 +506,8 @@
                                              os_name=OSName.ANDROID,
                                              architecture='x86_64',
                                              bitness='64',
-                                             version_code='358923')
+                                             version_code='358923',
+                                             modules=None)
     # None of the 'version_codes.txt' files match the trace's version code.
     match_arch_folder = None  # No valid paths can be None.
     symbol_fetcher._FetchGCSFile = mock.Mock(
@@ -522,7 +528,8 @@
                                              os_name=OSName.ANDROID,
                                              architecture='armv7',
                                              bitness='64',
-                                             version_code='358923')
+                                             version_code='358923',
+                                             modules=None)
     match_arch_folder = 'next-arm_64'
     symbol_fetcher._FetchGCSFile = mock.Mock(
         side_effect=self._mockVersionCodeFetcher(match_arch_folder, metadata))
@@ -552,7 +559,8 @@
                                              os_name=OSName.ANDROID,
                                              architecture='x86_64',
                                              bitness='64',
-                                             version_code='358923')
+                                             version_code='358923',
+                                             modules=None)
     match_arch_folder = 'x86_64'
     symbol_fetcher._FetchGCSFile = mock.Mock(
         side_effect=self._mockVersionCodeFetcher(match_arch_folder, metadata))
@@ -579,7 +587,8 @@
                                              os_name=OSName.ANDROID,
                                              architecture='x86_64',
                                              bitness='64',
-                                             version_code='358923')
+                                             version_code='358923',
+                                             modules=None)
     match_arch_folder = 'x86_64'
     symbol_fetcher._FetchGCSFile = mock.Mock(
         side_effect=self._mockVersionCodeFetcher(match_arch_folder, metadata))
diff --git a/tools/tracing/symbolize_trace.py b/tools/tracing/symbolize_trace.py
index e753fc5..582d822 100644
--- a/tools/tracing/symbolize_trace.py
+++ b/tools/tracing/symbolize_trace.py
@@ -93,13 +93,23 @@
   # |trace_file| can be symbolized using those symbols.
   if options.local_breakpad_dir is not None:
     return
+
+  # Extract Metadata
+  logging.info('Extracting proto trace metadata.')
+  trace_metadata = metadata_extractor.MetadataExtractor(
+      options.trace_processor_path, trace_file)
+  trace_metadata.Initialize()
+  logging.info(trace_metadata)
+
   if options.local_build_dir is not None:
     # Extract breakpad symbol files from binaries in |options.local_build_dir|.
     if not breakpad_file_extractor.ExtractBreakpadFiles(
         options.dump_syms_path,
         options.local_build_dir,
         options.breakpad_output_dir,
-        search_unstripped=True):
+        search_unstripped=True,
+        module_ids=breakpad_file_extractor.GetModuleIdsToSymbolize(
+            trace_metadata)):
       raise Exception(
           'No breakpad symbols could be extracted from files in: %s xor %s' %
           (options.local_build_dir,
@@ -109,13 +119,6 @@
                                         options.breakpad_output_dir)
     return
 
-  # Extract Metadata
-  logging.info('Extracting proto trace metadata.')
-  trace_metadata = metadata_extractor.MetadataExtractor(
-      options.trace_processor_path, trace_file)
-  trace_metadata.Initialize()
-  logging.info(trace_metadata)
-
   # Fetch trace breakpad symbols from GCS
   logging.info('Fetching and extracting trace breakpad symbols.')
   symbol_fetcher.GetTraceBreakpadSymbols(options.cloud_storage_bucket,
diff --git a/ui/base/interaction/interaction_sequence.cc b/ui/base/interaction/interaction_sequence.cc
index e5fdbc5a..6b108b1 100644
--- a/ui/base/interaction/interaction_sequence.cc
+++ b/ui/base/interaction/interaction_sequence.cc
@@ -231,7 +231,7 @@
 InteractionSequence::~InteractionSequence() {
   // We can abort during a step callback, but we cannot destroy this object.
   if (started_)
-    Abort();
+    Abort(AbortedReason::kSequenceDestroyed);
 }
 
 void InteractionSequence::Start() {
@@ -239,7 +239,7 @@
   DCHECK(!started_);
   started_ = true;
   if (missing_first_element_) {
-    Abort();
+    Abort(AbortedReason::kElementHiddenBeforeSequenceStart);
     return;
   }
   StageNextStep();
@@ -278,7 +278,7 @@
     // seen the triggering event for the next step, abort.
     if (current_step_->must_remain_visible.value() &&
         !activated_during_callback_) {
-      Abort();
+      Abort(AbortedReason::kElementHiddenDuringStep);
       return;
     }
 
@@ -434,7 +434,7 @@
     // We don't want to call the step-end callback during Abort() since we
     // didn't technically start the step.
     current_step_->end_callback = StepCallback();
-    Abort();
+    Abort(AbortedReason::kElementNotVisibleAtStartOfStep);
     return;
   }
 
@@ -473,7 +473,7 @@
   }
 }
 
-void InteractionSequence::Abort() {
+void InteractionSequence::Abort(AbortedReason reason) {
   DCHECK(started_);
   configuration_->steps.clear();
   // The current object could be destroyed during callbacks, so ensure we save
@@ -496,13 +496,13 @@
     RunIfValid(std::move(last_step->end_callback), element.get(), last_step->id,
                last_step->type);
     RunIfValid(std::move(aborted_callback), element.get(), last_step->id,
-               last_step->type);
+               last_step->type, reason);
   } else {
     // Aborted before any steps were run. Pass default values.
     // Note that if the sequence has already been aborted, this is a no-op, the
     // callback will already be null.
     RunIfValid(std::move(configuration_->aborted_callback), nullptr,
-               ElementIdentifier(), StepType::kShown);
+               ElementIdentifier(), StepType::kShown, reason);
   }
   RunIfValid(std::move(quit_closure));
 }
diff --git a/ui/base/interaction/interaction_sequence.h b/ui/base/interaction/interaction_sequence.h
index 4b9fbc7..83013ab 100644
--- a/ui/base/interaction/interaction_sequence.h
+++ b/ui/base/interaction/interaction_sequence.h
@@ -69,6 +69,18 @@
     kHidden
   };
 
+  // Details why a sequence was aborted.
+  enum class AbortedReason {
+    // External code destructed this object before the sequence could complete.
+    kSequenceDestroyed,
+    // The starting element was hidden before the sequence started.
+    kElementHiddenBeforeSequenceStart,
+    // An element should have been visible at the start of a step but was not.
+    kElementNotVisibleAtStartOfStep,
+    // An element should have remained visible during a step but did not.
+    kElementHiddenDuringStep
+  };
+
   // Callback when a step happens in the sequence, or when a step ends. If
   // |element| is no longer available, it will be null.
   using StepCallback = base::OnceCallback<void(TrackedElement* element,
@@ -79,9 +91,11 @@
   // sequence of steps, or if this object is deleted after the sequence starts.
   // The most recent event is described by the parameters; if the target element
   // is no longer available it will be null.
-  using AbortedCallback = base::OnceCallback<void(TrackedElement* last_element,
-                                                  ElementIdentifier last_id,
-                                                  StepType last_step_type)>;
+  using AbortedCallback =
+      base::OnceCallback<void(TrackedElement* last_element,
+                              ElementIdentifier last_id,
+                              StepType last_step_type,
+                              AbortedReason aborted_reason)>;
 
   using CompletedCallback = base::OnceClosure;
 
@@ -255,7 +269,7 @@
   void StageNextStep();
 
   // Cancels the sequence and cleans up.
-  void Abort();
+  void Abort(AbortedReason reason);
 
   // Returns true (and does some sanity checking) if the sequence was aborted
   // during the most recent callback.
diff --git a/ui/base/interaction/interaction_sequence_unittest.cc b/ui/base/interaction/interaction_sequence_unittest.cc
index f140654..4fb34ca 100644
--- a/ui/base/interaction/interaction_sequence_unittest.cc
+++ b/ui/base/interaction/interaction_sequence_unittest.cc
@@ -128,7 +128,12 @@
                        .Build())
           .Build();
   element.reset();
-  EXPECT_CALL_IN_SCOPE(aborted, Run, tracker->Start());
+  EXPECT_CALL_IN_SCOPE(
+      aborted,
+      Run(nullptr, ElementIdentifier(), InteractionSequence::StepType::kShown,
+          InteractionSequence::AbortedReason::
+              kElementHiddenBeforeSequenceStart),
+      tracker->Start());
 }
 
 TEST(InteractionSequenceTest,
@@ -274,8 +279,10 @@
                        .SetMustBeVisibleAtStart(true)
                        .Build())
           .Build();
-  EXPECT_CALL(aborted, Run(nullptr, element2.identifier(),
-                           InteractionSequence::StepType::kShown))
+  EXPECT_CALL(
+      aborted,
+      Run(nullptr, element2.identifier(), InteractionSequence::StepType::kShown,
+          InteractionSequence::AbortedReason::kElementNotVisibleAtStartOfStep))
       .Times(1);
   tracker->Start();
 }
@@ -383,8 +390,11 @@
                        .SetStartCallback(step.Get())
                        .Build())
           .Build();
-  EXPECT_CALL(aborted, Run(nullptr, element2.identifier(),
-                           InteractionSequence::StepType::kHidden))
+  EXPECT_CALL(
+      aborted,
+      Run(nullptr, element2.identifier(),
+          InteractionSequence::StepType::kHidden,
+          InteractionSequence::AbortedReason::kElementNotVisibleAtStartOfStep))
       .Times(1);
   tracker->Start();
 }
@@ -635,10 +645,12 @@
   EXPECT_CALLS_IN_SCOPE_2(step1_end, Run, step2_start, Run,
                           element2.Activate());
 
-  EXPECT_CALLS_IN_SCOPE_2(step2_end, Run, aborted,
-                          Run(testing::_, element2.identifier(),
-                              InteractionSequence::StepType::kActivated),
-                          element2.Hide());
+  EXPECT_CALLS_IN_SCOPE_2(
+      step2_end, Run, aborted,
+      Run(testing::_, element2.identifier(),
+          InteractionSequence::StepType::kActivated,
+          InteractionSequence::AbortedReason::kElementHiddenDuringStep),
+      element2.Hide());
 }
 
 TEST(InteractionSequenceTest, DontCancelIfViewDoesNotNeedToRemainVisible) {
@@ -1125,10 +1137,11 @@
   // First parameter will be null because during the delete the step end
   // callback will hide the element, which happens before the abort callback is
   // called.
-  EXPECT_CALL_IN_SCOPE(aborted,
-                       Run(nullptr, element2.identifier(),
-                           InteractionSequence::StepType::kShown),
-                       tracker.reset());
+  EXPECT_CALL_IN_SCOPE(
+      aborted,
+      Run(nullptr, element2.identifier(), InteractionSequence::StepType::kShown,
+          InteractionSequence::AbortedReason::kSequenceDestroyed),
+      tracker.reset());
 }
 
 TEST(InteractionSequenceTest, SequenceDestroyedDuringInitialStepStartCallback) {
@@ -1196,7 +1209,8 @@
 
   std::unique_ptr<InteractionSequence> tracker;
   auto callback = [&](TrackedElement*, ElementIdentifier,
-                      InteractionSequence::StepType) { tracker.reset(); };
+                      InteractionSequence::StepType,
+                      InteractionSequence::AbortedReason) { tracker.reset(); };
   tracker = InteractionSequence::Builder()
                 .SetAbortedCallback(base::BindLambdaForTesting(callback))
                 .SetCompletedCallback(completed.Get())
@@ -1302,7 +1316,8 @@
 
   std::unique_ptr<InteractionSequence> tracker;
   auto callback = [&](TrackedElement*, ElementIdentifier,
-                      InteractionSequence::StepType) { tracker.reset(); };
+                      InteractionSequence::StepType,
+                      InteractionSequence::AbortedReason) { tracker.reset(); };
   tracker = InteractionSequence::Builder()
                 .SetAbortedCallback(base::BindLambdaForTesting(callback))
                 .SetCompletedCallback(completed.Get())
diff --git a/ui/color/color_provider_manager.cc b/ui/color/color_provider_manager.cc
index c3dce3e0..43d492c 100644
--- a/ui/color/color_provider_manager.cc
+++ b/ui/color/color_provider_manager.cc
@@ -56,7 +56,8 @@
 #if !defined(OS_ANDROID)
     manager.value().AppendColorProviderInitializer(base::BindRepeating(
         [](ColorProvider* provider, ColorProviderManager::ColorMode color_mode,
-           ColorProviderManager::ContrastMode contrast_mode) {
+           ColorProviderManager::ContrastMode contrast_mode,
+           ColorProviderManager::SystemTheme system_theme) {
           const bool dark_mode =
               color_mode == ColorProviderManager::ColorMode::kDark;
           const bool high_contrast =
@@ -115,9 +116,12 @@
       DVLOG(2) << "ColorProviderManager: Initializing Color Provider"
                << " - ColorMode: " << ColorModeName(std::get<ColorMode>(key))
                << " - ContrastMode: "
-               << ContrastModeName(std::get<ContrastMode>(key));
+               << ContrastModeName(std::get<ContrastMode>(key))
+               << " - SystemTheme: "
+               << SystemThemeName(std::get<SystemTheme>(key));
       initializer_list_->Notify(provider.get(), std::get<ColorMode>(key),
-                                std::get<ContrastMode>(key));
+                                std::get<ContrastMode>(key),
+                                std::get<SystemTheme>(key));
     }
 
     iter = color_providers_.emplace(key, std::move(provider)).first;
diff --git a/ui/color/color_provider_manager.h b/ui/color/color_provider_manager.h
index c04d929..faa00cd 100644
--- a/ui/color/color_provider_manager.h
+++ b/ui/color/color_provider_manager.h
@@ -32,9 +32,13 @@
     kNormal,
     kHigh,
   };
-  using ColorProviderKey = std::tuple<ColorMode, ContrastMode>;
+  enum class SystemTheme {
+    kDefault,
+    kCustom,
+  };
+  using ColorProviderKey = std::tuple<ColorMode, ContrastMode, SystemTheme>;
   using ColorProviderInitializerList = base::RepeatingCallbackList<
-      void(ColorProvider*, ColorMode, ContrastMode)>;
+      void(ColorProvider*, ColorMode, ContrastMode, SystemTheme)>;
 
   ColorProviderManager(const ColorProviderManager&) = delete;
   ColorProviderManager& operator=(const ColorProviderManager&) = delete;
diff --git a/ui/color/color_provider_manager_unittest.cc b/ui/color/color_provider_manager_unittest.cc
index 46a007e..0d1c621 100644
--- a/ui/color/color_provider_manager_unittest.cc
+++ b/ui/color/color_provider_manager_unittest.cc
@@ -29,7 +29,8 @@
 ColorProvider* GetLightNormalColorProvider() {
   return ColorProviderManager::GetForTesting().GetColorProviderFor(
       {ColorProviderManager::ColorMode::kLight,
-       ColorProviderManager::ContrastMode::kNormal});
+       ColorProviderManager::ContrastMode::kNormal,
+       ColorProviderManager::SystemTheme::kDefault});
 }
 
 }  // namespace
@@ -50,7 +51,8 @@
   ColorProviderManager::GetForTesting().AppendColorProviderInitializer(
       base::BindRepeating([](ColorProvider* provider,
                              ColorProviderManager::ColorMode,
-                             ColorProviderManager::ContrastMode) {
+                             ColorProviderManager::ContrastMode,
+                             ColorProviderManager::SystemTheme) {
         provider->AddMixer().AddSet(
             {kColorSetTest0, {{kColorTest0, SK_ColorBLUE}}});
       }));
diff --git a/ui/color/color_provider_utils.cc b/ui/color/color_provider_utils.cc
index 1532ad2..23291f6 100644
--- a/ui/color/color_provider_utils.cc
+++ b/ui/color/color_provider_utils.cc
@@ -35,6 +35,18 @@
   }
 }
 
+base::StringPiece SystemThemeName(
+    ColorProviderManager::SystemTheme system_theme) {
+  switch (system_theme) {
+    case ColorProviderManager::SystemTheme::kDefault:
+      return "kDefault";
+    case ColorProviderManager::SystemTheme::kCustom:
+      return "kCustom";
+    default:
+      return "<invalid>";
+  }
+}
+
 #define E1(enum_name) \
   { enum_name, #enum_name }
 #define E2(enum_name, old_enum_name) \
diff --git a/ui/color/color_provider_utils.h b/ui/color/color_provider_utils.h
index 842c401..a87db08 100644
--- a/ui/color/color_provider_utils.h
+++ b/ui/color/color_provider_utils.h
@@ -27,6 +27,10 @@
 base::StringPiece COMPONENT_EXPORT(COLOR)
     ContrastModeName(ColorProviderManager::ContrastMode contrast_mode);
 
+// Converts SystemTheme.
+base::StringPiece COMPONENT_EXPORT(COLOR)
+    SystemThemeName(ColorProviderManager::SystemTheme system_theme);
+
 // Converts ColorId.
 base::StringPiece COMPONENT_EXPORT(COLOR) ColorIdName(ColorId color_id);
 
@@ -41,4 +45,4 @@
 
 }  // namespace ui
 
-#endif  // UI_COLOR_COLOR_PROVIDER_UTILS_H_
\ No newline at end of file
+#endif  // UI_COLOR_COLOR_PROVIDER_UTILS_H_
diff --git a/ui/events/gesture_event_details.h b/ui/events/gesture_event_details.h
index 5f81d91..d97a696 100644
--- a/ui/events/gesture_event_details.h
+++ b/ui/events/gesture_event_details.h
@@ -5,6 +5,8 @@
 #ifndef UI_EVENTS_GESTURE_EVENT_DETAILS_H_
 #define UI_EVENTS_GESTURE_EVENT_DETAILS_H_
 
+#include <string.h>
+
 #include "base/check_op.h"
 #include "ui/events/event_constants.h"
 #include "ui/events/events_base_export.h"
diff --git a/ui/gfx/render_text.h b/ui/gfx/render_text.h
index 3681169..78e0d9ca 100644
--- a/ui/gfx/render_text.h
+++ b/ui/gfx/render_text.h
@@ -18,6 +18,7 @@
 
 #include "base/i18n/rtl.h"
 #include "base/macros.h"
+#include "build/build_config.h"
 #include "cc/paint/paint_canvas.h"
 #include "cc/paint/paint_flags.h"
 #include "third_party/skia/include/core/SkColor.h"
@@ -616,6 +617,14 @@
   // resulting range. Maintains directionality of |range|.
   Range ExpandRangeToGraphemeBoundary(const Range& range) const;
 
+  // Specify the width/height of a glyph for test. The width/height of glyphs is
+  // very platform-dependent and environment-dependent. Otherwise multiline text
+  // will become really flaky.
+  void set_glyph_width_for_test(float width) { glyph_width_for_test_ = width; }
+  void set_glyph_height_for_test(float height) {
+    glyph_height_for_test_ = height;
+  }
+
  protected:
   RenderText();
 
@@ -874,14 +883,6 @@
   virtual bool GetDecoratedTextForRange(const Range& range,
                                         DecoratedText* decorated_text) = 0;
 
-  // Specify the width/height of a glyph for test. The width/height of glyphs is
-  // very platform-dependent and environment-dependent. Otherwise multiline text
-  // will become really flaky.
-  void set_glyph_width_for_test(float width) { glyph_width_for_test_ = width; }
-  void set_glyph_height_for_test(float height) {
-    glyph_height_for_test_ = height;
-  }
-
   // Logical UTF-16 string data to be drawn.
   std::u16string text_;
 
diff --git a/ui/gl/delegated_ink_point_renderer_gpu.h b/ui/gl/delegated_ink_point_renderer_gpu.h
index 5fe3b24..3139daf 100644
--- a/ui/gl/delegated_ink_point_renderer_gpu.h
+++ b/ui/gl/delegated_ink_point_renderer_gpu.h
@@ -8,9 +8,11 @@
 #include <dcomp.h>
 #include <wrl/client.h>
 
+#include <algorithm>
 #include <iterator>
 #include <memory>
 #include <utility>
+#include <vector>
 
 #include "base/containers/flat_map.h"
 #include "base/trace_event/trace_event.h"
@@ -36,6 +38,12 @@
 // of a trail.
 constexpr int kMaximumNumberOfPoints = 128;
 
+// Maximum number of pointer ids that will be considered for drawing. Number is
+// chosen arbitrarily, as it seems unlikely that the total number of input
+// sources using delegated ink trails would exceed 10, but it can be raised if
+// the need arises.
+constexpr uint64_t kMaximumNumberOfPointerIds = 10;
+
 struct DelegatedInkPointCompare {
   bool operator()(const gfx::DelegatedInkPoint& lhs,
                   const gfx::DelegatedInkPoint& rhs) const {
@@ -98,7 +106,13 @@
     return nullptr;
   }
 
-  DelegatedInkPointTokenMap DelegatedInkPointsForTesting() const {
+  uint64_t DelegatedInkPointPointerIdCountForTesting() const {
+    NOTREACHED();
+    return 0u;
+  }
+
+  DelegatedInkPointTokenMap DelegatedInkPointsForTesting(
+      int32_t pointer_id) const {
     NOTREACHED();
     return DelegatedInkPointTokenMap();
   }
@@ -112,6 +126,16 @@
     NOTREACHED();
     return false;
   }
+
+  bool CheckForPointerIdForTesting(int32_t pointer_id) const {
+    NOTREACHED();
+    return false;
+  }
+
+  uint64_t GetMaximumNumberOfPointerIdsForTesting() const {
+    NOTREACHED();
+    return kMaximumNumberOfPointerIds;
+  }
 };
 
 // This class will call the OS delegated ink trail APIs when they become
@@ -230,38 +254,57 @@
     DCHECK(ink_visual_);
     DCHECK(delegated_ink_trail_);
 
-    // If a trail has already been started and |metadata| is a point that was
-    // drawn as part of that trail (meaning |delegated_ink_points_| contains a
-    // key with the same timestamp as |metadata| and it has a valid token),
-    // simply remove all the points up to the point that |metadata| describes.
-    // However, if the colors of |metadata_| and |metadata| don't match or the
-    // location of the DelegatedInkPoint in |delegated_ink_points_| doesn't
-    // match |metadata|, then we will need to start a new trail to ensure that
-    // the color matches or that we are able to draw a new trail in the correct
-    // location.
-    auto point_matching_metadata_it = delegated_ink_points_.find(
-        gfx::DelegatedInkPoint(metadata->point(), metadata->timestamp()));
-    if (metadata_ && metadata->color() == metadata_->color() &&
-        point_matching_metadata_it != delegated_ink_points_.end()) {
-      gfx::DelegatedInkPoint point_matching_metadata =
-          point_matching_metadata_it->first;
-      absl::optional<unsigned int> token = point_matching_metadata_it->second;
-      if (point_matching_metadata.MatchesDelegatedInkMetadata(metadata.get()) &&
-          token) {
-        bool remove_trail_points_failed = TraceEventOnFailure(
-            delegated_ink_trail_->RemoveTrailPoints(token.value()),
-            "DelegatedInkPointRendererGpu::SetDelegatedInkTrailStartPoint - "
-            "Failed to remove trail points.");
-        if (!remove_trail_points_failed &&
-            UpdateVisualClip(metadata->presentation_area())) {
-          // Remove all points up to and including the point that matches
-          // |metadata|. No need to hold on to the point that matches metadata
-          // because we've already added it to AddTrailPoints previously, and
-          // the next valid |metadata| is guaranteed to be after it.
-          delegated_ink_points_.erase(delegated_ink_points_.begin(),
-                                      std::next(point_matching_metadata_it));
-          metadata_ = std::move(metadata);
-          return;
+    // If certain conditions are met, we can simply erase the trail up to the
+    // point matching |metadata|, instead of starting a new trail. These
+    // conditions include:
+    //  1. A trail has already been started, meaning that |metadata_| and
+    //     |pointer_id_| exist.
+    //  2. The current |metadata_| and |metadata| both have the same color, so
+    //     the color of the trail does not need to change.
+    // (continued below)
+    if (metadata_ && metadata->color() == metadata_->color() && pointer_id_) {
+      DelegatedInkPointTokenMap& token_map =
+          delegated_ink_points_[pointer_id_.value()];
+      auto point_matching_metadata_it = token_map.find(
+          gfx::DelegatedInkPoint(metadata->point(), metadata->timestamp()));
+      // (continued from above)
+      //  3. We have a DelegatedInkPoint with a timestamp equal to or greater
+      //     than |metadata|'s timestamp in the token map associated with
+      //     |pointer_id_|.
+      // (continued below)
+      if (point_matching_metadata_it != token_map.end()) {
+        gfx::DelegatedInkPoint point_matching_metadata =
+            point_matching_metadata_it->first;
+        absl::optional<unsigned int> token = point_matching_metadata_it->second;
+        // (continued from above)
+        //  4. The DelegatedInkPoint retrieved above matches |metadata| - this
+        //     means that the timestamp is an exact match and the point is
+        //     acceptably close.
+        //  5. The token associated with this DelegatedInkPoint is valid,
+        //     meaning that the point has previously been drawn as part of a
+        //     trail.
+        //
+        // If all of these conditions are met, then we can attempt to just
+        // remove points from the trail instead of starting a new trail
+        // altogether.
+        if (point_matching_metadata.MatchesDelegatedInkMetadata(
+                metadata.get()) &&
+            token) {
+          bool remove_trail_points_failed = TraceEventOnFailure(
+              delegated_ink_trail_->RemoveTrailPoints(token.value()),
+              "DelegatedInkPointRendererGpu::SetDelegatedInkTrailStartPoint - "
+              "Failed to remove trail points.");
+          if (!remove_trail_points_failed &&
+              UpdateVisualClip(metadata->presentation_area())) {
+            // Remove all points up to and including the point that matches
+            // |metadata|. No need to hold on to the point that matches metadata
+            // because we've already added it to AddTrailPoints previously, and
+            // the next valid |metadata| is guaranteed to be after it.
+            token_map.erase(token_map.begin(),
+                            std::next(point_matching_metadata_it));
+            metadata_ = std::move(metadata);
+            return;
+          }
         }
       }
     }
@@ -291,23 +334,37 @@
     TRACE_EVENT1("gpu", "DelegatedInkPointRendererGpu::StoreDelegatedInkPoint",
                  "delegated ink point", point.ToString());
 
-    DCHECK(delegated_ink_points_.empty() ||
-           point.timestamp() >
-               delegated_ink_points_.rbegin()->first.timestamp());
+    const int32_t pointer_id = point.pointer_id();
+
+    // TODO(1238497): Understand why we are being sent points from browser
+    // process that break this assertion so frequently and prevent it from
+    // happening.
+    // DCHECK(delegated_ink_points_.find(pointer_id) ==
+    //            delegated_ink_points_.end() ||
+    //        point.timestamp() >
+    //            delegated_ink_points_[pointer_id].rbegin()->
+    //                first.timestamp());
 
     if (metadata_ && point.timestamp() < metadata_->timestamp())
       return;
 
+    DelegatedInkPointTokenMap& token_map = delegated_ink_points_[pointer_id];
     // Always save the point so that it can be drawn later. This allows points
     // that arrive before the first metadata makes it here to be drawn after
     // StartNewTrail is called. If we get to the maximum number of points that
     // we will store, start erasing the oldest ones first. This matches what the
     // OS compositor does internally when it hits the max number of points.
-    if (delegated_ink_points_.size() == kMaximumNumberOfPoints)
-      delegated_ink_points_.erase(delegated_ink_points_.begin());
-    delegated_ink_points_.insert({point, absl::nullopt});
+    if (token_map.size() == kMaximumNumberOfPoints)
+      token_map.erase(token_map.begin());
+    token_map.insert({point, absl::nullopt});
 
-    DrawDelegatedInkPoint(point);
+    EraseExcessPointerIds();
+
+    if (pointer_id_ && pointer_id_.value() == pointer_id) {
+      DrawDelegatedInkPoint(point);
+    } else if (!pointer_id_ && metadata_) {
+      DrawSavedTrailPoints();
+    }
   }
 
   void ResetPrediction() override {
@@ -322,22 +379,39 @@
   }
 
   uint64_t InkTrailTokenCountForTesting() const {
+    DCHECK_EQ(delegated_ink_points_.size(), 1u);
     uint64_t valid_tokens = 0u;
-    for (const auto& it : delegated_ink_points_) {
+    for (const auto& it : delegated_ink_points_.begin()->second) {
       if (it.second)
         valid_tokens++;
     }
     return valid_tokens;
   }
 
-  const DelegatedInkPointTokenMap& DelegatedInkPointsForTesting() const {
-    return delegated_ink_points_;
+  uint64_t DelegatedInkPointPointerIdCountForTesting() const {
+    return delegated_ink_points_.size();
+  }
+
+  bool CheckForPointerIdForTesting(int32_t pointer_id) const {
+    return delegated_ink_points_.find(pointer_id) !=
+           delegated_ink_points_.end();
+  }
+
+  const DelegatedInkPointTokenMap& DelegatedInkPointsForTesting(
+      int32_t pointer_id) {
+    DCHECK(delegated_ink_points_.find(pointer_id) !=
+           delegated_ink_points_.end());
+    return delegated_ink_points_[pointer_id];
   }
 
   bool WaitForNewTrailToDrawForTesting() const {
     return wait_for_new_trail_to_draw_;
   }
 
+  uint64_t GetMaximumNumberOfPointerIdsForTesting() const {
+    return kMaximumNumberOfPointerIds;
+  }
+
  private:
   // Note that this returns true if the HRESULT is anything other than S_OK,
   // meaning that it returns true when an event is traced (because of a
@@ -370,8 +444,90 @@
            SUCCEEDED(dcomp_device_->Commit());
   }
 
+  void EraseExcessPointerIds() {
+    auto token_map_it = delegated_ink_points_.begin();
+    while (token_map_it != delegated_ink_points_.end()) {
+      const DelegatedInkPointTokenMap& token_map = token_map_it->second;
+      if (token_map.empty())
+        token_map_it = delegated_ink_points_.erase(token_map_it);
+      else
+        token_map_it++;
+    }
+
+    // In order to get to the maximum pointer id limit, sometimes one token map
+    // will need to be removed even if it has a valid DelegatedInkPoint. In this
+    // case, we'll remove the token map that has gone the longest without
+    // receiving a new point - similar to a least recently used eviction policy.
+    // This would also mean that the token map that would result in the smallest
+    // latency improvement if it were drawn is being removed. The only exception
+    // to this is if the above heuristic would result in the token map that has
+    // a pointer id matching |pointer_id_| being removed. Since |pointer_id_|
+    // matches the trail that is currently being drawn to the screen, we prefer
+    // to save it so we can try to continue that trail. In this case, just
+    // remove the token map with the oldest new point that is not currently
+    // being drawn.
+    if (delegated_ink_points_.size() > kMaximumNumberOfPointerIds) {
+      std::vector<std::pair<base::TimeTicks, int32_t>>
+          earliest_time_and_pointer_id;
+      for (const auto& it : delegated_ink_points_) {
+        earliest_time_and_pointer_id.emplace_back(
+            it.second.rbegin()->first.timestamp(), it.first);
+      }
+
+      std::sort(earliest_time_and_pointer_id.begin(),
+                earliest_time_and_pointer_id.end());
+
+      uint64_t pointer_id_to_remove = 0;
+      while (pointer_id_to_remove < earliest_time_and_pointer_id.size() &&
+             pointer_id_ &&
+             earliest_time_and_pointer_id[pointer_id_to_remove].second ==
+                 pointer_id_.value()) {
+        pointer_id_to_remove++;
+      }
+
+      CHECK_LT(pointer_id_to_remove, earliest_time_and_pointer_id.size());
+      delegated_ink_points_.erase(
+          earliest_time_and_pointer_id[pointer_id_to_remove].second);
+    }
+
+    DCHECK_LE(delegated_ink_points_.size(), kMaximumNumberOfPointerIds);
+  }
+
+  absl::optional<int32_t> GetPointerIdForMetadata() {
+    if (pointer_id_ && delegated_ink_points_.find(pointer_id_.value()) !=
+                           delegated_ink_points_.end()) {
+      // Since we remove all DelegatedInkPoints with timestamp before
+      // |metadata_|'s before calling GetPointerIdForMetadata(), we know we can
+      // just grab the first DelegatedInkPoint matching |pointer_id_|.
+      const gfx::DelegatedInkPoint& point_matching_metadata =
+          delegated_ink_points_[pointer_id_.value()].begin()->first;
+      if (point_matching_metadata.MatchesDelegatedInkMetadata(
+              metadata_.get())) {
+        return pointer_id_;
+      }
+    }
+
+    pointer_id_ = absl::nullopt;
+
+    for (auto token_map_it = delegated_ink_points_.begin();
+         token_map_it != delegated_ink_points_.end() && !pointer_id_;
+         ++token_map_it) {
+      int32_t potential_pointer_id = token_map_it->first;
+      const DelegatedInkPointTokenMap& token_map = token_map_it->second;
+      DCHECK(!token_map.empty());
+      if (token_map.begin()->first.MatchesDelegatedInkMetadata(
+              metadata_.get())) {
+        DCHECK(!pointer_id_);
+        pointer_id_ = potential_pointer_id;
+      }
+    }
+
+    return pointer_id_;
+  }
+
   void DrawSavedTrailPoints() {
     DCHECK(metadata_);
+    TRACE_EVENT0("gpu", "DrawSavedTrailPoints");
 
     // Remove all points that have a timestamp earlier than |metadata_|'s, since
     // we know that we won't need to draw them. This is subtly different than
@@ -385,23 +541,36 @@
     // before deciding to draw an ink trail. The point could then be erased
     // after it is successfully checked against |metadata_| and drawn, it just
     // isn't for simplicity's sake.
-    delegated_ink_points_.erase(
-        delegated_ink_points_.begin(),
-        delegated_ink_points_.lower_bound(gfx::DelegatedInkPoint(
-            metadata_->point(), metadata_->timestamp())));
+    for (auto& it : delegated_ink_points_) {
+      DelegatedInkPointTokenMap& token_map = it.second;
+      token_map.erase(token_map.begin(),
+                      token_map.lower_bound(gfx::DelegatedInkPoint(
+                          metadata_->point(), metadata_->timestamp())));
+    }
+
+    EraseExcessPointerIds();
+
+    absl::optional<unsigned int> pointer_id = GetPointerIdForMetadata();
 
     // Now, the very first point must match |metadata_|, and as long as it does
     // we can continue to draw everything else. If at any point something can't
     // or fails to draw though, don't attempt to draw anything after it so that
     // the trail can match the user's actual stroke.
-    if (!delegated_ink_points_.empty() &&
-        delegated_ink_points_.begin()->first.MatchesDelegatedInkMetadata(
-            metadata_.get())) {
-      for (auto it = delegated_ink_points_.begin();
-           it != delegated_ink_points_.end(); ++it) {
-        if (!DrawDelegatedInkPoint(it->first))
-          break;
+    if (pointer_id && delegated_ink_points_.find(pointer_id.value()) !=
+                          delegated_ink_points_.end()) {
+      DelegatedInkPointTokenMap& token_map =
+          delegated_ink_points_[pointer_id.value()];
+      if (!token_map.empty() &&
+          token_map.begin()->first.MatchesDelegatedInkMetadata(
+              metadata_.get())) {
+        for (const auto& it : token_map) {
+          if (!DrawDelegatedInkPoint(it.first))
+            break;
+        }
       }
+    } else {
+      TRACE_EVENT_INSTANT0("gpu", "DrawSavedTrailPoints failed - no pointer id",
+                           TRACE_EVENT_SCOPE_THREAD);
     }
   }
 
@@ -440,7 +609,11 @@
       return false;
     }
 
-    delegated_ink_points_[point] = token;
+    TRACE_EVENT_INSTANT1("gpu",
+                         "DelegatedInkPointRendererGpu::DrawDelegatedInkPoint "
+                         "- Point added to trail",
+                         TRACE_EVENT_SCOPE_THREAD, "point", point.ToString());
+    delegated_ink_points_[point.pointer_id()][point] = token;
     return true;
   }
 
@@ -476,7 +649,10 @@
   // Then as each new |metadata| arrives in SetDelegatedInkTrailStartPoint(),
   // we remove any old elements with earlier timestamps, up to and including the
   // element that matches the DelegatedInkMetadata.
-  DelegatedInkPointTokenMap delegated_ink_points_;
+  base::flat_map<int32_t, DelegatedInkPointTokenMap> delegated_ink_points_;
+
+  // Cached pointer id of the most recently drawn trail.
+  absl::optional<int32_t> pointer_id_;
 
   // Flag to know if new DelegatedInkPoints that arrive should be drawn
   // immediately or if they should wait for a new trail to be started. Set to
diff --git a/ui/gl/delegated_ink_point_renderer_gpu_unittest.cc b/ui/gl/delegated_ink_point_renderer_gpu_unittest.cc
index cfa9f0a..666f027de 100644
--- a/ui/gl/delegated_ink_point_renderer_gpu_unittest.cc
+++ b/ui/gl/delegated_ink_point_renderer_gpu_unittest.cc
@@ -7,6 +7,7 @@
 #include <memory>
 
 #include "base/run_loop.h"
+#include "base/time/time.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/base/win/hidden_window.h"
 #include "ui/gfx/geometry/vector2d_f.h"
@@ -19,6 +20,9 @@
 namespace gl {
 namespace {
 
+// Standard number of microseconds that is put between each DelegatedInkPoint.
+const int kMicrosecondsBetweenEachPoint = 10;
+
 class DelegatedInkPointRendererGpuTest : public testing::Test {
  public:
   DelegatedInkPointRendererGpuTest() : parent_window_(ui::GetHiddenWindow()) {}
@@ -32,29 +36,38 @@
   }
 
   void SendDelegatedInkPoint(const gfx::DelegatedInkPoint& point) {
-    stored_points_.push_back(point);
+    stored_points_[point.pointer_id()].push_back(point);
     ink_renderer()->StoreDelegatedInkPoint(point);
   }
 
-  void SendDelegatedInkPointBasedOnPrevious() {
-    DCHECK(!stored_points_.empty());
+  void SendDelegatedInkPointBasedOnPrevious(uint32_t pointer_id) {
+    DCHECK(stored_points_.find(pointer_id) != stored_points_.end());
+    DCHECK(!stored_points_[pointer_id].empty());
 
-    auto last_point = stored_points_.back();
+    auto last_point = stored_points_[pointer_id].back();
     SendDelegatedInkPoint(gfx::DelegatedInkPoint(
         last_point.point() + gfx::Vector2dF(5, 5),
-        last_point.timestamp() + base::TimeDelta::FromMicroseconds(10),
+        last_point.timestamp() +
+            base::TimeDelta::FromMicroseconds(kMicrosecondsBetweenEachPoint),
         last_point.pointer_id()));
   }
 
+  void SendDelegatedInkPointBasedOnPrevious() {
+    DCHECK_EQ(stored_points_.size(), 1u);
+    SendDelegatedInkPointBasedOnPrevious(stored_points_.begin()->first);
+  }
+
   void SendMetadata(const gfx::DelegatedInkMetadata& metadata) {
     surface()->SetDelegatedInkTrailStartPoint(
         std::make_unique<gfx::DelegatedInkMetadata>(metadata));
   }
 
-  gfx::DelegatedInkMetadata SendMetadataBasedOnStoredPoint(uint64_t point) {
-    EXPECT_GE(stored_points_.size(), point);
+  gfx::DelegatedInkMetadata SendMetadataBasedOnStoredPoint(int32_t pointer_id,
+                                                           uint64_t point) {
+    DCHECK(stored_points_.find(pointer_id) != stored_points_.end());
+    DCHECK_GE(stored_points_[pointer_id].size(), point);
 
-    auto ink_point = stored_points_[point];
+    const gfx::DelegatedInkPoint& ink_point = stored_points_[pointer_id][point];
     gfx::DelegatedInkMetadata metadata(
         ink_point.point(), /*diameter=*/3, SK_ColorBLACK, ink_point.timestamp(),
         gfx::RectF(0, 0, 100, 100), /*hovering=*/false);
@@ -62,6 +75,11 @@
     return metadata;
   }
 
+  gfx::DelegatedInkMetadata SendMetadataBasedOnStoredPoint(uint64_t point) {
+    DCHECK_EQ(stored_points_.size(), 1u);
+    return SendMetadataBasedOnStoredPoint(stored_points_.begin()->first, point);
+  }
+
   void StoredMetadataMatchesSentMetadata(
       const gfx::DelegatedInkMetadata& sent_metadata) {
     gfx::DelegatedInkMetadata* renderer_metadata =
@@ -147,7 +165,7 @@
 
   // Used as a reference when making DelegatedInkMetadatas based on previously
   // sent points.
-  std::vector<gfx::DelegatedInkPoint> stored_points_;
+  std::map<int32_t, std::vector<gfx::DelegatedInkPoint>> stored_points_;
 };
 
 // Test to confirm that points and tokens are stored and removed correctly based
@@ -157,13 +175,14 @@
     return;
 
   // Send some points and make sure they are all stored even with no metadata.
-  SendDelegatedInkPoint(
-      gfx::DelegatedInkPoint(gfx::PointF(10, 10), base::TimeTicks::Now(), 1));
+  const int32_t kPointerId = 1;
+  SendDelegatedInkPoint(gfx::DelegatedInkPoint(
+      gfx::PointF(10, 10), base::TimeTicks::Now(), kPointerId));
   const uint64_t kPointsToStore = 5u;
   for (uint64_t i = 1; i < kPointsToStore; ++i)
     SendDelegatedInkPointBasedOnPrevious();
 
-  EXPECT_EQ(ink_renderer()->DelegatedInkPointsForTesting().size(),
+  EXPECT_EQ(ink_renderer()->DelegatedInkPointsForTesting(kPointerId).size(),
             kPointsToStore);
   EXPECT_EQ(ink_renderer()->InkTrailTokenCountForTesting(), 0u);
   EXPECT_FALSE(ink_renderer()->MetadataForTesting());
@@ -175,7 +194,7 @@
   // until a metadata arrives with a later timestamp
   gfx::DelegatedInkMetadata metadata = SendMetadataBasedOnStoredPoint(0);
 
-  EXPECT_EQ(ink_renderer()->DelegatedInkPointsForTesting().size(),
+  EXPECT_EQ(ink_renderer()->DelegatedInkPointsForTesting(kPointerId).size(),
             kPointsToStore);
   EXPECT_EQ(ink_renderer()->InkTrailTokenCountForTesting(), kPointsToStore);
   EXPECT_FALSE(ink_renderer()->WaitForNewTrailToDrawForTesting());
@@ -187,7 +206,7 @@
   // should therefore have tokens associated with them.
   const uint64_t kPointToSend = 3u;
   metadata = SendMetadataBasedOnStoredPoint(kPointToSend);
-  EXPECT_EQ(ink_renderer()->DelegatedInkPointsForTesting().size(),
+  EXPECT_EQ(ink_renderer()->DelegatedInkPointsForTesting(kPointerId).size(),
             kPointsToStore - kPointToSend - 1);
   EXPECT_EQ(ink_renderer()->InkTrailTokenCountForTesting(),
             kPointsToStore - kPointToSend - 1);
@@ -197,14 +216,13 @@
   // results in all the tokens and stored points being erased because a new
   // trail is started.
   gfx::DelegatedInkPoint last_point =
-      ink_renderer()->DelegatedInkPointsForTesting().rbegin()->first;
+      ink_renderer()->DelegatedInkPointsForTesting(kPointerId).rbegin()->first;
   metadata = gfx::DelegatedInkMetadata(
       last_point.point() + gfx::Vector2dF(2, 2), /*diameter=*/3, SK_ColorBLACK,
       last_point.timestamp() + base::TimeDelta::FromMicroseconds(20),
       gfx::RectF(0, 0, 100, 100), /*hovering=*/false);
   SendMetadata(metadata);
-  EXPECT_EQ(ink_renderer()->DelegatedInkPointsForTesting().size(), 0u);
-  EXPECT_EQ(ink_renderer()->InkTrailTokenCountForTesting(), 0u);
+  EXPECT_EQ(ink_renderer()->DelegatedInkPointPointerIdCountForTesting(), 0u);
   StoredMetadataMatchesSentMetadata(metadata);
 }
 
@@ -221,15 +239,17 @@
 
   // Send some points that should all be drawn to ensure that they are all drawn
   // as they arrive.
+  const int32_t kPointerId = 1;
   const uint64_t kPointsToSend = 5u;
   for (uint64_t i = 1u; i <= kPointsToSend; ++i) {
     if (i == 1) {
       SendDelegatedInkPoint(gfx::DelegatedInkPoint(
-          metadata.point(), metadata.timestamp(), /*pointer_id=*/1));
+          metadata.point(), metadata.timestamp(), kPointerId));
     } else {
       SendDelegatedInkPointBasedOnPrevious();
     }
-    EXPECT_EQ(ink_renderer()->DelegatedInkPointsForTesting().size(), i);
+    EXPECT_EQ(ink_renderer()->DelegatedInkPointsForTesting(kPointerId).size(),
+              i);
     EXPECT_EQ(ink_renderer()->InkTrailTokenCountForTesting(), i);
   }
 
@@ -238,7 +258,7 @@
   // arrives with a presentation area that would contain this point, it can
   // still be drawn.
   gfx::DelegatedInkPoint last_point =
-      ink_renderer()->DelegatedInkPointsForTesting().rbegin()->first;
+      ink_renderer()->DelegatedInkPointsForTesting(kPointerId).rbegin()->first;
   gfx::DelegatedInkPoint outside_point(
       gfx::PointF(5, 5),
       last_point.timestamp() + base::TimeDelta::FromMicroseconds(10),
@@ -247,7 +267,7 @@
   SendDelegatedInkPoint(outside_point);
 
   const uint64_t kTotalPointsSent = kPointsToSend + 1u;
-  EXPECT_EQ(ink_renderer()->DelegatedInkPointsForTesting().size(),
+  EXPECT_EQ(ink_renderer()->DelegatedInkPointsForTesting(kPointerId).size(),
             kTotalPointsSent);
   EXPECT_EQ(ink_renderer()->InkTrailTokenCountForTesting(), kPointsToSend);
 
@@ -256,10 +276,165 @@
   // the points with later timestamps will be stored.
   const uint64_t kMetadataToSend = 3u;
   SendMetadataBasedOnStoredPoint(kMetadataToSend);
-  EXPECT_EQ(ink_renderer()->DelegatedInkPointsForTesting().size(),
+  EXPECT_EQ(ink_renderer()->DelegatedInkPointsForTesting(kPointerId).size(),
             kTotalPointsSent - kMetadataToSend - 1);
   EXPECT_EQ(ink_renderer()->InkTrailTokenCountForTesting(), 1u);
 }
 
+// Confirm that points with different pointer ids are handled correctly.
+TEST_F(DelegatedInkPointRendererGpuTest, MultiplePointerIds) {
+  if (!surface() || !surface()->SupportsDelegatedInk())
+    return;
+
+  const int32_t kPointerId1 = 1;
+  const int32_t kPointerId2 = 2;
+
+  // First send a few points with different pointer ids to make sure they are
+  // stored separately and correctly. Separate the timestamps so that we can
+  // test how sending metadatas impacts them each separately.
+  const base::TimeTicks timestamp = base::TimeTicks::Now();
+  const int kPointerId2PointsAheadOfPointerId1 = 3;
+  SendDelegatedInkPoint(
+      gfx::DelegatedInkPoint(gfx::PointF(16, 22), timestamp, kPointerId1));
+  SendDelegatedInkPoint(gfx::DelegatedInkPoint(
+      gfx::PointF(40, 13.3f),
+      timestamp +
+          base::TimeDelta::FromMicroseconds(kPointerId2PointsAheadOfPointerId1 *
+                                            kMicrosecondsBetweenEachPoint),
+      kPointerId2));
+
+  uint64_t pointer_id_1_count = 1u;
+  uint64_t pointer_id_2_count = 1u;
+  const uint64_t kTotalPoints = 9u;
+  for (uint64_t i = 0u; i < kTotalPoints; ++i) {
+    if (i % 2 == 0) {
+      SendDelegatedInkPointBasedOnPrevious(kPointerId1);
+      pointer_id_1_count++;
+    } else {
+      SendDelegatedInkPointBasedOnPrevious(kPointerId2);
+      pointer_id_2_count++;
+    }
+  }
+
+  EXPECT_EQ(ink_renderer()->DelegatedInkPointsForTesting(kPointerId1).size(),
+            pointer_id_1_count);
+  EXPECT_EQ(ink_renderer()->DelegatedInkPointsForTesting(kPointerId2).size(),
+            pointer_id_2_count);
+
+  // Send a metadata matching one of the points with |kPointerId1|. This should
+  // erase all points with a timestamp before that from the trail with
+  // |kPointerId1| but none from the other pointer id.
+  const uint64_t kMetadataToSendForPointerId1 = 1u;
+  SendMetadataBasedOnStoredPoint(kPointerId1, kMetadataToSendForPointerId1);
+  EXPECT_EQ(ink_renderer()->DelegatedInkPointsForTesting(kPointerId1).size(),
+            pointer_id_1_count - kMetadataToSendForPointerId1);
+  EXPECT_EQ(ink_renderer()->DelegatedInkPointsForTesting(kPointerId2).size(),
+            pointer_id_2_count);
+
+  // Now send a metadata matching one of the points with |kPointerId2|. Since
+  // |kPointerId2| points have timestamps greater than |kPointerId1|, it should
+  // cause some points with |kPointerId1| to be erased too.
+  const uint64_t kMetadataToSendForPointerId2 = 2u;
+  SendMetadataBasedOnStoredPoint(kPointerId2, kMetadataToSendForPointerId2);
+  EXPECT_EQ(ink_renderer()->DelegatedInkPointsForTesting(kPointerId1).size(),
+            pointer_id_1_count - kPointerId2PointsAheadOfPointerId1 -
+                kMetadataToSendForPointerId2);
+  EXPECT_EQ(ink_renderer()->DelegatedInkPointsForTesting(kPointerId2).size(),
+            pointer_id_2_count - kMetadataToSendForPointerId2);
+
+  // Add another pointer id into the mix and make sure the counts are all what
+  // we expect.
+  const int32_t kPointerId3 = 3;
+  const int kPointerId3PointsAheadOfPointerId1 = 5;
+  SendDelegatedInkPoint(gfx::DelegatedInkPoint(
+      gfx::PointF(23, 64),
+      timestamp +
+          base::TimeDelta::FromMicroseconds(kPointerId3PointsAheadOfPointerId1 *
+                                            kMicrosecondsBetweenEachPoint),
+      kPointerId3));
+
+  const uint64_t kPointsWithPointerId3 = 4u;
+  for (uint64_t i = 1u; i < kPointsWithPointerId3; ++i)
+    SendDelegatedInkPointBasedOnPrevious(kPointerId3);
+
+  EXPECT_EQ(ink_renderer()->DelegatedInkPointsForTesting(kPointerId1).size(),
+            pointer_id_1_count - kPointerId2PointsAheadOfPointerId1 -
+                kMetadataToSendForPointerId2);
+  EXPECT_EQ(ink_renderer()->DelegatedInkPointsForTesting(kPointerId2).size(),
+            pointer_id_2_count - kMetadataToSendForPointerId2);
+  EXPECT_EQ(ink_renderer()->DelegatedInkPointsForTesting(kPointerId3).size(),
+            kPointsWithPointerId3);
+
+  // Then send a point with |kPointerId3| that is beyond all the points with
+  // |kPointerId1| to make sure that |kPointerId1| is removed from the flat map
+  // completely.
+  const uint64_t kMetadataToSendForPointerId3 = 1u;
+  SendMetadataBasedOnStoredPoint(kPointerId3, kMetadataToSendForPointerId3);
+  EXPECT_EQ(ink_renderer()->DelegatedInkPointPointerIdCountForTesting(), 2u);
+  EXPECT_EQ(ink_renderer()->DelegatedInkPointsForTesting(kPointerId2).size(),
+            pointer_id_2_count - kMetadataToSendForPointerId2 -
+                kMetadataToSendForPointerId3);
+  EXPECT_EQ(ink_renderer()->DelegatedInkPointsForTesting(kPointerId3).size(),
+            kPointsWithPointerId3 - kMetadataToSendForPointerId3);
+}
+
+// Make sure that the DelegatedInkPoint with the earliest timestamp is removed
+// if we have reached the maximum number of pointer ids.
+TEST_F(DelegatedInkPointRendererGpuTest, MaximumPointerIds) {
+  if (!surface() || !surface()->SupportsDelegatedInk())
+    return;
+
+  // First add DelegatedInkPoints with unique pointer ids up to the limit and
+  // make sure they are all correctly added separately.
+  const base::TimeTicks kEarliestTimestamp =
+      base::TimeTicks::Now() -
+      base::TimeDelta::FromMicroseconds(kMicrosecondsBetweenEachPoint);
+  const int32_t kEarliestTimestampPointerId = 4;
+
+  base::TimeTicks timestamp = base::TimeTicks::Now();
+  gfx::PointF point(34.4f, 20);
+  const uint64_t kMaxNumberOfPointerIds =
+      ink_renderer()->GetMaximumNumberOfPointerIdsForTesting();
+  for (uint64_t pointer_id = 0; pointer_id < kMaxNumberOfPointerIds;
+       ++pointer_id) {
+    SendDelegatedInkPoint(gfx::DelegatedInkPoint(
+        point,
+        pointer_id == kEarliestTimestampPointerId ? kEarliestTimestamp
+                                                  : timestamp,
+        pointer_id));
+    point += gfx::Vector2dF(5, 5);
+    timestamp +=
+        base::TimeDelta::FromMicroseconds(kMicrosecondsBetweenEachPoint);
+  }
+
+  EXPECT_EQ(ink_renderer()->DelegatedInkPointPointerIdCountForTesting(),
+            kMaxNumberOfPointerIds);
+  EXPECT_TRUE(
+      ink_renderer()->CheckForPointerIdForTesting(kEarliestTimestampPointerId));
+
+  // Now send one more with a later timestamp to ensure that the one with the
+  // earliest timestamp was removed.
+  SendDelegatedInkPoint(
+      gfx::DelegatedInkPoint(point, timestamp, kMaxNumberOfPointerIds));
+
+  EXPECT_EQ(ink_renderer()->DelegatedInkPointPointerIdCountForTesting(),
+            kMaxNumberOfPointerIds);
+  EXPECT_FALSE(
+      ink_renderer()->CheckForPointerIdForTesting(kEarliestTimestampPointerId));
+
+  // Finally, add a point with a earlier timestamp than everything else and make
+  // sure that it is not added to the map of pointer ids.
+  point += gfx::Vector2dF(5, 5);
+  timestamp = kEarliestTimestamp + base::TimeDelta::FromMicroseconds(5);
+  const int32_t kEarlyTimestampPointerId = kMaxNumberOfPointerIds + 1;
+  SendDelegatedInkPoint(
+      gfx::DelegatedInkPoint(point, timestamp, kEarlyTimestampPointerId));
+
+  EXPECT_EQ(ink_renderer()->DelegatedInkPointPointerIdCountForTesting(),
+            kMaxNumberOfPointerIds);
+  EXPECT_FALSE(
+      ink_renderer()->CheckForPointerIdForTesting(kEarlyTimestampPointerId));
+}
+
 }  // namespace
 }  // namespace gl
diff --git a/ui/gtk/gtk_color_mixers.cc b/ui/gtk/gtk_color_mixers.cc
index e04e2cb..7730443a 100644
--- a/ui/gtk/gtk_color_mixers.cc
+++ b/ui/gtk/gtk_color_mixers.cc
@@ -21,7 +21,11 @@
 void AddGtkNativeCoreColorMixer(
     ui::ColorProvider* provider,
     ui::ColorProviderManager::ColorMode color_mode,
-    ui::ColorProviderManager::ContrastMode contrast_mode) {
+    ui::ColorProviderManager::ContrastMode contrast_mode,
+    ui::ColorProviderManager::SystemTheme system_theme) {
+  if (system_theme == ui::ColorProviderManager::SystemTheme::kDefault)
+    return;
+
   ui::ColorMixer& mixer = provider->AddMixer();
 
   ui::ColorSet::ColorMap color_map;
diff --git a/ui/gtk/gtk_color_mixers.h b/ui/gtk/gtk_color_mixers.h
index 3b4edb5..ad51b81 100644
--- a/ui/gtk/gtk_color_mixers.h
+++ b/ui/gtk/gtk_color_mixers.h
@@ -16,7 +16,8 @@
 void AddGtkNativeCoreColorMixer(
     ui::ColorProvider* provider,
     ui::ColorProviderManager::ColorMode color_mode,
-    ui::ColorProviderManager::ContrastMode contrast_mode);
+    ui::ColorProviderManager::ContrastMode contrast_mode,
+    ui::ColorProviderManager::SystemTheme system_theme);
 
 }  // namespace gtk
 
diff --git a/ui/gtk/native_theme_gtk.cc b/ui/gtk/native_theme_gtk.cc
index 3d896fd..1f735c18 100644
--- a/ui/gtk/native_theme_gtk.cc
+++ b/ui/gtk/native_theme_gtk.cc
@@ -75,7 +75,9 @@
   return s_native_theme.get();
 }
 
-NativeThemeGtk::NativeThemeGtk() {
+NativeThemeGtk::NativeThemeGtk()
+    : NativeThemeBase(/*should_only_use_dark_colors=*/false,
+                      /*is_custom_system_theme=*/true) {
   // g_type_from_name() is only used in GTK3.
   if (!GtkCheckVersion(4)) {
     // These types are needed by g_type_from_name(), but may not be registered
diff --git a/ui/latency/latency_info.cc b/ui/latency/latency_info.cc
index f12f1cd7..106fdca2 100644
--- a/ui/latency/latency_info.cc
+++ b/ui/latency/latency_info.cc
@@ -56,43 +56,6 @@
   return type == ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT;
 }
 
-// This class is for converting latency info to trace buffer friendly format.
-class LatencyInfoTracedValue
-    : public base::trace_event::ConvertableToTraceFormat {
- public:
-  static std::unique_ptr<ConvertableToTraceFormat> FromValue(
-      std::unique_ptr<base::Value> value);
-
-  void AppendAsTraceFormat(std::string* out) const override;
-
- private:
-  explicit LatencyInfoTracedValue(base::Value* value);
-  ~LatencyInfoTracedValue() override;
-
-  std::unique_ptr<base::Value> value_;
-
-  DISALLOW_COPY_AND_ASSIGN(LatencyInfoTracedValue);
-};
-
-std::unique_ptr<base::trace_event::ConvertableToTraceFormat>
-LatencyInfoTracedValue::FromValue(std::unique_ptr<base::Value> value) {
-  return std::unique_ptr<base::trace_event::ConvertableToTraceFormat>(
-      new LatencyInfoTracedValue(value.release()));
-}
-
-LatencyInfoTracedValue::~LatencyInfoTracedValue() {
-}
-
-void LatencyInfoTracedValue::AppendAsTraceFormat(std::string* out) const {
-  std::string tmp;
-  base::JSONWriter::Write(*value_, &tmp);
-  *out += tmp;
-}
-
-LatencyInfoTracedValue::LatencyInfoTracedValue(base::Value* value)
-    : value_(value) {
-}
-
 constexpr const char kTraceCategoriesForAsyncEvents[] =
     "benchmark,latencyInfo,rail";
 
diff --git a/ui/native_theme/native_theme.cc b/ui/native_theme/native_theme.cc
index 7adb292..6471581 100644
--- a/ui/native_theme/native_theme.cc
+++ b/ui/native_theme/native_theme.cc
@@ -115,8 +115,10 @@
   return border_radius;
 }
 
-NativeTheme::NativeTheme(bool should_use_dark_colors)
+NativeTheme::NativeTheme(bool should_use_dark_colors,
+                         bool is_custom_system_theme)
     : should_use_dark_colors_(should_use_dark_colors || IsForcedDarkMode()),
+      is_custom_system_theme_(is_custom_system_theme),
       forced_colors_(IsForcedHighContrast()),
       preferred_color_scheme_(CalculatePreferredColorScheme()),
       preferred_contrast_(CalculatePreferredContrast()) {}
@@ -135,7 +137,10 @@
                : ColorProviderManager::ColorMode::kLight,
            (color_scheme == NativeTheme::ColorScheme::kPlatformHighContrast)
                ? ColorProviderManager::ContrastMode::kHigh
-               : ColorProviderManager::ContrastMode::kNormal});
+               : ColorProviderManager::ContrastMode::kNormal,
+           is_custom_system_theme_
+               ? ColorProviderManager::SystemTheme::kCustom
+               : ColorProviderManager::SystemTheme::kDefault});
       ReportHistogramBooleanUsesColorProvider(true);
       return color_provider->GetColor(provider_color_id.value());
     }
diff --git a/ui/native_theme/native_theme.h b/ui/native_theme/native_theme.h
index b8ad810..44cd465 100644
--- a/ui/native_theme/native_theme.h
+++ b/ui/native_theme/native_theme.h
@@ -482,6 +482,7 @@
     preferred_contrast_ = preferred_contrast;
   }
   void set_system_colors(const std::map<SystemThemeColor, SkColor>& colors);
+  bool is_custom_system_theme() const { return is_custom_system_theme_; }
 
   // Updates the state of dark mode, forced colors mode, and the map of system
   // colors. Returns true if NativeTheme was updated as a result, or false if
@@ -503,7 +504,8 @@
                                          float zoom_level) const;
 
  protected:
-  explicit NativeTheme(bool should_only_use_dark_colors);
+  explicit NativeTheme(bool should_only_use_dark_colors,
+                       bool is_custom_system_theme = false);
   virtual ~NativeTheme();
 
   // Gets the color from the color provider if using a color provider is enable.
@@ -574,6 +576,7 @@
   base::ObserverList<NativeThemeObserver>::Unchecked native_theme_observers_;
 
   bool should_use_dark_colors_ = false;
+  const bool is_custom_system_theme_;
   bool forced_colors_ = false;
   PreferredColorScheme preferred_color_scheme_ = PreferredColorScheme::kLight;
   PreferredContrast preferred_contrast_ = PreferredContrast::kNoPreference;
diff --git a/ui/native_theme/native_theme_base.cc b/ui/native_theme/native_theme_base.cc
index 06497dd..95df197 100644
--- a/ui/native_theme/native_theme_base.cc
+++ b/ui/native_theme/native_theme_base.cc
@@ -371,8 +371,9 @@
 
 NativeThemeBase::NativeThemeBase() : NativeThemeBase(false) {}
 
-NativeThemeBase::NativeThemeBase(bool should_only_use_dark_colors)
-    : NativeTheme(should_only_use_dark_colors) {}
+NativeThemeBase::NativeThemeBase(bool should_only_use_dark_colors,
+                                 bool is_custom_system_theme)
+    : NativeTheme(should_only_use_dark_colors, is_custom_system_theme) {}
 
 NativeThemeBase::~NativeThemeBase() = default;
 
diff --git a/ui/native_theme/native_theme_base.h b/ui/native_theme/native_theme_base.h
index 28ad2c0..1a36bbd 100644
--- a/ui/native_theme/native_theme_base.h
+++ b/ui/native_theme/native_theme_base.h
@@ -88,7 +88,8 @@
 
   using NativeTheme::NativeTheme;
   NativeThemeBase();
-  explicit NativeThemeBase(bool should_only_use_dark_colors);
+  explicit NativeThemeBase(bool should_only_use_dark_colors,
+                           bool is_custom_system_theme = false);
   ~NativeThemeBase() override;
 
   // Draw the arrow. Used by scrollbar and inner spin button.
diff --git a/ui/ozone/platform/flatland/client_native_pixmap_factory_flatland.cc b/ui/ozone/platform/flatland/client_native_pixmap_factory_flatland.cc
index ebcea36..5d0b4d9 100644
--- a/ui/ozone/platform/flatland/client_native_pixmap_factory_flatland.cc
+++ b/ui/ozone/platform/flatland/client_native_pixmap_factory_flatland.cc
@@ -92,7 +92,7 @@
 
   int GetStride(size_t plane) const override {
     DCHECK_LT(plane, handle_.planes.size());
-    return handle_.planes[plane].stride;
+    return base::checked_cast<int>(handle_.planes[plane].stride);
   }
 
   gfx::NativePixmapHandle CloneHandleForIPC() const override {
@@ -151,6 +151,11 @@
         return nullptr;
       }
 
+      // The stride must be a valid integer in order to be consistent with the
+      // gfx::ClientNativePixmap::GetStride() API.
+      if (!base::IsValueInRangeForNumericType<int>(handle.planes[i].stride))
+        return nullptr;
+
       base::CheckedNumeric<size_t> min_size =
           base::CheckedNumeric<size_t>(handle.planes[i].stride) * plane_height;
       if (!min_size.IsValid() || handle.planes[i].size < min_size.ValueOrDie())
diff --git a/ui/views/view.cc b/ui/views/view.cc
index 6bb0a4e..56234161 100644
--- a/ui/views/view.cc
+++ b/ui/views/view.cc
@@ -13,6 +13,7 @@
 #include "base/command_line.h"
 #include "base/containers/adapters.h"
 #include "base/containers/contains.h"
+#include "base/debug/dump_without_crashing.h"
 #include "base/feature_list.h"
 #include "base/i18n/rtl.h"
 #include "base/logging.h"
@@ -1240,9 +1241,9 @@
   if (widget)
     return widget->GetNativeTheme();
 
-  // CHECK here to ensure we catch fallthrough to the global NativeTheme
+  // Crash dump here to ensure we catch fallthrough to the global NativeTheme
   // instance on all Chromium builds (crbug.com/1056756).
-  CHECK(false);
+  base::debug::DumpWithoutCrashing();
 
   return ui::NativeTheme::GetInstanceForNativeUi();
 }
diff --git a/ui/views/widget/widget.cc b/ui/views/widget/widget.cc
index 37047c6..892f47b 100644
--- a/ui/views/widget/widget.cc
+++ b/ui/views/widget/widget.cc
@@ -877,7 +877,10 @@
            : ui::ColorProviderManager::ColorMode::kLight,
        (color_scheme == ui::NativeTheme::ColorScheme::kPlatformHighContrast)
            ? ui::ColorProviderManager::ContrastMode::kHigh
-           : ui::ColorProviderManager::ContrastMode::kNormal});
+           : ui::ColorProviderManager::ContrastMode::kNormal,
+       GetNativeTheme()->is_custom_system_theme()
+           ? ui::ColorProviderManager::SystemTheme::kCustom
+           : ui::ColorProviderManager::SystemTheme::kDefault});
 }
 
 FocusManager* Widget::GetFocusManager() {
diff --git a/ui/webui/resources/BUILD.gn b/ui/webui/resources/BUILD.gn
index 4b155ecd..09524c9a 100644
--- a/ui/webui/resources/BUILD.gn
+++ b/ui/webui/resources/BUILD.gn
@@ -148,6 +148,7 @@
   "cr_elements/cr_radio_group/cr_radio_group.m.d.ts",
   "cr_elements/cr_scrollable_behavior.m.d.ts",
   "cr_elements/cr_search_field/cr_search_field_behavior.d.ts",
+  "cr_elements/cr_search_field/cr_search_field.d.ts",
   "cr_elements/cr_toast/cr_toast_manager.d.ts",
   "cr_elements/cr_toast/cr_toast.d.ts",
   "cr_elements/cr_toggle/cr_toggle.m.d.ts",
@@ -278,7 +279,6 @@
     "cr_elements/cr_profile_avatar_selector/cr_profile_avatar_selector_grid.js",
     "cr_elements/cr_profile_avatar_selector/cr_profile_avatar_selector.js",
     "cr_elements/cr_radio_button/cr_radio_button_style_css.m.js",
-    "cr_elements/cr_search_field/cr_search_field.js",
     "cr_elements/cr_slider/cr_slider.js",
     "cr_elements/cr_splitter/cr_splitter.js",
     "cr_elements/cr_tabs/cr_tabs.js",
diff --git a/ui/webui/resources/cr_elements/cr_search_field/cr_search_field.d.ts b/ui/webui/resources/cr_elements/cr_search_field/cr_search_field.d.ts
new file mode 100644
index 0000000..3df43de
--- /dev/null
+++ b/ui/webui/resources/cr_elements/cr_search_field/cr_search_field.d.ts
@@ -0,0 +1,17 @@
+// 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 {CrSearchFieldBehavior} from './cr_search_field_behavior.js';
+
+interface CrSearchFieldElement extends CrSearchFieldBehavior, HTMLElement {
+  autofocus: boolean;
+}
+
+export {CrSearchFieldElement};
+
+declare global {
+  interface HTMLElementTagNameMap {
+    'cr-search-field': CrSearchFieldElement;
+  }
+}
diff --git a/weblayer/browser/android/metrics/weblayer_metrics_service_client.cc b/weblayer/browser/android/metrics/weblayer_metrics_service_client.cc
index d31f4db4..9b99162 100644
--- a/weblayer/browser/android/metrics/weblayer_metrics_service_client.cc
+++ b/weblayer/browser/android/metrics/weblayer_metrics_service_client.cc
@@ -9,10 +9,13 @@
 #include <memory>
 
 #include "base/base64.h"
+#include "base/files/file_path.h"
 #include "base/no_destructor.h"
+#include "base/path_service.h"
 #include "components/metrics/metrics_provider.h"
 #include "components/metrics/metrics_service.h"
 #include "components/page_load_metrics/browser/metrics_web_contents_observer.h"
+#include "components/prefs/pref_service.h"
 #include "components/ukm/ukm_service.h"
 #include "components/variations/variations_ids_provider.h"
 #include "components/version_info/android/channel_getter.h"
@@ -23,9 +26,9 @@
 #include "weblayer/browser/java/jni/MetricsServiceClient_jni.h"
 #include "weblayer/browser/system_network_context_manager.h"
 #include "weblayer/browser/tab_impl.h"
+#include "weblayer/common/weblayer_paths.h"
 
 namespace weblayer {
-
 namespace {
 
 // IMPORTANT: DO NOT CHANGE sample rates without first ensuring the Chrome
@@ -104,6 +107,12 @@
       variations::SyntheticTrialRegistry::kOverrideExistingIds);
 }
 
+void WebLayerMetricsServiceClient::Initialize(PrefService* pref_service) {
+  base::FilePath user_data_dir;
+  base::PathService::Get(DIR_USER_DATA, &user_data_dir);
+  AndroidMetricsServiceClient::Initialize(user_data_dir, pref_service);
+}
+
 int32_t WebLayerMetricsServiceClient::GetProduct() {
   return metrics::ChromeUserMetricsExtension::ANDROID_WEBLAYER;
 }
diff --git a/weblayer/browser/android/metrics/weblayer_metrics_service_client.h b/weblayer/browser/android/metrics/weblayer_metrics_service_client.h
index 6dfab89..9db4c21f 100644
--- a/weblayer/browser/android/metrics/weblayer_metrics_service_client.h
+++ b/weblayer/browser/android/metrics/weblayer_metrics_service_client.h
@@ -20,6 +20,8 @@
 #include "weblayer/browser/browser_list_observer.h"
 #include "weblayer/browser/profile_impl.h"
 
+class PrefService;
+
 namespace weblayer {
 
 class WebLayerMetricsServiceClient
@@ -36,6 +38,9 @@
 
   void RegisterExternalExperiments(const std::vector<int>& experiment_ids);
 
+  // Initializes, but does not necessarily start, the MetricsService.
+  void Initialize(PrefService* pref_service);
+
   // metrics::MetricsServiceClient
   int32_t GetProduct() override;
   bool IsExternalExperimentAllowlistEnabled() override;
diff --git a/weblayer/browser/feature_list_creator.cc b/weblayer/browser/feature_list_creator.cc
index dbd12af..23c59748 100644
--- a/weblayer/browser/feature_list_creator.cc
+++ b/weblayer/browser/feature_list_creator.cc
@@ -6,7 +6,6 @@
 
 #include "base/base_switches.h"
 #include "base/debug/leak_annotations.h"
-#include "base/path_service.h"
 #include "build/build_config.h"
 #include "cc/base/switches.h"
 #include "components/metrics/metrics_state_manager.h"